Compare commits

...

66 Commits

Author SHA1 Message Date
Simon Kelley
83658efbf4 Fix occasional crashes with DNSSEC and large nunbers of --address configs.
Commit 3e659bd4ec removed the concept of
an usptream DNS server which is capable of DNSSEC: they are all
(at least in theory) now usable. As a very unfortunate side-effect,
this removed the filter that ensured that dnssec_server() ONLY
returns servers, and not domains with literal addresses.

If we try and do DNSSEC queries for a domain, and there's
a --address line which matches the domain, then dnssec_server()
will return that. This would break DNSSEC validation, but that's
turns out not to matter, because under these circumstances
dnssec_server() will probably return an out-of-bounds index into
the servers[] array, and the process dies with SIGSEGV.

Many thanks to the hard workers at the Tomato project who
found this bug and provided enough information to diagnose it.
2025-04-04 22:01:51 +01:00
Paul Donald
b0b4d90b6a Multiple typo and spelling fixes. 2025-03-29 21:41:40 +00:00
Simon Kelley
bdce03f928 DNAME documentation update. 2025-03-15 17:02:02 +00:00
Simon Kelley
d390dc0338 Implement RFC6672 para 5.3.2. check for DNAME.
Also fix overflow checking of NSEC type maps.
2025-03-15 16:47:55 +00:00
Simon Kelley
105c25e561 Fix DNSSEC and DNAME.
Do the correct things to validate replies which
include a DNAME record.

Thanks to Graham Clinch for pointing this out.
2025-03-15 09:05:47 +00:00
Simon Kelley
67e07b7fe8 Make extract_name() easier to call operating on first name in message. 2025-03-14 15:12:46 +00:00
Simon Kelley
f5659b406b Move find_pseudoheader() before add_edns0_config() in TCP codepath.
There's no point in checking if the query has edns0 headers _after_
adding our own.

This has the affect that if --add-cpe-id or --add-subnet or their friends
are configured,  a query via TCP without EDNS0 will get an answer with EDNS0.

It's highly unlikely that this breaks anything, but it is incorrect.

Thanks to  Tijs Van Buggenhout  for spotting this.
2025-03-14 15:12:46 +00:00
Simon Kelley
484fea238a Silence compiler warning. 2025-03-14 15:12:46 +00:00
Simon Kelley
1e587bec57 Silence compiler warning. 2025-03-14 15:12:45 +00:00
Simon Kelley
581c201aa8 Avoid division by zero with unlucky choices of max-port and min-port. 2025-03-14 15:12:45 +00:00
Simon Kelley
5487f6979e Fix (benign) use of uninitialised data. 2025-03-14 15:12:45 +00:00
Simon Kelley
99f12e3541 Default --dump-mask to all-on, rather than all-off. 2025-03-14 15:12:45 +00:00
Simon Kelley
7c1212e3d1 Fix query-combining for queries with class other than IN.
Along the way, use of extract_request() and extract_name() got further
refined.
2025-03-14 15:12:45 +00:00
Simon Kelley
0ccbdf8087 Make extract_name() easier to call operating on first name in message. 2025-03-14 15:12:45 +00:00
Simon Kelley
57f0489f38 Redesign the interaction between DNSSEC vaildation and per-domain servers.
This should just work in all cases now. If the normal chain-of-trust exists into
the delegated domain then whether the domain is signed or not, DNSSEC
validation will function normally. In the case the delgated domain
is an "overlay" on top of the global DNS and no NS and/or DS records
exist connecting it to the global dns, then if the domain is
unsigned the situation will be handled by synthesising a
proof-of-non-existance-of-DS for the domain and queries will be
answered unvalidated; this action will be logged. A signed domain
without chain-of-trust can be validated if a suitable trust-anchor
is provided using --trust-anchor.

Thanks to Uwe Kleine-König for prompting this change, and contributing
valuable insights into what could be improved.
2025-03-14 15:12:45 +00:00
Simon Kelley
3e659bd4ec Remove the concept of "DNSSEC incapable servers".
We're going to replace this with configured or extrapolated DS records.
2025-03-14 15:12:45 +00:00
Tijs Van Buggenhout
9af15871e6 Fix crash when no upstream servers defined.
This is a regession introduced in 3b6df06fb8.

When dnsmasq is started without upstreams (yet), but a
DNS query comes in that needs forwarding dnsmasq now potentially crashes as
the value for "first" variable is undetermined.

A segmentation violation occurs when the index
is out of bounds of  serverarray.

Credits go to pedro0311 <pedro@freshtomato.org>
2025-03-14 15:09:35 +00:00
Simon Kelley
5897e79d05 Fix bogus compiler warnings. 2025-03-12 15:44:19 +00:00
Simon Kelley
fc9135ca9f Documentation for --do-0x20-encode. 2025-03-09 16:15:16 +00:00
Simon Kelley
e427d4b0e6 Default-off 0x20 encoding and provide --do-0x20-encode option.
For now, this causes too many problems to default on.

Hopefully this will change for future releases.
2025-03-04 12:59:17 +00:00
Simon Kelley
9df1bd0cc1 Revert 368ceff6e0 and fix correct problem.
The next() function is broken for any TFTP packet with padding
which doesn't end with a zero.

Rewrite to handle such packets.

Thanks to Helge Deller <deller@gmx.de> for persisting in finding the
actual problem and proposing a solution. This patch is modelled on his,
but rewritten for personal preference by Simon Kelley, who is
responsible for all bugs.
2025-03-01 22:43:23 +00:00
Simon Kelley
5990074ab0 Fix stupid error in allocating 0x20-flip bitmaps. 2025-02-21 14:41:34 +00:00
Simon Kelley
dbb69bd192 Merge branch 'master' of onyx:dnsmasq/dnsmasq 2025-02-21 13:08:26 +00:00
Simon Kelley
d17581c4c6 Use correct packet length when 0x20 flipping truncated packet.
This makes no difference in practice, since only the query is
operated on, but it is more correct.
2025-02-21 13:02:04 +00:00
Simon Kelley
2c9ed7f425 Fix possible problems with case-encode bigmap array allocation. 2025-02-20 22:59:04 +00:00
Simon Kelley
717ff6adc3 Update plen when getting retried query from stash.
They should be equal, but that depends on untrusted data.
2025-02-10 12:26:15 +00:00
Simon Kelley
f9f8d19bf5 Yet another 0x20 fix.
To complement the previous one, which fixed the retry path
when the query is retried from a different id/source address, this
fixes retries from the same id/source address.
2025-02-09 11:06:59 +00:00
Simon Kelley
535be2f5d3 Fix possible SIGSEGV in bpf.c 2025-02-08 22:58:42 +00:00
Simon Kelley
bceab45dbe Fix 0x20 problem.
A retry to upstream DNS servers triggered by the following conditions

1) A query asking for the same data as a previous query which has not yet been answered.
2) The second query arrives more than two seconds after the first.
3) Either the source of the second query or the id field differs from the first.

fails to set the case of the retry to the same pattern as the first attempt.

However dnsmasq expects the reply from upstream to have the case
pattern of the first attempt.

If the answer to the retry arrives before the answer to the first
query, dnsmasq will notice the case mismatch, log an error, and
ignore the answer.

The worst case scenario would be the first upstream query or reply is
lost and there would follow a short period where all queries for that
particular domain would fail.

This is a 2.91 development issue, it doesn't apply to previous stable releases.
2025-02-07 19:56:33 +00:00
Helge Deller
368ceff6e0 TFTP off-by-2 bugfix
Some of my PA-RISC UNIX machines boot remotely via tftp, but dnsmasq
randomly fails to deliver (the identical file) to some of the machines.

I traced the issue and basically dnsmasq fails with error "unsupported
request from IP.x.y.z" (line 366 in tftp.c).

Here is an example package which is sent (516 hex bytes):
76 6d 6c 69 6e 75 78 00 6f 63 74 65 74 00 12 74 10 3c 00 00 00 00 00 01
a9 24 00 00 00 00 00 00 1e 38 00 00 00 00 00 00 1c a0 00 00 00 00 00 00
1d 08 00 00 00 00 00 00 1d 28 00 00 00 00 00 00 08 00 00 00 00 00 00 00
03 d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1d 30 00 00 00 02 ff e0
00 00 00 00 03 60 a8 49 55 93 00 00 00 01 f0 d4 21 e4 00 00 00 00 00 00
1d 78 00 00 00 f0 f0 d8 51 38 00 00 00 f0 f0 d4 21 c0 00 00 00 00 00 00
00 00 00 00 00 00 00 01 aa b8 00 00 00 f0 f0 e9 62 7c 00 00 00 00 00 00
03 01 ff ff ff ff ff ff 03 00 ff ff ff ff ff ff ff ff 00 00 00 00 00 00
00 03 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 04 ff ff ff ff ff ff
ff ff 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00 00 00
00 05 00 00 00 00 00 00 1e 38 00 00 00 00 00 00 00 60 00 00 00 00 00 01
a6 68 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 ff 00 00 00 00 00 00
00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00
00 00 00 00 00 f0 f0 d8 4f 30 00 00 00 00 00 00 00 01 00 00 00 00 00 00
00 00 00 00 00 00 00 01 ae ec 00 00 00 00 00 00 1f 70 00 00 00 00 00 00
1e b8 00 00 03 60 a8 49 55 93 00 00 00 02 18 71 1a 00 00 00 00 00 00 00
00 03 00 00 00 00 00 00 00 03 00 00 00 00 00 00 1e 38 00 00 00 00 00 00
00 07 00 00 00 00 00 00 00 00 00 00 00 f0 f0 d2 f0 70 00 00 00 00 00 00
1f c0 00 00 00 f0 f0 d4 0b e8 00 00 00 00 00 00 00 01 00 00 00 00 00 00
00 60 ff ff ff fc 00 60 18 00 00 00 00 00 00 00 00 00 00 00 00 f0 f0 d8
8f d0 00 00 00 00 00 00 1f f8 00 00 00 00 00 00 00 00 00 00 00 f0 f0 d8
8d b8 00 00 00 00 00 00 1e e8 00 00

Please note the last 3 bytes: "e8 00 00".
If the 3rd last byte is "00", then dnsmasq works and it fails it it's "e8".

So, the bug is in line 366 of tftp.c:
   filename = next(&p, end)
Here filename gets the value NULL from next(), because the "end" variable is off-by-2.
The fix is to change line 363 to add an offset of 2:
  end = packet + 2 + len;

Signed-off-by: Helge Deller <deller@gmx.de>
Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2293793
2025-02-06 17:04:06 +00:00
Simon Kelley
77c4e95d4a Fix for case-sensitivity problems in DNS.
Fix a case sensitivity problem which has been lurking for a long while.
When we get example.com and Example.com and combine them, we send whichever
query arrives first upstream and then later answer it, and we also
answer the second with the same answer. That means that if example.com
arrives first, it will get the answer example.com - good - but Example.com
will _also_ get the answer example.com - not so good.

In theory, fixing this is simple without having to keep seperate
copies of all the queries: Just use the bit-vector representation
of case flipping that we have for 0x20-encoding to keep the
differences in case. The complication comes from the fact that
the existing bit-vector code only holds data on the first 32 alpha
letters, because we only flip that up to many for 0x20 encoding.

In practise, the delta between combined queries can almost always
be represented with that data, since almost all queries are
all lower case and we only purturb the first 32 letters with
0x20 encoding. It's therefore worth keeping the existing,
efficient data structure for the 99.9% of the time it works.
For the 0.1% it doesn't, however, one needs an arbitrary-length data
structure with the resource implications of that.

Thanks to Peter Tirsek for the well researched bug report which set me
on to these problems.
2025-02-06 17:02:50 +00:00
Simon Kelley
e44165c0f7 Fix bug in 0x20 encoding.
We must only compare case when mapping an answer from upstream
to a forwarding record, not when checking a query to see if it's a
duplicate. Since the saved query name is scrambled, that ensures
that almost all such checks will wrongly fail.

Thanks to Peter Tirsek for an exemplary bug report for this.
2025-02-06 10:36:21 +00:00
Simon Kelley
a1a214c393 Bump date on manpage. 2025-02-05 21:34:54 +00:00
Simon Kelley
94b7144a1b Fix c99ism added in 0b6144583b 2025-02-05 21:02:54 +00:00
Matthias Andree
e72910dec8 Spell check v2.91 CHANGELOG 2025-02-05 20:46:55 +00:00
Simon Kelley
0b6144583b Log failed TCP DNS connections upstream when --log-debug active. 2025-02-05 17:15:52 +00:00
Simon Kelley
f31667317d Manpage typo. 2025-02-05 15:20:31 +00:00
Simon Kelley
5226b712a3 Add --no-0x20-encode config option.
The "bit 0x20 encoding" implemented in 995a16ca0c
can interact badly with (hopefully) rare broken upstream servers. Provide
an option to turn it off and a log message to give a clue as to why DNS service
is non-functional.
2025-02-03 21:02:12 +00:00
Simon Kelley
1f84cde024 Tweak to logging.
When a cached answer is too big, log

cached reply is truncated

and not

config reply is truncated
2025-02-03 15:26:55 +00:00
Paul Donald
046bfa2af0 Clean up some of the man page formatting.
Some writing was improved for clarity, especially regarding the use of
tags which can be confusing and difficult to grasp.
2025-02-01 22:40:54 +00:00
Simon Kelley
0762732647 belt-and-braces extra call to check_log_writer() in tcp_request() 2025-02-01 15:24:24 +00:00
Brian Haley
efb8f10450 Fix potential memory leak
When a new IPv6 address is being added to a dhcp_config
struct, if there is anything invalid regarding the prefix
it looks like there is a potential memory leak.
ret_err_free() should be used to free it.

Also, the new addrlist struct is being linked into
the existing addr6 list in the dhcp_config before the
validity check, it is best to defer this insertion
until later so an invalid entry is not present, since
the CONFIG_ADDR6 flag might not have been set yet.

Signed-off-by: Brian Haley <haleyb.dev@gmail.com>
2025-01-24 23:01:08 +00:00
Simon Kelley
6dbdf16fd1 Move debian submodule to submodules/dnsmasq-debian. 2025-01-24 21:05:43 +00:00
Simon Kelley
6e6a45a7d9 Bump copyrights to 2025. 2025-01-23 17:08:39 +00:00
Simon Kelley
a4569c22cc Correct BNF for --trust-anchor in manpage. 2025-01-20 16:20:13 +00:00
Simon Kelley
199e65c4d9 Remove misleading comment. 2025-01-20 15:55:42 +00:00
Simon Kelley
bb8811d472 Convert DNS names in logs to all lower case.
0x20 encoding makes them look odd, otherwise.
2025-01-20 15:25:26 +00:00
Simon Kelley
995a16ca0c Implement "DNS-0x20 encoding".
This provides extra protection against reply-spoof attacks.

Since DNS queries are case-insensitive, it's possible to randomly flip
the case of letters in a query and still get the correct answer back.
This adds an extra dimension for a cache-poisoning attacker to guess
when sending replies in-the-blind since it's expected that the
legitimate answer will have the same pattern of upper and lower case
as the query, so any replies which don't can be ignored as
malicious.

The amount of extra entropy clearly depends on the number
of a-z and A-Z characters in the query, and this implementation puts a
hard limit of 32 bits to make rescource allocation easy. This about
doubles entropy over the standard random ID and random port
combination.
2025-01-19 21:54:58 +00:00
Simon Kelley
65f9c1aca1 Case-sensitive matching of questions and answers.
When checking that an answer is the answer to the question that
we asked, compare the name in a case-sensitive manner.

Clients can set the letters in a query to a random pattern of
uppercase and lowercase to add more randomness as protection against
cache-poisoning attacks, and we don't want to nullify that.

This actually restores the status quo before
commit ed6d29a784
since matching questions and answers using a checksum
can't help but be case sensitive.

This patch is a preparation for introducing DNS-0x20
in the dnsmasq query path.
2025-01-19 00:08:36 +00:00
Simon Kelley
b72ecb3a59 Fix log message fields in wrong order in some auth replies. 2025-01-18 23:56:23 +00:00
Simon Kelley
c221030f89 Rename cache_validated() to cache_not_validated().
Let's give the poor programmers a chance.
2025-01-18 23:26:06 +00:00
Simon Kelley
5bbea085d0 Fix subtle bug in arbitrary-RR caching.
If the client asks for DNSSEC RRs via the do bit, and
we have an answer cached, we can only return the cached
answer if the RR was not validated. This is because
we don't the extra info (RRSIGS, NSECs) for a complete
validated answer. In that case we have to forward again.

This bug was that the "is the cache entry validated" test was
in an outer loop rather than an inner one. A cache hit on
a different RRtype that wasn't validated would satify the
condition to use the cache, even if the cache entry for
the required RRtype didn't. The only time when there can be a mix
of validated and non validated cache entries for the same domain
is when most are not validated, but one is a negative cache for
a DS record.

This bug took a long time to find.
2025-01-18 23:15:53 +00:00
Simon Kelley
622cf03ab9 Fix fubar that could return unsigned NODATA response when do bit set. 2025-01-18 22:16:29 +00:00
Simon Kelley
8ce27433f8 Handle DS queries to auth zones.
When dnsmasq is configured to act as an authoritative server and has
an authoritative zone configured, and recieves a query for
that zone _as_forwarder_ it answers the query directly rather
than forwarding it. This doesn't affect the answer, but it
saves dnsmasq forwarding the query to the recusor upstream,
whch then bounces it back to dnsmasq in auth mode. The
exception should be when the query is for the root of zone, for a DS
RR. The answer to that has to come from the parent, via the
recursor, and will typically be a proof-of-nonexistence since
dnsmasq doesn't support signed zones. This patch suppresses
local answers and forces forwarding to the upstream recursor
for such queries. It stops breakage when a DNSSEC validating
client makes queries to dnsmasq acting as forwarder for a zone
for which it is authoritative.
2025-01-18 08:57:14 +00:00
Simon Kelley
5d894620b4 Extend build fingerprinting to include CFLAGS.
If the value of CFLAGS is changed between builds, the makefile
will rebuid, in the same way as for COPTS.
2025-01-17 16:48:08 +00:00
Simon Kelley
71766c0c35 Tweak handling of duplicate DNS answers via UDP.
If we get a duplicate answer for a query via UDP which we have
either already received and started DNSSEC validation, or was
truncated and we've passed to TCP, then just ignore it.

The code was already in place, but had evolved wonky and
only worked for error replies which would otherwise prompt
a retransmit.
2025-01-13 20:30:37 +00:00
Simon Kelley
da58455508 Tweak 7d915a0bb9
A downstream query may have gone to TCP, not just DNSSEC queries.
2025-01-13 11:03:30 +00:00
Simon Kelley
b915c9a661 Attempt to keep running if a child process dies.
If a child process dies unexpectedly, log the error and
try and tidy up so the main process continues to run and
doesn't block awaiting the dead child.
2025-01-13 10:56:19 +00:00
Simon Kelley
424aaa0f9d Fix another 509afcd1d2 SNAFU 2025-01-13 10:32:55 +00:00
Andrew Sayers
c72c895869 Improve "no upstream servers configured" when D-Bus is enabled
Print a specific INFO message instead of a generic WARNING message,
so users know what to do.

Starting dnsmasq without upstream servers indicates a problem by default,
but is perfectly normal with D-Bus enabled.  For example, NetworkManager
starts dnsmasq with no upstream servers, then immediately populates it
over D-Bus.
2025-01-12 22:32:32 +00:00
Simon Kelley
b7156116c2 Fix SNAFU in 509afcd1d2 2025-01-12 22:28:12 +00:00
Simon Kelley
7d915a0bb9 Don't do retries over UDP when we've sent the query by TCP. 2025-01-12 22:02:05 +00:00
Simon Kelley
509afcd1d2 Refactor poll() loop.
Handling events on file descriptors can result in new file
descriptors being created or old ones being deleted. As such
the results of the last call to poll() become invalid in subtle
ways.

After handling each file descriptor in  check_dns_listeners()
return, to go around the poll() loop again and get valid data
for the new situation.

Thanks to Dominik Derigs for his indefatigable sleuthing of this one.
2025-01-12 21:36:09 +00:00
Simon Kelley
51343bd9a2 Treat replies with CD flag set the same for UDP and TCP code paths. 2025-01-12 16:25:07 +00:00
Simon Kelley
b58276a73c Return EDE OTHER error when DNSSEC validation abandoned.
This distinguishes the case where we found a message was bogus
from cases where the process failed.
2025-01-12 16:00:09 +00:00
Matthias Andree
f162d344c0 cache: Fix potential NULL deref in arcane situations. 2025-01-08 23:34:12 +00:00
54 changed files with 1531 additions and 945 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "debian"]
path = debian
url = git://thekelleys.org.uk/dnsmasq-debian.git
[submodule "submodules/dnsmasq-debian"]
path = submodules/dnsmasq-debian
url = https://thekelleys.org.uk/git/dnsmasq-debian

100
CHANGELOG
View File

@@ -1,3 +1,25 @@
version 2.92
Redesign the interaction between DNSSEC validation and per-domain
servers, specified as --server=/<domain>/<ip-address>. This should
just work in all cases now. If the normal chain-of-trust exists into
the delegated domain then whether the domain is signed or not, DNSSEC
validation will function normally. In the case the delegated domain
is an "overlay" on top of the global DNS and no NS and/or DS records
exist connecting it to the global dns, then if the domain is
unsigned the situation will be handled by synthesising a
proof-of-non-existence-of-DS for the domain and queries will be
answered unvalidated; this action will be logged. A signed domain
without chain-of-trust can be validated if a suitable trust-anchor
is provided using --trust-anchor. This change should be backwards
compatible for all existing working configurations; it extends the
space of possible configurations which are functional.
Fix a couple of problems with DNSSEC validation and DNAME. One
could cause validation failure on correct domains, and the other
would fail to spot an invalid domain. Thanks to Graham Clinch
for spotting the problem.
version 2.91
Fix spurious "resource limit exceeded messages". Thanks to
Dominik Derigs for the bug report.
@@ -7,9 +29,9 @@ version 2.91
Literal address records are smaller and don't have
this field and don't need to be ordered on it.
To actually provoke this bug seems to need the same server-literal
to be repeated twice, eg --address=/a/1.1.1.1 --address-/a/1.1.1.1
to be repeated twice, e.g., --address=/a/1.1.1.1 --address-/a/1.1.1.1
which is clearly rare in the wild, but if it did exist it could
provoke a SIGSEV. Thanks to Daniel Rhea for fuzzing this one.
provoke a SIGSEGV. Thanks to Daniel Rhea for fuzzing this one.
Fix buffer overflow when configured lease-change script name
is too long.
@@ -17,7 +39,7 @@ version 2.91
Improve behaviour in the face of non-responsive upstream TCP DNS
servers. Without shorter timeouts, clients are blocked for too long
and fail wuth their own timeouts.
and fail with their own timeouts.
Set --fast-dns-retries by default when doing DNSSEC. A single
downstream query can trigger many upstream queries. On an
@@ -36,7 +58,7 @@ version 2.91
empty answer.
Fix handling of EDNS0 UDP packet sizes.
When talking upstream we always add a pseudoheader, and set the
When talking upstream we always add a pseudo header, and set the
UDP packet size to --edns-packet-max. Answering queries from
downstream, we get the answer (either from upstream or local
data) If local data won't fit the advertised size (or 512 if
@@ -82,6 +104,50 @@ version 2.91
to queries which arrive a dnsmasq already carrying an EDNS client
subnet.
Handle DS queries to auth zones. When dnsmasq is configured to
act as an authoritative server and has an authoritative zone
configured, and receives a query for that zone _as_forwarder_
it answers the query directly rather than forwarding it. This
doesn't affect the answer, but it saves dnsmasq forwarding the
query to the recursor upstream, which then bounces it back to dnsmasq
in auth mode. The exception should be when the query is for the root
of zone, for a DS RR. The answer to that has to come from the parent,
via the recursor, and will typically be a proof-of-non-existence
since dnsmasq doesn't support signed zones. This patch suppresses
local answers and forces forwarding to the upstream recursor for such
queries. It stops breakage when a DNSSEC validating client makes
queries to dnsmasq acting as forwarder for a zone for which it is
authoritative.
Implement "DNS-0x20 encoding", for extra protection against
reply-spoof attacks. Since DNS queries are case-insensitive,
it's possible to randomly flip the case of letters in a query
and still get the correct answer back.
This adds an extra dimension for a cache-poisoning attacker
to guess when sending replies in-the-blind since it's expected
that the legitimate answer will have the same pattern of upper
and lower case as the query, so any replies which don't can be
ignored as malicious. The amount of extra entropy clearly depends
on the number of a-z and A-Z characters in the query, and this
implementation puts a hard limit of 32 bits to make resource
allocation easy. This about doubles entropy over the standard
random ID and random port combination. This technique can interact
badly with rare broken DNS servers which don't preserve the case
of the query in their reply. The first time a reply is returned
which matches the query in all respects except case, a warning
will be logged. In this release, 0x020-encoding is default-off
and must be explicitly enabled with --do-0x20-encoding. In future
releases it may default on. You can avoid a future release
changing the behaviour of an installation with --no-x20-encode.
Fix a long-standing problem when two queries which are identical
in every respect _except_ case, get combined by dnsmasq. If
dnsmasq gets eg, two queries for example.com and Example.com
in quick succession it will get the answer for example.com from
upstream and send that answer to both requestors. This means that
the query for Example.com will get an answer for example.com, and
in the modern DNS, that answer may not be accepted.
version 2.90
Fix reversion in --rev-server introduced in 2.88 which
@@ -93,7 +159,7 @@ version 2.90
for a particular domain. Thanks to Daniel Danzberger for
spotting this bug.
Set the default maximum DNS UDP packet sice to 1232. This
Set the default maximum DNS UDP packet size to 1232. This
has been the recommended value since 2020 because it's the
largest value that avoid fragmentation, and fragmentation
is just not reliable on the modern internet, especially
@@ -101,14 +167,14 @@ version 2.90
--edns-packet-max for special circumstances.
Add --no-dhcpv4-interface and --no-dhcpv6-interface for
better control over which inetrfaces are providing DHCP service.
better control over which interfaces are providing DHCP service.
Fix issue with stale caching: After replying with stale data,
dnsmasq sends the query upstream to refresh the cache asynchronously
and sometimes sends the wrong packet: packet length can be wrong,
and if an EDE marking stale data is added to the answer that can
end up in the query also. This bug only seems to cause problems
when the usptream server is a DOH/DOT proxy. Thanks to Justin He
when the upstream server is a DOH/DOT proxy. Thanks to Justin He
for the bug report.
Add configurable caching for arbitrary RR-types.
@@ -146,7 +212,7 @@ version 2.90
Applied Cybersecurity ATHENE for finding this vulnerability.
CVE 2023-50387 and CVE 2023-50868 apply.
Note that the is a security vulnerablity only when DNSSEC validation
Note that this a security vulnerability only when DNSSEC validation
is enabled.
Fix memory-leak when attempting to cache SRV records with zero TTL.
@@ -232,7 +298,7 @@ version 2.88
upstream servers from /etc/resolv.conf or other sources that
can change dnsmasq tries to avoid memory fragmentation by re-using
existing records that are being re-read unchanged. This involves
seaching all the server records for each new one installed.
searching all the server records for each new one installed.
During startup this search is pointless, and can cause long
start times with thousands of --server options because the work
needed is O(n^2). Handle this case more intelligently.
@@ -295,7 +361,7 @@ version 2.87
Enhance --domain to accept, for instance,
--domain=net2.thekelleys.org.uk,eth2 so that hosts get a domain
which relects the interface they are attached to in a way which
which reflects the interface they are attached to in a way which
doesn't require hard-coding addresses. Thanks to Sten Spans for
the idea.
@@ -669,22 +735,22 @@ version 2.80
but those which used the default of no checking will need to be
altered to explicitly select no checking. The new default is
because switching off checking for unsigned replies is
inherently dangerous. Not only does it open the possiblity of forged
inherently dangerous. Not only does it open the possibility of forged
replies, but it allows everything to appear to be working even
when the upstream namesevers do not support DNSSEC, and in this
case no DNSSEC validation at all is occuring.
case no DNSSEC validation at all is occurring.
Fix DHCP broken-ness when --no-ping AND --dhcp-sequential-ip
are set. Thanks to Daniel Miess for help with this.
Add a facilty to store DNS packets sent/recieved in a
Add a facility to store DNS packets sent/received in a
pcap-format file for later debugging. The file location
is given by the --dumpfile option, and a bitmap controlling
which packets should be dumped is given by the --dumpmask
option.
Handle the case of both standard and constructed dhcp-ranges on the
same interface better. We don't now contruct a dhcp-range if there's
same interface better. We don't now construct a dhcp-range if there's
already one specified. This allows the specified interface to
have different parameters and avoids advertising the same
prefix twice. Thanks to Luis Marsano for spotting this case.
@@ -1154,7 +1220,7 @@ version 2.73
Use inotify for checking on updates to /etc/resolv.conf and
friends under Linux. This fixes race conditions when the files are
updated rapidly and saves CPU by noy polling. To build
updated rapidly and saves CPU by not polling. To build
a binary that runs on old Linux kernels without inotify,
use make COPTS=-DNO_INOTIFY
@@ -1494,7 +1560,7 @@ version 2.68
are dynamic and works much better than the previous
work-around which exempted constructed DHCP ranges from the
IP address filtering. As a consequence, that work-around
is removed. Under certain circumstances, this change wil
is removed. Under certain circumstances, this change will
break existing configuration: if you're relying on the
constructed-range exception, you need to change --auth-zone
to specify the same interface as is used to construct your
@@ -1951,7 +2017,7 @@ version 2.61
Set the environment variable DNSMASQ_LOG_DHCP when running
the script id --log-dhcp is in effect, so that script can
taylor their logging verbosity. Suggestion from Malte
tailor their logging verbosity. Suggestion from Malte
Forkel.
Arrange that addresses specified with --listen-address

View File

@@ -1,4 +1,4 @@
# dnsmasq is Copyright (c) 2000-2024 Simon Kelley
# dnsmasq is Copyright (c) 2000-2025 Simon Kelley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -71,8 +71,8 @@ nft_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG
nft_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG) --libs libnftables`
version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
sum?=$(shell $(CC) -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' ')
sum!=$(CC) -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' '
sum?=$(shell echo $(CC) -DDNSMASQ_COMPILE_FLAGS="$(CFLAGS)" -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' ')
sum!=echo $(CC) -DDNSMASQ_COMPILE_FLAGS="$(CFLAGS)" -DDNSMASQ_COMPILE_OPTS $(COPTS) -E $(top)/$(SRC)/dnsmasq.h | ( md5sum 2>/dev/null || md5 ) | cut -f 1 -d ' '
copts_conf = .copts_$(sum)
objs = cache.o rfc1035.o util.o option.o forward.o network.o \

1
debian

Submodule debian deleted from 83e05da879

1
debian Symbolic link
View File

@@ -0,0 +1 @@
submodules/dnsmasq-debian/debian

View File

@@ -1,4 +1,4 @@
.TH DNSMASQ 8 2021-08-16
.TH DNSMASQ 8 2025-02-05
.SH NAME
dnsmasq \- A lightweight DHCP and caching DNS server.
.SH SYNOPSIS
@@ -498,10 +498,7 @@ xxx.internal.thekelleys.org.uk at 192.168.1.1 then giving the flag
.B --server=/internal.thekelleys.org.uk/192.168.1.1
will send all queries for
internal machines to that nameserver, everything else will go to the
servers in /etc/resolv.conf. DNSSEC validation is turned off for such
private nameservers, UNLESS a
.B --trust-anchor
is specified for the domain in question. An empty domain specification,
servers in /etc/resolv.conf. An empty domain specification,
.B //
has the special meaning of "unqualified names only" ie names without any
dots in them. A non-standard port may be specified as
@@ -852,6 +849,18 @@ name on successive queries, for load-balancing. This turns off that
behaviour, so that the records are always returned in the order
that they are received from upstream.
.TP
.B --do-0x20-encode, --no-0x20-encode
Dnsmasq can scramble the case of letters in DNS queries it sends upstream as a security feature.
This technique can interact badly with rare broken DNS servers which don't preserve the case
of the query in their reply. The first time a reply is returned
which matches the query in all respects except case, a warning
will be logged. If this coincides with DNS not functioning, it
is necessary to disable the feature. As at version 2.91, 0x20 encoding
is disabled by default, and must be enabled with --do-0x20-encode. The default
may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the
data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense
@@ -864,7 +873,7 @@ Set the maximum number of concurrent DNS queries. The default value is
150, which should be fine for most setups. The only known situation
where this needs to be increased is when using web-server log file
resolvers, which can generate large numbers of concurrent queries. This
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has it's own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has its own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
.TP
.B --dnssec
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
@@ -882,12 +891,15 @@ ie capable of returning DNSSEC records with data. If they are not,
then dnsmasq will not be able to determine the trusted status of
answers and this means that DNS service will be entirely broken.
.TP
.B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
.B --trust-anchor=<domain>,[<class>,][<key-tag>,<algorithm>,<digest-type>,<digest>]
Provide DS records to act a trust anchors for DNSSEC
validation. Typically these will be the DS record(s) for Key Signing
validation. The class defaults to IN. Typically these will be the DS record(s) for Key Signing
key(s) (KSK) of the root zone,
but trust anchors for limited domains are also possible. The current
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
but trust anchors for limited domains are also possible.
A negative trust anchor (ie. proof that a DS record doesn't exist) may be configured be specifying
only the name or only the name and class. This can be useful for forcing dnsmasq to treat zones delegated
using \fB--server=/<domain>/<ip-address>\fP as unsigned. The current
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
.TP
.B --dnssec-check-unsigned[=no]
As a default, dnsmasq checks that unsigned DNS replies are
@@ -1188,7 +1200,7 @@ available, though it normally is for direct-connected clients, or
clients using DHCP relays which support RFC 6939.
For DHCPv4, the special option id:* means "ignore any client-id
For DHCPv4, the special option \fBid:*\fP means "ignore any client-id
and use MAC addresses only." This is useful when a client presents a client-id sometimes
but not others.
@@ -1216,36 +1228,34 @@ the result is undefined. A corollary to this is that the name associated with a
does not appear in the DNS until the host obtains a DHCP lease.
The special keyword "ignore"
tells dnsmasq to never offer a DHCP lease to a machine. The machine
can be specified by hardware address, client ID or hostname, for
instance
.B --dhcp-host=00:20:e0:3b:13:af,ignore
This is
useful when there is another DHCP server on the network which should
The special keyword \fBignore\fP tells dnsmasq never to offer a DHCP lease
to a machine. The machine
can be specified by hardware address, client ID or hostname. For
example: \fB--dhcp-host=00:20:e0:3b:13:af,ignore\fP.
This can be useful when there is another DHCP server on the network which should
be used by some machines.
The set:<tag> construct sets the tag
The \fBset:<tag>\fP construct sets the tag
whenever this \fB--dhcp-host\fP directive is in use. This can be used to
selectively send DHCP options just for this host. More than one tag
can be set in a \fB--dhcp-host\fP directive (but not in other places where
"set:<tag>" is allowed). When a host matches any
\fBset:<tag>\fP is allowed). When a host matches any
\fB--dhcp-host\fP directive (or one implied by /etc/ethers) then the special
tag "known" is set. This allows dnsmasq to be configured to
tag \fBknown\fP is set. This allows dnsmasq to be configured to
ignore requests from unknown machines using
.B --dhcp-ignore=tag:!known
\fB--dhcp-ignore=tag:!known\fP.
If the host matches only a \fB--dhcp-host\fP directive which cannot
be used because it specifies an address on different subnet, the tag "known-othernet" is set.
be used because it specifies an address on a different subnet, the tag \fBknown-othernet\fP is set.
The tag:<tag> construct filters which dhcp-host directives are used; more than
one can be provided, in this case the request must match all of them. Tagged
The \fBtag:<tag>\fP construct filters which dhcp-host directives are used. More than
one tag can be provided. In this case, the request must match all tags. Tagged
directives are used in preference to untagged ones. Note that one of <hwaddr>,
<client_id> or <hostname> still needs to be specified (can be a wildcard).
<client_id> or <hostname> still must be specified (can be a wildcard).
Ethernet addresses (but not client-ids) may have
wildcard bytes, so for example
.B --dhcp-host=00:20:e0:3b:13:*,ignore
will cause dnsmasq to ignore a range of hardware addresses. Note that
will cause dnsmasq to ignore the given range of hardware addresses. Note that
the "*" will need to be escaped or quoted on a command line, but not
in the configuration file.
@@ -1259,11 +1269,11 @@ is 6.
As a special case, in DHCPv4, it is possible to include more than one
hardware address. eg:
.B --dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2
\fB--dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2\fP.
This allows an IP address to be associated with
multiple hardware addresses, and gives dnsmasq permission to abandon a
DHCP lease to one of the hardware addresses when another one asks for
a lease. Beware that this is a dangerous thing to do, it will only
a lease. Beware that this is a dangerous thing to do: it will only
work reliably if only one of the hardware addresses is active at any
time and there is no way for dnsmasq to enforce this. It is, for instance,
useful to allocate a stable IP address to a laptop which
@@ -1310,27 +1320,25 @@ options containing the same information. /etc/ethers is re-read when
dnsmasq receives SIGHUP. IPv6 addresses are NOT read from /etc/ethers.
.TP
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt-name>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
Specify different or extra options to DHCP clients. By default,
Send various extra options to DHCP clients. By default,
dnsmasq sends some standard options to DHCP clients, the netmask and
broadcast address are set to the same as the host running dnsmasq, and
the DNS server and default route are set to the address of the machine
running dnsmasq. (Equivalent rules apply for IPv6.) If the domain name option has been set, that is sent.
This configuration allows these defaults to be overridden,
or other options specified. The option, to be sent may be given as a
decimal number or as "option:<option-name>" The option numbers are
or other options specified. The option to be sent may be given as a
decimal number or as \fBoption:<option-name>\fP.
Option numbers are
specified in RFC2132 and subsequent RFCs. The set of option-names
known by dnsmasq can be discovered by running "dnsmasq --help dhcp".
For example, to set the default route option to
192.168.4.4, do
.B --dhcp-option=3,192.168.4.4
or
.B --dhcp-option = option:router, 192.168.4.4
and to set the time-server address to 192.168.0.4, do
.B --dhcp-option = 42,192.168.0.4
or
.B --dhcp-option = option:ntp-server, 192.168.0.4
The special address 0.0.0.0 is taken to mean "the address of the
machine running dnsmasq".
known by dnsmasq can be discovered by running \fBdnsmasq --help dhcp\fP.
For example, to set the default route option to 192.168.4.4, use
\fB--dhcp-option=3,192.168.4.4\fP or
\fB--dhcp-option=option:router,192.168.4.4\fP
and to set the time-server address to 192.168.0.4, use
\fB--dhcp-option=42,192.168.0.4\fP or
\fB--dhcp-option=option:ntp-server,192.168.0.4\fP.
The special address 0.0.0.0 means "the address of the system running dnsmasq".
An option without data is valid, and includes just the option without data.
(There is only one option with a zero length data field currently defined for DHCPv4, 80:rapid commit, so this feature is not very useful in practice). Options for which dnsmasq normally
@@ -1351,30 +1359,29 @@ to option 120 are handled as per RFC 3361. Dotted-quad IP addresses
which are followed by a slash and then a netmask size are encoded as
described in RFC 3442.
IPv6 options are specified using the
.B option6:
IPv6 options are specified using the \fBoption6:\fP
keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg.
.B --dhcp-option=option6:ntp-server,[1234::56]
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
For IPv6, [::] means "the global address of
the machine running dnsmasq", whilst [fd00::] is replaced with the
ULA, if it exists, and [fe80::] with the link-local address.
Be careful: no checking is done that the correct type of data for the
option number is sent, it is quite possible to
persuade dnsmasq to generate illegal DHCP packets with injudicious use
of this flag. When the value is a decimal number, dnsmasq must determine how
Be careful: data-type suitability for the option number sent is not checked.
It is quite possible to persuade dnsmasq to generate illegal DHCP packets with
injudicious use of this flag.
When the value is a decimal number, dnsmasq must determine how
large the data item is. It does this by examining the option number and/or the
value, but can be overridden by appending a single letter flag as follows:
b = one byte, s = two bytes, i = four bytes. This is mainly useful with
encapsulated vendor class options (see below) where dnsmasq cannot
determine data size from the option number. Option data which
determine data size from the option number. Option data which
consists solely of periods and digits will be interpreted by dnsmasq
as an IP address, and inserted into an option as such. To force a
literal string, use quotes. For instance when using option 66 to send
a literal IP address as TFTP server name, it is necessary to do
.B --dhcp-option=66,"1.2.3.4"
a literal IP address as TFTP server name, it is necessary to do
\fB--dhcp-option=66,"1.2.3.4"\fP.
Encapsulated Vendor-class options may also be specified (IPv4 only) using
\fB--dhcp-option\fP: for instance
@@ -1403,7 +1410,7 @@ Vendor Options" as specified by RFC3925. These are denoted like this:
The number in the vi-encap: section is the IANA enterprise number
used to identify this option. This form of encapsulation is supported
in IPv6.
The address 0.0.0.0 is not treated specially in
encapsulated options.
.TP
@@ -1529,45 +1536,47 @@ via relays. If a list of IP addresses is given, only interactions via
relays at those addresses are affected.
.TP
.B --dhcp-match=set:<tag>,<option number>|option:<option name>|vi-encap:<enterprise>[,<value>]
Without a value, set the tag if the client sends a DHCP
option of the given number or name. When a value is given, set the tag only if
the option is sent and matches the value. The value may be of the form
Without \fB<value>\fP, set \fB<tag>\fP if the client sends a DHCP
\fBoption\fP of the given number or name. With \fB<value>\fP, set \fB<tag>\fP only if
the client sends the \fBoption\fP matching or containing \fB<value>\fP.
The value may be of the form
"01:ff:*:02" in which case the value must match (apart from wildcards)
but the option sent may have unmatched data past the end of the
value. The value may also be of the same form as in
.B --dhcp-option
in which case the option sent is treated as an array, and one element
must match, so
must match.
.B --dhcp-match=set:efi-ia32,option:client-arch,6
will set the tag "efi-ia32" if the the number 6 appears in the list of
sets the tag \fBefi-ia32\fP if the number \fB6\fP appears in the list of
architectures sent by the client in option 93. (See RFC 4578 for
details.) If the value is a string, substring matching is used.
details.) If the value is a string, substring matching is used.
The special form with vi-encap:<enterprise number> matches against
vendor-identifying vendor classes for the specified enterprise. Please
see RFC 3925 for more details of these rare and interesting beasts.
.TP
.B --dhcp-name-match=set:<tag>,<name>[*]
Set the tag if the given name is supplied by a DHCP client. There may be a single trailing wildcard *, which has the usual meaning. Combined with dhcp-ignore or dhcp-ignore-names this gives the ability to ignore certain clients by name, or disallow certain hostnames from being claimed by a client.
Set a \fB<tag>\fP if the given \fB<name>\fP is supplied by a DHCP client. There may be a single trailing wildcard *, with a glob meaning. Combined with \fBdhcp-ignore\fP or \fBdhcp-ignore-names\fP this gives the ability to ignore certain clients by name, or disallow certain hostnames from being claimed by a client.
.TP
.B --tag-if=set:<tag>[,set:<tag>[,tag:<tag>[,tag:<tag>]]]
Perform boolean operations on tags. Any tag appearing as set:<tag> is set if
all the tags which appear as tag:<tag> are set, (or unset when tag:!<tag> is used)
If no tag:<tag> appears set:<tag> tags are set unconditionally.
Any number of set: and tag: forms may appear, in any order.
\fB--tag-if\fP lines are executed in order, so if the tag in tag:<tag> is a
Perform boolean operations on tags. All \fBset:<tag>\fP tags are set if
all \fBtag:<tag>\fP are already set, (or unset when \fBtag:!<tag>\fP is used).
If no \fBtag:<tag>\fP appears, \fBset:<tag>\fP tags are set unconditionally.
Any number of \fBset:\fP and \fBtag:\fP forms may appear, in any order.
\fB--tag-if\fP lines are executed in order, so if the tag in \fBtag:<tag>\fP is a
tag set by another
.B --tag-if,
the line which sets the tag must precede the one which tests it.
the line which sets the tag must precede the line which tests it.
As an extension, the tag:<tag> clauses support limited wildcard matching,
similar to the matching in the \fB--interface\fP directive. This allows, for
example, using \fB--tag-if=set:ppp,tag:ppp*\fP to set the tag 'ppp' for all requests
received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction
with the tag:!<tag> format meaning that no tag matching the wildcard may be set.
As an extension, the \fBtag:<tag>\fP clauses support limited wildcard matching,
similar to the matching in the \fB--interface\fP directive. This allows the
example \fB--tag-if=set:ppp,tag:ppp*\fP to set the tag 'ppp' for all requests
received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction
with the \fBtag:!<tag>\fP format meaning that no tag matching the wildcard may be set.
.TP
.B \-J, --dhcp-ignore=tag:<tag>[,tag:<tag>]
When all the given tags appear in the tag set ignore the host and do
When all the given tags appear in the tag set, ignore the host and do
not allocate it a DHCP lease.
.TP
.B --dhcp-ignore-names[=tag:<tag>[,tag:<tag>]]
@@ -2416,48 +2425,67 @@ the CNAME. To work around this, add the CNAME to /etc/hosts so that
the CNAME is shadowed too.
.PP
The tag system works as follows: For each DHCP request, dnsmasq
collects a set of valid tags from active configuration lines which
include set:<tag>, including one from the
.B --dhcp-range
used to allocate the address, one from any matching
.B --dhcp-host
(and "known" or "known-othernet" if a \fB--dhcp-host\fP matches)
The tag "bootp" is set for BOOTP requests, and a tag whose name is the
name of the interface on which the request arrived is also set.
The tag system works as follows: dnsmasq tags each DHCP request
with tags from applicable configuration lines containing \fBset:<tag>\fP i.e.
Any configuration lines which include one or more tag:<tag> constructs
will only be valid if all that tags are matched in the set derived
above. Typically this is \fB--dhcp-option\fP.
.B --dhcp-option
which has tags will be used in preference to an untagged
.B --dhcp-option,
provided that _all_ the tags match somewhere in the
set collected as described above. The prefix '!' on a tag means 'not'
so \fB--dhcp-option=tag:!purple,3,1.2.3.4\fP sends the option when the
tag purple is not in the set of valid tags. (If using this in a
command line rather than a configuration file, be sure to escape !,
which is a shell metacharacter)
\fBset:<tag>\fP from the \fB--dhcp-range\fP used to allocate the address;
\fBset:<tag>\fP from any matching \fB--dhcp-host\fP (plus the tag \fBknown\fP or
\fBknown-othernet\fP).
BOOTP requests are tagged \fBbootp\fP. Each request is also tagged with the name of
the interface on which the request arrived.
Each configuration line containing one or more \fBtag:<tag>\fP constructs
applies when all its tags exist on the request. That is:
Configuration tag:A applies to a request set:tagged A.
Configuration tag:B applies to a request set:tagged B.
Configuration tag:A+B doesn't apply to a request set:tagged A.
Configuration tag:A+B doesn't apply to a request set:tagged B.
Configurations tag:A+B, tag:A, tag:B apply to a request set:tagged A+B.
\fBset:<tag>\fP constructs in \fB--dhcp-range\fP and \fB--dhcp-host\fP tag requests.
Use \fBtag:<tag>\fPs in \fB--dhcp-option\fPs to match \fBset:<tag>\fP and apply configurations.
A \fB--dhcp-option\fP with \fBtag:<tag>\fP is preferred over an untagged
\fB--dhcp-option\fP, provided that \fBall\fP its tags match somewhere in the
set gathered above.
The tag prefix '!' means 'not'.
\fB--dhcp-option=tag:!purple,3,1.2.3.4\fP sends the option when the request
is not tagged with purple. (The shell metacharacter '!' must be escaped on the
command line but not in a configuration file).
When selecting \fB--dhcp-option\fPs, a \fB--dhcp-range\fP tag is second class
to other tags, to make it easy to override options for
individual hosts, so:
When selecting \fB--dhcp-options\fP, a tag from \fB--dhcp-range\fP is second class
relative to other tags, to make it easy to override options for
individual hosts, so
.B --dhcp-range=set:interface1,......
.B --dhcp-host=set:myhost,.....
.B --dhcp-option=tag:interface1,option:nis-domain,"domain1"
.B --dhcp-option=tag:myhost,option:nis-domain,"domain2"
will set the NIS-domain to domain1 for hosts in the range, but
override that to domain2 for a particular host.
sets the NIS-domain to domain1 for hosts in the range, but
to domain2 for a particular host that may or may not fall in the range.
.PP
Note that for
.B --dhcp-range
both tag:<tag> and set:<tag> are allowed, to both select the range in
Note that for \fB--dhcp-range\fP both
\fBtag:<tag>\fP and
\fBset:<tag>\fP are possible, in order to both select the range in
use based on (eg) \fB--dhcp-host\fP, and to affect the options sent, based on
the range selected.
This system evolved from an earlier, more limited one and for backward
compatibility "net:" may be used instead of "tag:" and "set:" may be
The tag system evolved from an earlier, more limited system. For backward
compatibility, "net:" may be used instead of "tag:" and "set:" may be
omitted. (Except in
.B --dhcp-host,
where "net:" may be used instead of "set:".) For the same reason, '#'
@@ -2475,7 +2503,7 @@ configuration option is present to activate the DHCP server
on a particular network. (Setting \fB--bootp-dynamic\fP removes the need for
static address mappings.) The filename
parameter in a BOOTP request is used as a tag,
as is the tag "bootp", allowing some control over the options returned to
as is the tag \fBbootp\fP, allowing some control over the options returned to
different classes of hosts.
.SH AUTHORITATIVE CONFIGURATION

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -137,7 +137,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
nameoffset = p - (unsigned char *)header;
/* now extract name as .-concatenated string into name */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -513,7 +513,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
nxdomain = 0;
if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
{
log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid), 0);
log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr, record_source(crecp->uid), 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr))

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

180
src/bpf.c
View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -126,112 +126,110 @@ int iface_enumerate(int family, void *parm, callback_t callback)
for (addrs = head; addrs; addrs = addrs->ifa_next)
{
if (addrs->ifa_addr->sa_family == family)
int iface_index = if_nametoindex(addrs->ifa_name);
if (iface_index == 0 || !addrs->ifa_addr ||
addrs->ifa_addr->sa_family != family ||
(!addrs->ifa_netmask && family != AF_LINK))
continue;
if (family == AF_INET)
{
int iface_index = if_nametoindex(addrs->ifa_name);
if (iface_index == 0 || !addrs->ifa_addr ||
(!addrs->ifa_netmask && family != AF_LINK))
struct in_addr addr, netmask, broadcast;
addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
continue;
if (family == AF_INET)
{
struct in_addr addr, netmask, broadcast;
addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
continue;
#endif
netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
if (addrs->ifa_broadaddr)
broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr;
else
broadcast.s_addr = 0;
if (!callback.af_inet(addr, iface_index, NULL, netmask, broadcast, parm))
goto err;
}
else if (family == AF_INET6)
{
struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
int i, j, prefix = 0;
u32 valid = 0xffffffff, preferred = 0xffffffff;
int flags = 0;
netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
if (addrs->ifa_broadaddr)
broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr;
else
broadcast.s_addr = 0;
if (!callback.af_inet(addr, iface_index, NULL, netmask, broadcast, parm))
goto err;
}
else if (family == AF_INET6)
{
struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
int i, j, prefix = 0;
u32 valid = 0xffffffff, preferred = 0xffffffff;
int flags = 0;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
continue;
if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
continue;
#endif
#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
struct in6_ifreq ifr6;
memset(&ifr6, 0, sizeof(ifr6));
safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
struct in6_ifreq ifr6;
memset(&ifr6, 0, sizeof(ifr6));
safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
{
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
flags |= IFACE_TENTATIVE;
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
flags |= IFACE_DEPRECATED;
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
{
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
flags |= IFACE_TENTATIVE;
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
flags |= IFACE_DEPRECATED;
#ifdef IN6_IFF_TEMPORARY
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
flags |= IFACE_PERMANENT;
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
flags |= IFACE_PERMANENT;
#endif
#ifdef IN6_IFF_PRIVACY
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
flags |= IFACE_PERMANENT;
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
flags |= IFACE_PERMANENT;
#endif
}
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
{
valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
}
}
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
{
valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
}
#endif
for (i = 0; i < IN6ADDRSZ; i++, prefix += 8)
if (netmask[i] != 0xff)
break;
if (i != IN6ADDRSZ && netmask[i])
for (j = 7; j > 0; j--, prefix++)
if ((netmask[i] & (1 << j)) == 0)
break;
/* voodoo to clear interface field in address */
if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
{
addr->s6_addr[2] = 0;
addr->s6_addr[3] = 0;
}
if (!callback.af_inet6(addr, prefix, scope_id, iface_index, flags,
(unsigned int) preferred, (unsigned int)valid, parm))
goto err;
}
#ifdef HAVE_DHCP6
else if (family == AF_LINK)
{
/* Assume ethernet again here */
struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
if (sdl->sdl_alen != 0 &&
!callback.af_local(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm))
goto err;
}
#endif
for (i = 0; i < IN6ADDRSZ; i++, prefix += 8)
if (netmask[i] != 0xff)
break;
if (i != IN6ADDRSZ && netmask[i])
for (j = 7; j > 0; j--, prefix++)
if ((netmask[i] & (1 << j)) == 0)
break;
/* voodoo to clear interface field in address */
if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
{
addr->s6_addr[2] = 0;
addr->s6_addr[3] = 0;
}
if (!callback.af_inet6(addr, prefix, scope_id, iface_index, flags,
(unsigned int) preferred, (unsigned int)valid, parm))
goto err;
}
#ifdef HAVE_DHCP6
else if (family == AF_LINK)
{
/* Assume ethernet again here */
struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
if (sdl->sdl_alen != 0 &&
!callback.af_local(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm))
goto err;
}
#endif
}
ret = 1;
err:
errsave = errno;
freeifaddrs(head);

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -479,7 +479,7 @@ static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned s
if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
{
int rrmatch = 0;
if (crecp->flags & flags & F_RR)
if (addr && (crecp->flags & flags & F_RR))
{
unsigned short rrc = (crecp->flags & F_KEYTAG) ? crecp->addr.rrblock.rrtype : crecp->addr.rrdata.rrtype;
unsigned short rra = (flags & F_KEYTAG) ? addr->rrblock.rrtype : addr->rrdata.rrtype;
@@ -1448,11 +1448,17 @@ void cache_reload(void)
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
cache->ttd = daemon->local_ttl;
cache->name.namep = ds->name;
cache->addr.ds.keylen = ds->digestlen;
cache->addr.ds.algo = ds->algo;
cache->addr.ds.keytag = ds->keytag;
cache->addr.ds.digest = ds->digest_type;
cache->uid = ds->class;
if (ds->digestlen != 0)
{
cache->addr.ds.keylen = ds->digestlen;
cache->addr.ds.algo = ds->algo;
cache->addr.ds.keytag = ds->keytag;
cache->addr.ds.digest = ds->digest_type;
}
else
cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR;
cache_hash(cache);
make_non_terminals(cache);
}
@@ -1852,15 +1858,31 @@ int cache_make_stat(struct txt_record *t)
#endif
/* There can be names in the cache containing control chars, don't
mess up logging or open security holes. */
mess up logging or open security holes. Also convert to all-LC
so that 0x20-encoding doesn't make logs look like ransom notes
made out of letters cut from a newspaper.
Overwrites daemon->workspacename */
static char *sanitise(char *name)
{
unsigned char *r;
unsigned char *r = (unsigned char *)name;
if (name)
for (r = (unsigned char *)name; *r; r++)
if (!isprint((int)*r))
return "<name unprintable>";
{
char *d = name = daemon->workspacename;
for (; *r; r++, d++)
if (!isprint((int)*r))
return "<name unprintable>";
else
{
unsigned char c = *r;
*d = (char)((c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c);
}
*d = 0;
}
return name;
}

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
#define DNSSEC_ASSUMED_DS_TTL 3600 /* TTL for negative DS records implied by server=/domain/ */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
@@ -362,6 +363,11 @@ HAVE_SOCKADDR_SA_LEN
#define HAVE_INOTIFY
#endif
/* This never compiles code, it's only used by the makefile to fingerprint builds. */
#ifdef DNSMASQ_COMPILE_FLAGS
static char *compile_flags = DNSMASQ_COMPILE_FLAGS;
#endif
/* Define a string indicating which options are in use.
DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -812,7 +812,7 @@ void dhcp_construct_contexts(time_t now)
{
if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA))
{
/* previously constructed context has gone. advertise it's demise */
/* previously constructed context has gone; advertise its demise */
context->flags |= CONTEXT_OLD;
context->address_lost_time = now;
/* Apply same ceiling of configured lease time as in radv.c */

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ static volatile int pipewrite;
static void set_dns_listeners(void);
static void set_tftp_listeners(void);
static void check_dns_listeners(time_t now);
static void do_tcp_connection(struct listener *listener, time_t now, int slot);
static void sig_handler(int sig);
static void async_event(int pipe, time_t now);
static void fatal_event(struct event_desc *ev, char *msg);
@@ -132,6 +133,7 @@ int main (int argc, char **argv)
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec. */
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
daemon->cname = safe_malloc((MAXDNAME * 2) + 1);
/* one char flag per possible RR in answer section (may get extended). */
daemon->rr_status_sz = 64;
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
@@ -929,7 +931,8 @@ int main (int argc, char **argv)
my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid"));
for (ds = daemon->ds; ds; ds = ds->next)
my_syslog(LOG_INFO, _("configured with trust anchor for %s keytag %u"),
my_syslog(LOG_INFO,
ds->digestlen == 0 ? _("configured with negative trust anchor for %s") : _("configured with trust anchor for %s keytag %u"),
ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
}
#endif
@@ -961,7 +964,14 @@ int main (int argc, char **argv)
my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set"));
daemon->resolv_files = NULL;
if (!daemon->servers)
my_syslog(LOG_WARNING, _("warning: no upstream servers configured"));
{
#ifdef HAVE_DBUS
if (option_bool(OPT_DBUS))
my_syslog(LOG_INFO, _("no upstream servers configured - please set them from DBus"));
else
#endif
my_syslog(LOG_WARNING, _("warning: no upstream servers configured"));
}
}
if (daemon->max_logs != 0)
@@ -1476,7 +1486,7 @@ static void async_event(int pipe, time_t now)
{
pid_t p;
struct event_desc ev;
int i, check = 0;
int wstatus, i, check = 0;
char *msg;
/* NOTE: the memory used to return msg is leaked: use msgs in events only
@@ -1539,7 +1549,7 @@ static void async_event(int pipe, time_t now)
case EVENT_CHILD:
/* See Stevens 5.10 */
while ((p = waitpid(-1, NULL, WNOHANG)) != 0)
while ((p = waitpid(-1, &wstatus, WNOHANG)) != 0)
if (p == -1)
{
if (errno != EINTR)
@@ -1550,6 +1560,20 @@ static void async_event(int pipe, time_t now)
if (daemon->tcp_pids[i] == p)
{
daemon->tcp_pids[i] = 0;
if (!WIFEXITED(wstatus))
{
/* If a helper process dies, (eg with SIGSEV)
log that and attempt to patch things up so that the
parent can continue to function. */
my_syslog(LOG_WARNING, _("TCP helper process %u died unexpectedly"), (unsigned int)p);
if (daemon->tcp_pipes[i] != -1)
{
close(daemon->tcp_pipes[i]);
daemon->tcp_pipes[i] = -1;
}
}
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pipes[i] == -1)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
@@ -1832,247 +1856,281 @@ static void check_dns_listeners(time_t now)
struct listener *listener;
struct randfd_list *rfl;
int i;
int pipefd[2];
/* Note that handling events here can create or destroy fds and
render the result of the last poll() call invalid. Once
we find an fd that needs service, do it, then return to go around the
poll() loop again. This avoid really, really, wierd bugs. */
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
if (daemon->tcp_pipes[i] != -1 &&
poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP))
{
/* Races. The child process can die before we read all of the data from the
pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the
process, and tcp_pipes to -1 and close the FD when we read the last
of the data - indicated by cache_recv_insert returning zero.
The order of these events is indeterminate, and both are needed
to free the process slot. Once the child process has gone, poll()
returns POLLHUP, not POLLIN, so have to check for both here. */
if (!cache_recv_insert(now, daemon->tcp_pipes[i]))
{
close(daemon->tcp_pipes[i]);
daemon->tcp_pipes[i] = -1;
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pids[i] == 0)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
}
return;
}
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
if (poll_check(serverfdp->fd, POLLIN))
reply_query(serverfdp->fd, now);
{
reply_query(serverfdp->fd, now);
return;
}
for (i = 0; i < daemon->numrrand; i++)
if (daemon->randomsocks[i].refcount != 0 &&
poll_check(daemon->randomsocks[i].fd, POLLIN))
reply_query(daemon->randomsocks[i].fd, now);
{
reply_query(daemon->randomsocks[i].fd, now);
return;
}
/* Check overflow random sockets too. */
for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
if (poll_check(rfl->rfd->fd, POLLIN))
reply_query(rfl->rfd->fd, now);
/* Races. The child process can die before we read all of the data from the
pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the
process, and tcp_pipes to -1 and close the FD when we read the last
of the data - indicated by cache_recv_insert returning zero.
The order of these events is indeterminate, and both are needed
to free the process slot. Once the child process has gone, poll()
returns POLLHUP, not POLLIN, so have to check for both here. */
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
if (daemon->tcp_pipes[i] != -1 &&
poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) &&
!cache_recv_insert(now, daemon->tcp_pipes[i]))
{
close(daemon->tcp_pipes[i]);
daemon->tcp_pipes[i] = -1;
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pids[i] == 0)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
}
{
reply_query(rfl->rfd->fd, now);
return;
}
for (listener = daemon->listeners; listener; listener = listener->next)
{
if (listener->fd != -1 && poll_check(listener->fd, POLLIN))
if (listener->fd != -1 && poll_check(listener->fd, POLLIN))
{
receive_query(listener, now);
/* check to see if we have a free tcp process slot.
Note that we can't assume that because we had
at least one a poll() time, that we still do.
There may be more waiting connections after
poll() returns then free process slots. */
for (i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
break;
return;
}
/* check to see if we have a free tcp process slot.
Note that we can't assume that because we had
at least one a poll() time, that we still do.
There may be more waiting connections after
poll() returns then free process slots. */
for (i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
break;
if (listener->tcpfd != -1 && i >= 0 && poll_check(listener->tcpfd, POLLIN))
if (i >= 0)
for (listener = daemon->listeners; listener; listener = listener->next)
if (listener->tcpfd != -1 && poll_check(listener->tcpfd, POLLIN))
{
int confd, client_ok = 1;
struct irec *iface = NULL;
pid_t p;
union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr);
do_tcp_connection(listener, now, i);
return;
}
}
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
continue;
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{
close(confd);
continue;
}
/* Make sure that the interface list is up-to-date.
We do this here as we may need the results below, and
the DNS code needs them for --interface-name stuff.
static void do_tcp_connection(struct listener *listener, time_t now, int slot)
{
int confd, client_ok = 1;
struct irec *iface = NULL;
pid_t p;
union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr);
unsigned char a = 0, *buff;
struct server *s;
int flags, auth_dns;
struct in_addr netmask;
int pipefd[2];
Multiple calls to enumerate_interfaces() per select loop are
inhibited, so calls to it in the child process (which doesn't select())
have no effect. This avoids two processes reading from the same
netlink fd and screwing the pooch entirely.
*/
enumerate_interfaces(0);
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
return;
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{
closeconandreturn:
shutdown(confd, SHUT_RDWR);
close(confd);
return;
}
/* Make sure that the interface list is up-to-date.
We do this here as we may need the results below, and
the DNS code needs them for --interface-name stuff.
Multiple calls to enumerate_interfaces() per select loop are
inhibited, so calls to it in the child process (which doesn't select())
have no effect. This avoids two processes reading from the same
netlink fd and screwing the pooch entirely.
*/
enumerate_interfaces(0);
if (option_bool(OPT_NOWILD))
iface = listener->iface; /* May be NULL */
else
{
int if_index;
char intr_name[IF_NAMESIZE];
/* if we can find the arrival interface, check it's one that's allowed */
if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
indextoname(listener->tcpfd, if_index, intr_name))
{
union all_addr addr;
if (option_bool(OPT_NOWILD))
iface = listener->iface; /* May be NULL */
else
{
int if_index;
char intr_name[IF_NAMESIZE];
/* if we can find the arrival interface, check it's one that's allowed */
if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
indextoname(listener->tcpfd, if_index, intr_name))
{
union all_addr addr;
if (tcp_addr.sa.sa_family == AF_INET6)
addr.addr6 = tcp_addr.in6.sin6_addr;
else
addr.addr4 = tcp_addr.in.sin_addr;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->index == if_index &&
iface->addr.sa.sa_family == tcp_addr.sa.sa_family)
break;
if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
client_ok = 0;
}
if (option_bool(OPT_CLEVERBIND))
iface = listener->iface; /* May be NULL */
else
{
/* Check for allowed interfaces when binding the wildcard address:
we do this by looking for an interface with the same address as
the local address of the TCP connection, then looking to see if that's
an allowed interface. As a side effect, we get the netmask of the
interface too, for localisation. */
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&iface->addr, &tcp_addr))
break;
if (!iface)
client_ok = 0;
}
}
if (!client_ok)
{
shutdown(confd, SHUT_RDWR);
close(confd);
}
else if (!option_bool(OPT_DEBUG) && pipe(pipefd) == 0 && (p = fork()) != 0)
{
close(pipefd[1]); /* parent needs read pipe end. */
if (p == -1)
close(pipefd[0]);
else
{
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
To avoid this, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
unsigned char a;
read_write(pipefd[0], &a, 1, RW_READ);
#endif
/* i holds index of free slot */
daemon->tcp_pids[i] = p;
daemon->tcp_pipes[i] = pipefd[0];
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
}
close(confd);
/* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */
daemon->log_id += TCP_MAX_QUERIES;
#ifdef HAVE_DNSSEC
/* It can do more if making DNSSEC queries too. */
if (option_bool(OPT_DNSSEC_VALID))
daemon->log_id += daemon->limit[LIMIT_WORK];
#endif
}
if (tcp_addr.sa.sa_family == AF_INET6)
addr.addr6 = tcp_addr.in6.sin6_addr;
else
{
unsigned char *buff;
struct server *s;
int flags;
struct in_addr netmask;
int auth_dns;
if (iface)
{
netmask = iface->netmask;
auth_dns = iface->dns_auth;
}
else
{
netmask.s_addr = 0;
auth_dns = 0;
}
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
if (!option_bool(OPT_DEBUG))
{
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
unsigned char a = 0;
close(daemon->netlinkfd);
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
}
/* The connected socket inherits non-blocking
attribute from the listening socket.
Reset that here. */
if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
while(retry_send(fcntl(confd, F_SETFL, flags & ~O_NONBLOCK)));
buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
if (buff)
free(buff);
for (s = daemon->servers; s; s = s->next)
if (s->tcpfd != -1)
{
shutdown(s->tcpfd, SHUT_RDWR);
close(s->tcpfd);
s->tcpfd = -1;
}
if (!option_bool(OPT_DEBUG))
{
close(daemon->pipe_to_parent);
flush_log();
_exit(0);
}
}
addr.addr4 = tcp_addr.in.sin_addr;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (iface->index == if_index &&
iface->addr.sa.sa_family == tcp_addr.sa.sa_family)
break;
if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
client_ok = 0;
}
if (option_bool(OPT_CLEVERBIND))
iface = listener->iface; /* May be NULL */
else
{
/* Check for allowed interfaces when binding the wildcard address:
we do this by looking for an interface with the same address as
the local address of the TCP connection, then looking to see if that's
an allowed interface. As a side effect, we get the netmask of the
interface too, for localisation. */
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&iface->addr, &tcp_addr))
break;
if (!iface)
client_ok = 0;
}
}
if (!client_ok)
goto closeconandreturn;
if (!option_bool(OPT_DEBUG))
{
if (pipe(pipefd) == -1)
goto closeconandreturn; /* pipe failed */
if ((p = fork()) == -1)
{
/* fork failed */
close(pipefd[0]);
close(pipefd[1]);
goto closeconandreturn;
}
if (p != 0)
{
/* fork() done: parent side */
close(pipefd[1]); /* parent needs read pipe end. */
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
To avoid this, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
daemon->tcp_pids[slot] = p;
daemon->tcp_pipes[slot] = pipefd[0];
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
close(confd);
/* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */
daemon->log_id += TCP_MAX_QUERIES;
#ifdef HAVE_DNSSEC
/* It can do more if making DNSSEC queries too. */
if (option_bool(OPT_DNSSEC_VALID))
daemon->log_id += daemon->limit[LIMIT_WORK];
#endif
return;
}
}
if (iface)
{
netmask = iface->netmask;
auth_dns = iface->dns_auth;
}
else
{
netmask.s_addr = 0;
auth_dns = 0;
}
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
if (!option_bool(OPT_DEBUG))
{
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
close(daemon->netlinkfd);
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
}
/* The connected socket inherits non-blocking
attribute from the listening socket.
Reset that here. */
if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
while(retry_send(fcntl(confd, F_SETFL, flags & ~O_NONBLOCK)));
buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
if (buff)
free(buff);
for (s = daemon->servers; s; s = s->next)
if (s->tcpfd != -1)
{
shutdown(s->tcpfd, SHUT_RDWR);
close(s->tcpfd);
s->tcpfd = -1;
}
if (!option_bool(OPT_DEBUG))
{
close(daemon->pipe_to_parent);
flush_log();
_exit(0);
}
}
#ifdef HAVE_DNSSEC
/* If a DNSSEC query over UDP returns a truncated answer,
we swap to the TCP path. This routine is responsible for forking
@@ -2084,7 +2142,7 @@ static void check_dns_listeners(time_t now)
cache_recv_insert() calls pop_and_retry_query() after the result
arrives via the pipe to the parent. */
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount)
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
{
struct server *s;
@@ -2158,8 +2216,7 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
}
}
status = tcp_from_udp(now, status, header, plen, class, daemon->namebuff, daemon->keyname,
server, keycount, validatecount);
status = tcp_from_udp(now, status, header, plen, class, name, server, keycount, validatecount);
/* close upstream connections. */
for (s = daemon->servers; s; s = s->next)

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -14,7 +14,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define COPYRIGHT "Copyright (c) 2000-2024 Simon Kelley"
#define COPYRIGHT "Copyright (c) 2000-2025 Simon Kelley"
/* We do defines that influence behavior of stdio.h, so complain
if included too early. */
@@ -279,7 +279,9 @@ struct event_desc {
#define OPT_CACHE_RR 71
#define OPT_LOCALHOST_SERVICE 72
#define OPT_LOG_PROTO 73
#define OPT_LAST 74
#define OPT_NO_0x20 74
#define OPT_DO_0x20 75
#define OPT_LAST 76
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -774,12 +776,13 @@ struct dyndir {
#define FREC_DO_QUESTION 64
#define FREC_HAS_PHEADER 128
#define FREC_GONE_TO_TCP 256
#define FREC_ANSWER 512
struct frec {
struct frec_src {
union mysockaddr source;
union all_addr dest;
unsigned int iface, log_id;
unsigned int iface, log_id, encode_bitmap, *encode_bigmap;
int fd;
unsigned short orig_id, udp_pkt_size;
struct frec_src *next;
@@ -1245,7 +1248,7 @@ extern struct daemon {
char *namebuff; /* MAXDNAME size buffer */
char *workspacename;
#ifdef HAVE_DNSSEC
char *keyname; /* MAXDNAME size buffer */
char *keyname, *cname; /* MAXDNAME size buffer */
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
int rr_status_sz;
int dnssec_no_time_check;
@@ -1386,12 +1389,12 @@ int is_rev_synth(int flag, union all_addr *addr, char *name);
/* rfc1035.c */
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff);
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes);
char *name, int func, unsigned int parm);
unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
unsigned char *skip_questions(struct dns_header *header, size_t plen);
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
unsigned short *typep, unsigned short *classp);
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
@@ -1413,6 +1416,11 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp,
int *offset, unsigned short type, unsigned short class, char *format, ...);
int in_arpa_name_2_addr(char *namein, union all_addr *addrp);
int private_net(struct in_addr addr, int ban_localhost);
/* extract_name ops */
#define EXTR_NAME_EXTRACT 1
#define EXTR_NAME_COMPARE 2
#define EXTR_NAME_NOCASE 3
#define EXTR_NAME_FLIP 4
/* auth.c */
#ifdef HAVE_AUTH
@@ -1522,7 +1530,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
#ifdef HAVE_DNSSEC
void pop_and_retry_query(struct frec *forward, int status, time_t now);
int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
int class, char *name, char *keyname, struct server *server,
int class, char *name, struct server *server,
int *keycount, int *validatecount);
#endif
unsigned char *tcp_request(int confd, time_t now,
@@ -1646,7 +1654,7 @@ void send_event(int fd, int event, int data, char *msg);
void clear_cache_and_reload(time_t now);
#ifdef HAVE_DNSSEC
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount);
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount);
#endif
/* netlink.c */

View File

@@ -1,5 +1,5 @@
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2023 Simon Kelley
and Copyright (c) 2012-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -191,7 +191,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
/* domain-name, canonicalise */
int len;
if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
@@ -339,7 +339,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
pstart = p;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
if (!(res = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return 0; /* bad packet */
GETSHORT(stype, p);
@@ -374,14 +374,14 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
if (gotkey)
{
/* If there's more than one SIG, ensure they all have same keyname */
if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
if (extract_name(header, plen, &p, keyname, EXTR_NAME_COMPARE, 0) != 1)
return 0;
}
else
{
gotkey = 1;
if (!extract_name(header, plen, &p, keyname, 1, 0))
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return 0;
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
@@ -503,7 +503,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (!extract_name(header, plen, &p, keyname, 1, 0))
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (!time_check)
@@ -568,7 +568,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
p = rrset[i];
if (!extract_name(header, plen, &p, name, 1, 10))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS;
name_start = name;
@@ -661,7 +661,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
if (!extract_name(header, plen, &p, name, 1, 0))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (key)
@@ -727,7 +727,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
static unsigned char **cached_digest;
static size_t cached_digest_size = 0;
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
@@ -752,7 +752,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -904,7 +904,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -997,49 +997,57 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
unsigned long ttl;
union all_addr a;
if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4)))
return STAT_BOGUS;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
return STAT_BOGUS;
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
so treat it as such and continue to search for a DS or proof of no existence
further down the tree. */
if (RCODE(header) == SERVFAIL)
servfail = neganswer = nons = 1;
else
{
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
return STAT_BOGUS;
if (!servfail)
{
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
}
p = (unsigned char *)(header+1);
if (!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS;
p += 4; /* qtype, qclass */
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
else
{
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
return STAT_BOGUS;
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
{
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
return STAT_BOGUS;
}
if (!STAT_ISEQUAL(rc, STAT_SECURE))
return rc;
}
if (!STAT_ISEQUAL(rc, STAT_SECURE))
return rc;
}
if (!neganswer)
@@ -1050,7 +1058,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
{
unsigned char *psave;
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(atype, p);
@@ -1129,9 +1137,19 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
flags &= ~F_DNSSECOK;
{
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
nons = 0;
}
else
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
flags &= ~F_DNSSECOK;
}
}
cache_start_insert();
/* Use TTL from NSEC for negative cache entries */
@@ -1231,12 +1249,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
int sig_labels, name_labels;
p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10))
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 0))
if (!extract_name(header, plen, &p, workspace2, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard
@@ -1258,7 +1277,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
workspace1--;
*workspace1 = '*';
}
rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
/* RFC 6672 5.3.4.1. */
#define DNAME_OFFSET (T_DNAME >> 3)
#define DNAME_MASK (0x80 >> (T_DNAME & 0x07))
if (p[0] == 0 && (p[1] >= DNAME_OFFSET + 1) && (p[2 + DNAME_OFFSET] & DNAME_MASK) != 0 &&
hostname_issubdomain(name, workspace1) == 1)
return DNSSEC_FAIL_NONSEC;
rc = hostname_cmp(workspace1, name);
if (rc == 0)
@@ -1269,16 +1303,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
if (rdlen >= 2 && p[0] == 0)
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
@@ -1290,10 +1320,10 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return DNSSEC_FAIL_NONSEC;
}
while (rdlen >= 2)
while (rdlen > 0)
{
if (!CHECK_LEN(header, p, plen, rdlen))
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
@@ -1400,7 +1430,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
@@ -1433,7 +1463,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
if (rdlen >= 2 && p[0] == 0)
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1451,8 +1485,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0;
}
while (rdlen >= 2)
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
@@ -1609,7 +1646,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0))
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
@@ -1684,7 +1721,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p);
@@ -1735,7 +1772,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *psav;
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, EXTR_NAME_COMPARE, 10)))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1);
@@ -1924,11 +1961,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
static unsigned char **targets = NULL;
static int target_sz = 0;
unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE;
unsigned char *ans_start, *p1, *p2, *p3;
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx, gotdname;
int i, j, k, rc = STAT_INSECURE;
int secure = STAT_SECURE;
int rc_nsec;
unsigned long ttl;
/* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{
@@ -1965,7 +2004,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
targets[0] = p1;
targetidx = 1;
if (!extract_name(header, plen, &p1, name, 1, 4))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
GETSHORT(qtype, p1);
@@ -1975,27 +2014,119 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Can't validate an RRSIG query */
if (qtype == T_RRSIG)
return STAT_INSECURE;
/* Find CNAME targets. */
for (gotdname = i = 0; i < ntohs(header->ancount); i++)
{
if (!(p1 = skip_name(p1, header, plen, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
if (type1 == T_DNAME)
gotdname = 1;
if (qtype != T_CNAME && qtype != T_ANY && type1 == T_CNAME && class1 == qclass)
{
if (!expand_workspace(&targets, &target_sz, targetidx))
return STAT_BOGUS;
targets[targetidx++] = p1; /* pointer to target name */
}
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
}
if (qtype != T_CNAME && qtype != T_ANY)
for (j = ntohs(header->ancount); j != 0; j--)
/* A DNAME capable of sythesising a CNAME means we don't need to validate the CNAME,
we can just assume that it's valid. RFC 4035 3.2.3 */
if (gotdname)
for (p1 = ans_start, i = 0; i < ntohs(header->ancount); i++)
{
if (!(p1 = skip_name(p1, header, plen, 10)))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p1);
p1 += 6; /* class, TTL */
GETSHORT(rdlen2, p1);
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
if (type2 == T_CNAME)
if (type1 != T_DNAME)
{
if (!expand_workspace(&targets, &target_sz, targetidx))
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
targets[targetidx++] = p1; /* pointer to target name */
}
if (!ADD_RDLEN(header, p1, plen, rdlen2))
return STAT_BOGUS;
else
{
if (!extract_name(header, plen, &p1, keyname, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS; /* bad packet */
/* We now have the name of the DNAME in name, and the target in keyname.
Look for any CNAMEs which could have been synthesised from this DNAME
and pre-qualify them. */
for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
{
if (!extract_name(header, plen, &p2, daemon->cname, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p2);
GETSHORT(class2, p2);
GETLONG(ttl, p2);
GETSHORT(rdlen2, p2);
if (type2 != T_CNAME || class2 != class1)
{
if (!ADD_RDLEN(header, p2, plen, rdlen2))
return STAT_BOGUS;
}
else
{
size_t name_prefix_len = strlen(daemon->cname) - strlen(name);
if (!extract_name(header, plen, &p2, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS; /* bad packet */
/* We have the name of the CNAME in daemon->cname, and the target in daemon->workspacename.
See if the CNAME was sythesised from the DNAME.
CNAME must be <subdomain>.<dname>
CNAME target must be <subdomain>.<dname_target>
<subdomain>s must match for name and target. */
if (hostname_issubdomain(name, daemon->cname) == 1 &&
hostname_issubdomain(keyname, daemon->workspacename) == 1 &&
name_prefix_len == strlen(daemon->workspacename) - strlen(keyname))
{
char save = daemon->cname[name_prefix_len];
daemon->cname[name_prefix_len] = 0;
daemon->workspacename[name_prefix_len] = 0;
if (hostname_isequal(daemon->cname, daemon->workspacename))
{
/* pre-qualify this as validated */
daemon->rr_status[j] = ttl > 0 ? ttl : 1;
/* and remove it from the targets we need to have validated answers to. */
if (class2 == qclass)
{
daemon->cname[name_prefix_len] = save;
for (k = 0; k <targetidx; k++)
if ((p3 = targets[k]))
{
int rc1;
if (!(rc1 = extract_name(header, plen, &p3, daemon->cname, EXTR_NAME_COMPARE, 0)))
return STAT_BOGUS; /* bad packet */
if (rc1 == 1)
targets[k] = NULL;
}
}
}
}
}
}
}
}
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
@@ -2003,7 +2134,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
if (!extract_name(header, plen, &p1, name, 1, 10))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type1, p1);
@@ -2014,11 +2145,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Don't try and validate RRSIGs! */
if (type1 == T_RRSIG)
continue;
/* Pre-validated by DNAME above don't validate. */
if (daemon->rr_status[i] != 0)
continue;
/* Check if we've done this RRset already */
for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p2);
@@ -2115,7 +2250,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if ((p2 = targets[j]))
{
int rc1;
if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc1 = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
@@ -2149,13 +2284,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (neganswer)
*neganswer = 1;
if (!extract_name(header, plen, &p2, name, 1, 10))
if (!extract_name(header, plen, &p2, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
/* For anything other than a DS record, this situation is OK if either
the answer is in an unsigned zone, or there's a NSEC records. */
the answer is in an unsigned zone, or there's NSEC records.
For a DS record, we return INSECURE, which almost always turns
into BOGUS in the caller. */
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
{
if (rc_nsec & DNSSEC_FAIL_WORK)
@@ -2163,7 +2300,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_BOGUS | rc_nsec;
return STAT_INSECURE;
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -94,8 +94,7 @@ void build_server_array(void)
server=/.example.com/ works.
A flag of F_SERVER returns an upstream server only.
A flag of F_DNSSECOK returns a DNSSEC capable server only and
also disables NODOTS servers from consideration.
A flag of F_DNSSECOK disables NODOTS servers from consideration.
A flag of F_DOMAINSRV returns a domain-specific server only.
A flag of F_CONFIG returns anything that generates a local
reply of IPv4 or IPV6.
@@ -338,12 +337,8 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
if (i != nlow)
{
/* If we want a server that can do DNSSEC, and this one can't,
return nothing, similarly if were looking only for a server
for a particular domain. */
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
nlow = nhigh;
else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
/* If we want a server for a particular domain, and this one isn't, return nothing. */
if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
nlow = nhigh;
else
nhigh = i;
@@ -351,7 +346,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
else
{
/* --local=/domain/, only return if we don't need a server. */
if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
if (flags & (F_DOMAINSRV | F_SERVER))
nhigh = i;
}
}
@@ -472,7 +467,7 @@ int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
/* Find server to send DNSSEC query to. This will normally be the
same as for the original query, but may be another if
servers for domains are involved. */
if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
if (!lookup_domain(keyname, F_SERVER | F_DNSSECOK, &first, &last))
return -1;
for (index = first; index != last; index++)

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -1379,7 +1379,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
/* cannot set source _port_ for TCP connections. */
if (is_tcp)
port = 0;
else if (port == 0 && daemon->max_port != 0)
else if (port == 0 && daemon->max_port != 0 && daemon->max_port >= daemon->min_port)
{
/* Bind a random port within the range given by min-port and max-port if either
or both are set. Otherwise use the OS's random ephemeral port allocation by
@@ -1587,33 +1587,6 @@ void check_servers(int no_loop_check)
for (count = 0, serv = daemon->servers; serv; serv = serv->next)
{
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
if (!(serv->flags & SERV_FOR_NODOTS))
serv->flags |= SERV_DO_DNSSEC;
/* Disable DNSSEC validation when using server=/domain/.... servers
unless there's a configured trust anchor. */
if (strlen(serv->domain) != 0)
{
struct ds_config *ds;
char *domain = serv->domain;
/* .example.com is valid */
while (*domain == '.')
domain++;
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
break;
if (!ds)
serv->flags &= ~SERV_DO_DNSSEC;
}
}
#endif
port = prettyprint_addr(&serv->addr, daemon->namebuff);
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
@@ -1659,10 +1632,6 @@ void check_servers(int no_loop_check)
{
char *s1, *s2, *s3 = "", *s4 = "";
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
s3 = _("(no DNSSEC)");
#endif
if (serv->flags & SERV_FOR_NODOTS)
s1 = _("unqualified"), s2 = _("names");
else if (strlen(serv->domain) == 0)

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -193,6 +193,8 @@ struct myoption {
#define LOPT_MAX_PROCS 384
#define LOPT_DNSSEC_LIMITS 385
#define LOPT_PXE_OPT 386
#define LOPT_NO_ENCODE 387
#define LOPT_DO_ENCODE 388
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -247,6 +249,8 @@ static const struct myoption opts[] =
{ "local-ttl", 1, 0, 'T' },
{ "no-negcache", 0, 0, 'N' },
{ "no-round-robin", 0, 0, LOPT_NORR },
{ "no-0x20-encode", 0, 0, LOPT_NO_ENCODE },
{ "do-0x20-encode", 0, 0, LOPT_DO_ENCODE },
{ "cache-rr", 1, 0, LOPT_CACHE_RR },
{ "addn-hosts", 1, 0, 'H' },
{ "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
@@ -568,7 +572,7 @@ static struct {
{ LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>,]...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
@@ -591,6 +595,8 @@ static struct {
{ LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
{ LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
{ LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
{ LOPT_NO_ENCODE, OPT_NO_0x20, NULL, gettext_noop("Suppress DNS bit 0x20 encoding."), NULL },
{ LOPT_DO_ENCODE, OPT_DO_0x20, NULL, gettext_noop("Enable DNS bit 0x20 encoding."), NULL },
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
{ LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
{ LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
@@ -955,7 +961,7 @@ char *parse_server(char *arg, struct server_details *sdetails)
hints.ai_family = AF_UNSPEC;
/* Get addresses suitable for sending datagrams. We assume that we can use the
same addresses for TCP connections. Settting this to zero gets each address
same addresses for TCP connections. Setting this to zero gets each address
threes times, for SOCK_STREAM, SOCK_RAW and SOCK_DGRAM, which is not useful. */
hints.ai_socktype = SOCK_DGRAM;
@@ -2669,15 +2675,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (msize > 128)
ret_err_free(_("bad prefix length"), new);
mask = (1LLU << (128 - msize)) - 1LLU;
/* prefix==64 overflows the mask calculation */
if (msize <= 64)
mask = (u64)-1LL;
else
mask = (1LLU << (128 - msize)) - 1LLU;
new->is6 = 1;
new->prefixlen = msize;
/* prefix==64 overflows the mask calculation above */
if (msize <= 64)
mask = (u64)-1LL;
new->end6 = new->start6;
setaddr6part(&new->start6, addrpart & ~mask);
setaddr6part(&new->end6, addrpart | mask);
@@ -3981,7 +3987,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
while (arg)
{
comma = split(arg);
if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */
if (strchr(arg, ':')) /* Ethernet address, netid or binary CLID */
{
if ((arg[0] == 'i' || arg[0] == 'I') &&
(arg[1] == 'd' || arg[1] == 'D') &&
@@ -4043,10 +4049,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
new_addr = opt_malloc(sizeof(struct addrlist));
new_addr->next = new->addr6;
new_addr->flags = 0;
new_addr->addr.addr6 = in6;
new->addr6 = new_addr;
if (pref)
{
@@ -4057,7 +4061,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0)
{
dhcp_config_free(new);
ret_err(_("bad IPv6 prefix"));
ret_err_free(_("bad IPv6 prefix"), new_addr);
}
new_addr->flags |= ADDRLIST_PREFIX;
@@ -4071,6 +4075,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (i == 8)
new_addr->flags |= ADDRLIST_WILDCARD;
new_addr->next = new->addr6;
new->addr6 = new_addr;
new->flags |= CONFIG_ADDR6;
}
#endif
@@ -5331,7 +5337,8 @@ err:
new->class = C_IN;
new->name = NULL;
new->digestlen = 0;
if ((comma = split(arg)) && (algo = split(comma)))
{
int class = 0;
@@ -5349,29 +5356,37 @@ err:
algo = split(comma);
}
}
if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
!atoi_check16(comma, &new->keytag) ||
!atoi_check8(algo, &new->algo) ||
!atoi_check8(digest, &new->digest_type) ||
!(new->name = canonicalise_opt(arg)))
if (!(new->name = canonicalise_opt(arg)))
ret_err_free(_("bad trust anchor"), new);
/* Upper bound on length */
len = (2*strlen(keyhex))+1;
new->digest = opt_malloc(len);
unhide_metas(keyhex);
/* 4034: "Whitespace is allowed within digits" */
for (cp = keyhex; *cp; )
if (isspace((unsigned char)*cp))
for (cp1 = cp; *cp1; cp1++)
*cp1 = *(cp1+1);
else
cp++;
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
if (comma)
{
free(new->name);
ret_err_free(_("bad HEX in trust anchor"), new);
if (!algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
!atoi_check16(comma, &new->keytag) ||
!atoi_check8(algo, &new->algo) ||
!atoi_check8(digest, &new->digest_type))
{
free(new->name);
ret_err_free(_("bad trust anchor"), new);
}
/* Upper bound on length */
len = (2*strlen(keyhex))+1;
new->digest = opt_malloc(len);
unhide_metas(keyhex);
/* 4034: "Whitespace is allowed within digits" */
for (cp = keyhex; *cp; )
if (isspace((unsigned char)*cp))
for (cp1 = cp; *cp1; cp1++)
*cp1 = *(cp1+1);
else
cp++;
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
{
free(new->name);
ret_err_free(_("bad HEX in trust anchor"), new);
}
}
new->next = daemon->ds;
@@ -5920,6 +5935,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->randport_limit = 1;
daemon->host_index = SRC_AH;
daemon->max_procs = MAX_PROCS;
#ifdef HAVE_DUMPFILE
daemon->dump_mask = 0xffffffff;
#endif
#ifdef HAVE_DNSSEC
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -98,7 +98,7 @@ void poll_listen(int fd, short event)
{
if (arrsize == nfds)
{
/* Array too small, extend. */
/* Array too small. Extend. */
struct pollfd *new;
arrsize = (arrsize == 0) ? 64 : arrsize * 2;

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -411,7 +411,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad
if (!old_prefix && !parm.found_context)
return;
/* If we're sending router address instead of prefix in at least on prefix,
/* If we're sending router address instead of prefix in at least one prefix,
include the advertisement interval option. */
if (parm.adv_router)
{
@@ -825,10 +825,10 @@ time_t periodic_ra(time_t now)
}
else if (iface_enumerate(AF_INET6, &param, (callback_t){.af_inet6=iface_search}))
/* There's a context overdue, but we can't find an interface
associated with it, because it's for a subnet we dont
associated with it, because it's for a subnet we don't
have an interface on. Probably we're doing DHCP on
a remote subnet via a relay. Zero the timer, since we won't
ever be able to send ra's and satisfy it. */
ever be able to send RAs to satisfy it. */
context->ra_time = 0;
if (param.iface != 0 &&

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,16 +16,42 @@
#include "dnsmasq.h"
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes)
{
unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
unsigned int j, l, namelen = 0, hops = 0;
int retvalue = 1;
if (isExtract)
*cp = 0;
/* EXTR_NAME_EXTRACT -> extract name
EXTR_NAME_COMPARE -> compare name, case insensitive
EXTR_NAME_NOCASE -> compare name, case sensitive
EXTR_NAME_FLIP -> flip 0x20 bits in packet.
For flip, name is an array of ints, whose size
is given in parm, which forms the bitmap. Bits beyond the size
are assumed to be zero.
return = 0 -> error
return = 1 -> extract OK, compare OK, flip OK
return = 2 -> extract OK, compare failed.
return = 3 -> extract OK, compare failed but only on case.
If pp == NULL, operate on the query name in the packet.
*/
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int func, unsigned int parm)
{
unsigned char *cp = (unsigned char *)name, *p1 = NULL;
unsigned int j, l, namelen = 0, hops = 0;
unsigned int bigmap_counter = 0, bigmap_posn = 0, bigmap_size = parm, bitmap = 0;
int retvalue = 1, case_insens = 1, isExtract = 0, flip = 0, extrabytes = (int)parm;
unsigned int *bigmap = (unsigned int *)name;
unsigned char *p = pp ? *pp : (unsigned char *)(header+1);
if (func == EXTR_NAME_EXTRACT)
isExtract = 1, *cp = 0;
else if (func == EXTR_NAME_NOCASE)
case_insens = 0;
else if (func == EXTR_NAME_FLIP)
{
flip = 1, extrabytes = 0;
name = NULL;
}
while (1)
{
unsigned int label_type;
@@ -46,13 +72,16 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
cp--;
*cp = 0; /* terminate: lose final period */
}
else if (*cp != 0)
else if (!flip && *cp != 0)
retvalue = 2;
if (p1) /* we jumped via compression */
*pp = p1;
else
*pp = p;
if (pp)
{
if (p1) /* we jumped via compression */
*pp = p1;
else
*pp = p;
}
return retvalue;
}
@@ -85,7 +114,7 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
if (!CHECK_LEN(header, p, plen, l))
return 0;
for(j=0; j<l; j++, p++)
for (j=0; j<l; j++, p++)
if (isExtract)
{
unsigned char c = *p;
@@ -98,6 +127,24 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
else
*cp++ = c;
}
else if (flip)
{
unsigned char c = *p;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
/* Get the next int of the bitmap */
if (bigmap_posn < bigmap_size && bigmap_counter-- == 0)
{
bitmap = bigmap[bigmap_posn++];
bigmap_counter = (sizeof(unsigned int) * 8) - 1;
}
if (bitmap & 1)
*p ^= 0x20;
bitmap >>= 1;
}
}
else
{
unsigned char c1 = *cp, c2 = *p;
@@ -107,23 +154,35 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
else
{
cp++;
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c1 == NAME_ESCAPE)
c1 = (*cp++)-1;
else if (case_insens && c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
if (case_insens && c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (!case_insens && retvalue != 2 && c1 != c2)
{
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (c1 == c2)
retvalue = 3;
}
if (c1 != c2)
retvalue = 2;
retvalue = 2;
}
}
if (isExtract)
*cp++ = '.';
else if (*cp != 0 && *cp++ != '.')
else if (!flip && *cp != 0 && *cp++ != '.')
retvalue = 2;
}
else
@@ -399,7 +458,7 @@ int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
if (i == ntohs(header->ancount) && !(p = skip_section(p, ntohs(header->nscount), header, qlen)))
return done;
if (!extract_name(header, qlen, &p, namebuff, 1, 10))
if (!extract_name(header, qlen, &p, namebuff, EXTR_NAME_EXTRACT, 10))
return done; /* bad packet */
GETSHORT(qtype, p);
@@ -480,7 +539,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
for (i = 0; i < ntohs(header->nscount); i++)
{
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -509,7 +568,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
for (j = 0; j < 2; j++) /* MNAME, RNAME */
{
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
{
if (!no_cache)
blockdata_free(addr.rrblock.rrdata);
@@ -537,18 +596,18 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
}
/* rest of RR */
if (!no_cache && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += 20;
if (!no_cache)
{
int secflag = 0;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += 20;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
{
@@ -660,7 +719,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 2; /* bad packet */
GETSHORT(qtype, p);
@@ -684,7 +743,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
for (j = 0; j < ntohs(header->ancount); j++)
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
if (!(res = extract_name(header, qlen, &p1, name, EXTR_NAME_COMPARE, 10)))
return 2; /* bad packet */
GETSHORT(aqtype, p1);
@@ -722,7 +781,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqtype == T_CNAME)
log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
if (!extract_name(header, qlen, &p1, name, 1, 0))
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
return 2;
if (aqtype == T_CNAME)
@@ -800,7 +859,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
if (!(res = extract_name(header, qlen, &p1, name, EXTR_NAME_COMPARE, 10)))
return 2; /* bad packet */
GETSHORT(aqtype, p1);
@@ -862,7 +921,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
namep = p1;
if (!extract_name(header, qlen, &p1, name, 1, 0))
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
return 2;
if (qtype != T_CNAME)
@@ -932,7 +991,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
/* Name, extract it then re-encode. */
int len;
if (!extract_name(header, qlen, &p1, name, 1, 0))
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
{
blockdata_free(addr.rrblock.rrdata);
return 2;
@@ -965,7 +1024,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
} while (desc != -1);
/* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0))
if (!extract_name(header, qlen, &tmp, name, EXTR_NAME_EXTRACT, 0))
{
blockdata_free(addr.rrblock.rrdata);
return 2;
@@ -1127,7 +1186,7 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
{
int aqtype, aqclass, ardlen;
if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
if (!extract_name(header, len, &p, daemon->namebuff, EXTR_NAME_EXTRACT, 10))
return;
if (!CHECK_LEN(header, p, len, 10))
@@ -1145,7 +1204,7 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
{
if (aqtype == T_CNAME)
{
if (!extract_name(header, len, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, len, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return;
if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename))
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, attl);
@@ -1179,7 +1238,8 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
/* If the packet holds exactly one query
return F_IPV4 or F_IPV6 and leave the name from the query in name */
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
unsigned short *typep, unsigned short *classp)
{
unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass;
@@ -1195,7 +1255,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
if (!(header->hb3 & HB3_QR) && (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0))
return 0; /* non-standard query. */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -1204,6 +1264,9 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
if (typep)
*typep = qtype;
if (classp)
*classp = qclass;
if (qclass == C_IN)
{
if (qtype == T_A)
@@ -1215,9 +1278,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
}
#ifdef HAVE_DNSSEC
/* F_DNSSECOK as agument to search_servers() inhibits forwarding
to servers for domains without a trust anchor. This make the
behaviour for DS and DNSKEY queries we forward the same
/* Make the behaviour for DS and DNSKEY queries we forward the same
as for DS and DNSKEY queries we originate. */
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
return F_DNSSECOK;
@@ -1309,7 +1370,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu
for (i = ntohs(header->ancount); i != 0; i--)
{
if (name && !extract_name(header, qlen, &p, name, 1, 10))
if (name && !extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 10))
return 0; /* bad packet */
if (!name && !(p = skip_name(p, header, qlen, 10)))
@@ -1559,7 +1620,7 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
return daemon->max_ttl;
}
static int cache_validated(const struct crec *crecp)
static int cache_not_validated(const struct crec *crecp)
{
return (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK));
}
@@ -1619,7 +1680,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
nameoffset = p - (unsigned char *)header;
/* now extract name as .-concatenated string into name */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -1660,7 +1721,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* If the client asked for DNSSEC don't use cached data. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
(rd_bit && (!do_bit || cache_validated(crecp))))
(rd_bit && (!do_bit || cache_not_validated(crecp))))
{
if (crecp->flags & F_CONFIG || qtype == T_CNAME)
ans = 1;
@@ -1819,7 +1880,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
the zone is unsigned, which implies that we're doing
validation. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
(rd_bit && (!do_bit || cache_validated(crecp)) ))
(rd_bit && (!do_bit || cache_not_validated(crecp)) ))
{
do
{
@@ -1975,7 +2036,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* If the client asked for DNSSEC don't use cached data. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
(rd_bit && (!do_bit || cache_validated(crecp)) ))
(rd_bit && (!do_bit || cache_not_validated(crecp)) ))
do
{
int stale_flag = 0;
@@ -2157,19 +2218,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!ans)
{
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) &&
rd_bit && (!do_bit || cache_validated(crecp)))
if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN)) && rd_bit)
do
{
int flags = crecp->flags;
unsigned short rrtype;
if (flags & F_KEYTAG)
rrtype = crecp->addr.rrblock.rrtype;
else
rrtype = crecp->addr.rrdata.rrtype;
if ((flags & F_NXDOMAIN) || rrtype == qtype)
if (((flags & F_NXDOMAIN) || rrtype == qtype) &&
(!do_bit || cache_not_validated(crecp)))
{
char *rrdata = NULL;
unsigned short rrlen = 0;
@@ -2232,14 +2293,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype))
if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype) && !do_bit)
{
/* We don't have a cached answer and when we get an answer from upstream we're going to
filter it anyway. If we have a cached answer for the domain for another RRtype then
that may be enough to tell us if the answer should be NODATA and save the round trip.
Cached NXDOMAIN has already been handled, so here we look for any record for the domain,
since its existence allows us to return a NODATA answer. Note that we never set the AD flag,
since we didn't authenticate the record. */
since we didn't authenticate the record; this doesn't work if we want auth data, so
don't use this shortcut in that case. */
if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME))
{
@@ -2338,7 +2400,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!(ansp = skip_questions(header, qlen)))
return 0; /* bad packet */
anscount = nscount = addncount = 0;
log_query(F_CONFIG, "reply", NULL, "truncated", 0);
log_query(0, "reply", NULL, "truncated", 0);
}
if (nxdomain)

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -1345,7 +1345,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
else if (!lease && (ltmp = lease_find_by_addr(mess->yiaddr)))
{
/* If a host is configured with more than one MAC address, it's OK to 'nix
a lease from one of it's MACs to give the address to another. */
a lease from one of its MACs to give the address to another. */
if (config && config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type))
{
inet_ntop(AF_INET, &ltmp->addr, daemon->addrbuff, ADDRSTRLEN);

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -377,7 +377,7 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
int to_wire(char *name)
{
unsigned char *l, *p, *q, term;
int len;
unsigned int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
@@ -409,7 +409,7 @@ int to_wire(char *name)
void from_wire(char *name)
{
unsigned char *l, *p, *last;
int len;
unsigned int len;
for (last = (unsigned char *)name; *last != 0; last += *last+1);

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* firewire has EUI-64 identifier as clid */
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
#endif
else

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -274,7 +274,7 @@ void tftp_request(struct listener *listen, time_t now)
}
/* Enforce simultaneous transfer limit. In non-single-port mode
this is doene by not listening on the server socket when
this is done by not listening on the server socket when
too many transfers are in progress. */
if (!transfer && tftp_cnt >= daemon->tftp_max)
return;
@@ -742,15 +742,16 @@ static void free_transfer(struct tftp_transfer *transfer)
static char *next(char **p, char *end)
{
char *ret = *p;
size_t len;
char *n, *ret = *p;
/* Look for end of string, without running off the end of the packet. */
for (n = *p; n < end && *n != 0; n++);
if (*(end-1) != 0 ||
*p == end ||
(len = strlen(ret)) == 0)
/* ran off the end or zero length string - failed */
if (n == end || n == ret)
return NULL;
*p += len + 1;
*p = n + 1;
return ret;
}

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -426,7 +426,7 @@ int hostname_order(const char *a, const char *b)
int hostname_isequal(const char *a, const char *b)
{
return hostname_order(a, b) == 0;
return strlen(a) == strlen(b) && hostname_order(a, b) == 0;
}
/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */