1#!/usr/bin/python 2 3from __future__ import print_function 4from bcc import BPF 5from ctypes import * 6 7import os 8import sys 9import fcntl 10import dnslib 11import argparse 12 13 14def encode_dns(name): 15 if len(name) + 1 > 255: 16 raise Exception("DNS Name too long.") 17 b = bytearray() 18 for element in name.split('.'): 19 sublen = len(element) 20 if sublen > 63: 21 raise ValueError('DNS label %s is too long' % element) 22 b.append(sublen) 23 b.extend(element.encode('ascii')) 24 b.append(0) # Add 0-len octet label for the root server 25 return b 26 27def add_cache_entry(cache, name): 28 key = cache.Key() 29 key_len = len(key.p) 30 name_buffer = encode_dns(name) 31 # Pad the buffer with null bytes if it is too short 32 name_buffer.extend((0,) * (key_len - len(name_buffer))) 33 key.p = (c_ubyte * key_len).from_buffer(name_buffer) 34 leaf = cache.Leaf() 35 leaf.p = (c_ubyte * 4).from_buffer(bytearray(4)) 36 cache[key] = leaf 37 38 39parser = argparse.ArgumentParser(usage='For detailed information about usage,\ 40 try with -h option') 41req_args = parser.add_argument_group("Required arguments") 42req_args.add_argument("-i", "--interface", type=str, default="", 43 help="Interface name, defaults to all if unspecified.") 44req_args.add_argument("-d", "--domains", type=str, required=True, nargs="+", 45 help='List of domain names separated by space. For example: -d abc.def xyz.mno') 46args = parser.parse_args() 47 48# initialize BPF - load source code from http-parse-simple.c 49bpf = BPF(src_file = "dns_matching.c", debug=0) 50# print(bpf.dump_func("dns_test")) 51 52#load eBPF program http_filter of type SOCKET_FILTER into the kernel eBPF vm 53#more info about eBPF program types 54#http://man7.org/linux/man-pages/man2/bpf.2.html 55function_dns_matching = bpf.load_func("dns_matching", BPF.SOCKET_FILTER) 56 57 58#create raw socket, bind it to user provided interface 59#attach bpf program to socket created 60BPF.attach_raw_socket(function_dns_matching, args.interface) 61 62# Get the table. 63cache = bpf.get_table("cache") 64 65# Add cache entries 66for e in args.domains: 67 print(">>>> Adding map entry: ", e) 68 add_cache_entry(cache, e) 69 70print("\nTry to lookup some domain names using nslookup from another terminal.") 71print("For example: nslookup foo.bar") 72print("\nBPF program will filter-in DNS packets which match with map entries.") 73print("Packets received by user space program will be printed here") 74print("\nHit Ctrl+C to end...") 75 76socket_fd = function_dns_matching.sock 77fl = fcntl.fcntl(socket_fd, fcntl.F_GETFL) 78fcntl.fcntl(socket_fd, fcntl.F_SETFL, fl & (~os.O_NONBLOCK)) 79 80while 1: 81 #retrieve raw packet from socket 82 try: 83 packet_str = os.read(socket_fd, 2048) 84 except KeyboardInterrupt: 85 sys.exit(0) 86 packet_bytearray = bytearray(packet_str) 87 88 ETH_HLEN = 14 89 UDP_HLEN = 8 90 91 #IP HEADER 92 #calculate ip header length 93 ip_header_length = packet_bytearray[ETH_HLEN] #load Byte 94 ip_header_length = ip_header_length & 0x0F #mask bits 0..3 95 ip_header_length = ip_header_length << 2 #shift to obtain length 96 97 #calculate payload offset 98 payload_offset = ETH_HLEN + ip_header_length + UDP_HLEN 99 100 payload = packet_bytearray[payload_offset:] 101 # pass the payload to dnslib for parsing 102 dnsrec = dnslib.DNSRecord.parse(payload) 103 print (dnsrec.questions, "\n") 104