refactor: prepare client init for upstream DoH servers (#10851)

In order to support multiple different protocols of upstream DNS
resolvers, we deprecate the `upstream_dns` field in the client's `init`
message and introduce two new fields:

- `upstream_do53`
- `upstream_doh`

For now, only `upstream_do53` is populated and `upstream_doh` is always
empty.

On the client-side, we for now only introduce the `upstream_do53` field
but fall-back to `upstream_dns` if that one is empty. This makes this PR
backwards-compatible with the portal version that is currently deployed
in production. Thus, this PR can be merged even prior to deploying the
portal.

Internally, we prepare connlib's abstractions to deal with different
kinds of upstreams by renaming all existing "upstream DNS" references to
`upstream_do53`: DNS over port 53. That includes UDP as well as TCP DNS
resolution.

Resolves: #10791

---------

Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
Thomas Eizinger
2025-11-12 16:40:58 +11:00
committed by GitHub
parent 4bd768aed5
commit cd650de1f8
12 changed files with 147 additions and 95 deletions

View File

@@ -1,20 +1,36 @@
defmodule API.Client.Views.Interface do
alias Domain.{Accounts, Clients}
alias Domain.Clients
def render(%Clients.Client{} = client) do
clients_upstream_dns = Map.get(client.account.config, :clients_upstream_dns, [])
# TODO: DOH RESOLVERS
# Remove this old field once clients are upgraded.
# old field - append normalized port
upstream_dns =
client.account.config
|> Map.get(:clients_upstream_dns, [])
|> Enum.map(fn dns_config ->
address = Accounts.Config.Changeset.normalize_dns_address(dns_config)
Map.from_struct(%{dns_config | address: address})
clients_upstream_dns
|> Enum.map(fn %{address: address} = dns_config ->
ip = URI.parse("//" <> address).host
Map.from_struct(%{dns_config | address: "#{ip}:53"})
end)
# new field - no port
upstream_do53 =
clients_upstream_dns
|> Enum.map(fn %{address: address} ->
%{ip: URI.parse("//" <> address).host}
end)
%{
search_domain: client.account.config.search_domain,
upstream_dns: upstream_dns,
upstream_do53: upstream_do53,
# Populate from DB once present.
upstream_doh: [],
ipv4: client.ipv4,
ipv6: client.ipv6
ipv6: client.ipv6,
# Legacy field
upstream_dns: upstream_dns
}
end
end

View File

@@ -351,6 +351,11 @@ defmodule API.Client.ChannelTest do
%{protocol: :ip_port, address: "1.1.1.1:53"},
%{protocol: :ip_port, address: "8.8.8.8:53"}
],
upstream_do53: [
%{ip: "1.1.1.1"},
%{ip: "8.8.8.8"}
],
upstream_doh: [],
search_domain: "example.com"
}
end
@@ -909,7 +914,12 @@ defmodule API.Client.ChannelTest do
upstream_dns: [
%{address: "1.1.1.1:53", protocol: :ip_port},
%{address: "8.8.8.8:53", protocol: :ip_port}
]
],
upstream_do53: [
%{ip: "1.1.1.1"},
%{ip: "8.8.8.8"}
],
upstream_doh: []
}
}
end

View File

@@ -64,22 +64,6 @@ defmodule Domain.Accounts.Config.Changeset do
end
end
def normalize_dns_address(%Config.ClientsUpstreamDNS{protocol: :ip_port, address: address}) do
case IPPort.cast(address) do
{:ok, address} ->
address
|> IPPort.put_default_port(@default_dns_port)
|> to_string()
_ ->
address
end
end
def normalize_dns_address(%Config.ClientsUpstreamDNS{address: address}) do
address
end
def client_upstream_dns_changeset(client_upstream_dns \\ %Config.ClientsUpstreamDNS{}, attrs) do
client_upstream_dns
|> cast(attrs, [:protocol, :address])
@@ -130,4 +114,20 @@ defmodule Domain.Accounts.Config.Changeset do
|> cast_embed(:outdated_gateway, with: &Config.Notifications.Email.Changeset.changeset/2)
|> cast_embed(:idp_sync_error, with: &Config.Notifications.Email.Changeset.changeset/2)
end
defp normalize_dns_address(%Config.ClientsUpstreamDNS{protocol: :ip_port, address: address}) do
case IPPort.cast(address) do
{:ok, address} ->
address
|> IPPort.put_default_port(@default_dns_port)
|> to_string()
_ ->
address
end
end
defp normalize_dns_address(%Config.ClientsUpstreamDNS{address: address}) do
address
end
end