Compare commits

...
163 Commits
Author SHA1 Message Date
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
Simon Kelley f74b74e521 Refactor relay_reply6().
Traverse the nested data structures more efficiently,
more understandably, and with watertight checking of
buffer boundaries.
2025-07-15 15:17:46 +01:00
Simon Kelley c9342cb556 Check for constructed name in --srv-host exceeding length limit.
Thanks to Mikhail Dmitrichenko for spotting this.
2025-07-15 15:15:32 +01:00
Simon Kelley 2b844b8c83 Fix BNF from --srv-host in manpage. 2025-07-14 15:15:26 +01:00
Simon Kelley 65e9a8957c Fix order of reads dhcp-hostsfile and /etc/ethers.
Reading /etc/ethers assumes that dhcp-host cofig has already
been read, and that is the case for dhcp-host, but for unknown
reasons, the analogous congiguration is dhcp-hostfile was being
read after /etc/ethers.

Swap the order.

Thanks to Andreas Kuropka for spotting the problem.
2025-07-10 10:58:14 +01:00
Simon Kelley da868a2fbe Restore bit-rotted warning about non-recursive upstreams. 2025-07-08 16:18:03 +01:00
Simon Kelley bceb5287b9 Fix FTBFS on MacOS.
Fallout from 98189ff988

Thanks to Travis Rayan for the bug report.
2025-07-07 17:58:41 +01:00
Simon Kelley 84445dec26 Fix server selection for DS queries.
This was mainly done in 57e582492b
This commit extends the new behaviour to cases where DNSSEC
validation is not compiled-in or not enabled.
2025-07-07 17:16:32 +01:00
Rob Gill via Dnsmasq-discuss c70b92b2a4 Add CG-NAT range to non-globally routed IP space per RFC7793
The Shared address space (100.64.0.0/10) used by CG-NAT, defined in RFC6598 should
not have reverse DNS queries sent to global DNS infrastructure.

Confirmed in RFC7793 and listed on
https://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xhtml

Signed-off-by: Rob Gill <rrobgill@protonmail.com>
2025-06-26 13:10:48 +01:00
Simon Kelley 57e582492b Fix to 57f0489f38
When choosing a server to send a DS query to take
account of the need for DS records for a domain
to come from the parent of that domain.
2025-06-25 15:02:47 +01:00
Simon Kelley ec8f3e65c1 Tweak domain-specific servers and DNSSEC code to treat
servers for plain names (server=//1.2.3.4) as domain-specific.

This means that if we fail to get a DS record for such a query,
it gets given the benefit of the doubt.

Updates 57f0489f38
2025-06-23 21:48:29 +01:00
Simon Kelley 15841f187d Fix issue with fast file-descriptor close on *BSD.
This fixes a problem introduced in 8a5fe8ce6b

On BSD, fdescfs is normally mounted at /dev/fd. However
if it is NOT mounted, devfs creates a directory at /dev/fd
which contains (only) the file descriptors 0,1 and 2.

Under these conditions, opendir() will succeed, and
if we proceed we will fail to close extant
file descriptors which should be closed.

Check that there is a filesystem mounted at /dev/fd
by checking that the device changes between /dev/fd
and /dev. If if doesn't, fall back to the dumb path.

Thanks to Roman Bogorodskiy for spotting the problem
and helping with diagnosis.
2025-06-22 23:04:36 +01:00
Simon Kelley ade97495e6 Trvial formatting fix. 2025-06-20 17:12:32 +01:00
Simon Kelley 2b19285724 Fix breakage of ipset on *BSD.
Regression introduced in 98189ff988

Thanks to Harper Andrews for spotting this.
2025-06-20 15:48:47 +01:00
Simon Kelley a444715bf0 Fix FTBFS on Jurassic gcc versions. 2025-06-20 15:27:43 +01:00
Simon Kelley 14e81b6976 Additional sanity check in filter_servers(). 2025-06-06 23:33:25 +01:00
Simon Kelley 6ce7f2d55a Fix regression in 9e67099ce7
Logging queries lost the RRtype.

Revert to e.g. query[A] example.com
2025-06-06 23:29:31 +01:00
Simon Kelley 287d6bc88d Fix 5846f749e5
Not sure how that bug got in there. Very (un)lucky choice
of test data, or last-minute "it'll be fine" modification I guess.

It was late, I can't remember.

Thanks to Dominik Derrigs for spotting the problem.
2025-06-03 22:16:50 +01:00
Simon Kelley c378d2c1de Fix crash in filter_servers().
The bug occurs when we ask lookup_domain() for a server for a domain
which is not a general upstream server, by setting F_DOMAINSRV in the flags.

If there are no possible servers, because there are no upstream servers
defined (for instance, at startup) then the code steps off the end of an
array and SEGVs.

The bug has been latent for some time, but
3e659bd4ec added a new call to lookup_domain()
which can actually trigger the bug if DNSSEC is enabled and a certain
amount of bad luck ensues.

Thanks to the testers extraordinaire at PiHole for reporting this.
2025-06-02 22:37:08 +01:00
Simon Kelley 5846f749e5 Rewrite chunks of inotify.c to remove a memory leak.
The code is much easier to follow now, this shouldn't
happen again.
2025-05-29 22:44:15 +01:00
Matthias Andree c9a4240ec4 Unbreak compilation on non-Linux systems (FreeBSD)
and put the void cast of netlink_warn under the same preprocessor
condition as its definition, #ifdef HAVE_LINUX_NETWORK.
2025-05-25 21:15:41 +01:00
Simon Kelley e7b87dee85 Tftp code tweaks. 2025-05-24 21:15:02 +01:00
Simon Kelley 90b248582c Remove compiler warnings for obscure combinations of build options. 2025-05-24 15:09:53 +01:00
Simon Kelley ebef27f321 Add TFTP options windowsize (RFC 7440) and timeout (RFC 2349). 2025-05-24 14:41:40 +01:00
Matthias Andree 1861a881eb Only define variable "a" if we HAVE_LINUX_NETWORK
Variable unsigned char a is defined unconditionally,
but it is only used if HAVE_LINUX_NETWORK is defined.
This triggers compiler warnings on, say, FreeBSD.

Fix by wrapping the definition in proper #ifdef.
2025-05-21 19:48:39 +01:00
Matthias Andree 96bdb42d40 Fix GCC's -Wunterminated-string-initialization warning in edns0.c.
GCC complains that writing the five-character "ODNS\0" string into
a four-element char magic[4] array truncates the NUL character.
The warning's rationale is that this is incompatible with C++, or
maybe non-intentional.

GCC 8 has added a nonstring variable attribute, clang 20.1 does
not yet support this, but clang's Git head does.

Add an ATTRIBUTE_NONSTRING macro, currently only defined on GCC >= 8
as __attribute__ ((nonstring)).  This successfully suppresses
the warning on Fedora Linux 42's default compiler.

The alternative would be to replace the "ODNS" literal by {0} and
instead memcpy(opt.magic, "ODNS", sizeof(opt.magic)); on the next line,
which is correct, C++ compatible, but also less concise.
2025-05-21 15:06:27 +01:00
Simon Kelley c7a909ad65 Add RRtypes HHIT and BRID to the table. 2025-05-21 15:03:51 +01:00
Simon Kelley baf3c57af5 Fix compiler warnings. 2025-05-18 18:22:48 +01:00
Simon Kelley e48a2af4f5 Overhaul extract_addresses() function.
The proximate cause for doing this is to fix a bug that
causes replies to PTR queries with more than one answer to have the
second and subsequent answers ignored.

The fix turned into a small re-write which removed a very old hack.
When caching reponses which include CNAME records, the cache system
stores the CNAME with a link to the record representing the target of
the CNAME. This isn't possible for PTR records representing IP
addresses since the name stored is the target of the PTR, record and
its name is inferred from the address in the cache record. Such
cache records have the F_REVERSE flag set. To get
around this, long ago, the code which stores such records elided the
CNAME entirely, so
4.3.2.1.in-addr.arpa   CNAME 18/3.2.1.in-addr.arpa
18/3.2.1.in-addr.arpa   PTR   myhost.example.com

would be stored as
4.3.2.1.in-addr.arpa PTR   myhost.example.com

and returned from the cache to subsequent requestor in that form.

Since that hack was committed, dnsmasq has learned to cache arbitrary
RRs. So now we can store the PTR records for all the no-trivial cases.

The means the CNAME chains ending in PTR records don't get mangled,
and we can store PTR records whose name in not w.x.y.x.in-addr.arpa
or the IPv6 equivalent.
2025-05-18 17:24:41 +01:00
Simon Kelley 91b800cc62 CHANGELOG update. 2025-05-14 22:38:14 +01:00
Simon Kelley 075e4a56b7 --leasequery can now take a subnet argument. 2025-05-14 22:36:03 +01:00
Simon Kelley 48658ebc54 Add address filtering for leasequery. 2025-05-14 22:36:03 +01:00
Simon Kelley 692ed0dd32 Log source address of leasequeries. 2025-05-14 22:36:03 +01:00
Simon Kelley 43805c1859 Omit message type from leasequery.c option dump. 2025-05-14 22:36:03 +01:00
Simon Kelley 4fbe1add95 Implement RFC-4388 DHCPv4 leasequery. 2025-05-14 22:35:51 +01:00
Simon Kelley 57c7ae8fc0 CHANGELOG update. 2025-05-14 22:33:44 +01:00
Simon Kelley d1008215dc Better error message when rejecting a TFTP transfer. 2025-05-14 21:15:17 +01:00
Simon Kelley b0aa604fcc Tidy-up of TCP-child pipe handling code.
Functionality is unchanged, but the code is easier to read and understand.

Also fix memory leak of blocks when cache insert fails.
2025-05-11 15:30:30 +01:00
Simon Kelley 8ddabd11bc DNSSEC validation change for reverse lookups in RFC-1918 ranges and friends.
The large public DNS services seem not to return proof-of-nonexistence
for DS records at the start of RFC-1918 in-addr.arpa domains and the their
IPv6 equivalents. 10.in-addr.arpa, 168.192.in-addr.arpa etc.

Since dnsmasq already has an option which instructs it not bother
upstream servers with pointless queries about these address ranges,
namely --bogus-priv, we extend that to enable behaviour which allows
dnsmasq to assume that insecure NXDOMAIN replies for these domains
are expected and to assume that the domains are legitimately unsigned.

This behaviour only matters when some address range is directed to
another upstream server using --rev-server. In that case it allows
replies from that server to pass DNSSEC validation. Without such a
server configured, queries are never sent upstream so they are never
validated and the new behaviour is moot.
2025-05-09 21:46:50 +01:00
Simon Kelley 98189ff988 Fix problems with ipset or nftset and TCP DNS transport.
If DNS is happening over TCP, the query is handled by a forked
process. Of ipset ot nftset is configured, this might include
inserting addresses in the *sets. Before this update, that
was done by the forked process using handles inherited from the
parent "master" process.

This is inherently racy. If the master process or another
child process tries to do updates at the same time, the
updates can clash and fail.

To see this, you need a busy server doing lots of DNS
queries over TCP, and ipset or nftset configured.

Going forward, we use the already established pipe to send the
updates from the child back to the master process, which
serialises them.
2025-05-08 16:07:19 +01:00
Simon Kelley e86d53c438 Fix some edge cases wth domains and --address and --server.
Consider what happens when the same domain appears in
--address and --server.

This commit fixes the order, I think correctly like this:
highest to lowest priority.

--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.
--server with address set to # to use the unqualified servers.
--server with matching domain.
--server without domain or from /etc/resolv.conf.

Note that the above is only valid when same domain appears.
The domain being matched is determined first, and has a higher
priority, so you can send google.com to a server and force com
to return NXDOMAIN and for google.com the server config will
override the address config, because there's a longer match.
2025-04-29 16:33:22 +01:00
Simon Kelley e127a972d1 Fix logging booboo. 2025-04-27 23:25:30 +01:00
Simon Kelley a458c2bfb0 Tidy up pipe-to-parent code in DNS TCP path. 2025-04-23 12:14:00 +01:00
Simon Kelley 9e67099ce7 Tidy up replies to non-QUERY DNS opcodes in auth mode. 2025-04-22 18:07:24 +01:00
Rob Gill cfa1313e1f Log format error from upstream as 'FORMERR'
Signed-off-by: Rob Gill <rrobgill@protonmail.com>

At the moment if a misformatted query is reported by the upstream server
it is not clear from the log.
Other error codes from RFC1035 (server failure, not implemented,
refused) are logged with text, but format error is logged merely as "1".

Such that an upstream reporting a format error is presently logged as eg:
Apr 20 12:01:55 dnsmasq[3023]: reply error is 1

After this patch they are logged informatively, eg:
Apr 20 12:48:40 dnsmasq[3023]: reply error is FORMERR

This is a two line fix, FORMERR is already defined in dns-protocol.h.
2025-04-20 22:38:43 +01:00
Simon Kelley e3a2c8dadf Add --log-queries=auth option. 2025-04-20 22:20:52 +01:00
Simon Kelley 95b74a7acf Fix copy 'n paste error in DBUS server-statistics code. 2025-04-18 23:50:46 +01:00
Simon Kelley ae57f84061 Do a better job of 942a35f517 2025-04-18 14:01:14 +01:00
Simon Kelley 0620309b73 Revise negative DNS caching to better comply with RFC2308. 2025-04-16 21:29:42 +01:00
Opty 942a35f517 Silence compiler warnings. 2025-04-16 16:00:47 +01:00
Simon Kelley 83658efbf4 Fix occasional crashes with DNSSEC and large nunbers of --address configs.
Commit 3e659bd4ec removed the concept of
an usptream DNS server which is capable of DNSSEC: they are all
(at least in theory) now usable. As a very unfortunate side-effect,
this removed the filter that ensured that dnssec_server() ONLY
returns servers, and not domains with literal addresses.

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

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

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

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

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

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

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

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

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

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

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

Rewrite to handle such packets.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

cached reply is truncated

and not

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

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

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

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

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

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

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

This patch is a preparation for introducing DNS-0x20
in the dnsmasq query path.
2025-01-19 00:08:36 +00:00
73 changed files with 19744 additions and 9238 deletions

No files matched your search

+3
View File
@@ -1,3 +1,6 @@
[submodule "debian"]
path = debian
url = git://thekelleys.org.uk/dnsmasq-debian.git
[submodule "submodules/dnsmasq-debian"]
path = submodules/dnsmasq-debian
url = https://thekelleys.org.uk/git/dnsmasq-debian
+165 -20
View File
@@ -1,3 +1,119 @@
version 2.93
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, 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. As 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.
version 2.92
Redesign the interaction between DNSSEC validation and per-domain
servers, specified as --server=/<domain>/<ip-address>. This should
just work in all cases now. If the normal chain-of-trust exists into
the delegated domain then whether the domain is signed or not, DNSSEC
validation will function normally. In the case the delegated domain
is an "overlay" on top of the global DNS and no NS and/or DS records
exist connecting it to the global dns, then if the domain is
unsigned the situation will be handled by synthesising a
proof-of-non-existence-of-DS for the domain and queries will be
answered unvalidated; this action will be logged. A signed domain
without chain-of-trust can be validated if a suitable trust-anchor
is provided using --trust-anchor. This change should be backwards
compatible for all existing working configurations; it extends the
space of possible configurations which are functional.
Fix a couple of problems with DNSSEC validation and DNAME. One
could cause validation failure on correct domains, and the other
would fail to spot an invalid domain. Thanks to Graham Clinch
for spotting the problem.
Add --log-queries=auth option to only log replies from the auth DNS
facility.
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 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.
--server with address set to # to use the unqualified servers.
--server with matching domain.
--server without domain or from /etc/resolv.conf.
Fix problems with ipset or nftset and TCP DNS transport. Previously
this was racy, and insertion of addresses could fail on a busy server
when DNS-over-TCP transport was involved.
DNSSEC validation change for reverse lookups in RFC-1918 ranges and friends.
The large public DNS services seem not to return proof-of-nonexistence
for DS records at the start of RFC-1918 in-addr.arpa domains and the their
IPv6 equivalents. 10.in-addr.arpa, 168.192.in-addr.arpa etc.
Since dnsmasq already has an option which instructs it not bother
upstream servers with pointless queries about these address ranges,
namely --bogus-priv, we extend that to enable behaviour which allows
dnsmasq to assume that insecure NXDOMAIN replies for these domains
are expected and to assume that the domains are legitimately unsigned.
This behaviour only matters when some address range is directed to
another upstream server using --rev-server. In that case it allows
replies from that server to pass DNSSEC validation. Without such a
server configured, queries are never sent upstream so they are never
validated and the new behaviour is moot.
Add support for leasequery to the dnsmasq DHCPv4 server.
This has to be specifically enabled with the --leasequery option.
Many thanks to JAXPORT, Jacksonville Port Authority for sponsoring
this enhancement to dnsmasq.
Fix failure to cache PTR RRs when a reply contains more than one answer.
Thanks to Dmitry for spotting this.
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
Dominik Derigs for the bug report.
@@ -7,9 +123,9 @@ version 2.91
Literal address records are smaller and don't have
this field and don't need to be ordered on it.
To actually provoke this bug seems to need the same server-literal
to be repeated twice, eg --address=/a/1.1.1.1 --address-/a/1.1.1.1
to be repeated twice, e.g., --address=/a/1.1.1.1 --address-/a/1.1.1.1
which is clearly rare in the wild, but if it did exist it could
provoke a SIGSEV. Thanks to Daniel Rhea for fuzzing this one.
provoke a SIGSEGV. Thanks to Daniel Rhea for fuzzing this one.
Fix buffer overflow when configured lease-change script name
is too long.
@@ -17,7 +133,7 @@ version 2.91
Improve behaviour in the face of non-responsive upstream TCP DNS
servers. Without shorter timeouts, clients are blocked for too long
and fail wuth their own timeouts.
and fail with their own timeouts.
Set --fast-dns-retries by default when doing DNSSEC. A single
downstream query can trigger many upstream queries. On an
@@ -36,7 +152,7 @@ version 2.91
empty answer.
Fix handling of EDNS0 UDP packet sizes.
When talking upstream we always add a pseudoheader, and set the
When talking upstream we always add a pseudo header, and set the
UDP packet size to --edns-packet-max. Answering queries from
downstream, we get the answer (either from upstream or local
data) If local data won't fit the advertised size (or 512 if
@@ -84,19 +200,48 @@ version 2.91
Handle DS queries to auth zones. When dnsmasq is configured to
act as an authoritative server and has an authoritative zone
configured, and recieves a query for that zone _as_forwarder_
configured, and receives a query for that zone _as_forwarder_
it answers the query directly rather than forwarding it. This
doesn't affect the answer, but it saves dnsmasq forwarding the
query to the recusor upstream, whch then bounces it back to dnsmasq
query to the recursor upstream, which then bounces it back to dnsmasq
in auth mode. The exception should be when the query is for the root
of zone, for a DS RR. The answer to that has to come from the parent,
via the recursor, and will typically be a proof-of-nonexistence
via the recursor, and will typically be a proof-of-non-existence
since dnsmasq doesn't support signed zones. This patch suppresses
local answers and forces forwarding to the upstream recursor for such
queries. It stops breakage when a DNSSEC validating client makes
queries to dnsmasq acting as forwarder for a zone for which it is
authoritative.
Implement "DNS-0x20 encoding", for extra protection against
reply-spoof attacks. Since DNS queries are case-insensitive,
it's possible to randomly flip the case of letters in a query
and still get the correct answer back.
This adds an extra dimension for a cache-poisoning attacker
to guess when sending replies in-the-blind since it's expected
that the legitimate answer will have the same pattern of upper
and lower case as the query, so any replies which don't can be
ignored as malicious. The amount of extra entropy clearly depends
on the number of a-z and A-Z characters in the query, and this
implementation puts a hard limit of 32 bits to make resource
allocation easy. This about doubles entropy over the standard
random ID and random port combination. This technique can interact
badly with rare broken DNS servers which don't preserve the case
of the query in their reply. The first time a reply is returned
which matches the query in all respects except case, a warning
will be logged. In this release, 0x020-encoding is default-off
and must be explicitly enabled with --do-0x20-encoding. In future
releases it may default on. You can avoid a future release
changing the behaviour of an installation with --no-x20-encode.
Fix a long-standing problem when two queries which are identical
in every respect _except_ case, get combined by dnsmasq. If
dnsmasq gets eg, two queries for example.com and Example.com
in quick succession it will get the answer for example.com from
upstream and send that answer to both requestors. This means that
the query for Example.com will get an answer for example.com, and
in the modern DNS, that answer may not be accepted.
version 2.90
Fix reversion in --rev-server introduced in 2.88 which
@@ -108,7 +253,7 @@ version 2.90
for a particular domain. Thanks to Daniel Danzberger for
spotting this bug.
Set the default maximum DNS UDP packet sice to 1232. This
Set the default maximum DNS UDP packet size to 1232. This
has been the recommended value since 2020 because it's the
largest value that avoid fragmentation, and fragmentation
is just not reliable on the modern internet, especially
@@ -116,14 +261,14 @@ version 2.90
--edns-packet-max for special circumstances.
Add --no-dhcpv4-interface and --no-dhcpv6-interface for
better control over which inetrfaces are providing DHCP service.
better control over which interfaces are providing DHCP service.
Fix issue with stale caching: After replying with stale data,
dnsmasq sends the query upstream to refresh the cache asynchronously
and sometimes sends the wrong packet: packet length can be wrong,
and if an EDE marking stale data is added to the answer that can
end up in the query also. This bug only seems to cause problems
when the usptream server is a DOH/DOT proxy. Thanks to Justin He
when the upstream server is a DOH/DOT proxy. Thanks to Justin He
for the bug report.
Add configurable caching for arbitrary RR-types.
@@ -161,7 +306,7 @@ version 2.90
Applied Cybersecurity ATHENE for finding this vulnerability.
CVE 2023-50387 and CVE 2023-50868 apply.
Note that the is a security vulnerablity only when DNSSEC validation
Note that this a security vulnerability only when DNSSEC validation
is enabled.
Fix memory-leak when attempting to cache SRV records with zero TTL.
@@ -247,7 +392,7 @@ version 2.88
upstream servers from /etc/resolv.conf or other sources that
can change dnsmasq tries to avoid memory fragmentation by re-using
existing records that are being re-read unchanged. This involves
seaching all the server records for each new one installed.
searching all the server records for each new one installed.
During startup this search is pointless, and can cause long
start times with thousands of --server options because the work
needed is O(n^2). Handle this case more intelligently.
@@ -310,7 +455,7 @@ version 2.87
Enhance --domain to accept, for instance,
--domain=net2.thekelleys.org.uk,eth2 so that hosts get a domain
which relects the interface they are attached to in a way which
which reflects the interface they are attached to in a way which
doesn't require hard-coding addresses. Thanks to Sten Spans for
the idea.
@@ -684,22 +829,22 @@ version 2.80
but those which used the default of no checking will need to be
altered to explicitly select no checking. The new default is
because switching off checking for unsigned replies is
inherently dangerous. Not only does it open the possiblity of forged
inherently dangerous. Not only does it open the possibility of forged
replies, but it allows everything to appear to be working even
when the upstream namesevers do not support DNSSEC, and in this
case no DNSSEC validation at all is occuring.
case no DNSSEC validation at all is occurring.
Fix DHCP broken-ness when --no-ping AND --dhcp-sequential-ip
are set. Thanks to Daniel Miess for help with this.
Add a facilty to store DNS packets sent/recieved in a
Add a facility to store DNS packets sent/received in a
pcap-format file for later debugging. The file location
is given by the --dumpfile option, and a bitmap controlling
which packets should be dumped is given by the --dumpmask
option.
Handle the case of both standard and constructed dhcp-ranges on the
same interface better. We don't now contruct a dhcp-range if there's
same interface better. We don't now construct a dhcp-range if there's
already one specified. This allows the specified interface to
have different parameters and avoids advertising the same
prefix twice. Thanks to Luis Marsano for spotting this case.
@@ -1169,7 +1314,7 @@ version 2.73
Use inotify for checking on updates to /etc/resolv.conf and
friends under Linux. This fixes race conditions when the files are
updated rapidly and saves CPU by noy polling. To build
updated rapidly and saves CPU by not polling. To build
a binary that runs on old Linux kernels without inotify,
use make COPTS=-DNO_INOTIFY
@@ -1509,7 +1654,7 @@ version 2.68
are dynamic and works much better than the previous
work-around which exempted constructed DHCP ranges from the
IP address filtering. As a consequence, that work-around
is removed. Under certain circumstances, this change wil
is removed. Under certain circumstances, this change will
break existing configuration: if you're relying on the
constructed-range exception, you need to change --auth-zone
to specify the same interface as is used to construct your
@@ -1966,7 +2111,7 @@ version 2.61
Set the environment variable DNSMASQ_LOG_DHCP when running
the script id --log-dhcp is in effect, so that script can
taylor their logging verbosity. Suggestion from Malte
tailor their logging verbosity. Suggestion from Malte
Forkel.
Arrange that addresses specified with --listen-address
+1 -1
View File
@@ -1,4 +1,4 @@
# dnsmasq is Copyright (c) 2000-2024 Simon Kelley
# dnsmasq is Copyright (c) 2000-2025 Simon Kelley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
+695
View File
@@ -0,0 +1,695 @@
/* leasequery is Copyright (c) 2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Author's email: simon@thekelleys.org.uk */
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <limits.h>
#include <net/if.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <arpa/inet.h>
#include <ctype.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
#define INADDRSZ 4
#define ADDRSTRLEN 46
#define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
#define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
#define DHCP_CHADDR_MAX 16
#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
#define BOOTREQUEST 1
#define BOOTREPLY 2
#define DHCP_COOKIE 0x63825363
#define DHCPLEASEQUERY 10
#define DHCPLEASEUNASSIGNED 11
#define DHCPLEASEUNKNOWN 12
#define DHCPLEASEACTIVE 13
#define OPTION_END 255
#define OPTION_ROUTER 3
#define OPTION_VENDOR_CLASS_OPT 43
#define OPTION_LEASE_TIME 51
#define OPTION_MESSAGE_TYPE 53
#define OPTION_SERVER_IDENTIFIER 54
#define OPTION_REQUESTED_OPTIONS 55
#define OPTION_VENDOR_ID 60
#define OPTION_CLIENT_ID 61
/* flags in top of length field for DHCP-option tables */
#define OT_ADDR_LIST 0x8000
#define OT_RFC1035_NAME 0x4000
#define OT_INTERNAL 0x2000
#define OT_NAME 0x1000
#define OT_CSTRING 0x0800
#define OT_DEC 0x0400
#define OT_TIME 0x0200
#define GETSHORT(s, cp) do { \
unsigned char *t_cp = (unsigned char *)(cp); \
(s) = ((u16)t_cp[0] << 8) \
| ((u16)t_cp[1]) \
; \
(cp) += 2; \
} while(0)
#define GETLONG(l, cp) do { \
unsigned char *t_cp = (unsigned char *)(cp); \
(l) = ((u32)t_cp[0] << 24) \
| ((u32)t_cp[1] << 16) \
| ((u32)t_cp[2] << 8) \
| ((u32)t_cp[3]) \
; \
(cp) += 4; \
} while (0)
#define PUTSHORT(s, cp) do { \
u16 t_s = (u16)(s); \
unsigned char *t_cp = (unsigned char *)(cp); \
*t_cp++ = t_s >> 8; \
*t_cp = t_s; \
(cp) += 2; \
} while(0)
#define PUTLONG(l, cp) do { \
u32 t_l = (u32)(l); \
unsigned char *t_cp = (unsigned char *)(cp); \
*t_cp++ = t_l >> 24; \
*t_cp++ = t_l >> 16; \
*t_cp++ = t_l >> 8; \
*t_cp = t_l; \
(cp) += 4; \
} while (0)
union all_addr {
struct in_addr addr4;
struct in6_addr addr6;
};
struct dhcp_packet_with_opts{
struct dhcp_packet {
unsigned char op, htype, hlen, hops;
unsigned int xid;
unsigned short secs, flags;
struct in_addr ciaddr, yiaddr, siaddr, giaddr;
unsigned char chaddr[DHCP_CHADDR_MAX], sname[64], file[128];
} header;
unsigned char options[312];
};
static const struct opttab_t {
char *name;
u16 val, size;
} opttab[] = {
{ "netmask", 1, OT_ADDR_LIST },
{ "time-offset", 2, 4 },
{ "router", 3, OT_ADDR_LIST },
{ "dns-server", 6, OT_ADDR_LIST },
{ "log-server", 7, OT_ADDR_LIST },
{ "lpr-server", 9, OT_ADDR_LIST },
{ "hostname", 12, OT_INTERNAL | OT_NAME },
{ "boot-file-size", 13, 2 | OT_DEC },
{ "domain-name", 15, OT_NAME },
{ "swap-server", 16, OT_ADDR_LIST },
{ "root-path", 17, OT_NAME },
{ "extension-path", 18, OT_NAME },
{ "ip-forward-enable", 19, 1 },
{ "non-local-source-routing", 20, 1 },
{ "policy-filter", 21, OT_ADDR_LIST },
{ "max-datagram-reassembly", 22, 2 | OT_DEC },
{ "default-ttl", 23, 1 | OT_DEC },
{ "mtu", 26, 2 | OT_DEC },
{ "all-subnets-local", 27, 1 },
{ "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST },
{ "router-discovery", 31, 1 },
{ "router-solicitation", 32, OT_ADDR_LIST },
{ "static-route", 33, OT_ADDR_LIST },
{ "trailer-encapsulation", 34, 1 },
{ "arp-timeout", 35, 4 | OT_DEC },
{ "ethernet-encap", 36, 1 },
{ "tcp-ttl", 37, 1 },
{ "tcp-keepalive", 38, 4 | OT_DEC },
{ "nis-domain", 40, OT_NAME },
{ "nis-server", 41, OT_ADDR_LIST },
{ "ntp-server", 42, OT_ADDR_LIST },
{ "vendor-encap", 43, OT_INTERNAL },
{ "netbios-ns", 44, OT_ADDR_LIST },
{ "netbios-dd", 45, OT_ADDR_LIST },
{ "netbios-nodetype", 46, 1 },
{ "netbios-scope", 47, 0 },
{ "x-windows-fs", 48, OT_ADDR_LIST },
{ "x-windows-dm", 49, OT_ADDR_LIST },
{ "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST },
{ "lease-time", 51, OT_INTERNAL | OT_TIME },
{ "option-overload", 52, OT_INTERNAL },
{ "message-type", 53, OT_INTERNAL | OT_DEC },
{ "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST },
{ "parameter-request", 55, OT_INTERNAL },
{ "message", 56, OT_INTERNAL },
{ "max-message-size", 57, OT_INTERNAL },
{ "T1", 58, OT_TIME},
{ "T2", 59, OT_TIME},
{ "vendor-class", 60, OT_NAME },
{ "client-id", 61, OT_INTERNAL },
{ "nis+-domain", 64, OT_NAME },
{ "nis+-server", 65, OT_ADDR_LIST },
{ "tftp-server", 66, OT_NAME },
{ "bootfile-name", 67, OT_NAME },
{ "mobile-ip-home", 68, OT_ADDR_LIST },
{ "smtp-server", 69, OT_ADDR_LIST },
{ "pop3-server", 70, OT_ADDR_LIST },
{ "nntp-server", 71, OT_ADDR_LIST },
{ "irc-server", 74, OT_ADDR_LIST },
{ "user-class", 77, 0 },
{ "rapid-commit", 80, 0 },
{ "FQDN", 81, OT_INTERNAL },
{ "agent-info", 82, OT_INTERNAL },
{ "last-transaction", 91, 4 | OT_TIME },
{ "associated-ip", 92, OT_ADDR_LIST },
{ "client-arch", 93, 2 | OT_DEC },
{ "client-interface-id", 94, 0 },
{ "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 */
{ "subnet-select", 118, OT_INTERNAL },
{ "domain-search", 119, OT_RFC1035_NAME },
{ "sip-server", 120, 0 },
{ "classless-static-route", 121, 0 },
{ "vendor-id-encap", 125, 0 },
{ "tftp-server-address", 150, OT_ADDR_LIST },
{ "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */
{ NULL, 0, 0 }
};
static void prettyprint_time(char *buf, unsigned int t);
static char *print_mac(char *buff, unsigned char *mac, int len);
int lookup_dhcp_opt(char *name)
{
const struct opttab_t *t = opttab;
int i;
for (i = 0; t[i].name; i++)
if (strcasecmp(t[i].name, name) == 0)
return t[i].val;
return -1;
}
char *option_string(unsigned int opt, unsigned char *val, int opt_len, char *buf, int buf_len)
{
int o, i, j, nodecode = 0;
const struct opttab_t *ot = opttab;
char addrbuff[ADDRSTRLEN];
for (o = 0; ot[o].name; o++)
if (ot[o].val == opt)
{
if (buf)
{
memset(buf, 0, buf_len);
if (ot[o].size & OT_ADDR_LIST)
{
union all_addr addr;
int addr_len = INADDRSZ;
for (buf[0]= 0, i = 0; i <= opt_len - addr_len; i += addr_len)
{
if (i != 0)
strncat(buf, ", ", buf_len - strlen(buf));
/* align */
memcpy(&addr, &val[i], addr_len);
inet_ntop(AF_INET, &val[i], addrbuff, ADDRSTRLEN);
strncat(buf, addrbuff, buf_len - strlen(buf));
}
}
else if (ot[o].size & OT_NAME)
for (i = 0, j = 0; i < opt_len && j < buf_len ; i++)
{
char c = val[i];
if (isprint((unsigned char)c))
buf[j++] = c;
}
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{
unsigned int dec = 0;
for (i = 0; i < opt_len; i++)
dec = (dec << 8) | val[i];
if (ot[o].size & OT_TIME)
prettyprint_time(buf, dec);
else
sprintf(buf, "%u", dec);
}
else
nodecode = 1;
}
break;
}
if (opt_len != 0 && buf && (!ot[o].name || nodecode))
{
int trunc = 0;
if (opt_len > 14)
{
trunc = 1;
opt_len = 14;
}
print_mac(buf, val, opt_len);
if (trunc)
strncat(buf, "...", buf_len - strlen(buf));
}
return ot[o].name ? ot[o].name : "";
}
static void prettyprint_time(char *buf, unsigned int t)
{
if (t == 0xffffffff)
sprintf(buf, "infinite");
else
{
unsigned int x, p = 0;
if ((x = t/86400))
p += sprintf(&buf[p], "%ud", x);
if ((x = (t/3600)%24))
p += sprintf(&buf[p], "%uh", x);
if ((x = (t/60)%60))
p += sprintf(&buf[p], "%um", x);
if ((x = t%60))
sprintf(&buf[p], "%us", x);
}
}
static 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) ? "" : ":");
return buff;
}
static unsigned char *dhcp_skip_opts(struct dhcp_packet_with_opts *pktp)
{
unsigned char *start = (unsigned char *)(((unsigned int *)&pktp->options[0]) + 1);
while (*start != 0)
start += start[1] + 2;
return start;
}
static unsigned char *dhcp_find_opt(struct dhcp_packet_with_opts *pktp, int optno)
{
unsigned char *start = (unsigned char *)(((unsigned int *)&pktp->options[0]) + 1);
while (*start != OPTION_END)
{
if (*start == optno)
return start;
start += start[1] + 2;
}
return NULL;
}
static void option_put(struct dhcp_packet_with_opts *pktp, int opt, int len, unsigned int val)
{
int i;
unsigned char *p = dhcp_skip_opts(pktp);
if (p)
{
*p++ = opt;
*p++ = len;
for (i = 0; i < len; i++)
*(p++) = val >> (8 * (len - (i + 1)));
}
}
static void option_put_string(struct dhcp_packet_with_opts *pktp, int opt,
const char *string, int null_term)
{
unsigned char *p;
size_t len = strlen(string);
if (null_term && len != 255)
len++;
if ((p = dhcp_skip_opts(pktp)))
{
*p++ = opt;
*p++ = len;
memcpy(p, string, len);
}
}
/* in may equal out, when maxlen may be -1 (No max len).
Return -1 for extraneous no-hex chars found. */
int parse_hex(char *in, unsigned char *out, int maxlen,
unsigned int *wildcard_mask, int *mac_type)
{
int done = 0, mask = 0, i = 0;
char *r;
if (mac_type)
*mac_type = 0;
while (!done && (maxlen == -1 || i < maxlen))
{
for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++)
if (*r != '*' && !isxdigit((unsigned char)*r))
return -1;
if (*r == 0)
done = 1;
if (r != in )
{
if (*r == '-' && i == 0 && mac_type)
{
*r = 0;
*mac_type = strtol(in, NULL, 16);
mac_type = NULL;
}
else
{
*r = 0;
if (strcmp(in, "*") == 0)
{
mask = (mask << 1) | 1;
i++;
}
else
{
int j, bytes = (1 + (r - in))/2;
for (j = 0; j < bytes; j++)
{
char sav;
if (j < bytes - 1)
{
sav = in[(j+1)*2];
in[(j+1)*2] = 0;
}
/* checks above allow mix of hexdigit and *, which
is illegal. */
if (strchr(&in[j*2], '*'))
return -1;
out[i] = strtol(&in[j*2], NULL, 16);
mask = mask << 1;
if (++i == maxlen)
break;
if (j < bytes - 1)
in[(j+1)*2] = sav;
}
}
}
}
in = r+1;
}
if (wildcard_mask)
*wildcard_mask = mask;
return i;
}
static char *split_chr(char *s, char c)
{
char *comma, *p;
if (!s || !(comma = strchr(s, c)))
return NULL;
p = comma;
*comma = ' ';
for (; *comma == ' '; comma++);
for (; (p >= s) && *p == ' '; p--)
*p = 0;
return comma;
}
static char *split(char *s)
{
return split_chr(s, ',');
}
int main(int argc, char **argv)
{
int fd;
struct ifreq ifr;
struct in_addr iface_addr, server_addr, lease_addr;
struct dhcp_packet_with_opts pkt;
struct sockaddr_in saddr;
unsigned char *p;
ssize_t sz;
unsigned char mac[DHCP_CHADDR_MAX], clid[256], req_options[256];
char buff[500];
int clid_len = 0, mac_len = 0, mac_type = 0, opts_len = 0;
unsigned short port = DHCP_SERVER_PORT;
unsigned int xid;
struct timeval tv;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
xid = rand();
server_addr.s_addr = lease_addr.s_addr = iface_addr.s_addr = 0;
if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("leasequery: cannot create socket");
exit(1);
}
while (1)
{
int option = getopt(argc, argv, "i:s:l:m:c:p:r:");
if (option == -1)
break;
switch (option)
{
default:
fprintf(stderr,
"-p <port> port on DHCP server.\n"
"-i <interface> exit interface to DHCP server.\n"
"-s <inet-addr> DHCP server address.\n"
"-l <inet-addr> Query lease by IP address.\n"
"-m <MAC-addr> Query lease by MAC address.\n"
"-c <hex> Query lease by client-id.\n"
"-r <name>|<int>[,...] List of options to return.\n");
exit(1);
case 'p':
port = atoi(optarg);
break;
case 'i':
strncpy(ifr.ifr_name, optarg, IF_NAMESIZE);
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl(fd, SIOCGIFADDR, &ifr) == -1)
{
perror("leasequery: cannot get interface address");
exit(1);
}
iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
break;
case 's':
if (inet_pton(AF_INET, optarg, &server_addr) <= 0)
{
fprintf(stderr, "leasequery: bad server address\n");
exit(1);
}
break;
case 'l':
if (inet_pton(AF_INET, optarg, &lease_addr) <= 0)
{
fprintf(stderr, "leasequery: bad lease address\n");
exit(1);
}
break;
case 'm':
mac_type = 1; /* default ethernet */
mac_len = parse_hex(optarg, mac, DHCP_CHADDR_MAX, NULL, &mac_type);
if (!mac_type)
mac_type = 1; /* default ethernet */
break;
case 'c':
clid_len = parse_hex(optarg, clid, 256, NULL, NULL);
break;
case 'r':
{
char *comma;
int opt;
while (optarg)
{
comma = split(optarg);
if ((opt = lookup_dhcp_opt(optarg)) != -1 || (opt = atoi(optarg)) != 0)
req_options[opts_len++] = opt;
optarg = comma;
}
break;
}
}
}
if (!server_addr.s_addr)
{
fprintf(stderr, "leasequery: no server address\n");
exit(1);
}
memset(&pkt, 0, sizeof pkt);
pkt.header.op = BOOTREQUEST;
pkt.header.xid = xid;
pkt.header.ciaddr = lease_addr;
pkt.header.hlen = mac_len;
pkt.header.htype = mac_type;
memcpy(pkt.header.chaddr, mac, mac_len);
*((unsigned int *)&pkt.options[0]) = htonl(DHCP_COOKIE);
/* Dnsmasq extension. */
pkt.header.giaddr.s_addr = iface_addr.s_addr ? iface_addr.s_addr : INADDR_BROADCAST;
option_put(&pkt, OPTION_MESSAGE_TYPE, 1, DHCPLEASEQUERY);
if (clid_len != 0)
{
p = dhcp_skip_opts(&pkt);
*p++ = OPTION_CLIENT_ID;
*p++ = clid_len;
memcpy(p, clid, clid_len);
}
if (opts_len != 0)
{
p = dhcp_skip_opts(&pkt);
*p++ = OPTION_REQUESTED_OPTIONS;
*p++ = opts_len;
memcpy(p, req_options, opts_len);
}
if (iface_addr.s_addr)
{
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = iface_addr.s_addr;
if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)))
{
perror("leasequery: cannot bind DHCP server socket");
exit(1);
}
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr = server_addr;
while((sz = sendto(fd, &pkt, dhcp_skip_opts(&pkt) - ((unsigned char *)&pkt), 0, (struct sockaddr *)&saddr, sizeof(saddr))) == -1 &&
errno == EINTR);
if (sz == -1)
{
perror("leasequery: sendto()");
exit(1);
}
sz = recv(fd, &pkt, sizeof(pkt), 0);
if (sz == -1)
{
perror("leasequery: recv()");
exit(1);
}
if (sz >= sizeof(pkt.header) &&
pkt.header.op == BOOTREPLY &&
(p = dhcp_find_opt(&pkt, OPTION_MESSAGE_TYPE)) &&
pkt.header.xid == xid)
{
if (p[2] == DHCPLEASEUNASSIGNED)
printf("UNASSIGNED\n");
else if (p[2] == DHCPLEASEUNKNOWN)
printf("UNKNOWN\n");
else if (p[2] == DHCPLEASEACTIVE)
{
print_mac(buff, pkt.header.chaddr, pkt.header.hlen);
printf("ACTIVE %s %s\n", inet_ntoa(pkt.header.ciaddr), buff);
p = (unsigned char *)(((unsigned int *)&pkt.options[0]) + 1);
while (*p != OPTION_END)
{
if (*p != OPTION_MESSAGE_TYPE)
{
char *optname = option_string(p[0], option_ptr(p, 0), option_len(p), buff, 500);
printf("size:%3d option:%3d %s %s\n", option_len(p), p[0], optname, buff);
}
p += p[1] + 2;
}
}
}
}
Submodule debian deleted from 83e05da879.
Symlink
+1
View File
@@ -0,0 +1 @@
submodules/dnsmasq-debian/debian
+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>
+189 -116
View File
@@ -1,4 +1,4 @@
.TH DNSMASQ 8 2021-08-16
.TH DNSMASQ 8 2025-02-05
.SH NAME
dnsmasq \- A lightweight DHCP and caching DNS server.
.SH SYNOPSIS
@@ -134,7 +134,10 @@ only, to stop dnsmasq daemonising in production, use
Log the results of DNS queries handled by dnsmasq. Enable a full cache dump on receipt of SIGUSR1. If the argument "extra" is supplied, ie
.B --log-queries=extra
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.
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
@@ -345,6 +348,10 @@ Bogus private reverse lookups. All reverse lookups for private IP ranges (ie 192
which are not found in /etc/hosts or the DHCP leases file are answered
with "no such domain" rather than being forwarded upstream. The
set of prefixes affected is the list given in RFC6303, for IPv4 and IPv6.
Enabling this also subtly alters DNSSEC validation for reverse lookups in the
private ranges such that a non-secure DS record is accepted as proof that
the range is not signed. This works around behaviour by the public DNS services
which seem not to return validated proof-of-non-existence for DS records in these domains.
.TP
.B \-V, --alias=[<old-ip>]|[<start-ip>-<end-ip>],<new-ip>[,<mask>]
Modify IPv4 addresses returned from upstream nameservers; old-ip is
@@ -498,10 +505,7 @@ xxx.internal.thekelleys.org.uk at 192.168.1.1 then giving the flag
.B --server=/internal.thekelleys.org.uk/192.168.1.1
will send all queries for
internal machines to that nameserver, everything else will go to the
servers in /etc/resolv.conf. DNSSEC validation is turned off for such
private nameservers, UNLESS a
.B --trust-anchor
is specified for the domain in question. An empty domain specification,
servers in /etc/resolv.conf. An empty domain specification,
.B //
has the special meaning of "unqualified names only" ie names without any
dots in them. A non-standard port may be specified as
@@ -671,7 +675,7 @@ machine on which dnsmasq is running) for each
local machine. Local machines are those in /etc/hosts or with DHCP
leases.
.TP
.B \-W, --srv-host=<_service>.<_prot>.[<domain>],[<target>[,<port>[,<priority>[,<weight>]]]]
.B \-W, --srv-host=<_service>.<_prot>[.<domain>],[<target>[,<port>[,<priority>[,<weight>]]]]
Return a SRV DNS record. See RFC2782 for details. If not supplied, the
domain defaults to that given by
.B --domain.
@@ -852,6 +856,18 @@ name on successive queries, for load-balancing. This turns off that
behaviour, so that the records are always returned in the order
that they are received from upstream.
.TP
.B --do-0x20-encode, --no-0x20-encode
Dnsmasq can scramble the case of letters in DNS queries it sends upstream as a security feature.
This technique can interact badly with rare broken DNS servers which don't preserve the case
of the query in their reply. The first time a reply is returned
which matches the query in all respects except case, a warning
will be logged. If this coincides with DNS not functioning, it
is necessary to disable the feature. As at version 2.91, 0x20 encoding
is disabled by default, and must be enabled with --do-0x20-encode. The default
may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the
data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense
@@ -864,7 +880,7 @@ Set the maximum number of concurrent DNS queries. The default value is
150, which should be fine for most setups. The only known situation
where this needs to be increased is when using web-server log file
resolvers, which can generate large numbers of concurrent queries. This
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has it's own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has its own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
.TP
.B --dnssec
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
@@ -882,12 +898,15 @@ ie capable of returning DNSSEC records with data. If they are not,
then dnsmasq will not be able to determine the trusted status of
answers and this means that DNS service will be entirely broken.
.TP
.B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
.B --trust-anchor=<domain>,[<class>,][<key-tag>,<algorithm>,<digest-type>,<digest>]
Provide DS records to act a trust anchors for DNSSEC
validation. Typically these will be the DS record(s) for Key Signing
validation. The class defaults to IN. Typically these will be the DS record(s) for Key Signing
key(s) (KSK) of the root zone,
but trust anchors for limited domains are also possible. The current
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
but trust anchors for limited domains are also possible.
A negative trust anchor (ie. proof that a DS record doesn't exist) may be configured be specifying
only the name or only the name and class. This can be useful for forcing dnsmasq to treat zones delegated
using \fB--server=/<domain>/<ip-address>\fP as unsigned. The current
root-zone trust anchors may be downloaded from https://data.iana.org/root-anchors/root-anchors.xml
.TP
.B --dnssec-check-unsigned[=no]
As a default, dnsmasq checks that unsigned DNS replies are
@@ -901,7 +920,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
@@ -1180,7 +1199,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
@@ -1188,7 +1207,7 @@ available, though it normally is for direct-connected clients, or
clients using DHCP relays which support RFC 6939.
For DHCPv4, the special option id:* means "ignore any client-id
For DHCPv4, the special option \fBid:*\fP means "ignore any client-id
and use MAC addresses only." This is useful when a client presents a client-id sometimes
but not others.
@@ -1216,36 +1235,34 @@ the result is undefined. A corollary to this is that the name associated with a
does not appear in the DNS until the host obtains a DHCP lease.
The special keyword "ignore"
tells dnsmasq to never offer a DHCP lease to a machine. The machine
can be specified by hardware address, client ID or hostname, for
instance
.B --dhcp-host=00:20:e0:3b:13:af,ignore
This is
useful when there is another DHCP server on the network which should
The special keyword \fBignore\fP tells dnsmasq never to offer a DHCP lease
to a machine. The machine
can be specified by hardware address, client ID or hostname. For
example: \fB--dhcp-host=00:20:e0:3b:13:af,ignore\fP.
This can be useful when there is another DHCP server on the network which should
be used by some machines.
The set:<tag> construct sets the tag
The \fBset:<tag>\fP construct sets the tag
whenever this \fB--dhcp-host\fP directive is in use. This can be used to
selectively send DHCP options just for this host. More than one tag
can be set in a \fB--dhcp-host\fP directive (but not in other places where
"set:<tag>" is allowed). When a host matches any
\fBset:<tag>\fP is allowed). When a host matches any
\fB--dhcp-host\fP directive (or one implied by /etc/ethers) then the special
tag "known" is set. This allows dnsmasq to be configured to
tag \fBknown\fP is set. This allows dnsmasq to be configured to
ignore requests from unknown machines using
.B --dhcp-ignore=tag:!known
\fB--dhcp-ignore=tag:!known\fP.
If the host matches only a \fB--dhcp-host\fP directive which cannot
be used because it specifies an address on different subnet, the tag "known-othernet" is set.
be used because it specifies an address on a different subnet, the tag \fBknown-othernet\fP is set.
The tag:<tag> construct filters which dhcp-host directives are used; more than
one can be provided, in this case the request must match all of them. Tagged
The \fBtag:<tag>\fP construct filters which dhcp-host directives are used. More than
one tag can be provided. In this case, the request must match all tags. Tagged
directives are used in preference to untagged ones. Note that one of <hwaddr>,
<client_id> or <hostname> still needs to be specified (can be a wildcard).
<client_id> or <hostname> still must be specified (can be a wildcard).
Ethernet addresses (but not client-ids) may have
wildcard bytes, so for example
.B --dhcp-host=00:20:e0:3b:13:*,ignore
will cause dnsmasq to ignore a range of hardware addresses. Note that
will cause dnsmasq to ignore the given range of hardware addresses. Note that
the "*" will need to be escaped or quoted on a command line, but not
in the configuration file.
@@ -1259,11 +1276,11 @@ is 6.
As a special case, in DHCPv4, it is possible to include more than one
hardware address. eg:
.B --dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2
\fB--dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2\fP.
This allows an IP address to be associated with
multiple hardware addresses, and gives dnsmasq permission to abandon a
DHCP lease to one of the hardware addresses when another one asks for
a lease. Beware that this is a dangerous thing to do, it will only
a lease. Beware that this is a dangerous thing to do: it will only
work reliably if only one of the hardware addresses is active at any
time and there is no way for dnsmasq to enforce this. It is, for instance,
useful to allocate a stable IP address to a laptop which
@@ -1310,31 +1327,29 @@ options containing the same information. /etc/ethers is re-read when
dnsmasq receives SIGHUP. IPv6 addresses are NOT read from /etc/ethers.
.TP
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt-name>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
Specify different or extra options to DHCP clients. By default,
Send various extra options to DHCP clients. By default,
dnsmasq sends some standard options to DHCP clients, the netmask and
broadcast address are set to the same as the host running dnsmasq, and
the DNS server and default route are set to the address of the machine
running dnsmasq. (Equivalent rules apply for IPv6.) If the domain name option has been set, that is sent.
This configuration allows these defaults to be overridden,
or other options specified. The option, to be sent may be given as a
decimal number or as "option:<option-name>" The option numbers are
or other options specified. The option to be sent may be given as a
decimal number or as \fBoption:<option-name>\fP.
Option numbers are
specified in RFC2132 and subsequent RFCs. The set of option-names
known by dnsmasq can be discovered by running "dnsmasq --help dhcp".
For example, to set the default route option to
192.168.4.4, do
.B --dhcp-option=3,192.168.4.4
or
.B --dhcp-option = option:router, 192.168.4.4
and to set the time-server address to 192.168.0.4, do
.B --dhcp-option = 42,192.168.0.4
or
.B --dhcp-option = option:ntp-server, 192.168.0.4
The special address 0.0.0.0 is taken to mean "the address of the
machine running dnsmasq".
known by dnsmasq can be discovered by running \fBdnsmasq --help dhcp\fP.
For example, to set the default route option to 192.168.4.4, use
\fB--dhcp-option=3,192.168.4.4\fP or
\fB--dhcp-option=option:router,192.168.4.4\fP
and to set the time-server address to 192.168.0.4, use
\fB--dhcp-option=42,192.168.0.4\fP or
\fB--dhcp-option=option:ntp-server,192.168.0.4\fP.
The special address 0.0.0.0 means "the address of the system running dnsmasq".
An option without data is valid, and includes just the option without data.
(There is only one option with a zero length data field currently defined for DHCPv4, 80:rapid commit, so this feature is not very useful in practice). Options for which dnsmasq normally
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.
@@ -1351,30 +1366,29 @@ to option 120 are handled as per RFC 3361. Dotted-quad IP addresses
which are followed by a slash and then a netmask size are encoded as
described in RFC 3442.
IPv6 options are specified using the
.B option6:
IPv6 options are specified using the \fBoption6:\fP
keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg.
.B --dhcp-option=option6:ntp-server,[1234::56]
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
For IPv6, [::] means "the global address of
the machine running dnsmasq", whilst [fd00::] is replaced with the
ULA, if it exists, and [fe80::] with the link-local address.
Be careful: no checking is done that the correct type of data for the
option number is sent, it is quite possible to
persuade dnsmasq to generate illegal DHCP packets with injudicious use
of this flag. When the value is a decimal number, dnsmasq must determine how
Be careful: data-type suitability for the option number sent is not checked.
It is quite possible to persuade dnsmasq to generate illegal DHCP packets with
injudicious use of this flag.
When the value is a decimal number, dnsmasq must determine how
large the data item is. It does this by examining the option number and/or the
value, but can be overridden by appending a single letter flag as follows:
b = one byte, s = two bytes, i = four bytes. This is mainly useful with
encapsulated vendor class options (see below) where dnsmasq cannot
determine data size from the option number. Option data which
determine data size from the option number. Option data which
consists solely of periods and digits will be interpreted by dnsmasq
as an IP address, and inserted into an option as such. To force a
literal string, use quotes. For instance when using option 66 to send
a literal IP address as TFTP server name, it is necessary to do
.B --dhcp-option=66,"1.2.3.4"
a literal IP address as TFTP server name, it is necessary to do
\fB--dhcp-option=66,"1.2.3.4"\fP.
Encapsulated Vendor-class options may also be specified (IPv4 only) using
\fB--dhcp-option\fP: for instance
@@ -1403,7 +1417,7 @@ Vendor Options" as specified by RFC3925. These are denoted like this:
The number in the vi-encap: section is the IANA enterprise number
used to identify this option. This form of encapsulation is supported
in IPv6.
The address 0.0.0.0 is not treated specially in
encapsulated options.
.TP
@@ -1426,6 +1440,18 @@ DHCP options. This make extra space available in the DHCP packet for
options but can, rarely, confuse old or broken clients. This flag
forces "simple and safe" behaviour to avoid problems in such a case.
.TP
.B --leasequery[=<addr>[/<prefix>]]
Enable RFC 4388 leasequery. The dnsmasq DHCP server will answer LEASEQUERY messages from DHCP relays
when this option is given. If an address or subnet is given, only queries from a relay at that source are allowed. Repeat
the --leasequery option to specify multiple allowed sources.
To correctly answer lease queries it is necessary to store extra data in
the lease database, and this is also enabled by the \fB--leasequery\fP option. The extra fields
(agent-info and vendorclass) are stored in the leases file in a somewhat backwards compatible manner.
Enabling and then disabling leasequery will not cause problems; the extra information will be aged
out of the database. However, enabling leasequery in release 2.92 or later, storing leases
which come via DHCP relays which add option-82 agent-info data, and then moving back to a pre-2.92 dnsmasq
binary may cause problems reading the leases file. As of release 2.92, leasequery is only supported for DHCPv4.
.TP
.B --dhcp-relay=<local address>[,<server address>[#<server port>]][,<interface]
Configure dnsmasq to do DHCP relay. The local address is an address
allocated to an interface on the host running dnsmasq. All DHCP
@@ -1463,7 +1489,31 @@ 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 --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
@@ -1529,45 +1579,47 @@ via relays. If a list of IP addresses is given, only interactions via
relays at those addresses are affected.
.TP
.B --dhcp-match=set:<tag>,<option number>|option:<option name>|vi-encap:<enterprise>[,<value>]
Without a value, set the tag if the client sends a DHCP
option of the given number or name. When a value is given, set the tag only if
the option is sent and matches the value. The value may be of the form
Without \fB<value>\fP, set \fB<tag>\fP if the client sends a DHCP
\fBoption\fP of the given number or name. With \fB<value>\fP, set \fB<tag>\fP only if
the client sends the \fBoption\fP matching or containing \fB<value>\fP.
The value may be of the form
"01:ff:*:02" in which case the value must match (apart from wildcards)
but the option sent may have unmatched data past the end of the
value. The value may also be of the same form as in
.B --dhcp-option
in which case the option sent is treated as an array, and one element
must match, so
must match.
.B --dhcp-match=set:efi-ia32,option:client-arch,6
will set the tag "efi-ia32" if the the number 6 appears in the list of
sets the tag \fBefi-ia32\fP if the number \fB6\fP appears in the list of
architectures sent by the client in option 93. (See RFC 4578 for
details.) If the value is a string, substring matching is used.
details.) If the value is a string, substring matching is used.
The special form with vi-encap:<enterprise number> matches against
vendor-identifying vendor classes for the specified enterprise. Please
see RFC 3925 for more details of these rare and interesting beasts.
.TP
.B --dhcp-name-match=set:<tag>,<name>[*]
Set the tag if the given name is supplied by a DHCP client. There may be a single trailing wildcard *, which has the usual meaning. Combined with dhcp-ignore or dhcp-ignore-names this gives the ability to ignore certain clients by name, or disallow certain hostnames from being claimed by a client.
Set a \fB<tag>\fP if the given \fB<name>\fP is supplied by a DHCP client. There may be a single trailing wildcard *, with a glob meaning. Combined with \fBdhcp-ignore\fP or \fBdhcp-ignore-names\fP this gives the ability to ignore certain clients by name, or disallow certain hostnames from being claimed by a client.
.TP
.B --tag-if=set:<tag>[,set:<tag>[,tag:<tag>[,tag:<tag>]]]
Perform boolean operations on tags. Any tag appearing as set:<tag> is set if
all the tags which appear as tag:<tag> are set, (or unset when tag:!<tag> is used)
If no tag:<tag> appears set:<tag> tags are set unconditionally.
Any number of set: and tag: forms may appear, in any order.
\fB--tag-if\fP lines are executed in order, so if the tag in tag:<tag> is a
Perform boolean operations on tags. All \fBset:<tag>\fP tags are set if
all \fBtag:<tag>\fP are already set, (or unset when \fBtag:!<tag>\fP is used).
If no \fBtag:<tag>\fP appears, \fBset:<tag>\fP tags are set unconditionally.
Any number of \fBset:\fP and \fBtag:\fP forms may appear, in any order.
\fB--tag-if\fP lines are executed in order, so if the tag in \fBtag:<tag>\fP is a
tag set by another
.B --tag-if,
the line which sets the tag must precede the one which tests it.
the line which sets the tag must precede the line which tests it.
As an extension, the tag:<tag> clauses support limited wildcard matching,
similar to the matching in the \fB--interface\fP directive. This allows, for
example, using \fB--tag-if=set:ppp,tag:ppp*\fP to set the tag 'ppp' for all requests
received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction
with the tag:!<tag> format meaning that no tag matching the wildcard may be set.
As an extension, the \fBtag:<tag>\fP clauses support limited wildcard matching,
similar to the matching in the \fB--interface\fP directive. This allows the
example \fB--tag-if=set:ppp,tag:ppp*\fP to set the tag 'ppp' for all requests
received on any matching interface (ppp0, ppp1, etc). This can be used in conjunction
with the \fBtag:!<tag>\fP format meaning that no tag matching the wildcard may be set.
.TP
.B \-J, --dhcp-ignore=tag:<tag>[,tag:<tag>]
When all the given tags appear in the tag set ignore the host and do
When all the given tags appear in the tag set, ignore the host and do
not allocate it a DHCP lease.
.TP
.B --dhcp-ignore-names[=tag:<tag>[,tag:<tag>]]
@@ -1622,7 +1674,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
@@ -1713,7 +1765,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
@@ -1901,7 +1955,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
@@ -2252,7 +2306,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
@@ -2416,48 +2470,67 @@ the CNAME. To work around this, add the CNAME to /etc/hosts so that
the CNAME is shadowed too.
.PP
The tag system works as follows: For each DHCP request, dnsmasq
collects a set of valid tags from active configuration lines which
include set:<tag>, including one from the
.B --dhcp-range
used to allocate the address, one from any matching
.B --dhcp-host
(and "known" or "known-othernet" if a \fB--dhcp-host\fP matches)
The tag "bootp" is set for BOOTP requests, and a tag whose name is the
name of the interface on which the request arrived is also set.
The tag system works as follows: dnsmasq tags each DHCP request
with tags from applicable configuration lines containing \fBset:<tag>\fP i.e.
Any configuration lines which include one or more tag:<tag> constructs
will only be valid if all that tags are matched in the set derived
above. Typically this is \fB--dhcp-option\fP.
.B --dhcp-option
which has tags will be used in preference to an untagged
.B --dhcp-option,
provided that _all_ the tags match somewhere in the
set collected as described above. The prefix '!' on a tag means 'not'
so \fB--dhcp-option=tag:!purple,3,1.2.3.4\fP sends the option when the
tag purple is not in the set of valid tags. (If using this in a
command line rather than a configuration file, be sure to escape !,
which is a shell metacharacter)
\fBset:<tag>\fP from the \fB--dhcp-range\fP used to allocate the address;
\fBset:<tag>\fP from any matching \fB--dhcp-host\fP (plus the tag \fBknown\fP or
\fBknown-othernet\fP).
BOOTP requests are tagged \fBbootp\fP. Each request is also tagged with the name of
the interface on which the request arrived.
Each configuration line containing one or more \fBtag:<tag>\fP constructs
applies when all its tags exist on the request. That is:
Configuration tag:A applies to a request set:tagged A.
Configuration tag:B applies to a request set:tagged B.
Configuration tag:A+B doesn't apply to a request set:tagged A.
Configuration tag:A+B doesn't apply to a request set:tagged B.
Configurations tag:A+B, tag:A, tag:B apply to a request set:tagged A+B.
\fBset:<tag>\fP constructs in \fB--dhcp-range\fP and \fB--dhcp-host\fP tag requests.
Use \fBtag:<tag>\fPs in \fB--dhcp-option\fPs to match \fBset:<tag>\fP and apply configurations.
A \fB--dhcp-option\fP with \fBtag:<tag>\fP is preferred over an untagged
\fB--dhcp-option\fP, provided that \fBall\fP its tags match somewhere in the
set gathered above.
The tag prefix '!' means 'not'.
\fB--dhcp-option=tag:!purple,3,1.2.3.4\fP sends the option when the request
is not tagged with purple. (The shell metacharacter '!' must be escaped on the
command line but not in a configuration file).
When selecting \fB--dhcp-option\fPs, a \fB--dhcp-range\fP tag is second class
to other tags, to make it easy to override options for
individual hosts, so:
When selecting \fB--dhcp-options\fP, a tag from \fB--dhcp-range\fP is second class
relative to other tags, to make it easy to override options for
individual hosts, so
.B --dhcp-range=set:interface1,......
.B --dhcp-host=set:myhost,.....
.B --dhcp-option=tag:interface1,option:nis-domain,"domain1"
.B --dhcp-option=tag:myhost,option:nis-domain,"domain2"
will set the NIS-domain to domain1 for hosts in the range, but
override that to domain2 for a particular host.
sets the NIS-domain to domain1 for hosts in the range, but
to domain2 for a particular host that may or may not fall in the range.
.PP
Note that for
.B --dhcp-range
both tag:<tag> and set:<tag> are allowed, to both select the range in
Note that for \fB--dhcp-range\fP both
\fBtag:<tag>\fP and
\fBset:<tag>\fP are possible, in order to both select the range in
use based on (eg) \fB--dhcp-host\fP, and to affect the options sent, based on
the range selected.
This system evolved from an earlier, more limited one and for backward
compatibility "net:" may be used instead of "tag:" and "set:" may be
The tag system evolved from an earlier, more limited system. For backward
compatibility, "net:" may be used instead of "tag:" and "set:" may be
omitted. (Except in
.B --dhcp-host,
where "net:" may be used instead of "set:".) For the same reason, '#'
@@ -2475,7 +2548,7 @@ configuration option is present to activate the DHCP server
on a particular network. (Setting \fB--bootp-dynamic\fP removes the need for
static address mappings.) The filename
parameter in a BOOTP request is used as a tag,
as is the tag "bootp", allowing some control over the options returned to
as is the tag \fBbootp\fP, allowing some control over the options returned to
different classes of hosts.
.SH AUTHORITATIVE CONFIGURATION
+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-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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)
{
+56 -43
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -103,9 +103,9 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
unsigned char *p, *ansp;
int qtype, qclass, rc;
int nameoffset, axfroffset = 0;
int q, anscount = 0, authcount = 0;
int anscount = 0, authcount = 0;
struct crec *crecp;
int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0, out_of_zone = 0;
int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0, out_of_zone = 0, notimp = 0;
struct auth_zone *zone = NULL;
struct addrlist *subnet = NULL;
char *cut;
@@ -116,18 +116,20 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
union all_addr addr;
struct cname *a, *candidate;
unsigned int wclen;
unsigned int log_flags = local_query ? 0 : F_NOERR;
if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
if (ntohs(header->qdcount) != 1)
return 0;
/* determine end of question section (we put answers there) */
if (!(ansp = skip_questions(header, qlen)))
return 0; /* bad packet */
/* now process each question, answers go in RRs after the question */
p = (unsigned char *)(header+1);
for (q = ntohs(header->qdcount); q != 0; q--)
if (OPCODE(header) != QUERY)
notimp = 1;
else
{
unsigned int flag = 0;
int found = 0;
@@ -137,7 +139,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
nameoffset = p - (unsigned char *)header;
/* now extract name as .-concatenated string into name */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -147,7 +149,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
{
auth = 0;
out_of_zone = 1;
continue;
goto done;
}
if ((qtype == T_PTR || qtype == T_SOA || qtype == T_NS) &&
@@ -162,7 +164,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
{
out_of_zone = 1;
auth = 0;
continue;
goto done;
}
else if (qtype == T_SOA)
soa = 1, found = 1;
@@ -210,7 +212,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (local_query || in_zone(zone, intr->name, NULL))
{
found = 1;
log_query(flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL, 0);
log_query(log_flags | flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL, 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL,
T_PTR, C_IN, "d", intr->name))
@@ -234,7 +236,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
strcat(name, ".");
strcat(name, zone->domain);
}
log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid), 0);
log_query(log_flags | flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid), 0);
found = 1;
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL,
@@ -243,7 +245,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
}
else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || in_zone(zone, name, NULL)))
{
log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid), 0);
log_query(log_flags | (crecp->flags & ~F_FORWARD), name, &addr, record_source(crecp->uid), 0);
found = 1;
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL,
@@ -257,7 +259,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (!found && is_rev_synth(flag, &addr, name) && (local_query || in_zone(zone, name, NULL)))
{
log_query(F_CONFIG | F_REVERSE | flag, name, &addr, NULL, 0);
log_query(log_flags | F_CONFIG | F_REVERSE | flag, name, &addr, NULL, 0);
found = 1;
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
@@ -269,9 +271,9 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (found)
nxdomain = 0;
else
log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | (auth ? F_AUTH : 0), NULL, &addr, NULL, 0);
log_query(log_flags | flag | F_NEG | F_NXDOMAIN | F_REVERSE | (auth ? F_AUTH : 0), NULL, &addr, NULL, 0);
continue;
goto done;
}
cname_restart:
@@ -288,7 +290,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
{
out_of_zone = 1;
auth = 0;
continue;
goto done;
}
}
@@ -300,7 +302,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (rc == 2 && qtype == T_MX)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>", 0);
log_query(log_flags | F_CONFIG | F_RRNAME, name, NULL, "<MX>", 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
NULL, T_MX, C_IN, "sd", rec->weight, rec->target))
anscount++;
@@ -315,7 +317,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (rc == 2 && qtype == T_SRV)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>", 0);
log_query(log_flags | F_CONFIG | F_RRNAME, name, NULL, "<SRV>", 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
NULL, T_SRV, C_IN, "sssd",
rec->priority, rec->weight, rec->srvport, rec->target))
@@ -349,7 +351,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (rc == 2 && txt->class == qtype)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, NULL, txt->class);
log_query(log_flags | F_CONFIG | F_RRNAME, name, NULL, NULL, txt->class);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
NULL, txt->class, C_IN, "t", txt->len, txt->txt))
anscount++;
@@ -363,7 +365,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (rc == 2 && qtype == T_TXT)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>", 0);
log_query(log_flags | F_CONFIG | F_RRNAME, name, NULL, "<TXT>", 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
NULL, T_TXT, C_IN, "t", txt->len, txt->txt))
anscount++;
@@ -377,7 +379,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (rc == 2 && qtype == T_NAPTR)
{
found = 1;
log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>", 0);
log_query(log_flags | F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>", 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
NULL, T_NAPTR, C_IN, "sszzzd",
na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
@@ -407,7 +409,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
continue;
found = 1;
log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL, 0);
log_query(log_flags | F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL, 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &addrlist->addr))
@@ -419,7 +421,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
{
nxdomain = 0;
log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL, 0);
log_query(log_flags | F_FORWARD | F_CONFIG | flag, name, &addr, NULL, 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN, qtype == T_A ? "4" : "6", &addr))
anscount++;
@@ -432,7 +434,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (qtype == T_SOA)
{
auth = soa = 1; /* inhibits auth section */
log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>", 0);
log_query(log_flags | F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>", 0);
}
else if (qtype == T_AXFR)
{
@@ -468,13 +470,13 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
ns = 1; /* ensure we include NS records! */
axfr = 1;
axfroffset = nameoffset;
log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<AXFR>", 0);
log_query(log_flags | F_RRNAME | F_AUTH, zone->domain, NULL, "<AXFR>", 0);
}
else if (qtype == T_NS)
{
auth = 1;
ns = 1; /* inhibits auth section */
log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>", 0);
log_query(log_flags | F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>", 0);
}
}
@@ -492,7 +494,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
(local_query || filter_zone(zone, flag, &(crecp->addr))))
{
*cut = '.'; /* restore domain part */
log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid), 0);
log_query(log_flags | crecp->flags, name, &crecp->addr, record_source(crecp->uid), 0);
*cut = 0; /* remove domain part */
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
@@ -513,7 +515,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
nxdomain = 0;
if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
{
log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr, record_source(crecp->uid), 0);
log_query(log_flags | (crecp->flags & ~F_REVERSE), name, &crecp->addr, record_source(crecp->uid), 0);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
daemon->auth_ttl, NULL, qtype, C_IN,
qtype == T_A ? "4" : "6", &crecp->addr))
@@ -560,7 +562,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (candidate)
{
log_query(F_CONFIG | F_CNAME, name, NULL, NULL, 0);
log_query(log_flags | F_CONFIG | F_CNAME, name, NULL, NULL, 0);
strcpy(name, candidate->target);
if (!strchr(name, '.'))
{
@@ -578,16 +580,18 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
else if (cache_find_non_terminal(name, now))
nxdomain = 0;
log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL, 0);
log_query(log_flags | flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL, 0);
}
}
done:
/* Add auth section */
if (auth && zone)
{
char *authname;
int newoffset, offset = 0;
int newoffset = ansp - (unsigned char *)header, offset = 0;
if (!subnet)
authname = zone->domain;
@@ -627,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,
@@ -646,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))
{
@@ -665,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
@@ -873,7 +877,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
if (!(ansp = skip_questions(header, qlen)))
return 0; /* bad packet */
anscount = authcount = 0;
log_query(F_AUTH, "reply", NULL, "truncated", 0);
log_query(log_flags | F_AUTH, "reply", NULL, "truncated", 0);
}
if ((auth || local_query) && nxdomain)
@@ -885,14 +889,23 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
header->nscount = htons(authcount);
header->arcount = htons(0);
if (!local_query && out_of_zone)
if ((!local_query && out_of_zone) || notimp)
{
SET_RCODE(header, REFUSED);
if (out_of_zone)
{
addr.log.rcode = REFUSED;
addr.log.ede = EDE_NOT_AUTH;
}
else
{
addr.log.rcode = NOTIMP;
addr.log.ede = EDE_UNSET;
}
SET_RCODE(header, addr.log.rcode);
header->ancount = htons(0);
header->nscount = htons(0);
addr.log.rcode = REFUSED;
addr.log.ede = EDE_NOT_AUTH;
log_query(F_UPSTREAM | F_RCODE, "error", &addr, NULL, 0);
log_query(log_flags | F_UPSTREAM | F_RCODE, "error", &addr, NULL, 0);
return resize_packet(header, ansp - (unsigned char *)header, NULL, 0);
}
+10 -8
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -193,20 +193,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;
}
+89 -91
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -126,112 +126,110 @@ int iface_enumerate(int family, void *parm, callback_t callback)
for (addrs = head; addrs; addrs = addrs->ifa_next)
{
if (addrs->ifa_addr->sa_family == family)
int iface_index = if_nametoindex(addrs->ifa_name);
if (iface_index == 0 || !addrs->ifa_addr ||
addrs->ifa_addr->sa_family != family ||
(!addrs->ifa_netmask && family != AF_LINK))
continue;
if (family == AF_INET)
{
int iface_index = if_nametoindex(addrs->ifa_name);
if (iface_index == 0 || !addrs->ifa_addr ||
(!addrs->ifa_netmask && family != AF_LINK))
struct in_addr addr, netmask, broadcast;
addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
continue;
if (family == AF_INET)
{
struct in_addr addr, netmask, broadcast;
addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
continue;
#endif
netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
if (addrs->ifa_broadaddr)
broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr;
else
broadcast.s_addr = 0;
if (!callback.af_inet(addr, iface_index, NULL, netmask, broadcast, parm))
goto err;
}
else if (family == AF_INET6)
{
struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
int i, j, prefix = 0;
u32 valid = 0xffffffff, preferred = 0xffffffff;
int flags = 0;
netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
if (addrs->ifa_broadaddr)
broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr;
else
broadcast.s_addr = 0;
if (!callback.af_inet(addr, iface_index, NULL, netmask, broadcast, parm))
goto err;
}
else if (family == AF_INET6)
{
struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
int i, j, prefix = 0;
u32 valid = 0xffffffff, preferred = 0xffffffff;
int flags = 0;
#ifdef HAVE_BSD_NETWORK
if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
continue;
if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
continue;
#endif
#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
struct in6_ifreq ifr6;
memset(&ifr6, 0, sizeof(ifr6));
safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
struct in6_ifreq ifr6;
memset(&ifr6, 0, sizeof(ifr6));
safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
{
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
flags |= IFACE_TENTATIVE;
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
flags |= IFACE_DEPRECATED;
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
{
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
flags |= IFACE_TENTATIVE;
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
flags |= IFACE_DEPRECATED;
#ifdef IN6_IFF_TEMPORARY
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
flags |= IFACE_PERMANENT;
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
flags |= IFACE_PERMANENT;
#endif
#ifdef IN6_IFF_PRIVACY
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
flags |= IFACE_PERMANENT;
if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
flags |= IFACE_PERMANENT;
#endif
}
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
{
valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
}
}
ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
{
valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
}
#endif
for (i = 0; i < IN6ADDRSZ; i++, prefix += 8)
if (netmask[i] != 0xff)
break;
if (i != IN6ADDRSZ && netmask[i])
for (j = 7; j > 0; j--, prefix++)
if ((netmask[i] & (1 << j)) == 0)
break;
/* voodoo to clear interface field in address */
if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
{
addr->s6_addr[2] = 0;
addr->s6_addr[3] = 0;
}
if (!callback.af_inet6(addr, prefix, scope_id, iface_index, flags,
(unsigned int) preferred, (unsigned int)valid, parm))
goto err;
}
#ifdef HAVE_DHCP6
else if (family == AF_LINK)
{
/* Assume ethernet again here */
struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
if (sdl->sdl_alen != 0 &&
!callback.af_local(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm))
goto err;
}
#endif
for (i = 0; i < IN6ADDRSZ; i++, prefix += 8)
if (netmask[i] != 0xff)
break;
if (i != IN6ADDRSZ && netmask[i])
for (j = 7; j > 0; j--, prefix++)
if ((netmask[i] & (1 << j)) == 0)
break;
/* voodoo to clear interface field in address */
if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
{
addr->s6_addr[2] = 0;
addr->s6_addr[3] = 0;
}
if (!callback.af_inet6(addr, prefix, scope_id, iface_index, flags,
(unsigned int) preferred, (unsigned int)valid, parm))
goto err;
}
#ifdef HAVE_DHCP6
else if (family == AF_LINK)
{
/* Assume ethernet again here */
struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
if (sdl->sdl_alen != 0 &&
!callback.af_local(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm))
goto err;
}
#endif
}
ret = 1;
err:
errsave = errno;
freeifaddrs(head);
+444 -261
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,13 +17,16 @@
#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,
@@ -102,6 +105,8 @@ static const struct {
{ 64, "SVCB" }, /* Service Binding [draft-ietf-dnsop-svcb-https-00] SVCB/svcb-completed-template 2020-06-30*/
{ 65, "HTTPS" }, /* HTTPS Binding [draft-ietf-dnsop-svcb-https-00] HTTPS/https-completed-template 2020-06-30*/
{ 66, "DSYNC" }, /* Endpoint discovery for delegation synchronization [draft-ietf-dnsop-generalized-notify-03] DSYNC/dsync-completed-template 2024-12-10 */
{ 67, "HHIT" }, /* [draft-ietf-drip-registries-28] */
{ 68, "BRID" }, /* [draft-ietf-drip-registries-28] */
{ 99, "SPF" }, /* [RFC7208] */
{ 100, "UINFO" }, /* [IANA-Reserved] */
{ 101, "UID" }, /* [IANA-Reserved] */
@@ -190,6 +195,61 @@ 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 char *store_name(unsigned int namelen, unsigned int index)
{
struct nameblock *block;
char *ret = NULL;
for (block = hostblocks; block; block = block->next)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
break;
if (!block && ((block = whine_malloc(sizeof(struct nameblock)))))
{
block->next = hostblocks;
block->index = index;
hostblocks = block;
}
if (block && NAMEBLOCK_CHARS - block->last >= namelen)
{
ret = &block->data[block->last];
block->last += namelen;
}
return ret;
}
static void free_names(unsigned int index)
{
struct nameblock *block;
for (block = hostblocks; block; block = block->next)
if (index == UID_NONE || block->index == index)
block->last = 0;
}
/* 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
@@ -439,12 +499,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;
}
@@ -779,7 +841,14 @@ void cache_end_insert(void)
{
if (insert_error)
return;
/* signal start of cache insert transaction to master process */
if (daemon->pipe_to_parent != -1)
{
unsigned char op = PIPE_OP_INSERT;
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
}
while (new_chain)
{
struct crec *tmp = new_chain->next;
@@ -806,7 +875,7 @@ void cache_end_insert(void)
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)name, m, RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), RW_WRITE);
if (flags & F_RR)
@@ -816,7 +885,7 @@ void cache_end_insert(void)
blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, daemon->pipe_to_parent);
}
#ifdef HAVE_DNSSEC
if (flags & F_DNSKEY)
else if (flags & F_DNSKEY)
{
read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), RW_WRITE);
blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
@@ -839,166 +908,243 @@ void cache_end_insert(void)
if (daemon->pipe_to_parent != -1)
{
ssize_t m = -1;
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
#ifdef HAVE_DNSSEC
/* Sneak out possibly updated crypto HWM values. */
m = daemon->metrics[METRIC_CRYPTO_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
m = daemon->metrics[METRIC_SIG_FAIL_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
m = daemon->metrics[METRIC_WORK_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
#endif
}
new_chain = NULL;
}
#ifdef HAVE_DNSSEC
void cache_update_hwm(void)
{
/* Sneak out possibly updated crypto HWM values. */
unsigned char op = PIPE_OP_STATS;
/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
read_write(daemon->pipe_to_parent,
(unsigned char *)&daemon->metrics[METRIC_CRYPTO_HWM],
sizeof(daemon->metrics[METRIC_CRYPTO_HWM]), RW_WRITE);
read_write(daemon->pipe_to_parent,
(unsigned char *)&daemon->metrics[METRIC_SIG_FAIL_HWM],
sizeof(daemon->metrics[METRIC_SIG_FAIL_HWM]), RW_WRITE);
read_write(daemon->pipe_to_parent,
(unsigned char *)&daemon->metrics[METRIC_WORK_HWM],
sizeof(daemon->metrics[METRIC_WORK_HWM]), RW_WRITE);
}
#endif
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
void cache_send_ipset(unsigned char op, struct ipsets *sets, int flags, union all_addr *addr)
{
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&sets, sizeof(sets), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)addr, sizeof(*addr), RW_WRITE);
}
#endif
/* Retrieve and handle a result from child TCP-handler.
Return 0 when pipe is closed by far end. */
int cache_recv_insert(time_t now, int fd)
{
ssize_t m;
union all_addr addr;
unsigned long ttl;
time_t ttd;
unsigned int flags;
struct crec *crecp = NULL;
unsigned char op;
cache_start_insert();
if (!read_write(fd, &op, sizeof(op), RW_READ))
return 0;
while (1)
switch (op)
{
if (!read_write(fd, (unsigned char *)&m, sizeof(m), RW_READ))
return 0;
case PIPE_OP_INSERT:
{
/* A marshalled set if cache entries arrives on fd, read, unmarshall and insert into cache of master process. */
ssize_t m;
union all_addr addr;
unsigned long ttl;
time_t ttd;
unsigned int flags;
struct crec *crecp = NULL;
cache_start_insert();
/* loop reading RRs, since we don't want to go back to the poll() loop
and start processing other queries which might pollute the insertion
chain. The child will never block between the first OP_RR and the
minus-one length marking the end. */
while (1)
{
if (!read_write(fd, (unsigned char *)&m, sizeof(m), RW_READ))
return 0;
if (m == -1)
{
cache_end_insert();
return 1;
}
if (!read_write(fd, (unsigned char *)daemon->namebuff, m, RW_READ) ||
!read_write(fd, (unsigned char *)&ttd, sizeof(ttd), RW_READ) ||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), RW_READ) ||
!read_write(fd, (unsigned char *)&addr, sizeof(addr), RW_READ))
return 0;
daemon->namebuff[m] = 0;
ttl = difftime(ttd, now);
if (flags & F_CNAME)
{
struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
/* This relies on the fact that the target of a CNAME immediately precedes
it because of the order of extraction in extract_addresses, and
the order reversal on the new_chain. */
if (newc)
{
newc->addr.cname.is_name_ptr = 0;
newc->addr.cname.target.cache = crecp;
if (crecp)
{
next_uid(crecp);
newc->addr.cname.uid = crecp->uid;
}
crecp = newc;
}
}
else
{
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;
#ifdef HAVE_DNSSEC
else if (flags & F_DNSKEY)
{
if (!read_write(fd, (unsigned char *)&class, sizeof(class), RW_READ))
return 0;
if (!(block = addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
continue;
}
else if (flags & F_DS)
{
if (!read_write(fd, (unsigned char *)&class, sizeof(class), RW_READ))
return 0;
if (!(flags & F_NEG) && !(block = addr.ds.keydata = blockdata_read(fd, addr.ds.keylen)))
continue;
}
#endif
if (!(crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags)))
blockdata_free(block);
}
}
}
if (m == -1)
{
#ifdef HAVE_DNSSEC
/* Sneak in possibly updated crypto HWM. */
if (!read_write(fd, (unsigned char *)&m, sizeof(m), RW_READ))
return 0;
if (m > daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), RW_READ))
return 0;
if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), RW_READ))
return 0;
if (m > daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = m;
#endif
cache_end_insert();
return 1;
}
#ifdef HAVE_DNSSEC
/* UDP validation moved to TCP to avoid truncation.
Restart UDP validation process with the returned result. */
if (m == -2)
{
int status, uid, keycount, validatecount;
int *keycountp, *validatecountp;
size_t ret_len;
struct frec *forward;
if (!read_write(fd, (unsigned char *)&status, sizeof(status), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&ret_len, sizeof(ret_len), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)daemon->packet, ret_len, RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&forward, sizeof(forward), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ))
return 0;
if (!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ))
return 0;
/* 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)
{
/* 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);
else
pop_and_retry_query(forward, status, now);
}
return 1;
}
#endif
if (!read_write(fd, (unsigned char *)daemon->namebuff, m, RW_READ) ||
!read_write(fd, (unsigned char *)&ttd, sizeof(ttd), RW_READ) ||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), RW_READ) ||
!read_write(fd, (unsigned char *)&addr, sizeof(addr), RW_READ))
return 0;
daemon->namebuff[m] = 0;
ttl = difftime(ttd, now);
case PIPE_OP_STATS:
{
/* Sneak in possibly updated crypto HWM. */
unsigned int val;
if (!read_write(fd, (unsigned char *)&val, sizeof(val), RW_READ))
return 0;
if (val > daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = val;
if (!read_write(fd, (unsigned char *)&val, sizeof(val), RW_READ))
return 0;
if (val > daemon->metrics[METRIC_SIG_FAIL_HWM])
daemon->metrics[METRIC_SIG_FAIL_HWM] = val;
if (!read_write(fd, (unsigned char *)&val, sizeof(val), RW_READ))
return 0;
if (val > daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = val;
return 1;
}
if (flags & F_CNAME)
{
struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
/* This relies on the fact that the target of a CNAME immediately precedes
it because of the order of extraction in extract_addresses, and
the order reversal on the new_chain. */
if (newc)
{
newc->addr.cname.is_name_ptr = 0;
if (!crecp)
newc->addr.cname.target.cache = NULL;
else
{
next_uid(crecp);
newc->addr.cname.target.cache = crecp;
newc->addr.cname.uid = crecp->uid;
}
}
}
else
{
unsigned short class = C_IN;
case PIPE_OP_RESULT:
case PIPE_OP_KILLED:
{
/* UDP validation moved to TCP to avoid truncation.
Restart UDP validation process with the returned result. */
int status, uid, keycount, validatecount;
int *keycountp, *validatecountp;
size_t ret_len;
struct frec *forward;
if (!read_write(fd, (unsigned char *)&status, sizeof(status), RW_READ) ||
!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))
return 0;
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
&& !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
return 0;
#ifdef HAVE_DNSSEC
if (flags & F_DNSKEY)
{
if (!read_write(fd, (unsigned char *)&class, sizeof(class), RW_READ) ||
!(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
return 0;
}
else if (flags & F_DS)
{
if (!read_write(fd, (unsigned char *)&class, sizeof(class), RW_READ) ||
(!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
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;
/* 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)
{
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);
else
pop_and_retry_query(forward, status, now);
}
return 1;
}
#endif
crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
}
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
case PIPE_OP_IPSET:
case PIPE_OP_NFTSET:
{
struct ipsets *sets;
char **sets_cur;
unsigned int flags;
union all_addr addr;
if (!read_write(fd, (unsigned char *)&sets, sizeof(sets), RW_READ) ||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), RW_READ) ||
!read_write(fd, (unsigned char *)&addr, sizeof(addr), RW_READ))
return 0;
for (sets_cur = sets->sets; *sets_cur; sets_cur++)
{
int rc = -1;
#ifdef HAVE_IPSET
if (op == PIPE_OP_IPSET)
rc = add_to_ipset(*sets_cur, &addr, flags, 0);
#endif
#ifdef HAVE_NFTSET
if (op == PIPE_OP_NFTSET)
rc = add_to_nftset(*sets_cur, &addr, flags, 0);
#endif
if (rc == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, sets->domain, &addr, *sets_cur, op == PIPE_OP_IPSET);
}
return 1;
}
#endif
}
return 0;
}
int cache_find_non_terminal(char *name, time_t now)
@@ -1175,7 +1321,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;
}
@@ -1298,13 +1444,13 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr
{
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);
}
@@ -1338,27 +1484,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);
}
@@ -1409,7 +1565,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))
{
@@ -1424,11 +1580,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;
@@ -1442,19 +1600,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->addr.ds.keylen = ds->digestlen;
cache->addr.ds.algo = ds->algo;
cache->addr.ds.keytag = ds->keytag;
cache->addr.ds.digest = ds->digest_type;
cache->uid = ds->class;
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
@@ -1469,7 +1638,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;
@@ -1478,7 +1647,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;
@@ -1570,8 +1739,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;
@@ -1642,12 +1810,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)
@@ -1694,15 +1857,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
@@ -1735,17 +1890,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))
@@ -1852,15 +1997,31 @@ int cache_make_stat(struct txt_record *t)
#endif
/* There can be names in the cache containing control chars, don't
mess up logging or open security holes. */
mess up logging or open security holes. Also convert to all-LC
so that 0x20-encoding doesn't make logs look like ransom notes
made out of letters cut from a newspaper.
Overwrites daemon->workspacename */
static char *sanitise(char *name)
{
unsigned char *r;
unsigned char *r = (unsigned char *)name;
if (name)
for (r = (unsigned char *)name; *r; r++)
if (!isprint((int)*r))
return "<name unprintable>";
{
char *d = name = daemon->workspacename;
for (; *r; r++, d++)
if (!isprint((int)*r))
return "<name unprintable>";
else
{
unsigned char c = *r;
*d = (char)((c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c);
}
*d = 0;
}
return name;
}
@@ -2058,9 +2219,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)
{
@@ -2075,37 +2236,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)
@@ -2142,10 +2294,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_occured(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;
@@ -2153,12 +2315,20 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
char *extra = "";
char *gap = " ";
char portstring[7]; /* space for #<portnum> */
char opcodestring[3]; /* maximum is 15 */
if (!option_bool(OPT_LOG))
return;
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occured(flags))
return;
/* F_NOERR is reused here to indicate logs arrising 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)) && type > 0)
if (!(flags & (F_SERVER | F_IPSET | F_QUERY)) && type > 0)
arg = querystr(arg, type);
dest = arg;
@@ -2191,6 +2361,8 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
dest = "SERVFAIL";
else if (rcode == REFUSED)
dest = "REFUSED";
else if (rcode == FORMERR)
dest = "FORMERR";
else if (rcode == NOTIMP)
dest = "not implemented";
else
@@ -2249,6 +2421,10 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
source = arg;
else if (flags & F_UPSTREAM)
source = "reply";
else if (flags & F_AUTH)
source = "auth";
else if (flags & F_QUERY)
source = "query";
else if (flags & F_SECSTAT)
{
if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG))
@@ -2259,8 +2435,6 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
source = "validation";
dest = arg;
}
else if (flags & F_AUTH)
source = "auth";
else if (flags & F_DNSSEC)
{
source = arg;
@@ -2271,11 +2445,6 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
source = "forwarded";
verb = "to";
}
else if (flags & F_QUERY)
{
source = arg;
verb = "from";
}
else if (flags & F_IPSET)
{
source = type ? "ipset add" : "nftset add";
@@ -2287,7 +2456,21 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
source = "cached-stale";
else
source = "cached";
if (flags & F_QUERY)
{
if (flags & F_CONFIG)
{
sprintf(opcodestring, "%u", type & 0xf);
source = "non-query opcode";
name = opcodestring;
}
else if (type > 0)
source = querystr(source, type);
verb = "from";
}
if (!name)
gap = name = "";
else if (!name[0])
+32 -6
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -22,10 +22,12 @@
#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. */
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
#define DNSSEC_ASSUMED_DS_TTL 3600 /* TTL for negative DS records implied by server=/domain/ */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
@@ -51,6 +53,8 @@
#define CHUSER "nobody"
#define CHGRP "dip"
#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
#define TFTP_MAX_WINDOW 32 /* max window size to negotiate */
#define TFTP_TRANSFER_TIME 120 /* Abandon TFTP transfers after this long. Two mins. */
#define LOG_MAX 5 /* log-queue length */
#define RANDFILE "/dev/urandom"
#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */
@@ -139,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.
@@ -152,6 +157,7 @@ NO_AUTH
NO_DUMPFILE
NO_LOOP
NO_INOTIFY
NO_IPSET
these are available to explicitly disable compile time options which would
otherwise be enabled automatically or which are enabled by default
in the distributed source tree. Building dnsmasq
@@ -296,7 +302,6 @@ HAVE_SOCKADDR_SA_LEN
#ifndef SOL_TCP
# define SOL_TCP IPPROTO_TCP
#endif
#define NO_IPSET
#elif defined(__NetBSD__)
#define HAVE_BSD_NETWORK
@@ -346,10 +351,24 @@ HAVE_SOCKADDR_SA_LEN
#undef HAVE_AUTH
#endif
#if !defined(HAVE_LINUX_NETWORK)
#undef HAVE_NFTSET
#endif
#if defined(NO_IPSET)
#undef HAVE_IPSET
#endif
#if defined(HAVE_IPSET)
# if defined(HAVE_LINUX_NETWORK)
# define HAVE_LINUX_IPSET
# elif defined(HAVE_BSD_NETWORK)
# define HAVE_BSD_IPSET
# else
# undef HAVE_IPSET
# endif
#endif
#ifdef NO_LOOP
#undef HAVE_LOOP
#endif
@@ -358,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. */
@@ -459,4 +485,4 @@ static char *compile_opts =
#endif
"dumpfile";
#endif /* defined(HAVE_DHCP) */
#endif /* defined(DNSMASQ_COMPILE_OPTS) */
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+5 -7
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -126,20 +126,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;
}
+24 -11
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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
@@ -768,10 +781,10 @@ static DBusMessage *dbus_get_server_metrics(DBusMessage* message)
add_dict_entry(&dict_array, "address", daemon->namebuff);
add_dict_int(&dict_array, "port", port);
add_dict_int(&dict_array, "queries", serv->queries);
add_dict_int(&dict_array, "failed_queries", serv->failed_queries);
add_dict_int(&dict_array, "nxdomain", serv->nxdomain_replies);
add_dict_int(&dict_array, "retries", serv->retrys);
add_dict_int(&dict_array, "queries", queries);
add_dict_int(&dict_array, "failed_queries", failed_queries);
add_dict_int(&dict_array, "nxdomain", nxdomain_replies);
add_dict_int(&dict_array, "retries", retrys);
add_dict_int(&dict_array, "latency", sigma_latency/count_latency);
dbus_message_iter_close_container(&server_array, &dict_array);
+10 -4
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -701,13 +701,16 @@ static const struct opttab_t {
{ "user-class", 77, 0 },
{ "rapid-commit", 80, 0 },
{ "FQDN", 81, OT_INTERNAL },
{ "agent-id", 82, OT_INTERNAL },
{ "agent-info", 82, OT_INTERNAL },
{ "last-transaction", 91, 4 | OT_TIME },
{ "associated-ip", 92, OT_ADDR_LIST },
{ "client-arch", 93, 2 | OT_DEC },
{ "client-interface-id", 94, 0 },
{ "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 },
@@ -749,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
@@ -1065,10 +1069,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);
}
+9 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -57,6 +57,8 @@
#define OPTION_RAPID_COMMIT 80
#define OPTION_CLIENT_FQDN 81
#define OPTION_AGENT_ID 82
#define OPTION_LAST_TRANSACTION 91
#define OPTION_ASSOCIATED_IP 92
#define OPTION_ARCH 93
#define OPTION_PXE_UUID 97
#define OPTION_SUBNET_SELECT 118
@@ -71,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 */
@@ -87,6 +90,11 @@
#define DHCPNAK 6
#define DHCPRELEASE 7
#define DHCPINFORM 8
#define DHCPFORCERENEW 9
#define DHCPLEASEQUERY 10
#define DHCPLEASEUNASSIGNED 11
#define DHCPLEASEUNKNOWN 12
#define DHCPLEASEACTIVE 13
#define BRDBAND_FORUM_IANA 3561 /* Broadband forum IANA enterprise */
+75 -142
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -32,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)
{
@@ -135,7 +133,7 @@ void dhcp_packet(time_t now, int pxe_fd)
struct dhcp_packet *mess;
struct dhcp_context *context;
struct dhcp_relay *relay;
int is_relay_reply = 0;
int is_relay_reply = 0, is_relay_use_source = 0;
struct iname *tmp;
struct ifreq ifr;
struct msghdr msg;
@@ -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,12 +222,44 @@ 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;
mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
/* Non-standard extension:
If giaddr == 255.255.255.255 we reply to the source
address in the request packet header. This makes
stand-alone leasequery clients easier, as they
can leave source address determination to the kernel.
In this case, set a flag and clear giaddr here,
to avoid massive relay confusion. */
if (mess->giaddr.s_addr == INADDR_BROADCAST)
{
mess->giaddr.s_addr = 0;
is_relay_use_source = 1;
}
loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK);
#ifdef HAVE_LINUX_NETWORK
@@ -272,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;
@@ -300,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;
@@ -330,15 +363,16 @@ 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)
return;
lease_prune(NULL, now); /* lose any expired leases */
iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz,
now, unicast_dest, loopback, &is_inform, pxe_fd, iface_addr, recvtime);
iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, now, unicast_dest,
loopback, &is_inform, pxe_fd, iface_addr, recvtime,
is_relay_use_source ? dest.sin_addr : mess->giaddr);
lease_update_file(now);
lease_update_dns(0);
@@ -365,11 +399,17 @@ void dhcp_packet(time_t now, int pxe_fd)
if (mess->ciaddr.s_addr != 0)
dest.sin_addr = mess->ciaddr;
}
else if (mess->giaddr.s_addr && !is_relay_reply)
else if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
{
/* Send to BOOTP relay */
dest.sin_port = htons(daemon->dhcp_server_port);
dest.sin_addr = mess->giaddr;
/* Send to BOOTP relay. */
if (is_relay_use_source)
/* restore as-received value */
mess->giaddr.s_addr = INADDR_BROADCAST;
else
{
dest.sin_addr = mess->giaddr;
dest.sin_port = htons(daemon->dhcp_server_port);
}
}
else if (mess->ciaddr.s_addr)
{
@@ -627,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;
}
@@ -645,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)
@@ -1070,122 +1121,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-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+32 -10
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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;
}
@@ -812,7 +834,7 @@ void dhcp_construct_contexts(time_t now)
{
if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA))
{
/* previously constructed context has gone. advertise it's demise */
/* previously constructed context has gone; advertise its demise */
context->flags |= CONTEXT_OLD;
context->address_lost_time = now;
/* Apply same ceiling of configured lease time as in radv.c */
+2 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -86,6 +86,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 */
+198 -100
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,7 +30,9 @@ static volatile pid_t pid = 0;
static volatile int pipewrite;
static void set_dns_listeners(void);
#ifdef HAVE_TFTP
static void set_tftp_listeners(void);
#endif
static void check_dns_listeners(time_t now);
static void do_tcp_connection(struct listener *listener, time_t now, int slot);
static void sig_handler(int sig);
@@ -38,7 +40,6 @@ static void async_event(int pipe, time_t now);
static void fatal_event(struct event_desc *ev, char *msg);
static int read_event(int fd, struct event_desc *evp, char **msg);
static void poll_resolv(int force, int do_reload, time_t now);
static void tcp_init(void);
int main (int argc, char **argv)
{
@@ -63,14 +64,16 @@ int main (int argc, char **argv)
int need_cap_net_raw = 0;
int need_cap_net_bind_service = 0;
int have_cap_chown = 0;
# ifdef HAVE_DHCP
char *bound_device = NULL;
int did_bind = 0;
# endif
struct server *serv;
char *netlink_warn;
#else
int bind_fallback = 0;
#endif
#if defined(HAVE_DHCP) || defined(HAVE_DHCP6)
#if defined(HAVE_DHCP)
struct dhcp_context *context;
struct dhcp_relay *relay;
#endif
@@ -78,6 +81,10 @@ int main (int argc, char **argv)
int tftp_prefix_missing = 0;
#endif
#ifdef HAVE_LINUX_NETWORK
(void)netlink_warn;
#endif
#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2) || defined(LOCALEDIR)
setlocale(LC_ALL, "");
#endif
@@ -120,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);
@@ -133,6 +141,7 @@ int main (int argc, char **argv)
'.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec. */
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
daemon->cname = safe_malloc((MAXDNAME * 2) + 1);
/* one char flag per possible RR in answer section (may get extended). */
daemon->rr_status_sz = 64;
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
@@ -421,16 +430,12 @@ int main (int argc, char **argv)
/* safe_malloc returns zero'd memory */
daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd));
tcp_init();
}
daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t));
daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int));
#ifdef HAVE_INOTIFY
if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
daemon->dynamic_dirs)
inotify_dnsmasq_init();
else
daemon->inotifyfd = -1;
#endif
for (i = 0; i < daemon->max_procs; i++)
daemon->tcp_pipes[i] = -1;
}
if (daemon->dump_file)
#ifdef HAVE_DUMPFILE
@@ -851,6 +856,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
@@ -930,8 +946,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, _("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
@@ -1067,12 +1102,6 @@ int main (int argc, char **argv)
pid = getpid();
daemon->pipe_to_parent = -1;
if (daemon->port != 0)
for (i = 0; i < daemon->max_procs; i++)
daemon->tcp_pipes[i] = -1;
#ifdef HAVE_INOTIFY
/* Using inotify, have to select a resolv file at startup */
poll_resolv(1, 0, now);
@@ -1327,11 +1356,42 @@ 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);
my_syslog(LOG_INFO, _("TCP process for DNSSEC validation timed out"));
_exit(0);
}
}
#endif
}
}
else
{
@@ -1408,8 +1468,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;
@@ -1419,12 +1477,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;
@@ -1477,6 +1545,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);
}
}
@@ -1487,9 +1575,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)
{
@@ -1595,7 +1680,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 */
@@ -1768,9 +1852,9 @@ void clear_cache_and_reload(time_t now)
#ifdef HAVE_DHCP
if (daemon->dhcp || daemon->doing_dhcp6)
{
reread_dhcp();
if (option_bool(OPT_ETHERS))
dhcp_read_ethers();
reread_dhcp();
dhcp_update_configs(daemon->dhcp_conf);
lease_update_from_configs();
lease_update_file(now);
@@ -1933,17 +2017,21 @@ 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 a = 0, *buff;
struct server *s;
int flags, auth_dns;
int flags, auth_dns = 0;
struct in_addr netmask;
int pipefd[2];
struct iovec tcpbuff;
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
netmask.s_addr = 0;
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
@@ -1970,60 +2058,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 */
@@ -2051,7 +2159,7 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
@@ -2074,23 +2182,9 @@ 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))
{
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
close(daemon->netlinkfd);
@@ -2107,10 +2201,8 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
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)
@@ -2122,6 +2214,10 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
if (!option_bool(OPT_DEBUG))
{
#ifdef HAVE_DNSSEC
cache_update_hwm(); /* Sneak out possibly updated crypto HWM values. */
#endif
close(daemon->pipe_to_parent);
flush_log();
_exit(0);
@@ -2140,7 +2236,7 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
cache_recv_insert() calls pop_and_retry_query() after the result
arrives via the pipe to the parent. */
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount)
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
{
struct server *s;
@@ -2209,13 +2305,16 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
close(daemon->netlinkfd);
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
daemon->pipe_to_parent = pipefd[1];
daemon->forward_to_tcp = forward;
daemon->header_to_tcp = header;
daemon->plen_to_tcp = *plen;
}
}
status = tcp_from_udp(now, status, header, plen, class, daemon->namebuff, daemon->keyname,
server, keycount, validatecount);
status = tcp_from_udp(now, status, header, plen, class, name, server, keycount, validatecount);
/* close upstream connections. */
for (s = daemon->servers; s; s = s->next)
@@ -2228,10 +2327,10 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
if (!option_bool(OPT_DEBUG))
{
ssize_t m = -2;
unsigned char op = PIPE_OP_RESULT;
/* tell our parent we're done, and what the result was then exit. */
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), RW_WRITE);
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 *)plen, sizeof(*plen), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)header, *plen, RW_WRITE);
@@ -2241,6 +2340,9 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
read_write(daemon->pipe_to_parent, (unsigned char *)&keycount, sizeof(keycount), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)validatecount, sizeof(*validatecount), RW_WRITE);
read_write(daemon->pipe_to_parent, (unsigned char *)&validatecount, sizeof(validatecount), RW_WRITE);
cache_update_hwm(); /* Sneak out possibly updated crypto HWM values. */
close(daemon->pipe_to_parent);
flush_log();
@@ -2411,8 +2513,4 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
}
#endif /* HAVE_DHCP */
void tcp_init(void)
{
daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t));
daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int));
}
+99 -38
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -14,7 +14,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define COPYRIGHT "Copyright (c) 2000-2024 Simon Kelley"
#define COPYRIGHT "Copyright (c) 2000-2025 Simon Kelley"
/* We do defines that influence behavior of stdio.h, so complain
if included too early. */
@@ -48,6 +48,12 @@
#define ATTRIBUTE_NORETURN
#endif
#if __GNUC__ + 0 >= 8 // clang 20.1.0 does not yet support this
#define ATTRIBUTE_NONSTRING __attribute__ ((nonstring))
#else
#define ATTRIBUTE_NONSTRING
#endif
/* get these before config.h for IPv6 stuff... */
#include <sys/types.h>
#include <sys/socket.h>
@@ -195,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
@@ -279,7 +290,13 @@ struct event_desc {
#define OPT_CACHE_RR 71
#define OPT_LOCALHOST_SERVICE 72
#define OPT_LOG_PROTO 73
#define OPT_LAST 74
#define OPT_NO_0x20 74
#define OPT_DO_0x20 75
#define OPT_AUTH_LOG 76
#define OPT_LEASEQUERY 77
#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) )
@@ -534,6 +551,12 @@ struct crec {
#define SRC_HOSTS 2
#define SRC_AH 3
#define PIPE_OP_INSERT 1 /* Cache entry */
#define PIPE_OP_RESULT 2 /* Validation result */
#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.
@@ -551,9 +574,9 @@ union mysockaddr {
/* The actual values here matter, since we sort on them to get records in the order
IPv6 addr, IPv4 addr, all zero return, resolvconf servers, upstream server, no-data return */
#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next four flags */
#define SERV_USE_RESOLV 2 /* forward this domain in the normal way */
IPv6 addr, IPv4 addr, all zero return, no-data return, resolvconf servers, upstream server */
#define SERV_USE_RESOLV 1 /* forward this domain in the normal way */
#define SERV_LITERAL_ADDRESS 2 /* addr is the answer, or NoDATA is the answer, depending on the next four flags */
#define SERV_ALL_ZEROS 4 /* return all zeros for A and AAAA */
#define SERV_4ADDR 8 /* addr is IPv4 */
#define SERV_6ADDR 16 /* addr is IPv6 */
@@ -745,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 */
@@ -762,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))
@@ -774,12 +797,13 @@ struct dyndir {
#define FREC_DO_QUESTION 64
#define FREC_HAS_PHEADER 128
#define FREC_GONE_TO_TCP 256
#define FREC_ANSWER 512
struct frec {
struct frec_src {
union mysockaddr source;
union all_addr dest;
unsigned int iface, log_id;
unsigned int iface, log_id, encode_bitmap, *encode_bigmap;
int fd;
unsigned short orig_id, udp_pkt_size;
struct frec_src *next;
@@ -855,6 +879,8 @@ struct dhcp_lease {
int last_interface;
int new_interface; /* save possible originated interface */
int new_prefixlen; /* and its prefix length */
unsigned char *agent_id, *vendorclass;
int agent_id_len, vendorclass_len;
#ifdef HAVE_DHCP6
struct in6_addr addr6;
unsigned int iaid;
@@ -1091,7 +1117,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[];
@@ -1099,14 +1125,14 @@ struct tftp_file {
struct tftp_transfer {
int sockfd;
time_t timeout;
int backoff;
unsigned int block, blocksize, expansion;
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;
char opt_blocksize, opt_transize, netascii, carrylf;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, lastcarrylf, backoff;
struct tftp_file *file;
struct tftp_transfer *next;
};
@@ -1127,10 +1153,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;
@@ -1174,7 +1202,7 @@ extern struct daemon {
char *runfile;
char *lease_change_command;
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
struct bogus_addr *bogus_addr, *ignore_addr;
struct bogus_addr *bogus_addr, *ignore_addr, *leasequery_addr;
struct server *servers, *servers_tail, *local_domains, **serverarray;
struct rebind_domain *no_rebind;
int server_has_wildcard;
@@ -1185,6 +1213,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;
@@ -1245,12 +1274,15 @@ extern struct daemon {
char *namebuff; /* MAXDNAME size buffer */
char *workspacename;
#ifdef HAVE_DNSSEC
char *keyname; /* MAXDNAME size buffer */
char *keyname, *cname; /* MAXDNAME size buffer */
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
int rr_status_sz;
int dnssec_no_time_check;
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;
@@ -1258,7 +1290,7 @@ 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;
@@ -1350,6 +1382,13 @@ void cache_end_insert(void);
void cache_start_insert(void);
unsigned int cache_remove_uid(const unsigned int uid);
int cache_recv_insert(time_t now, int fd);
#ifdef HAVE_DNSSEC
void cache_update_hwm(void);
#endif
#if defined(HAVE_IPSET) || defined(HAVE_NFTSET)
void cache_send_ipset(unsigned char op, struct ipsets *sets,
int flags, union all_addr *addr);
#endif
struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags);
void cache_reload(void);
@@ -1386,15 +1425,15 @@ int is_rev_synth(int flag, union all_addr *addr, char *name);
/* rfc1035.c */
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff);
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes);
char *name, int func, unsigned int parm);
unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
unsigned char *skip_questions(struct dns_header *header, size_t plen);
unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
unsigned int extract_request(struct dns_header *header, size_t qlen,
char *name, unsigned short *typep);
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
unsigned short *typep, unsigned short *classp);
void setup_reply(struct dns_header *header, unsigned int flags, int ede);
int extract_addresses(struct dns_header *header, size_t qlen, char *name,
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
time_t now, struct ipsets *ipsets, struct ipsets *nftsets,
int check_rebind, int no_cache_dnssec, int secure);
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
void report_addresses(struct dns_header *header, size_t len, u32 mark);
@@ -1413,6 +1452,12 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp,
int *offset, unsigned short type, unsigned short class, char *format, ...);
int in_arpa_name_2_addr(char *namein, union all_addr *addrp);
int private_net(struct in_addr addr, int ban_localhost);
int private_net6(struct in6_addr *a, int ban_localhost);
/* extract_name ops */
#define EXTR_NAME_EXTRACT 1
#define EXTR_NAME_COMPARE 2
#define EXTR_NAME_NOCASE 3
#define EXTR_NAME_FLIP 4
/* auth.c */
#ifdef HAVE_AUTH
@@ -1429,7 +1474,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
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);
@@ -1457,8 +1502,18 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval, 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 sa_len(union mysockaddr *addr);
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
int sockaddr_isnull(const union mysockaddr *s);
@@ -1480,9 +1535,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);
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);
@@ -1522,11 +1577,11 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
#ifdef HAVE_DNSSEC
void pop_and_retry_query(struct frec *forward, int status, time_t now);
int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
int class, char *name, char *keyname, struct server *server,
int class, char *name, struct server *server,
int *keycount, int *validatecount);
#endif
unsigned char *tcp_request(int confd, time_t now,
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,
@@ -1597,6 +1652,7 @@ void lease6_reset(void);
struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
unsigned char *clid, int clid_len, unsigned int iaid);
struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr);
struct dhcp_lease *lease6_find_by_plain_addr(struct in6_addr *addr);
u64 lease_find_max_addr6(struct dhcp_context *context);
void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface);
void lease_update_slaac(time_t now);
@@ -1609,6 +1665,8 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain);
void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now);
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now);
void lease_set_agent_id(struct dhcp_lease *lease, unsigned char *new, int len);
void lease_set_vendorclass(struct dhcp_lease *lease, unsigned char *new, int len);
struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,
unsigned char *clid, int clid_len);
struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
@@ -1629,9 +1687,13 @@ void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data,
#ifdef HAVE_DHCP
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int loopback,
int *is_inform, int pxe, struct in_addr fallback, time_t recvtime);
int *is_inform, int pxe, struct in_addr fallback,
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 */
@@ -1646,7 +1708,7 @@ void send_event(int fd, int event, int data, char *msg);
void clear_cache_and_reload(time_t now);
#ifdef HAVE_DNSSEC
int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header *header,
ssize_t *plen, int class, struct server *server, int *keycount, int *validatecount);
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount);
#endif
/* netlink.c */
@@ -1733,7 +1795,6 @@ void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *p
/* tftp.c */
#ifdef HAVE_TFTP
void tftp_request(struct listener *listen, time_t now);
void check_tftp_listeners(time_t now);
int do_tftp_script_run(void);
#endif
@@ -1768,7 +1829,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,
@@ -1851,7 +1912,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
@@ -1865,7 +1926,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. */
@@ -1879,6 +1939,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
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);
void edns0_needs_mac(union mysockaddr *addr, time_t now);
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
union mysockaddr *source, time_t now, int *cacheable);
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
@@ -1905,7 +1966,7 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
char *name, char *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 *firstp, int *lastp);
int dnssec_server(struct server *server, char *keyname, int is_ds, int *firstp, int *lastp);
#endif
void mark_servers(int flag);
void cleanup_servers(void);
+338 -190
View File
@@ -1,5 +1,5 @@
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2023 Simon Kelley
and Copyright (c) 2012-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -191,7 +191,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
/* domain-name, canonicalise */
int len;
if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
@@ -339,7 +339,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
pstart = p;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
if (!(res = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return 0; /* bad packet */
GETSHORT(stype, p);
@@ -370,45 +370,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, 0, 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, 1, 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(name, daemon->workspacename) != 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 +435,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 +450,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 +458,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 */
@@ -503,7 +500,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (!extract_name(header, plen, &p, keyname, 1, 0))
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (!time_check)
@@ -568,13 +565,15 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
p = rrset[i];
if (!extract_name(header, plen, &p, name, 1, 10))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS;
name_start = name;
/* 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_start[0] == '*' && name_start[1] == '.'))
{
for (j = name_labels - labels; j != 0; j--)
{
@@ -584,11 +583,11 @@ 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 = '*';
*name_start = '*';
}
wire_len = to_wire(name_start);
@@ -661,7 +660,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
if (!extract_name(header, plen, &p, name, 1, 0))
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS;
if (key)
@@ -687,7 +686,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
@@ -727,7 +726,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
static unsigned char **cached_digest;
static size_t cached_digest_size = 0;
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
@@ -752,7 +751,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -904,7 +903,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p);
@@ -992,54 +991,76 @@ 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;
if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4)))
return STAT_BOGUS;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
return STAT_BOGUS;
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
/* A SERVFAIL answer has been seen to a DS query not at start of authority,
so treat it as such and continue to search for a DS or proof of no existence
further down the tree. */
if (RCODE(header) == SERVFAIL)
servfail = neganswer = nons = 1;
else
{
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &prim_ok, &nons, &neg_ttl, validate_counter);
p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 ||
!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
return STAT_BOGUS;
if (!servfail)
{
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
/* 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)
{
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;
}
}
}
p = (unsigned char *)(header+1);
if (!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS;
p += 4; /* qtype, qclass */
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
else
{
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
return STAT_BOGUS;
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
{
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
return STAT_BOGUS;
}
if (!STAT_ISEQUAL(rc, STAT_SECURE))
return rc;
}
if (!STAT_ISEQUAL(rc, STAT_SECURE))
return rc;
}
if (!neganswer)
@@ -1050,7 +1071,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
{
unsigned char *psave;
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(atype, p);
@@ -1129,9 +1150,19 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
flags &= ~F_DNSSECOK;
{
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
nons = 0;
}
else
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
flags &= ~F_DNSSECOK;
}
}
cache_start_insert();
/* Use TTL from NSEC for negative cache entries */
@@ -1231,12 +1262,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
int sig_labels, name_labels;
p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10))
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 0))
if (!extract_name(header, plen, &p, workspace2, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard
@@ -1258,7 +1290,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
workspace1--;
*workspace1 = '*';
}
rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
/* RFC 6672 5.3.4.1. */
#define DNAME_OFFSET (T_DNAME >> 3)
#define DNAME_MASK (0x80 >> (T_DNAME & 0x07))
if (p[0] == 0 && (p[1] >= DNAME_OFFSET + 1) && (p[2 + DNAME_OFFSET] & DNAME_MASK) != 0 &&
hostname_issubdomain(name, workspace1) == 1)
return DNSSEC_FAIL_NONSEC;
rc = hostname_cmp(workspace1, name);
if (rc == 0)
@@ -1269,16 +1316,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
if (rdlen >= 2 && p[0] == 0)
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
@@ -1290,10 +1333,10 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return DNSSEC_FAIL_NONSEC;
}
while (rdlen >= 2)
while (rdlen > 0)
{
if (!CHECK_LEN(header, p, plen, rdlen))
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
@@ -1380,7 +1423,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;
}
}
@@ -1400,7 +1446,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 10) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
@@ -1433,7 +1479,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
if (rdlen >= 2 && p[0] == 0)
/* check that the first typemap is complete. */
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == 0 && p[1] >= 1)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1451,8 +1501,11 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0;
}
while (rdlen >= 2)
while (rdlen > 0)
{
if (rdlen < 2 || rdlen < p[1] + 2)
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC3 say our type exists? */
@@ -1499,11 +1552,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)
@@ -1597,9 +1650,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 && name - closest_encloser == wild_offset)
break;
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
@@ -1609,7 +1662,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0))
if (!extract_name(header, plen, &p, workspace1, EXTR_NAME_EXTRACT, 0))
return DNSSEC_FAIL_BADPACKET;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
@@ -1641,7 +1694,7 @@ 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;
@@ -1664,7 +1717,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;
@@ -1684,7 +1737,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 10))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p);
@@ -1735,7 +1788,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
{
unsigned char *psav;
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, EXTR_NAME_COMPARE, 10)))
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1);
@@ -1812,7 +1865,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;
}
@@ -1919,16 +1972,18 @@ 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;
unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE;
unsigned char *ans_start, *p1, *p2, *p3;
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx, gotdname;
int i, j, k, rc = STAT_INSECURE;
int secure = STAT_SECURE;
int rc_nsec;
unsigned long ttl;
/* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{
@@ -1946,8 +2001,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)
@@ -1965,7 +2023,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
targets[0] = p1;
targetidx = 1;
if (!extract_name(header, plen, &p1, name, 1, 4))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 4))
return STAT_BOGUS;
GETSHORT(qtype, p1);
@@ -1975,27 +2033,119 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Can't validate an RRSIG query */
if (qtype == T_RRSIG)
return STAT_INSECURE;
/* Find CNAME targets. */
for (gotdname = i = 0; i < ntohs(header->ancount); i++)
{
if (!(p1 = skip_name(p1, header, plen, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
if (type1 == T_DNAME)
gotdname = 1;
if (qtype != T_CNAME && qtype != T_ANY && type1 == T_CNAME && class1 == qclass)
{
if (!expand_workspace(&targets, &target_sz, targetidx))
return STAT_BOGUS;
targets[targetidx++] = p1; /* pointer to target name */
}
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
}
if (qtype != T_CNAME && qtype != T_ANY)
for (j = ntohs(header->ancount); j != 0; j--)
/* A DNAME capable of sythesising a CNAME means we don't need to validate the CNAME,
we can just assume that it's valid. RFC 4035 3.2.3 */
if (gotdname)
for (p1 = ans_start, i = 0; i < ntohs(header->ancount); i++)
{
if (!(p1 = skip_name(p1, header, plen, 10)))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p1);
p1 += 6; /* class, TTL */
GETSHORT(rdlen2, p1);
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
if (type2 == T_CNAME)
if (type1 != T_DNAME)
{
if (!expand_workspace(&targets, &target_sz, targetidx))
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
targets[targetidx++] = p1; /* pointer to target name */
}
if (!ADD_RDLEN(header, p1, plen, rdlen2))
return STAT_BOGUS;
else
{
if (!extract_name(header, plen, &p1, keyname, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS; /* bad packet */
/* We now have the name of the DNAME in name, and the target in keyname.
Look for any CNAMEs which could have been synthesised from this DNAME
and pre-qualify them. */
for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
{
if (!extract_name(header, plen, &p2, daemon->cname, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p2);
GETSHORT(class2, p2);
GETLONG(ttl, p2);
GETSHORT(rdlen2, p2);
if (type2 != T_CNAME || class2 != class1)
{
if (!ADD_RDLEN(header, p2, plen, rdlen2))
return STAT_BOGUS;
}
else
{
size_t name_prefix_len = strlen(daemon->cname) - strlen(name);
if (!extract_name(header, plen, &p2, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return STAT_BOGUS; /* bad packet */
/* We have the name of the CNAME in daemon->cname, and the target in daemon->workspacename.
See if the CNAME was sythesised from the DNAME.
CNAME must be <subdomain>.<dname>
CNAME target must be <subdomain>.<dname_target>
<subdomain>s must match for name and target. */
if (hostname_issubdomain(daemon->cname, name) == 1 &&
hostname_issubdomain(daemon->workspacename, keyname) == 1 &&
name_prefix_len == strlen(daemon->workspacename) - strlen(keyname))
{
char save = daemon->cname[name_prefix_len];
daemon->cname[name_prefix_len] = 0;
daemon->workspacename[name_prefix_len] = 0;
if (hostname_isequal(daemon->cname, daemon->workspacename))
{
/* pre-qualify this as validated */
daemon->rr_status[j] = ttl > 0 ? ttl : 1;
/* and remove it from the targets we need to have validated answers to. */
if (class2 == qclass)
{
daemon->cname[name_prefix_len] = save;
for (k = 0; k <targetidx; k++)
if ((p3 = targets[k]))
{
int rc1;
if (!(rc1 = extract_name(header, plen, &p3, daemon->cname, EXTR_NAME_COMPARE, 0)))
return STAT_BOGUS; /* bad packet */
if (rc1 == 1)
targets[k] = NULL;
}
}
}
}
}
}
}
}
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
@@ -2003,7 +2153,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
return STAT_BOGUS;
if (!extract_name(header, plen, &p1, name, 1, 10))
if (!extract_name(header, plen, &p1, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
GETSHORT(type1, p1);
@@ -2014,11 +2164,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Don't try and validate RRSIGs! */
if (type1 == T_RRSIG)
continue;
/* Pre-validated by DNAME above don't validate. */
if (daemon->rr_status[i] != 0)
continue;
/* Check if we've done this RRset already */
for (p2 = ans_start, j = 0; j < i; j++)
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
GETSHORT(type2, p2);
@@ -2039,47 +2193,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))
@@ -2089,12 +2228,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))
{
@@ -2103,7 +2242,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;
@@ -2115,7 +2254,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if ((p2 = targets[j]))
{
int rc1;
if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
if (!(rc1 = extract_name(header, plen, &p2, name, EXTR_NAME_COMPARE, 10)))
return STAT_BOGUS; /* bad packet */
if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
@@ -2129,8 +2268,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;
@@ -2142,6 +2281,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]))
@@ -2149,21 +2302,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (neganswer)
*neganswer = 1;
if (!extract_name(header, plen, &p2, name, 1, 10))
if (!extract_name(header, plen, &p2, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
/* For anything other than a DS record, this situation is OK if either
the answer is in an unsigned zone, or there's a NSEC records. */
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_BOGUS | rc_nsec;
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
@@ -2235,8 +2382,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;
+50 -38
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@ static int order(char *qdomain, size_t qlen, struct server *serv);
static int order_qsort(const void *a, const void *b);
static int order_servers(struct server *s, struct server *s2);
/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
/* If the server is USE_RESOLV or LITERAL_ADDRESS, it lives on the local_domains chain. */
#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
void build_server_array(void)
@@ -94,8 +94,8 @@ void build_server_array(void)
server=/.example.com/ works.
A flag of F_SERVER returns an upstream server only.
A flag of F_DNSSECOK returns a DNSSEC capable server only and
also disables NODOTS servers from consideration.
A flag of F_DNSSECOK disables NODOTS servers from consideration.
A flag of F_DS returns parent domain server.
A flag of F_DOMAINSRV returns a domain-specific server only.
A flag of F_CONFIG returns anything that generates a local
reply of IPv4 or IPV6.
@@ -107,12 +107,23 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
ssize_t qlen;
int try, high, low = 0;
int nlow = 0, nhigh = 0;
char *cp, *qdomain = domain;
char *cp, *qdomain;
/* may be no configured servers. */
if (daemon->serverarraysz == 0)
return 0;
/* DS records should come from the parent domain. */
if (flags & F_DS)
{
if ((cp = strchr(domain, '.')))
domain = cp+1;
else
domain = "";
}
qdomain = domain;
/* find query length and presence of '.' */
for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
if (*cp == '.')
@@ -245,7 +256,10 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
if (nodots &&
(daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
(nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
{
filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
qlen = 0;
}
if (lowout)
*lowout = nlow;
@@ -260,7 +274,6 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
return 1;
}
/* Return first server in group of equivalent servers; this is the "master" record. */
int server_samegroup(struct server *a, struct server *b)
{
return order_servers(a, b) == 0;
@@ -297,11 +310,13 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
}
else
{
/* Now the servers are on order between low and high, in the order
IPv6 addr, IPv4 addr, return zero for both, resolvconf servers, send upstream, no-data return.
/* Now the matching server records are all between low and high.
order_qsort() ensures that they are in the order
IPv6 addr, IPv4 addr, return zero for both, no-data return,
"use resolvconf" servers, domain-specific upstream servers.
See which of those match our query in that priority order and narrow (low, high) */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6))
@@ -326,33 +341,26 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
{
nlow = i;
/* Short to resolv.conf servers */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
/* now look for a NXDOMAIN answer --local=/domain/ */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
if (i != nlow)
if (!(flags & (F_DOMAINSRV | F_SERVER)) && i != nlow)
nhigh = i;
else
{
/* now look for a server */
for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
nlow = i;
/* return "use resolv.conf servers" if they exist */
for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
if (i != nlow)
{
/* If we want a server that can do DNSSEC, and this one can't,
return nothing, similarly if were looking only for a server
for a particular domain. */
if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
nlow = nhigh;
else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
nlow = nhigh;
else
nhigh = i;
}
nhigh = i;
else
{
/* --local=/domain/, only return if we don't need a server. */
if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
nhigh = i;
/* If we want a server for a particular domain, and this one isn't, return nothing. */
if (nlow < daemon->serverarraysz && nlow != nhigh && (flags & F_DOMAINSRV) &&
daemon->serverarray[nlow]->domain_len == 0 && !(daemon->serverarray[nlow]->flags & SERV_FOR_NODOTS))
nlow = nhigh;
}
}
}
@@ -407,6 +415,8 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
setup_reply(header, flags, ede);
gotname &= ~(F_QUERY | F_DS);
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0);
@@ -465,14 +475,14 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
}
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
int dnssec_server(struct server *server, char *keyname, int is_ds, int *firstp, int *lastp)
{
int first, last, index;
/* Find server to send DNSSEC query to. This will normally be the
same as for the original query, but may be another if
servers for domains are involved. */
if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
if (!lookup_domain(keyname, F_SERVER | F_DNSSECOK | (is_ds ? F_DS : 0), &first, &last))
return -1;
for (index = first; index != last; index++)
@@ -546,12 +556,14 @@ static int order_qsort(const void *a, const void *b)
rc = order_servers(s1, s2);
/* Sort all literal NODATA and local IPV4 or IPV6 responses together,
in a very specific order. We flip the SERV_LITERAL_ADDRESS bit
so the order is IPv6 literal, IPv4 literal, all-zero literal,
unqualified servers, upstream server, NXDOMAIN literal. */
in a very specific order IPv6 literal, IPv4 literal, all-zero literal,
NXDOMAIN literal. We also include SERV_USE_RESOLV in this, so that
use-standard servers sort before ordinary servers. (SERV_USR_RESOLV set
implies that none of SERV_LITERAL_ADDRESS,SERV_4ADDR,SERV_6ADDR,SERV_ALL_ZEROS
are set) */
if (rc == 0)
rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV))) -
((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS | SERV_USE_RESOLV)));
/* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
if (rc == 0)
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+13 -3
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -103,7 +103,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
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;
@@ -265,6 +265,15 @@ 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 */
@@ -494,7 +503,7 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
#define UMBRELLA_DEVICESZ sizeof(daemon->umbrella_device)
struct umbrella_opt {
u8 magic[4];
u8 magic[4] ATTRIBUTE_NONSTRING;
u8 version;
u8 flags;
/* We have 4 possible fields since we'll never send both IPv4 and
@@ -562,3 +571,4 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l
return plen;
}
+520 -315
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+132 -96
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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,38 +133,59 @@ 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);
}
}
}
static struct hostsfile *dyndir_addhosts(struct dyndir *dd, char *path)
static struct hostsfile *dyndir_addhosts(struct dyndir *dd, char *file)
{
/* Check if this file is already known in dd->files */
struct hostsfile *ah = NULL;
for(ah = dd->files; ah; ah = ah->next)
if(ah && ah->fname && strcmp(path, ah->fname) == 0)
struct hostsfile *ah;
size_t dirlen = strlen(dd->dname);
/* ah->fname always starts with the string in dd->dname */
for (ah = dd->files; ah; ah = ah->next)
if (ah->fname[dirlen] == '/' &&
strcmp(&ah->fname[dirlen+1], file) == 0)
return ah;
/* Not known, create new hostsfile record for this dyndir */
struct hostsfile *newah = NULL;
if(!(newah = whine_malloc(sizeof(struct hostsfile))))
return NULL;
if ((ah = whine_malloc(sizeof(struct hostsfile))))
{
char *path;
/* Add this file to the tip of the linked list */
newah->next = dd->files;
dd->files = newah;
/* Copy flags, set index and the full file path */
newah->flags = dd->flags;
newah->index = daemon->host_index++;
newah->fname = path;
return newah;
if (!(path = whine_malloc(dirlen + strlen(file) + 2)))
{
free(ah);
return NULL;
}
strcpy(path, dd->dname);
strcat(path, "/");
strcat(path, file);
/* Add this file to the tip of the linked list */
ah->next = dd->files;
dd->files = ah;
/* Copy flags, set index and the full file path */
ah->flags = dd->flags;
ah->index = daemon->host_index++;
ah->fname = path;
}
return ah;
}
@@ -204,10 +234,8 @@ void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revh
while ((ent = readdir(dir_stream)))
{
size_t lendir = strlen(dd->dname);
size_t lenfile = strlen(ent->d_name);
char *path;
/* ignore emacs backups and dotfiles */
if (lenfile == 0 ||
ent->d_name[lenfile - 1] == '~' ||
@@ -215,33 +243,36 @@ void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revh
ent->d_name[0] == '.')
continue;
if ((path = whine_malloc(lendir + lenfile + 2)))
if (dd->flags & AH_HOSTS)
{
struct hostsfile *ah;
strcpy(path, dd->dname);
strcat(path, "/");
strcat(path, ent->d_name);
if (!(ah = dyndir_addhosts(dd, path)))
{
free(path);
continue;
}
/* ignore non-regular files */
if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
{
if (dd->flags & AH_HOSTS)
total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
if ((ah = dyndir_addhosts(dd, ent->d_name)) &&
stat(ah->fname, &buf) != -1 && S_ISREG(buf.st_mode))
total_size = read_hostsfile(ah->fname, ah->index, total_size, rhash, revhashsz);
}
#ifdef HAVE_DHCP
else if (dd->flags & (AH_DHCP_HST | AH_DHCP_OPT))
else if (dd->flags & (AH_DHCP_HST | AH_DHCP_OPT))
{
char *path;
if ((path = whine_malloc(strlen(dd->dname) + lenfile + 2)))
{
strcpy(path, dd->dname);
strcat(path, "/");
strcat(path, ent->d_name);
/* ignore non-regular files */
if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
option_read_dynfile(path, dd->flags);
#endif
free(path);
}
}
#endif
}
closedir(dir_stream);
}
}
@@ -251,6 +282,8 @@ int inotify_check(time_t now)
int hit = 0;
struct dyndir *dd;
(void)now;
while (1)
{
int rc;
@@ -283,50 +316,27 @@ int inotify_check(time_t now)
for (dd = daemon->dynamic_dirs; dd; dd = dd->next)
if (dd->wd == in->wd)
{
size_t lendir = strlen(dd->dname);
char *path;
if ((path = whine_malloc(lendir + in->len + 2)))
if (dd->flags & AH_HOSTS)
{
struct hostsfile *ah = NULL;
strcpy(path, dd->dname);
strcat(path, "/");
strcat(path, in->name);
/* Is this is a deletion event? */
if (in->mask & IN_DELETE)
my_syslog(LOG_INFO, _("inotify: %s removed"), path);
else
my_syslog(LOG_INFO, _("inotify: %s new or modified"), path);
if (dd->flags & AH_HOSTS)
struct hostsfile *ah;
if ((ah = dyndir_addhosts(dd, in->name)))
{
if ((ah = dyndir_addhosts(dd, path)))
{
const unsigned int removed = cache_remove_uid(ah->index);
if (removed > 0)
my_syslog(LOG_INFO, _("inotify: flushed %u names read from %s"), removed, path);
const unsigned int removed = cache_remove_uid(ah->index);
/* (Re-)load hostsfile only if this event isn't triggered by deletion */
if (!(in->mask & IN_DELETE))
read_hostsfile(path, ah->index, 0, NULL, 0);
/* Is this is a deletion event? */
if (in->mask & IN_DELETE)
my_syslog(LOG_INFO, _("inotify: %s removed"), ah->fname);
else
my_syslog(LOG_INFO, _("inotify: %s new or modified"), ah->fname);
if (removed > 0)
my_syslog(LOG_INFO, _("inotify: flushed %u names read from %s"), removed, ah->fname);
/* (Re-)load hostsfile only if this event isn't triggered by deletion */
if (!(in->mask & IN_DELETE))
read_hostsfile(ah->fname, ah->index, 0, NULL, 0);
#ifdef HAVE_DHCP
if (daemon->dhcp || daemon->doing_dhcp6)
{
/* Propagate the consequences of loading a new dhcp-host */
dhcp_update_configs(daemon->dhcp_conf);
lease_update_from_configs();
lease_update_file(now);
lease_update_dns(1);
}
#endif
}
}
#ifdef HAVE_DHCP
else if (dd->flags & AH_DHCP_HST)
{
if (option_read_dynfile(path, AH_DHCP_HST))
if (daemon->dhcp || daemon->doing_dhcp6)
{
/* Propagate the consequences of loading a new dhcp-host */
dhcp_update_configs(daemon->dhcp_conf);
@@ -334,17 +344,43 @@ int inotify_check(time_t now)
lease_update_file(now);
lease_update_dns(1);
}
}
else if (dd->flags & AH_DHCP_OPT)
option_read_dynfile(path, AH_DHCP_OPT);
#endif
if (!ah)
free(path);
}
}
#ifdef HAVE_DHCP
else if (!(in->mask & IN_DELETE))
{
char *path;
if ((path = whine_malloc(strlen(dd->dname) + in->len + 2)))
{
strcpy(path, dd->dname);
strcat(path, "/");
strcat(path, in->name);
my_syslog(LOG_INFO, _("inotify: %s new or modified"), path);
if ((dd->flags & AH_DHCP_HST) && option_read_dynfile(path, AH_DHCP_HST))
{
/* Propagate the consequences of loading a new dhcp-host */
dhcp_update_configs(daemon->dhcp_conf);
lease_update_from_configs();
lease_update_file(now);
lease_update_dns(1);
}
if (dd->flags & AH_DHCP_OPT)
option_read_dynfile(path, AH_DHCP_OPT);
free(path);
}
}
#endif
}
}
}
return hit;
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+1 -1
View File
@@ -16,7 +16,7 @@
#include "dnsmasq.h"
#if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK)
#if defined(HAVE_LINUX_IPSET)
#include <string.h>
#include <errno.h>
+126 -8
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ static int read_leases(time_t now, FILE *leasestream)
unsigned long ei;
union all_addr addr;
struct dhcp_lease *lease;
int clid_len, hw_len, hw_type;
int opt_len, clid_len, hw_len, hw_type;
int items;
*daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
@@ -55,6 +55,34 @@ static int read_leases(time_t now, FILE *leasestream)
continue;
}
#endif
/* Weird backwards compatible way of adding extra fields to leases */
if ((strcmp(daemon->dhcp_buff3, "vendorclass") == 0 || strcmp(daemon->dhcp_buff3, "agent-info") == 0))
{
if (fscanf(leasestream, " %764s", daemon->packet) == 1)
{
if (inet_pton(AF_INET, daemon->dhcp_buff2, &addr.addr4))
lease = lease_find_by_addr(addr.addr4);
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, daemon->dhcp_buff2, &addr.addr6))
lease = lease6_find_by_plain_addr(&addr.addr6);
#endif
else
continue;
if (lease)
{
opt_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
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)
lease_set_agent_id(lease, (unsigned char *)daemon->packet, opt_len);
}
}
continue;
}
if (fscanf(leasestream, " %64s %255s %764s",
daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3)
@@ -251,8 +279,8 @@ void lease_update_file(time_t now)
{
struct dhcp_lease *lease;
time_t next_event;
int i, err = 0;
int i, err = 0, extras;
if (file_dirty != 0 && daemon->lease_stream)
{
errno = 0;
@@ -260,9 +288,11 @@ void lease_update_file(time_t now)
if (errno != 0 || ftruncate(fileno(daemon->lease_stream), 0) != 0)
err = errno;
for (lease = leases; lease; lease = lease->next)
for (extras = 0, lease = leases; lease; lease = lease->next)
{
if (lease->agent_id || lease->vendorclass)
extras = 1;
#ifdef HAVE_DHCP6
if (lease->flags & (LEASE_TA | LEASE_NA))
continue;
@@ -335,7 +365,37 @@ void lease_update_file(time_t now)
}
}
#endif
if (extras)
{
/* Dump this at the end for least confusion with older parsing code. */
for (lease = leases; lease; lease = lease->next)
{
#ifdef HAVE_DHCP6
if (lease->flags & (LEASE_TA | LEASE_NA))
inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
else
#endif
inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
if (lease->agent_id)
{
ourprintf(&err, "agent-info %s ", daemon->addrbuff);
for (i = 0; i < lease->agent_id_len - 1; i++)
ourprintf(&err, "%.2x:", lease->agent_id[i]);
ourprintf(&err, "%.2x\n", lease->agent_id[i]);
}
if (lease->vendorclass)
{
ourprintf(&err, "vendorclass %s ", daemon->addrbuff);
for (i = 0; i < lease->vendorclass_len - 1; i++)
ourprintf(&err, "%.2x:", lease->vendorclass[i]);
ourprintf(&err, "%.2x\n", lease->vendorclass[i]);
}
}
}
if (fflush(daemon->lease_stream) != 0 ||
fsync(fileno(daemon->lease_stream)) < 0)
err = errno;
@@ -713,6 +773,22 @@ struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 add
return NULL;
}
struct dhcp_lease *lease6_find_by_plain_addr(struct in6_addr *addr)
{
struct dhcp_lease *lease;
for (lease = leases; lease; lease = lease->next)
{
if (!(lease->flags & (LEASE_TA | LEASE_NA)))
continue;
if (IN6_ARE_ADDR_EQUAL(&lease->addr6, addr))
return lease;
}
return NULL;
}
/* Find largest assigned address in context */
u64 lease_find_max_addr6(struct dhcp_context *context)
{
@@ -1084,6 +1160,46 @@ void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
#endif
}
void lease_set_agent_id(struct dhcp_lease *lease, unsigned char *new, int len)
{
if (!lease->agent_id && !new)
return;
if (lease->agent_id && new && lease->agent_id_len == len && memcmp(lease->agent_id, new, len) == 0)
return;
file_dirty = 1;
free(lease->agent_id);
lease->agent_id = NULL;
if (new && (lease->agent_id = whine_malloc(len)))
{
memcpy(lease->agent_id, new, len);
lease->agent_id_len = len;
}
}
void lease_set_vendorclass(struct dhcp_lease *lease, unsigned char *new, int len)
{
if (!lease->vendorclass && !new)
return;
if (lease->vendorclass && new && lease->vendorclass_len == len && memcmp(lease->vendorclass, new, len) == 0)
return;
file_dirty = 1;
free(lease->vendorclass);
lease->vendorclass = NULL;
if (new && (lease->vendorclass = whine_malloc(len)))
{
memcpy(lease->vendorclass, new, len);
lease->vendorclass_len = len;
}
}
void rerun_scripts(void)
{
struct dhcp_lease *lease;
@@ -1143,9 +1259,11 @@ int do_script_run(time_t now)
#endif
old_leases = lease->next;
free(lease->old_hostname);
free(lease->hostname);
free(lease->clid);
free(lease->extradata);
free(lease->agent_id);
free(lease->vendorclass);
free(lease);
return 1;
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+5 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -43,6 +43,10 @@ const char * metric_names[] = {
"leases_allocated_6",
"leases_pruned_6",
"tcp_connections",
"dhcp_leasequery",
"dhcp_lease_unassigned",
"dhcp_lease_actve",
"dhcp_lease_unknown"
};
const char* get_metric_name(int i) {
+5 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -42,6 +42,10 @@ enum {
METRIC_LEASES_ALLOCATED_6,
METRIC_LEASES_PRUNED_6,
METRIC_TCP_CONNECTIONS,
METRIC_DHCPLEASEQUERY,
METRIC_DHCPLEASEUNASSIGNED,
METRIC_DHCPLEASEACTIVE,
METRIC_DHCPLEASEUNKNOWN,
__METRIC_MAX,
};
+8 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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));
+8 -35
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -1379,7 +1379,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
/* cannot set source _port_ for TCP connections. */
if (is_tcp)
port = 0;
else if (port == 0 && daemon->max_port != 0)
else if (port == 0 && daemon->max_port != 0 && daemon->max_port >= daemon->min_port)
{
/* Bind a random port within the range given by min-port and max-port if either
or both are set. Otherwise use the OS's random ephemeral port allocation by
@@ -1568,6 +1568,8 @@ void check_servers(int no_loop_check)
struct serverfd *sfd, *tmp, **up;
int port = 0, count;
int locals = 0;
(void)no_loop_check;
#ifdef HAVE_LOOP
if (!no_loop_check)
@@ -1587,33 +1589,6 @@ void check_servers(int no_loop_check)
for (count = 0, serv = daemon->servers; serv; serv = serv->next)
{
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
if (!(serv->flags & SERV_FOR_NODOTS))
serv->flags |= SERV_DO_DNSSEC;
/* Disable DNSSEC validation when using server=/domain/.... servers
unless there's a configured trust anchor. */
if (strlen(serv->domain) != 0)
{
struct ds_config *ds;
char *domain = serv->domain;
/* .example.com is valid */
while (*domain == '.')
domain++;
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
break;
if (!ds)
serv->flags &= ~SERV_DO_DNSSEC;
}
}
#endif
port = prettyprint_addr(&serv->addr, daemon->namebuff);
/* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
@@ -1659,10 +1634,6 @@ void check_servers(int no_loop_check)
{
char *s1, *s2, *s3 = "", *s4 = "";
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
s3 = _("(no DNSSEC)");
#endif
if (serv->flags & SERV_FOR_NODOTS)
s1 = _("unqualified"), s2 = _("names");
else if (strlen(serv->domain) == 0)
@@ -1696,7 +1667,7 @@ void check_servers(int no_loop_check)
if (++locals <= LOCALS_LOGGED)
my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
}
else if (serv->flags & SERV_USE_RESOLV)
else if (serv->flags & SERV_USE_RESOLV && serv->domain_len != 0)
my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
}
@@ -1806,8 +1777,10 @@ int reload_servers(char *fname)
/* Called when addresses are added or deleted from an interface */
void newaddress(time_t now)
{
#ifdef HAVE_DHCP
struct dhcp_relay *relay;
#endif
(void)now;
if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) ||
+2 -2
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
#include "dnsmasq.h"
#if defined (HAVE_NFTSET) && defined (HAVE_LINUX_NETWORK)
#if defined (HAVE_NFTSET)
#include <nftables/libnftables.h>
+112 -41
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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 {
@@ -193,6 +196,11 @@ struct myoption {
#define LOPT_MAX_PROCS 384
#define LOPT_DNSSEC_LIMITS 385
#define LOPT_PXE_OPT 386
#define LOPT_NO_ENCODE 387
#define LOPT_DO_ENCODE 388
#define LOPT_LEASEQUERY 389
#define LOPT_SPLIT_RELAY 390
#define LOPT_LOG_MALLOC 391
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -247,6 +255,8 @@ static const struct myoption opts[] =
{ "local-ttl", 1, 0, 'T' },
{ "no-negcache", 0, 0, 'N' },
{ "no-round-robin", 0, 0, LOPT_NORR },
{ "no-0x20-encode", 0, 0, LOPT_NO_ENCODE },
{ "do-0x20-encode", 0, 0, LOPT_DO_ENCODE },
{ "cache-rr", 1, 0, LOPT_CACHE_RR },
{ "addn-hosts", 1, 0, 'H' },
{ "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
@@ -369,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 },
@@ -390,6 +401,8 @@ static const struct myoption opts[] =
{ "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
{ "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 }
};
@@ -495,6 +508,7 @@ static struct {
{ '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL },
{ LOPT_BRIDGE, ARG_DUP, "<iface>,<alias>..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL },
{ LOPT_SHARED_NET, ARG_DUP, "<iface>|<addr>,<addr>", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL},
{ LOPT_LEASEQUERY, ARG_DUP, "[<addr>[/prefix>]]", gettext_noop("Enable RFC 4388 leasequery functions for DHCPv4"), NULL },
{ '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL },
{ '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL },
{ LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL },
@@ -535,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 },
@@ -568,7 +583,7 @@ static struct {
{ LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>,]...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
@@ -591,9 +606,12 @@ static struct {
{ LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
{ LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
{ LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
{ LOPT_NO_ENCODE, OPT_NO_0x20, NULL, gettext_noop("Suppress DNS bit 0x20 encoding."), NULL },
{ LOPT_DO_ENCODE, OPT_DO_0x20, NULL, gettext_noop("Enable DNS bit 0x20 encoding."), NULL },
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
{ LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
{ LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
{ LOPT_LOG_MALLOC, OPT_LOG_MALLOC, NULL, gettext_noop("Log memory allocation for debugging."), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -640,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);
}
@@ -955,7 +973,7 @@ char *parse_server(char *arg, struct server_details *sdetails)
hints.ai_family = AF_UNSPEC;
/* Get addresses suitable for sending datagrams. We assume that we can use the
same addresses for TCP connections. Settting this to zero gets each address
same addresses for TCP connections. Setting this to zero gets each address
threes times, for SOCK_STREAM, SOCK_RAW and SOCK_DGRAM, which is not useful. */
hints.ai_socktype = SOCK_DGRAM;
@@ -2669,15 +2687,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (msize > 128)
ret_err_free(_("bad prefix length"), new);
mask = (1LLU << (128 - msize)) - 1LLU;
/* prefix==64 overflows the mask calculation */
if (msize <= 64)
mask = (u64)-1LL;
else
mask = (1LLU << (128 - msize)) - 1LLU;
new->is6 = 1;
new->prefixlen = msize;
/* prefix==64 overflows the mask calculation above */
if (msize <= 64)
mask = (u64)-1LL;
new->end6 = new->start6;
setaddr6part(&new->start6, addrpart & ~mask);
setaddr6part(&new->end6, addrpart | mask);
@@ -2906,7 +2924,20 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
arg = comma;
} while (arg);
break;
#ifdef HAVE_DHCP
# if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
# endif
case LOPT_LEASEQUERY:
set_option_bool(OPT_LEASEQUERY);
if (!arg)
break;
# if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
# pragma GCC diagnostic pop
# endif
#endif
case 'B': /* --bogus-nxdomain */
case LOPT_IGNORE_ADDR: /* --ignore-address */
{
@@ -2942,6 +2973,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
baddr->next = daemon->bogus_addr;
daemon->bogus_addr = baddr;
}
#ifdef HAVE_DHCP
else if (option == LOPT_LEASEQUERY)
{
baddr->next = daemon->leasequery_addr;
daemon->leasequery_addr = baddr;
}
#endif
else
{
baddr->next = daemon->ignore_addr;
@@ -3429,6 +3467,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
set_option_bool(OPT_EXTRALOG);
set_option_bool(OPT_LOG_PROTO);
}
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;
@@ -3981,7 +4023,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
while (arg)
{
comma = split(arg);
if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */
if (strchr(arg, ':')) /* Ethernet address, netid or binary CLID */
{
if ((arg[0] == 'i' || arg[0] == 'I') &&
(arg[1] == 'd' || arg[1] == 'D') &&
@@ -4043,10 +4085,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
new_addr = opt_malloc(sizeof(struct addrlist));
new_addr->next = new->addr6;
new_addr->flags = 0;
new_addr->addr.addr6 = in6;
new->addr6 = new_addr;
if (pref)
{
@@ -4057,7 +4097,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0)
{
dhcp_config_free(new);
ret_err(_("bad IPv6 prefix"));
ret_err_free(_("bad IPv6 prefix"), new_addr);
}
new_addr->flags |= ADDRLIST_PREFIX;
@@ -4071,6 +4111,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (i == 8)
new_addr->flags |= ADDRLIST_WILDCARD;
new_addr->next = new->addr6;
new->addr6 = new_addr;
new->flags |= CONFIG_ADDR6;
}
#endif
@@ -4685,11 +4727,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)
@@ -4712,12 +4764,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, '#');
@@ -4738,7 +4793,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);
}
@@ -5331,7 +5388,8 @@ err:
new->class = C_IN;
new->name = NULL;
new->digestlen = 0;
if ((comma = split(arg)) && (algo = split(comma)))
{
int class = 0;
@@ -5349,29 +5407,37 @@ err:
algo = split(comma);
}
}
if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
!atoi_check16(comma, &new->keytag) ||
!atoi_check8(algo, &new->algo) ||
!atoi_check8(digest, &new->digest_type) ||
!(new->name = canonicalise_opt(arg)))
if (!(new->name = canonicalise_opt(arg)))
ret_err_free(_("bad trust anchor"), new);
/* Upper bound on length */
len = (2*strlen(keyhex))+1;
new->digest = opt_malloc(len);
unhide_metas(keyhex);
/* 4034: "Whitespace is allowed within digits" */
for (cp = keyhex; *cp; )
if (isspace((unsigned char)*cp))
for (cp1 = cp; *cp1; cp1++)
*cp1 = *(cp1+1);
else
cp++;
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
if (comma)
{
free(new->name);
ret_err_free(_("bad HEX in trust anchor"), new);
if (!algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
!atoi_check16(comma, &new->keytag) ||
!atoi_check8(algo, &new->algo) ||
!atoi_check8(digest, &new->digest_type))
{
free(new->name);
ret_err_free(_("bad trust anchor"), new);
}
/* Upper bound on length */
len = (2*strlen(keyhex))+1;
new->digest = opt_malloc(len);
unhide_metas(keyhex);
/* 4034: "Whitespace is allowed within digits" */
for (cp = keyhex; *cp; )
if (isspace((unsigned char)*cp))
for (cp1 = cp; *cp1; cp1++)
*cp1 = *(cp1+1);
else
cp++;
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
{
free(new->name);
ret_err_free(_("bad HEX in trust anchor"), new);
}
}
new->next = daemon->ds;
@@ -5671,7 +5737,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;
@@ -5920,6 +5986,9 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->randport_limit = 1;
daemon->host_index = SRC_AH;
daemon->max_procs = MAX_PROCS;
#ifdef HAVE_DUMPFILE
daemon->dump_mask = 0xffffffff;
#endif
#ifdef HAVE_DNSSEC
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
@@ -6219,6 +6288,8 @@ 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)
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);
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+2 -2
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -98,7 +98,7 @@ void poll_listen(int fd, short event)
{
if (arrsize == nfds)
{
/* Array too small, extend. */
/* Array too small. Extend. */
struct pollfd *new;
arrsize = (arrsize == 0) ? 64 : arrsize * 2;
+1 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+4 -4
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -411,7 +411,7 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad
if (!old_prefix && !parm.found_context)
return;
/* If we're sending router address instead of prefix in at least on prefix,
/* If we're sending router address instead of prefix in at least one prefix,
include the advertisement interval option. */
if (parm.adv_router)
{
@@ -825,10 +825,10 @@ time_t periodic_ra(time_t now)
}
else if (iface_enumerate(AF_INET6, &param, (callback_t){.af_inet6=iface_search}))
/* There's a context overdue, but we can't find an interface
associated with it, because it's for a subnet we dont
associated with it, because it's for a subnet we don't
have an interface on. Probably we're doing DHCP on
a remote subnet via a relay. Zero the timer, since we won't
ever be able to send ra's and satisfy it. */
ever be able to send RAs to satisfy it. */
context->ra_time = 0;
if (param.iface != 0 &&
+264 -223
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,16 +16,42 @@
#include "dnsmasq.h"
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes)
{
unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
unsigned int j, l, namelen = 0, hops = 0;
int retvalue = 1;
if (isExtract)
*cp = 0;
/* EXTR_NAME_EXTRACT -> extract name
EXTR_NAME_COMPARE -> compare name, case insensitive
EXTR_NAME_NOCASE -> compare name, case sensitive
EXTR_NAME_FLIP -> flip 0x20 bits in packet.
For flip, name is an array of ints, whose size
is given in parm, which forms the bitmap. Bits beyond the size
are assumed to be zero.
return = 0 -> error
return = 1 -> extract OK, compare OK, flip OK
return = 2 -> extract OK, compare failed.
return = 3 -> extract OK, compare failed but only on case.
If pp == NULL, operate on the query name in the packet.
*/
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int func, unsigned int parm)
{
unsigned char *cp = (unsigned char *)name, *p1 = NULL;
unsigned int j, l, namelen = 0, hops = 0;
unsigned int bigmap_counter = 0, bigmap_posn = 0, bigmap_size = parm, bitmap = 0;
int retvalue = 1, case_insens = 1, isExtract = 0, flip = 0, extrabytes = (int)parm;
unsigned int *bigmap = (unsigned int *)name;
unsigned char *p = pp ? *pp : (unsigned char *)(header+1);
if (func == EXTR_NAME_EXTRACT)
isExtract = 1, *cp = 0;
else if (func == EXTR_NAME_NOCASE)
case_insens = 0;
else if (func == EXTR_NAME_FLIP)
{
flip = 1, extrabytes = 0;
name = NULL;
}
while (1)
{
unsigned int label_type;
@@ -46,13 +72,16 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
cp--;
*cp = 0; /* terminate: lose final period */
}
else if (*cp != 0)
else if (!flip && *cp != 0)
retvalue = 2;
if (p1) /* we jumped via compression */
*pp = p1;
else
*pp = p;
if (pp)
{
if (p1) /* we jumped via compression */
*pp = p1;
else
*pp = p;
}
return retvalue;
}
@@ -85,7 +114,7 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
if (!CHECK_LEN(header, p, plen, l))
return 0;
for(j=0; j<l; j++, p++)
for (j=0; j<l; j++, p++)
if (isExtract)
{
unsigned char c = *p;
@@ -98,6 +127,24 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
else
*cp++ = c;
}
else if (flip)
{
unsigned char c = *p;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
/* Get the next int of the bitmap */
if (bigmap_posn < bigmap_size && bigmap_counter-- == 0)
{
bitmap = bigmap[bigmap_posn++];
bigmap_counter = (sizeof(unsigned int) * 8) - 1;
}
if (bitmap & 1)
*p ^= 0x20;
bitmap >>= 1;
}
}
else
{
unsigned char c1 = *cp, c2 = *p;
@@ -107,23 +154,35 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
else
{
cp++;
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c1 == NAME_ESCAPE)
c1 = (*cp++)-1;
else if (case_insens && c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
if (case_insens && c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (!case_insens && retvalue != 2 && c1 != c2)
{
if (c1 >= 'A' && c1 <= 'Z')
c1 += 'a' - 'A';
if (c2 >= 'A' && c2 <= 'Z')
c2 += 'a' - 'A';
if (c1 == c2)
retvalue = 3;
}
if (c1 != c2)
retvalue = 2;
retvalue = 2;
}
}
if (isExtract)
*cp++ = '.';
else if (*cp != 0 && *cp++ != '.')
else if (!flip && *cp != 0 && *cp++ != '.')
retvalue = 2;
}
else
@@ -356,6 +415,7 @@ int private_net(struct in_addr addr, int ban_localhost)
(((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost) /* 127.0.0.0/8 (loopback) */ ||
(((ip_addr & 0xFF000000) == 0x00000000) && ban_localhost) /* RFC 5735 section 3. "here" network */ ||
((ip_addr & 0xFF000000) == 0x0A000000) /* 10.0.0.0/8 (private) */ ||
((ip_addr & 0xFFC00000) == 0x64400000) /* 100.64.0.0/10 (CG-NAT) RFC6598/RFC7793*/ ||
((ip_addr & 0xFFF00000) == 0xAC100000) /* 172.16.0.0/12 (private) */ ||
((ip_addr & 0xFFFF0000) == 0xC0A80000) /* 192.168.0.0/16 (private) */ ||
((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ||
@@ -365,7 +425,7 @@ int private_net(struct in_addr addr, int ban_localhost)
((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF) /* 255.255.255.255/32 (broadcast)*/ ;
}
static int private_net6(struct in6_addr *a, int ban_localhost)
int private_net6(struct in6_addr *a, int ban_localhost)
{
/* Block IPv4-mapped IPv6 addresses in private IPv4 address space */
if (IN6_IS_ADDR_V4MAPPED(a))
@@ -399,7 +459,7 @@ int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
if (i == ntohs(header->ancount) && !(p = skip_section(p, ntohs(header->nscount), header, qlen)))
return done;
if (!extract_name(header, qlen, &p, namebuff, 1, 10))
if (!extract_name(header, qlen, &p, namebuff, EXTR_NAME_EXTRACT, 10))
return done; /* bad packet */
GETSHORT(qtype, p);
@@ -456,7 +516,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;
@@ -475,12 +535,9 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
if (substring)
*substring = name_len;
if (ttlp)
*ttlp = daemon->neg_ttl;
for (i = 0; i < ntohs(header->nscount); i++)
{
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -499,7 +556,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
{
int prefix = name_len - soa_len;
if (!no_cache)
if (cache)
{
if (!(addr.rrblock.rrdata = blockdata_alloc(NULL, 0)))
return 0;
@@ -509,14 +566,14 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
for (j = 0; j < 2; j++) /* MNAME, RNAME */
{
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, qlen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
{
if (!no_cache)
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))
@@ -531,24 +588,24 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
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 && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += 20;
if (!no_cache)
if (cache)
{
int secflag = 0;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += 20;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
{
@@ -631,12 +688,12 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int flag)
Return 2 if the packet is malformed.
*/
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
struct ipsets *ipsets, struct ipsets *nftsets, int check_rebind,
int no_cache_dnssec, int secure)
{
unsigned char *p, *p1, *endrr, *namep;
int j, qtype, qclass, aqtype, aqclass, ardlen, res;
unsigned long ttl = 0;
unsigned long ttl;
union all_addr addr;
#ifdef HAVE_IPSET
char **ipsets_cur;
@@ -648,19 +705,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
#else
(void)nftsets; /* unused */
#endif
int found = 0, cname_count = CNAME_CHAIN;
struct crec *cpp = NULL;
int name_encoding, found = 0, ptr = 0;
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
#ifdef HAVE_DNSSEC
int cname_short = 0;
#endif
unsigned long cttl = ULONG_MAX, attl;
cache_start_insert();
namep = p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 2; /* bad packet */
GETSHORT(qtype, p);
@@ -668,114 +720,73 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (qclass != C_IN)
return 0;
/* PTRs: we chase CNAMEs here, since we have no way to
represent them in the cache. */
if (qtype == T_PTR)
/* If the PTR record encodes an address, store using a name/address record with F_REVERSE set.
Otherwise, it gets stored as an arbitrary RR below. If the query is answerable with
a CNAME, also take the arbitrary-RR route, since the cache can't represent a CNAME
whose target is stored in a F_REVERSE record. */
if (qtype == T_PTR && !(flags & F_NXDOMAIN) && (name_encoding = in_arpa_name_2_addr(name, &addr)))
{
int insert = 1, name_encoding = in_arpa_name_2_addr(name, &addr);
ptr = 1;
if (!(flags & F_NXDOMAIN))
{
cname_loop:
if (!(p1 = skip_questions(header, qlen)))
return 2;
for (j = 0; j < ntohs(header->ancount); j++)
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 2; /* bad packet */
GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1);
GETLONG(attl, p1);
if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign)
{
(p1) -= 4;
PUTLONG(daemon->max_ttl, p1);
}
GETSHORT(ardlen, p1);
endrr = p1+ardlen;
/* TTL of record is minimum of CNAMES and PTR */
if (attl < cttl)
cttl = attl;
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR))
{
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && j < daemon->rr_status_sz && daemon->rr_status[j] != 0)
{
/* validated RR anywhere in CNAME chain, don't cache. */
if (cname_short || aqtype == T_CNAME)
insert = 0;
secflag = F_DNSSECOK;
/* limit TTL based on signature. */
if (daemon->rr_status[j] < cttl)
cttl = daemon->rr_status[j];
}
#endif
if (aqtype == T_CNAME)
log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
if (!extract_name(header, qlen, &p1, name, 1, 0))
return 2;
if (aqtype == T_CNAME)
{
if (!cname_count--)
return 0; /* looped CNAMES, we can't cache. */
#ifdef HAVE_DNSSEC
cname_short = 1;
#endif
goto cname_loop;
}
found = 1;
if (!name_encoding)
log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, NULL, aqtype);
else
{
log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL, 0);
if (insert)
cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE);
}
}
p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0))
return 2; /* bad packet */
}
}
if (!(p1 = skip_questions(header, qlen)))
return 2;
if (!found && !option_bool(OPT_NO_NEG))
for (j = 0; j < ntohs(header->ancount); j++)
{
/* For reverse records, we use the name field to store the SOA name. */
int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, EXTR_NAME_COMPARE, 10)))
return 2; /* bad packet */
flags |= F_NEG | (secure ? F_DNSSECOK : 0);
if (name_encoding && ttl)
GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1);
p = p1;
GETLONG(ttl, p1);
GETSHORT(ardlen, p1);
endrr = p1+ardlen;
if (aqclass == C_IN && res == 1 && aqtype == T_PTR)
{
flags |= F_REVERSE | name_encoding;
if (!have_soa)
flags |= F_NO_RR; /* Marks no SOA found. */
cache_insert(name + substring, &addr, C_IN, now, ttl, flags);
found = 1;
if ((daemon->max_ttl != 0) && (ttl > daemon->max_ttl))
ttl = daemon->max_ttl;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && j < daemon->rr_status_sz && daemon->rr_status[j] != 0)
{
secflag = F_DNSSECOK;
/* limit TTL based on signature. */
if (daemon->rr_status[j] < ttl)
ttl = daemon->rr_status[j];
}
#endif
PUTLONG(ttl, p);
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
return 2;
log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL, 0);
cache_insert(name, &addr, C_IN, now, ttl, name_encoding | secflag | F_REVERSE);
/* restore query into name */
p1 = namep;
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
return 2;
}
log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0))
return 2; /* bad packet */
}
}
else
if (!ptr || !found)
{
/* everything other than PTR */
struct crec *newc;
int addrlen = 0, insert = 1;
struct crec *newc, *cpp = NULL;
int cname_count = CNAME_CHAIN, addrlen = 0, insert = 1;
if (qtype == T_A)
{
addrlen = INADDRSZ;
@@ -787,12 +798,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
flags |= F_IPV6;
}
else if (qtype != T_CNAME &&
(qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype) || rr_on_list(daemon->cache_rr, T_ANY)))
(qtype == T_SRV || qtype == T_PTR || rr_on_list(daemon->cache_rr, qtype) || rr_on_list(daemon->cache_rr, T_ANY)))
flags |= F_RR;
else
insert = 0; /* NOTE: do not cache data from CNAME queries. */
cname_loop1:
cname_loop:
if (!(p1 = skip_questions(header, qlen)))
return 2;
@@ -800,17 +811,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
if (!(res = extract_name(header, qlen, &p1, name, EXTR_NAME_COMPARE, 10)))
return 2; /* bad packet */
GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1);
GETLONG(attl, p1);
if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign)
{
(p1) -= 4;
PUTLONG(daemon->max_ttl, p1);
}
p = p1;
GETLONG(ttl, p1);
GETSHORT(ardlen, p1);
endrr = p1+ardlen;
@@ -823,6 +830,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr;
continue;
}
if ((daemon->max_ttl != 0) && (ttl > daemon->max_ttl))
ttl = daemon->max_ttl;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && j < daemon->rr_status_sz && daemon->rr_status[j] != 0)
@@ -830,10 +840,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
secflag = F_DNSSECOK;
/* limit TTl based on sig. */
if (daemon->rr_status[j] < attl)
attl = daemon->rr_status[j];
if (daemon->rr_status[j] < ttl)
ttl = daemon->rr_status[j];
}
#endif
PUTLONG(ttl, p);
if (aqtype == T_CNAME)
{
@@ -844,7 +856,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (insert)
{
if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag)))
if ((newc = cache_insert(name, NULL, C_IN, now, ttl, F_CNAME | F_FORWARD | secflag)))
{
newc->addr.cname.target.cache = NULL;
newc->addr.cname.is_name_ptr = 0;
@@ -857,16 +869,15 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
}
cpp = newc;
if (attl < cttl)
cttl = attl;
}
/* Set the query to the CNAME target and go again unless the query was just for a CNAME. */
namep = p1;
if (!extract_name(header, qlen, &p1, name, 1, 0))
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
return 2;
if (qtype != T_CNAME)
goto cname_loop1;
goto cname_loop;
found = 1;
}
@@ -932,7 +943,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
/* Name, extract it then re-encode. */
int len;
if (!extract_name(header, qlen, &p1, name, 1, 0))
if (!extract_name(header, qlen, &p1, name, EXTR_NAME_EXTRACT, 0))
{
blockdata_free(addr.rrblock.rrdata);
return 2;
@@ -965,7 +976,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
} while (desc != -1);
/* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0))
if (!extract_name(header, qlen, &tmp, name, EXTR_NAME_EXTRACT, 0))
{
blockdata_free(addr.rrblock.rrdata);
return 2;
@@ -990,24 +1001,39 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
private_net6(&addr.addr6, !option_bool(OPT_LOCAL_REBIND)))
return 1;
}
if (flags & (F_IPV4 | F_IPV6))
{
/* If we're a child process, send this to the parent,
since the ipset and nfset access is not re-entrant. */
#ifdef HAVE_IPSET
if (ipsets && (flags & (F_IPV4 | F_IPV6)))
for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
if (ipsets)
{
if (daemon->pipe_to_parent != -1)
cache_send_ipset(PIPE_OP_IPSET, ipsets, flags, &addr);
else
for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
}
#endif
#ifdef HAVE_NFTSET
if (nftsets && (flags & (F_IPV4 | F_IPV6)))
for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
if (nftsets)
{
if (daemon->pipe_to_parent != -1)
cache_send_ipset(PIPE_OP_NFTSET, nftsets, flags, &addr);
else
for (nftsets_cur = nftsets->sets; *nftsets_cur; nftsets_cur++)
if (add_to_nftset(*nftsets_cur, &addr, flags, 0) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, nftsets->domain, &addr, *nftsets_cur, 0);
}
#endif
}
}
if (insert)
{
newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag);
newc = cache_insert(name, &addr, C_IN, now, ttl, flags | F_FORWARD | secflag);
if (newc && cpp)
{
next_uid(newc);
@@ -1053,28 +1079,43 @@ 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 there's no SOA to get the TTL from, but there is a CNAME
pointing at this, inherit its TTL */
if (ttl || cpp)
{
if (!ttl)
ttl = cttl;
addr.rrdata.datalen = substring;
addr.rrdata.rrtype = qtype;
if (!have_soa)
flags |= F_NO_RR; /* Marks no SOA found. */
}
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp)
{
next_uid(newc);
cpp->addr.cname.target.cache = newc;
cpp->addr.cname.uid = newc->uid;
if (have_soa || daemon->neg_ttl)
{
if (have_soa)
{
addr.rrdata.datalen = substring;
addr.rrdata.rrtype = qtype;
}
else
{
/* If daemon->neg_ttl is set, we can cache even without an SOA. */
ttl = daemon->neg_ttl;
flags |= F_NO_RR; /* Marks no SOA found. */
}
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp)
{
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);
}
}
}
}
@@ -1127,7 +1168,7 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
{
int aqtype, aqclass, ardlen;
if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
if (!extract_name(header, len, &p, daemon->namebuff, EXTR_NAME_EXTRACT, 10))
return;
if (!CHECK_LEN(header, p, len, 10))
@@ -1145,7 +1186,7 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
{
if (aqtype == T_CNAME)
{
if (!extract_name(header, len, &p, daemon->workspacename, 1, 0))
if (!extract_name(header, len, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return;
if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename))
ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, attl);
@@ -1179,7 +1220,8 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark)
/* If the packet holds exactly one query
return F_IPV4 or F_IPV6 and leave the name from the query in name */
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
unsigned short *typep, unsigned short *classp)
{
unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass;
@@ -1195,7 +1237,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
if (!(header->hb3 & HB3_QR) && (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0))
return 0; /* non-standard query. */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -1204,6 +1246,9 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
if (typep)
*typep = qtype;
if (classp)
*classp = qclass;
if (qclass == C_IN)
{
if (qtype == T_A)
@@ -1214,14 +1259,10 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name,
return F_IPV4 | F_IPV6;
}
#ifdef HAVE_DNSSEC
/* F_DNSSECOK as agument to search_servers() inhibits forwarding
to servers for domains without a trust anchor. This make the
behaviour for DS and DNSKEY queries we forward the same
/* Make the behaviour for DS and DNSKEY queries we forward the same
as for DS and DNSKEY queries we originate. */
if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
return F_DNSSECOK;
#endif
if (qtype == T_DS || qtype == T_DNSKEY)
return F_DNSSECOK | (qtype == T_DS ? F_DS : 0);
return F_QUERY;
}
@@ -1309,7 +1350,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu
for (i = ntohs(header->ancount); i != 0; i--)
{
if (name && !extract_name(header, qlen, &p, name, 1, 10))
if (name && !extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 10))
return 0; /* bad packet */
if (!name && !(p = skip_name(p, header, qlen, 10)))
@@ -1386,6 +1427,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, ...)
{
@@ -1583,7 +1629,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int rd_bit = (header->hb3 & HB3_RD);
int count = 255; /* catch loops */
/* Suppress cached answers of no_cache set. */
/* Suppress cached answers if no_cache set. */
if (no_cache)
rd_bit = 0;
@@ -1593,14 +1639,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (filtered)
*filtered = 0;
/* never answer queries with RD unset, to avoid cache snooping. */
if ( ntohs(header->qdcount) != 1 ||
ntohs(header->ancount) != 0 ||
if (ntohs(header->qdcount) != 1 ||
ntohs(header->ancount) != 0 ||
ntohs(header->nscount) != 0 ||
ntohs(header->qdcount) == 0 ||
OPCODE(header) != QUERY )
return 0;
/* Don't return AD set if checking disabled. */
if (header->hb4 & HB4_CD)
sec_data = 0;
@@ -1619,7 +1663,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
nameoffset = p - (unsigned char *)header;
/* now extract name as .-concatenated string into name */
if (!extract_name(header, qlen, &p, name, 1, 4))
if (!extract_name(header, qlen, &p, name, EXTR_NAME_EXTRACT, 4))
return 0; /* bad packet */
GETSHORT(qtype, p);
@@ -2263,14 +2307,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
For FORWARD NEG records, the addr.rrdata.datalen field of the othewise
empty addr is used to held an offset in to the name which yields the SOA
name. For REVERSE NEG records, the otherwise empty name field holds the
SOA name. If soa_name has zero length, then no SOA is known. soa_lookup
MUST be a neg record here.
name.
If the F_NO_RR flag is set, there was no SOA record supplied with the RR. */
if (soa_lookup && !(soa_lookup->flags & F_NO_RR))
{
char *soa_name = soa_lookup->flags & F_REVERSE ? cache_get_name(soa_lookup) : name + soa_lookup->addr.rrdata.datalen;
char *soa_name = name + soa_lookup->addr.rrdata.datalen;
crecp = NULL;
while ((crecp = cache_find_by_name(crecp, soa_name, now, F_RR)))
@@ -2339,7 +2380,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!(ansp = skip_questions(header, qlen)))
return 0; /* bad packet */
anscount = nscount = addncount = 0;
log_query(F_CONFIG, "reply", NULL, "truncated", 0);
log_query(0, "reply", NULL, "truncated", 0);
}
if (nxdomain)
+542 -123
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -56,7 +56,8 @@ static void do_options(struct dhcp_context *context,
time_t now,
unsigned int lease_time,
unsigned short fuzz,
const char *pxevendor);
const char *pxevendor,
int leasequery);
static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
@@ -73,7 +74,7 @@ static void handle_encap(struct dhcp_packet *mess, unsigned char *end, unsigned
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int loopback,
int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
int *is_inform, int pxe, struct in_addr fallback, time_t recvtime, struct in_addr leasequery_source)
{
unsigned char *opt, *clid = NULL;
struct dhcp_lease *ltmp, *lease = NULL;
@@ -108,7 +109,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
#endif
subnet_addr.s_addr = override.s_addr = 0;
/* set tag with name == interface */
iface_id.net = iface_name;
iface_id.next = NULL;
@@ -185,7 +186,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
if (mess_type != DHCPLEASEQUERY && (opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
{
/* Any agent-id needs to be copied back out, verbatim, as the last option
in the packet. Here, we shift it to the very end of the buffer, if it doesn't
@@ -203,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);
@@ -211,7 +216,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SERVER_OR, INADDRSZ)))
override = option_addr(sopt);
/* if a circuit-id or remote-is option is provided, exact-match to options. */
/* if a circuit-id or remote-is option is provided, exact-match to options. */
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
{
int search;
@@ -224,7 +229,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
search = SUBOPT_SUBSCR_ID;
else
continue;
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), search, 1)) &&
vendor->len == option_len(sopt) &&
memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0)
@@ -234,7 +239,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
}
/* Check for RFC3011 subnet selector - only if RFC3527 one not present */
if (subnet_addr.s_addr == 0 && (opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
subnet_addr = option_addr(opt);
@@ -288,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)
{
@@ -356,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 )
@@ -373,44 +385,47 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
context = context_new;
}
if (!context)
if (mess_type != DHCPLEASEQUERY)
{
const char *via;
if (subnet_addr.s_addr)
if (!context)
{
via = _("with subnet selector");
inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
}
else
{
via = _("via");
if (mess->giaddr.s_addr)
inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
else
safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
}
my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
via, daemon->addrbuff);
return 0;
}
if (option_bool(OPT_LOG_OPTS))
{
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);
if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
const char *via;
if (subnet_addr.s_addr)
{
inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
via = _("with subnet selector");
inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
}
else
{
inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
via = _("via");
if (mess->giaddr.s_addr)
inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
else
safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
}
my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
via, daemon->addrbuff);
return 0;
}
if (option_bool(OPT_LOG_OPTS))
{
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);
if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
{
inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
}
else
{
inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
}
}
}
}
@@ -672,7 +687,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
clear_packet(mess, end);
do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr),
netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL, 0);
}
}
@@ -818,8 +833,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
}
if (config)
if (mess_type != DHCPLEASEQUERY && config)
{
struct dhcp_netid_list *list;
@@ -860,7 +875,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
clid = NULL;
/* Check if client is PXE client. */
if (daemon->enable_pxe &&
if (mess_type != DHCPLEASEQUERY &&
daemon->enable_pxe &&
is_pxe_client(mess, sz, &pxevendor))
{
if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
@@ -1035,7 +1051,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
/* if we're just a proxy server, go no further */
if ((context->flags & CONTEXT_PROXY) || pxe)
if (mess_type != DHCPLEASEQUERY &&
((context->flags & CONTEXT_PROXY) || pxe))
return 0;
if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
@@ -1047,6 +1064,176 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
switch (mess_type)
{
case DHCPLEASEQUERY:
mess_type = DHCPLEASEUNKNOWN;
if (!option_bool(OPT_LEASEQUERY))
return 0;
if (leasequery_source.s_addr == 0)
return 0;
inet_ntop(AF_INET, &leasequery_source, daemon->workspacename, ADDRSTRLEN);
if (daemon->leasequery_addr)
{
struct bogus_addr *baddrp;
for (baddrp = daemon->leasequery_addr; baddrp; baddrp = baddrp->next)
if (!baddrp->is6 && is_same_net_prefix(leasequery_source, baddrp->addr.addr4, baddrp->prefix))
break;
if (!baddrp)
{
my_syslog(MS_DHCP | LOG_WARNING, _("leasequery from %s not permitted"), daemon->workspacename);
return 0;
}
}
daemon->metrics[METRIC_DHCPLEASEQUERY]++;
log_packet("DHCPLEASEQUERY", mess->ciaddr.s_addr ? &mess->ciaddr : NULL, emac_len != 0 ? emac : NULL, emac_len,
iface_name, "from ", daemon->workspacename, mess->xid);
/* Put all the contexts on the ->current list for the next stages. */
for (context = daemon->dhcp; context; context = context->next)
context->current = context->next;
/* Have maybe already found the lease by MAC or clid. */
if (mess->ciaddr.s_addr != 0 &&
!(lease = lease_find_by_addr(mess->ciaddr)) &&
address_available(daemon->dhcp, mess->ciaddr, tagif_netid))
{
mess_type = DHCPLEASEUNASSIGNED;
daemon->metrics[METRIC_DHCPLEASEUNASSIGNED]++;
}
if (lease)
{
/* RFC4388 para 6.4.2 */
if (lease->agent_id)
{
unsigned char *sopt ;
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
{
int search;
if (vendor->match_type == MATCH_CIRCUIT)
search = SUBOPT_CIRCUIT_ID;
else if (vendor->match_type == MATCH_REMOTE)
search = SUBOPT_REMOTE_ID;
else if (vendor->match_type == MATCH_SUBSCRIBER)
search = SUBOPT_SUBSCR_ID;
else
continue;
if ((sopt = option_find1(lease->agent_id, lease->agent_id + lease->agent_id_len, search, 1)) &&
vendor->len == option_len(sopt) &&
memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0)
{
vendor->netid.next = netid;
netid = &vendor->netid;
}
}
tagif_netid = run_tag_if(netid);
}
/* Now find the context for this lease and config for this host. */
if ((context = narrow_context(daemon->dhcp, lease->addr, tagif_netid)))
{
if ((config = find_config(daemon->dhcp_conf, context, lease->clid, lease->clid_len,
lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, lease->hostname, tagif_netid)))
{
struct dhcp_netid_list *list;
for (list = config->netid; list; list = list->next)
{
list->list->next = netid;
netid = list->list;
}
tagif_netid = run_tag_if(netid);
}
if (context->netid.net)
{
context->netid.next = netid;
tagif_netid = run_tag_if(&context->netid);
}
log_tags(tagif_netid, ntohl(mess->xid));
emac = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len, lease->hwaddr, lease->clid_len, lease->clid, &emac_len);
mess_type = DHCPLEASEACTIVE;
daemon->metrics[METRIC_DHCPLEASEACTIVE]++;
}
}
log_packet(mess_type == DHCPLEASEACTIVE ? "DHCPLEASEACTIVE" : (mess_type == DHCPLEASEUNASSIGNED ? "DHCPLEASEUNASSIGNED" : "DHCPLEASEUNKNOWN"),
mess_type == DHCPLEASEACTIVE ? &lease->addr : (mess->ciaddr.s_addr != 0 ? &mess->ciaddr : NULL),
emac_len != 0 ? emac : NULL, emac_len,
iface_name, mess_type == DHCPLEASEACTIVE ? lease->hostname : NULL, NULL, mess->xid);
clear_packet(mess, end);
option_put(mess, end, OPTION_MESSAGE_TYPE, 1, mess_type);
if (mess_type == DHCPLEASEUNKNOWN)
{
daemon->metrics[METRIC_DHCPLEASEUNKNOWN]++;
mess->ciaddr.s_addr = 0;
}
if (mess_type == DHCPLEASEACTIVE)
{
unsigned char *p;
mess->ciaddr = lease->addr;
mess->hlen = lease->hwaddr_len;
mess->htype = lease->hwaddr_type;
memcpy(mess->chaddr, lease->hwaddr, lease->hwaddr_len);
if (lease->clid && in_list(req_options, OPTION_CLIENT_ID) &&
(p = free_space(mess, end, OPTION_CLIENT_ID, lease->clid_len)))
memcpy(p, lease->clid, lease->clid_len);
if (in_list(req_options, OPTION_LEASE_TIME))
{
if (lease->expires == 0) /* infinite lease */
option_put(mess, end, OPTION_LEASE_TIME, 4, 0xffffffff);
else
option_put(mess, end, OPTION_LEASE_TIME, 4, (unsigned int)(lease->expires - now));
}
if (lease->expires != 0)
{
time = calc_time(context, config, NULL);
if (in_list(req_options, OPTION_T1) && (lease->expires - now) > time/2)
option_put(mess, end, OPTION_T1, 4, ((unsigned int)(lease->expires - now)) - time/2);
if (in_list(req_options, OPTION_T2) && (lease->expires - now) > time/8)
option_put(mess, end, OPTION_T2, 4, ((unsigned int)(lease->expires - now)) - time/8);
if (in_list(req_options, OPTION_LAST_TRANSACTION) && (lease->expires - now) < time)
option_put(mess, end, OPTION_LAST_TRANSACTION, 4, time - ((unsigned int)(lease->expires - now)));
}
if (lease->vendorclass)
{
memcpy(daemon->dhcp_buff3, lease->vendorclass, lease->vendorclass_len);
vendor_class_len = lease->vendorclass_len;
}
subnet_addr.s_addr = 0;
do_options(context, mess, end, req_options, lease->hostname, get_domain(lease->addr), netid, subnet_addr,
0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL, 1);
/* Does this have to be last for leasequery replies also? RFC 4388 is silent on the subject. */
if (lease->agent_id && in_list(req_options, OPTION_AGENT_ID) &&
(p = free_space(mess, end, OPTION_AGENT_ID, lease->agent_id_len)))
memcpy(p, lease->agent_id, lease->agent_id_len);
}
return dhcp_packet_size(mess, NULL, real_end);
case DHCPDECLINE:
if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
option_addr(opt).s_addr != server_id(context, override, fallback).s_addr)
@@ -1197,7 +1384,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_LEASE_TIME, 4, time);
/* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr),
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor, 0);
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1345,7 +1532,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
else if (!lease && (ltmp = lease_find_by_addr(mess->yiaddr)))
{
/* If a host is configured with more than one MAC address, it's OK to 'nix
a lease from one of it's MACs to give the address to another. */
a lease from one of its MACs to give the address to another. */
if (config && config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type))
{
inet_ntop(AF_INET, &ltmp->addr, daemon->addrbuff, ADDRSTRLEN);
@@ -1528,7 +1715,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
lease_set_expires(lease, time, now);
lease_set_interface(lease, int_index, now);
if (option_bool(OPT_LEASEQUERY))
{
if (agent_id)
lease_set_agent_id(lease, option_ptr(agent_id, 0), option_len(agent_id));
if (vendor_class_len != 0)
lease_set_vendorclass(lease, (unsigned char *)daemon->dhcp_buff3, vendor_class_len);
}
else
{
/* if leasequery no longer enabled, remove stuff that may have been stored when it was. */
lease_set_agent_id(lease, NULL, 0);
lease_set_vendorclass(lease, NULL, 0);
}
if (override.s_addr != 0)
lease->override = override;
else
@@ -1544,7 +1745,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (rapid_commit)
option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr),
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor, 0);
}
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1611,7 +1812,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor, 0);
*is_inform = 1; /* handle reply differently */
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1720,29 +1921,33 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
return;
daemon->addrbuff[0] = 0;
daemon->addrbuff[0] = daemon->namebuff[0] = 0;
if (addr)
inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
print_mac(daemon->namebuff, ext_mac, mac_len);
if (ext_mac)
print_mac(daemon->namebuff, ext_mac, mac_len);
if (option_bool(OPT_LOG_OPTS))
my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s",
my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s%s%s%s",
ntohl(xid),
type,
interface,
daemon->addrbuff,
addr ? " " : "",
daemon->namebuff,
ext_mac ? " " : "",
string ? string : "",
err ? err : "");
else
my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s",
my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s%s%s%s",
type,
interface,
daemon->addrbuff,
addr ? " " : "",
daemon->namebuff,
ext_mac ? " " : "",
string ? string : "",
err ? err : "");
@@ -2412,7 +2617,8 @@ static void do_options(struct dhcp_context *context,
time_t now,
unsigned int lease_time,
unsigned short fuzz,
const char *pxevendor)
const char *pxevendor,
int leasequery)
{
struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
struct dhcp_boot *boot;
@@ -2450,70 +2656,73 @@ static void do_options(struct dhcp_context *context,
}
}
for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
if ((!id_list->list) || match_netid(id_list->list, netid, 0))
break;
if (id_list)
mess->flags |= htons(0x8000); /* force broadcast */
if (context)
mess->siaddr = context->local;
/* See if we can send the boot stuff as options.
To do this we need a requested option list, BOOTP
and very old DHCP clients won't have this, we also
provide a manual option to disable it.
Some PXE ROMs have bugs (surprise!) and need zero-terminated
names, so we always send those. */
if ((boot = find_boot(tagif)))
if (!leasequery)
{
if (boot->sname)
{
if (!option_bool(OPT_NO_OVERRIDE) &&
req_options &&
in_list(req_options, OPTION_SNAME))
option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
else
safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
}
for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
if ((!id_list->list) || match_netid(id_list->list, netid, 0))
break;
if (id_list)
mess->flags |= htons(0x8000); /* force broadcast */
if (boot->file)
if (context)
mess->siaddr = context->local;
/* See if we can send the boot stuff as options.
To do this we need a requested option list, BOOTP
and very old DHCP clients won't have this, we also
provide a manual option to disable it.
Some PXE ROMs have bugs (surprise!) and need zero-terminated
names, so we always send those. */
if ((boot = find_boot(tagif)))
{
if (!option_bool(OPT_NO_OVERRIDE) &&
req_options &&
in_list(req_options, OPTION_FILENAME))
option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
else
safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
if (boot->sname)
{
if (!option_bool(OPT_NO_OVERRIDE) &&
req_options &&
in_list(req_options, OPTION_SNAME))
option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
else
safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
}
if (boot->file)
{
if (!option_bool(OPT_NO_OVERRIDE) &&
req_options &&
in_list(req_options, OPTION_FILENAME))
option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
else
safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
}
if (boot->next_server.s_addr)
mess->siaddr = boot->next_server;
else if (boot->tftp_sname)
mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
}
if (boot->next_server.s_addr)
mess->siaddr = boot->next_server;
else if (boot->tftp_sname)
mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
}
else
/* Use the values of the relevant options if no dhcp-boot given and
they're not explicitly asked for as options. OPTION_END is used
as an internal way to specify siaddr without using dhcp-boot, for use in
dhcp-optsfile. */
{
if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
(opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
else
/* Use the values of the relevant options if no dhcp-boot given and
they're not explicitly asked for as options. OPTION_END is used
as an internal way to specify siaddr without using dhcp-boot, for use in
dhcp-optsfile. */
{
safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
done_file = 1;
if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
(opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
{
safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
done_file = 1;
}
if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
(opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
{
safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
done_server = 1;
}
if ((opt = option_find2(OPTION_END)))
mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;
}
if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
(opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
{
safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
done_server = 1;
}
if ((opt = option_find2(OPTION_END)))
mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;
}
/* We don't want to do option-overload for BOOTP, so make the file and sname
@@ -2534,7 +2743,7 @@ static void do_options(struct dhcp_context *context,
end -= 3;
/* rfc3011 says this doesn't need to be in the requested options list. */
if (subnet_addr.s_addr)
if (!leasequery && subnet_addr.s_addr)
option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
if (lease_time != 0xffffffff)
@@ -2575,14 +2784,17 @@ static void do_options(struct dhcp_context *context,
/* replies to DHCPINFORM may not have a valid context */
if (context)
{
if (!option_find2(OPTION_NETMASK))
/* Netmask and broadcast always sent, except leasequery. */
if (!option_find2(OPTION_NETMASK) &&
(!leasequery || in_list(req_options, OPTION_NETMASK)))
option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
/* May not have a "guessed" broadcast address if we got no packets via a relay
from this net yet (ie just unicast renewals after a restart */
if (context->broadcast.s_addr &&
!option_find2(OPTION_BROADCAST))
option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
!option_find2(OPTION_BROADCAST) &&
(!leasequery || in_list(req_options, OPTION_BROADCAST)))
option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
/* Same comments as broadcast apply, and also may not be able to get a sensible
default when using subnet select. User must configure by steam in that case. */
@@ -2663,7 +2875,7 @@ static void do_options(struct dhcp_context *context,
continue;
/* was it asked for, or are we sending it anyway? */
if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno))
if ((!(opt->flags & DHOPT_FORCE) || leasequery) && !in_list(req_options, optno))
continue;
/* prohibit some used-internally options. T1 and T2 already handled. */
@@ -2721,7 +2933,7 @@ static void do_options(struct dhcp_context *context,
handle_encap(mess, end, req_options, null_term, tagif, pxe_arch != 1);
force_encap = prune_vendor_opts(tagif);
if (context && pxe_arch != -1)
{
pxe_misc(mess, end, uuid, pxevendor);
@@ -2729,8 +2941,8 @@ static void do_options(struct dhcp_context *context,
config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
}
if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&
do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) &&
if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT) || in_list(req_options, OPTION_VENDOR_ID)) &&
(leasequery || do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term)) &&
pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 &&
(p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len)))
/* If we send vendor encapsulated options, and haven't already sent option 60,
@@ -2843,4 +3055,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) + 2);
}
}
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 */
+90 -100
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -34,8 +34,8 @@ 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, 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);
@@ -68,7 +68,7 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
#define opt6_user_vendor_next(opt, end) (opt6_next(((uint8_t *) opt) - 2, 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 +80,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)
@@ -96,8 +100,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
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, now))
return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
return 0;
@@ -105,7 +108,7 @@ 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, time_t now)
{
uint8_t *end = inbuff + sz;
uint8_t *opts = inbuff + 34;
@@ -183,7 +186,7 @@ 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);
return dhcp6_no_relay(state, msg_type, inbuff, sz, now);
}
/* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
@@ -244,10 +247,12 @@ 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 */
if (!IN6_IS_ADDR_UNSPECIFIED(&align))
state->link_address = &align;
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, now))
return 0;
}
else
@@ -259,7 +264,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;
@@ -333,18 +338,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 +365,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)
{
@@ -634,7 +626,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 +645,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;
@@ -764,15 +757,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 +783,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 +802,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
break;
}
}
tagif = add_options(state, 0);
break;
}
@@ -977,8 +961,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
@@ -1301,7 +1287,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. */
@@ -1593,9 +1578,14 @@ 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;
/* must be a minimal option to check without stepping outside received packet. */
if (opt6_ptr(opt, 4) > state->end)
return 0;
state->ia_type = opt6_type(opt);
if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
return 0;
@@ -1605,7 +1595,10 @@ static int check_ia(struct state *state, void *opt, void **endp, void **ia_optio
if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4)
return 0;
*endp = opt6_ptr(opt, opt6_len(opt));
/* Check we don't overflow the received packet. */
if ((*endp = opt6_ptr(opt, opt6_len(opt))) > state->end)
return 0;
state->iaid = opt6_uint(opt, 0, 4);
*ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24);
@@ -2276,64 +2269,61 @@ int relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
{
void *opt, *opts = inbuff + 34;
void *end = inbuff + sz;
for (opt = opts; opt; opt = opt6_next(opt, end))
if (opt6_type(opt) == OPTION6_RELAY_MSG && opt6_len(opt) > 0)
{
int encap_type = *((unsigned char *)opt6_ptr(opt, 0));
put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ);
peer->sin6_scope_id = relay->iface_index;
if (encap_type == DHCP6RELAYREPL)
{
peer->sin6_port = ntohs(DHCPV6_SERVER_PORT);
return 1;
}
peer->sin6_port = ntohs(DHCPV6_CLIENT_PORT);
#ifdef HAVE_SCRIPT
if (daemon->lease_change_command && encap_type == DHCP6REPLY)
{
/* decapsulate relayed message */
opts = opt6_ptr(opt, 4);
end = opt6_ptr(opt, opt6_len(opt));
for (opt = opts; opt; opt = opt6_next(opt, end))
if (opt6_type(opt) == OPTION6_IA_PD && opt6_len(opt) > 12)
{
void *ia_opts = opt6_ptr(opt, 12);
void *ia_end = opt6_ptr(opt, opt6_len(opt));
void *ia_opt;
for (ia_opt = ia_opts; ia_opt; ia_opt = opt6_next(ia_opt, ia_end))
/* valid lifetime must not be zero. */
if (opt6_type(ia_opt) == OPTION6_IAPREFIX && opt6_len(ia_opt) >= 25 && opt6_uint(ia_opt, 4, 4) != 0)
{
if (daemon->free_snoops ||
(daemon->free_snoops = whine_malloc(sizeof(struct snoop_record))))
{
struct snoop_record *snoop = daemon->free_snoops;
daemon->free_snoops = snoop->next;
snoop->client = peer->sin6_addr;
snoop->prefix_len = opt6_uint(ia_opt, 8, 1);
memcpy(&snoop->prefix, opt6_ptr(ia_opt, 9), IN6ADDRSZ);
snoop->next = relay->snoop_records;
relay->snoop_records = snoop;
}
}
}
}
#endif
return 1;
}
if ((opt = opt6_find(opts, end, OPTION6_RELAY_MSG, 4)))
{
int encap_type = opt6_uint(opt, 0, 1);
put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ);
peer->sin6_scope_id = relay->iface_index;
if (encap_type == DHCP6RELAYREPL)
{
peer->sin6_port = ntohs(DHCPV6_SERVER_PORT);
return 1;
}
peer->sin6_port = ntohs(DHCPV6_CLIENT_PORT);
#ifdef HAVE_SCRIPT
if (daemon->lease_change_command && encap_type == DHCP6REPLY)
{
/* skip over message type and transaction-id. to get to options. */
opts = opt6_ptr(opt, 4);
end = opt6_ptr(opt, opt6_len(opt));
if ((opt = opt6_find(opts, end, OPTION6_IA_PD, 12)))
{
opts = opt6_ptr(opt, 12);
end = opt6_ptr(opt, opt6_len(opt));
for (opt = opt6_find(opts, end, OPTION6_IAPREFIX, 25); opt; opt = opt6_find(opt6_next(opt, end), end, OPTION6_IAPREFIX, 25))
/* valid lifetime must not be zero. */
if (opt6_uint(opt, 4, 4) != 0)
{
if (daemon->free_snoops ||
(daemon->free_snoops = whine_malloc(sizeof(struct snoop_record))))
{
struct snoop_record *snoop = daemon->free_snoops;
daemon->free_snoops = snoop->next;
snoop->client = peer->sin6_addr;
snoop->prefix_len = opt6_uint(opt, 8, 1);
memcpy(&snoop->prefix, opt6_ptr(opt, 9), IN6ADDRSZ);
snoop->next = relay->snoop_records;
relay->snoop_records = snoop;
}
}
}
}
#endif
return 1;
}
}
return 0;
}
#ifdef HAVE_SCRIPT
int do_snoop_script_run(void)
{
+3 -24
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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
@@ -377,7 +356,7 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
int to_wire(char *name)
{
unsigned char *l, *p, *q, term;
int len;
unsigned int len;
for (l = (unsigned char*)name; *l != 0; l = p)
{
@@ -409,7 +388,7 @@ int to_wire(char *name)
void from_wire(char *name)
{
unsigned char *l, *p, *last;
int len;
unsigned int len;
for (last = (unsigned char *)name; *last != 0; last += *last+1);
+2 -2
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* firewire has EUI-64 identifier as clid */
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
#endif
else
+1 -1
View File
@@ -18,7 +18,7 @@
#include "dnsmasq.h"
#if defined(HAVE_IPSET) && defined(HAVE_BSD_NETWORK)
#if defined(HAVE_BSD_IPSET)
#include <string.h>
+371 -231
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -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,11 +41,10 @@ static void sanitise(char *buf);
#define ERR_ILL 4
#define ERR_TID 5
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, *opt;
char *filename, *mode, *p, *end;
union mysockaddr addr, peer;
struct msghdr msg;
struct iovec iov;
@@ -87,12 +86,10 @@ 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;
@@ -198,11 +195,11 @@ void tftp_request(struct listener *listen, time_t now)
name = namebuff;
addra.addr4 = addr.in.sin_addr;
if (family == AF_INET6)
addra.addr6 = addr.in6.sin6_addr;
else
addra.addr4 = addr.in.sin_addr;
if (daemon->tftp_interfaces)
{
/* dedicated tftp interface list */
@@ -267,14 +264,14 @@ void tftp_request(struct listener *listen, time_t now)
}
else
{
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
return;
}
}
}
/* Enforce simultaneous transfer limit. In non-single-port mode
this is doene by not listening on the server socket when
this is done by not listening on the server socket when
too many transfers are in progress. */
if (!transfer && tftp_cnt >= daemon->tftp_max)
return;
@@ -308,7 +305,9 @@ void tftp_request(struct listener *listen, time_t now)
/* May reuse struct transfer from abandoned transfer in single port mode. */
if (!transfer && !(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)
@@ -320,15 +319,15 @@ void tftp_request(struct listener *listen, time_t now)
transfer->peer = peer;
transfer->source = addra;
transfer->if_index = if_index;
transfer->timeout = now + 2;
transfer->timeout = 2;
transfer->start = now;
transfer->backoff = 1;
transfer->block = 1;
transfer->ackprev = 0;
transfer->block_hi = 0;
transfer->blocksize = 512;
transfer->offset = 0;
transfer->file = NULL;
transfer->opt_blocksize = transfer->opt_transize = 0;
transfer->netascii = transfer->carrylf = 0;
transfer->windowsize = 1;
(void)prettyprint_addr(&peer, daemon->addrbuff);
/* if we have a nailed-down range, iterate until we find a free one. */
@@ -361,136 +360,171 @@ void tftp_request(struct listener *listen, time_t now)
p = packet + 2;
end = packet + len;
len = 0;
if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
!(filename = next(&p, end)) ||
!(mode = next(&p, end)) ||
(strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0))
if (ntohs(*((unsigned short *)packet)) == OP_WRQ)
len = tftp_err(ERR_ILL, packet, _("unsupported write request from %s"),daemon->addrbuff, NULL);
else if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
{
len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff, NULL);
is_err = 1;
}
else
{
if (strcasecmp(mode, "netascii") == 0)
transfer->netascii = 1;
while ((opt = next(&p, end)))
if (!(filename = next(&p, end)))
len = tftp_err(ERR_ILL, packet, _("empty filename in request from %s"), daemon->addrbuff, NULL);
else if (!(mode = next(&p, end)) || (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0))
len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"),daemon->addrbuff, NULL);
else
{
if (strcasecmp(opt, "blksize") == 0)
char *opt, *arg;
if (strcasecmp(mode, "netascii") == 0)
transfer->netascii = 1;
while ((opt = next(&p, end)) && (arg = next(&p, end)))
{
if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK))
unsigned int val = atoi(arg);
if (strcasecmp(opt, "blksize") == 0 && !option_bool(OPT_TFTP_NOBLOCK))
{
/* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */
int overhead = (family == AF_INET) ? 32 : 52;
transfer->blocksize = atoi(opt);
if (transfer->blocksize < 1)
transfer->blocksize = 1;
if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
if (mtu != 0 && transfer->blocksize > (unsigned)mtu - overhead)
transfer->blocksize = (unsigned)mtu - overhead;
if (val < 1)
val = 1;
if (val > (unsigned)daemon->packet_buff_sz - 4)
val = (unsigned)daemon->packet_buff_sz - 4;
if (mtu != 0 && val > (unsigned)mtu - overhead)
val = (unsigned)mtu - overhead;
transfer->blocksize = val;
transfer->opt_blocksize = 1;
transfer->block = 0;
}
}
else if (strcasecmp(opt, "tsize") == 0 && next(&p, end) && !transfer->netascii)
{
transfer->opt_transize = 1;
transfer->block = 0;
}
}
/* cope with backslashes from windows boxen. */
for (p = filename; *p; p++)
if (*p == '\\')
*p = '/';
else if (option_bool(OPT_TFTP_LC))
*p = tolower((unsigned char)*p);
strcpy(daemon->namebuff, "/");
if (prefix)
{
if (prefix[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
if (prefix[strlen(prefix)-1] != '/')
strncat(daemon->namebuff, "/", (MAXDNAME-1) - 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));
/* remove unique-directory if it doesn't exist */
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
daemon->namebuff[oldlen] = 0;
else if (strcasecmp(opt, "tsize") == 0 && !transfer->netascii)
{
transfer->opt_transize = 1;
transfer->block = 0;
}
else if (strcasecmp(opt, "timeout") == 0)
{
if (val > 255)
val = 255;
transfer->timeout = val;
transfer->opt_timeout = 1;
transfer->block = 0;
}
else if (strcasecmp(opt, "windowsize") == 0 && !transfer->netascii)
{
/* windowsize option only supported for binary transfers. */
if (val < 1)
val = 1;
if (val > TFTP_MAX_WINDOW)
val = TFTP_MAX_WINDOW;
transfer->windowsize = val;
transfer->opt_windowsize = 1;
transfer->block = 0;
}
}
if (option_bool(OPT_TFTP_APREF_MAC))
/* cope with backslashes from windows boxen. */
for (p = filename; *p; p++)
if (*p == '\\')
*p = '/';
else if (option_bool(OPT_TFTP_LC))
*p = tolower((unsigned char)*p);
strcpy(daemon->namebuff, "/");
if (prefix)
{
unsigned char *macaddr = NULL;
unsigned char macbuf[DHCP_CHADDR_MAX];
if (prefix[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
if (prefix[strlen(prefix)-1] != '/')
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
#ifdef HAVE_DHCP
if (daemon->dhcp && peer.sa.sa_family == AF_INET)
{
/* Check if the client IP is in our lease database */
struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr);
if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN)
macaddr = lease->hwaddr;
}
#endif
/* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */
if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0)
macaddr = macbuf;
if (macaddr)
{
if (option_bool(OPT_TFTP_APREF_IP))
{
size_t oldlen = strlen(daemon->namebuff);
struct stat statbuf;
snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff));
strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
/* remove unique-directory if it doesn't exist */
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
daemon->namebuff[oldlen] = 0;
}
if (option_bool(OPT_TFTP_APREF_MAC))
{
unsigned char *macaddr = NULL;
unsigned char macbuf[DHCP_CHADDR_MAX];
#ifdef HAVE_DHCP
if (daemon->dhcp && peer.sa.sa_family == AF_INET)
{
/* Check if the client IP is in our lease database */
struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr);
if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN)
macaddr = lease->hwaddr;
}
#endif
/* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */
if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0)
macaddr = macbuf;
if (macaddr)
{
size_t oldlen = strlen(daemon->namebuff);
struct stat statbuf;
snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - 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 */
if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
daemon->namebuff[oldlen] = 0;
}
}
/* Absolute pathnames OK if they match prefix */
if (filename[0] == '/')
{
if (strstr(filename, daemon->namebuff) == filename)
daemon->namebuff[0] = 0;
else
filename++;
}
}
else if (filename[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
/* Absolute pathnames OK if they match prefix */
if (filename[0] == '/')
/* check permissions and open file */
if ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff)))
{
if (strstr(filename, daemon->namebuff) == filename)
daemon->namebuff[0] = 0;
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(transfer)) == -1)
len = tftp_err_oops(packet, daemon->namebuff);
else
filename++;
{
is_err = 0;
/* get_block put the packet to send in a different buffer. */
packet = daemon->packet;
}
}
}
else if (filename[0] == '/')
daemon->namebuff[0] = 0;
strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
/* check permissions and open file */
if ((transfer->file = check_tftp_fileperm(&len, prefix, daemon->addrbuff)))
{
if ((len = get_block(packet, transfer)) == -1)
len = tftp_err_oops(packet, daemon->namebuff);
else
is_err = 0;
}
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
if (len)
{
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
#endif
}
if (is_err)
free_transfer(transfer);
@@ -501,9 +535,9 @@ 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();
@@ -566,6 +600,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;
@@ -585,12 +620,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 = MAXDNAME * 2;
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))
@@ -601,22 +639,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 *)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
}
}
@@ -624,35 +663,52 @@ void check_tftp_listeners(time_t now)
for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
{
tmp = transfer->next;
int endcon = 0, error = 0, timeout = 0;
if (difftime(now, transfer->timeout) >= 0.0)
tmp = transfer->next;
/* ->start set to zero in handle_tftp() when we recv an error packet. */
if (transfer->start == 0)
endcon = error = 1;
else if (difftime(now, transfer->start) > TFTP_TRANSFER_TIME)
{
int endcon = 0;
endcon = 1;
/* don't complain about timeout when we're awaiting the last
ACK, some clients never send it */
if (get_block(transfer) > 0)
error = timeout = 1;
}
else if (difftime(now, transfer->retransmit) >= 0.0)
{
/* Do transmission or re-transmission. When we get an ACK, the call to handle_tftp()
bumps transfer->lastack and trips the retransmit timer so that we send the next block(s)
here. */
ssize_t len;
/* timeout, retransmit */
transfer->timeout += 1 + (1<<(transfer->backoff/2));
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((len = get_block(daemon->packet, transfer)) == -1)
unsigned int i, winsize;
transfer->retransmit += transfer->timeout + (1<<(transfer->backoff/2));
transfer->backoff++;
transfer->block = transfer->lastack;
/* 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++)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = 1;
}
else if (++transfer->backoff > 7)
{
/* don't complain about timeout when we're awaiting the last
ACK, some clients never send it */
if ((unsigned)len == transfer->blocksize + 4)
endcon = 1;
len = 0;
}
if ((len = get_block(transfer)) == 0)
{
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
@@ -660,51 +716,93 @@ void check_tftp_listeners(time_t now)
#endif
}
if (endcon || len == 0)
/* 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);
sanitise(daemon->namebuff);
(void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
if (timeout)
my_syslog(MS_TFTP | LOG_ERR, _("timeout sending %s to %s"), daemon->namebuff, daemon->addrbuff);
else if (error)
my_syslog(MS_TFTP | LOG_ERR, _("failed sending %s to %s"), daemon->namebuff, daemon->addrbuff);
else
my_syslog(MS_TFTP | LOG_INFO, _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
/* unlink */
*up = tmp;
if (error)
free_transfer(transfer);
else
{
strcpy(daemon->namebuff, transfer->file->filename);
sanitise(daemon->namebuff);
(void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
/* unlink */
*up = tmp;
if (endcon)
free_transfer(transfer);
else
{
/* put on queue to be sent to script and deleted */
transfer->next = daemon->tftp_done_trans;
daemon->tftp_done_trans = transfer;
}
continue;
/* put on queue to be sent to script and deleted */
transfer->next = daemon->tftp_done_trans;
daemon->tftp_done_trans = transfer;
}
}
up = &transfer->next;
}
else
up = &transfer->next;
}
}
/* 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 && ntohs(mess->block) == (unsigned short)transfer->block)
if (ntohs(mess->op) == OP_ACK)
{
/* Got ack, ensure we take the (re)transmit path */
transfer->timeout = now;
transfer->backoff = 0;
if (transfer->block++ != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
/* 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)
{
/* Got ack, move forward and ensure we take the (re)transmit path */
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 += (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);
@@ -720,8 +818,7 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
daemon->addrbuff);
/* Got err, ensure we take abort */
transfer->timeout = now;
transfer->backoff = 100;
transfer->start = 0;
}
}
}
@@ -742,26 +839,35 @@ static void free_transfer(struct tftp_transfer *transfer)
static char *next(char **p, char *end)
{
char *ret = *p;
size_t len;
char *n, *ret = *p;
/* Look for end of string, without running off the end of the packet. */
for (n = *p; n < end && *n != 0; n++);
if (*(end-1) != 0 ||
*p == end ||
(len = strlen(ret)) == 0)
/* ran off the end or zero length string - failed */
if (n == end || n == ret)
return NULL;
*p += len + 1;
*p = n + 1;
return ret;
}
/* If we don't do anything, don't write the the input/ouptut
buffer. This allows us to pass in safe read-only strings constants. */
static void sanitise(char *buf)
{
unsigned char *q, *r;
for (q = r = (unsigned char *)buf; *r; r++)
if (isprint((int)*r))
*(q++) = *r;
*q = 0;
{
if (q != r)
*q = *r;
q++;
}
if (q != r)
*q = 0;
}
#define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */
@@ -772,8 +878,7 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *
char message[];
} *mess = (struct errmess *)packet;
ssize_t len, ret = 4;
memset(packet, 0, daemon->packet_buff_sz);
if (file)
sanitise(file);
@@ -797,10 +902,11 @@ 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);
static off_t saved_offset = 0;
static ssize_t saved_len = 0;
if (transfer->block == 0)
{
/* send OACK */
@@ -808,8 +914,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)
@@ -822,8 +932,18 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
p += (sprintf(p,"tsize") + 1);
p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1);
}
return p - packet;
if (transfer->opt_timeout)
{
p += (sprintf(p,"timeout") + 1);
p += (sprintf(p, "%u", transfer->timeout) + 1);
}
if (transfer->opt_windowsize)
{
p += (sprintf(p,"windowsize") + 1);
p += (sprintf(p, "%u", (unsigned int)transfer->windowsize) + 1);
}
return p - daemon->packet;
}
else
{
@@ -831,40 +951,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 = transfer->file->size - transfer->offset;
size_t size;
if (!transfer->netascii)
transfer->offset = (off_t)(transfer->block - 1) * (off_t)transfer->blocksize;
if (transfer->offset > transfer->file->size)
return 0; /* finished */
if (size > 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 (lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
!read_write(transfer->file->fd, mess->data, size, RW_READ))
return -1;
transfer->expansion = 0;
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;
for (i = 0, newcarrylf = 0; i < size; i++)
if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf))
/* Map '\n' to CR-LF in netascii mode */
transfer->expansion = transfer->carrylf = 0;
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));
@@ -872,11 +1010,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;
}
}
+6 -1
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -213,6 +213,11 @@ static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct u
size_t num_patterns = 0;
struct blob_attr *tb[policy_len];
struct blob_attr *attr;
(void)ctx;
(void)obj;
(void)req;
(void)method;
if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)))
return UBUS_STATUS_INVALID_ARGUMENT;
+179 -83
View File
@@ -1,4 +1,4 @@
/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -34,6 +34,10 @@
#include <sys/utsname.h>
#endif
#ifdef HAVE_BSD_NETWORK
#include <libgen.h>
#endif
/* SURF random number generator */
static u32 seed[32];
@@ -262,6 +266,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;
}
@@ -319,13 +327,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);
}
}
@@ -337,26 +347,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)
@@ -426,7 +416,7 @@ int hostname_order(const char *a, const char *b)
int hostname_isequal(const char *a, const char *b)
{
return hostname_order(a, b) == 0;
return strlen(a) == strlen(b) && hostname_order(a, b) == 0;
}
/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */
@@ -439,8 +429,8 @@ 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)
/* a shorter than b */
if ((ap - a) < (bp - b))
return 0;
do
@@ -455,12 +445,12 @@ int hostname_issubdomain(char *a, char *b)
if (c1 != c2)
return 0;
} while (ap != a);
} while (bp != b);
if (bp == b)
if (ap == a)
return 2;
if (*(--bp) == '.')
if (*(--ap) == '.')
return 1;
return 0;
@@ -688,32 +678,6 @@ 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)
{
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;
@@ -773,43 +737,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)
{
@@ -831,9 +812,34 @@ void close_fds(long max_fd, int spare1, int spare2, int spare3)
#endif
#ifdef FDESCFS
DIR *d;
DIR *d = NULL;
if ((d = opendir(FDESCFS)))
# ifdef HAVE_BSD_NETWORK
dev_t dirdev = 0;
char fdescfs[] = FDESCFS; /* string must be writable */
struct stat statbuf;
/* On BSD, fdescfs is normally mounted at /dev/fd. However
if it is NOT mounted, devfs creates a directory at /dev/fd
which contains (only) the file descriptors 0,1 and 2.
Under these conditions, opendir() will succeed, and
if we proceed we will fail to close extant
file descriptors which should be closed.
Check that there is a filesystem mounted at /dev/fd
by checking that the device changes between /dev/fd
and /dev. If if doesn't, fall back to the dumb path. */
if (stat(fdescfs, &statbuf) != -1)
dirdev = statbuf.st_dev;
if (stat(dirname(fdescfs), &statbuf) != -1 &&
dirdev != statbuf.st_dev)
# endif
d = opendir(FDESCFS);
if (d)
{
struct dirent *de;
@@ -855,9 +861,9 @@ void close_fds(long max_fd, int spare1, int spare2, int spare3)
closedir(d);
return;
}
}
#endif
/* fallback, dumb code. */
for (max_fd--; max_fd >= 0; max_fd--)
if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO &&
@@ -920,3 +926,93 @@ int kernel_version(void)
return version * 256 + (split ? atoi(split) : 0);
}
#endif
#define hash_ptr(x) (((unsigned int)(((char *)(x)) - ((char *)NULL))) & 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);
*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);
}
}