proto header work

This commit is contained in:
DOWRIGHT 2021-08-28 23:26:49 -07:00
parent b250f70e33
commit 38f494df2c
3 changed files with 102 additions and 72 deletions

View File

@ -58,7 +58,6 @@ cdef enum:
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
@ -113,39 +112,19 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
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) nogil
int nfq_get_payload(nfq_data *nfad, unsigned char **data) nogil
@ -198,15 +177,13 @@ cdef class CPacket:
# Packet details:
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[6]
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)
cdef double get_timestamp(self)
cdef u_int8_t get_inint(self, bint name=?)
cdef u_int8_t get_outint(self, bint name=?)
cpdef update_mark(self, u_int32_t mark)
cpdef accept(self)
cpdef drop(self)

View File

@ -26,13 +26,13 @@ def set_user_callback(ref):
user_callback = ref
cdef int nf_callback(nfq_q_handle *qh, nfgenmsg *nfmsg, nfq_data *nfa, void *data):
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)
# with nogil:
mark = packet.parse(qh, nfa)
user_callback(packet, mark)
@ -79,48 +79,58 @@ cdef class CPacket:
self.ip_header = <iphdr*>self.data
cdef u_int8_t hdr_shift = 4
cdef u_int8_t hdr_multiplier = 4
cdef u_int8_t hdr_xand = 15
cdef u_int8_t iphdr_len
iphdr_len = self.ip_header.ver_ihl & hdr_xand
iphdr_len = iphdr_len * hdr_multiplier
iphdr_len = (self.ip_header.ver_ihl & 15) * 4
# NOTE: tshoot print
printf('ip header length=%f\n', <double>iphdr_len)
cdef u_int8_t tcphdr_len
cdef u_int8_t udphdr_len = 8
cdef u_int8_t icmphdr_len = 4
cdef u_int8_t protohdr_len
cdef void *data = &self.data[iphdr_len]
cdef ptrdiff_t hdrptr = <u_int32_t*>data - <u_int32_t*>self.data
# printf('data=%p, self.data=%p\n', <void*>&data, <void*>&self.data)
# cdef ptrdiff_t hdrptr = <u_int32_t*>data - <u_int32_t*>self.data
if (self.ip_header.protocol == IPPROTO_TCP):
printf('TCP=%s\n', <char*>self.data_len)
self.tcp_header = <tcphdr*>&hdrptr
self.tcp_header = <tcphdr*>&data
tcphdr_len = self.tcp_header.th_off >> hdr_shift
tcphdr_len = tcphdr_len & hdr_xand
tcphdr_len = tcphdr_len * hdr_multiplier
tcphdr_len = (self.tcp_header.th_off >> 4) & 15
protohdr_len = tcphdr_len * 4
# NOTE: tshoot print
printf('TCP HEADER LEN=%f\n', <double>tcphdr_len)
printf('TCP SPORT=%f\n', <double>self.tcp_header.th_sport)
self.cmbhdr_len = iphdr_len + tcphdr_len
# self.cmbhdr_len = iphdr_len + tcphdr_len
elif (self.ip_header.protocol == IPPROTO_UDP):
self.udp_header = <udphdr*>&hdrptr
self.udp_header = <udphdr*>&data
self.cmbhdr_len = iphdr_len + udphdr_len
protohdr_len = iphdr_len + 8
# self.cmbhdr_len = iphdr_len + 8
elif (self.ip_header.protocol == IPPROTO_ICMP):
self.icmp_header = <icmphdr*>&hdrptr
self.icmp_header = <icmphdr*>&data
self.cmbhdr_len = iphdr_len + icmphdr_len
protohdr_len = iphdr_len + 4
# self.cmbhdr_len = iphdr_len + 4
else:
printf('UNKNOWN PROTOCOL=%f\n', <double>self.ip_header.protocol)
self.cmbhdr_len = protohdr_len + 20
self.payload = &self.data[iphdr_len+protohdr_len]
printf('CMBHDR LEN=%f\n', <double>self.cmbhdr_len)
printf('DATA LEN=%f\n', <double>self.data_len)
cdef void verdict(self, u_int32_t verdict):
'''Call appropriate set_verdict function on packet.'''
@ -208,17 +218,18 @@ cdef class CPacket:
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 char* hw_addr = self._hw.hw_addr
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)
mac_addr = PyBytes_FromStringAndSize(<char*>hw_addr, 6)
hw_info = (
in_interface,
@ -234,6 +245,12 @@ cdef class CPacket:
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.'''
@ -298,11 +315,20 @@ cdef class CPacket:
def get_payload(self):
'''Return payload (>layer4) as Python bytes.'''
cdef object payload
# cdef Py_ssize_t hdr_len = <Py_ssize_t>self.cmbhdr_len
payload = self.data[<Py_ssize_t>self.cmbhdr_len:self.data_len]
cdef Py_ssize_t payload_len = self.data_len - self.cmbhdr_len
return payload
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:
@ -371,9 +397,7 @@ cdef class NetfilterQueue:
cdef int fd = self.get_fd()
cdef char buf[4096]
cdef int rv
cdef int recv_flags
recv_flags = 0
cdef int recv_flags = 0
while True:
with nogil:

View File

@ -4,8 +4,11 @@ import threading
from time import perf_counter_ns
from ipaddress import IPv4Address
from struct import Struct
from new_packet import set_user_callback, NetfilterQueue
from new_packet import set_user_callback, NetfilterQueue # pylint: disable=import-error
short_unpack = Struct('!H').unpack
TEST_FORWARD = 0
@ -17,34 +20,44 @@ def print_and_accept(pkt, pkt_mark):
# print(pkt_mark)
hw_info = pkt.get_hw()
print('[rcvd] hw info')
# print(hw_info)
# print(hw_info)
# print(hw_info[2].hex())
# print('-'*30)
# print('-'*30)
# data = pkt.get_raw_packet()
data = pkt.get_raw_packet()
print('[rcvd] RAW PACKET')
# print(data[0], (data[0] & 15) * 4)
# print('-'*30)
# print('-'*30)
ip_header = pkt.get_ip_header()
if ip_header[6] == 6:
print('[rcvd] ip header')
# print(ip_header)
# print(ip_header[6], IPv4Address(ip_header[8]), IPv4Address(ip_header[9]))
print(ip_header[6], IPv4Address(ip_header[8]), IPv4Address(ip_header[9]))
# print('-'*30)
proto_header = pkt.get_proto_header()
# print(pkt.get_proto_header())
if ip_header[6] == 6:
print('[rcvd] proto header')
print(f'th_off={proto_header[4]}', proto_header)
# print('-'*30)
print('-'*30)
payload = pkt.get_payload()
# print(pkt.get_payload())
if ip_header[6] == 6:
payload = pkt.get_payload()
print(payload)
# t, s, d = pkt.get_ip_header()
@ -102,7 +115,7 @@ def q_two(pkt):
print('-'*30)
def queue(callback, queue_num):
set_user_callback(print_and_accept)
set_user_callback(callback)
nfqueue = NetfilterQueue()
nfqueue.bind(queue_num)
@ -117,11 +130,27 @@ def queue(callback, queue_num):
finally:
nfqueue.unbind()
def tcp_test(packet, mark):
print('+'*30)
start = perf_counter_ns()
ip_header = packet.get_ip_header()
if ip_header[6] == 6:
proto_header = packet.get_proto_header()
print(f'{IPv4Address(ip_header[8])}:{proto_header[0]} > {IPv4Address(ip_header[9])}:{proto_header[1]}')
print(packet.get_ip_header_raw())
print(packet.get_proto_header_raw())
print(packet.get_raw_packet())
print('='*30)
if __name__ == '__main__':
CALLBACK = tcp_test
if (TEST_FORWARD):
threading.Thread(target=queue, args=(q_one, 1)).start()
threading.Thread(target=queue, args=(q_two, 2)).start()
else:
queue(print_and_accept, 1)
queue(CALLBACK, 1)