--- /dev/null
+/*
+ * dns_matching.c Drop DNS packets requesting DNS name contained in hash map
+ * For Linux, uses BCC, eBPF. See .py file.
+ *
+ * Copyright (c) 2016 Rudi Floren.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 11-May-2016 Rudi Floren Created this.
+ */
+
+#include <uapi/linux/bpf.h>
+#include <uapi/linux/if_ether.h>
+#include <uapi/linux/if_packet.h>
+#include <uapi/linux/ip.h>
+#include <uapi/linux/in.h>
+#include <uapi/linux/udp.h>
+#include <bcc/proto.h>
+
+#define ETH_LEN 14
+
+struct dns_hdr_t
+{
+ uint16_t id;
+ uint16_t flags;
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+} BPF_PACKET_HEADER;
+
+
+struct dns_query_flags_t
+{
+ uint16_t qtype;
+ uint16_t qclass;
+} BPF_PACKET_HEADER;
+
+struct dns_char_t
+{
+ char c;
+} BPF_PACKET_HEADER;
+
+struct Key {
+ unsigned char p[32];
+};
+
+struct Leaf {
+ // Not really needed in this example
+ unsigned char p[4];
+};
+
+BPF_TABLE("hash", struct Key, struct Leaf, cache, 128);
+
+int dns_test(struct __sk_buff *skb)
+{
+ u8 *cursor = 0;
+ struct Key key = {};
+ // Check of ethernet/IP frame.
+ struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
+ if(ethernet->type == ETH_P_IP) {
+
+ // Check for UDP.
+ struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
+ u16 hlen_bytes = ip->hlen << 2;
+ if(ip->nextp == IPPROTO_UDP) {
+
+ // Check for Port 53, DNS packet.
+ struct udp_t *udp = cursor_advance(cursor, sizeof(*udp));
+ if(udp->dport == 53){
+
+ // Our Cursor + the length of our udp packet - size of the udp header
+ // - the two 16bit values for QTYPE and QCLASS.
+ u8 *sentinel = cursor + udp->length - sizeof(*udp) - 4;
+
+ struct dns_hdr_t *dns_hdr = cursor_advance(cursor, sizeof(*dns_hdr));
+
+ // Do nothing if packet is not a request.
+ if((dns_hdr->flags >>15) != 0) {
+ // Exit if this packet is not a request.
+ return -1;
+ }
+
+ u16 i = 0;
+ struct dns_char_t *c;
+ // This unroll worked not in latest BCC version.
+ for(u8 j = 0; i<255;i++){
+ if (cursor == sentinel) goto end; c = cursor_advance(cursor, 1); key.p[i++] = c->c;
+ }
+ end:
+ {}
+
+ struct Leaf * lookup_leaf = cache.lookup(&key);
+
+ // If DNS name is contained in our map, drop packet.
+ if(lookup_leaf) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
--- /dev/null
+#!/usr/bin/python
+
+from __future__ import print_function
+from bcc import BPF
+from ctypes import *
+
+import sys
+import socket
+import os
+import struct
+
+
+def encode_dns(name):
+ size = 32
+ if len(name) > 253:
+ raise Exception("DNS Name too long.")
+ b = bytearray(size)
+ i = 0;
+ elements = name.split(".")
+ for element in elements:
+ b[i] = struct.pack("!B", len(element))
+ i += 1
+ for j in range(0, len(element)):
+ b[i] = element[j]
+ i += 1
+
+
+ return (c_ubyte * size).from_buffer(b)
+
+
+
+# initialize BPF - load source code from http-parse-simple.c
+bpf = BPF(src_file = "dns_matching.c", debug=0)
+# print(bpf.dump_func("dns_test"))
+
+#load eBPF program http_filter of type SOCKET_FILTER into the kernel eBPF vm
+#more info about eBPF program types
+#http://man7.org/linux/man-pages/man2/bpf.2.html
+function_dns_matching = bpf.load_func("dns_matching", BPF.SOCKET_FILTER)
+
+
+#create raw socket, bind it to eth0
+#attach bpf program to socket created
+BPF.attach_raw_socket(function_dns_matching, "eth1")
+
+# Get the table.
+cache = bpf.get_table("cache")
+
+# Create first entry for foo.bar
+key = cache.Key()
+key.p = encode_dns("foo.bar")
+
+leaf = cache.Leaf()
+leaf.p = (c_ubyte * 4).from_buffer(bytearray(4))
+cache[key] = leaf
+
+bpf.trace_print()