From 7fe1d0c5bc14e67e351f1926f2c59df3d2dbbafe Mon Sep 17 00:00:00 2001 From: dowright Date: Mon, 23 Aug 2021 13:01:24 -0700 Subject: [PATCH] work on new packet --- netfilterqueue/_impl.pxd | 7 +- new_packet.pxd | 74 ++++++++++++++ new_packet.pyx | 214 +++++++++++++++++++++++++++++++++++++++ setup2.py | 13 +++ 4 files changed, 304 insertions(+), 4 deletions(-) create mode 100644 new_packet.pxd create mode 100644 new_packet.pyx create mode 100644 setup2.py diff --git a/netfilterqueue/_impl.pxd b/netfilterqueue/_impl.pxd index 35b6a45..11dca6f 100644 --- a/netfilterqueue/_impl.pxd +++ b/netfilterqueue/_impl.pxd @@ -13,7 +13,7 @@ cdef enum: ENOBUFS = 105 # No buffer space available cdef extern from "netinet/ip.h": - struct iphdr: + struct ip_header: u_int8_t tos u_int16_t tot_len u_int16_t id @@ -174,7 +174,7 @@ cdef class Packet: cdef nfqnl_msg_packet_hdr *_hdr cdef nfqnl_msg_packet_hw *_hw cdef bint _verdict_is_set # True if verdict has been issued, otherwise false - cdef u_int32_t _modified_mark # Mark given to packet + cdef u_int32_t _mark # Mark given to packet cdef bytes _given_payload # New payload of packet, or null # From NFQ packet header: @@ -198,14 +198,13 @@ cdef class Packet: #cdef readonly u_int32_t physoutdev cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa) - cdef void verdict(self, u_int8_t verdict) + cdef void verdict(self, u_int32_t verdict) cpdef get_inint(self, bint name=*) cpdef get_outint(self, bint name=*) cpdef update_mark(self, u_int32_t mark) cpdef Py_ssize_t get_payload_len(self) cpdef double get_timestamp(self) cpdef set_payload(self, bytes payload) - #cpdef get_mark(self) cpdef accept(self) cpdef drop(self) cpdef forward(self, u_int16_t queue_num) diff --git a/new_packet.pxd b/new_packet.pxd new file mode 100644 index 0000000..5083d9b --- /dev/null +++ b/new_packet.pxd @@ -0,0 +1,74 @@ +cdef extern from "sys/types.h": + ctypedef unsigned char u_int8_t + ctypedef unsigned short int u_int16_t + ctypedef unsigned int u_int32_t + +cdef extern from "": + int errno + +# cython define +cdef extern from "netinet/ip.h": + struct iphdr: + u_int8_t tos + u_int16_t tot_len + u_int16_t id + u_int16_t frag_off + u_int8_t ttl + u_int8_t protocol + u_int16_t check + u_int32_t saddr + u_int32_t daddr + +# cython define +cdef extern from "netinet/tcp.h": + struct tcphdr: + u_int16_t th_sport + u_int16_t th_dport + u_int32_t th_seq + u_int32_t th_ack + + u_int8_t th_x2:4 + u_int8_t th_off:4 + + u_int8_t th_flags + + u_int16_t th_win + u_int16_t th_sum + u_int16_t th_urp + +# cython define +cdef extern from "netinet/udp.h": + struct udphdr: + u_int16_t uh_sport + u_int16_t uh_dport + u_int16_t uh_ulen + u_int16_t uh_sum + +cdef struct icmphdr: + u_int8_t type + +# from netinet/in.h: +cdef enum: + IPPROTO_IP = 0 # Dummy protocol for TCP. + IPPROTO_ICMP = 1 # Internet Control Message Protocol. + IPPROTO_TCP = 6 # Transmission Control Protocol. + IPPROTO_UDP = 17 # User Datagram Protocol. + + +cdef class CPacket: + cdef nfq_q_handle *_qh + cdef nfq_data *_nfa + cdef nfqnl_msg_packet_hdr *_hdr + cdef nfqnl_msg_packet_hw *_hw + + cdef u_int16_t __queue_num + cdef bint threaded + + cdef bint _verdict_is_set + cdef u_int32_t _mark + + # Packet details: + cdef Py_ssize_t payload_len + cdef readonly unsigned char *payload + cdef timeval timestamp + cdef u_int8_t hw_addr[8] diff --git a/new_packet.pyx b/new_packet.pyx new file mode 100644 index 0000000..6fba6ad --- /dev/null +++ b/new_packet.pyx @@ -0,0 +1,214 @@ +class CPacket: + '''parent class designed to index/parse full tcp/ip packets (including ethernet). two alternate + constructors are supplied to support nfqueue or raw sockets. + raw socket: + packet = RawPacket.interface(data, address, socket) + nfqueue: + packet = RawPacket.netfilter(nfqueue) + the before_exit method can be overridden to extend the parsing functionality, for example to group + objects in namedtuples or to index application data. + ''' + + def __new__(cls, *args, **kwargs): + if (cls is RawPacket): + raise TypeError('RawPacket can only be used via inheritance.') + + return object.__new__(cls) + + def __cinit__(self): + self._verdict_is_set = False + self._mark = 0 + + self.protocol = PROTO.NOT_SET + + def __str__(self): + cdef iphdr * hdr = < iphdr * > self.payload + protocol = PROTOCOLS.get(hdr.protocol, "Unknown protocol") + + return "%s packet, %s bytes" % (protocol, self.payload_len) + + # NOTE: this will be callback target for nfqueue + cdef netfilter(nfq_q_handle * qh, nfgenmsg * nfmsg, nfq_data * nfa, void * data): + '''alternate constructor. used to start listener/proxy instances using nfqueue bindings.''' + + '''Assign a packet from NFQ to this object. Parse the header and load local values.''' + + self = cls() + self.parse() + + self._qh = qh + self._nfa = nfa + self._hdr = nfq_get_msg_packet_hdr(nfa) + + self.id = ntohl(self._hdr.packet_id) + self.hw_protocol = ntohs(self._hdr.hw_protocol) + self.hook = self._hdr.hook + + self.payload_len = nfq_get_payload(self._nfa, & self.data) + if self.payload_len < 0: + raise OSError("Failed to get payload of packet.") + + # timestamp gets assigned via pointer/struct -> time_val: (t_sec, t_usec). + nfq_get_timestamp(self._nfa, & self.timestamp) + + cdef u_int_32_t self._mark = nfq_get_nfmark(nfa) + + # if (self.continue_condition): + # self._before_exit() + + # TODO: send to module callback here + # with gil: + # callback(self) + + return 0 + + cdef void verdict(self, u_int32_t verdict): + '''Call appropriate set_verdict... function on packet.''' + + if self._verdict_is_set: + raise RuntimeWarning("Verdict already given for this packet.") + + cdef u_int32_t modified_payload_len = 0 + cdef unsigned char * modified_payload = NULL + + # rewriting payload data/len + if self._given_payload: + modified_payload_len = len(self._given_payload) + modified_payload = self._given_payload + + if self._modified_mark: + nfq_set_verdict2( + self._qh, self.id, verdict, self._modified_mark, modified_payload_len, modified_payload + ) + + else: + nfq_set_verdict( + self._qh, self.id, verdict, modified_payload_len, modified_payload + ) + + self._verdict_is_set = True + + cdef def parse(self) with nogil: + '''index tcp/ip packet layers 3 & 4 for use as instance objects. + the before_exit method will be called before returning, which can be used to create + subclass specific objects like namedtuples or application layer data.''' + + cdef iphdr * ip_header = < iphdr * > self.payload + + cdef u_int8_t iphdr_len = iphdr.tos & 15) * 4 + + if (iphdr.protocol == IPPROTO_TCP): + + cdef tcphdr * tcp_header = < tcphdr * > self.payload[iphdr_len:] + + return 0 + + if (iphdr.protocol == IPPROTO_UDP): + cdef tcphdr * icmp_header = < tcphdr * > self.payload[iphdr_len:] + + return 0 + + if (iphdr.protocol == IPPROTO_ICMP): + cdef icmphdr * icmp_header = < icmphdr * > self.payload[iphdr_len:] + + return 0 + + return 1 + + def _before_exit(self): + '''executes before returning from parse call. + May be overridden. + ''' + pass + + @property + def continue_condition(self): + '''controls whether the _before_exit method gets called. must return a boolean. + May be overridden. + ''' + return True + + +cdef class NetfilterQueue: + '''Handle a single numbered queue.''' + + def __cinit__(self, *args, **kwargs): + self.af = kwargs.get('af', PF_INET) + + self.h = nfq_open() + if self.h == NULL: + raise OSError('Failed to open NFQueue.') + + # This does NOT kick out previous running queues + nfq_unbind_pf(self.h, self.af) + + if nfq_bind_pf(self.h, self.af) < 0: + raise OSError('Failed to bind family %s. Are you root?' % self.af) + + def __dealloc__(self): + if self.qh != NULL: + nfq_destroy_queue(self.qh) + + # Don't call nfq_unbind_pf unless you want to disconnect any other + # processes using this libnetfilter_queue on this protocol family! + nfq_close(self.h) + + def bind(self, int queue_num, object user_callback, u_int16_t max_len=DEFAULT_MAX_QUEUELEN, + u_int8_t mode=NFQNL_COPY_PACKET, u_int16_t range=MaxPacketSize, u_int32_t sock_len=SockRcvSize): + '''Create and bind to a new queue.''' + + cdef unsigned int newsiz + + self.user_callback = user_callback + self.qh = nfq_create_queue(self.h, queue_num, < nfq_callback * > global_callback, < void * > self) + if self.qh == NULL: + raise OSError(f'Failed to create queue {queue_num}') + + if range > MaxCopySize: + range = MaxCopySize + + if nfq_set_mode(self.qh, mode, range) < 0: + raise OSError("Failed to set packet copy mode.") + + nfq_set_queue_maxlen(self.qh, max_len) + + newsiz = nfnl_rcvbufsiz(nfq_nfnlh(self.h), sock_len) + if newsiz != sock_len * 2: + raise RuntimeWarning("Socket rcvbuf limit is now %d, requested %d." % (newsiz, sock_len)) + + def unbind(self): + '''Destroy the queue.''' + + if self.qh != NULL: + nfq_destroy_queue(self.qh) + + self.qh = NULL + # See warning about nfq _unbind_pf in __dealloc__ above. + + def get_fd(self): + '''Get the file descriptor of the queue handler.''' + + return nfq_fd(self.h) + + def run(self, bint block=True): + '''Accept packets using recv.''' + + cdef int fd = self.get_fd() + cdef char buf[4096] + cdef int rv + cdef int recv_flags + + recv_flags = 0 + + while True: + with nogil: + rv = recv(fd, buf, sizeof(buf), recv_flags) + + print(rv) + + if (rv >= 0): + nfq_handle_packet(self.h, buf, rv) + + else: + if errno != ENOBUFS: + break diff --git a/setup2.py b/setup2.py new file mode 100644 index 0000000..dbf4d43 --- /dev/null +++ b/setup2.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +from setuptools import setup +from Cython.Build import cythonize +from Cython.Distutils import build_ext + +cmd = {'build_ext': build_ext} +ext = Extension( + 'dnx_nfqueue', sources=['new_packet.pyx'], libraries=['netfilter_queue']) + +setup( + name='DNX-NFQUEUE', cmdclass=cmd, ext_modules=cythonize(ext, language_level='3') +)