]> git.ipfire.org Git - thirdparty/pdns.git/blob - contrib/xdp.py
Merge pull request #14701 from zeha/zeha-13039-refac
[thirdparty/pdns.git] / contrib / xdp.py
1 #!/usr/bin/env python3
2 import argparse
3 import ctypes as ct
4 import netaddr
5 import socket
6
7 from bcc import BPF
8
9 # Constants
10 QTYPES = {'LOC': 29, '*': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, 'AAAA': 28, 'CNAME': 5, 'MINFO': 14, 'EID': 31, 'GPOS': 27, 'X25': 19, 'HINFO': 13, 'CAA': 257, 'NULL': 10, 'DNSKEY': 48, 'DS': 43, 'ISDN': 20, 'SOA': 6, 'RP': 17, 'UID': 101, 'TALINK': 58, 'TKEY': 249, 'PX': 26, 'NSAP-PTR': 23, 'TXT': 16, 'IPSECKEY': 45, 'DNAME': 39, 'MAILA': 254, 'AFSDB': 18, 'SSHFP': 44, 'NS': 2, 'PTR': 12, 'SPF': 99, 'TA': 32768, 'A': 1, 'NXT': 30, 'AXFR': 252, 'RKEY': 57, 'KEY': 25, 'NIMLOC': 32, 'A6': 38, 'TLSA': 52, 'MG': 8, 'HIP': 55, 'NSEC': 47, 'GID': 102, 'SRV': 33, 'DLV': 32769, 'NSEC3PARAM': 51, 'UNSPEC': 103, 'TSIG': 250, 'ATMA': 34, 'RRSIG': 46, 'OPT': 41, 'MD': 3, 'NAPTR': 35, 'MF': 4, 'MB': 7, 'DHCID': 49, 'MX': 15, 'MAILB': 253, 'CERT': 37, 'NINFO': 56, 'APL': 42, 'MR': 9, 'SIG': 24, 'WKS': 11, 'KX': 36, 'NSAP': 22, 'RT': 21, 'SINK': 40}
11 INV_QTYPES = {v: k for k, v in QTYPES.items()}
12 ACTIONS = {1 : 'DROP', 2 : 'TC'}
13
14 DROP_ACTION = 1
15 TC_ACTION = 2
16
17 # The list of blocked IPv4, IPv6 and QNames
18 # IP format : (IPAddress, Action)
19 # CIDR format : (IPAddress/cidr, Action)
20 # QName format : (QName, QType, Action)
21 blocked_ipv4 = [("192.0.2.1", TC_ACTION)]
22 blocked_ipv6 = [("2001:db8::1", TC_ACTION)]
23 blocked_cidr4 = [("192.0.1.1/24", TC_ACTION)]
24 blocked_cidr6 = [("2001:db8::1/128", TC_ACTION)]
25 blocked_qnames = [("localhost", "A", DROP_ACTION), ("test.com", "*", TC_ACTION)]
26
27 # Main
28 parser = argparse.ArgumentParser(description='XDP helper for DNSDist')
29 parser.add_argument('--xsk', action='store_true', help='Enable XSK (AF_XDP) mode', default=False)
30 parser.add_argument('--interface', '-i', type=str, default='eth0', help='The interface on which the filter will be attached')
31
32 parameters = parser.parse_args()
33 cflag = []
34 if parameters.xsk:
35 print(f'Enabling XSK (AF_XDP) on {parameters.interface}..')
36 cflag.append("-DUseXsk")
37 else:
38 Ports = [53]
39 portsStr = ', '.join(str(port) for port in Ports)
40 print(f'Enabling XDP on {parameters.interface} and ports {portsStr}..')
41 IN_DNS_PORT_SET = "||".join("COMPARE_PORT((x),"+str(i)+")" for i in Ports)
42 cflag.append(r"-DIN_DNS_PORT_SET(x)=(" + IN_DNS_PORT_SET + r")")
43
44 xdp = BPF(src_file="xdp-filter.ebpf.src", cflags=cflag)
45
46 fn = xdp.load_func("xdp_dns_filter", BPF.XDP)
47 xdp.attach_xdp(parameters.interface, fn, 0)
48
49 v4filter = xdp.get_table("v4filter")
50 v6filter = xdp.get_table("v6filter")
51 cidr4filter = xdp.get_table("cidr4filter")
52 cidr6filter = xdp.get_table("cidr6filter")
53 qnamefilter = xdp.get_table("qnamefilter")
54
55 if parameters.xsk:
56 xskDestinations = xdp.get_table("xskDestinationsV4")
57
58 for ip in blocked_ipv4:
59 print(f"Blocking {ip}")
60 key = v4filter.Key(int(netaddr.IPAddress(ip[0]).value))
61 leaf = v4filter.Leaf()
62 leaf.counter = 0
63 leaf.action = ip[1]
64 v4filter[key] = leaf
65
66 for ip in blocked_ipv6:
67 print(f"Blocking {ip}")
68 ipv6_int = int(netaddr.IPAddress(ip[0]).value)
69 ipv6_bytes = bytearray([(ipv6_int & (255 << 8*(15-i))) >> (8*(15-i)) for i in range(16)])
70 key = (ct.c_uint8 * 16).from_buffer(ipv6_bytes)
71 leaf = v6filter.Leaf()
72 leaf.counter = 0
73 leaf.action = ip[1]
74 v6filter[key] = leaf
75
76 for item in blocked_cidr4:
77 print(f"Blocking {item}")
78 key = cidr4filter.Key()
79 network = netaddr.IPNetwork(item[0])
80 key.cidr = network.prefixlen
81 key.addr = socket.htonl(network.network.value)
82 leaf = cidr4filter.Leaf()
83 leaf.counter = 0
84 leaf.action = item[1]
85 cidr4filter[key] = leaf
86
87 for item in blocked_cidr6:
88 print(f"Blocking {item}")
89 key = cidr6filter.Key()
90 network = netaddr.IPNetwork(item[0])
91 key.cidr = network.prefixlen
92 ipv6_int = int(network.network.value)
93 ipv6_bytes = bytearray([(ipv6_int & (255 << 8*(15-i))) >> (8*(15-i)) for i in range(16)])
94 key.addr.in6_u.u6_addr8 = (ct.c_uint8 * 16).from_buffer(ipv6_bytes)
95 leaf = cidr6filter.Leaf()
96 leaf.counter = 0
97 leaf.action = item[1]
98 cidr6filter[key] = leaf
99
100 for qname in blocked_qnames:
101 print(f"Blocking {qname}")
102 key = qnamefilter.Key()
103 qn = bytearray()
104 for sub in qname[0].split('.'):
105 qn.append(len(sub))
106 for ch in sub:
107 qn.append(ord(ch))
108 qn.extend((0,) * (255 - len(qn)))
109 key.qname = (ct.c_ubyte * 255).from_buffer(qn)
110 key.qtype = ct.c_uint16(QTYPES[qname[1]])
111 leaf = qnamefilter.Leaf()
112 leaf.counter = 0
113 leaf.action = qname[2]
114 qnamefilter[key] = leaf
115
116 print(f"Filter is ready on {parameters.interface}")
117
118 try:
119 xdp.trace_print()
120 except KeyboardInterrupt:
121 pass
122
123 for item in v4filter.items():
124 print(f"{str(netaddr.IPAddress(item[0].value))} ({ACTIONS[item[1].action]}): {item[1].counter}")
125 for item in v6filter.items():
126 print(f"{str(socket.inet_ntop(socket.AF_INET6, item[0]))} ({ACTIONS[item[1].action]}): {item[1].counter}")
127 for item in cidr4filter.items():
128 addr = netaddr.IPAddress(socket.ntohl(item[0].addr))
129 print(f"{str(addr)}/{str(item[0].cidr)} ({ACTIONS[item[1].action]}): {item[1].counter}")
130 for item in cidr6filter.items():
131 print(f"{str(socket.inet_ntop(socket.AF_INET6, item[0].addr))}/{str(item[0].cidr)} ({ACTIONS[item[1].action]}): {item[1].counter}")
132 for item in qnamefilter.items():
133 print(f"{''.join(map(chr, item[0].qname)).strip()}/{INV_QTYPES[item[0].qtype]} ({ACTIONS[item[1].action]}): {item[1].counter}")
134
135 xdp.remove_xdp(parameters.interface, 0)