├── .gitignore ├── LICENSE ├── Makefile ├── Notes ├── README.md ├── RPMS ├── dnsflow-1.1-1.el6.x86_64.rpm └── dnsflow-1.1-1.el7.x86_64.rpm ├── dcap.c ├── dcap.h ├── default └── dnsflow ├── dnsflow.c ├── dnsflow.service ├── dnsflow_read.py ├── init └── dnsflow ├── rpmbuild ├── SOURCES │ └── dnsflow-1.1.tar.gz └── SPECS │ └── dnsflow.spec └── tests └── v6.pcap /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.o 3 | *.a 4 | *.lo 5 | *.la 6 | *.pyc 7 | *.dSYM 8 | .DS_Store 9 | .deps 10 | tags 11 | .idea/* 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, DeepField Networks, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 3. Neither the name of DeepField Networks, Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OS = $(shell uname) 2 | DESTDIR=/usr/local/ 3 | 4 | CC = gcc -g -L/usr/lib -Wall -O3 -D_BSD_SOURCE -I/usr/local/include/ -L/usr/local/lib/ 5 | 6 | LIBS_DEFAULT = -lldns -lpcap -levent 7 | 8 | ifeq ($(OS), Linux) 9 | LIBS_LINUX += -lrt 10 | LIBS = $(LIBS_DEFAULT) $(LIBS_LINUX) 11 | else 12 | LIBS = $(LIBS_DEFAULT) 13 | endif 14 | 15 | dnsflow: dnsflow.c dcap.c dcap.h 16 | @echo "Building on OS [${OS}]" 17 | $(CC) dnsflow.c dcap.c -o dnsflow $(LIBS) 18 | 19 | clean: 20 | @rm -f *.o dnsflow 21 | @rm -rf *.dSYM 22 | 23 | uninstall: clean 24 | @rm -v /usr/local/sbin/dnsflow 25 | 26 | ubuntu-uninstall: uninstall 27 | @update-rc.d -f dnsflow remove 28 | @rm -v /etc/init.d/dnsflow 29 | @rm -v /etc/default/dnsflow 30 | 31 | install: dnsflow 32 | @mkdir -p $(DESTDIR)/sbin/ 33 | @install -cv dnsflow $(DESTDIR)/sbin/ 34 | 35 | ubuntu-install: install 36 | @install -cv init/dnsflow /etc/init.d/ 37 | @install -cv default/dnsflow /etc/default/ 38 | @update-rc.d dnsflow defaults 39 | 40 | install-service: 41 | @echo "install" 42 | -------------------------------------------------------------------------------- /Notes: -------------------------------------------------------------------------------- 1 | # Build RPM 2 | # Build RPM 3 | > sudo yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional 4 | > sudo yum install -y gcc rpm-build git libevent-devel libpcap-devel ldns-devel openssl-devel 5 | > git clone git@github.com:deepfield/dnsflow.git dnsflow 6 | > cd dnsflow/rpmbuild 7 | > cp SOURCES/dnsflow-1.1.tar.gz /home/ec2-user/rpmbuild/SOURCES/dnsflow-1.1.tar.gz 8 | > rpmbuild -ba SPECS/dnsflow.spec 9 | 10 | # Install RPM 11 | > sudo yum install dnsflow-1.1-1.el6.x86_64.rpm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNSFlow — Lightweight DNS telemetry 2 | 3 | ## Quick start 4 | * [Download the latest release](https://github.com/deepfield/dnsflow/archive/master.tar.gz) 5 | * [Install Dependencies](#dependencies) 6 | * [Install Reader Dependencies](#install-dnsflow-reader-dependencies) 7 | * [Build DNSFlow](#building-dnsflow-daemon) 8 | * [Running](#running) 9 | * [Running as an Upstart job](#running-as-an-upstart-job) 10 | * [High-Flow Multi-Process Performance](#high-flow-multi-process-performance) 11 | 12 | ## Running 13 | After you get it built, start the daemon that will forward the DNS (to the localhost in this case): 14 | ``` 15 | ./dnsflow -i eth0 -u 127.0.0.1 -P /tmp/dnsflow.pid 16 | ``` 17 | 18 | The daemon can also run in multi-process mode to take advantage of multiple cores. Use the -M option. In this case, dnsflow will run as 4 processes. 19 | ``` 20 | ./dnsflow -i eth0 -u 127.0.0.1 -P /tmp/dnsflow.pid -M 4 21 | ``` 22 | 23 | Use the -s option to randomly sample 1 out of N DNS packets. For highest accuracy, use this as a last resort, and keep the rate as low as possible. For example, to sample 1 out of 2 (50%). 24 | ``` 25 | ./dnsflow -i eth0 -u 127.0.0.1 -P /tmp/dnsflow.pid -M 4 -s 2 26 | ``` 27 | 28 | Read the packets being sent to the local host: 29 | ``` 30 | ./dnsflow_read.py -i lo 31 | ``` 32 | 33 | ## Running as an Upstart job 34 | Running as an Upstart job requires DNSFlow to be installed on a Ubuntu/Debian deployment. These commands should be run with root privileges. 35 | 36 | Starting DNSFlow. 37 | ``` 38 | service dnsflow start 39 | ``` 40 | 41 | Stopping DNSFlow. 42 | ``` 43 | service dnsflow stop 44 | ``` 45 | 46 | Restarting DNSFlow. 47 | ``` 48 | service dnsflow restart 49 | ``` 50 | 51 | Command line options, pid file location, and DNSFlow binary location can be specified in the following location: 52 | ``` 53 | /etc/default/dnsflow 54 | ``` 55 | 56 | ## High-Flow Multi-Process Performance 57 | 58 | When running in multi-process mode (using -M ), it is 59 | important to consider both the hardware capabilities of the machine 60 | you are running on and the characteristics of the processes that are 61 | running. For this application, although processes must wait for 62 | packets to arrive over the network, in high-flow situations these 63 | dnsflow processes are CPU hungry. Thus, in the common case of running 64 | on multi-core, shared-memory, commodity-hardware machine (e.g., 32 65 | cores, 64 GB of RAM), it does not make sense to set the number of 66 | processes to more than the number of CPUs. Typically, the ideal number 67 | of processes is roughly **half of the number of CPUs**, as the CPUs 68 | themselves also share hardware resources. This can be set 69 | automatically with `-M 0`. 70 | 71 | Along with the number processes, the share of packets consumed by each 72 | process is important. The default multi-processing filter distributes 73 | packets based on a modulo of the DNS response destination for ipv4 74 | addresses, and the UDP checksum for ipv6 DNS responses. This can be 75 | good enough in moderate flow situations, but may result in packets 76 | being lost if flow is high and not evenly distributed over client ipv4 77 | addresses. The `-c` option activates a new multi-process filter that 78 | utilizes the ipv4 checksum when it is available, falling back to the 79 | default ipv4 filter when it is not. This balancing mode tends to 80 | provide better load balance over many processes, which may result in 81 | fewer packets being dropped. 82 | 83 | If you would like to automatically apply the recommended 84 | high-performance settings discussed above, launch the application with 85 | the following two additional options: 86 | ``` 87 | sudo ./dnsflow .. -c -M 0 88 | ``` 89 | 90 | If you suspect additional speedup is possible by utilizing more than 91 | half of the available CPUs, we recommend you verify this by examining 92 | the total time to process a large pcap file under different -M 93 | settings on the target machine. A good procedure is to start with a 94 | small setting for M, and then double it until the total processing 95 | time no longer decreases. 96 | 97 | ## Install DNSFlow Reader Dependencies 98 | The dnsflow reader is a python3 script with the following dependencies: 99 | 100 | Install the python package installer pip (via apt on Ubuntu). 101 | ``` 102 | sudo apt-get install python-pip 103 | ``` 104 | 105 | Install python pip modules for dpkt and ipaddr. 106 | ``` 107 | sudo pip install dpkt ipaddr 108 | ``` 109 | 110 | Install [pypcap](https://github.com/pynetwork/pypcap). 111 | 112 | Note: Previously (with python2) the [python-libpcap](http://sourceforge.net/projects/pylibpcap/files/pylibpcap/0.6.4) 113 | was used, and both modules are imported into python with the same name. 114 | So, the python-libcpap may need to first be uninstalled to prevent the naming conflict. 115 | ``` 116 | sudo pip install pypcap 117 | ``` 118 | 119 | ## Building DNSFlow daemon 120 | ``` 121 | cd dnsflow 122 | make 123 | make install # optional 124 | ``` 125 | 126 | ## Dependencies 127 | 128 | ### Ubuntu/Debian Install 129 | ``` 130 | sudo apt-get install build-essential libpcap-dev libevent-dev libldns-dev 131 | ``` 132 | 133 | ### Manual Install 134 | You may need to install the dependencies for your distribution manually. 135 | 136 | For RedHat, you may have to install flex/bison first (for pcap): 137 | ``` 138 | yum install flex bison 139 | ``` 140 | 141 | #### ldns 142 | 143 | ``` 144 | curl -O http://nlnetlabs.nl/downloads/ldns/ldns-1.6.16.tar.gz 145 | tar xvf ldns-1.6.16.tar.gz 146 | cd ldns-1.6.16 147 | ./configure --disable-gost --disable-ecdsa --disable-sha2 --without-ssl --prefix=/usr 148 | make; make install; ldconfig 149 | ``` 150 | 151 | #### libpcap 152 | 153 | ``` 154 | curl -O http://www.tcpdump.org/release/libpcap-1.3.0.tar.gz 155 | tar xf libpcap-1.3.0.tar.gz 156 | cd libpcap-1.3.0 157 | ./configure 158 | make; make install; ldconfig 159 | ``` 160 | 161 | #### libevent 162 | ``` 163 | curl -L -O https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz 164 | tar xf libevent-2.0.21-stable.tar.gz 165 | cd libevent-2.0.21-stable 166 | ./configure 167 | make; make install; ldconfig 168 | ``` 169 | 170 | ## Dependency Links 171 | 172 | - [ldns](http://nlnetlabs.nl/projects/ldns/) 173 | - [libevent](http://monkey.org/~provos/libevent/) 174 | - [pcap](http://www.tcpdump.org/) 175 | 176 | 177 | 178 | ## Installing Source RPM 179 | 180 | In additional to installing from source code, you can choose to build 181 | the CentOS / RedHat 7 RPM 182 | 183 | ``` 184 | > yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional 185 | > sudo yum install /tmp/dnsflow-1.1-1.el7.x86_64.rpm 186 | > sudo vi /lib/systemd/system/dnsflow.service 187 | edit destination IP and interface 188 | > sudo service dnsflow start 189 | ``` 190 | -------------------------------------------------------------------------------- /RPMS/dnsflow-1.1-1.el6.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfield/dnsflow/790ea950a0904c6a3a1cfbc6cd9bc953f2da2f08/RPMS/dnsflow-1.1-1.el6.x86_64.rpm -------------------------------------------------------------------------------- /RPMS/dnsflow-1.1-1.el7.x86_64.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfield/dnsflow/790ea950a0904c6a3a1cfbc6cd9bc953f2da2f08/RPMS/dnsflow-1.1-1.el7.x86_64.rpm -------------------------------------------------------------------------------- /dcap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dcap.c 3 | * 4 | * Copyright (c) 2011, DeepField Networks, Inc. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 3. Neither the name of DeepField Networks, Inc. nor the names of its 16 | * contributors may be used to endorse or promote products derived from this 17 | * software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | */ 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include 56 | #include 57 | 58 | #include "dcap.h" 59 | 60 | #ifndef ETHERTYPE_VLAN 61 | #define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */ 62 | #endif 63 | 64 | #define MAXIMUM_SNAPLEN 65535 65 | 66 | static int 67 | datalink_offset(int i) 68 | { 69 | switch (i) { 70 | case DLT_EN10MB: 71 | i = sizeof(struct ether_header); 72 | break; 73 | case DLT_IEEE802: 74 | i = 22; 75 | break; 76 | case DLT_FDDI: 77 | i = 21; 78 | break; 79 | case DLT_LINUX_SLL: 80 | i = 16; 81 | break; 82 | #ifdef DLT_LOOP 83 | case DLT_LOOP: 84 | #endif 85 | case DLT_NULL: 86 | i = 4; 87 | break; 88 | default: 89 | i = -1; 90 | break; 91 | } 92 | return (i); 93 | } 94 | 95 | static void 96 | dcap_pcap_cb(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *pkt) 97 | { 98 | struct ether_header *eh; 99 | uint16_t ether_type; 100 | pcap_t *pcap = NULL; 101 | struct dcap *dcap; 102 | int length, dl, dloff; 103 | char *p; 104 | 105 | dcap = (struct dcap *) user; 106 | pcap = dcap->_pcap; 107 | 108 | if (dcap->sample_rate > 1) { 109 | /* Seeding is left to the user. */ 110 | if (random() % dcap->sample_rate != 0) { 111 | return; 112 | } 113 | } 114 | 115 | if (pkthdr->caplen != pkthdr->len) { 116 | warnx("Invalid caplen: %d %d\n", pkthdr->caplen, pkthdr->len); 117 | return; 118 | } 119 | 120 | dl = pcap_datalink(pcap); 121 | dloff = datalink_offset(dl); 122 | if(dloff == -1) { 123 | warnx("Unsupported datalink: %d\n", dl); 124 | return; 125 | } 126 | 127 | if (pkthdr->len < dloff + sizeof(struct ip)) { 128 | warnx("Invalid packet: length=%d\n", pkthdr->len); 129 | return; 130 | } 131 | 132 | p = (char *)pkt; 133 | length = pkthdr->len; 134 | 135 | if(dl != DLT_NULL 136 | #ifdef DLT_LOOP 137 | && dl != DLT_LOOP 138 | #endif 139 | && dl != DLT_LINUX_SLL) 140 | { 141 | eh = (struct ether_header *)p; 142 | ether_type = ntohs(eh->ether_type); 143 | p += dloff; 144 | length -= dloff; 145 | 146 | /* Unencapsulate 802.1Q VLAN */ 147 | /* XXX Only supporting 1 level. Just loop for qinq? */ 148 | if (ether_type == ETHERTYPE_VLAN) { 149 | ether_type = ntohs(*(uint16_t *)(p + 2)); 150 | p += 4; 151 | length -= 4; 152 | } 153 | 154 | /* XXX Why are we only checking for IP if it's ethernet? */ 155 | if (ether_type != ETHERTYPE_IP && ether_type != ETHERTYPE_IPV6) { 156 | warnx("Non-ip: ether_type=%d\n", ether_type); 157 | return; 158 | } 159 | } else { 160 | p += dloff; 161 | length -= dloff; 162 | } 163 | 164 | dcap->pkts_captured++; 165 | dcap->_callback((struct timeval *)&pkthdr->ts, length, p, dcap->user); 166 | } 167 | 168 | /* Returns fd if you want to set up libevent yourself. */ 169 | int 170 | dcap_get_fd(struct dcap *dcap) 171 | { 172 | /* See man page. Apparently it's not always selectable on OS X. */ 173 | return (pcap_get_selectable_fd(dcap->_pcap)); 174 | } 175 | 176 | static void 177 | dcap_event_cb(int fd, short event, void *arg) 178 | { 179 | struct dcap *dcap = (struct dcap *)arg; 180 | #if __APPLE__ && __MACH__ 181 | struct timeval ev_tv[1] = {{1, 0}}; 182 | #else 183 | struct timeval *ev_tv = NULL; 184 | #endif 185 | 186 | /* Use pcap_dispatch with cnt of -1 so entire buffer is processed. */ 187 | pcap_dispatch(dcap->_pcap, -1, 188 | (pcap_handler)dcap_pcap_cb, (u_char *)dcap); 189 | event_add(dcap->_ev_pcap, ev_tv); 190 | } 191 | /* Use libevent to check for readiness. */ 192 | int 193 | dcap_event_set(struct dcap *dcap) 194 | { 195 | #if __APPLE__ && __MACH__ 196 | /* Not totally sure what's going on, but it seems bpf won't 197 | * consistently mark the fd as readable, possibly not until the whole 198 | * buffer fills. So, add timeout to handle low packet rates. 199 | * pcap_dispatch() seems to grab any waiting packets, even if the 200 | * buffer hasn't filled. man pcap_get_selectable_fd has some notes on 201 | * this. */ 202 | struct timeval ev_tv[1] = {{1, 0}}; 203 | #else 204 | struct timeval *ev_tv = NULL; 205 | #endif 206 | event_set(dcap->_ev_pcap, dcap_get_fd(dcap), EV_READ, 207 | dcap_event_cb, dcap); 208 | if (event_add(dcap->_ev_pcap, ev_tv) < 0) { 209 | warnx("event_add error"); 210 | return (-1); 211 | } 212 | 213 | return (0); 214 | } 215 | 216 | struct dcap * 217 | dcap_init_live(char *intf_name, int promisc, char *filter, 218 | dcap_handler callback) 219 | { 220 | char errbuf[PCAP_ERRBUF_SIZE]; 221 | struct bpf_program bpf_program; 222 | bpf_u_int32 localnet, netmask; 223 | struct dcap *dcap = NULL; 224 | pcap_t *pcap = NULL; 225 | int status; 226 | 227 | /* Basically mirroring how tcpdump sets up pcap. Exceptions noted. */ 228 | if (intf_name == NULL) { 229 | if ((intf_name = pcap_lookupdev(errbuf)) == NULL) { 230 | warnx("%s", errbuf); 231 | return (NULL); 232 | } 233 | } 234 | 235 | if ((pcap = pcap_create(intf_name, errbuf)) == NULL) { 236 | warnx("%s", errbuf); 237 | return (NULL); 238 | } 239 | 240 | /* Try large enough to hold 1 sec of a GigE interface. */ 241 | if ((status = pcap_set_buffer_size(pcap, 1000000000 / 8)) != 0) { 242 | warnx("%s: Can't set buffer size: %s", 243 | intf_name, pcap_statustostr(status)); 244 | pcap_close(pcap); 245 | return (NULL); 246 | } 247 | if ((status = pcap_set_snaplen(pcap, MAXIMUM_SNAPLEN)) != 0) { 248 | warnx("%s: Can't set snapshot length: %s", 249 | intf_name, pcap_statustostr(status)); 250 | pcap_close(pcap); 251 | return (NULL); 252 | } 253 | if ((status = pcap_set_promisc(pcap, promisc)) !=0) { 254 | warnx("%s: Can't set promiscuous mode: %s", 255 | intf_name, pcap_statustostr(status)); 256 | pcap_close(pcap); 257 | return (NULL); 258 | } 259 | /* What should timeout be? tcpdump just sets to 1000. */ 260 | if ((status = pcap_set_timeout(pcap, 1000)) != 0) { 261 | warnx("%s: pcap_set_timeout failed: %s", 262 | intf_name, pcap_statustostr(status)); 263 | pcap_close(pcap); 264 | return (NULL); 265 | } 266 | 267 | if ((status = pcap_activate(pcap)) != 0) { 268 | warnx("%s: %s\n(%s)", intf_name, pcap_statustostr(status), 269 | pcap_geterr(pcap)); 270 | pcap_close(pcap); 271 | return (NULL); 272 | } 273 | 274 | /* Using libevent to check for readiness, so set nonblocking. */ 275 | if ((status = pcap_setnonblock(pcap, 1, errbuf)) !=0) { 276 | warnx("pcap_setnonblock failed: %s: %s", 277 | pcap_statustostr(status), errbuf); 278 | pcap_close(pcap); 279 | return (NULL); 280 | } 281 | 282 | dcap = calloc(1, sizeof(struct dcap)); 283 | dcap->_pcap = pcap; 284 | dcap->_callback = callback; 285 | dcap->user = NULL; 286 | 287 | /* Get the netmask. Only used for "ip broadcast" filter expression, 288 | * so doesn't really matter. */ 289 | if (pcap_lookupnet(intf_name, &localnet, &netmask, errbuf) < 0) { 290 | /* Not a fatal error, since we don't care. */ 291 | localnet = 0; 292 | netmask = 0; 293 | warnx("%s", errbuf); 294 | } 295 | if (pcap_compile(pcap, &bpf_program, filter, 1, netmask) < 0) { 296 | warnx("%s", pcap_geterr(pcap)); 297 | pcap_close(pcap); 298 | return (NULL); 299 | } 300 | 301 | /* XXX There's a race here. The pcap is already activated above, 302 | * but we haven't set the filter yet. Could get some unwanted pkts. */ 303 | if (pcap_setfilter(pcap, &bpf_program) < 0) { 304 | warnx("%s", pcap_geterr(pcap)); 305 | pcap_close(pcap); 306 | return (NULL); 307 | } 308 | 309 | dcap = calloc(1, sizeof(struct dcap)); 310 | dcap->_pcap = pcap; 311 | snprintf(dcap->intf_name, sizeof(dcap->intf_name), "%s", intf_name); 312 | dcap->_callback = callback; 313 | 314 | return (dcap); 315 | } 316 | 317 | struct dcap * 318 | dcap_init_file(char *filename, char *filter, dcap_handler callback) 319 | { 320 | char pcap_errbuf[PCAP_ERRBUF_SIZE]; 321 | struct bpf_program bpf; 322 | struct dcap *dcap = NULL; 323 | pcap_t *pcap = NULL; 324 | 325 | pcap = pcap_open_offline(filename, pcap_errbuf); 326 | if (pcap == NULL) { 327 | warnx("Could not open file %s (%s)\n", filename, pcap_errbuf); 328 | return (NULL); 329 | } 330 | 331 | if (pcap_compile(pcap, &bpf, filter, 1, 0) < 0) { 332 | warnx("filter compile failed: %s\n", pcap_geterr(pcap)); 333 | pcap_close(pcap); 334 | return (NULL); 335 | } 336 | 337 | if (pcap_setfilter(pcap, &bpf) < 0) { 338 | warnx("Pcap setfilter failed: %s\n", pcap_geterr(pcap)); 339 | pcap_close(pcap); 340 | return (NULL); 341 | } 342 | 343 | dcap = calloc(1, sizeof(struct dcap)); 344 | dcap->_pcap = pcap; 345 | dcap->_callback = callback; 346 | dcap->user = NULL; 347 | 348 | return (dcap); 349 | } 350 | 351 | void 352 | dcap_loop_all(struct dcap *dcap) 353 | { 354 | pcap_loop(dcap->_pcap, -1, dcap_pcap_cb, (u_char *)dcap); 355 | } 356 | 357 | void 358 | dcap_close(struct dcap *dcap) 359 | { 360 | pcap_close(dcap->_pcap); 361 | free(dcap); 362 | } 363 | 364 | struct dcap_stat * 365 | dcap_get_stats(struct dcap *dcap) 366 | { 367 | static struct dcap_stat ds; 368 | struct pcap_stat ps; 369 | 370 | bzero(&ds, sizeof(ds)); 371 | ds.captured = dcap->pkts_captured; 372 | 373 | /* pcap stats not valid for file. */ 374 | if (pcap_file(dcap->_pcap) == NULL) { 375 | bzero(&ps, sizeof(ps)); 376 | if (pcap_stats(dcap->_pcap, &ps) < 0) { 377 | warnx("pcap_stats: %s", pcap_geterr(dcap->_pcap)); 378 | } else { 379 | ds.ps_valid = 1; 380 | ds.ps_recv = ps.ps_recv; 381 | ds.ps_drop = ps.ps_drop; 382 | ds.ps_ifdrop = ps.ps_ifdrop; 383 | } 384 | } 385 | 386 | return (&ds); 387 | } 388 | -------------------------------------------------------------------------------- /dcap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dcap.h 3 | * 4 | * Copyright (c) 2011, DeepField Networks, Inc. 5 | * All rights reserved. 6 | * 7 | */ 8 | 9 | #ifndef __DCAP_H__ 10 | #define __DCAP_H__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | typedef void (*dcap_handler)(struct timeval *tv, int pkt_len, char *ip_pkt, void *user); 20 | 21 | struct dcap { 22 | char intf_name[128]; /* Read-only */ 23 | uint32_t pkts_captured; /* Read-only */ 24 | void *user; /* Read/write */ 25 | uint16_t sample_rate; /* Read/write - 0 or 1 to 26 | disable sampling */ 27 | 28 | /* Private vars */ 29 | pcap_t *_pcap; 30 | struct event _ev_pcap[1]; 31 | dcap_handler _callback; 32 | }; 33 | 34 | struct dcap_stat { 35 | int ps_valid; /* pcap stats only valid for live capture. */ 36 | /* pcap stats */ 37 | uint32_t ps_recv; 38 | uint32_t ps_drop; 39 | uint32_t ps_ifdrop; 40 | 41 | uint32_t captured; 42 | }; 43 | 44 | 45 | struct dcap * dcap_init_file(char *filename, char *filter, 46 | dcap_handler callback); 47 | struct dcap * dcap_init_live(char *intf_name, int promisc, char *filter, 48 | dcap_handler callback); 49 | int dcap_event_set(struct dcap *dcap); 50 | int dcap_get_fd(struct dcap *dcap); 51 | void dcap_loop_all(struct dcap *dcap); 52 | void dcap_close(struct dcap *dcap); 53 | 54 | struct dcap_stat * dcap_get_stats(struct dcap *dcap); 55 | 56 | #endif /* __DCAP_H__*/ 57 | 58 | -------------------------------------------------------------------------------- /default/dnsflow: -------------------------------------------------------------------------------- 1 | # DNSFlow Upstart and SysVinit configuration file 2 | 3 | # Customize location of DNSFlow binary (especially for development testing). 4 | #DNSFLOW="/usr/local/bin/dnsflow" 5 | 6 | # Customize location of DNSFlow PID file. 7 | #DNSFLOW_PIDFILE="/var/run/dnsflow.pid" 8 | 9 | # Use DNSFLOW_OPTS to modify the daemon startup options. 10 | DNSFLOW_OPTS="-i eth0 -u 127.0.0.1 -M 4" 11 | -------------------------------------------------------------------------------- /dnsflow.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dnsflow.c 3 | * 4 | * Copyright (c) 2011, DeepField Networks, Inc. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 3. Neither the name of DeepField Networks, Inc. nor the names of its 16 | * contributors may be used to endorse or promote products derived from this 17 | * software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | */ 32 | 33 | /* DNS Flow Packet Format 34 | Header: 35 | version [1 bytes] 36 | sets_count [1 bytes] 37 | flags [2 bytes] 38 | sequence_number [4 bytes] 39 | sets [variable] 40 | 41 | Data Set: 42 | ip_vers [1 byte] 43 | client_ip [4 bytes]/[16 bytes] 44 | resolver_ip [4 bytes]/[16 bytes] 45 | names_count [1 byte] 46 | ips_count [1 byte] 47 | ip6s_count [1 byte] 48 | names_len [2 bytes] 49 | names [variable] Each is a Nul terminated string. 50 | ips [variable] Word-aligned, starts at names + names_len, each is 4 bytes 51 | ip6s [variable] Word-aligned, starts at names + names_len, each is 16 bytes 52 | 53 | Stats Set: 54 | pkts_captured [4 bytes] 55 | pkts_received [4 bytes] 56 | pkts_dropped [4 bytes] 57 | pkts_ifdropped [4 bytes] Only supported on some platforms. 58 | sample_rate [4 bytes] 59 | */ 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #if __linux__ 68 | #include 69 | #endif 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | 91 | #include 92 | #include 93 | 94 | #include "dcap.h" 95 | 96 | 97 | /* Define a MAX/MIN macros, if we don't already have then. */ 98 | #ifndef MAX 99 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 100 | #endif 101 | #ifndef MIN 102 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 103 | #endif 104 | 105 | #define DNSFLOW_FILTER_MAX_SRC_PORTS 10 106 | #define DNSFLOW_PORT_SLOT_UNUSED -1 107 | static int dnsflow_filter_src_ports[DNSFLOW_FILTER_MAX_SRC_PORTS]; 108 | static int dnsflow_port_override_used = 0; 109 | 110 | #define DNSFLOW_MAX_PARSE 255 111 | #define DNSFLOW_PKT_BUF_SIZE 65535 112 | #define DNSFLOW_PKT_MAX_SIZE 1500 113 | #define DNSFLOW_PKT_TARGET_SIZE 1200 114 | #define DNSFLOW_VERSION 4 115 | #define DNSFLOW_PORT 5300 116 | #define DNSFLOW_UDP_MAX_DSTS 10 117 | 118 | #define DNSFLOW_FLAG_STATS 0x0001 119 | 120 | #define DNSFLOW_SETS_COUNT_MAX 255 121 | #define IP6VERSION 0x60 122 | #define IP6VERSIONMASK 0xf0 123 | #define IP6PACKETHDRLEN 40 124 | struct dnsflow_hdr { 125 | uint8_t version; 126 | uint8_t sets_count; 127 | uint16_t flags; 128 | uint32_t sequence_number; 129 | }; 130 | 131 | #define DNSFLOW_NAMES_COUNT_MAX 255 132 | #define DNSFLOW_IPS_COUNT_MAX 255 133 | #define DNSFLOW_IP4_SET_HDR 40 134 | #define DNSFLOW_IP6_SET_HDR 40 135 | struct dnsflow_set_hdr { 136 | uint8_t ip_vers; 137 | uint8_t names_count; 138 | uint8_t ips_count; 139 | uint8_t ip6s_count; 140 | uint16_t names_len; 141 | union { 142 | struct in_addr client_ip4; 143 | struct in6_addr client_ip6; 144 | } client_ip; 145 | union { 146 | struct in_addr resolver_ip4; 147 | struct in6_addr resolver_ip6; 148 | } resolver_ip; 149 | }; 150 | struct dns_data_set { 151 | uint8_t *names[DNSFLOW_MAX_PARSE]; 152 | int name_lens[DNSFLOW_MAX_PARSE]; 153 | int num_names; 154 | struct in_addr ips[DNSFLOW_MAX_PARSE]; 155 | struct in6_addr ip6s[DNSFLOW_MAX_PARSE]; 156 | int num_ips; 157 | int num_ip6s; 158 | }; 159 | 160 | struct dnsflow_data_pkt { 161 | /* Variable sized pkt, allocate maximum size when it's a data pkt. */ 162 | char pkt[1]; /* DNSFLOW_PKT_BUF_SIZE */ 163 | }; 164 | 165 | 166 | struct dnsflow_stats_pkt { 167 | uint32_t pkts_captured; 168 | uint32_t pkts_received; 169 | uint32_t pkts_dropped; 170 | uint32_t pkts_ifdropped; /* according to pcap, only supported 171 | on some platforms */ 172 | uint32_t sample_rate; 173 | }; 174 | 175 | enum dnsflow_buf_type { 176 | DNSFLOW_DATA, 177 | DNSFLOW_STATS, 178 | }; 179 | struct dnsflow_buf { 180 | uint32_t db_type; /* What's in the union */ 181 | uint32_t db_len; /* Size of what's in the pkt, 182 | db_pkt_hdr and below. */ 183 | 184 | uint32_t db_loop_hdr; /* Holds PF_ type when dumping 185 | straight to pcap file. */ 186 | struct dnsflow_hdr db_pkt_hdr; 187 | union { 188 | struct dnsflow_data_pkt data_pkt; 189 | struct dnsflow_stats_pkt stats_pkt; 190 | } DB_dat; 191 | }; 192 | 193 | /* pcap record headers for saved files */ 194 | struct pcap_timeval { 195 | bpf_int32 tv_sec; /* seconds */ 196 | bpf_int32 tv_usec; /* microseconds */ 197 | }; 198 | 199 | struct pcap_sf_pkthdr { 200 | struct pcap_timeval ts; /* time stamp */ 201 | bpf_u_int32 caplen; /* length of portion present */ 202 | bpf_u_int32 len; /* length this packet (off wire) */ 203 | }; 204 | 205 | /* From http://www.juniper.net/techpubs/en_US/junos/topics/concept/subscriber-management-subscriber-secure-policy-radius-header.html 206 | * Note: looks like juniper has another slightly different mirror format: 207 | * https://www.juniper.net/techpubs/en_US/junos/topics/concept/subscriber-management-subscriber-secure-policy-dtcp-header.html. Not sure when that would be 208 | * used. */ 209 | struct jmirror_hdr { 210 | uint32_t intercept_id; 211 | uint32_t session_id; 212 | }; 213 | 214 | #define db_data_pkt DB_dat.data_pkt 215 | #define db_stats_pkt DB_dat.stats_pkt 216 | 217 | /*** Globals ***/ 218 | /* pkt building */ 219 | static uint32_t sequence_number = 1; 220 | static struct dnsflow_buf *data_buf = NULL; 221 | static time_t last_send = 0; 222 | 223 | static struct event push_ev; 224 | static struct timeval push_tv = {1, 0}; 225 | 226 | static struct event stats_ev; 227 | static struct timeval stats_tv = {10, 0}; 228 | 229 | #if !__linux__ 230 | static struct event check_parent_ev; 231 | static struct timeval check_parent_tv = {1, 0}; 232 | #endif 233 | 234 | static struct event sigterm_ev, sigint_ev, sigchld_ev; 235 | 236 | /* config */ 237 | 238 | /* pcap-record dest port (*network* byte order) */ 239 | static int pcap_record_dst_port_enabled = 0; 240 | static uint16_t pcap_record_dst_port = 0; 241 | 242 | /* jmirror dest port (*network* byte order) - typically 30030 */ 243 | static int jmirror_dst_port_enabled = 0; 244 | static uint16_t jmirror_dst_port = 0; 245 | 246 | static int udp_num_dsts = 0; 247 | static struct sockaddr_in dst_so_addrs[DNSFLOW_UDP_MAX_DSTS]; 248 | 249 | static pcap_t *pc_dump = NULL; 250 | static pcap_dumper_t *pdump = NULL; 251 | 252 | #define MAX_MPROC_CHILDREN 64 253 | static pid_t mproc_children[MAX_MPROC_CHILDREN]; 254 | static int n_mproc_children = 0; 255 | static pid_t my_pid; 256 | 257 | 258 | static void 259 | _log(const char *format, ...) 260 | { 261 | char buf[1024]; 262 | va_list args; 263 | 264 | va_start(args, format); 265 | vsnprintf(buf, sizeof(buf), format, args); 266 | va_end(args); 267 | 268 | fprintf(stderr, "[%d]: %s\n", my_pid, buf); 269 | } 270 | 271 | 272 | /* Add up to 1 sec of jitter. */ 273 | static struct timeval * 274 | jitter_tv(struct timeval *tv) 275 | { 276 | tv->tv_usec = random() % 1000000; 277 | return (tv); 278 | } 279 | 280 | static void 281 | dnsflow_print_stats(struct dcap_stat *ds) 282 | { 283 | _log("%u packets captured", ds->captured); 284 | if (ds->ps_valid) { 285 | _log("%u packets received by filter", ds->ps_recv); 286 | _log("%u packets dropped by kernel", ds->ps_drop); 287 | _log("%u packets dropped by interface", ds->ps_ifdrop); 288 | } 289 | } 290 | 291 | static void 292 | clean_exit(struct dcap *dcap) 293 | { 294 | struct dcap_stat *ds; 295 | int i; 296 | 297 | if (n_mproc_children != 0) { 298 | /* Tell children to exit. */ 299 | for (i = 0; i < n_mproc_children; i++) { 300 | kill(mproc_children[i], SIGTERM); 301 | } 302 | } 303 | 304 | _log("Shutting down."); 305 | ds = dcap_get_stats(dcap); 306 | dnsflow_print_stats(ds); 307 | if (pdump != NULL) { 308 | pcap_dump_close(pdump); 309 | pcap_close(pc_dump); 310 | } 311 | 312 | exit(0); 313 | } 314 | 315 | #if !__linux__ 316 | static void 317 | check_parent_cb(int fd, short event, void *arg) 318 | { 319 | struct dcap *dcap = (struct dcap *)arg; 320 | 321 | if (getppid() == 1) { 322 | /* orphaned */ 323 | _log("parent exited"); 324 | clean_exit(dcap); 325 | } 326 | evtimer_add(&check_parent_ev, &check_parent_tv); 327 | } 328 | #endif 329 | 330 | /* When running in multi-proc mode, if the parent dies, want to make sure the 331 | * children exit. */ 332 | static void 333 | check_parent_setup(struct dcap *dcap) 334 | { 335 | #if __linux__ 336 | /* Linux provides a more efficient way to check for parent exit. */ 337 | if (prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { 338 | errx(1, "prctl failed"); 339 | } 340 | #else 341 | bzero(&check_parent_ev, sizeof(check_parent_ev)); 342 | evtimer_set(&check_parent_ev, check_parent_cb, dcap); 343 | evtimer_add(&check_parent_ev, &check_parent_tv); 344 | #endif 345 | } 346 | 347 | /* Returns the proc number for this process. The parent is always 1. */ 348 | static int 349 | mproc_fork(int num_procs) 350 | { 351 | int proc_i; 352 | pid_t pid; 353 | 354 | if ((num_procs < 1) || (num_procs > MAX_MPROC_CHILDREN)) { 355 | errx(1, "num_procs (%d) is not in range [1, %d]. " 356 | "Recompile dnsflow.c with a larger value for " 357 | "MAX_MPROC_CHILDREN to utilize more processes.", 358 | num_procs, MAX_MPROC_CHILDREN); 359 | } 360 | if (num_procs > get_nprocs()) { 361 | errx(1, "num_procs (%d) is more than " 362 | "the number of available processors (%d). " 363 | "This degrades performance, so it is not allowed.", 364 | num_procs, get_nprocs()); 365 | } 366 | 367 | /* proc_i is 1-based. 1 is parent; start at 2. */ 368 | for (proc_i = 2; proc_i <= num_procs; proc_i++) { 369 | if((pid = fork()) < 0) { 370 | errx(1, "fork error"); 371 | } else if (pid == 0) { 372 | /* child */ 373 | n_mproc_children = 0; 374 | return (proc_i); 375 | } else { 376 | /* parent */ 377 | /* XXX Use process group instead? */ 378 | mproc_children[n_mproc_children++] = pid; 379 | } 380 | } 381 | 382 | /* parent gets slot 1. */ 383 | return (1); 384 | } 385 | 386 | /* encap_offset is the number of bytes between the end of the udp header 387 | * and the start of the encapsulated ip header. 388 | * Ie., the length of foo bar: ip udp (foo bar) ip udp dns 389 | * 390 | * proc_i and num_procs use 1-based numbering. 391 | * */ 392 | static char * 393 | build_pcap_filter(int encap_offset, int proc_i, int num_procs, int enable_mdns, int enable_ipv4_checksum_mproc_filter) 394 | { 395 | /* Note: according to pcap-filter(7), udp offsets only work for ipv4. 396 | * (Would have to use ip6 offsets.) */ 397 | 398 | /* Offsets from start of udp. */ 399 | int udp_offset = 0; /* Offset from udp to encap udp. */ 400 | /* Label offsets used. */ 401 | int dns_flags_offset = 10; 402 | 403 | /* Offsets from start of ip. */ 404 | int ip_offset = 0; /* Offset from ip to encap ip. */ 405 | int ip6_offset = 40; /* Offset of udp from ip6. */ 406 | /* Label offsets used. */ 407 | int dst_ip_offset = 16; 408 | int checksum_udp_offset = 6; 409 | 410 | 411 | /* Buffers to build pcap filter */ 412 | int *p_filter_port = NULL; 413 | char port_filter[1024], *pf_cp; 414 | char dns_resp_filter[1024]; 415 | char multi_proc_filter[1024]; 416 | /* The final filter returned in static buf. */ 417 | static char full_filter_ret[1024]; 418 | 419 | if (encap_offset != 0) { 420 | /* udp, encap, ip, udp */ 421 | udp_offset = sizeof(struct udphdr) + encap_offset + 422 | sizeof(struct ip); 423 | /* ip, udp, encap, ip */ 424 | ip_offset = sizeof(struct ip) + sizeof(struct udphdr) + 425 | encap_offset; 426 | } 427 | 428 | /* Port filter - Match src port 53 (and optionally 5353). */ 429 | if (dnsflow_port_override_used) { 430 | pf_cp = port_filter; 431 | p_filter_port = dnsflow_filter_src_ports; 432 | while (p_filter_port != dnsflow_filter_src_ports + DNSFLOW_FILTER_MAX_SRC_PORTS) { 433 | if (*p_filter_port != DNSFLOW_PORT_SLOT_UNUSED) { 434 | if (p_filter_port == dnsflow_filter_src_ports) { 435 | pf_cp += sprintf(pf_cp, "(src port %d", *p_filter_port); 436 | } else { 437 | pf_cp += sprintf(pf_cp, " or src port %d", *p_filter_port); 438 | } 439 | // handle edge case with -Y command-line option 440 | if (*p_filter_port == 5353) { 441 | enable_mdns = 0; 442 | } 443 | } 444 | ++p_filter_port; 445 | } 446 | // if this is still true, it has not already been added above 447 | if (enable_mdns) { 448 | pf_cp += sprintf(pf_cp, " or src port 5353"); 449 | } 450 | *(pf_cp++) = ')'; 451 | *pf_cp = '\0'; 452 | } else if (enable_mdns) { 453 | snprintf(port_filter, sizeof(port_filter), 454 | "(src port 53 or src port 5353)"); 455 | } else { 456 | snprintf(port_filter, sizeof(port_filter), 457 | "src port 53"); 458 | } 459 | 460 | /* Base dns filter - combine port and flag filters. */ 461 | /* Match valid recursive response flags. 462 | * qr=1, rd=1, ra=1, rcode=0. 463 | * XXX Could also pull out just A/AAAA. */ 464 | snprintf(dns_resp_filter, sizeof(dns_resp_filter), 465 | "(udp and %s and (ip6 or (ip and udp[%d:1] & 0x80 = 0x80)))", port_filter, dns_flags_offset + udp_offset); 466 | 467 | if (num_procs > 1) { 468 | char *cp = NULL; 469 | /* Multi-process filter: 470 | * - For ip6, select based on modulo of udp checksum. Note that 471 | * the 'udp' shorthand does not work in the ip6 context for the 472 | * pcap filter language, so the checksum is found by offsetting 473 | * appropriately from the ip6 accessor. 474 | * - For ip4, since the udp checksum is optional (0 when not computed), 475 | * the default filter selects based on modulo of client ip, 476 | * which is the dst ip for dns responses. It does so using offset 477 | * from ip, so ip options will break view into encap. Using the client 478 | * ip as the load balance key may be useful to keep each client 479 | * in same stream. Optionally, one can activate an ip4 filter 480 | * that selects based on module of udp checksum when it is available, 481 | * falling back to default ip4 filter when it is not. 482 | */ 483 | /* Note about performance: 484 | * - We avoid explicit use of the % operator, and instead 485 | * express (x % y) as (x - x/y*y) for integers x, y. According 486 | * to the pcap-filter(7) man page, the % and ^ operators 487 | * have limited support in some kernels (older or non-linux), 488 | * which can have severe performance impacts. 489 | */ 490 | if (!enable_ipv4_checksum_mproc_filter) { 491 | snprintf(multi_proc_filter, sizeof(multi_proc_filter), 492 | "(ip and (%s) and ((ip[%d:4] - ip[%d:4]/%u*%u) = %u)) or ", 493 | dns_resp_filter, dst_ip_offset + ip_offset, 494 | dst_ip_offset + ip_offset, num_procs, num_procs, proc_i - 1); 495 | cp = multi_proc_filter; 496 | cp += strlen(multi_proc_filter); 497 | } else { 498 | snprintf(multi_proc_filter, sizeof(multi_proc_filter), 499 | "(ip and (%s) and (((udp[%d:2] != 0) and ((udp[%d:2] - udp[%d:2]/%u*%u) = %u)) or ((udp[%d:2] = 0) and ((ip[%d:4] - ip[%d:4]/%u*%u) = %u)))) or ", 500 | dns_resp_filter, 501 | checksum_udp_offset, checksum_udp_offset, checksum_udp_offset, num_procs, num_procs, proc_i - 1, 502 | checksum_udp_offset, dst_ip_offset + ip_offset, dst_ip_offset + ip_offset, num_procs, num_procs, proc_i - 1); 503 | cp = multi_proc_filter; 504 | cp += strlen(multi_proc_filter); 505 | } 506 | snprintf(cp, sizeof(multi_proc_filter) - (cp - multi_proc_filter), 507 | "(ip6 and (%s) and ((ip6[%d:2] - ip6[%d:2]/%u*%u) = %u))", 508 | dns_resp_filter, ip6_offset + checksum_udp_offset, 509 | ip6_offset + checksum_udp_offset, num_procs, num_procs, proc_i - 1); 510 | } else { 511 | /* Just copy base dns filter. */ 512 | snprintf(multi_proc_filter, sizeof(multi_proc_filter), "%s", dns_resp_filter); 513 | } 514 | 515 | 516 | /* The final filter returned in static buf. Incorporates one level 517 | * of vlan encapsulation. */ 518 | bzero(full_filter_ret, sizeof(full_filter_ret)); 519 | snprintf(full_filter_ret, sizeof(full_filter_ret), 520 | "(%s) or (vlan and (%s))", 521 | multi_proc_filter, multi_proc_filter); 522 | 523 | return (full_filter_ret); 524 | } 525 | 526 | char * 527 | ts_format(struct timeval *ts) 528 | { 529 | int sec, usec; 530 | static char buf[256]; 531 | 532 | sec = ts->tv_sec % 86400; 533 | usec = ts->tv_usec; 534 | 535 | snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06u", 536 | sec / 3600, (sec % 3600) / 60, sec % 60, usec); 537 | 538 | return buf; 539 | } 540 | 541 | /** 542 | * Write and lock pid file. 543 | * 544 | * return 1 on success, 0 on failure. 545 | */ 546 | static int 547 | write_pid_file(char *pid_file) 548 | { 549 | int fd; 550 | FILE *fp; 551 | 552 | fd = open(pid_file, O_CREAT | O_RDWR, 0666); 553 | if (fd == -1) { 554 | perror(pid_file); 555 | return 0; 556 | } 557 | 558 | if (flock(fd, LOCK_EX | LOCK_NB) < 0) { 559 | if (errno != EWOULDBLOCK) { 560 | perror(pid_file); 561 | } 562 | return 0; 563 | } 564 | 565 | if (ftruncate(fd, 0) < 0) { 566 | perror(pid_file); 567 | return 0; 568 | } 569 | fp = fdopen(fd, "w"); 570 | 571 | fprintf(fp, "%u\n", getpid()); 572 | 573 | fflush(fp); 574 | fsync(fileno(fp)); 575 | 576 | return 1; 577 | } 578 | 579 | static struct ip6_hdr * 580 | ip6_check(int pkt_len, char *ip_pkt) { 581 | struct ip6_hdr *ip6 = (struct ip6_hdr *)ip_pkt; 582 | if (pkt_len < sizeof(struct ip6_hdr)) { 583 | return (NULL); 584 | } 585 | if ((ip6->ip6_vfc & IP6VERSIONMASK) != IP6VERSION) { 586 | return (NULL); 587 | } 588 | if (pkt_len < IP6PACKETHDRLEN) { 589 | return (NULL); 590 | } 591 | if (pkt_len < ntohs(ip6->ip6_plen)) { 592 | return (NULL); 593 | } 594 | return (ip6); 595 | } 596 | 597 | /* IP checks - version, header len, pkt len. */ 598 | static struct ip * 599 | ip4_check(int pkt_len, char *ip_pkt) { 600 | struct ip *ip = (struct ip *)ip_pkt; 601 | 602 | if (pkt_len < sizeof(struct ip)) { 603 | return (NULL); 604 | } 605 | if (ip->ip_v != IPVERSION) { 606 | return (NULL); 607 | } 608 | 609 | if (pkt_len < (ip->ip_hl << 2)) { 610 | return (NULL); 611 | } 612 | if (pkt_len < ntohs(ip->ip_len)) { 613 | return (NULL); 614 | } 615 | if (ntohs(ip->ip_len) < (ip->ip_hl << 2)) { 616 | return (NULL); 617 | } 618 | 619 | return (ip); 620 | } 621 | 622 | static struct udphdr * 623 | udp_check(int pkt_len, struct ip *ip, struct ip6_hdr *ip6) 624 | { 625 | struct udphdr *udphdr; 626 | struct ip6_ext *ip6_ext; 627 | int ip_hdr_len; 628 | int next_hdr; 629 | int offset; 630 | 631 | if (ip && ip->ip_p == IPPROTO_UDP) { 632 | ip_hdr_len = ip->ip_hl << 2; 633 | if (pkt_len < sizeof(struct ip) + sizeof(struct udphdr)) { 634 | return (NULL); 635 | } 636 | udphdr = (struct udphdr *) (((u_char *) ip) + ip_hdr_len); 637 | } else if (ip6) { 638 | // Note that this loop does not provide much utility 639 | // when the default base filter using `src port 53` is 640 | // in use because the pcap-filter does not know to 641 | // offset to the correct location to check for 642 | // UDP.src_port when additional extension headers 643 | // appear before the UDP section. Thus, we would 644 | // likely never encounter a valid IPv6/UDP packet that 645 | // would need to enter the while-loop 646 | // below. Fortunately, most (if not all) IPv6/UDP/DNS 647 | // responses that we are collecting do not have 648 | // additional extension headers. We leave this loop in 649 | // for exploratory analysis with custom filters. 650 | next_hdr = ip6->ip6_nxt; 651 | ip_hdr_len = sizeof(struct ip6_hdr); 652 | ip6_ext = (struct ip6_ext *)((struct ip6_hdr *)(ip6 + 1)); 653 | while (next_hdr != IPPROTO_UDP) { 654 | if (pkt_len < ip_hdr_len + sizeof(struct ip6_ext)) { 655 | return (NULL); 656 | } 657 | next_hdr = ip6_ext->ip6e_nxt; 658 | offset = (ip6_ext->ip6e_len + 1)*8; 659 | // this may occur if we are dealing with garbage data, 660 | // which may cause this loop to run forever 661 | if (offset <= 0) { 662 | return (NULL); 663 | } 664 | ip_hdr_len +=offset; 665 | ip6_ext = (struct ip6_ext *)(((caddr_t)ip6_ext) + offset); 666 | } 667 | if (next_hdr == IPPROTO_UDP) { 668 | if (pkt_len < ip_hdr_len + sizeof(struct udphdr)) { 669 | return (NULL); 670 | } 671 | udphdr = (struct udphdr *)ip6_ext; 672 | } else { 673 | return (NULL); 674 | } 675 | } else { 676 | return (NULL); 677 | } 678 | 679 | if (pkt_len < ip_hdr_len + ntohs(udphdr->uh_ulen)) { 680 | return (NULL); 681 | } 682 | 683 | return (udphdr); 684 | } 685 | 686 | /* Sanity/buffer-len checks. 687 | * Returns pointer to udp data, or NULL on error. 688 | * On success, ip_ret and udphdr_ret will point to the headers. */ 689 | static char * 690 | ip_udp_check(int pkt_len, char *ip_pkt, 691 | struct ip **ip_ret, struct ip6_hdr **ip6_ret, struct udphdr **udphdr_ret) 692 | { 693 | char *udp_data = NULL; 694 | struct ip *ip = NULL; 695 | struct ip6_hdr *ip6 = NULL; 696 | struct udphdr *udphdr = NULL; 697 | 698 | *ip_ret = NULL; 699 | *ip6_ret = NULL; 700 | *udphdr_ret = NULL; 701 | 702 | /* XXX Count/log number of bad pkts. */ 703 | /* XXX Need to pull in ip/udp checksumming and fragment handling. */ 704 | 705 | if (((ip = ip4_check(pkt_len, ip_pkt)) == NULL) && 706 | ((ip6 = ip6_check(pkt_len, ip_pkt)) == NULL)) { 707 | return NULL; 708 | } 709 | 710 | if ((udphdr = udp_check(pkt_len, ip, ip6)) == NULL) { 711 | return NULL; 712 | } 713 | udp_data = (char *)udphdr + sizeof(struct udphdr); 714 | 715 | *ip_ret = ip; 716 | *ip6_ret = ip6; 717 | *udphdr_ret = udphdr; 718 | return (udp_data); 719 | } 720 | 721 | /* Handle encapsulated ip pkts. 722 | * encap_hdr should point to the start of encapsulated pkt. 723 | * ip_encap_offset is the number of bytes to reach the ip header. 724 | * Returns pointer to udp data, or NULL on error. 725 | * On success, ip_ret and udphdr_ret will point to the headers. */ 726 | static char * 727 | ip_encap_check(int pkt_len, char *encap_hdr, int ip_encap_offset, 728 | struct ip **ip_ret, struct ip6_hdr **ip6_ret, struct udphdr **udphdr_ret) 729 | { 730 | *ip_ret = NULL; 731 | *ip6_ret = NULL; 732 | *udphdr_ret = NULL; 733 | 734 | if (pkt_len < ip_encap_offset) { 735 | return (NULL); 736 | } 737 | 738 | return (ip_udp_check(pkt_len - ip_encap_offset, 739 | encap_hdr + ip_encap_offset, ip_ret, ip6_ret, udphdr_ret)); 740 | } 741 | 742 | static ldns_pkt * 743 | dnsflow_dns_check(int pkt_len, char *dns_pkt) 744 | { 745 | ldns_status status; 746 | ldns_pkt *lp; 747 | ldns_rr *q_rr; 748 | 749 | status = ldns_wire2pkt(&lp, (uint8_t *)dns_pkt, pkt_len); 750 | if (status != LDNS_STATUS_OK) { 751 | _log("Bad DNS pkt: %s", ldns_get_errorstr_by_id(status)); 752 | return (NULL); 753 | } 754 | 755 | /* Looking for valid recursive replies */ 756 | if (ldns_pkt_qr(lp) != 1 || 757 | ldns_pkt_rd(lp) != 1 || 758 | ldns_pkt_ra(lp) != 1 || 759 | ldns_pkt_get_rcode(lp) != LDNS_RCODE_NOERROR) { 760 | ldns_pkt_free(lp); 761 | return (NULL); 762 | } 763 | 764 | /* Check that there's only one question. Generally, this should be true 765 | * as there's no way to reply to more than one question prior to 766 | * proposals in EDNS1. */ 767 | if (ldns_pkt_qdcount(lp) != 1) { 768 | ldns_pkt_free(lp); 769 | return (NULL); 770 | } 771 | 772 | /* Only look at replies to A queries. Could possibly look at 773 | * CNAME queries as well, but those aren't generally used. */ 774 | q_rr = ldns_rr_list_rr(ldns_pkt_question(lp), 0); 775 | if (ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_A && 776 | ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_AAAA && 777 | ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_ANY) { 778 | ldns_pkt_free(lp); 779 | return (NULL); 780 | } 781 | 782 | return (lp); 783 | } 784 | 785 | /* NOTE: The names in the returned dns_data_set point to data inside the 786 | * ldns_pkt. So, don't free the packet until the names have been copied. */ 787 | static struct dns_data_set * 788 | dnsflow_dns_extract(ldns_pkt *lp) 789 | { 790 | /* Statics */ 791 | static struct dns_data_set data[1]; 792 | 793 | ldns_rr_type rr_type; 794 | ldns_rr *q_rr, *a_rr; 795 | ldns_rdf *rdf; 796 | 797 | int i, j; 798 | struct in_addr *ip_ptr; 799 | struct in6_addr *ip6_ptr; 800 | 801 | 802 | data->num_names = 0; 803 | data->num_ips = 0; 804 | data->num_ip6s = 0; 805 | 806 | q_rr = ldns_rr_list_rr(ldns_pkt_question(lp), 0); 807 | 808 | if (ldns_rdf_size(ldns_rr_owner(q_rr)) > LDNS_MAX_DOMAINLEN) { 809 | /* I believe this should never happen for valid DNS. */ 810 | _log("Invalid query string"); 811 | return (NULL); 812 | } 813 | data->names[data->num_names] = ldns_rdf_data(ldns_rr_owner(q_rr)); 814 | data->name_lens[data->num_names] = ldns_rdf_size(ldns_rr_owner(q_rr)); 815 | data->num_names++; 816 | 817 | for (i = 0; i < ldns_pkt_ancount(lp); i++) { 818 | a_rr = ldns_rr_list_rr(ldns_pkt_answer(lp), i); 819 | rr_type = ldns_rr_get_type(a_rr); 820 | 821 | /* XXX Not necessary, remove when we have more confidence. */ 822 | /* 823 | str = ldns_rdf2str(ldns_rr_owner(a_rr)); 824 | if (strcmp(str, data->names[data->num_names - 1])) { 825 | _log("XXX msg not in sequence"); 826 | ldns_pkt_print(stdout, lp); 827 | } 828 | LDNS_FREE(str); 829 | */ 830 | 831 | for (j = 0; j < ldns_rr_rd_count(a_rr); j++) { 832 | rdf = ldns_rr_rdf(a_rr, j); 833 | 834 | if (rr_type == LDNS_RR_TYPE_CNAME) { 835 | if (data->num_names == DNSFLOW_MAX_PARSE) { 836 | _log("Too many names"); 837 | continue; 838 | } 839 | if (ldns_rdf_size(rdf) > LDNS_MAX_DOMAINLEN) { 840 | /* Again, I believe this should never 841 | * happen. */ 842 | _log("Invalid name"); 843 | continue; 844 | } 845 | data->names[data->num_names] = 846 | ldns_rdf_data(rdf); 847 | data->name_lens[data->num_names] = 848 | ldns_rdf_size(rdf); 849 | data->num_names++; 850 | } else if (rr_type == LDNS_RR_TYPE_A) { 851 | if (data->num_ips == DNSFLOW_MAX_PARSE) { 852 | _log("Too many ips"); 853 | continue; 854 | } 855 | ip_ptr = (struct in_addr *) ldns_rdf_data(rdf); 856 | data->ips[data->num_ips++] = *ip_ptr; 857 | } else if (rr_type == LDNS_RR_TYPE_AAAA) { 858 | if (data->num_ip6s == DNSFLOW_MAX_PARSE) { 859 | _log("Too many ips"); 860 | continue; 861 | } 862 | ip6_ptr = (struct in6_addr *) ldns_rdf_data(rdf); 863 | data->ip6s[data->num_ip6s++] = *ip6_ptr; 864 | } else { 865 | /* XXX Only looking at A queries, so this is 866 | * unexpected rdata. */ 867 | } 868 | } 869 | } 870 | 871 | /* Sanity checks */ 872 | if (data->num_names == 0) { 873 | return (NULL); 874 | } 875 | if (data->num_ips == 0 && data->num_ip6s == 0) { 876 | return (NULL); 877 | } 878 | 879 | return (data); 880 | } 881 | 882 | static void 883 | dnsflow_pkt_send(struct dnsflow_buf *dnsflow_buf) 884 | { 885 | static int udp_socket = 0; 886 | struct pcap_pkthdr pkthdr; 887 | int i; 888 | 889 | if (pdump != NULL) { 890 | uint8_t tmp[4096]; 891 | struct iphdr *ip; 892 | struct udphdr *udp; 893 | uint8_t *data; 894 | struct sockaddr_in saddr, daddr; 895 | u_int32_t *link; 896 | 897 | uint32_t pf_type; /* Loopback "header" */ 898 | int linkheader_size = sizeof(pf_type); 899 | 900 | memset(tmp, 0, sizeof(tmp)); 901 | 902 | pf_type = PF_INET; 903 | 904 | link = (u_int32_t *) tmp; 905 | ip = (struct iphdr *) (link + 1); 906 | udp = (struct udphdr *) (ip+1); 907 | data = (uint8_t *) (udp+1); 908 | 909 | 910 | daddr.sin_family = AF_INET; 911 | saddr.sin_family = AF_INET; 912 | daddr.sin_port = htons(5300); 913 | saddr.sin_port = htons(0); 914 | inet_pton(AF_INET, "127.0.0.1", (struct in_addr *)&daddr.sin_addr.s_addr); 915 | inet_pton(AF_INET, "127.0.0.1", (struct in_addr *)&saddr.sin_addr.s_addr); 916 | 917 | ip->ihl = 5; /* header length, number of 32-bit words */ 918 | ip->version = 4; 919 | ip->tos = 0x0; 920 | ip->id = 0; 921 | ip->frag_off = htons(0x4000); /* Don't fragment */ 922 | ip->ttl = 64; 923 | ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + dnsflow_buf->db_len); 924 | ip->protocol = IPPROTO_UDP; 925 | ip->saddr = saddr.sin_addr.s_addr; 926 | ip->daddr = daddr.sin_addr.s_addr; 927 | 928 | udp->uh_sport = saddr.sin_port; 929 | udp->uh_dport = daddr.sin_port; 930 | udp->uh_ulen = htons(sizeof(struct udphdr) + dnsflow_buf->db_len); 931 | udp->uh_sum = 0; 932 | 933 | memcpy(link, &pf_type, linkheader_size); 934 | memcpy(data, &dnsflow_buf->db_pkt_hdr, sizeof(tmp) - (data - tmp)); 935 | 936 | gettimeofday(&pkthdr.ts, NULL); 937 | pkthdr.len = dnsflow_buf->db_len + 20 + 8 + 4; /* ip=20, udp=8, 4=loopback */ 938 | pkthdr.caplen = pkthdr.len; 939 | pcap_dump((u_char *)pdump, &pkthdr, (u_char *) tmp); 940 | return; 941 | } 942 | 943 | if (udp_num_dsts == 0) { 944 | return; 945 | } 946 | 947 | if (udp_socket == 0) { 948 | if ((udp_socket = socket(PF_INET, SOCK_DGRAM, 0)) < 1) { 949 | err(1, "socket failed"); 950 | } 951 | } 952 | for (i = 0; i < udp_num_dsts; i++) { 953 | if (sendto(udp_socket, &dnsflow_buf->db_pkt_hdr, dnsflow_buf->db_len, 0, 954 | (struct sockaddr *)&dst_so_addrs[i], 955 | sizeof(struct sockaddr_in)) < 0) { 956 | warnx("send failed"); 957 | } 958 | } 959 | } 960 | 961 | static void 962 | dnsflow_pkt_send_data() 963 | { 964 | if (data_buf->db_len == 0) { 965 | return; 966 | } 967 | data_buf->db_pkt_hdr.sequence_number = htonl(sequence_number++); 968 | dnsflow_pkt_send(data_buf); 969 | data_buf->db_len = 0; 970 | last_send = time(NULL); 971 | } 972 | 973 | static void 974 | dnsflow_push_cb(int fd, short event, void *arg) 975 | { 976 | time_t now = time(NULL); 977 | 978 | if (now - last_send >= push_tv.tv_sec) { 979 | dnsflow_pkt_send_data(); 980 | } 981 | evtimer_add(&push_ev, jitter_tv(&push_tv)); 982 | } 983 | 984 | /* XXX Need more care to prevent buffer overruns. */ 985 | static void 986 | dnsflow_pkt_build(struct in_addr* client_ip, struct in6_addr* client_ip6, struct in_addr* resolver_ip, struct in6_addr* resolver_ip6, struct dns_data_set *dns_data) 987 | { 988 | struct dnsflow_hdr *dnsflow_hdr; 989 | struct dnsflow_set_hdr *set_hdr; 990 | char *pkt_start, *pkt_cur, *pkt_end, *names_start; 991 | int i, j; 992 | int header_len = 0; 993 | struct in_addr *ip_ptr; 994 | struct in6_addr *ip6_ptr; 995 | int set_len, ips_len_total, ip6s_len_total; 996 | int name_len_total = 0; 997 | uint8_t names_count, ips_count, ip6s_count; 998 | 999 | dnsflow_hdr = &data_buf->db_pkt_hdr; 1000 | pkt_start = (char *)dnsflow_hdr; 1001 | if (data_buf->db_len == 0) { 1002 | /* Starting a new pkt. */ 1003 | bzero(dnsflow_hdr, sizeof(struct dnsflow_hdr)); 1004 | data_buf->db_len += sizeof(struct dnsflow_hdr); 1005 | dnsflow_hdr->version = DNSFLOW_VERSION; 1006 | dnsflow_hdr->sets_count = 0; 1007 | } 1008 | pkt_cur = pkt_start + data_buf->db_len; 1009 | pkt_end = pkt_start + DNSFLOW_PKT_MAX_SIZE - 1; 1010 | 1011 | /* Estimate length of set to see if it fits in this pkt*/ 1012 | set_len = sizeof(struct dnsflow_set_hdr); 1013 | 1014 | names_count = 1015 | MIN(dns_data->num_names, DNSFLOW_NAMES_COUNT_MAX); 1016 | ips_count = 1017 | MIN(dns_data->num_ips, DNSFLOW_IPS_COUNT_MAX); 1018 | ip6s_count = 1019 | MIN(dns_data->num_ip6s, DNSFLOW_IPS_COUNT_MAX); 1020 | for (j = 0; j < names_count; j++) { 1021 | name_len_total += dns_data->name_lens[j]; 1022 | } 1023 | set_len += name_len_total; 1024 | ips_len_total = ips_count * sizeof(struct in_addr); 1025 | ip6s_len_total = ip6s_count * sizeof(struct in6_addr); 1026 | set_len += ips_len_total; 1027 | set_len += ip6s_len_total; 1028 | 1029 | /* This set will not fit in any dnsflow pkt*/ 1030 | if (set_len > DNSFLOW_PKT_MAX_SIZE - sizeof(dnsflow_hdr) + 1) { 1031 | return; 1032 | } 1033 | /* This set will fit in the remainder of this dnsflow pkt*/ 1034 | if (set_len < pkt_end - pkt_cur + 1) { 1035 | /* Start building new set. */ 1036 | set_hdr = (struct dnsflow_set_hdr *)pkt_cur; 1037 | bzero(set_hdr, sizeof(struct dnsflow_set_hdr)); 1038 | /* XXX Not warning if we're truncating names, ips. */ 1039 | if (client_ip) { 1040 | header_len = DNSFLOW_IP4_SET_HDR; 1041 | set_hdr->ip_vers = 4; 1042 | set_hdr->client_ip.client_ip4 = *client_ip; 1043 | set_hdr->resolver_ip.resolver_ip4 = *resolver_ip; 1044 | } else { 1045 | header_len = DNSFLOW_IP6_SET_HDR; 1046 | set_hdr->ip_vers = 6; 1047 | set_hdr->client_ip.client_ip6 = *client_ip6; 1048 | set_hdr->resolver_ip.resolver_ip6 = *resolver_ip6; 1049 | } 1050 | set_hdr->names_count = names_count; 1051 | set_hdr->ips_count = ips_count; 1052 | set_hdr->ip6s_count = ip6s_count; 1053 | data_buf->db_len += header_len; 1054 | pkt_cur = pkt_start + data_buf->db_len; 1055 | 1056 | names_start = pkt_cur; 1057 | for (i = 0; i < set_hdr->names_count; i++) { 1058 | if (dns_data->name_lens[i] > pkt_end - pkt_cur) { 1059 | /* Not enough room. Shouldn't happen. */ 1060 | _log("Pkt create error"); 1061 | data_buf->db_len = 0; 1062 | return; 1063 | } 1064 | memcpy(pkt_cur, dns_data->names[i], dns_data->name_lens[i]); 1065 | data_buf->db_len += dns_data->name_lens[i]; 1066 | pkt_cur = pkt_start + data_buf->db_len; 1067 | } 1068 | while (data_buf->db_len % 4 != 0) { 1069 | /* Pad to word boundary. */ 1070 | pkt_start[data_buf->db_len++] = '\0'; 1071 | } 1072 | pkt_cur = pkt_start + data_buf->db_len; 1073 | set_hdr->names_len = htons(pkt_cur - names_start); 1074 | 1075 | for (i = 0; i < set_hdr->ips_count; i++) { 1076 | ip_ptr = (struct in_addr *)pkt_cur; 1077 | *ip_ptr = dns_data->ips[i]; 1078 | data_buf->db_len += sizeof(struct in_addr); 1079 | pkt_cur = pkt_start + data_buf->db_len; 1080 | } 1081 | for (i = 0; i < set_hdr->ip6s_count; i++) { 1082 | ip6_ptr = (struct in6_addr *)pkt_cur; 1083 | *ip6_ptr = dns_data->ip6s[i]; 1084 | data_buf->db_len += sizeof(struct in6_addr); 1085 | pkt_cur = pkt_start + data_buf->db_len; 1086 | } 1087 | 1088 | dnsflow_hdr->sets_count++; 1089 | 1090 | if (data_buf->db_len >= DNSFLOW_PKT_TARGET_SIZE || 1091 | dnsflow_hdr->sets_count == DNSFLOW_SETS_COUNT_MAX) { 1092 | /* Send */ 1093 | dnsflow_pkt_send_data(); 1094 | } 1095 | } 1096 | /* This set will fit in the next dnsflow pkt*/ 1097 | else { 1098 | /* Send and reset data buffer len */ 1099 | dnsflow_pkt_send_data(); 1100 | /* Start a new packet*/ 1101 | dnsflow_pkt_build(client_ip, client_ip6, resolver_ip, resolver_ip6, dns_data); 1102 | } 1103 | 1104 | } 1105 | 1106 | static void 1107 | dnsflow_dcap_cb(struct timeval *tv, int pkt_len, char *ip_pkt, void *user) 1108 | { 1109 | struct ip *ip; 1110 | struct ip6_hdr *ip6; 1111 | struct udphdr *udphdr; 1112 | char *udp_data; 1113 | int ip_encap_offset = 0; 1114 | int remaining = pkt_len; 1115 | 1116 | ldns_pkt *lp; 1117 | struct dns_data_set *dns_data; 1118 | 1119 | if ((udp_data = ip_udp_check(pkt_len, ip_pkt, &ip, &ip6, &udphdr)) == NULL) { 1120 | return; 1121 | } 1122 | remaining -= udp_data - ip_pkt; 1123 | 1124 | /* Handle various encapsulations. */ 1125 | if (pcap_record_dst_port_enabled && (udphdr->uh_dport == pcap_record_dst_port)) { 1126 | /* pcap header, eth, ip, udp, dns */ 1127 | ip_encap_offset = sizeof(struct pcap_sf_pkthdr) 1128 | + sizeof(struct ether_header); 1129 | } else if (jmirror_dst_port_enabled && (udphdr->uh_dport == jmirror_dst_port)) { 1130 | /* jmirror, ip, udp, dns */ 1131 | ip_encap_offset = sizeof(struct jmirror_hdr); 1132 | } 1133 | if (ip_encap_offset != 0) { 1134 | 1135 | udp_data = ip_encap_check(remaining, udp_data, ip_encap_offset, 1136 | &ip, &ip6, &udphdr); 1137 | if (udp_data == NULL) { 1138 | return; 1139 | } 1140 | } 1141 | 1142 | lp = dnsflow_dns_check(ntohs(udphdr->uh_ulen), udp_data); 1143 | if (lp == NULL) { 1144 | /* Bad dns pkt, or one we're not interested in. */ 1145 | return; 1146 | } 1147 | 1148 | if ((dns_data = dnsflow_dns_extract(lp)) == NULL) { 1149 | ldns_pkt_free(lp); 1150 | return; 1151 | } 1152 | 1153 | /* Should be good to go. */ 1154 | if (ip) { 1155 | dnsflow_pkt_build(&ip->ip_dst, NULL, &ip->ip_src, NULL, dns_data); 1156 | } else if (ip6) { 1157 | dnsflow_pkt_build(NULL, &ip6->ip6_dst, NULL, &ip6->ip6_src, dns_data); 1158 | } 1159 | //ldns_pkt_print(stdout, lp); 1160 | ldns_pkt_free(lp); 1161 | lp = NULL; 1162 | } 1163 | 1164 | static void 1165 | dnsflow_stats_cb(int fd, short event, void *arg) 1166 | { 1167 | struct dcap *dcap = (struct dcap *)arg; 1168 | struct dcap_stat *ds; 1169 | struct dnsflow_buf buf; 1170 | 1171 | static int stats_counter = 0; 1172 | 1173 | evtimer_add(&stats_ev, jitter_tv(&stats_tv)); 1174 | 1175 | ds = dcap_get_stats(dcap); 1176 | stats_counter++; 1177 | if (stats_counter % 6 == 0) { 1178 | /* Print stats once a minute. */ 1179 | dnsflow_print_stats(ds); 1180 | } 1181 | 1182 | bzero(&buf, sizeof(buf)); 1183 | 1184 | buf.db_type = DNSFLOW_STATS; 1185 | buf.db_len = sizeof(struct dnsflow_hdr) + 1186 | sizeof(struct dnsflow_stats_pkt); 1187 | 1188 | buf.db_pkt_hdr.version = DNSFLOW_VERSION; 1189 | buf.db_pkt_hdr.sets_count = 1; 1190 | buf.db_pkt_hdr.flags = htons(DNSFLOW_FLAG_STATS); 1191 | buf.db_pkt_hdr.sequence_number = htonl(sequence_number++); 1192 | 1193 | buf.db_stats_pkt.pkts_captured = htonl(ds->captured); 1194 | buf.db_stats_pkt.pkts_received = htonl(ds->ps_recv); 1195 | buf.db_stats_pkt.pkts_dropped = htonl(ds->ps_drop); 1196 | buf.db_stats_pkt.pkts_ifdropped = htonl(ds->ps_ifdrop); 1197 | buf.db_stats_pkt.sample_rate = htonl(dcap->sample_rate); 1198 | 1199 | dnsflow_pkt_send(&buf); 1200 | } 1201 | 1202 | static void 1203 | signal_cb(int signal, short event, void *arg) 1204 | { 1205 | struct dcap *dcap = (struct dcap *)arg; 1206 | int stat_loc; 1207 | pid_t pid; 1208 | 1209 | switch (signal) { 1210 | case SIGINT: 1211 | case SIGTERM: 1212 | _log("received exit signal: %d", signal); 1213 | clean_exit(dcap); /* Doesn't return. */ 1214 | break; 1215 | case SIGCHLD: 1216 | pid = wait(&stat_loc); 1217 | _log("child exited: %d", pid); 1218 | clean_exit(dcap); 1219 | break; 1220 | default: 1221 | errx(1, "caught unexpected signal: %d", signal); 1222 | break; 1223 | } 1224 | 1225 | } 1226 | 1227 | /* XXX Disable for production */ 1228 | static void 1229 | dnsflow_event_log_cb(int severity, const char *msg) 1230 | { 1231 | if (severity == _EVENT_LOG_DEBUG) { 1232 | return; 1233 | } 1234 | _log("event: %d: %s", severity, msg); 1235 | } 1236 | 1237 | static void 1238 | usage(void) 1239 | { 1240 | extern char *__progname; 1241 | 1242 | fprintf(stderr, "Usage: %s [-hp] [-i interface] [-r pcap_file] " 1243 | "[-f filter_expression] \n", __progname); 1244 | fprintf(stderr, "\t[-P pidfile] [-m proc_i/n_procs] [-M n_procs] " 1245 | "[-s sample_rate]\n"); 1246 | /* Encap options */ 1247 | fprintf(stderr, "\t[-X pcap_record_recv_port] " 1248 | "[-J jmirror_port (usually 30030)]\n"); 1249 | fprintf(stderr, "\t[-Y] (add mDNS port to filter)\n"); 1250 | fprintf(stderr, "\t[-d] (list individual ports to filter, can be used multiple times)\n"); 1251 | /* Output options */ 1252 | fprintf(stderr, "\t[-u udp_dst] [-w pcap_file_dst]\n"); 1253 | /* new improved ipv4 checksum-based filter */ 1254 | fprintf(stderr, "\t[-c] (use ipv4 checksum for multi-proc filter, use with -M)\n"); 1255 | fprintf(stderr, "\n Default filter: %s\n", 1256 | build_pcap_filter(0, 1, 1, 0, 0)); 1257 | 1258 | exit(1); 1259 | } 1260 | 1261 | int 1262 | main(int argc, char *argv[]) 1263 | { 1264 | int c, rv, promisc = 1; 1265 | char *pcap_file_read = NULL, *pcap_file_write = NULL; 1266 | char *filter = NULL, *intf_name = NULL; 1267 | struct dcap *dcap = NULL; 1268 | struct dcap_stat *ds = NULL; 1269 | struct sockaddr_in *so_addr = NULL; 1270 | int encap_offset = 0; 1271 | int enable_mdns = 0; 1272 | int enable_ipv4_checksum_mproc_filter = 0; 1273 | uint32_t n_procs = 1, proc_i = 1, auto_n_procs = 0; 1274 | int is_child = 0; 1275 | uint16_t sample_rate = 0; 1276 | long tmp_filter_port; 1277 | int *p_next_filter_port; 1278 | p_next_filter_port = dnsflow_filter_src_ports; 1279 | while (p_next_filter_port != dnsflow_filter_src_ports + DNSFLOW_FILTER_MAX_SRC_PORTS) { 1280 | *(p_next_filter_port++) = DNSFLOW_PORT_SLOT_UNUSED; 1281 | } 1282 | 1283 | while ((c = getopt(argc, argv, "i:J:r:f:m:M:pP:s:u:w:X:Yv:h:cd:")) != -1) { 1284 | switch (c) { 1285 | case 'c': 1286 | enable_ipv4_checksum_mproc_filter = 1; 1287 | break; 1288 | case 'i': 1289 | intf_name = optarg; 1290 | break; 1291 | case 'J': 1292 | jmirror_dst_port_enabled = 1; 1293 | jmirror_dst_port = htons(atoi(optarg)); 1294 | if (filter == NULL) { 1295 | /* udp, jmirror, ip, udp, dns */ 1296 | encap_offset = sizeof(struct jmirror_hdr); 1297 | } 1298 | break; 1299 | case 'f': 1300 | filter = optarg; 1301 | break; 1302 | case 'm': 1303 | if (sscanf(optarg, "%u/%u", &proc_i, &n_procs) !=2 ) { 1304 | errx(1, "invalid multiproc option -- %s", 1305 | optarg); 1306 | } 1307 | if (n_procs == 0 || proc_i == 0 || proc_i > n_procs) { 1308 | errx(1, "invalid multiproc option -- %s", 1309 | optarg); 1310 | } 1311 | break; 1312 | case 'M': 1313 | auto_n_procs = atoi(optarg); 1314 | // automatically set to half of CPUs if desired 1315 | if (auto_n_procs == 0) { 1316 | auto_n_procs = MAX(1, get_nprocs()/2); 1317 | if (auto_n_procs > MAX_MPROC_CHILDREN) { 1318 | warnx("Reducing num procs (%d) to static limit defined by MAX_MPROC_CHILDREN (%d). " 1319 | "Recompile dnsflow.c with a larger value for MAX_MPROC_CHILDREN to utilize " 1320 | "more processes.", auto_n_procs, MAX_MPROC_CHILDREN); 1321 | auto_n_procs = MAX_MPROC_CHILDREN; 1322 | } 1323 | } 1324 | break; 1325 | case 'p': 1326 | promisc = 0; 1327 | break; 1328 | case 'P': 1329 | if (!write_pid_file(optarg)) { 1330 | errx(1, "dnsflow already running"); 1331 | } 1332 | break; 1333 | case 'r': 1334 | pcap_file_read = optarg; 1335 | break; 1336 | case 's': 1337 | sample_rate = atoi(optarg); 1338 | break; 1339 | case 'u': 1340 | if (udp_num_dsts == DNSFLOW_UDP_MAX_DSTS) { 1341 | errx(1, "too many udp dsts"); 1342 | } 1343 | so_addr = &dst_so_addrs[udp_num_dsts++]; 1344 | bzero(so_addr, sizeof(struct sockaddr_in)); 1345 | so_addr->sin_family = AF_INET; 1346 | so_addr->sin_port = htons(DNSFLOW_PORT); 1347 | if (inet_pton(AF_INET, optarg, 1348 | &so_addr->sin_addr) != 1) { 1349 | errx(1, "invalid ip: %s", optarg); 1350 | } 1351 | break; 1352 | case 'X': 1353 | pcap_record_dst_port_enabled = 1; 1354 | pcap_record_dst_port = htons(atoi(optarg)); 1355 | if (filter == NULL) { 1356 | /* udp, pcap header, eth, ip, udp, dns */ 1357 | encap_offset = sizeof(struct pcap_sf_pkthdr) + 1358 | sizeof(struct ether_header); 1359 | } 1360 | break; 1361 | case 'Y': 1362 | enable_mdns = 1; 1363 | break; 1364 | case 'w': 1365 | pcap_file_write = optarg; 1366 | break; 1367 | case 'd': 1368 | tmp_filter_port = strtol(optarg, NULL, 0); 1369 | if ((errno) || (tmp_filter_port < 0) || (tmp_filter_port > USHRT_MAX)) { 1370 | errx(1, "values for dns src port filter must be in range [0, %d]", USHRT_MAX); 1371 | } 1372 | p_next_filter_port = dnsflow_filter_src_ports; 1373 | while (1) { 1374 | if (p_next_filter_port == dnsflow_filter_src_ports + DNSFLOW_FILTER_MAX_SRC_PORTS) { 1375 | errx(1, "at most %d unique dns filter ports can be assigned", DNSFLOW_FILTER_MAX_SRC_PORTS); 1376 | } 1377 | if (*p_next_filter_port == (int)tmp_filter_port) { 1378 | break; 1379 | } else if (*p_next_filter_port == DNSFLOW_PORT_SLOT_UNUSED) { 1380 | dnsflow_port_override_used = 1; 1381 | *p_next_filter_port = (int)tmp_filter_port; 1382 | break; 1383 | } 1384 | ++p_next_filter_port; 1385 | } 1386 | break; 1387 | case 'h': 1388 | default: 1389 | usage(); 1390 | /* NOTREACHED */ 1391 | } 1392 | } 1393 | 1394 | argc -= optind; 1395 | argv += optind; 1396 | 1397 | if (udp_num_dsts == 0 && pcap_file_write == NULL) { 1398 | errx(1, "output dst missing"); 1399 | } 1400 | 1401 | /* Fork if requested, and not done manually. */ 1402 | if (n_procs == 1 && auto_n_procs > 0) { 1403 | if (pcap_file_write != NULL) { 1404 | errx(1, "can't use -w and -M together"); 1405 | } 1406 | if ((proc_i = mproc_fork(auto_n_procs)) != 1) { 1407 | is_child = 1; 1408 | } 1409 | n_procs = auto_n_procs; 1410 | } 1411 | my_pid = getpid(); 1412 | 1413 | /* Need some randomness for jitter. */ 1414 | srandom(getpid()); 1415 | 1416 | /* for testing. Note, event debug only available in libevent2. */ 1417 | /* 1418 | event_enable_debug_mode(); 1419 | event_enable_debug_logging(EVENT_DBG_ALL); 1420 | */ 1421 | /* Init libevent - must happen after fork on os x (kqueue), or use 1422 | * event_reinit() */ 1423 | event_init(); 1424 | event_set_log_callback(dnsflow_event_log_cb); 1425 | 1426 | if (filter == NULL) { 1427 | filter = build_pcap_filter(encap_offset, proc_i, n_procs, 1428 | enable_mdns, enable_ipv4_checksum_mproc_filter); 1429 | } 1430 | 1431 | /* Init pcap */ 1432 | if (pcap_file_read != NULL) { 1433 | dcap = dcap_init_file(pcap_file_read, filter, dnsflow_dcap_cb); 1434 | _log("reading from file %s, filter %s", pcap_file_read, 1435 | filter); 1436 | } else { 1437 | dcap = dcap_init_live(intf_name, promisc, filter, 1438 | dnsflow_dcap_cb); 1439 | if (dcap == NULL) { 1440 | errx(1, "dcap_init failed"); 1441 | } 1442 | if (dcap_event_set(dcap) < 0) { 1443 | errx(1, "dcap_event_set failed"); 1444 | } 1445 | 1446 | _log("listening on %s, filter %s", dcap->intf_name, filter); 1447 | 1448 | /* Send pcap stats every 10sec. */ 1449 | bzero(&stats_ev, sizeof(stats_ev)); 1450 | evtimer_set(&stats_ev, dnsflow_stats_cb, dcap); 1451 | evtimer_add(&stats_ev, jitter_tv(&stats_tv)); 1452 | } 1453 | if (dcap == NULL) { 1454 | exit(1); 1455 | } 1456 | 1457 | /* Sampling */ 1458 | if (sample_rate > 1) { 1459 | dcap->sample_rate = sample_rate; 1460 | _log("sample_rate set to %u", sample_rate); 1461 | } 1462 | 1463 | /* Even if the flow pkt isn't full, send any buffered data every 1464 | * second. */ 1465 | bzero(&push_ev, sizeof(push_ev)); 1466 | evtimer_set(&push_ev, dnsflow_push_cb, NULL); 1467 | evtimer_add(&push_ev, jitter_tv(&push_tv)); 1468 | 1469 | /* Set signal handlers. Do after dcap_init so dcap can be passed 1470 | * as arg. */ 1471 | bzero(&sigterm_ev, sizeof(sigterm_ev)); 1472 | signal_set(&sigterm_ev, SIGTERM, signal_cb, dcap); 1473 | signal_add(&sigterm_ev, NULL); 1474 | 1475 | bzero(&sigint_ev, sizeof(sigint_ev)); 1476 | signal_set(&sigint_ev, SIGINT, signal_cb, dcap); 1477 | signal_add(&sigint_ev, NULL); 1478 | 1479 | bzero(&sigchld_ev, sizeof(sigchld_ev)); 1480 | signal_set(&sigchld_ev, SIGCHLD, signal_cb, dcap); 1481 | signal_add(&sigchld_ev, NULL); 1482 | 1483 | if (is_child) { 1484 | check_parent_setup(dcap); 1485 | } 1486 | 1487 | if (pcap_file_write != NULL) { 1488 | pc_dump = pcap_open_dead(DLT_NULL, 65535); 1489 | pdump = pcap_dump_open(pc_dump, pcap_file_write); 1490 | if (pdump == NULL) { 1491 | errx(1, "%s: %s", pcap_file_write, 1492 | pcap_geterr(pc_dump)); 1493 | } 1494 | } 1495 | 1496 | /* B/c of the union, this allocates more than max for the pkt, but 1497 | * not a big deal. */ 1498 | data_buf = calloc(1, sizeof(struct dnsflow_buf) + DNSFLOW_PKT_BUF_SIZE); 1499 | data_buf->db_type = DNSFLOW_DATA; 1500 | 1501 | /* Pcap/event loop */ 1502 | if (pcap_file_read != NULL) { 1503 | dcap_loop_all(dcap); 1504 | dnsflow_pkt_send_data(); /* Send last pkt. */ 1505 | if (pdump != NULL) { 1506 | pcap_dump_close(pdump); 1507 | pcap_close(pc_dump); 1508 | } 1509 | free(data_buf); 1510 | ds = dcap_get_stats(dcap); 1511 | dnsflow_print_stats(ds); 1512 | dcap_close(dcap); 1513 | dcap = NULL; 1514 | } else { 1515 | rv = event_dispatch(); 1516 | // Note that this code will typically not execute, as 1517 | // there is a signal handler and clean shutdown 1518 | // callback used during the event loop. If the 1519 | // event_dispatch function ever returns, it means 1520 | // there was a serious error. 1521 | dcap_close(dcap); 1522 | dcap = NULL; 1523 | errx(1, "event_dispatch terminated: %d", rv); 1524 | } 1525 | 1526 | return (0); 1527 | } 1528 | -------------------------------------------------------------------------------- /dnsflow.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=dnsflow 3 | 4 | [Service] 5 | ExecStart=/usr/bin/dnsflow -i eth0 -u 127.0.0.1 6 | 7 | [Install] 8 | WantedBy=multi-user.target 9 | -------------------------------------------------------------------------------- /dnsflow_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | See dnsflow.c header comment for packet formats. 5 | 6 | Utility functions to simplify interface. 7 | """ 8 | import time 9 | import argparse 10 | import gzip 11 | import socket 12 | import dpkt 13 | import struct 14 | import sys 15 | import ipaddr 16 | from dpkt.ip import IP_PROTO_UDP 17 | from dpkt.udp import UDP 18 | 19 | try: 20 | import pcap 21 | except ImportError: 22 | print( 23 | "Could not import the 'pcap' module.\n" 24 | "Please see README for dependency instructions and ensure pypcap (not python-libpcap) is installed." 25 | ) 26 | sys.exit(-1) 27 | 28 | DNSFLOW_FLAG_STATS = 0x0001 29 | DEFAULT_PCAP_FILTER = "udp and dst port 5300" 30 | SNAPLEN = 65535 31 | TIMEOUT = 100 # milliseconds 32 | DNSFLOW_PORT = 5300 33 | 34 | 35 | def get_pcap(fspec): 36 | if fspec.endswith(".gz"): 37 | f = gzip.open(fspec, "rb") 38 | else: 39 | f = open(fspec, "rb") 40 | 41 | pcap_reader = dpkt.pcap.Reader(f) 42 | return pcap_reader 43 | 44 | 45 | def pkt_iter(**kwargs): 46 | """ Iterate over dnsflow pkts. """ 47 | try: 48 | rdr = Reader(**kwargs) 49 | except ValueError as e: 50 | print("\n{}\n".format(e)) 51 | res = [] 52 | else: 53 | res = rdr.iter_interface() if rdr.live_capture else rdr.iter_pcap() 54 | return res 55 | 56 | 57 | # Top-level interface for reading/capturing dnsflow. Instantiate object, 58 | # then iterate using pkt_iter(). 59 | class Reader(object): 60 | def __init__(self, interface=None, pcap_file=None, 61 | pcap_filter=DEFAULT_PCAP_FILTER, stats_only=False): 62 | if interface is None and pcap_file is None: 63 | raise Exception("Specify interface or pcap_file") 64 | if interface is not None and pcap_file is not None: 65 | raise Exception("Specify only interface or pcap_file") 66 | 67 | self.interface = interface 68 | self.pcap_file = pcap_file 69 | self.pcap_filter = pcap_filter 70 | self.stats_only = stats_only 71 | self.live_capture = bool(not self.pcap_file) 72 | self.pcap = None 73 | 74 | if self.pcap_file: 75 | self.pcap = get_pcap(self.pcap_file) 76 | if pcap_filter != DEFAULT_PCAP_FILTER: 77 | msg = "ERROR: Only support default filter {} when reading from PCAP.".format( 78 | DEFAULT_PCAP_FILTER 79 | ) 80 | raise ValueError(msg) 81 | else: 82 | # Interface 83 | self.pcap = pcap.pcap(name=interface, snaplen=SNAPLEN, promisc=True, timeout_ms=TIMEOUT) 84 | self.pcap.setfilter(self.pcap_filter, optimize=1) 85 | 86 | def iter_interface(self): 87 | """ Live-capture packets and process them. """ 88 | print('Listening on %s: %s' % (self.pcap.name, self.pcap.filter)) 89 | while True: 90 | next_item = next(self.pcap) 91 | if next_item is not None: 92 | ts, frame = next_item 93 | # When live-capturing, have already set the filter, so no need to do additional filtering 94 | res = self.handle_frame(ts, frame) 95 | if res: 96 | yield res 97 | 98 | def iter_pcap(self): 99 | """ Iterate through pcap file and process packets. """ 100 | for ts, frame in self.pcap: 101 | res = self.handle_frame(ts, frame, filter=True) 102 | if res: 103 | yield res 104 | 105 | def handle_frame(self, ts, frame, filter=False): 106 | pkt, err = process_pkt( 107 | self.pcap.datalink(), ts, frame, stats_only=self.stats_only, filter=filter 108 | ) 109 | if err is not None: 110 | print(err) 111 | pkt = None 112 | 113 | return pkt 114 | 115 | 116 | def dnsflow_lo(lo): 117 | """ Check if dpkt.loopback.Loopback represents DNSFlow """ 118 | dnsflow = False 119 | if lo.family == socket.AF_INET: 120 | ip = lo.data 121 | if type(ip.data) == UDP and ip.data.dport == DNSFLOW_PORT: 122 | dnsflow = True 123 | 124 | return dnsflow 125 | 126 | 127 | def dnsflow_eth(eth): 128 | """ Check if dpkt.loopback.Loopback represents DNSFlow """ 129 | dnsflow = False 130 | if eth.type != dpkt.ethernet.ETH_TYPE_IP: 131 | return dnsflow 132 | 133 | ip = eth.data 134 | if ip.p == IP_PROTO_UDP and ip.data.dport == DNSFLOW_PORT: 135 | dnsflow = True 136 | 137 | return dnsflow 138 | 139 | 140 | # Returns a tuple(pkt_contents, error_string). 141 | # error_string is None on success; on failure it contains a message 142 | # describing the error. 143 | # pkt_contents is a dict containing the unmarshaled data from the packet. It 144 | # may be incomplete or empty on error. 145 | # stats_only - set to True to parse stats pkts and headers only of data pkts. 146 | def process_pkt(dl_type, ts, buf, stats_only=False, filter=False): 147 | pkt = {} 148 | err = None 149 | ip_pkt = None 150 | packet_filtered = (None, None) 151 | if dl_type == dpkt.pcap.DLT_NULL: 152 | # Loopback 153 | try: 154 | lo = dpkt.loopback.Loopback(buf) 155 | except: 156 | err = 'LOOPBACK-PARSE-FAILED|%s' % (buf) 157 | return (pkt, err) 158 | 159 | if filter and not dnsflow_lo(lo): 160 | return packet_filtered 161 | 162 | if lo.family == socket.AF_UNSPEC: 163 | # dnsflow dumped straight to pcap 164 | dnsflow_pkt = lo.data 165 | src_ip = '0.0.0.0' 166 | src_port = 0 167 | elif lo.family == socket.AF_INET: 168 | # dl, ip, udp, dnsflow_pkt 169 | udp = lo.data.data 170 | dnsflow_pkt = udp.data 171 | ip_pkt = lo.data 172 | src_ip = socket.inet_ntop(socket.AF_INET, ip_pkt.src) 173 | src_port = ip_pkt.data.sport 174 | 175 | elif dl_type == dpkt.pcap.DLT_EN10MB: 176 | # Ethernet 177 | try: 178 | eth = dpkt.ethernet.Ethernet(buf) 179 | except: 180 | err = 'ETHERNET-PARSE-FAILED|%s' % (buf) 181 | return (pkt, err) 182 | 183 | if filter and not dnsflow_eth(eth): 184 | return packet_filtered 185 | 186 | dnsflow_pkt = eth.data.data.data 187 | ip_pkt = eth.data 188 | src_ip = socket.inet_ntop(socket.AF_INET, ip_pkt.src) 189 | src_port = ip_pkt.data.sport 190 | 191 | cp = 0 192 | 193 | # vers, sets_count, flags, seq_num 194 | fmt = '!BBHI' 195 | try: 196 | vers, sets_count, flags, seq_num = struct.unpack(fmt, 197 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 198 | except struct.error as e: 199 | err = 'PARSE_ERROR|%s|%s' % (fmt, e) 200 | return (pkt, err) 201 | cp += struct.calcsize(fmt) 202 | 203 | if (vers not in [0, 1, 2, 3, 4]) or sets_count == 0: 204 | err = 'BAD_PKT|%s' % (src_ip) 205 | return (pkt, err) 206 | 207 | hdr = {} 208 | hdr['src_ip'] = src_ip 209 | hdr['src_port'] = src_port 210 | hdr['timestamp'] = ts 211 | hdr['sets_count'] = sets_count 212 | hdr['flags'] = flags 213 | hdr['sequence_number'] = seq_num 214 | pkt['header'] = hdr 215 | hdr['src_ip_str'] = str(ipaddr.IPAddress(src_ip)) 216 | 217 | if flags & DNSFLOW_FLAG_STATS: 218 | if vers == 2 or vers == 3: 219 | fmt = '!5I' 220 | else: 221 | # vers 0 or 1 222 | fmt = '!4I' 223 | try: 224 | stats = struct.unpack(fmt, 225 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 226 | except struct.error as e: 227 | err = 'HEADER_PARSE_ERROR|%s|%s' % (fmt, e) 228 | return (pkt, err) 229 | sp = {} 230 | sp['pkts_captured'] = stats[0] 231 | sp['pkts_received'] = stats[1] 232 | sp['pkts_dropped'] = stats[2] 233 | sp['pkts_ifdropped'] = stats[3] 234 | if vers == 2: 235 | sp['sample_rate'] = stats[4] 236 | pkt['stats'] = sp 237 | 238 | elif not stats_only: 239 | # data pkt 240 | pkt['data'] = [] 241 | 242 | for i in range(sets_count): 243 | 244 | client_ip = 0 245 | resolver_ip = 0 246 | 247 | try: 248 | if vers == 4: 249 | #ipvers 250 | fmt = '!B' 251 | ipvers = struct.unpack(fmt, 252 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 253 | cp += struct.calcsize(fmt) 254 | 255 | # names_count, ips_count, ip6s_count, names_len, padding, client_ip(v4/v6), resolver_ip(v4/v6) 256 | if ipvers[0] == 4: 257 | fmt = '!BBBHHIIIIIIII' 258 | vals = struct.unpack(fmt, 259 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 260 | names_count, ips_count, ip6s_count, names_len, padding, client_ip, padding2, padding3, padding4, resolver_ip, rpadding2, rpadding3, rpadding4 = vals 261 | else : 262 | fmt = '!BBBHHIIIIIIII' 263 | vals = struct.unpack(fmt, 264 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 265 | 266 | names_count, ips_count, ip6s_count, names_len, padding, ip1, ip2, ip3, ip4, rip1, rip2, rip3, rip4 = vals 267 | ipoctets = [ip4, ip3, ip2, ip1] 268 | for i, val in enumerate(ipoctets): 269 | val <<= 8* struct.calcsize('I') * i 270 | client_ip = client_ip | val 271 | 272 | ipoctets = [rip4, rip3, rip2, rip1] 273 | for i, val in enumerate(ipoctets): 274 | val <<= 8* struct.calcsize('I') * i 275 | resolver_ip = resolver_ip | val 276 | 277 | elif vers == 3 : 278 | #ipvers 279 | fmt = '!B' 280 | ipvers = struct.unpack(fmt, 281 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 282 | cp += struct.calcsize(fmt) 283 | 284 | # names_count, ips_count, ip6s_count, names_len, padding, client_ip(v4/v6) 285 | if ipvers[0] == 4: 286 | fmt = '!BBBHHI' 287 | vals = struct.unpack(fmt, 288 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 289 | names_count, ips_count, ip6s_count, names_len, padding, client_ip = vals 290 | else : 291 | fmt = '!BBBHHIIII' 292 | vals = struct.unpack(fmt, 293 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 294 | names_count, ips_count, ip6s_count, names_len, padding, ip1, ip2, ip3, ip4 = vals 295 | ipoctets = [ip4, ip3, ip2, ip1] 296 | client_ip = 0 297 | for i, val in enumerate(ipoctets): 298 | val <<= 8* struct.calcsize('I') * i 299 | client_ip = client_ip | val 300 | 301 | else : 302 | # client_ip, names_count, ips_count, names_len 303 | fmt = '!IBBH' 304 | ipvers = 4 305 | vals = struct.unpack(fmt, 306 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 307 | client_ip, names_count, ips_count, names_len = vals 308 | 309 | except struct.error as e: 310 | err = 'DATA_PARSE_ERROR|%s|%s' % (fmt, e) 311 | return (pkt, err) 312 | cp += struct.calcsize(fmt) 313 | 314 | client_ip = str(ipaddr.IPAddress(client_ip)) 315 | resolver_ip = str(ipaddr.IPAddress(resolver_ip)) 316 | 317 | fmt = '%ds' % (names_len) 318 | 319 | try: 320 | name_set = struct.unpack(fmt, 321 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)])[0].decode("utf-8") 322 | except struct.error as e: 323 | err = 'DATA_PARSE_ERROR|%s|%s' % (fmt, e) 324 | return (pkt, err) 325 | cp += struct.calcsize(fmt) 326 | if vers in [1, 2, 3, 4] : 327 | # Each name is in the form of an uncompressed dns name. 328 | # names are root domain (Nul) terminated, and padded with Nuls 329 | # on the end to word align. 330 | names = [] 331 | np = 0 332 | try: 333 | for x in range(names_count): 334 | name = [] 335 | label_len = ord(name_set[np]) 336 | np += 1 337 | while label_len != 0: 338 | name.append(name_set[np: np + label_len]) 339 | np += label_len 340 | label_len = ord(name_set[np]) 341 | np += 1 342 | name = '.'.join(name) 343 | names.append(name) 344 | except IndexError as e: 345 | # Hit the end of the name_set buffer. 346 | err = 'NAMES_PARSE_ERROR|%s|%d|%s' % (repr(name_set), 347 | names_count, e) 348 | return (pkt, err) 349 | else: 350 | # vers = 0 351 | # names are Nul terminated, and padded with Nuls on the end to 352 | # word align. 353 | names = name_set.split('\0') 354 | names = names[0:names_count] 355 | 356 | fmt = '!%dI' % (ips_count) 357 | try: 358 | ips = struct.unpack(fmt, 359 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)]) 360 | except struct.error as e: 361 | err = 'DATA_PARSE_ERROR|%s|%s' % (fmt, e) 362 | return (pkt, err) 363 | cp += struct.calcsize(fmt) 364 | ips = [str(ipaddr.IPAddress(x)) for x in ips] 365 | 366 | ip6s = [] 367 | if vers in [3, 4]: 368 | fmt = '!IIII' 369 | for x in range(ip6s_count): 370 | try: 371 | ipval = list(struct.unpack(fmt, 372 | dnsflow_pkt[cp:cp + struct.calcsize(fmt)])) 373 | except struct.error as e: 374 | err = 'DATA_PARSE_ERROR|%s|%s' % (fmt, e) 375 | return (pkt, err) 376 | cp += struct.calcsize(fmt) 377 | 378 | ipval.reverse() 379 | ip6_val = 0 380 | for i, val in enumerate(ipval): 381 | val <<= 8* struct.calcsize('I') * i 382 | ip6_val = ip6_val | val 383 | 384 | ip6s.append(ip6_val) 385 | ip6s = [str(ipaddr.IPAddress(x, 6)) for x in ip6s] 386 | 387 | data = {} 388 | data['client_ip'] = client_ip 389 | data['resolver_ip'] = resolver_ip 390 | data['names'] = names 391 | data['ips'] = ips 392 | data['ip6s'] = ip6s 393 | pkt['data'].append(data) 394 | 395 | return (pkt, err) 396 | 397 | 398 | def _print_parsed_pkt(pkt): 399 | hdr = pkt['header'] 400 | ts = hdr['timestamp'] 401 | tstr = time.strftime('%H:%M:%S', time.gmtime(ts)) 402 | 403 | if 'stats' in pkt: 404 | stats = pkt['stats'] 405 | print("STATS|%s" % ('|'.join(['%s:%d' % (x[0], x[1]) for x in list(stats.items())]))) 406 | else: 407 | for data in pkt['data']: 408 | print('%s|%s|%s|%s|%s|%s|%s' % (hdr['src_ip_str'], data['resolver_ip'], data['client_ip'], tstr, 409 | ','.join(data['names']), ','.join(data['ips']), ','.join(data['ip6s']))) 410 | 411 | 412 | class SrcTracker(object): 413 | def __init__(self): 414 | self.srcs = {} 415 | 416 | def update(self, pkt): 417 | hdr = pkt['header'] 418 | src_id = (hdr['src_ip'], hdr['src_port']) 419 | src = self.srcs.get(src_id) 420 | if src is None: 421 | src = { 422 | 'n_records': 0, 423 | 'n_data_pkts': 0, 424 | 'n_stats_pkts': 0, 425 | 'first_timestamp': hdr['timestamp'], 426 | 'seq': { 427 | 'seq_last': None, 428 | 'seq_total': 0, 429 | 'seq_lost': 0, 430 | 'seq_ooo': 0, 431 | } 432 | } 433 | self.srcs[src_id] = src 434 | src['last_timestamp'] = hdr['timestamp'] 435 | if 'stats' in pkt: 436 | src['n_stats_pkts'] += 1 437 | if 'stats_last' not in src: 438 | # First stats for src 439 | src['stats_last'] = pkt['stats'] 440 | src['stats_delta_last'] = {} 441 | src['stats_delta_total'] = {} 442 | for k in pkt['stats'].keys(): 443 | if k == 'sample_rate': 444 | continue 445 | src['stats_delta_total'][k] = 0 446 | for k in pkt['stats'].keys(): 447 | if k == 'sample_rate': 448 | continue 449 | src['stats_delta_last'][k] = pkt['stats'][k] - src['stats_last'][k] 450 | src['stats_delta_total'][k] += src['stats_delta_last'][k] 451 | src['stats_last'] = pkt['stats'] 452 | else: 453 | src['n_data_pkts'] += 1 454 | src['n_records'] += hdr['sets_count'] 455 | 456 | # Track lost packets. Won't work if there are duplicates. 457 | src_seq = src['seq'] 458 | src_seq['seq_total'] += 1 459 | seq_num = hdr['sequence_number'] 460 | if src_seq['seq_last'] is None: 461 | src_seq['seq_last'] = seq_num 462 | elif seq_num == src_seq['seq_last'] + 1: 463 | src_seq['seq_last'] = seq_num 464 | elif seq_num > src_seq['seq_last'] + 1: 465 | src_seq['seq_lost'] += seq_num - src_seq['seq_last'] - 1 466 | src_seq['seq_last'] = seq_num 467 | elif seq_num < src_seq['seq_last']: 468 | src_seq['seq_lost'] -= 1 469 | src_seq['seq_ooo'] += 1 470 | # Don't update seq_last. 471 | 472 | return src_id 473 | 474 | def print_summary_src(self, src_id): 475 | src = self.srcs[src_id] 476 | ts_delta = src['last_timestamp'] - src['first_timestamp'] 477 | print('%s:%s' % (src_id[0], src_id[1])) 478 | print(' %s' % (' '.join(['%s=%d' % (k, src[k]) 479 | for k in ['n_data_pkts', 'n_records', 'n_stats_pkts']]))) 480 | if ts_delta > 0: 481 | print(' %s' % (' '.join(['%s/s=%.2f' % (k, src[k]/ts_delta) 482 | for k in ['n_data_pkts', 'n_records', 'n_stats_pkts']]))) 483 | if 'stats_delta_total' in src: 484 | print(' %s' % (' '.join(['%s=%d' % (x[0], x[1]) 485 | for x in list(src['stats_delta_total'].items())]))) 486 | if ts_delta > 0: 487 | print(' %s' % (' '.join(['%s/s=%.2f' % 488 | (x[0], x[1]/ts_delta) 489 | for x in list(src['stats_delta_total'].items())]))) 490 | print(' %s' % (' '.join(['%s=%d' % (x[0], x[1]) 491 | for x in list(src['seq'].items())]))) 492 | 493 | 494 | def print_summary(self): 495 | for src_id in self.srcs.keys(): 496 | self.print_summary_src(src_id) 497 | 498 | 499 | def parse_args(): 500 | p = argparse.ArgumentParser() 501 | p.add_argument('-f', dest='extra_filter') 502 | p.add_argument('-F', dest='complete_filter') 503 | p.add_argument('-s', dest='stats_only', action='store_true', 504 | help="show only status packets") 505 | p.add_argument('-S', dest='src_summary', action='store_true', 506 | help="show source summaries") 507 | input_group = p.add_mutually_exclusive_group(required=True) 508 | input_group.add_argument('-r', dest='pcap_file') 509 | input_group.add_argument('-i', dest='interface') 510 | args = p.parse_args() 511 | 512 | return args 513 | 514 | 515 | def main(): 516 | args = parse_args() 517 | 518 | pcap_filter = DEFAULT_PCAP_FILTER 519 | if args.extra_filter: 520 | pcap_filter = '(%s) and (%s)' % (DEFAULT_PCAP_FILTER, args.extra_filter) 521 | elif args.complete_filter: 522 | pcap_filter = args.complete_filter 523 | 524 | if args.stats_only or args.src_summary: 525 | # Only parse headers and stats pkts. I.e., skip payload of data pkts. 526 | parse_stats = True 527 | else: 528 | parse_stats = False 529 | 530 | if args.pcap_file: 531 | diter = pkt_iter( 532 | pcap_file=args.pcap_file, 533 | pcap_filter=pcap_filter, 534 | stats_only=parse_stats 535 | ) 536 | else: 537 | diter = pkt_iter( 538 | interface=args.interface, 539 | pcap_filter=pcap_filter, 540 | stats_only=parse_stats 541 | ) 542 | 543 | srcs = SrcTracker() 544 | 545 | if not diter: 546 | return 547 | 548 | print('%s|%s|%s|%s|%s|%s|%s' % ("dnsflow server", "resolver ip", "client ip", "time", "names", "ipv4", "ipv6")) 549 | 550 | try: 551 | for cnt, pkt in enumerate(diter): 552 | src_id = srcs.update(pkt) 553 | if args.stats_only: 554 | if 'stats' in pkt: 555 | _print_parsed_pkt(pkt) 556 | # XXX This is just printing the total so far, not since 557 | # the last stats pkt. 558 | srcs.print_summary_src(src_id) 559 | elif args.src_summary: 560 | if cnt != 0 and cnt % 100000 == 0: 561 | srcs.print_summary() 562 | print('-'*40) 563 | else: 564 | _print_parsed_pkt(pkt) 565 | except KeyboardInterrupt: 566 | print('\nSummary:') 567 | srcs.print_summary() 568 | else: 569 | print('\nSummary:') 570 | srcs.print_summary() 571 | 572 | 573 | if __name__ == '__main__': 574 | main() 575 | 576 | -------------------------------------------------------------------------------- /init/dnsflow: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: dnsflow 5 | # Required-Start: $syslog $remote_fs 6 | # Required-Stop: $syslog $remote_fs 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: DeepField DNS telemetry tool. 10 | ### END INIT INFO 11 | 12 | BASE="dnsflow" 13 | 14 | # modify these in /etc/default/$BASE (/etc/default/dnsflow) 15 | DNSFLOW=/usr/local/sbin/$BASE 16 | DNSFLOW_PIDFILE=/var/run/$BASE.pid 17 | DNSFLOW_OPTS= 18 | DNSFLOW_DESC="DNSFlow" 19 | 20 | # Get lsb functions 21 | . /lib/lsb/init-functions 22 | 23 | if [ -f /etc/default/$BASE ]; then 24 | . /etc/default/$BASE 25 | fi 26 | 27 | # see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it) 28 | if [[ -x /sbin/initctl ]] && [[ ! $(/sbin/initctl version 2> /dev/null) =~ "upstart" ]] ; then 29 | log_failure_msg "$DNSFLOW_DESC is managed via upstart, try using service $BASE $1" 30 | exit 1 31 | fi 32 | 33 | # Check dnsflow is present 34 | if [ ! -x $DNSFLOW ]; then 35 | log_failure_msg "$DNSFLOW not present or not executable" 36 | exit 1 37 | fi 38 | 39 | fail_unless_root() { 40 | if [ "$(id -u)" != '0' ]; then 41 | log_failure_msg "$DNSFLOW_DESC must be run as root" 42 | exit 1 43 | fi 44 | } 45 | 46 | case "$1" in 47 | start) 48 | fail_unless_root 49 | 50 | log_begin_msg "Starting $DNSFLOW_DESC: $BASE" 51 | start-stop-daemon \ 52 | --start \ 53 | --background \ 54 | --exec "$DNSFLOW" \ 55 | --pidfile "$DNSFLOW_PIDFILE" \ 56 | -- \ 57 | -P "$DNSFLOW_PIDFILE" \ 58 | $DNSFLOW_OPTS 59 | log_end_msg $? 60 | ;; 61 | 62 | stop) 63 | fail_unless_root 64 | log_begin_msg "Stopping $DNSFLOW_DESC: $BASE" 65 | start-stop-daemon --stop --pidfile "$DNSFLOW_PIDFILE" 66 | log_end_msg $? 67 | ;; 68 | 69 | restart) 70 | fail_unless_root 71 | dnsflow_pid=`cat "$DNSFLOW_PIDFILE" 2>/dev/null` 72 | [ -n "$dnsflow_pid" ] \ 73 | && ps -p $dnsflow_pid > /dev/null 2>&1 \ 74 | && $0 stop 75 | $0 start 76 | ;; 77 | 78 | force-reload) 79 | fail_unless_root 80 | $0 restart 81 | ;; 82 | 83 | status) 84 | status_of_proc -p "$DNSFLOW_PIDFILE" "$DNSFLOW" dnsflow 85 | ;; 86 | 87 | *) 88 | echo "Usage: $0 {start|stop|restart|status}" 89 | exit 1 90 | ;; 91 | esac 92 | 93 | exit 0 94 | -------------------------------------------------------------------------------- /rpmbuild/SOURCES/dnsflow-1.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfield/dnsflow/790ea950a0904c6a3a1cfbc6cd9bc953f2da2f08/rpmbuild/SOURCES/dnsflow-1.1.tar.gz -------------------------------------------------------------------------------- /rpmbuild/SPECS/dnsflow.spec: -------------------------------------------------------------------------------- 1 | Name: dnsflow 2 | Version: 1.1 3 | Release: 1%{?dist} 4 | Summary: Convert DNS pcap to compressed DNSFlow 5 | 6 | License: GPL 7 | URL: https://github.com/deepfield/dnsflow 8 | Source0: dnsflow-1.1.tar.gz 9 | 10 | Requires: ldns-devel libpcap-devel libevent-devel openssl-devel 11 | Requires(post): info 12 | Requires(preun): info 13 | 14 | %description 15 | Convert DNS pcap to compressed DNSFlow 16 | 17 | %prep 18 | %setup 19 | 20 | %build 21 | make PREFIX=/usr %{?_smp_mflags} 22 | 23 | %install 24 | make PREFIX=/usr DESTDIR=%{?buildroot} install 25 | make DESTDIR=%{?buildroot} install-service 26 | 27 | %clean 28 | rm -rf %{buildroot} 29 | 30 | %files 31 | %{_bindir}/dnsflow 32 | %{_usr}/lib/systemd/system/dnsflow.service 33 | -------------------------------------------------------------------------------- /tests/v6.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepfield/dnsflow/790ea950a0904c6a3a1cfbc6cd9bc953f2da2f08/tests/v6.pcap --------------------------------------------------------------------------------