mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
296 lines
9.0 KiB
C
296 lines
9.0 KiB
C
/* dnsmasq is Copyright (c) 2000-2022 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
|
|
|
|
#include <netinet/icmp6.h>
|
|
|
|
static u32 packet_count;
|
|
static void do_dump_packet(int mask, void *packet, size_t len,
|
|
union mysockaddr *src, union mysockaddr *dst, int port, int proto);
|
|
|
|
/* 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))
|
|
die(_("cannot access %s: %s"), daemon->dump_file, EC_FILE);
|
|
else if (header.magic_number != 0xa1b2c3d4)
|
|
die(_("bad header in %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_udp(int mask, void *packet, size_t len,
|
|
union mysockaddr *src, union mysockaddr *dst, int fd)
|
|
{
|
|
union mysockaddr fd_addr;
|
|
socklen_t addr_len = sizeof(fd_addr);
|
|
|
|
if (daemon->dumpfd != -1 && (mask & daemon->dump_mask))
|
|
{
|
|
/* if fd is negative it carries a port number (negated)
|
|
which we use as a source or destination when not otherwise
|
|
specified so wireshark can ID the packet.
|
|
If both src and dst are specified, set this to -1 to avoid
|
|
a spurious getsockname() call. */
|
|
int port = (fd < 0) ? -fd : -1;
|
|
|
|
/* fd >= 0 is a file descriptor and the address of that file descriptor is used
|
|
in place of a NULL src or dst. */
|
|
if (fd >= 0 && getsockname(fd, (struct sockaddr *)&fd_addr, &addr_len) != -1)
|
|
{
|
|
if (!src)
|
|
src = &fd_addr;
|
|
|
|
if (!dst)
|
|
dst = &fd_addr;
|
|
}
|
|
|
|
do_dump_packet(mask, packet, len, src, dst, port, IPPROTO_UDP);
|
|
}
|
|
}
|
|
|
|
void dump_packet_icmp(int mask, void *packet, size_t len,
|
|
union mysockaddr *src, union mysockaddr *dst)
|
|
{
|
|
if (daemon->dumpfd != -1 && (mask & daemon->dump_mask))
|
|
do_dump_packet(mask, packet, len, src, dst, -1, IPPROTO_ICMP);
|
|
}
|
|
|
|
static void do_dump_packet(int mask, void *packet, size_t len,
|
|
union mysockaddr *src, union mysockaddr *dst, int port, int proto)
|
|
{
|
|
struct ip ip;
|
|
struct ip6_hdr ip6;
|
|
int family;
|
|
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 port != -1 it carries a port number
|
|
which we use as a source or destination when not otherwise
|
|
specified so wireshark can ID the packet.
|
|
If both src and dst are specified, set this to -1 to avoid
|
|
a spurious getsockname() call. */
|
|
udp.uh_sport = udp.uh_dport = htons(port < 0 ? 0 : port);
|
|
|
|
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_hops = 64;
|
|
|
|
if ((ip6.ip6_nxt = proto) == IPPROTO_UDP)
|
|
ip6.ip6_plen = htons(sizeof(struct udphdr) + len);
|
|
else
|
|
{
|
|
proto = ip6.ip6_nxt = IPPROTO_ICMPV6;
|
|
ip6.ip6_plen = htons(len);
|
|
}
|
|
|
|
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+=2)
|
|
{
|
|
sum += ntohs((ip6.ip6_src.s6_addr[i] << 8) + (ip6.ip6_src.s6_addr[i+1])) ;
|
|
sum += ntohs((ip6.ip6_dst.s6_addr[i] << 8) + (ip6.ip6_dst.s6_addr[i+1])) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iphdr = &ip;
|
|
ipsz = sizeof(ip);
|
|
memset(&ip, 0, sizeof(ip));
|
|
|
|
ip.ip_v = IPVERSION;
|
|
ip.ip_hl = sizeof(struct ip) / 4;
|
|
ip.ip_ttl = IPDEFTTL;
|
|
|
|
if ((ip.ip_p = proto) == IPPROTO_UDP)
|
|
ip.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len);
|
|
else
|
|
{
|
|
ip.ip_len = htons(sizeof(struct ip) + len);
|
|
proto = ip.ip_p = IPPROTO_ICMP;
|
|
}
|
|
|
|
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/ICMP 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. */
|
|
|
|
if (proto == IPPROTO_UDP)
|
|
{
|
|
/* Add Remaining part of the pseudoheader. Note that though the
|
|
IPv6 pseudoheader is very different to the IPv4 one, the
|
|
net result of this calculation is correct as long as the
|
|
packet length is less than 65536, which is fine for us. */
|
|
sum += htons(IPPROTO_UDP);
|
|
sum += htons(sizeof(struct udphdr) + len);
|
|
|
|
udp.uh_sum = 0;
|
|
udp.uh_ulen = htons(sizeof(struct udphdr) + len);
|
|
|
|
for (i = 0; i < sizeof(struct udphdr)/2; i++)
|
|
sum += ((u16 *)&udp)[i];
|
|
for (i = 0; i < (len + 1) / 2; i++)
|
|
sum += ((u16 *)packet)[i];
|
|
while (sum >> 16)
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
|
|
|
|
pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
|
|
}
|
|
else
|
|
{
|
|
/* ICMP - ICMPv6 packet is a superset of ICMP */
|
|
struct icmp6_hdr *icmp = packet;
|
|
|
|
/* See comment in UDP code above. */
|
|
sum += htons(proto);
|
|
sum += htons(len);
|
|
|
|
icmp->icmp6_cksum = 0;
|
|
for (i = 0; i < (len + 1) / 2; i++)
|
|
sum += ((u16 *)packet)[i];
|
|
while (sum >> 16)
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
icmp->icmp6_cksum = (sum == 0xffff) ? sum : ~sum;
|
|
|
|
pcap_header.incl_len = pcap_header.orig_len = ipsz + len;
|
|
}
|
|
|
|
rc = gettimeofday(&time, NULL);
|
|
pcap_header.ts_sec = time.tv_sec;
|
|
pcap_header.ts_usec = time.tv_usec;
|
|
|
|
if (rc == -1 ||
|
|
!read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 0) ||
|
|
!read_write(daemon->dumpfd, iphdr, ipsz, 0) ||
|
|
(proto == IPPROTO_UDP && !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 if (option_bool(OPT_EXTRALOG))
|
|
my_syslog(LOG_INFO, _("%u dumping packet %u mask 0x%04x"), daemon->log_display_id, ++packet_count, mask);
|
|
else
|
|
my_syslog(LOG_INFO, _("dumping packet %u mask 0x%04x"), ++packet_count, mask);
|
|
|
|
}
|
|
|
|
#endif
|