Add packet-dump debugging facility.
diff --git a/src/config.h b/src/config.h
index ecefb87..33ff36a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -117,6 +117,9 @@
HAVE_DNSSEC
include DNSSEC validator.
+HAVE_DUMPFILE
+ include code to dump packets to a libpcap-format file for debugging.
+
HAVE_LOOP
include functionality to probe for and remove DNS forwarding loops.
@@ -132,6 +135,7 @@
NO_SCRIPT
NO_LARGEFILE
NO_AUTH
+NO_DUMPFILE
NO_INOTIFY
these are available to explicitly disable compile time options which would
otherwise be enabled automatically (HAVE_IPV6, >2Gb file sizes) or
@@ -164,6 +168,7 @@
#define HAVE_AUTH
#define HAVE_IPSET
#define HAVE_LOOP
+#define HAVE_DUMPFILE
/* Build options which require external libraries.
@@ -363,6 +368,10 @@
#undef HAVE_LOOP
#endif
+#ifdef NO_DUMPFILE
+#undef HAVE_DUMPFILE
+#endif
+
#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY)
#define HAVE_INOTIFY
#endif
@@ -451,8 +460,11 @@
#ifndef HAVE_INOTIFY
"no-"
#endif
-"inotify";
-
+"inotify "
+#ifndef HAVE_DUMPFILE
+"no-"
+#endif
+"dumpfile";
#endif
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 38d1dd3..7f7d50f 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -366,7 +366,16 @@
else
daemon->inotifyfd = -1;
#endif
-
+
+ if (daemon->dump_file)
+#ifdef HAVE_DUMPFILE
+ dump_init();
+ else
+ daemon->dumpfd = -1;
+#else
+ die(_("Packet dumps not available: set HAVE_DUMP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
if (option_bool(OPT_DBUS))
#ifdef HAVE_DBUS
{
@@ -1424,6 +1433,11 @@
if (daemon->runfile)
unlink(daemon->runfile);
+
+#ifdef HAVE_DUMPFILE
+ if (daemon->dumpfd != -1)
+ close(daemon->dumpfd);
+#endif
my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM"));
flush_log();
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 863f827..e31889b 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -119,6 +119,9 @@
#include <net/if_arp.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
+#ifdef HAVE_IPV6
+#include <netinet/ip6.h>
+#endif
#include <netinet/ip_icmp.h>
#include <sys/uio.h>
#include <syslog.h>
@@ -598,6 +601,16 @@
unsigned int index; /* matches to cache entries for logging */
};
+/* packet-dump flags */
+#define DUMP_QUERY 0x0001
+#define DUMP_REPLY 0x0002
+#define DUMP_UP_QUERY 0x0004
+#define DUMP_UP_REPLY 0x0008
+#define DUMP_SEC_QUERY 0x0010
+#define DUMP_SEC_REPLY 0x0020
+#define DUMP_BOGUS 0x0040
+#define DUMP_SEC_BOGUS 0x0080
+
/* DNSSEC status values. */
#define STAT_SECURE 1
@@ -1020,14 +1033,14 @@
unsigned int duid_enterprise, duid_config_len;
unsigned char *duid_config;
char *dbus_name;
+ char *dump_file;
+ int dump_mask;
unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
#ifdef OPTION6_PREFIX_CLASS
struct prefix_class *prefix_classes;
#endif
#ifdef HAVE_DNSSEC
struct ds_config *ds;
- int dnssec_no_time_check;
- int back_to_the_future;
char *timestamp_file;
#endif
@@ -1040,6 +1053,8 @@
char *workspacename; /* ditto */
char *rr_status; /* flags for individual RRs */
int rr_status_sz;
+ int dnssec_no_time_check;
+ int back_to_the_future;
#endif
unsigned int local_answer, queries_forwarded, auth_answer;
struct frec *frec_list;
@@ -1094,6 +1109,10 @@
char *addrbuff;
char *addrbuff2; /* only allocated when OPT_EXTRALOG */
+#ifdef HAVE_DUMPFILE
+ /* file for packet dumps. */
+ int dumpfd;
+#endif
} *daemon;
/* cache.c */
@@ -1588,3 +1607,9 @@
/* arp.c */
int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now);
int do_arp_script_run(void);
+
+/* dump.c */
+#ifdef HAVE_DUMPFILE
+void dump_init(void);
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst);
+#endif
diff --git a/src/dump.c b/src/dump.c
new file mode 100644
index 0000000..6b5a1e9
--- /dev/null
+++ b/src/dump.c
@@ -0,0 +1,210 @@
+/* dnsmasq is Copyright (c) 2000-2018 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DUMPFILE
+
+static u32 packet_count;
+
+/* https://wiki.wireshark.org/Development/LibpcapFileFormat */
+struct pcap_hdr_s {
+ u32 magic_number; /* magic number */
+ u16 version_major; /* major version number */
+ u16 version_minor; /* minor version number */
+ u32 thiszone; /* GMT to local correction */
+ u32 sigfigs; /* accuracy of timestamps */
+ u32 snaplen; /* max length of captured packets, in octets */
+ u32 network; /* data link type */
+};
+
+struct pcaprec_hdr_s {
+ u32 ts_sec; /* timestamp seconds */
+ u32 ts_usec; /* timestamp microseconds */
+ u32 incl_len; /* number of octets of packet saved in file */
+ u32 orig_len; /* actual length of packet */
+};
+
+
+void dump_init(void)
+{
+ struct stat buf;
+ struct pcap_hdr_s header;
+ struct pcaprec_hdr_s pcap_header;
+
+ packet_count = 0;
+
+ if (stat(daemon->dump_file, &buf) == -1)
+ {
+ /* doesn't exist, create and add header */
+ header.magic_number = 0xa1b2c3d4;
+ header.version_major = 2;
+ header.version_minor = 4;
+ header.thiszone = 0;
+ header.sigfigs = 0;
+ header.snaplen = daemon->edns_pktsz + 200; /* slop for IP/UDP headers */
+ header.network = 101; /* DLT_RAW http://www.tcpdump.org/linktypes.html */
+
+ if (errno != ENOENT ||
+ (daemon->dumpfd = creat(daemon->dump_file, S_IRUSR | S_IWUSR)) == -1 ||
+ !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 0))
+ die(_("cannot create %s: %s"), daemon->dump_file, EC_FILE);
+ }
+ else if ((daemon->dumpfd = open(daemon->dump_file, O_APPEND | O_RDWR)) == -1 ||
+ !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 1) ||
+ header.magic_number != 0xa1b2c3d4)
+ die(_("cannot access %s: %s"), daemon->dump_file, EC_FILE);
+ else
+ {
+ /* count existing records */
+ while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 1))
+ {
+ lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR);
+ packet_count++;
+ }
+ }
+}
+
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst)
+{
+ struct ip ip;
+#ifdef HAVE_IPV6
+ struct ip6_hdr ip6;
+ int family;
+#endif
+ struct udphdr {
+ u16 uh_sport; /* source port */
+ u16 uh_dport; /* destination port */
+ u16 uh_ulen; /* udp length */
+ u16 uh_sum; /* udp checksum */
+ } udp;
+ struct pcaprec_hdr_s pcap_header;
+ struct timeval time;
+ u32 i, sum;
+ void *iphdr;
+ size_t ipsz;
+ int rc;
+
+ if (daemon->dumpfd == -1 || !(mask & daemon->dump_mask))
+ return;
+
+ /* So wireshark can Id the packet. */
+ udp.uh_sport = udp.uh_dport = htons(NAMESERVER_PORT);
+
+#ifdef HAVE_IPV6
+ if (src)
+ family = src->sa.sa_family;
+ else
+ family = dst->sa.sa_family;
+
+ if (family == AF_INET6)
+ {
+ iphdr = &ip6;
+ ipsz = sizeof(ip6);
+ memset(&ip6, 0, sizeof(ip6));
+
+ ip6.ip6_vfc = 6 << 4;
+ ip6.ip6_plen = htons(sizeof(struct udphdr) + len);
+ ip6.ip6_nxt = IPPROTO_UDP;
+ ip6.ip6_hops = 64;
+
+ if (src)
+ {
+ memcpy(&ip6.ip6_src, &src->in6.sin6_addr, IN6ADDRSZ);
+ udp.uh_sport = src->in6.sin6_port;
+ }
+
+ if (dst)
+ {
+ memcpy(&ip6.ip6_dst, &dst->in6.sin6_addr, IN6ADDRSZ);
+ udp.uh_dport = dst->in6.sin6_port;
+ }
+
+ /* start UDP checksum */
+ for (sum = 0, i = 0; i < IN6ADDRSZ; i++)
+ sum += ((u16 *)&ip6.ip6_src)[i];
+ }
+ else
+#endif
+ {
+ iphdr = &ip;
+ ipsz = sizeof(ip);
+ memset(&ip, 0, sizeof(ip));
+
+ ip.ip_v = IPVERSION;
+ ip.ip_hl = sizeof(struct ip) / 4;
+ ip.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len);
+ ip.ip_ttl = IPDEFTTL;
+ ip.ip_p = IPPROTO_UDP;
+
+ if (src)
+ {
+ ip.ip_src = src->in.sin_addr;
+ udp.uh_sport = src->in.sin_port;
+ }
+
+ if (dst)
+ {
+ ip.ip_dst = dst->in.sin_addr;
+ udp.uh_dport = dst->in.sin_port;
+ }
+
+ ip.ip_sum = 0;
+ for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
+ sum += ((u16 *)&ip)[i];
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
+
+ /* start UDP checksum */
+ sum = ip.ip_src.s_addr & 0xffff;
+ sum += (ip.ip_src.s_addr >> 16) & 0xffff;
+ sum += ip.ip_dst.s_addr & 0xffff;
+ sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
+ }
+
+ if (len & 1)
+ ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
+
+ udp.uh_sum = 0;
+ udp.uh_ulen = htons(sizeof(struct udphdr) + len);
+ sum += htons(IPPROTO_UDP);
+ sum += htons(sizeof(struct udphdr) + len);
+ for (i = 0; i < sizeof(struct udphdr)/2; i++)
+ sum += ((u16 *)&udp)[i];
+ for (i = 0; i < (len + 1) / 2; i++)
+ sum += ((u16 *)packet)[i];
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
+
+ rc = gettimeofday(&time, NULL);
+ pcap_header.ts_sec = time.tv_sec;
+ pcap_header.ts_usec = time.tv_usec;
+ pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
+
+ if (rc == -1 ||
+ !read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 0) ||
+ !read_write(daemon->dumpfd, iphdr, ipsz, 0) ||
+ !read_write(daemon->dumpfd, (void *)&udp, sizeof(udp), 0) ||
+ !read_write(daemon->dumpfd, (void *)packet, len, 0))
+ my_syslog(LOG_ERR, _("failed to write packet dump"));
+ else
+ my_syslog(LOG_INFO, _("dumping UDP packet %u mask 0x%04x"), ++packet_count, mask);
+
+}
+
+#endif
diff --git a/src/forward.c b/src/forward.c
index 03f4f41..79acff5 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -508,6 +508,10 @@
if (errno == 0)
{
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &start->addr);
+#endif
+
/* Keep info in case we want to re-send this packet */
daemon->srv_save = start;
daemon->packet_len = plen;
@@ -769,7 +773,7 @@
#endif
header = (struct dns_header *)daemon->packet;
-
+
if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR))
return;
@@ -796,6 +800,12 @@
if (!(forward = lookup_frec(ntohs(header->id), hash)))
return;
+#ifdef HAVE_DUMPFILE
+ dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_REPLY : DUMP_UP_REPLY,
+ (void *)header, n, &serveraddr, NULL);
+#endif
+
+
/* log_query gets called indirectly all over the place, so
pass these in global variables - sorry. */
daemon->log_display_id = forward->log_id;
@@ -934,6 +944,11 @@
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
NULL, NULL);
+#ifdef HAVE_DUMPFILE
+ if (status == STAT_BOGUS)
+ dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
+ header, (size_t)n, &serveraddr, NULL);
+#endif
}
/* Can't validate, as we're missing key data. Put this
@@ -1060,6 +1075,11 @@
setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
}
#endif
+
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)nn, NULL, &server->addr);
+#endif
+
while (retry_send(sendto(fd, (char *)header, nn, 0,
&server->addr.sa,
sa_len(&server->addr))));
@@ -1114,8 +1134,8 @@
bogusanswer = 1;
}
}
-#endif
-
+#endif
+
/* restore CD bit to the value in the query */
if (forward->flags & FREC_CHECKING_DISABLED)
header->hb4 |= HB4_CD;
@@ -1141,6 +1161,11 @@
nn = resize_packet(header, nn, NULL, 0);
}
#endif
+
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
+#endif
+
send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
&forward->source, &forward->dest, forward->iface);
}
@@ -1394,7 +1419,11 @@
pass these in global variables - sorry. */
daemon->log_display_id = ++daemon->log_id;
daemon->log_source_addr = &source_addr;
-
+
+#ifdef HAVE_DUMPFILE
+ dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
+#endif
+
if (extract_request(header, (size_t)n, daemon->namebuff, &type))
{
#ifdef HAVE_AUTH
diff --git a/src/option.c b/src/option.c
index 65df93a..0f01c94 100644
--- a/src/option.c
+++ b/src/option.c
@@ -161,6 +161,8 @@
#define LOPT_TFTP_MTU 349
#define LOPT_REPLY_DELAY 350
#define LOPT_RAPID_COMMIT 351
+#define LOPT_DUMPFILE 352
+#define LOPT_DUMPMASK 353
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -327,6 +329,8 @@
{ "dhcp-ttl", 1, 0 , LOPT_DHCPTTL },
{ "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY },
{ "dhcp-rapid-commit", 0, 0, LOPT_RAPID_COMMIT },
+ { "dumpfile", 1, 0, LOPT_DUMPFILE },
+ { "dumpmask", 1, 0, LOPT_DUMPMASK },
{ NULL, 0, 0, 0 }
};
@@ -500,6 +504,8 @@
{ LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL },
{ LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL },
{ LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL },
+ { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
+ { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -1811,6 +1817,14 @@
ret_err(_("bad MX target"));
break;
+ case LOPT_DUMPFILE: /* --dumpfile */
+ daemon->dump_file = opt_string_alloc(arg);
+ break;
+
+ case LOPT_DUMPMASK: /* --dumpmask */
+ daemon->dump_mask = strtol(arg, NULL, 0);
+ break;
+
#ifdef HAVE_DHCP
case 'l': /* --dhcp-leasefile */
daemon->lease_file = opt_string_alloc(arg);