Compare commits

...
45 Commits
Author SHA1 Message Date
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 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
36 changed files with 15876 additions and 7605 deletions

No files matched your search

+35 -2
View File
@@ -1,3 +1,30 @@
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.
version 2.92
Redesign the interaction between DNSSEC validation and per-domain
servers, specified as --server=/<domain>/<ip-address>. This should
@@ -24,7 +51,7 @@ version 2.92
Fix some edge-cases with domains and --address and --server. There
has been some regressions with this in previous releases. This change
fixes the priority order from loqwer to highest as:
fixes the priority order from lower to highest as:
--address with a IPv4 or IPv6 address (as long as the query matches the type)
--address with # for all-zeros, as long as the query is A or AAAA)
--address with no address, which returns NXDOMAIN or NOERROR for all types.
@@ -61,7 +88,7 @@ version 2.92
Add TFTP options windowsize (RFC 7440) and timeout (RFC 2349).
Change the behaviour of the DHVPv6 server when a REBIND message
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.
@@ -70,6 +97,12 @@ version 2.92
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
+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
+15 -10
View File
@@ -136,6 +136,8 @@ Log the results of DNS queries handled by dnsmasq. Enable a full cache dump on r
then the log has extra information at the start of each line.
This consists of a serial number which ties together the log lines associated with an individual query, and the IP address of the requestor. If the argument "proto" is supplied, this shows everything that "extra" does and also the network protocol used to communicate the queries. Logging of only queries to the authoritative server can be configured with
.B --log-queries=auth
and logging only failed replies (Error, NXDOMAIN or NODATA) can be configured with
.B --log-queries=only_failed
.TP
.B \-8, --log-facility=<facility>
Set the facility to which dnsmasq will send syslog entries, this
@@ -918,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
@@ -1197,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
@@ -1347,7 +1349,7 @@ 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.
@@ -1489,19 +1491,22 @@ prefix-delegation from relayed DHCP transactions. See
.B --dhcp-script
for details.
.TP
.B --dhcp-split-relay=<local address>,[<server address>[#<server port>]],<server-facing-interface>
A usefully enchanced version of DHCPv4 relay. IPv4 DHCP normally uses a single address
.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 as the address that the server talks to. The address of the client-facing interface
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 interface parameter is mandatory and a cannot be a wildcard.
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
@@ -1669,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
@@ -1950,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
@@ -2301,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
+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
+13 -5
View File
@@ -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)
{
+9 -7
View File
@@ -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;
}
+29 -9
View File
@@ -855,7 +855,7 @@ void cache_end_insert(void)
#ifdef HAVE_DNSSEC
void cache_update_hwm(void)
{
/* Sneak out possibly updated crypto HWM values. */
/* Sneak out possibly updated crypto HWM values. */
unsigned char op = PIPE_OP_STATS;
read_write(daemon->pipe_to_parent, &op, sizeof(op), RW_WRITE);
@@ -1000,6 +1000,7 @@ int cache_recv_insert(time_t now, int fd)
}
case PIPE_OP_RESULT:
case PIPE_OP_KILLED:
{
/* UDP validation moved to TCP to avoid truncation.
Restart UDP validation process with the returned result. */
@@ -1013,11 +1014,14 @@ int cache_recv_insert(time_t now, int fd)
!read_write(fd, (unsigned char *)&ret_len, sizeof(ret_len), RW_READ) ||
!read_write(fd, (unsigned char *)daemon->packet, ret_len, RW_READ) ||
!read_write(fd, (unsigned char *)&forward, sizeof(forward), RW_READ) ||
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ) ||
!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ))
!read_write(fd, (unsigned char *)&uid, sizeof(uid), RW_READ))
return 0;
if (op == PIPE_OP_RESULT &&
(!read_write(fd, (unsigned char *)&keycount, sizeof(keycount), RW_READ) ||
!read_write(fd, (unsigned char *)&keycountp, sizeof(keycountp), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecount, sizeof(validatecount), RW_READ) ||
!read_write(fd, (unsigned char *)&validatecountp, sizeof(validatecountp), RW_READ)))
return 0;
/* There's a tiny chance that the frec may have been freed
@@ -1025,9 +1029,12 @@ int cache_recv_insert(time_t now, int fd)
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 (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);
@@ -2243,10 +2250,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;
@@ -2259,6 +2276,9 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
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;
+11 -3
View File
@@ -142,7 +142,8 @@ HAVE_LOOP
include functionality to probe for and remove DNS forwarding loops.
HAVE_INOTIFY
use the Linux inotify facility to efficiently re-read configuration files.
use the Linux and FreeBSD >= 15 inotify facility
to efficiently re-read configuration files.
NO_ID
Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
@@ -375,8 +376,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. */
+19 -6
View File
@@ -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
+3 -1
View File
@@ -709,7 +709,8 @@ static const struct opttab_t {
{ "client-machine-id", 97, 0 },
{ "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
{ "captive-portal", 114, OT_NAME }, /* RFC 8910 */
{ "subnet-select", 118, OT_INTERNAL },
{ "domain-search", 119, OT_RFC1035_NAME },
{ "sip-server", 120, 0 },
@@ -751,6 +752,7 @@ static const struct opttab_t opttab6[] = {
{ "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
{ "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING },
{ "captive-portal", 103, OT_NAME }, /* RFC 8910 */
{ NULL, 0, 0 }
};
#endif
+22 -8
View File
@@ -142,7 +142,7 @@ 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;
@@ -302,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;
@@ -330,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;
@@ -360,7 +363,7 @@ void dhcp_packet(time_t now, int pxe_fd)
if (!iface_enumerate(AF_INET, &parm, (callback_t){.af_inet=complete_context}))
return;
relay_upstream4(iface_index, mess, (size_t)sz, unicast_dest);
relay_upstream4(iface_addr, iface_index, mess, (size_t)sz, unicast_dest);
/* May have configured relay, but not DHCP server */
if (!daemon->dhcp)
@@ -664,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;
}
@@ -682,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)
+16 -2
View File
@@ -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;
@@ -231,7 +232,10 @@ 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;
@@ -452,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;
}
+1
View File
@@ -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 */
+116 -70
View File
@@ -127,6 +127,7 @@ int main (int argc, char **argv)
This might be increased is EDNS packet size if greater than the minimum. */
daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ;
daemon->packet = safe_malloc(daemon->packet_buff_sz);
daemon->pipe_to_parent = -1;
if (option_bool(OPT_EXTRALOG))
daemon->addrbuff2 = safe_malloc(ADDRSTRLEN);
@@ -863,6 +864,9 @@ int main (int argc, char **argv)
}
#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
@@ -1080,8 +1084,6 @@ int main (int argc, char **argv)
pid = getpid();
daemon->pipe_to_parent = -1;
#ifdef HAVE_INOTIFY
/* Using inotify, have to select a resolv file at startup */
poll_resolv(1, 0, now);
@@ -1336,11 +1338,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
{
@@ -1417,8 +1450,6 @@ void send_event(int fd, int event, int data, char *msg)
while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR);
}
/* NOTE: the memory used to return msg is leaked: use msgs in events only
to describe fatal errors. */
static int read_event(int fd, struct event_desc *evp, char **msg)
{
char *buf;
@@ -1428,12 +1459,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;
@@ -1496,9 +1537,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)
{
@@ -1604,7 +1642,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 */
@@ -1942,20 +1979,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 *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)
@@ -1982,60 +2020,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 */
@@ -2086,23 +2144,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);
@@ -2119,10 +2163,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)
@@ -2225,8 +2267,12 @@ 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;
}
}
+32 -16
View File
@@ -289,7 +289,9 @@ struct event_desc {
#define OPT_DO_0x20 75
#define OPT_AUTH_LOG 76
#define OPT_LEASEQUERY 77
#define OPT_LAST 78
#define OPT_LOG_ONLY_FAILED 78
#define OPT_LOG_MALLOC 79
#define OPT_LAST 80
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -549,6 +551,7 @@ struct crec {
#define PIPE_OP_STATS 3 /* Update parent's stats */
#define PIPE_OP_IPSET 4 /* Update IPset */
#define PIPE_OP_NFTSET 5 /* Update NFTset */
#define PIPE_OP_KILLED 6 /* child killed by SIGALARM */
/* struct sockaddr is not large enough to hold any address,
and specifically not big enough to hold an IPv6 address.
@@ -760,10 +763,9 @@ struct dyndir {
#define STAT_NEED_DS 0x40000
#define STAT_NEED_KEY 0x50000
#define STAT_TRUNCATED 0x60000
#define STAT_SECURE_WILDCARD 0x70000
#define STAT_OK 0x80000
#define STAT_ABANDONED 0x90000
#define STAT_ASYNC 0xa0000
#define STAT_OK 0x70000
#define STAT_ABANDONED 0x80000
#define STAT_ASYNC 0x90000
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
@@ -777,6 +779,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))
@@ -1109,7 +1112,7 @@ struct ping_result {
struct tftp_file {
int refcount, fd;
off_t size;
off_t size, posn;
dev_t dev;
ino_t inode;
char filename[];
@@ -1117,13 +1120,14 @@ struct tftp_file {
struct tftp_transfer {
int sockfd;
u16 block_hi, ackprev;
time_t retransmit, start;
unsigned int lastack, block, blocksize, windowsize, timeout, expansion;
off_t offset;
union mysockaddr peer;
union all_addr source;
int if_index;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, backoff;
unsigned char opt_blocksize, opt_transize, opt_windowsize, opt_timeout, netascii, carrylf, lastcarrylf, backoff;
struct tftp_file *file;
struct tftp_transfer *next;
};
@@ -1144,11 +1148,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;
@@ -1203,6 +1208,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;
@@ -1269,6 +1275,9 @@ extern struct daemon {
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;
@@ -1276,7 +1285,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;
@@ -1460,7 +1469,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);
@@ -1488,8 +1497,12 @@ 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 whine_malloc(x) whine_malloc_real(__func__, __LINE__, (x))
#define whine_realloc(x, y) whine_realloc_real(__func__, __LINE__, (x), (y))
#define free(x) free_real(__func__, __LINE__, (x))
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 *func, unsigned int line, void *ptr, size_t size);
int sa_len(union mysockaddr *addr);
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
int sockaddr_isnull(const union mysockaddr *s);
@@ -1514,6 +1527,7 @@ int memcmp_masked(unsigned char *a, unsigned char *b, int len,
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);
@@ -1556,8 +1570,8 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *n,
int class, char *name, struct server *server,
int *keycount, int *validatecount);
#endif
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void tcp_request(int confd, time_t now, struct iovec *bigbuff,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source,
@@ -1667,8 +1681,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
time_t recvtime, struct in_addr leasequery_source);
unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
int clid_len, unsigned char *clid, int *len_out);
void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz, int unicast);
struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
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 */
@@ -1915,6 +1930,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);
+131 -129
View File
@@ -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, EXTR_NAME_COMPARE, 0) != 1)
if (!extract_name(header, plen, &p, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
return 0;
}
else
{
gotkey = 1;
if (!extract_name(header, plen, &p, keyname, EXTR_NAME_EXTRACT, 0))
return 0;
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
the name of the zone containing the RRset. We can't tell that
for certain, but we can check that the RRset name is equal to
or encloses the signers name, which should be enough to stop
an attacker using signatures made with the key of an unrelated
zone he controls. Note that the root key is always allowed. */
if (*keyname != 0)
zone he controls. Note that the root key is always allowed.
Ignore sigs which aren't valid */
if (*daemon->workspacename == 0 || hostname_issubdomain(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 */
@@ -574,7 +571,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
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);
@@ -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
@@ -992,7 +991,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
char *keyname, int class, int *validate_counter)
{
unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
int qtype, qclass, rc, i, neganswer = 0, prim_ok = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
int aclass, atype, rdlen, flags;
unsigned long ttl;
union all_addr a;
@@ -1003,7 +1002,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (RCODE(header) == SERVFAIL)
servfail = neganswer = nons = 1;
else
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &prim_ok, &nons, &neg_ttl, validate_counter);
p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 ||
@@ -1020,27 +1019,32 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
{
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
if (option_bool(OPT_BOGUSPRIV) &&
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
/* A INSECURE DS answer is OK if it's negative and there's a CNAME answer to the DS answer which is
signed, since that's enough to prove that the DS record doesn't exist. */
if (!neganswer || !prim_ok)
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
if (option_bool(OPT_BOGUSPRIV) &&
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
}
}
else
@@ -1419,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;
}
}
@@ -1545,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)
@@ -1643,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;
@@ -1687,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;
@@ -1710,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;
@@ -1858,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;
}
@@ -1965,7 +1972,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
if the nons argument is non-NULL.
*/
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
int *class, int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_counter)
{
static unsigned char **targets = NULL;
static int target_sz = 0;
@@ -1994,8 +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)
@@ -2103,8 +2113,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
CNAME must be <subdomain>.<dname>
CNAME target must be <subdomain>.<dname_target>
<subdomain>s must match for name and target. */
if (hostname_issubdomain(name, daemon->cname) == 1 &&
hostname_issubdomain(keyname, daemon->workspacename) == 1 &&
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];
@@ -2183,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))
@@ -2233,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))
{
@@ -2247,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;
@@ -2273,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;
@@ -2286,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]))
@@ -2296,20 +2305,12 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (!extract_name(header, plen, &p2, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS; /* bad packet */
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
/* For anything other than a DS record, this situation is OK if either
the answer is in an unsigned zone, or there's NSEC records.
For a DS record, we return INSECURE, which almost always turns
into BOGUS in the caller. */
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype)
This situation is OK if either the answer is in an unsigned zone, or there's NSEC records. */
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, 0, nons, nsec_ttl, validate_counter)) != 0)
{
if (rc_nsec & DNSSEC_FAIL_WORK)
return STAT_ABANDONED;
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_INSECURE;
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
@@ -2381,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;
+11 -1
View File
@@ -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 */
@@ -562,3 +571,4 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l
return plen;
}
+189 -168
View File
@@ -17,7 +17,7 @@
#include "dnsmasq.h"
static struct frec *get_new_frec(time_t now, struct server *serv, int force);
static struct frec *lookup_frec(char *target, int class, int rrtype, int id, int flags, int flagmask);
static struct frec *lookup_frec(time_t now, char *target, int class, int rrtype, int id, int flags, int flagmask);
#ifdef HAVE_DNSSEC
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server,
@@ -135,9 +135,9 @@ static void log_query_mysockaddr(unsigned int flags, char *name, union mysockadd
}
static void server_send(struct server *server, int fd,
const void *header, size_t plen, int flags)
const void *header, size_t plen)
{
while (retry_send(sendto(fd, header, plen, flags,
while (retry_send(sendto(fd, header, plen, 0,
&server->addr.sa,
sa_len(&server->addr))));
}
@@ -192,7 +192,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
old_reply = 1;
fwd_flags = forward->flags;
}
else if (gotname && (forward = lookup_frec(daemon->namebuff, (int)rrclass, (int)rrtype, -1, fwd_flags,
else if (gotname && (forward = lookup_frec(now, daemon->namebuff, (int)rrclass, (int)rrtype, -1, fwd_flags,
FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
{
@@ -960,7 +960,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, &orig->validate_counter);
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, NULL, &orig->validate_counter);
if (STAT_ISEQUAL(status, STAT_ABANDONED))
log_resource = 1;
@@ -980,7 +980,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
unsigned int flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
struct frec *old;
if ((old = lookup_frec(daemon->keyname, forward->class, -1, -1, flags, flags)))
if ((old = lookup_frec(now, daemon->keyname, forward->class, -1, -1, flags, flags)))
{
/* This is tricky; it detects loops in the dependency
graph for DNSSEC validation, say validating A requires DS B
@@ -1074,7 +1074,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
set_outgoing_mark(orig, fd);
#endif
server_send(server, fd, header, nn, 0);
server_send(server, fd, header, nn);
server->queries++;
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_SEC_QUERY, (void *)header, (size_t)nn, NULL, &server->addr, fd);
@@ -1170,7 +1170,7 @@ void reply_query(int fd, time_t now)
GETSHORT(rrtype, p);
GETSHORT(class, p);
if (!(forward = lookup_frec(daemon->namebuff, class, rrtype, ntohs(header->id), FREC_ANSWER, 0)))
if (!(forward = lookup_frec(now, daemon->namebuff, class, rrtype, ntohs(header->id), FREC_ANSWER, 0)))
return;
filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last);
@@ -1399,6 +1399,9 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s
a.log.ede = ede;
log_query(F_SECSTAT, domain, &a, result, 0);
if (ede == EDE_US_SERVFAIL)
ede = EDE_DNSSEC_BOGUS;
}
}
@@ -2015,20 +2018,21 @@ void receive_query(struct listener *listen, time_t now)
}
/* Send query in packet, qsize to a server determined by first,last,start and
get the reply. return reply size. */
static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, size_t qsize,
int have_mark, unsigned int mark, struct server **servp)
/* Send query in header, qsize to a server determined by first,last,start and
get the reply into the buffer passed by outbuff. Return reply size. */
static ssize_t tcp_talk(int first, int last, int start, struct dns_header *header, size_t qsize,
struct iovec *recvbuff, int have_mark, unsigned int mark, struct server **servp)
{
int firstsendto = -1;
u16 *length = (u16 *)packet;
unsigned char *payload = &packet[2];
struct dns_header *header = (struct dns_header *)payload;
unsigned int rsize;
u16 length;
unsigned int rsize = 0;
int class, rclass, type, rtype;
unsigned char *p;
struct blockdata *saved_question;
struct timeval tv;
struct iovec sendio[2];
#ifdef MSG_FASTOPEN
struct msghdr msg;
#endif
(void)mark;
(void)have_mark;
@@ -2040,10 +2044,12 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
GETSHORT(type, p);
GETSHORT(class, p);
/* Save question for retry. */
if (!(saved_question = blockdata_alloc((char *)header, (size_t)qsize)))
return 0;
length = htons(qsize);
sendio[0].iov_base = &length;
sendio[0].iov_len = sizeof(length);
sendio[1].iov_base = header;
sendio[1].iov_len = qsize;
while (1)
{
int data_sent = 0, fatal = 0;
@@ -2065,10 +2071,6 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
*servp = serv = daemon->serverarray[start];
retry:
blockdata_retrieve(saved_question, qsize, header);
*length = htons(qsize);
if (serv->tcpfd == -1)
{
if ((serv->tcpfd = socket(serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
@@ -2099,10 +2101,17 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
tv.tv_sec += TCP_TIMEOUT;
setsockopt(serv->tcpfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif
#ifdef MSG_FASTOPEN
server_send(serv, serv->tcpfd, packet, qsize + sizeof(u16), MSG_FASTOPEN);
msg.msg_name = &serv->addr.sa;
msg.msg_namelen = sa_len(&serv->addr);
msg.msg_iov = sendio;
msg.msg_iovlen = 2;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
while (retry_send(sendmsg(serv->tcpfd, &msg, MSG_FASTOPEN)));
if (errno == 0)
data_sent = 1;
else if (errno == ETIMEDOUT || errno == EHOSTUNREACH || errno == EINPROGRESS || errno == ECONNREFUSED)
@@ -2128,12 +2137,13 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
serv->flags &= ~SERV_GOT_TCP;
}
/* We us the _ONCE veriant of read_write() here because we've set a timeout on the tcp socket
/* We use the _ONCE variant of read_write() here because we've set a timeout on the tcp socket
and wish to abort if the whole data is not read/written within the timeout. */
if ((!data_sent && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) ||
!read_write(serv->tcpfd, (unsigned char *)length, sizeof (*length), RW_READ_ONCE) ||
!read_write(serv->tcpfd, payload, (rsize = ntohs(*length)), RW_READ_ONCE))
{
if ((!data_sent && !read_writev(serv->tcpfd, sendio, 2, RW_WRITE_ONCE)) ||
!read_write(serv->tcpfd, (unsigned char *)&length, sizeof(length), RW_READ_ONCE) ||
!expand_buf(recvbuff, (rsize = ntohs(length))) ||
!read_write(serv->tcpfd, recvbuff->iov_base, rsize, RW_READ_ONCE))
{
/* We get data then EOF, reopen connection to same server,
else try next. This avoids DoS from a server which accepts
connections and then closes them. */
@@ -2146,28 +2156,28 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
else
goto failed;
}
else
{
/* If the question section of the reply doesn't match the question we sent, then
someone might be attempting to insert bogus values into the cache by
sending replies containing questions and bogus answers.
Try another server, or give up */
p = (unsigned char *)(((struct dns_header *)recvbuff->iov_base)+1);
if (extract_name(((struct dns_header *)recvbuff->iov_base), rsize, &p, daemon->namebuff, EXTR_NAME_COMPARE, 4) != 1)
continue;
GETSHORT(rtype, p);
GETSHORT(rclass, p);
/* If the question section of the reply doesn't match the question we sent, then
someone might be attempting to insert bogus values into the cache by
sending replies containing questions and bogus answers.
Try another server, or give up */
p = (unsigned char *)(header+1);
if (extract_name(header, rsize, &p, daemon->namebuff, EXTR_NAME_COMPARE, 4) != 1)
continue;
GETSHORT(rtype, p);
GETSHORT(rclass, p);
if (type != rtype || class != rclass)
continue;
if (type != rtype || class != rclass)
continue;
}
serv->flags |= SERV_GOT_TCP;
*servp = serv;
blockdata_free(saved_question);
return rsize;
}
blockdata_free(saved_question);
return 0;
}
@@ -2179,19 +2189,16 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
int class, char *name, struct server *server,
int *keycount, int *validatecount)
{
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
struct dns_header *new_header = (struct dns_header *)&packet[2];
int start, first, last, new_status;
ssize_t n = *plenp;
int log_save = daemon->log_display_id;
struct iovec recvbuff;
recvbuff.iov_len = 0;
recvbuff.iov_base = NULL;
*plenp = 0;
if (!packet)
return STAT_ABANDONED;
memcpy(new_header, header, n);
/* Set TCP flag in logs. */
daemon->log_display_id = -daemon->log_display_id;
@@ -2209,10 +2216,11 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, name, &server->addr,
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
if ((n = tcp_talk(first, last, start, packet, n, 0, 0, &server)) == 0)
if ((n = tcp_talk(first, last, start, header, n, &recvbuff, 0, 0, &server)) == 0)
new_status = STAT_ABANDONED;
else
{
struct dns_header *new_header = (struct dns_header *)recvbuff.iov_base;
new_status = tcp_key_recurse(now, status, new_header, n, class, daemon->namebuff, daemon->keyname, server, 0, 0, keycount, validatecount);
if (STAT_ISEQUAL(status, STAT_OK))
@@ -2223,7 +2231,7 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
if (n >= daemon->edns_pktsz)
{
/* still too bIg, strip optional sections and try again. */
/* still too big, strip optional sections and try again. */
new_header->nscount = htons(0);
new_header->arcount = htons(0);
n = resize_packet(new_header, n, NULL, 0);
@@ -2237,6 +2245,10 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
}
}
/* we have succeeded and are no longer blocked talking
on a TCP connection, so if the watchdog alarm goes off,
ignore it. */
daemon->forward_to_tcp = NULL;
/* return the stripped or truncated reply. */
memcpy(header, new_header, n);
*plenp = n;
@@ -2245,7 +2257,7 @@ int tcp_from_udp(time_t now, int status, struct dns_header *header, ssize_t *ple
}
daemon->log_display_id = log_save;
free(packet);
free(recvbuff.iov_base);
return new_status;
}
@@ -2255,9 +2267,12 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
int have_mark, unsigned int mark, int *keycount, int *validatecount)
{
int first, last, start, new_status;
unsigned char *packet = NULL;
struct dns_header *new_header = NULL;
struct iovec new_packet;
struct dns_header *query_header = NULL, *new_header;
new_packet.iov_base = NULL;
new_packet.iov_len = 0;
while (1)
{
size_t m;
@@ -2270,7 +2285,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
else
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, validatecount);
!option_bool(OPT_DNSSEC_IGN_NS), NULL, NULL, NULL, NULL, validatecount);
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
break;
@@ -2291,34 +2306,21 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
}
/* Can't validate because we need a key/DS whose name now in keyname.
Make query for same, and recurse to validate */
if (!packet)
{
packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
new_header = (struct dns_header *)&packet[2];
}
if (!packet)
{
new_status = STAT_ABANDONED;
break;
}
m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, 0,
Make query for same in UDP packet buffer, recurse to validate*/
query_header = (struct dns_header *)daemon->packet;
daemon->srv_save = NULL;
m = dnssec_generate_query(query_header, ((unsigned char *)query_header) + daemon->edns_pktsz, keyname, class, 0,
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS);
if ((start = dnssec_server(server, keyname, STAT_ISEQUAL(new_status, STAT_NEED_DS), &first, &last)) == -1)
if ((start = dnssec_server(server, keyname, STAT_ISEQUAL(new_status, STAT_NEED_DS), &first, &last)) == -1 ||
(m = tcp_talk(first, last, start, query_header, m, &new_packet, have_mark, mark, &server)) == 0)
{
new_status = STAT_ABANDONED;
break;
}
if ((m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0)
{
new_status = STAT_ABANDONED;
break;
}
new_header = new_packet.iov_base;
log_save = daemon->log_display_id;
daemon->log_display_id = -(++daemon->log_id);
@@ -2335,8 +2337,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
break;
}
if (packet)
free(packet);
free(new_packet.iov_base);
return new_status;
}
@@ -2346,11 +2347,11 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
/* The daemon forks before calling this: it should deal with one connection,
blocking as necessary, and then return. Note, need to be a bit careful
about resources for debug mode, when the fork is suppressed: that's
done by the caller. */
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
done by the caller, which also frees bigbuff. */
void tcp_request(int confd, time_t now, struct iovec *bigbuff,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
{
size_t size = 0, saved_size = 0;
size_t size = 0;
int norebind = 0;
#ifdef HAVE_CONNTRACK
int allowed = 1;
@@ -2359,16 +2360,10 @@ unsigned char *tcp_request(int confd, time_t now,
int local_auth = 0;
#endif
int checking_disabled, do_bit = 0, ad_reqd = 0, have_pseudoheader = 0;
struct blockdata *saved_question = NULL;
unsigned short qtype;
unsigned int gotname = 0;
/* Max TCP packet + slop + size */
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
unsigned char *payload = &packet[2];
u16 tcp_len;
/* largest field in header is 16-bits, so this is still sufficiently aligned */
struct dns_header *header = (struct dns_header *)payload;
u16 *length = (u16 *)packet;
u16 tcp_len, out_len;
struct dns_header *header, *out_header;
struct server *serv;
struct in_addr dst_addr_4;
union mysockaddr peer_addr;
@@ -2378,9 +2373,13 @@ unsigned char *tcp_request(int confd, time_t now,
unsigned int mark = 0;
int have_mark = 0;
int first, last, filtered, do_stale = 0;
if (!packet || getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet;
struct iovec out_iov[2];
bigbuff->iov_base = NULL;
bigbuff->iov_len = 0;
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return;
#ifdef HAVE_CONNTRACK
/* Get connection mark of incoming query to set on outgoing connections. */
@@ -2401,7 +2400,7 @@ unsigned char *tcp_request(int confd, time_t now,
if (option_bool(OPT_LOCAL_SERVICE))
{
struct addrlist *addr;
if (peer_addr.sa.sa_family == AF_INET6)
{
for (addr = daemon->interface_addrs; addr; addr = addr->next)
@@ -2424,7 +2423,7 @@ unsigned char *tcp_request(int confd, time_t now,
{
prettyprint_addr(&peer_addr, daemon->addrbuff);
my_syslog(LOG_WARNING, _("ignoring query from non-local network %s"), daemon->addrbuff);
return packet;
return;
}
}
@@ -2442,18 +2441,26 @@ unsigned char *tcp_request(int confd, time_t now,
if (query_count >= TCP_MAX_QUERIES)
break;
/* Now get the query into the normal UDP packet buffer.
Ignore queries long than this. If we're answering locally,
copy the query into the output buffer, but for forwarding, tcp_talk()
wants the query in a a different buffer from the reply.
Note that we overwrote any saved UDP query - this onlt matters in debug mode. */
daemon->srv_save = NULL;
if (!read_write(confd, (unsigned char *)&tcp_len, sizeof(tcp_len), RW_READ) ||
!(size = ntohs(tcp_len)) ||
!read_write(confd, payload, size, RW_READ))
!(size = ntohs(tcp_len)) || size > (size_t)daemon->packet_buff_sz ||
!read_write(confd, (unsigned char *)daemon->packet, size, RW_READ))
break;
if (size < (int)sizeof(struct dns_header))
continue;
/* Clear buffer beyond request to avoid risk of
information disclosure. */
memset(payload + size, 0, 65536 - size);
/* Make sure we have a buffer big enough for the largest answer. */
expand_buf(bigbuff, 65536 + MAXDNAME + RRFIXEDSZ);
out_header = bigbuff->iov_base;
/* header == query */
header = (struct dns_header *)daemon->packet;
query_count++;
/* log_query gets called indirectly all over the place, so
@@ -2472,9 +2479,6 @@ unsigned char *tcp_request(int confd, time_t now,
ede = EDE_INVALID_DATA;
else
{
if (saved_question)
blockdata_free(saved_question);
do_bit = 0;
if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
@@ -2489,9 +2493,12 @@ unsigned char *tcp_request(int confd, time_t now,
do_bit = 1; /* do bit */
}
size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &cacheable);
saved_question = blockdata_alloc((char *)header, (size_t)size);
saved_size = size;
size = add_edns0_config(header, size, ((unsigned char *) header) + daemon->edns_pktsz, &peer_addr, now, &cacheable);
/* Clear buffer to avoid risk of information disclosure. */
memset(bigbuff->iov_base, 0, bigbuff->iov_len);
/* Copy query into output buffer for local answering */
memcpy(out_header, header, size);
log_query_mysockaddr((auth_dns ? F_NOERR | F_AUTH : 0) | F_QUERY | F_FORWARD, daemon->namebuff,
&peer_addr, NULL, qtype);
@@ -2550,10 +2557,10 @@ unsigned char *tcp_request(int confd, time_t now,
#endif
#ifdef HAVE_AUTH
else if (auth_dns)
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth);
m = answer_auth(out_header, ((char *) out_header) + 65536, (size_t)size, now, &peer_addr, local_auth);
#endif
else
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
m = answer_request(out_header, ((char *) out_header) + 65536, (size_t)size,
dst_addr_4, netmask, now, ad_reqd, do_bit, !cacheable, &stale, &filtered);
}
}
@@ -2561,15 +2568,12 @@ unsigned char *tcp_request(int confd, time_t now,
/* Do this by steam now we're not in the select() loop */
check_log_writer(1);
if (m == 0 && ede == EDE_UNSET && saved_question)
if (m == 0 && ede == EDE_UNSET)
{
struct server *master;
int start;
int no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
blockdata_retrieve(saved_question, (size_t)saved_size, header);
size = saved_size;
/* save state of "cd" flag in query */
checking_disabled = header->hb4 & HB4_CD;
@@ -2598,7 +2602,7 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
size = add_do_bit(header, size, ((unsigned char *) header) + daemon->edns_pktsz);
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
this allows it to select auth servers when one is returning bad data. */
@@ -2608,12 +2612,14 @@ unsigned char *tcp_request(int confd, time_t now,
#endif
/* Loop round available servers until we succeed in connecting to one. */
if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0)
if ((m = tcp_talk(first, last, start, header, size, bigbuff, have_mark, mark, &serv)) == 0)
ede = EDE_NETERR;
else
{
/* just in case tcp_talk() expanded buffer - should never happen */
out_header = bigbuff->iov_base;
/* get query name again for logging - may have been overwritten */
if (!extract_name(header, (unsigned int)size, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
if (!extract_name(out_header, (unsigned int)size, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
strcpy(daemon->namebuff, "query");
log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL, 0);
@@ -2629,7 +2635,8 @@ unsigned char *tcp_request(int confd, time_t now,
{
int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int validatecount = daemon->limit[LIMIT_CRYPTO];
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
/* tcp_key_recurse() may overwrite packetbuf, and thuse *header is now invalid */
int status = tcp_key_recurse(now, STAT_OK, out_header, m, 0, daemon->namebuff, daemon->keyname,
serv, have_mark, mark, &keycount, &validatecount);
char *result, *domain = "result";
@@ -2655,12 +2662,14 @@ unsigned char *tcp_request(int confd, time_t now,
no_cache_dnssec = 1;
bogusanswer = 1;
if (extract_name(header, m, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
if (extract_name(out_header, m, NULL, daemon->namebuff, EXTR_NAME_EXTRACT, 0))
domain = daemon->namebuff;
}
a.log.ede = ede;
log_query(F_SECSTAT, domain, &a, result, 0);
if (ede == EDE_US_SERVFAIL)
ede = EDE_DNSSEC_BOGUS;
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
@@ -2677,18 +2686,18 @@ unsigned char *tcp_request(int confd, time_t now,
/* restore CD bit to the value in the query */
if (checking_disabled)
header->hb4 |= HB4_CD;
out_header->hb4 |= HB4_CD;
else
header->hb4 &= ~HB4_CD;
out_header->hb4 &= ~HB4_CD;
/* Never cache answers which are contingent on the source or MAC address EDSN0 option,
since the cache is ignorant of such things. */
if (!cacheable)
no_cache_dnssec = 1;
m = process_reply(header, now, serv, (unsigned int)m,
m = process_reply(out_header, now, serv, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
ad_reqd, do_bit, !have_pseudoheader, &peer_addr, ((unsigned char *)header) + 65536, ede);
ad_reqd, do_bit, !have_pseudoheader, &peer_addr, ((unsigned char *)out_header) + 65536, ede);
/* process_reply() adds pheader itself */
have_pseudoheader = 0;
@@ -2703,8 +2712,8 @@ unsigned char *tcp_request(int confd, time_t now,
/* In case of local answer or no connections made. */
if (m == 0)
{
if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff,
((char *) header) + 65536, first, last, ede)))
if (!(m = make_local_answer(flags, gotname, size, out_header, daemon->namebuff,
((char *) out_header) + 65536, first, last, ede)))
break;
}
else if (ede == EDE_UNSET)
@@ -2720,14 +2729,12 @@ unsigned char *tcp_request(int confd, time_t now,
u16 swap = htons((u16)ede);
if (ede != EDE_UNSET)
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
m = add_pseudoheader(out_header, m, ((unsigned char *) out_header) + 65536, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
else
m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, 0, NULL, 0, do_bit, 0);
m = add_pseudoheader(out_header, m, ((unsigned char *) out_header) + 65536, 0, NULL, 0, do_bit, 0);
}
check_log_writer(1);
*length = htons(m);
check_log_writer(1);
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
#ifdef HAVE_AUTH
@@ -2737,7 +2744,13 @@ unsigned char *tcp_request(int confd, time_t now,
report_addresses(header, m, mark);
#endif
if (!read_write(confd, packet, m + sizeof(u16), RW_WRITE))
/* use scatter-gather IO so that length doesn't end up in separate packet. */
out_len = htons(m);
out_iov[0].iov_len = sizeof(out_len);
out_iov[0].iov_base = &out_len;
out_iov[1].iov_len = m;
out_iov[1].iov_base = bigbuff->iov_base;
if (!read_writev(confd, out_iov, 2, RW_WRITE))
break;
/* If we answered with stale data, this process will now try and get fresh data into
@@ -2753,18 +2766,15 @@ unsigned char *tcp_request(int confd, time_t now,
daemon->log_source_addr = NULL;
}
}
/* If we ran once to get fresh data, confd is already closed. */
/* If we ran once to get fresh data, confd is already closed. */
if (!do_stale)
{
shutdown(confd, SHUT_RDWR);
close(confd);
}
blockdata_free(saved_question);
check_log_writer(1);
return packet;
}
/* return a UDP socket bound to a random port, have to cope with straying into
@@ -3106,7 +3116,7 @@ static void free_frec(struct frec *f)
/* Impose an absolute
limit of 4*TIMEOUT before we wipe things (for random sockets).
If force is set, always return a result, even if we have
to allocate above the limit, and don'y free any records.
to allocate above the limit, and don't free any records.
This is set when allocating for DNSSEC to avoid cutting off
the branch we are sitting on. */
static struct frec *get_new_frec(time_t now, struct server *master, int force)
@@ -3128,36 +3138,40 @@ static struct frec *get_new_frec(time_t now, struct server *master, int force)
/* Don't free DNSSEC sub-queries here, as we may end up with
dangling references to them. They'll go when their "real" query
is freed. */
if (!f->dependent && !force)
if (!f->dependent)
#endif
{
if (difftime(now, f->time) >= 4*TIMEOUT)
{
daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++;
free_frec(f);
target = f;
}
else if (!oldest || difftime(f->time, oldest->time) <= 0)
oldest = f;
}
if (!force)
{
if (difftime(now, f->time) >= 4*TIMEOUT)
{
daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++;
free_frec(f);
target = f;
}
else if (!oldest || difftime(f->time, oldest->time) <= 0)
oldest = f;
}
}
if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master))
count++;
}
if (!force && count >= daemon->ftabsize)
{
query_full(now, master->domain);
return NULL;
}
if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
{
/* can't find empty one, use oldest if there is one and it's older than timeout */
daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++;
free_frec(oldest);
target = oldest;
if (!force)
{
if (count >= daemon->ftabsize)
{
query_full(now, master->domain);
return NULL;
}
if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
{
/* can't find empty one, use oldest if there is one and it's older than timeout */
daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++;
free_frec(oldest);
target = oldest;
}
}
if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec))))
@@ -3192,7 +3206,7 @@ static void query_full(time_t now, char *domain)
}
}
static struct frec *lookup_frec(char *target, int class, int rrtype, int id, int flags, int flagmask)
static struct frec *lookup_frec(time_t now, char *target, int class, int rrtype, int id, int flags, int flagmask)
{
struct frec *f;
struct dns_header *header;
@@ -3242,7 +3256,14 @@ static struct frec *lookup_frec(char *target, int class, int rrtype, int id, int
continue;
}
/* frecs older than this will get garbage-collected in
get_new_frec(), so don't return them here, so we have
consistent behaviour from an idle dnsmasq which
is not calling get_new_frec() often. */
if (difftime(now, f->time) >= 4*TIMEOUT)
return NULL;
return f;
}
@@ -3254,7 +3275,7 @@ void resend_query(void)
{
if (daemon->srv_save)
server_send(daemon->srv_save, daemon->fd_save,
daemon->packet, daemon->packet_len, 0);
daemon->packet, daemon->packet_len);
}
/* A server record is going away, remove references to it */
+7
View File
@@ -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));
+9 -1
View File
@@ -197,6 +197,7 @@ struct myoption {
#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[] =
@@ -398,6 +399,7 @@ static const struct myoption opts[] =
{ "no-ident", 0, 0, LOPT_NO_IDENT },
{ "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
{ "leasequery", 2, 0, LOPT_LEASEQUERY },
{ "log-malloc", 0, 0, LOPT_LOG_MALLOC },
{ NULL, 0, 0, 0 }
};
@@ -606,6 +608,7 @@ static struct {
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
{ LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
{ LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
{ LOPT_LOG_MALLOC, OPT_LOG_MALLOC, NULL, gettext_noop("Log memory allocation for debugging."), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -3463,6 +3466,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
else if (strcmp(arg, "auth") == 0)
set_option_bool(OPT_AUTH_LOG);
else if (strcmp(arg, "only_failed") == 0)
set_option_bool(OPT_LOG_ONLY_FAILED);
}
break;
@@ -4756,6 +4761,9 @@ 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;
@@ -5726,7 +5734,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;
+168 -144
View File
@@ -3055,7 +3055,7 @@ static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid)
}
}
void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz, int unicast)
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;
@@ -3063,141 +3063,159 @@ void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz, int u
size_t orig_sz = sz;
unsigned char *endopt = NULL;
if (mess->op != BOOTREQUEST)
if (mess->op != BOOTREQUEST || (mess->hops++) > 20)
return;
for (relay = daemon->relay4; relay; relay = relay->next)
if (relay->iface_index != 0 && relay->iface_index == iface_index)
{
union mysockaddr to;
union all_addr from;
struct ifreq ifr;
/* restore orig packet */
mess->hops = hops;
mess->giaddr = giaddr;
if (endopt)
*endopt = OPTION_END;
sz = orig_sz;
if ((mess->hops++) > 20)
continue;
{
union mysockaddr to;
union all_addr from;
struct ifreq ifr;
if (relay->interface)
{
safe_strncpy(ifr.ifr_name, relay->interface, IF_NAMESIZE);
ifr.ifr_addr.sa_family = AF_INET;
}
if (!relay->split_mode)
{
/* 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;
}
/* restore orig packet */
mess->giaddr = giaddr;
if (endopt)
*endopt = OPTION_END;
sz = orig_sz;
/* plug in our address */
from.addr4 = mess->giaddr = relay->local.addr4;
}
else
{
/* Split mode. We put our address on the server-facing interface
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. */
/* get our address on the server-facing interface. */
if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1)
continue;
/* already gatewayed ? */
if (giaddr.s_addr)
{
/* if so check if by us, to stomp on loops. */
if (giaddr.s_addr == ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr)
continue;
}
/* giaddr is our address on the outgoing interface in split mode. */
from.addr4 = mess->giaddr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
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 (3 bytes).
New END option is a 18th byte, so we need 18 bytes free.
We only need to do this once, and poke the address into the same place each time. */
if (!(endopt = option_find1((&mess->options[0] + sizeof(u32)), ((unsigned char *)mess) + sz, OPTION_END, 0)) ||
(endopt + 18 > (unsigned char *)(mess + 1)))
continue;
endopt[1] = 15; /* 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] = OPTION_END;
sz = (endopt - (unsigned char *)mess) + 18;
}
/* 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;
endopt[0] = OPTION_AGENT_ID;
}
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 (!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
if (relay->interface)
{
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);
safe_strncpy(ifr.ifr_name, relay->interface, IF_NAMESIZE);
ifr.ifr_addr.sa_family = AF_INET;
}
#endif
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;
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);
}
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;
@@ -3206,35 +3224,41 @@ void relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz, int u
*endopt = OPTION_END;
}
struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface)
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 NULL;
return 0;
for (relay = daemon->relay4; relay; relay = relay->next)
{
unsigned int return_iface = 0;
if (relay->split_mode)
{
struct ifreq ifr;
safe_strncpy(ifr.ifr_name, arrival_interface, IF_NAMESIZE);
ifr.ifr_addr.sa_family = AF_INET;
unsigned char *opt, *sopt;
/* giaddr is our address on the returning interface in split mode. */
if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1 ||
mess->giaddr.s_addr != ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr)
continue;
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)
continue;
else if (mess->giaddr.s_addr == relay->local.addr4.s_addr)
return_iface = relay->iface_index;
if (!relay->interface || wildcard_match(relay->interface, arrival_interface))
return relay->iface_index != 0 ? relay : NULL;
if (return_iface && (!relay->interface || wildcard_match(relay->interface, arrival_interface)))
return return_iface;
}
return NULL;
return 0;
}
+31 -41
View File
@@ -21,7 +21,7 @@
struct state {
unsigned char *clid;
int multicast_dest, clid_len, ia_type, interface, hostname_auth, lease_allocate;
int clid_len, ia_type, interface, hostname_auth, lease_allocate;
char *client_hostname, *hostname, *domain, *send_domain;
struct dhcp_context *context;
struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
@@ -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);
@@ -80,6 +80,10 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
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)
@@ -87,7 +91,6 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
reset_counter();
state.context = context;
state.multicast_dest = multicast_dest;
state.interface = interface;
state.iface_name = iface_name;
state.fallback = fallback;
@@ -97,8 +100,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
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;
@@ -106,7 +108,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int
/* 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;
@@ -184,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
@@ -250,9 +252,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
/* RFC6221 para 4 */
if (!IN6_IS_ADDR_UNSPECIFIED(&align))
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))
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, now))
return 0;
}
else
@@ -264,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;
@@ -340,22 +340,17 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1);
if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND || msg_type == DHCP6IREQ)
if (msg_type == DHCP6SOLICIT || msg_type == DHCP6CONFIRM || msg_type == DHCP6REBIND)
{
/* Above message types must be multicast 3315 Section 15. */
if (!state->multicast_dest)
/* SOLICIT, CONFIRM and REBIND messages MUST NOT have a server-id. 3315 para 15.x */
if (opt)
return 0;
/* server-id must match except for SOLICIT, CONFIRM and REBIND messages, which MUST NOT
have a server-id. 3315 para 15.x */
if (msg_type == DHCP6IREQ)
{
/* 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;
}
else if (opt)
}
else if (msg_type == DHCP6IREQ)
{
/* 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;
}
else
@@ -370,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)
{
@@ -1304,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. */
@@ -1596,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;
@@ -1608,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);
+132 -96
View File
@@ -18,12 +18,12 @@
#ifdef HAVE_TFTP
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client);
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len);
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client);
static void free_transfer(struct tftp_transfer *transfer);
static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2);
static ssize_t tftp_err_oops(char *packet, const char *file);
static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
static ssize_t get_block(struct tftp_transfer *transfer);
static char *next(char **p, char *end);
static void sanitise(char *buf);
@@ -41,10 +41,9 @@ static void sanitise(char *buf);
#define ERR_ILL 4
#define ERR_TID 5
static void tftp_request(struct listener *listen, time_t now)
static void tftp_request(char *packet, ssize_t plen, struct listener *listen, time_t now)
{
ssize_t len;
char *packet = daemon->packet;
char *filename, *mode, *p, *end;
union mysockaddr addr, peer;
struct msghdr msg;
@@ -87,12 +86,10 @@ static void tftp_request(struct listener *listen, time_t now)
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* packet buff is DNS name workspace. */
iov.iov_base = packet;
iov.iov_len = daemon->packet_buff_sz;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
iov.iov_len = plen;
if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
return;
@@ -267,7 +264,7 @@ static void tftp_request(struct listener *listen, time_t now)
}
else
{
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
return;
}
}
@@ -326,6 +323,8 @@ static void tftp_request(struct listener *listen, time_t now)
transfer->start = now;
transfer->backoff = 1;
transfer->block = 1;
transfer->ackprev = 0;
transfer->block_hi = 0;
transfer->blocksize = 512;
transfer->windowsize = 1;
@@ -499,17 +498,21 @@ static void tftp_request(struct listener *listen, time_t now)
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 ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff)))
{
transfer->lastack = transfer->block;
transfer->retransmit = now + transfer->timeout;
/* This packet is may be the first data packet, but only if windowsize == 1
To get windowsize greater then one requires an option negotiation,
in which case this packet is the OACK. */
if ((len = get_block(packet, transfer)) == -1)
if ((len = get_block(transfer)) == -1)
len = tftp_err_oops(packet, daemon->namebuff);
else
is_err = 0;
{
is_err = 0;
/* get_block put the packet to send in a different buffer. */
packet = daemon->packet;
}
}
}
}
@@ -532,9 +535,9 @@ static void tftp_request(struct listener *listen, time_t now)
}
}
static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, char *client)
static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client)
{
char *packet = daemon->packet, *namebuff = daemon->namebuff;
char *namebuff = daemon->namebuff;
struct tftp_file *file;
struct tftp_transfer *t;
uid_t uid = geteuid();
@@ -597,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;
@@ -616,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))
@@ -632,26 +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 *)daemon->packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd);
#endif
if (sockaddr_isequal(&peer, &transfer->peer))
handle_tftp(now, transfer, len);
handle_tftp(packet, now, transfer, len);
else
{
/* Wrong source address. See rfc1350 para 4. */
prettyprint_addr(&peer, daemon->addrbuff);
len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer))));
len = tftp_err(ERR_TID, packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL);
while(retry_send(sendto(transfer->sockfd, packet, len, 0, &peer.sa, sa_len(&peer))));
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd);
#endif
}
}
@@ -671,7 +675,7 @@ void check_tftp_listeners(time_t now)
endcon = 1;
/* don't complain about timeout when we're awaiting the last
ACK, some clients never send it */
if (get_block(daemon->packet, transfer) > 0)
if (get_block(transfer) > 0)
error = timeout = 1;
}
else if (difftime(now, transfer->retransmit) >= 0.0)
@@ -680,41 +684,43 @@ void check_tftp_listeners(time_t now)
bumps transfer->lastack and trips the retransmit timer so that we send the next block(s)
here. */
ssize_t len;
unsigned int i, winsize;
transfer->retransmit += transfer->timeout + (1<<(transfer->backoff/2));
transfer->backoff++;
transfer->block = transfer->lastack;
if ((len = get_block(daemon->packet, transfer)) == 0)
endcon = 1; /* got last ACK */
else
/* send a window'a worth of blocks unless we're retransmitting OACK */
winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize; i++, transfer->block++)
{
/* send a window'a worth of blocks unless we're retransmitting OACK */
unsigned int i, winsize = transfer->block ? transfer->windowsize : 1;
for (i = 0; i < winsize && !endcon; i++, transfer->block++)
if ((len = get_block(transfer)) == 0)
{
if (i != 0)
len = get_block(daemon->packet, transfer);
if (i == 0)
endcon = 1; /* got last ACK */
if (len == 0)
break;
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
break;
}
}
}
if (len == -1)
{
len = tftp_err_oops(daemon->packet, transfer->file->filename);
endcon = error = 1;
}
send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
&transfer->peer, &transfer->source, transfer->if_index);
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd);
#endif
}
/* prefetch the block we'll probably need when we get an ACK. */
if (!endcon)
get_block(transfer);
}
if (endcon)
{
strcpy(daemon->namebuff, transfer->file->filename);
@@ -743,23 +749,36 @@ void check_tftp_listeners(time_t now)
}
}
/* packet in daemon->packet as this is called. */
static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len)
{
struct ack {
unsigned short op, block;
} *mess = (struct ack *)daemon->packet;
} *mess = (struct ack *)packet;
if (len >= (ssize_t)sizeof(struct ack))
{
if (ntohs(mess->op) == OP_ACK)
{
/* try and handle 16-bit blockno wrap-around */
unsigned int block = (unsigned short)ntohs(mess->block);
if (block < transfer->lastack)
block |= transfer->block & 0xffff0000;
/* ignore duplicate ACKs and ACKs for blocks we've not yet sent. */
/* Handle 16-bit block number wrap-around. */
u16 new = ntohs(mess->block);
u32 block;
/* If the last ack received was in the top quarter of a 64k block
and this one is in the bottom quarter, assume it has wrapped.
Since this is UDP and an old packet can in theory wander in we may also
need to drop back to a previous segment. Such an ACK is ignored below;
here we're just getting the most likely 32 bit value from the
16 bits that we have. */
if (new <= 0x4000 && transfer->ackprev >= 0xc000)
transfer->block_hi++;
else if (new >= 0xc000 && transfer->ackprev <= 0x4000 && transfer->block_hi != 0)
transfer->block_hi--;
transfer->ackprev = new;
block = (((u32)transfer->block_hi) << 16) + (u32)new;
/* Ignore duplicate ACKs and ACKs for blocks we've not yet sent. */
if (block >= transfer->lastack &&
block <= transfer->block)
{
@@ -767,20 +786,23 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
transfer->retransmit = transfer->start = now;
transfer->backoff = 0;
transfer->lastack = block + 1;
/* We have no easy function from block no. to file offset when
expanding line breaks in netascii mode, so we update the offset here
as each block is acknowledged. This explains why the window size must be
one for a netascii transfer; to avoid the block no. doing anything
other than incrementing by one. */
if (transfer->netascii && block != 0)
transfer->offset += transfer->blocksize - transfer->expansion;
{
transfer->offset += (off_t)transfer->blocksize - (off_t)transfer->expansion;
transfer->lastcarrylf = transfer->carrylf;
}
}
}
else if (ntohs(mess->op) == OP_ERR)
{
char *p = daemon->packet + sizeof(struct ack);
char *end = daemon->packet + len;
char *p = packet + sizeof(struct ack);
char *end = packet + len;
char *err = next(&p, end);
(void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
@@ -857,10 +879,6 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *
} *mess = (struct errmess *)packet;
ssize_t len, ret = 4;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
memset(packet, 0, daemon->packet_buff_sz);
if (file)
sanitise(file);
@@ -884,12 +902,10 @@ static ssize_t tftp_err_oops(char *packet, const char *file)
}
/* return -1 for error, zero for done. */
static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
static ssize_t get_block(struct tftp_transfer *transfer)
{
memset(packet, 0, daemon->packet_buff_sz);
/* we overwrote the buffer... */
daemon->srv_save = NULL;
static off_t saved_offset = 0;
static ssize_t saved_len = 0;
if (transfer->block == 0)
{
@@ -898,8 +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)
@@ -923,7 +943,7 @@ static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
p += (sprintf(p, "%u", (unsigned int)transfer->windowsize) + 1);
}
return p - packet;
return p - daemon->packet;
}
else
{
@@ -931,44 +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;
if (!transfer->netascii)
transfer->offset = (transfer->block - 1) * transfer->blocksize;
transfer->offset = (off_t)(transfer->block - 1) * (off_t)transfer->blocksize;
if (transfer->offset > transfer->file->size)
return 0; /* finished */
if ((size = transfer->file->size - transfer->offset) > transfer->blocksize)
size = transfer->blocksize;
/* We may have a prefetched block already in the buffer. */
if (daemon->srv_save == transfer && saved_offset == transfer->offset)
return saved_len;
/* we overwrote the buffer... */
daemon->srv_save = NULL;
if ((size = transfer->file->size - transfer->offset) > (size_t)transfer->blocksize)
size = (size_t)transfer->blocksize;
mess->op = htons(OP_DATA);
mess->block = htons((unsigned short)(transfer->block));
if (size != 0 &&
(lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
!read_write(transfer->file->fd, mess->data, size, RW_READ)))
return -1;
if (size != 0)
{
if (transfer->file->posn != transfer->offset &&
lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1)
return -1;
if (!read_write(transfer->file->fd, mess->data, size, RW_READ))
return -1;
transfer->file->posn = transfer->offset + size;
}
/* Map '\n' to CR-LF in netascii mode */
if (transfer->netascii)
{
size_t i;
int newcarrylf;
/* Map '\n' to CR-LF in netascii mode */
transfer->expansion = transfer->carrylf = 0;
transfer->expansion = 0;
for (i = 0, newcarrylf = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->carrylf))
for (i = 0; i < size; i++)
if (mess->data[i] == '\n' && (i != 0 || !transfer->lastcarrylf))
{
transfer->expansion++;
if (size != transfer->blocksize)
size++; /* room in this block */
else if (i == size - 1)
newcarrylf = 1; /* don't expand LF again if it moves to the next block */
transfer->carrylf = 1; /* don't expand LF again if it moves to the next block */
/* make space and insert CR */
memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
@@ -976,11 +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;
}
}
+85 -55
View File
@@ -341,26 +341,6 @@ void safe_pipe(int *fd, int read_noblock)
die(_("cannot create pipe: %s"), NULL, EC_MISC);
}
void *whine_malloc(size_t size)
{
void *ret = calloc(1, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
return ret;
}
void *whine_realloc(void *ptr, size_t size)
{
void *ret = realloc(ptr, size);
if (!ret)
my_syslog(LOG_ERR, _("failed to reallocate %d bytes"), (int) size);
return ret;
}
int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
{
if (s1->sa.sa_family == s2->sa.sa_family)
@@ -443,8 +423,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
@@ -459,12 +439,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;
@@ -700,18 +680,12 @@ int expand_buf(struct iovec *iov, size_t size)
if (size <= (size_t)iov->iov_len)
return 1;
if (!(new = whine_malloc(size)))
if (!(new = whine_realloc(iov->iov_base, 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;
@@ -777,43 +751,60 @@ int retry_send(ssize_t rc)
"once" fails on EAGAIN, as this a timeout.
This indicates a timeout of a TCP socket.
*/
int read_write(int fd, unsigned char *packet, int size, int rw)
int read_writev(int fd, struct iovec *iov, int iovcnt, int rw)
{
ssize_t n, done;
for (done = 0; done < size; done += n)
{
if (rw & 1)
n = read(fd, &packet[done], (size_t)(size - done));
else
n = write(fd, &packet[done], (size_t)(size - done));
if (n == 0)
return 0;
int cur = 0;
ssize_t n, done = 0;
while (cur < iovcnt)
{
iov[cur].iov_len -= done;
iov[cur].iov_base = ((char *)iov[cur].iov_base) + done;
if (rw & 1)
n = readv(fd, &iov[cur], iovcnt - cur);
else
n = writev(fd, &iov[cur], iovcnt - cur);
iov[cur].iov_len += done;
iov[cur].iov_base = ((char *)iov[cur].iov_base) - done;
if (n == -1)
{
n = 0; /* don't mess with counter when we loop. */
if (errno == EINTR || errno == ENOMEM || errno == ENOBUFS)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
/* "once" variant */
if (rw & 2)
return 0;
continue;
}
if (!(rw & 2) && (errno == EAGAIN || errno == EWOULDBLOCK))
continue;
return 0;
}
if (n == 0 && (rw & 1))
return 0;
done += n;
while (cur < iovcnt && (size_t)done >= iov[cur].iov_len)
done -= iov[cur++].iov_len;
}
return 1;
}
int read_write(int fd, unsigned char *packet, int size, int rw)
{
struct iovec iov;
/* size == 0 is not an error, just a NOOP. */
if (size == 0)
return 1;
iov.iov_len = (size_t)size;
iov.iov_base = packet;
return read_writev(fd, &iov, 1, rw);
}
/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */
void close_fds(long max_fd, int spare1, int spare2, int spare3)
{
@@ -949,3 +940,42 @@ 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 *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)
my_syslog(LOG_INFO, _("realloc: %s:%u %zu bytes from %x to %x"), func, line, size, old, hash_ptr(ret));
return ret;
}
#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);
}
}