Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/debug-messages.dox
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ID Message gist
12 Shutdown: Basic authentication.
13 Accepting ... connections at ...
14 Closing HTTP(S) port ...
15 Adding nameserver ... from squid.conf
15 Added nameserver # ... ( ...) from ... ...
16 DNS IPv6 socket created at ..., FD ...
17 Open FD ... ...
18 Loading cache_dir # ... from ...
Expand Down
14 changes: 13 additions & 1 deletion src/cf.data.pre
Original file line number Diff line number Diff line change
Expand Up @@ -10177,7 +10177,19 @@ DOC_START
taken from the Windows registry, both static and dynamic DHCP
configurations are supported.

Example: dns_nameservers 10.0.0.1 192.172.0.4
When a domain or hostname is used instead of IP address the
OS default resolver is used to lookup its first IP address.
Secondary IPs for nameservers are not yet supported.

NOTICE: The /etc/resolv.conf sortlist feature is not supported.
Name servers are contacted in the order given by this
directive with first configured treated as primary server.
Each use of this directive appends new name servers to the
list of known DNS servers.

Example:
dns_nameservers 10.0.0.1 fe80::1 [fe80::2]:54
dns_nameservers example.com:80 localhost
DOC_END

NAME: hosts_file
Expand Down
116 changes: 77 additions & 39 deletions src/dns_internal.cc
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we make this code more complex, I recommend changing/fixing this code error handling so that configuration errors trigger an exception (and, hence, become fatal at startup). That change should be done in a dedicated prerequisite PR. It will affect how this code is structured and called/used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setup should be done by the Dns::ConfigRr runner. However that refactoring project for startup runners is stuck awaiting reviewer on a number of prerequisites already.

For now this PR keeps the status-quo behaviour when receiving invalid configurations: log and ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have done the Dns::ConfigRr changes in PR #2378. Order of merge does not matter for these two.

Have been experimenting today with what is needed to parse the dns_nameservers and validate immediately instead of just storing for later validation. I am not seeing any particular reason why that change cannot be done as a followup - there is only a few lines of overlap with this PR.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "ip/tools.h"
#include "MemBuf.h"
#include "mgr/Registration.h"
#include "parser/Tokenizer.h"
#include "snmp_agent.h"
#include "SquidConfig.h"
#include "Store.h"
Expand Down Expand Up @@ -252,7 +253,7 @@ static int max_shared_edns = RFC1035_DEFAULT_PACKET_SZ;
#endif

static OBJH idnsStats;
static void idnsAddNameserver(const char *buf);
static void idnsAddNameserver(const SBuf &ns, const SBuf &origin);
static void idnsAddMDNSNameservers();
static void idnsAddPathComponent(const char *buf);
static void idnsFreeSearchpath(void);
Expand Down Expand Up @@ -304,27 +305,73 @@ idnsAddMDNSNameservers()
return;

// mDNS resolver addresses are explicit multicast group IPs
static const SBuf origin("mDNS specification");
if (Ip::EnableIpv6) {
idnsAddNameserver("FF02::FB");
nameservers.back().S.port(5353);
static const SBuf mDnsIp6("[FF02::FB]:5353");
idnsAddNameserver(mDnsIp6, origin);
nameservers.back().mDNSResolver = true;
++nns_mdns_count;
}

idnsAddNameserver("224.0.0.251");
nameservers.back().S.port(5353);
static const SBuf mDnsIp4("224.0.0.251:5353");
idnsAddNameserver(mDnsIp4, origin);
nameservers.back().mDNSResolver = true;

++nns_mdns_count;
}

static void
idnsAddNameserver(const char *buf)
idnsAddNameserver(const SBuf &buf, const SBuf &origin)
{
Ip::Address A;

if (!(A = buf)) {
debugs(78, DBG_CRITICAL, "WARNING: rejecting '" << buf << "' as a name server, because it is not a numeric IP address");
try {
static const CharacterSet hostChars = CharacterSet("host", "._-") + CharacterSet::ALPHA + CharacterSet::DIGIT;
static const CharacterSet ip6Chars = CharacterSet("ip6", ":") + CharacterSet::HEXDIG;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not add a yet another code snippet parsing host[:port].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no choice at the moment, the other parsers we have a) do not check the requirements properly, or b) do not parse all the valid cases, or c) are hidden privately inside code that cannot be made a dependency of DNS startup.
The refactor project to update the AnyP::Uri interface for a generically useful parser are stuck awaiting reviewer.

Parser::Tokenizer tok(buf);
SBuf foundHost;
if (tok.prefix(foundHost, hostChars)) {
debugs(78, 5, "found host name or IPv4: " << foundHost);
} else if (tok.prefix(foundHost, ip6Chars) && tok.remaining().isEmpty() /* no port allowed */) {
debugs(78, 5, "found IPv6 address: " << foundHost);
} else if (tok.skip('[')) {
// Squid extension for servers with custom ports needs to handle bracketed IPv6
if (tok.prefix(foundHost, ip6Chars) && tok.skip(']'))
debugs(78, 5, "found IPv6 with brackets: " << foundHost);
else
throw TextException("invalid IP address", Here());
} else {
throw TextException("missing or invalid host/IP", Here());
}

// Squid extension for servers with custom ports
int64_t foundPort = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use std::optional<AnyP::KnownPort> instead of a magic number to handle unspecified ports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And how exactly do you propose we access the non-existent value inside this std::optional container in order to pass it to the tokenizer for initialization?
I see your approach in AnyP::Uri::parsePort() uses a temporary local with magic number rawPort = 0 as well in exactly this same way as here.

if (tok.skip(':')) {
if (tok.int64(foundPort, 10, false)) {
if (foundPort <= 0 || foundPort > std::numeric_limits<uint16_t>::max())
throw TextException("invalid port number", Here());
debugs(78, 5, "found port number: " << foundPort);
} else
throw TextException("missing or invalid port", Here());
}

if (!tok.remaining().isEmpty())
throw TextException("garbage after host:port", Here());

// TODO: support host/FQDN that resolves to multiple IPs
// TODO: support configured append_domain search for non-IPs

// XXX: performance regression. c_str() copies
if (!A.GetHostByName(foundHost.c_str()))
throw TextException("invalid IP, or unknown hostname", Here());
A.port(foundPort != 0 ? foundPort : NS_DEFAULTPORT);

if (!Ip::EnableIpv6 && !A.setIPv4())
throw TextException("IPv6 is disabled", Here());

} catch (...) {
debugs(78, DBG_CRITICAL, "ERROR: rejecting " << origin << " nameserver '" << buf << "' with " << CurrentException);
return;
}

Expand All @@ -334,19 +381,14 @@ idnsAddNameserver(const char *buf)
debugs(78, DBG_CRITICAL, "Will be using " << A << " instead, assuming you meant that DNS is running on the same machine");
}

if (!Ip::EnableIpv6 && !A.setIPv4()) {
debugs(78, DBG_IMPORTANT, "WARNING: IPv6 is disabled. Discarding " << A << " in DNS server specifications.");
return;
}

auto &nameserver = nameservers.emplace_back(ns());
A.port(NS_DEFAULTPORT);
ns nameserver;
nameserver.S = A;
#if WHEN_EDNS_RESPONSES_ARE_PARSED
nameserver.last_seen_edns = RFC1035_DEFAULT_PACKET_SZ;
// TODO generate a test packet to probe this NS from EDNS size and ability.
#endif
debugs(78, 3, "Added nameserver #" << nameservers.size()-1 << " (" << A << ")");
nameservers.emplace_back(nameserver);
debugs(78, Important(15), "Added nameserver #" << nameservers.size()-1 << " (" << A << ") from " << origin << " " << buf);
}

static void
Expand Down Expand Up @@ -388,10 +430,10 @@ idnsFreeSearchpath(void)
static bool
idnsParseNameservers(void)
{
static const SBuf origin("squid.conf dns_nameservers");
bool result = false;
for (auto &i : Config.dns.nameservers) {
debugs(78, Important(15), "Adding nameserver " << i << " from squid.conf");
idnsAddNameserver(i.c_str());
idnsAddNameserver(i, origin);
result = true;
}
return result;
Expand All @@ -418,15 +460,10 @@ idnsParseResolvConf(void)
if (nullptr == t) {
continue;
} else if (strcmp(t, "nameserver") == 0) {
t = strtok(nullptr, w_space);

if (nullptr == t)
continue;

debugs(78, DBG_IMPORTANT, "Adding nameserver " << t << " from " << _PATH_RESCONF);

idnsAddNameserver(t);
result = true;
if (const auto label = strtok(nullptr, w_space)) {
idnsAddNameserver(SBuf(label), SBuf(_PATH_RESCONF));
result = true;
}
} else if (strcmp(t, "domain") == 0) {
idnsFreeSearchpath();
t = strtok(nullptr, w_space);
Expand Down Expand Up @@ -526,6 +563,9 @@ idnsParseWIN32SearchList(const char * Separator)
static bool
idnsParseWIN32Registry(void)
{
static const SBuf originDhcp("Registry 'DhcpNameServer'");
static const SBuf originNs("Registry 'NameServer'");

char *t;
char *token;
HKEY hndKey, hndKey2;
Expand All @@ -548,9 +588,8 @@ idnsParseWIN32Registry(void)
token = strtok(t, ", ");

while (token) {
idnsAddNameserver(token);
idnsAddNameserver(SBuf(token), originDhcp);
result = true;
debugs(78, DBG_IMPORTANT, "Adding DHCP nameserver " << token << " from Registry");
token = strtok(nullptr, ",");
}
xfree(t);
Expand All @@ -564,8 +603,7 @@ idnsParseWIN32Registry(void)
token = strtok(t, ", ");

while (token) {
debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
idnsAddNameserver(token);
idnsAddNameserver(SBuf(token), originNs);
result = true;
token = strtok(nullptr, ", ");
}
Expand Down Expand Up @@ -618,8 +656,7 @@ idnsParseWIN32Registry(void)
RegQueryValueEx(hndKey2, "DhcpNameServer", nullptr, &Type, (LPBYTE)t, &Size);
token = strtok(t, ", ");
while (token) {
debugs(78, DBG_IMPORTANT, "Adding DHCP nameserver " << token << " from Registry");
idnsAddNameserver(token);
idnsAddNameserver(SBuf(token), originDhcp);
result = true;
token = strtok(nullptr, ", ");
}
Expand All @@ -632,8 +669,7 @@ idnsParseWIN32Registry(void)
RegQueryValueEx(hndKey2, "NameServer", nullptr, &Type, (LPBYTE)t, &Size);
token = strtok(t, ", ");
while (token) {
debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
idnsAddNameserver(token);
idnsAddNameserver(SBuf(token), originNs);
result = true;
token = strtok(nullptr, ", ");
}
Expand Down Expand Up @@ -677,8 +713,7 @@ idnsParseWIN32Registry(void)
token = strtok(t, ", ");

while (token) {
debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
idnsAddNameserver(token);
idnsAddNameserver(SBuf(token), originNs);
result = true;
token = strtok(nullptr, ", ");
}
Expand Down Expand Up @@ -1618,9 +1653,12 @@ Dns::Init(void)
#endif

debugs(78, DBG_IMPORTANT, "or use the 'dns_nameservers' option in squid.conf.");
static const SBuf origin("squid default");
static const SBuf localhost4("127.0.0.1");
static const SBuf localhost6("::1");
if (Ip::EnableIpv6)
idnsAddNameserver("::1");
idnsAddNameserver("127.0.0.1");
idnsAddNameserver(localhost6, origin);
idnsAddNameserver(localhost4, origin);
}

if (!init) {
Expand Down
41 changes: 41 additions & 0 deletions test-suite/squidconf/nameservers-8.0.conf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test for multiple dns_nameservers parameters on the same directive line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8aa7366

Copy link
Contributor

@rousskov rousskov Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please adjust dns_nameservers description in cf.data.pre to avoid the current possible implication that port numbers are not supported and to disclose that domain names are supported (in addition to IP addresses).

It is probably a good idea to also note

  • that domain names specified in dns_nameservers are resolved without using (previously configured) dns_nameservers;
  • how repeated/duplicate dns_nameservers directive entries are interpreted;
  • how multiple dns_nameservers directives are interpreted; and
  • what affect does the order of dns_nameservers` directive entries have.

Edit: GitHub hit this change request while I was writing my review, so I added another, similar one. I deleted the latter after I noticed that this change request was resurrected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in d904ba0

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Copyright (C) 1996-2026 The Squid Software Foundation and contributors
##
## Squid software is distributed under GPLv2+ license and includes
## contributions from numerous individuals and organizations.
## Please see the COPYING and CONTRIBUTORS files for details.
##

#
# This file contains the list of valid nameserver syntax Squid is supposed
# to accept. It covers various uses of:
# IPv4
# IPv6 with and without IPvFuture compatible braces
# Fully Qualified Domain Name (FQDN)
# Hostname label
# Optional port values
#

# IPv4
dns_nameservers 127.0.0.1
dns_nameservers 127.0.0.1:53
dns_nameservers 127.0.0.1:80

# IPv6
dns_nameservers ::1
dns_nameservers [::1]
dns_nameservers [::1]:53
dns_nameservers [::1]:80

# FQDN
dns_nameservers example.com
dns_nameservers example.com:53
dns_nameservers example.com:80

# host name
dns_nameservers localhost
dns_nameservers localhost:53
dns_nameservers localhost:80


# multiple mixed entries per line
dns_nameservers localhost:81 example.com:52 ::7 [::2] 127.0.0.2
Loading