diff --git a/new_packet.pxd b/dnx-netfilterqueue.pxd similarity index 51% rename from new_packet.pxd rename to dnx-netfilterqueue.pxd index 4d47456..11dca6f 100644 --- a/new_packet.pxd +++ b/dnx-netfilterqueue.pxd @@ -12,59 +12,58 @@ cdef enum: EWOULDBLOCK = EAGAIN ENOBUFS = 105 # No buffer space available -# cython define -cdef struct iphdr: - u_int8_t ver_ihl - u_int8_t tos +cdef extern from "netinet/ip.h": + struct ip_header: + 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_int8_t ttl + u_int8_t protocol u_int16_t check u_int32_t saddr u_int32_t daddr -# cython define -cdef 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_off - - u_int8_t th_flags - u_int16_t th_win - u_int16_t th_sum - u_int16_t th_urp - -# cython define -cdef 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: +# Dummy defines from netinet/in.h: cdef enum: IPPROTO_IP = 0 # Dummy protocol for TCP. + IPPROTO_HOPOPTS = 0 # IPv6 Hop-by-Hop options. IPPROTO_ICMP = 1 # Internet Control Message Protocol. + IPPROTO_IGMP = 2 # Internet Group Management Protocol. */ + IPPROTO_IPIP = 4 # IPIP tunnels (older KA9Q tunnels use 94). IPPROTO_TCP = 6 # Transmission Control Protocol. + IPPROTO_EGP = 8 # Exterior Gateway Protocol. + IPPROTO_PUP = 12 # PUP protocol. IPPROTO_UDP = 17 # User Datagram Protocol. + IPPROTO_IDP = 22 # XNS IDP protocol. + IPPROTO_TP = 29 # SO Transport Protocol Class 4. + IPPROTO_IPV6 = 41 # IPv6 header. + IPPROTO_ROUTING = 43 # IPv6 routing header. + IPPROTO_FRAGMENT = 44 # IPv6 fragmentation header. + IPPROTO_RSVP = 46 # Reservation Protocol. + IPPROTO_GRE = 47 # General Routing Encapsulation. + IPPROTO_ESP = 50 # encapsulating security payload. + IPPROTO_AH = 51 # authentication header. + IPPROTO_ICMPV6 = 58 # ICMPv6. + IPPROTO_NONE = 59 # IPv6 no next header. + IPPROTO_DSTOPTS = 60 # IPv6 destination options. + IPPROTO_MTP = 92 # Multicast Transport Protocol. + IPPROTO_ENCAP = 98 # Encapsulation Header. + IPPROTO_PIM = 103 # Protocol Independent Multicast. + IPPROTO_COMP = 108 # Compression Header Protocol. + IPPROTO_SCTP = 132 # Stream Control Transmission Protocol. + IPPROTO_RAW = 255 # Raw IP packets. + IPPROTO_MAX cdef extern from "Python.h": object PyBytes_FromStringAndSize(char *s, Py_ssize_t len) + object PyString_FromStringAndSize(char *s, Py_ssize_t len) cdef extern from "sys/time.h": ctypedef long time_t struct timeval: time_t tv_sec time_t tv_usec - struct timezone: pass @@ -83,7 +82,6 @@ cdef extern from "libnfnetlink/linux_nfnetlink.h": cdef extern from "libnfnetlink/libnfnetlink.h": struct nfnl_handle: pass - unsigned int nfnl_rcvbufsiz(nfnl_handle *h, unsigned int size) cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": @@ -91,7 +89,6 @@ cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": NFQNL_COPY_NONE NFQNL_COPY_META NFQNL_COPY_PACKET - struct nfqnl_msg_packet_hdr: u_int32_t packet_id u_int16_t hw_protocol @@ -100,41 +97,58 @@ cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": cdef extern from "libnetfilter_queue/libnetfilter_queue.h": struct nfq_handle: pass - struct nfq_q_handle: pass - struct nfq_data: pass - struct nfqnl_msg_packet_hw: u_int8_t hw_addr[8] nfq_handle *nfq_open() int nfq_close(nfq_handle *h) + int nfq_bind_pf(nfq_handle *h, u_int16_t pf) int nfq_unbind_pf(nfq_handle *h, u_int16_t pf) - ctypedef int *nfq_callback(nfq_q_handle *gh, nfgenmsg *nfmsg, nfq_data *nfad, void *data) - nfq_q_handle *nfq_create_queue(nfq_handle *h, u_int16_t num, nfq_callback *cb, void *data) + ctypedef int *nfq_callback(nfq_q_handle *gh, nfgenmsg *nfmsg, + nfq_data *nfad, void *data) + nfq_q_handle *nfq_create_queue(nfq_handle *h, + u_int16_t num, + nfq_callback *cb, + void *data) int nfq_destroy_queue(nfq_q_handle *qh) - int nfq_handle_packet(nfq_handle *h, char *buf, int len) - int nfq_set_mode(nfq_q_handle *qh, u_int8_t mode, unsigned int len) - q_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen) - int nfq_set_verdict(nfq_q_handle *qh, u_int32_t id, u_int32_t verdict, u_int32_t data_len, unsigned char *buf) nogil - int nfq_set_verdict2(nfq_q_handle *qh, u_int32_t id, u_int32_t verdict, u_int32_t mark, - u_int32_t datalen, unsigned char *buf) nogil + int nfq_handle_packet(nfq_handle *h, char *buf, int len) + + int nfq_set_mode(nfq_q_handle *qh, + u_int8_t mode, unsigned int len) + + q_set_queue_maxlen(nfq_q_handle *qh, + u_int32_t queuelen) + + int nfq_set_verdict(nfq_q_handle *qh, + u_int32_t id, + u_int32_t verdict, + u_int32_t data_len, + unsigned char *buf) nogil + + int nfq_set_verdict2(nfq_q_handle *qh, + u_int32_t id, + u_int32_t verdict, + u_int32_t mark, + u_int32_t datalen, + unsigned char *buf) nogil int nfq_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen) + int nfq_fd(nfq_handle *h) - nfqnl_msg_packet_hdr *nfq_get_msg_packet_hdr(nfq_data *nfad) nogil - int nfq_get_payload(nfq_data *nfad, unsigned char **data) nogil - int nfq_get_timestamp(nfq_data *nfad, timeval *tv) nogil + nfqnl_msg_packet_hdr *nfq_get_msg_packet_hdr(nfq_data *nfad) + int nfq_get_payload(nfq_data *nfad, unsigned char **data) + int nfq_get_timestamp(nfq_data *nfad, timeval *tv) nfqnl_msg_packet_hw *nfq_get_packet_hw(nfq_data *nfad) - int nfq_get_nfmark (nfq_data *nfad) nogil + int nfq_get_nfmark (nfq_data *nfad) u_int8_t nfq_get_indev(nfq_data *nfad) u_int8_t nfq_get_outdev(nfq_data *nfad) nfnl_handle *nfq_nfnlh(nfq_handle *h) - + # Dummy defines from linux/socket.h: cdef enum: # Protocol families, same as address families. PF_INET = 2 @@ -154,44 +168,52 @@ cdef enum: NF_STOP NF_MAX_VERDICT = NF_STOP - -cdef class CPacket: +cdef class Packet: cdef nfq_q_handle *_qh cdef nfq_data *_nfa 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 _mark # Mark given to packet + cdef bytes _given_payload # New payload of packet, or null - cdef u_int32_t id - - # protocol headers - cdef iphdr *ip_header - cdef tcphdr *tcp_header - cdef udphdr *udp_header - cdef icmphdr *icmp_header - - cdef u_int8_t cmbhdr_len - - cdef bint _verdict_is_set - cdef u_int32_t _mark + # From NFQ packet header: + cdef readonly u_int32_t id + cdef readonly u_int16_t hw_protocol + cdef readonly u_int8_t hook + cdef readonly u_int32_t mark # Packet details: - cdef Py_ssize_t data_len - cdef readonly unsigned char *data + cdef Py_ssize_t payload_len cdef readonly unsigned char *payload cdef timeval timestamp + cdef u_int8_t hw_addr[8] - cdef u_int32_t parse(self, nfq_q_handle *qh, nfq_data *nfa) nogil - cdef void _parse(self) nogil + # TODO: implement these | likely not using in this manner. + #cdef u_int8_t hw_addr[8] # A eui64-formatted address? + #cdef readonly u_int32_t nfmark + #cdef readonly u_int32_t indev + #cdef readonly u_int32_t physindev + #cdef readonly u_int32_t outdev + #cdef readonly u_int32_t physoutdev + + cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa) cdef void verdict(self, u_int32_t verdict) - cdef double get_timestamp(self) + 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 accept(self) cpdef drop(self) cpdef forward(self, u_int16_t queue_num) cpdef repeat(self) cdef class NetfilterQueue: + cdef object user_callback # User callback cdef nfq_handle *h # Handle to NFQueue library cdef nfq_q_handle *qh # A handle to the queue cdef u_int16_t af # Address family - cdef packet_copy_size # Amount of packet metadata + data copied to buffer \ No newline at end of file + cdef packet_copy_size # Amount of packet metadata + data copied to buffer + diff --git a/dnx-netfilterqueue.pyx b/dnx-netfilterqueue.pyx new file mode 100644 index 0000000..065b1ee --- /dev/null +++ b/dnx-netfilterqueue.pyx @@ -0,0 +1,481 @@ +#!/usr/bin/env python3 + +''' +Bind to a Linux netfilter queue. Send packets to a user-specified callback +function. + +Copyright: (c) 2011, Kerkhoff Technologies Inc. +License: MIT; see SOURCE-LICENSE.txt +''' + +''' +Expanded features + general refactor to be used with DNXFIREWALL. + - DOWRIGHT @ Wright Network Solutions, LLC. +''' + +import socket + +# Constants for module users +cdef int COPY_NONE = 0 +cdef int COPY_META = 1 +cdef int COPY_PACKET = 2 + +cdef u_int16_t DEFAULT_MAX_QUEUELEN = 1024 +cdef u_int16_t MaxPacketSize = 0xFFFF + +# buffer size - metadata size +cdef u_int16_t MaxCopySize = 4096 - 80 + +# Socket queue should hold max number of packets of copy size bytes +# formula: DEF_MAX_QUEUELEN * (MaxCopySize+SockOverhead) / 2 +cdef u_int32_t SockRcvSize = 1024 * 4796 // 2 + +cdef int global_callback(nfq_q_handle * qh, nfgenmsg * nfmsg, nfq_data * nfa, void * data) with gil: + '''Create a Packet and pass it to appropriate callback.''' + + cdef NetfilterQueue nfqueue = < NetfilterQueue > data + cdef object user_callback = < object > nfqueue.user_callback + + packet = Packet() + packet.set_nfq_data(qh, nfa) + + user_callback(packet) + + return 1 + +cdef class Packet: + '''A packet received from NetfilterQueue.''' + + def __cinit__(self): + self._verdict_is_set = False + self._mark = 0 + self._given_payload = None + + def __str__(self): + cdef ip_header * hdr = < ip_header * > self.payload + protocol = PROTOCOLS.get(hdr.protocol, "Unknown protocol") + + return "%s packet, %s bytes" % (protocol, self.payload_len) + + cdef set_nfq_data(self, nfq_q_handle * qh, nfq_data * nfa): + '''Assign a packet from NFQ to this object. Parse the header and load local values.''' + + 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.payload) + if self.payload_len < 0: + raise OSError("Failed to get payload of packet.") + + self._payload + + # timestamp gets assigned via pointer/struct -> time_val: (t_sec, t_usec). + nfq_get_timestamp(self._nfa, &self.timestamp) + + cdef u_int32_t mark = nfq_get_nfmark(nfa) + + 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 + + cpdef get_inint(self, bint name=False): + '''Returns index of inbound interface of packet. If the packet sourced from localhost or the input + interface is not known, 0 will be returned. + + if name=True, socket.if_indextoname() will be returned. + ''' + + cdef object in_interface_name + + in_interface = nfq_get_indev(self._nfa) + if not name: + return in_interface + + try: + in_interface_name = socket.if_indextoname(in_interface) + except OSError: + in_interface_name = 'unknown' + + return in_interface_name + + # NOTE: keeping these funtions separate instead of making an argument option to adjust which interface to return. + # this will keep it explicit for which interface is returning to minimize chance of confusion/bugs. + cpdef get_outint(self, bint name=False): + '''Returns index of outbound interface of packet. If the packet is destined for localhost or the output + interface is not yet known, 0 will be returned. + + if name=True, socket.if_indextoname() will be returned. + ''' + + cdef object out_interface_name + + out_interface = nfq_get_outdev(self._nfa) + if not name: + return out_interface + + try: + out_interface_name = socket.if_indextoname(out_interface) + except OSError: + out_interface_name = 'unknown' + + return out_interface_name + + def get_mark(self): + '''Return mark set at first receipt of packet.''' + + return self._mark + + cpdef update_mark(self, u_int32_t mark): + '''Modifies the running mark of the packet. This will not override the initial mark received, but any + subsequent update will override the previous.''' + + self._modified_mark = mark + + def get_hw(self): + '''Return the source mac address of the packet.''' + + cdef object mac_addr + + self._hw = nfq_get_packet_hw(self._nfa) + if self._hw == NULL: + # nfq_get_packet_hw doesn't work on OUTPUT and PREROUTING chains + # NOTE: making this a quick fail scenario since this would likely cause problems later in the packet + # parsing process and forcing error handling will ensure it is dealt with [properly]. + raise OSError('MAC address not available in OUTPUT and PREROUTING chains') + + # NOTE: can this not just be directly referenced below? + self.hw_addr = self._hw.hw_addr + + mac_addr = PyBytes_FromStringAndSize( < char * > self.hw_addr, 8) + + return mac_addr + + def get_payload(self): + '''Return payload as Python string.''' + + cdef object payload + + payload = self.payload[:self.payload_len] + + return payload + + cpdef set_payload(self, bytes payload): + '''Set the new payload of this packet.''' + + self._given_payload = payload + + cpdef Py_ssize_t get_payload_len(self): + + return self.payload_len + + cpdef double get_timestamp(self): + + return self.timestamp.tv_sec + (self.timestamp.tv_usec / 1000000.0) + + cpdef accept(self): + '''Accept the packet.''' + + self.verdict(NF_ACCEPT) + + cpdef drop(self): + '''Drop the packet.''' + + self.verdict(NF_DROP) + + cpdef forward(self, u_int16_t queue_num): + '''Send the packet to a different queue.''' + + cdef u_int32_t forward_to_queue + + forward_to_queue = queue_num << 16 | NF_QUEUE + + self.verdict(forward_to_queue) + + cpdef repeat(self): + '''Repeat the packet.''' + + self.verdict(NF_REPEAT) + + +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 + + def run_socket(self, s): + '''Accept packets using socket.recv so that, for example, gevent can monkeypatch it.''' + + while True: + try: + buf = s.recv(4096) + except socket.error as e: + err = e.args[0] + if err == ENOBUFS: + continue + + elif err == EAGAIN or err == EWOULDBLOCK: + # This should only happen with a non-blocking socket, and the + # app should call run_socket again when more data is available. + break + + else: + # This is bad. Let the caller handle it. + raise e + + else: + rv = len(buf) + if rv >= 0: + nfq_handle_packet(self.h, buf, rv) + else: + break + +PROTOCOLS = { + 0: "HOPOPT", + 1: "ICMP", + 2: "IGMP", + 3: "GGP", + 4: "IP", + 5: "ST", + 6: "TCP", + 7: "CBT", + 8: "EGP", + 9: "IGP", + 10: "BBN-RCC-MON", + 11: "NVP-II", + 12: "PUP", + 13: "ARGUS", + 14: "EMCON", + 15: "XNET", + 16: "CHAOS", + 17: "UDP", + 18: "MUX", + 19: "DCN-MEAS", + 20: "HMP", + 21: "PRM", + 22: "XNS-IDP", + 23: "TRUNK-1", + 24: "TRUNK-2", + 25: "LEAF-1", + 26: "LEAF-2", + 27: "RDP", + 28: "IRTP", + 29: "ISO-TP4", + 30: "NETBLT", + 31: "MFE-NSP", + 32: "MERIT-INP", + 33: "DCCP", + 34: "3PC", + 35: "IDPR", + 36: "XTP", + 37: "DDP", + 38: "IDPR-CMTP", + 39: "TP++", + 40: "IL", + 41: "IPv6", + 42: "SDRP", + 43: "IPv6-Route", + 44: "IPv6-Frag", + 45: "IDRP", + 46: "RSVP", + 47: "GRE", + 48: "DSR", + 49: "BNA", + 50: "ESP", + 51: "AH", + 52: "I-NLSP", + 53: "SWIPE", + 54: "NARP", + 55: "MOBILE", + 56: "TLSP", + 57: "SKIP", + 58: "IPv6-ICMP", + 59: "IPv6-NoNxt", + 60: "IPv6-Opts", + 61: "any host internal protocol", + 62: "CFTP", + 63: "any local network", + 64: "SAT-EXPAK", + 65: "KRYPTOLAN", + 66: "RVD", + 67: "IPPC", + 68: "any distributed file system", + 69: "SAT-MON", + 70: "VISA", + 71: "IPCV", + 72: "CPNX", + 73: "CPHB", + 74: "WSN", + 75: "PVP", + 76: "BR-SAT-MON", + 77: "SUN-ND", + 78: "WB-MON", + 79: "WB-EXPAK", + 80: "ISO-IP", + 81: "VMTP", + 82: "SECURE-VMTP", + 83: "VINES", + 84: "TTP", + 85: "NSFNET-IGP", + 86: "DGP", + 87: "TCF", + 88: "EIGRP", + 89: "OSPFIGP", + 90: "Sprite-RPC", + 91: "LARP", + 92: "MTP", + 93: "AX.25", + 94: "IPIP", + 95: "MICP", + 96: "SCC-SP", + 97: "ETHERIP", + 98: "ENCAP", + 99: "any private encryption scheme", + 100: "GMTP", + 101: "IFMP", + 102: "PNNI", + 103: "PIM", + 104: "ARIS", + 105: "SCPS", + 106: "QNX", + 107: "A/N", + 108: "IPComp", + 109: "SNP", + 110: "Compaq-Peer", + 111: "IPX-in-IP", + 112: "VRRP", + 113: "PGM", + 114: "any 0-hop protocol", + 115: "L2TP", + 116: "DDX", + 117: "IATP", + 118: "STP", + 119: "SRP", + 120: "UTI", + 121: "SMP", + 122: "SM", + 123: "PTP", + 124: "ISIS", + 125: "FIRE", + 126: "CRTP", + 127: "CRUDP", + 128: "SSCOPMCE", + 129: "IPLT", + 130: "SPS", + 131: "PIPE", + 132: "SCTP", + 133: "FC", + 134: "RSVP-E2E-IGNORE", + 135: "Mobility", + 136: "UDPLite", + 137: "MPLS-in-IP", + 138: "manet", + 139: "HIP", + 140: "Shim6", + 255: "Reserved", +} diff --git a/netfilterqueue/_impl.pxd b/netfilterqueue/_impl.pxd index 11dca6f..4d47456 100644 --- a/netfilterqueue/_impl.pxd +++ b/netfilterqueue/_impl.pxd @@ -12,58 +12,59 @@ cdef enum: EWOULDBLOCK = EAGAIN ENOBUFS = 105 # No buffer space available -cdef extern from "netinet/ip.h": - struct ip_header: - u_int8_t tos +# cython define +cdef struct iphdr: + u_int8_t ver_ihl + 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_int8_t ttl + u_int8_t protocol u_int16_t check u_int32_t saddr u_int32_t daddr -# Dummy defines from netinet/in.h: +# cython define +cdef 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_off + + u_int8_t th_flags + u_int16_t th_win + u_int16_t th_sum + u_int16_t th_urp + +# cython define +cdef 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_HOPOPTS = 0 # IPv6 Hop-by-Hop options. IPPROTO_ICMP = 1 # Internet Control Message Protocol. - IPPROTO_IGMP = 2 # Internet Group Management Protocol. */ - IPPROTO_IPIP = 4 # IPIP tunnels (older KA9Q tunnels use 94). IPPROTO_TCP = 6 # Transmission Control Protocol. - IPPROTO_EGP = 8 # Exterior Gateway Protocol. - IPPROTO_PUP = 12 # PUP protocol. IPPROTO_UDP = 17 # User Datagram Protocol. - IPPROTO_IDP = 22 # XNS IDP protocol. - IPPROTO_TP = 29 # SO Transport Protocol Class 4. - IPPROTO_IPV6 = 41 # IPv6 header. - IPPROTO_ROUTING = 43 # IPv6 routing header. - IPPROTO_FRAGMENT = 44 # IPv6 fragmentation header. - IPPROTO_RSVP = 46 # Reservation Protocol. - IPPROTO_GRE = 47 # General Routing Encapsulation. - IPPROTO_ESP = 50 # encapsulating security payload. - IPPROTO_AH = 51 # authentication header. - IPPROTO_ICMPV6 = 58 # ICMPv6. - IPPROTO_NONE = 59 # IPv6 no next header. - IPPROTO_DSTOPTS = 60 # IPv6 destination options. - IPPROTO_MTP = 92 # Multicast Transport Protocol. - IPPROTO_ENCAP = 98 # Encapsulation Header. - IPPROTO_PIM = 103 # Protocol Independent Multicast. - IPPROTO_COMP = 108 # Compression Header Protocol. - IPPROTO_SCTP = 132 # Stream Control Transmission Protocol. - IPPROTO_RAW = 255 # Raw IP packets. - IPPROTO_MAX cdef extern from "Python.h": object PyBytes_FromStringAndSize(char *s, Py_ssize_t len) - object PyString_FromStringAndSize(char *s, Py_ssize_t len) cdef extern from "sys/time.h": ctypedef long time_t struct timeval: time_t tv_sec time_t tv_usec + struct timezone: pass @@ -82,6 +83,7 @@ cdef extern from "libnfnetlink/linux_nfnetlink.h": cdef extern from "libnfnetlink/libnfnetlink.h": struct nfnl_handle: pass + unsigned int nfnl_rcvbufsiz(nfnl_handle *h, unsigned int size) cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": @@ -89,6 +91,7 @@ cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": NFQNL_COPY_NONE NFQNL_COPY_META NFQNL_COPY_PACKET + struct nfqnl_msg_packet_hdr: u_int32_t packet_id u_int16_t hw_protocol @@ -97,58 +100,41 @@ cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h": cdef extern from "libnetfilter_queue/libnetfilter_queue.h": struct nfq_handle: pass + struct nfq_q_handle: pass + struct nfq_data: pass + struct nfqnl_msg_packet_hw: u_int8_t hw_addr[8] nfq_handle *nfq_open() int nfq_close(nfq_handle *h) - int nfq_bind_pf(nfq_handle *h, u_int16_t pf) int nfq_unbind_pf(nfq_handle *h, u_int16_t pf) - ctypedef int *nfq_callback(nfq_q_handle *gh, nfgenmsg *nfmsg, - nfq_data *nfad, void *data) - nfq_q_handle *nfq_create_queue(nfq_handle *h, - u_int16_t num, - nfq_callback *cb, - void *data) + ctypedef int *nfq_callback(nfq_q_handle *gh, nfgenmsg *nfmsg, nfq_data *nfad, void *data) + nfq_q_handle *nfq_create_queue(nfq_handle *h, u_int16_t num, nfq_callback *cb, void *data) int nfq_destroy_queue(nfq_q_handle *qh) - int nfq_handle_packet(nfq_handle *h, char *buf, int len) + int nfq_set_mode(nfq_q_handle *qh, u_int8_t mode, unsigned int len) + q_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen) + int nfq_set_verdict(nfq_q_handle *qh, u_int32_t id, u_int32_t verdict, u_int32_t data_len, unsigned char *buf) nogil + int nfq_set_verdict2(nfq_q_handle *qh, u_int32_t id, u_int32_t verdict, u_int32_t mark, + u_int32_t datalen, unsigned char *buf) nogil - int nfq_set_mode(nfq_q_handle *qh, - u_int8_t mode, unsigned int len) - - q_set_queue_maxlen(nfq_q_handle *qh, - u_int32_t queuelen) - - int nfq_set_verdict(nfq_q_handle *qh, - u_int32_t id, - u_int32_t verdict, - u_int32_t data_len, - unsigned char *buf) nogil - - int nfq_set_verdict2(nfq_q_handle *qh, - u_int32_t id, - u_int32_t verdict, - u_int32_t mark, - u_int32_t datalen, - unsigned char *buf) nogil int nfq_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen) - int nfq_fd(nfq_handle *h) - nfqnl_msg_packet_hdr *nfq_get_msg_packet_hdr(nfq_data *nfad) - int nfq_get_payload(nfq_data *nfad, unsigned char **data) - int nfq_get_timestamp(nfq_data *nfad, timeval *tv) + nfqnl_msg_packet_hdr *nfq_get_msg_packet_hdr(nfq_data *nfad) nogil + int nfq_get_payload(nfq_data *nfad, unsigned char **data) nogil + int nfq_get_timestamp(nfq_data *nfad, timeval *tv) nogil nfqnl_msg_packet_hw *nfq_get_packet_hw(nfq_data *nfad) - int nfq_get_nfmark (nfq_data *nfad) + int nfq_get_nfmark (nfq_data *nfad) nogil u_int8_t nfq_get_indev(nfq_data *nfad) u_int8_t nfq_get_outdev(nfq_data *nfad) nfnl_handle *nfq_nfnlh(nfq_handle *h) - + # Dummy defines from linux/socket.h: cdef enum: # Protocol families, same as address families. PF_INET = 2 @@ -168,52 +154,44 @@ cdef enum: NF_STOP NF_MAX_VERDICT = NF_STOP -cdef class Packet: + +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 bint _verdict_is_set # True if verdict has been issued, otherwise false - cdef u_int32_t _mark # Mark given to packet - cdef bytes _given_payload # New payload of packet, or null - # From NFQ packet header: - cdef readonly u_int32_t id - cdef readonly u_int16_t hw_protocol - cdef readonly u_int8_t hook - cdef readonly u_int32_t mark + cdef u_int32_t id + + # protocol headers + cdef iphdr *ip_header + cdef tcphdr *tcp_header + cdef udphdr *udp_header + cdef icmphdr *icmp_header + + cdef u_int8_t cmbhdr_len + + cdef bint _verdict_is_set + cdef u_int32_t _mark # Packet details: - cdef Py_ssize_t payload_len + cdef Py_ssize_t data_len + cdef readonly unsigned char *data cdef readonly unsigned char *payload cdef timeval timestamp - cdef u_int8_t hw_addr[8] - # TODO: implement these | likely not using in this manner. - #cdef u_int8_t hw_addr[8] # A eui64-formatted address? - #cdef readonly u_int32_t nfmark - #cdef readonly u_int32_t indev - #cdef readonly u_int32_t physindev - #cdef readonly u_int32_t outdev - #cdef readonly u_int32_t physoutdev - - cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa) + cdef u_int32_t parse(self, nfq_q_handle *qh, nfq_data *nfa) nogil + cdef void _parse(self) nogil cdef void verdict(self, u_int32_t verdict) - cpdef get_inint(self, bint name=*) - cpdef get_outint(self, bint name=*) + cdef double get_timestamp(self) 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 accept(self) cpdef drop(self) cpdef forward(self, u_int16_t queue_num) cpdef repeat(self) cdef class NetfilterQueue: - cdef object user_callback # User callback cdef nfq_handle *h # Handle to NFQueue library cdef nfq_q_handle *qh # A handle to the queue cdef u_int16_t af # Address family - cdef packet_copy_size # Amount of packet metadata + data copied to buffer - + cdef packet_copy_size # Amount of packet metadata + data copied to buffer \ No newline at end of file diff --git a/new_packet.pyx b/new_packet.pyx deleted file mode 100644 index f0b3aab..0000000 --- a/new_packet.pyx +++ /dev/null @@ -1,411 +0,0 @@ -#!/usr/bin/env python3 - -import socket - -from libc.stdio cimport printf - -# Constants for module users -cdef int COPY_NONE = 0 -cdef int COPY_META = 1 -cdef int COPY_PACKET = 2 - -cdef u_int16_t DEFAULT_MAX_QUEUELEN = 1024 -cdef u_int16_t MaxPacketSize = 0xFFFF - -# buffer size - metadata size -cdef u_int16_t MaxCopySize = 4096 - 80 - -# Socket queue should hold max number of packets of copy size bytes -# formula: DEF_MAX_QUEUELEN * (MaxCopySize+SockOverhead) / 2 -cdef u_int32_t SockRcvSize = 1024 * 4796 // 2 - -cdef object user_callback -def set_user_callback(ref): - '''Set required reference which will be called after packet data is parsed into C structs.''' - global user_callback - - user_callback = ref - -cdef int nf_callback(nfq_q_handle *qh, nfgenmsg *nfmsg, nfq_data *nfa, void *data) with gil: - - cdef u_int32_t mark - - packet = CPacket() - # with nogil: - mark = packet.parse(qh, nfa) - - user_callback(packet, mark) - - return 1 - - -cdef class CPacket: - - def __cinit__(self): - self._verdict_is_set = False - self._mark = 0 - - # NOTE: this will be callback target for nfqueue - cdef u_int32_t parse(self, nfq_q_handle *qh, nfq_data *nfa) nogil: - - self._qh = qh - self._nfa = nfa - - self._hdr = nfq_get_msg_packet_hdr(nfa) - self.id = ntohl(self._hdr.packet_id) - # NOTE: these are not needed at this moment. - # self.hw_protocol = ntohs(hdr.hw_protocol) - # self.hook = hdr.hook - - self.data_len = nfq_get_payload(self._nfa, &self.data) - # TODO: figure this out. cant use no gil if its here. - # 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) - - self._mark = nfq_get_nfmark(nfa) - - # splitting packet by tcp/ip layers - self._parse() - - return self._mark - - # if (self.continue_condition): - # self._before_exit() - - cdef void _parse(self) nogil: - - self.ip_header = self.data - - cdef u_int8_t iphdr_len - - iphdr_len = (self.ip_header.ver_ihl & 15) * 4 - - # NOTE: tshoot print - printf('ip header length=%f\n', iphdr_len) - - cdef u_int8_t tcphdr_len - cdef u_int8_t protohdr_len - - cdef void *data = &self.data[iphdr_len] - # printf('data=%p, self.data=%p\n', &data, &self.data) - # cdef ptrdiff_t hdrptr = data - self.data - - if (self.ip_header.protocol == IPPROTO_TCP): - printf('TCP=%s\n', self.data_len) - - self.tcp_header = &data - - tcphdr_len = (self.tcp_header.th_off >> 4) & 15 - protohdr_len = tcphdr_len * 4 - - # NOTE: tshoot print - printf('TCP SPORT=%f\n', self.tcp_header.th_sport) - - # self.cmbhdr_len = iphdr_len + tcphdr_len - - elif (self.ip_header.protocol == IPPROTO_UDP): - - self.udp_header = &data - - protohdr_len = iphdr_len + 8 - - # self.cmbhdr_len = iphdr_len + 8 - - elif (self.ip_header.protocol == IPPROTO_ICMP): - - self.icmp_header = &data - - protohdr_len = iphdr_len + 4 - - # self.cmbhdr_len = iphdr_len + 4 - - else: - printf('UNKNOWN PROTOCOL=%f\n', self.ip_header.protocol) - - self.cmbhdr_len = protohdr_len + 20 - - self.payload = &self.data[iphdr_len+protohdr_len] - - printf('CMBHDR LEN=%f\n', self.cmbhdr_len) - printf('DATA LEN=%f\n', self.data_len) - - cdef void verdict(self, u_int32_t verdict): - '''Call appropriate set_verdict function on packet.''' - - # TODO: figure out what to do about this. maybe just printf instead? - if self._verdict_is_set: - raise RuntimeWarning('Verdict already given for this packet.') - - if self._mark: - nfq_set_verdict2( - self._qh, self.id, verdict, self._mark, self.data_len, self.data - ) - - else: - nfq_set_verdict( - self._qh, self.id, verdict, self.data_len, self.data - ) - - self._verdict_is_set = True - - cdef double get_timestamp(self): - - return self.timestamp.tv_sec + (self.timestamp.tv_usec / 1000000.0) - - cpdef update_mark(self, u_int32_t mark): - '''Modifies the running mark of the packet.''' - - self._mark = mark - - cpdef accept(self): - '''Accept the packet.''' - - self.verdict(NF_ACCEPT) - - cpdef drop(self): - '''Drop the packet.''' - - self.verdict(NF_DROP) - - cpdef forward(self, u_int16_t queue_num): - '''Send the packet to a different queue.''' - - cdef u_int32_t forward_to_queue - - forward_to_queue = queue_num << 16 | NF_QUEUE - - self.verdict(forward_to_queue) - - cpdef repeat(self): - '''Repeat the packet.''' - - self.verdict(NF_REPEAT) - - def get_inint_name(self): - - # cdef object *int_name - # - # nfq_get_indev_name(self) - - pass - - def get_outint_name(self): - - # cdef object *int_name - # - # nfq_get_outdev_name(self) - - pass - - def get_hw(self): - '''Return hardware information of the packet. - - hw_info = ( - in_interface, out_interface, mac_addr, self.get_timestamp() - ) - ''' - - cdef object mac_addr - cdef tuple hw_info - cdef int in_interface - cdef int out_interface - - in_interface = nfq_get_indev(self._nfa) - out_interface = nfq_get_outdev(self._nfa) - - self._hw = nfq_get_packet_hw(self._nfa) - if self._hw == NULL: - print(f'HW ERROR: {self._hw.hw_addr}') - # nfq_get_packet_hw doesn't work on OUTPUT and PREROUTING chains - # NOTE: making this a quick fail scenario since this would likely cause problems later in the packet - # parsing process and forcing error handling will ensure it is dealt with [properly]. - raise OSError('MAC address not available in OUTPUT and PREROUTING chains') - - cdef u_int8_t[8] hw_addr = self._hw.hw_addr - - # NOTE: this is 8 bytes in source and lib_netfilter_queue, but unsure why since mac addresses are only 6 - # bytes. the last two bytes may be padding, but either way removing here so it will not need to be done - # on the python side. - mac_addr = PyBytes_FromStringAndSize(hw_addr, 6) - - hw_info = ( - in_interface, - out_interface, - mac_addr, - self.get_timestamp(), - ) - - return hw_info - - def get_raw_packet(self): - '''Return layer 3-7 of packet data.''' - - return self.data[:self.data_len] - - def get_ip_header_raw(self): - return self.data[:20] - - def get_proto_header_raw(self): - return self.data[20:self.cmbhdr_len] - - def get_ip_header(self): - '''Return layer3 of packet data as a tuple converted directly from C struct.''' - - cdef tuple ip_header - - ip_header = ( - self.ip_header.ver_ihl, - self.ip_header.tos, - ntohs(self.ip_header.tot_len), - ntohs(self.ip_header.id), - ntohs(self.ip_header.frag_off), - self.ip_header.ttl, - self.ip_header.protocol, - ntohs(self.ip_header.check), - ntohl(self.ip_header.saddr), - ntohl(self.ip_header.daddr), - ) - - return ip_header - - def get_proto_header(self): - '''Return layer4 of packet data as a tuple converted directly from C struct.''' - - cdef tuple proto_header - - if (self.ip_header.protocol == IPPROTO_TCP): - - proto_header = ( - ntohs(self.tcp_header.th_sport), - ntohs(self.tcp_header.th_dport), - ntohl(self.tcp_header.th_seq), - ntohl(self.tcp_header.th_ack), - - self.tcp_header.th_off, - - self.tcp_header.th_flags, - ntohs(self.tcp_header.th_win), - ntohs(self.tcp_header.th_sum), - ntohs(self.tcp_header.th_urp), - ) - - elif (self.ip_header.protocol == IPPROTO_UDP): - - proto_header = ( - ntohs(self.udp_header.uh_sport), - ntohs(self.udp_header.uh_dport), - ntohs(self.udp_header.uh_ulen), - ntohs(self.udp_header.uh_sum), - ) - - elif (self.ip_header.protocol == IPPROTO_ICMP): - - proto_header = ( - self.icmp_header.type, - ) - - else: - proto_header = () - - return proto_header - - def get_payload(self): - '''Return payload (>layer4) as Python bytes.''' - - # cdef Py_ssize_t hdr_len = self.cmbhdr_len - - cdef Py_ssize_t payload_len = self.data_len - self.cmbhdr_len - - - return self.payload[:payload_len] - - # if self.data_len - hdr_len > 0: - # # print(f'DATA_LEN-1 = {self.data_len-1}') - # # return self.data[hdr_len:self.data_len-1] - # return self.payload - - # else: - # return b'' - - -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, 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.qh = nfq_create_queue(self.h, queue_num, nf_callback, 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 = 0 - - while True: - with nogil: - rv = recv(fd, buf, sizeof(buf), recv_flags) - - if (rv >= 0): - nfq_handle_packet(self.h, buf, rv) - - else: - if errno != ENOBUFS: - break diff --git a/setup2.py b/setup2.py deleted file mode 100644 index c471657..0000000 --- a/setup2.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 - -from setuptools import setup, Extension -from Cython.Build import cythonize -from Cython.Distutils import build_ext - -cmd = {'build_ext': build_ext} -ext = Extension( - 'new_packet', sources=['new_packet.pyx'], libraries=['netfilter_queue']) - -setup( - name='DNX-NFQUEUE', cmdclass=cmd, ext_modules=cythonize(ext, language_level='3') -)