updated 200-ubus_dns.patch all remaining patches not required Changelog for version 2.91 - https://thekelleys.org.uk/dnsmasq/CHANGELOG version 2.91 Fix spurious "resource limit exceeded messages". Thanks to Dominik Derigs for the bug report. Fix out-of-bounds heap read in order_qsort(). We only need to order two server records on the ->serial field. Literal address records are smaller and don't have this field and don't need to be ordered on it. To actually provoke this bug seems to need the same server-literal to be repeated twice, e.g., --address=/a/1.1.1.1 --address-/a/1.1.1.1 which is clearly rare in the wild, but if it did exist it could provoke a SIGSEGV. Thanks to Daniel Rhea for fuzzing this one. Fix buffer overflow when configured lease-change script name is too long. Thanks to Daniel Rhea for finding this one. Improve behaviour in the face of non-responsive upstream TCP DNS servers. Without shorter timeouts, clients are blocked for too long and fail with their own timeouts. Set --fast-dns-retries by default when doing DNSSEC. A single downstream query can trigger many upstream queries. On an unreliable network, there may not be enough downstream retries to ensure that all these queries complete. Improve behaviour in the face of truncated answers to queries for DNSSEC records. Getting these answers by TCP doesn't now involve a faked truncated answer to the downstream client to force it to move to TCP. This improves performance and robustness in the face of broken clients which can't fall back to TCP. No longer remove data from truncated upstream answers. If an upstream replies with a truncated answer, but the answer has some RRs included, return those RRs, rather than returning and empty answer. Fix handling of EDNS0 UDP packet sizes. When talking upstream we always add a pseudo header, and set the UDP packet size to --edns-packet-max. Answering queries from downstream, we get the answer (either from upstream or local data) If local data won't fit the advertised size (or 512 if there's not an EDNS0 header) return truncated. If upstream returns truncated, do likewise. If upstream is OK, but the answer is too big for downstream, truncate the answer. Modify the behaviour of --synth-domain for IPv6. When deriving a domain name from an IPv6 address, an address such as 1234:: would become 1234--.example.com, which is not legal in IDNA2008. Stop using the :: compression method, so 1234:: becomes 1234-0000-0000-0000-0000-0000-0000-0000.example.com Fix broken dhcp-relay on *BSD. Thanks to Harold for finding this problem. Add --dhcp-option-pxe config. This acts almost exactly like --dhcp-option except that the defined option is only sent when replying to PXE clients. More importantly, these options are sent in reply PXE clients when dnsmasq in acting in PXE proxy mode. In PXE proxy mode, the set of options sent is defined by the PXE standard and the normal set of options is not sent. This config allows arbitrary options in PXE-proxy replies. A typical use-case is to send option 175 to iPXE. Thanks to Jason Berry for finding the requirement for this. Support PXE proxy-DHCP and DHCP-relay at the same time. When using PXE proxy-DHCP, dnsmasq supplies PXE information to the client, which also talks to another "normal" DHCP server for address allocation and similar. The normal DHCP server may be on the local network, but it may also be remote, and accessed via a DHCP relay. This change allows dnsmasq to act as both a PXE proxy-DHCP server AND a DHCP relay for the same network. Fix erroneous "DNSSEC validated" state with non-DNSSEC upstream servers. Thanks to Dominik Derigs for the bug report. Handle queries with EDNS client subnet fields better. If dnsmasq is configured to add an EDNS client subnet to a query, it is careful to suppress use of the cache, since a cached answer may not be valid for a query with a different client subnet. Extend this behaviour to queries which arrive a dnsmasq already carrying an EDNS client subnet. Handle DS queries to auth zones. When dnsmasq is configured to act as an authoritative server and has an authoritative zone configured, and receives a query for that zone _as_forwarder_ it answers the query directly rather than forwarding it. This doesn't affect the answer, but it saves dnsmasq forwarding the query to the recursor upstream, which then bounces it back to dnsmasq in auth mode. The exception should be when the query is for the root of zone, for a DS RR. The answer to that has to come from the parent, via the recursor, and will typically be a proof-of-non-existence since dnsmasq doesn't support signed zones. This patch suppresses local answers and forces forwarding to the upstream recursor for such queries. It stops breakage when a DNSSEC validating client makes queries to dnsmasq acting as forwarder for a zone for which it is authoritative. Implement "DNS-0x20 encoding", for extra protection against reply-spoof attacks. Since DNS queries are case-insensitive, it's possible to randomly flip the case of letters in a query and still get the correct answer back. This adds an extra dimension for a cache-poisoning attacker to guess when sending replies in-the-blind since it's expected that the legitimate answer will have the same pattern of upper and lower case as the query, so any replies which don't can be ignored as malicious. The amount of extra entropy clearly depends on the number of a-z and A-Z characters in the query, and this implementation puts a hard limit of 32 bits to make resource allocation easy. This about doubles entropy over the standard random ID and random port combination. This technique can interact badly with rare broken DNS servers which don't preserve the case of the query in their reply. The first time a reply is returned which matches the query in all respects except case, a warning will be logged. In this release, 0x020-encoding is default-off and must be explicitly enabled with --do-0x20-encoding. In future releases it may default on. You can avoid a future release changing the behaviour of an installation with --no-x20-encode. Fix a long-standing problem when two queries which are identical in every repect _except_ case, get combined by dnsmasq. If dnsmasq gets eg, two queries for example.com and Example.com in quick succession it will get the answer for example.com from upstream and send that answer to both requestors. This means that the query for Example.com will get an answer for example.com, and in the modern DNS, that answer may not be accepted. Signed-off-by: Rudy Andram <rmandrad@gmail.com> Link: https://github.com/openwrt/openwrt/pull/18357 Signed-off-by: Robert Marko <robimarko@gmail.com>
278 lines
7.1 KiB
Diff
278 lines
7.1 KiB
Diff
--- a/src/dnsmasq.c
|
|
+++ b/src/dnsmasq.c
|
|
@@ -2097,6 +2097,10 @@
|
|
daemon->pipe_to_parent = pipefd[1];
|
|
}
|
|
|
|
+#ifdef HAVE_UBUS
|
|
+ drop_ubus_listeners();
|
|
+#endif
|
|
+
|
|
/* The connected socket inherits non-blocking
|
|
attribute from the listening socket.
|
|
Reset that here. */
|
|
--- a/src/dnsmasq.h
|
|
+++ b/src/dnsmasq.h
|
|
@@ -1670,14 +1670,26 @@ void emit_dbus_signal(int action, struct
|
|
|
|
/* ubus.c */
|
|
#ifdef HAVE_UBUS
|
|
+struct blob_attr;
|
|
+typedef void (*ubus_dns_notify_cb)(struct blob_attr *msg, void *priv);
|
|
+
|
|
char *ubus_init(void);
|
|
void set_ubus_listeners(void);
|
|
void check_ubus_listeners(void);
|
|
+void drop_ubus_listeners(void);
|
|
+int ubus_dns_notify_has_subscribers(void);
|
|
+struct blob_buf *ubus_dns_notify_prepare(void);
|
|
+int ubus_dns_notify(const char *type, ubus_dns_notify_cb cb, void *priv);
|
|
void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
|
|
# ifdef HAVE_CONNTRACK
|
|
void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name);
|
|
void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl);
|
|
# endif
|
|
+#else
|
|
+static inline int ubus_dns_notify_has_subscribers(void)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
#endif
|
|
|
|
/* ipset.c */
|
|
--- a/src/forward.c
|
|
+++ b/src/forward.c
|
|
@@ -803,7 +803,7 @@ static size_t process_reply(struct dns_h
|
|
cache_secure = 0;
|
|
}
|
|
|
|
- if (daemon->doctors && do_doctor(header, n, daemon->namebuff))
|
|
+ if ((daemon->doctors || ubus_dns_notify_has_subscribers()) && do_doctor(header, n, daemon->namebuff))
|
|
cache_secure = 0;
|
|
|
|
/* check_for_bogus_wildcard() does it's own caching, so
|
|
--- a/src/rfc1035.c
|
|
+++ b/src/rfc1035.c
|
|
@@ -13,8 +13,10 @@
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
-
|
|
#include "dnsmasq.h"
|
|
+#ifdef HAVE_UBUS
|
|
+#include <libubox/blobmsg.h>
|
|
+#endif
|
|
|
|
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
|
|
char *name, int isExtract, int extrabytes)
|
|
@@ -384,10 +386,65 @@ static int private_net6(struct in6_addr
|
|
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
|
|
}
|
|
|
|
+#ifdef HAVE_UBUS
|
|
+static void ubus_dns_doctor_cb(struct blob_attr *msg, void *priv)
|
|
+{
|
|
+ static const struct blobmsg_policy policy = {
|
|
+ .name = "address",
|
|
+ .type = BLOBMSG_TYPE_STRING,
|
|
+ };
|
|
+ struct blob_attr *val;
|
|
+ char **dest = priv;
|
|
+
|
|
+ blobmsg_parse(&policy, 1, &val, blobmsg_data(msg), blobmsg_data_len(msg));
|
|
+ if (val)
|
|
+ *dest = blobmsg_get_string(val);
|
|
+}
|
|
+
|
|
+static int ubus_dns_doctor(const char *name, int ttl, void *p, int af)
|
|
+{
|
|
+ struct blob_buf *b;
|
|
+ char *addr;
|
|
+
|
|
+ if (!name)
|
|
+ return 0;
|
|
+
|
|
+ b = ubus_dns_notify_prepare();
|
|
+ if (!b)
|
|
+ return 0;
|
|
+
|
|
+ blobmsg_add_string(b, "name", name);
|
|
+
|
|
+ blobmsg_add_u32(b, "ttl", ttl);
|
|
+
|
|
+ blobmsg_add_string(b, "type", af == AF_INET6 ? "AAAA" : "A");
|
|
+
|
|
+ addr = blobmsg_alloc_string_buffer(b, "address", INET6_ADDRSTRLEN);
|
|
+ if (!addr)
|
|
+ return 0;
|
|
+
|
|
+ inet_ntop(af, p, addr, INET6_ADDRSTRLEN);
|
|
+ blobmsg_add_string_buffer(b);
|
|
+
|
|
+ addr = NULL;
|
|
+ ubus_dns_notify("dns_result", ubus_dns_doctor_cb, &addr);
|
|
+
|
|
+ if (!addr)
|
|
+ return 0;
|
|
+
|
|
+ return inet_pton(af, addr, p) == 1;
|
|
+}
|
|
+#else
|
|
+static int ubus_dns_doctor(const char *name, int ttl, void *p, int af)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
|
|
{
|
|
unsigned char *p;
|
|
- int i, qtype, qclass, rdlen;
|
|
+ int i, qtype, qclass, rdlen, ttl;
|
|
int done = 0;
|
|
|
|
if (!(p = skip_questions(header, qlen)))
|
|
@@ -404,7 +461,7 @@ int do_doctor(struct dns_header *header,
|
|
|
|
GETSHORT(qtype, p);
|
|
GETSHORT(qclass, p);
|
|
- p += 4; /* ttl */
|
|
+ GETLONG(ttl, p); /* ttl */
|
|
GETSHORT(rdlen, p);
|
|
|
|
if (qclass == C_IN && qtype == T_A)
|
|
@@ -415,6 +472,9 @@ int do_doctor(struct dns_header *header,
|
|
if (!CHECK_LEN(header, p, qlen, INADDRSZ))
|
|
return done;
|
|
|
|
+ if (ubus_dns_doctor(daemon->namebuff, ttl, p, AF_INET))
|
|
+ header->hb3 &= ~HB3_AA;
|
|
+
|
|
/* alignment */
|
|
memcpy(&addr.addr4, p, INADDRSZ);
|
|
|
|
@@ -444,6 +504,14 @@ int do_doctor(struct dns_header *header,
|
|
break;
|
|
}
|
|
}
|
|
+ else if (qclass == C_IN && qtype == T_AAAA)
|
|
+ {
|
|
+ if (!CHECK_LEN(header, p, qlen, IN6ADDRSZ))
|
|
+ return 0;
|
|
+
|
|
+ if (ubus_dns_doctor(daemon->namebuff, ttl, p, AF_INET6))
|
|
+ header->hb3 &= ~HB3_AA;
|
|
+ }
|
|
|
|
if (!ADD_RDLEN(header, p, qlen, rdlen))
|
|
return done; /* bad packet */
|
|
--- a/src/ubus.c
|
|
+++ b/src/ubus.c
|
|
@@ -72,6 +72,13 @@ static struct ubus_object ubus_object =
|
|
.subscribe_cb = ubus_subscribe_cb,
|
|
};
|
|
|
|
+static struct ubus_object_type ubus_dns_object_type =
|
|
+ { .name = "dnsmasq.dns" };
|
|
+
|
|
+static struct ubus_object ubus_dns_object = {
|
|
+ .type = &ubus_dns_object_type,
|
|
+};
|
|
+
|
|
static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
|
|
{
|
|
(void)ctx;
|
|
@@ -105,13 +112,21 @@ static void ubus_disconnect_cb(struct ub
|
|
char *ubus_init()
|
|
{
|
|
struct ubus_context *ubus = NULL;
|
|
+ char *dns_name;
|
|
int ret = 0;
|
|
|
|
if (!(ubus = ubus_connect(NULL)))
|
|
return NULL;
|
|
|
|
+ dns_name = whine_malloc(strlen(daemon->ubus_name) + 5);
|
|
+ sprintf(dns_name, "%s.dns", daemon->ubus_name);
|
|
+
|
|
ubus_object.name = daemon->ubus_name;
|
|
+ ubus_dns_object.name = dns_name;
|
|
+
|
|
ret = ubus_add_object(ubus, &ubus_object);
|
|
+ if (!ret)
|
|
+ ret = ubus_add_object(ubus, &ubus_dns_object);
|
|
if (ret)
|
|
{
|
|
ubus_destroy(ubus);
|
|
@@ -181,6 +196,17 @@ void check_ubus_listeners()
|
|
} \
|
|
} while (0)
|
|
|
|
+void drop_ubus_listeners()
|
|
+{
|
|
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|
|
+
|
|
+ if (!ubus)
|
|
+ return;
|
|
+
|
|
+ ubus_free(ubus);
|
|
+ daemon->ubus = NULL;
|
|
+}
|
|
+
|
|
static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method,
|
|
struct blob_attr *msg)
|
|
@@ -328,6 +354,53 @@ fail:
|
|
} \
|
|
} while (0)
|
|
|
|
+int ubus_dns_notify_has_subscribers(void)
|
|
+{
|
|
+ return (daemon->ubus && ubus_dns_object.has_subscribers);
|
|
+}
|
|
+
|
|
+struct blob_buf *ubus_dns_notify_prepare(void)
|
|
+{
|
|
+ if (!ubus_dns_notify_has_subscribers())
|
|
+ return NULL;
|
|
+
|
|
+ blob_buf_init(&b, 0);
|
|
+ return &b;
|
|
+}
|
|
+
|
|
+struct ubus_dns_notify_req {
|
|
+ struct ubus_notify_request req;
|
|
+ ubus_dns_notify_cb cb;
|
|
+ void *priv;
|
|
+};
|
|
+
|
|
+static void dns_notify_cb(struct ubus_notify_request *req, int type, struct blob_attr *msg)
|
|
+{
|
|
+ struct ubus_dns_notify_req *dreq = container_of(req, struct ubus_dns_notify_req, req);
|
|
+
|
|
+ dreq->cb(msg, dreq->priv);
|
|
+}
|
|
+
|
|
+int ubus_dns_notify(const char *type, ubus_dns_notify_cb cb, void *priv)
|
|
+{
|
|
+ struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|
|
+ struct ubus_dns_notify_req dreq;
|
|
+ int ret;
|
|
+
|
|
+ if (!ubus || !ubus_dns_object.has_subscribers)
|
|
+ return 0;
|
|
+
|
|
+ ret = ubus_notify_async(ubus, &ubus_dns_object, type, b.head, &dreq.req);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ dreq.req.data_cb = dns_notify_cb;
|
|
+ dreq.cb = cb;
|
|
+ dreq.priv = priv;
|
|
+
|
|
+ return ubus_complete_request(ubus, &dreq.req.req, 100);
|
|
+}
|
|
+
|
|
void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface)
|
|
{
|
|
struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
|