work on new packet

This commit is contained in:
dowright 2021-08-23 13:01:24 -07:00 committed by DOWRIGHT
parent 5af79a5303
commit 7fe1d0c5bc
4 changed files with 304 additions and 4 deletions

View File

@ -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)

74
new_packet.pxd Normal file
View File

@ -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 "<errno.h>":
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]

214
new_packet.pyx Normal file
View File

@ -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

13
setup2.py Normal file
View File

@ -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')
)