1""" 2Packet interceptor using NFQueue 3 4Requirements: 5- CPython 6- NFQUEUE target support in Kernel 7- iptables 8""" 9import ctypes 10import errno 11import threading 12import logging 13import socket 14from socket import htons, ntohl, ntohs 15from socket import timeout as socket_timeout 16from ctypes import util as utils 17from collections import namedtuple 18 19logger = logging.getLogger("pypacker") 20 21MSG_NO_NFQUEUE = "Could not load netfilter_queue library. See README.md for interceptor requirements." 22 23netfilter = None 24 25try: 26 # Load library 27 nflib = utils.find_library("netfilter_queue") 28 29 if nflib is None: 30 raise RuntimeError() 31 32 netfilter = ctypes.cdll.LoadLibrary(nflib) 33except: 34 logger.exception(MSG_NO_NFQUEUE) 35 36 37class NfqQHandler(ctypes.Structure): 38 pass 39 40 41class NfnlHandle(ctypes.Structure): 42 pass 43 44 45nfnl_callback_ctype = ctypes.CFUNCTYPE( 46 ctypes.c_int, *(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) 47) 48 49 50class NfnlCallback(ctypes.Structure): 51 _fileds_ = [("call", nfnl_callback_ctype), 52 ("data", ctypes.c_void_p), 53 ("attr_count", ctypes.c_uint16) 54 ] 55 56 57class NfnlSubsysHandle(ctypes.Structure): 58 _fields_ = [("nfilter_handler", ctypes.POINTER(NfnlHandle)), 59 ("subscriptions", ctypes.c_uint32), 60 ("subsys_id", ctypes.c_uint8), 61 ("cb_count", ctypes.c_uint8), 62 ("callback", ctypes.POINTER(NfnlCallback)) 63 ] 64 65 66class NfqHandle(ctypes.Structure): 67 _fields_ = [("nfnlh", ctypes.POINTER(NfnlHandle)), 68 ("nfnlssh", ctypes.POINTER(NfnlSubsysHandle)), 69 ("qh_list", ctypes.POINTER(NfqQHandler)) 70 ] 71 72 73class NfqQHandle(ctypes.Structure): 74 _fields_ = [("next", ctypes.POINTER(NfqQHandler)), 75 ("h", ctypes.POINTER(NfqHandle)), 76 ("id", ctypes.c_uint16), 77 ("cb", ctypes.POINTER(NfnlHandle)), 78 ("data", ctypes.c_void_p) 79 ] 80 81 82class NfqData(ctypes.Structure): 83 _fields_ = [("data", ctypes.POINTER(ctypes.c_void_p))] 84 85 86class NfqnlMsgPacketHw(ctypes.Structure): 87 _fields_ = [("hw_addrlen", ctypes.c_uint16), 88 ("_pad", ctypes.c_uint16), 89 ############################# 90 ("hw_addr", ctypes.c_uint8 * 8)] 91 92 93class NfqnlMsgPacketHdr(ctypes.Structure): 94 _fields_ = [("packet_id", ctypes.c_uint32), 95 ("hw_protocol", ctypes.c_uint16), 96 ("hook", ctypes.c_uint8) 97 ] 98 99 100class Timeval(ctypes.Structure): 101 _fields_ = [("tv_sec", ctypes.c_long), 102 ("tv_usec", ctypes.c_long)] 103 104 105# Return netfilter netlink handler 106nfnlh = netfilter.nfq_nfnlh 107nfnlh.restype = ctypes.POINTER(NfnlHandle) 108nfnlh.argtypes = ctypes.POINTER(NfqHandle), 109 110# Return a file descriptor for the netlink connection associated with the 111# given queue connection handle. 112nfq_fd = netfilter.nfnl_fd 113nfq_fd.restype = ctypes.c_int 114nfq_fd.argtypes = ctypes.POINTER(NfnlHandle), 115 116nfnl_rcvbufsiz = netfilter.nfnl_rcvbufsiz 117nfnl_rcvbufsiz.restype = ctypes.c_int 118nfnl_rcvbufsiz.argtypes = ctypes.POINTER(NfnlHandle), ctypes.c_uint 119 120# This function obtains a netfilter queue connection handle 121ll_open_queue = netfilter.nfq_open 122ll_open_queue.restype = ctypes.POINTER(NfqHandle) 123 124# This function closes the nfqueue handler and free associated resources. 125close_queue = netfilter.nfq_close 126close_queue.restype = ctypes.c_int 127close_queue.argtypes = ctypes.POINTER(NfqHandle), 128 129# Bind a nfqueue handler to a given protocol family. 130bind_pf = netfilter.nfq_bind_pf 131bind_pf.restype = ctypes.c_int 132bind_pf.argtypes = ctypes.POINTER(NfqHandle), ctypes.c_uint16 133 134# Unbind nfqueue handler from a protocol family. 135unbind_pf = netfilter.nfq_unbind_pf 136unbind_pf.restype = ctypes.c_int 137unbind_pf.argtypes = ctypes.POINTER(NfqHandle), ctypes.c_uint16 138 139# Creates a new queue handle, and returns it. 140create_queue = netfilter.nfq_create_queue 141create_queue.restype = ctypes.POINTER(NfqQHandler) 142create_queue.argtypes = ctypes.POINTER(NfqHandle), ctypes.c_uint16, ctypes.c_void_p, ctypes.c_void_p 143 144# Removes the binding for the specified queue handle. 145destroy_queue = netfilter.nfq_destroy_queue 146destroy_queue.restype = ctypes.c_int 147destroy_queue.argtypes = ctypes.POINTER(NfqQHandler), 148 149# Triggers an associated callback for the given packet received from the queue. 150handle_packet = netfilter.nfq_handle_packet 151handle_packet.restype = ctypes.c_int 152handle_packet.argtypes = ctypes.POINTER(NfqHandle), ctypes.c_char_p, ctypes.c_int 153 154# nfqnl_config_mode 155NFQNL_COPY_NONE, NFQNL_COPY_META, NFQNL_COPY_PACKET = 0, 1, 2 156 157# Sets the amount of data to be copied to userspace for each packet queued 158# to the given queue. 159# 160# NFQNL_COPY_NONE - do not copy any data 161# NFQNL_COPY_META - copy only packet metadata 162# NFQNL_COPY_PACKET - copy entire packet 163set_mode = netfilter.nfq_set_mode 164set_mode.restype = ctypes.c_int 165set_mode.argtypes = ctypes.POINTER(NfqQHandler), ctypes.c_uint8, ctypes.c_uint 166 167# Sets the size of the queue in kernel. This fixes the maximum number 168# of packets the kernel will store before internally before dropping 169# upcoming packets. 170set_queue_maxlen = netfilter.nfq_set_queue_maxlen 171set_queue_maxlen.restype = ctypes.c_int 172set_queue_maxlen.argtypes = ctypes.POINTER(NfqQHandler), ctypes.c_uint32 173 174# Responses from hook functions. 175NF_DROP, NF_ACCEPT, NF_STOLEN = 0, 1, 2 176NF_QUEUE, NF_REPEAT, NF_STOP = 3, 4, 5 177NF_MAX_VERDICT = NF_STOP 178 179# Notifies netfilter of the userspace verdict for the given packet. Every 180# queued packet _must_ have a verdict specified by userspace, either by 181# calling this function, or by calling the nfq_set_verdict_mark() function. 182# NF_DROP - Drop packet 183# NF_ACCEPT - Accept packet 184# NF_STOLEN - Don't continue to process the packet and not deallocate it. 185# NF_QUEUE - Enqueue the packet 186# NF_REPEAT - Handle the same packet 187# NF_STOP - 188# NF_MAX_VERDICT - 189set_verdict = netfilter.nfq_set_verdict 190set_verdict.restype = ctypes.c_int 191set_verdict.argtypes = ctypes.POINTER(NfqQHandler), ctypes.c_uint32, ctypes.c_uint32,\ 192 ctypes.c_uint32, ctypes.c_char_p 193 194# Return the metaheader that wraps the packet. 195get_msg_packet_hdr = netfilter.nfq_get_msg_packet_hdr 196get_msg_packet_hdr.restype = ctypes.POINTER(NfqnlMsgPacketHdr) 197get_msg_packet_hdr.argtypes = ctypes.POINTER(NfqData), 198 199 200# Get interface index 201# Translation from interface index -> interface name: socket.if_indextoname(1) 202 203# uint32_t nfq_get_physindev ( struct nfq_data * nfad ) 204get_physindev = netfilter.nfq_get_physindev 205get_physindev.restype = ctypes.c_uint32 206get_physindev.argtypes = ctypes.POINTER(NfqData), 207 208# uint32_t nfq_get_physoutdev ( struct nfq_data * nfad ) 209get_physoutdev = netfilter.nfq_get_physoutdev 210get_physoutdev.restype = ctypes.c_uint32 211get_physoutdev.argtypes = ctypes.POINTER(NfqData), 212 213 214# uint32_t nfq_get_indev (struct nfq_data *nfad) 215get_indev = netfilter.nfq_get_indev 216get_indev.restype = ctypes.c_uint32 217get_indev.argtypes = ctypes.POINTER(NfqData), 218 219# uint32_t nfq_get_outdev ( struct nfq_data * nfad ) 220get_outdev = netfilter.nfq_get_outdev 221get_outdev.restype = ctypes.c_uint32 222get_outdev.argtypes = ctypes.POINTER(NfqData), 223 224 225# Retrieves the hardware address associated with the given queued packet. 226# struct nfqnl_msg_packet_hw* nfq_get_packet_hw ( struct nfq_data * nfad ) [read] 227# Can be used to retrieve the source MAC address. 228# The destination MAC address is not known until after POSTROUTING and a successful ARP request, 229# so cannot currently be retrieved. (nfqueue documentation) 230get_packet_hw = netfilter.nfq_get_packet_hw 231get_packet_hw.restype = ctypes.POINTER(NfqnlMsgPacketHw) 232get_packet_hw.argtypes = ctypes.POINTER(NfqData), 233 234# Retrieve the payload for a queued packet. 235get_payload = netfilter.nfq_get_payload 236get_payload.restype = ctypes.c_int 237get_payload.argtypes = ctypes.POINTER(NfqData), ctypes.POINTER(ctypes.c_void_p) 238 239 240HANDLER = ctypes.CFUNCTYPE( 241 #(struct NfqQHandler *qh, struct nfgenmsg *nfmsg, struct NfqData *nfa, void *data) 242 None, *(ctypes.POINTER(NfqQHandler), ctypes.c_void_p, ctypes.POINTER(NfqData), ctypes.c_void_p) 243) 244 245 246def get_full_payload(nfa, ptr_packet): 247 len_recv = get_payload(nfa, ctypes.byref(ptr_packet)) 248 data = ctypes.string_at(ptr_packet, len_recv) 249 return len_recv, data 250 251 252class Interceptor(object): 253 """ 254 Packet interceptor. Allows MITM and filtering. 255 Example config for iptables: 256 iptables -I INPUT 1 -p icmp -j NFQUEUE --queue-balance 0:2 257 """ 258 QueueConfig = namedtuple("QueueConfig", 259 ["queue", "queue_id", "nfq_handle", "nfq_socket", "verdictthread", "handler"]) 260 261 def __init__(self, nfqueue_size=2048, rcvbufsiz=2048): 262 """ 263 nfqueue_size -- Sets the size of the queue in kernel. This fixes the maximum number of packets the 264 kernel will store before internally before dropping upcoming packets. 265 rcvbufsiz -- Sets the new size of the socket buffer. Use this setting to increase the socket buffer 266 size if your system is reporting ENOBUFS errors. 267 See: https://www.netfilter.org/projects/libnetfilter_queue/doxygen/ 268 269 WARNING: Set nfqueue_size and rcvbufsiz to None (or lower values) if there are any problems on receiving 270 """ 271 self._netfilterqueue_configs = [] 272 self._is_running = False 273 self._nfqueue_size = nfqueue_size 274 self._rcvbufsiz = rcvbufsiz 275 276 @staticmethod 277 def verdict_trigger_cycler(recv, nfq_handle, obj): 278 try: 279 while obj._is_running: 280 # TODO: exception in outer loop? 281 try: 282 # max IP packet size = 65535 bytes 283 bts = recv(65535) 284 except socket_timeout: 285 continue 286 except OSError as e: 287 # Ignore ENOBUFS errors, we can't handle this anyway 288 # Alternative is to set NETLINK_NO_ENOBUFS socket option 289 if e.errno == errno.ENOBUFS: 290 #logger.debug("Droppin' a packet, consider increasing receive buffer") 291 continue 292 raise e 293 294 handle_packet(nfq_handle, bts, len(bts)) 295 except OSError: 296 # eg "Bad file descriptor": started and nothing read yet 297 #logger.error(ex) 298 pass 299 except Exception as ex: 300 logger.error("Exception while reading: %r", ex) 301 #finally: 302 # logger.debug("verdict_trigger_cycler finished, stopping Interceptor") 303 # obj.stop() 304 305 def _setup_queue(self, queue_id, ctx, verdict_callback): 306 def verdict_callback_ind(queue_handle, nfmsg, nfa, _data): 307 packet_ptr = ctypes.c_void_p(0) 308 309 # logger.debug("verdict cb for queue %d", queue_id) 310 pkg_hdr = get_msg_packet_hdr(nfa) 311 packet_id = ntohl(pkg_hdr.contents.packet_id) 312 linklayer_protoid = htons(pkg_hdr.contents.hw_protocol) 313 314 len_recv, data = get_full_payload(nfa, packet_ptr) 315 316 try: 317 # TODO: avoid exception, check for hw_addrlen? 318 hw_info = get_packet_hw(nfa).contents 319 hw_addrlen = ntohs(hw_info.hw_addrlen) 320 hw_addr = ctypes.string_at(hw_info.hw_addr, size=hw_addrlen) 321 except: 322 # hw address not always present, eg DHCP discover -> offer... 323 hw_addr = None 324 325 if_idx_in = get_indev(nfa) 326 if_idx_out = get_outdev(nfa) 327 328 data_ret, verdict = data, NF_DROP 329 330 try: 331 data_ret, verdict = verdict_callback(hw_addr, linklayer_protoid, data, ctx, if_idx_in, if_idx_out) 332 except Exception as ex: 333 logger.warning("Verdict callback problem, packet will be dropped: %r", ex) 334 335 set_verdict(queue_handle, packet_id, verdict, len(data_ret), ctypes.c_char_p(data_ret)) 336 337 nfq_handle = ll_open_queue() # 2 338 339 # This call is obsolete, Linux kernels from 3.8 onwards ignore it. 340 #unbind_pf(nfq_handle, socket.AF_INET) 341 #bind_pf(nfq_handle, socket.AF_INET) 342 343 c_handler = HANDLER(verdict_callback_ind) 344 queue = create_queue(nfq_handle, queue_id, c_handler, None) # 1 345 346 set_mode(queue, NFQNL_COPY_PACKET, 0xFFFF) 347 348 nf = nfnlh(nfq_handle) 349 fd = nfq_fd(nf) 350 # fd, family, sockettype 351 nfq_socket = socket.fromfd(fd, 0, 0) # 3 352 353 if self._nfqueue_size is not None: 354 ret = set_queue_maxlen(queue, self._nfqueue_size) 355 if ret == -1: 356 logger.warning("Could not set queue_maxlen to %d", self._nfqueue_size) 357 358 if self._rcvbufsiz is not None: 359 ret = nfnl_rcvbufsiz(nf, self._rcvbufsiz) 360 #logger.debug("Update rcvbufsiz: %d", ret) 361 362 # TODO: better solution to check for running state? close socket and raise exception does not work in stop() 363 nfq_socket.settimeout(1) 364 365 # TODO: faster w/ asyncio? 366 thread = threading.Thread( 367 target=Interceptor.verdict_trigger_cycler, 368 args=[nfq_socket.recv, nfq_handle, self] 369 ) 370 371 thread.start() 372 373 qconfig = Interceptor.QueueConfig( 374 queue=queue, queue_id=queue_id, nfq_handle=nfq_handle, nfq_socket=nfq_socket, 375 verdictthread=thread, handler=c_handler 376 ) 377 self._netfilterqueue_configs.append(qconfig) 378 379 def start(self, verdict_callback, queue_ids, ctx=None): 380 """ 381 verdict_callback -- callback with this signature: 382 callback(data, ctx): data, verdict 383 queue_id -- id of the que placed using iptables 384 ctx -- context object given to verdict callback 385 """ 386 if self._is_running: 387 return 388 389 if queue_ids is None: 390 queue_ids = [] 391 392 self._is_running = True 393 394 for queue_id in queue_ids: 395 # Setup queue and start producer threads 396 self._setup_queue(queue_id, ctx, verdict_callback) 397 398 def stop(self): 399 if not self._is_running: 400 return 401 402 # logger.debug("stopping Interceptor") 403 self._is_running = False 404 405 for qconfig in self._netfilterqueue_configs: 406 destroy_queue(qconfig.queue) 407 close_queue(qconfig.nfq_handle) 408 qconfig.nfq_socket.close() 409 # logger.debug("joining verdict thread for queue %d", qconfig.queue_id) 410 qconfig.verdictthread.join() 411 412 self._netfilterqueue_configs.clear() 413