• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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