python-netfilterqueue/netfilterqueue.pyx

386 lines
10 KiB
Cython
Raw Normal View History

"""
2015-05-16 09:26:41 +02:00
Bind to a Linux netfilter queue. Send packets to a user-specified callback
function.
Copyright: (c) 2011, Kerkhoff Technologies Inc.
License: MIT; see LICENSE.txt
"""
VERSION = (0, 7, 0)
2011-05-12 22:45:14 +02:00
# Constants for module users
COPY_NONE = 0
COPY_META = 1
COPY_PACKET = 2
2011-05-12 22:45:14 +02:00
# Packet copying defaults
DEF DEFAULT_MAX_QUEUELEN = 1024
2011-05-12 22:45:14 +02:00
DEF MaxPacketSize = 0xFFFF
DEF BufferSize = 4096
DEF MetadataSize = 80
DEF MaxCopySize = BufferSize - MetadataSize
# Experimentally determined overhead
DEF SockOverhead = 760+20
DEF SockCopySize = MaxCopySize + SockOverhead
# Socket queue should hold max number of packets of copysize bytes
DEF SockRcvSize = DEFAULT_MAX_QUEUELEN * SockCopySize / 2
2011-05-12 22:45:14 +02:00
import socket
2015-05-16 09:25:44 +02:00
cimport cpython.version
2015-05-16 09:26:41 +02:00
cdef int global_callback(nfq_q_handle *qh, nfgenmsg *nfmsg,
2011-05-12 22:45:14 +02:00
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
2011-05-13 17:42:05 +02:00
packet = Packet()
2011-05-12 22:45:14 +02:00
packet.set_nfq_data(qh, nfa)
user_callback(packet)
2011-05-12 22:45:14 +02:00
return 1
2011-05-13 17:42:05 +02:00
cdef class Packet:
"""A packet received from NetfilterQueue."""
2011-05-12 22:45:14 +02:00
def __cinit__(self):
self._verdict_is_set = False
self._mark_is_set = False
2012-12-24 04:17:54 +01:00
self._given_payload = None
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
def __str__(self):
cdef iphdr *hdr = <iphdr*>self.payload
2011-05-13 17:42:05 +02:00
protocol = PROTOCOLS.get(hdr.protocol, "Unknown protocol")
2011-05-12 22:45:14 +02:00
return "%s packet, %s bytes" % (protocol, self.payload_len)
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa):
"""
2015-05-16 09:26:41 +02:00
Assign a packet from NFQ to this object. Parse the header and load
2011-05-12 22:45:14 +02:00
local values.
"""
self._qh = qh
self._nfa = nfa
self._hdr = nfq_get_msg_packet_hdr(nfa)
2016-10-28 23:22:52 +02:00
self._hw = nfq_get_packet_hw(nfa)
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
self.id = ntohl(self._hdr.packet_id)
self.hw_protocol = ntohs(self._hdr.hw_protocol)
self.hook = self._hdr.hook
2016-10-28 23:22:52 +02:00
self.hw_addr = self._hw.hw_addr
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
self.payload_len = nfq_get_payload(self._nfa, &self.payload)
if self.payload_len < 0:
raise OSError("Failed to get payload of packet.")
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
nfq_get_timestamp(self._nfa, &self.timestamp)
2013-04-15 05:30:35 +02:00
self.mark = nfq_get_nfmark(nfa)
2011-05-12 22:45:14 +02:00
cdef void verdict(self, u_int8_t verdict):
"""Call appropriate set_verdict... function on packet."""
2011-05-13 17:42:05 +02:00
if self._verdict_is_set:
raise RuntimeWarning("Verdict already given for this packet.")
2012-12-24 04:17:54 +01:00
cdef u_int32_t modified_payload_len = 0
2015-05-16 09:26:41 +02:00
cdef unsigned char *modified_payload = NULL
2012-12-24 04:17:54 +01:00
if self._given_payload:
modified_payload_len = len(self._given_payload)
modified_payload = self._given_payload
2011-05-12 22:45:14 +02:00
if self._mark_is_set:
nfq_set_verdict2(
2011-05-12 22:45:14 +02:00
self._qh,
self.id,
verdict,
htonl(self._given_mark),
2012-12-24 04:17:54 +01:00
modified_payload_len,
modified_payload)
2011-05-12 22:45:14 +02:00
else:
nfq_set_verdict(
self._qh,
self.id,
verdict,
2012-12-24 04:17:54 +01:00
modified_payload_len,
modified_payload)
2011-05-12 22:45:14 +02:00
2011-05-13 17:42:05 +02:00
self._verdict_is_set = True
2015-05-16 09:26:41 +02:00
2016-10-28 23:22:52 +02:00
def get_hw(self):
"""Return the hardware address as Python string."""
cdef object py_string
if cpython.version.PY_MAJOR_VERSION >= 3:
py_string = PyBytes_FromStringAndSize(<char*>self.hw_addr, 8)
else:
py_string = PyString_FromStringAndSize(<char*>self.hw_addr, 8)
return py_string
2011-05-12 22:45:14 +02:00
def get_payload(self):
2011-05-13 17:42:05 +02:00
"""Return payload as Python string."""
2015-05-16 09:25:44 +02:00
cdef object py_string
py_string = self.payload[:self.payload_len]
2011-05-12 22:45:14 +02:00
return py_string
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cpdef Py_ssize_t get_payload_len(self):
return self.payload_len
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cpdef double get_timestamp(self):
return self.timestamp.tv_sec + (self.timestamp.tv_usec/1000000.0)
2015-05-16 09:26:41 +02:00
2012-12-24 04:17:54 +01:00
cpdef set_payload(self, bytes payload):
"""Set the new payload of this packet."""
self._given_payload = payload
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cpdef set_mark(self, u_int32_t mark):
self._given_mark = mark
self._mark_is_set = True
2013-04-15 05:30:35 +02:00
cpdef get_mark(self):
if self._mark_is_set:
return self._given_mark
return self.mark
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cpdef accept(self):
"""Accept the packet."""
self.verdict(NF_ACCEPT)
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
cpdef drop(self):
"""Drop the packet."""
self.verdict(NF_DROP)
2013-04-03 13:55:51 +02:00
cpdef repeat(self):
"""Repeat the packet."""
self.verdict(NF_REPEAT)
cdef class NetfilterQueue:
2011-05-12 22:45:14 +02:00
"""Handle a single numbered queue."""
def __cinit__(self, *args, **kwargs):
2011-05-13 17:42:05 +02:00
self.af = kwargs.get("af", PF_INET)
2011-05-12 22:45:14 +02:00
self.h = nfq_open()
if self.h == NULL:
raise OSError("Failed to open NFQueue.")
2015-05-16 09:26:41 +02:00
nfq_unbind_pf(self.h, self.af) # This does NOT kick out previous
2011-05-12 22:45:14 +02:00
# running queues
if nfq_bind_pf(self.h, self.af) < 0:
2011-05-13 17:42:05 +02:00
raise OSError("Failed to bind family %s. Are you root?" % self.af)
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
def __dealloc__(self):
if self.qh != NULL:
nfq_destroy_queue(self.qh)
2015-05-16 09:26:41 +02:00
# Don't call nfq_unbind_pf unless you want to disconnect any other
2011-05-12 22:45:14 +02:00
# processes using this libnetfilter_queue on this protocol family!
nfq_close(self.h)
def bind(self, int queue_num, object user_callback,
2015-05-16 09:26:41 +02:00
u_int32_t max_len=DEFAULT_MAX_QUEUELEN,
u_int8_t mode=NFQNL_COPY_PACKET,
u_int32_t range=MaxPacketSize,
u_int32_t sock_len=SockRcvSize):
2011-05-13 17:42:05 +02:00
"""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)
2011-05-12 22:45:14 +02:00
if self.qh == NULL:
raise OSError("Failed to create queue %s." % queue_num)
2015-05-16 09:26:41 +02:00
2011-05-12 22:45:14 +02:00
if range > MaxCopySize:
range = MaxCopySize
if nfq_set_mode(self.qh, mode, range) < 0:
raise OSError("Failed to set packet copy mode.")
2015-05-16 09:26:41 +02:00
2011-05-13 18:23:17 +02:00
nfq_set_queue_maxlen(self.qh, max_len)
2015-05-16 09:26:41 +02:00
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))
2011-05-12 22:45:14 +02:00
def unbind(self):
"""Destroy the queue."""
if self.qh != NULL:
nfq_destroy_queue(self.qh)
self.qh = NULL
2011-05-12 22:45:14 +02:00
# See warning about nfq_unbind_pf in __dealloc__ above.
2015-05-16 09:26:41 +02:00
2015-11-17 23:07:50 +01:00
def get_fd(self):
"""Get the file descriptor of the queue handler."""
return nfq_fd(self.h)
def run(self, block=True):
"""Accept packets using recv."""
cdef int fd = self.get_fd()
2011-05-12 22:45:14 +02:00
cdef char buf[BufferSize]
cdef int rv
2015-11-17 23:07:50 +01:00
cdef int recv_flags
recv_flags = 0 if block else MSG_DONTWAIT
while True:
2011-05-12 22:45:14 +02:00
with nogil:
2015-11-17 23:07:50 +01:00
rv = recv(fd, buf, sizeof(buf), recv_flags)
if (rv >= 0):
nfq_handle_packet(self.h, buf, rv)
else:
if errno != ENOBUFS:
break
2011-05-12 22:45:14 +02:00
def run_socket(self, s):
"""Accept packets using socket.recv so that, for example, gevent can monkeypatch it."""
2016-06-28 09:13:40 +02:00
while True:
try:
buf = s.recv(BufferSize)
rv = len(buf)
if rv >= 0:
nfq_handle_packet(self.h, buf, rv)
else:
break
2016-06-28 09:13:40 +02:00
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
2011-05-13 17:42:05 +02:00
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",
}