Skip to content

Commit 62da18e

Browse files
committed
Document and test remaining attributes/methods; fix PyPy tests by not calling unbind() from tp_dealloc; misc other cleanups
1 parent 2c782b9 commit 62da18e

File tree

5 files changed

+242
-137
lines changed

5 files changed

+242
-137
lines changed

README.rst

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,17 @@ To install from source::
9090
API
9191
===
9292

93-
``NetfilterQueue.COPY_NONE``
94-
95-
``NetfilterQueue.COPY_META``
96-
97-
``NetfilterQueue.COPY_PACKET``
93+
``NetfilterQueue.COPY_NONE``, ``NetfilterQueue.COPY_META``, ``NetfilterQueue.COPY_PACKET``
9894
These constants specify how much of the packet should be given to the
99-
script- nothing, metadata, or the whole packet.
95+
script: nothing, metadata, or the whole packet.
10096

10197
NetfilterQueue objects
10298
----------------------
10399

104100
A NetfilterQueue object represents a single queue. Configure your queue with
105101
a call to ``bind``, then start receiving packets with a call to ``run``.
106102

107-
``QueueHandler.bind(queue_num, callback[, max_len[, mode[, range[, sock_len]]]])``
103+
``NetfilterQueue.bind(queue_num, callback, max_len=1024, mode=COPY_PACKET, range=65535, sock_len=...)``
108104
Create and bind to the queue. ``queue_num`` uniquely identifies this
109105
queue for the kernel. It must match the ``--queue-num`` in your iptables
110106
rule, but there is no ordering requirement: it's fine to either ``bind()``
@@ -118,22 +114,23 @@ a call to ``bind``, then start receiving packets with a call to ``run``.
118114
the source and destination IPs of a IPv4 packet, ``range`` could be 20.
119115
``sock_len`` sets the receive socket buffer size.
120116

121-
``QueueHandler.unbind()``
117+
``NetfilterQueue.unbind()``
122118
Remove the queue. Packets matched by your iptables rule will be dropped.
123119

124-
``QueueHandler.get_fd()``
120+
``NetfilterQueue.get_fd()``
125121
Get the file descriptor of the socket used to receive queued
126122
packets and send verdicts. If you're using an async event loop,
127123
you can poll this FD for readability and call ``run(False)`` every
128124
time data appears on it.
129125

130-
``QueueHandler.run([block])``
126+
``NetfilterQueue.run(block=True)``
131127
Send packets to your callback. By default, this method blocks, running
132128
until an exception is raised (such as by Ctrl+C). Set
133-
block=False to process the pending messages without waiting for more.
134-
You can get the file descriptor of the socket with the ``get_fd`` method.
129+
``block=False`` to process the pending messages without waiting for more;
130+
in conjunction with the ``get_fd`` method, you can use this to integrate
131+
with async event loops.
135132

136-
``QueueHandler.run_socket(socket)``
133+
``NetfilterQueue.run_socket(socket)``
137134
Send packets to your callback, but use the supplied socket instead of
138135
recv, so that, for example, gevent can monkeypatch it. You can make a
139136
socket with ``socket.fromfd(nfqueue.get_fd(), socket.AF_NETLINK, socket.SOCK_RAW)``
@@ -148,6 +145,8 @@ Objects of this type are passed to your callback.
148145
Return the packet's payload as a bytes object. The returned value
149146
starts with the IP header. You must call ``retain()`` if you want
150147
to be able to ``get_payload()`` after your callback has returned.
148+
If you have already called ``set_payload()``, then ``get_payload()``
149+
returns what you passed to ``set_payload()``.
151150

152151
``Packet.set_payload(payload)``
153152
Set the packet payload. Call this before ``accept()`` if you want to
@@ -166,12 +165,46 @@ Objects of this type are passed to your callback.
166165
rules. ``mark`` is a 32-bit number.
167166

168167
``Packet.get_mark()``
169-
Get the mark already on the packet (either the one you set using
168+
Get the mark on the packet (either the one you set using
170169
``set_mark()``, or the one it arrived with if you haven't called
171170
``set_mark()``).
172171

173172
``Packet.get_hw()``
174-
Return the hardware address as a Python string.
173+
Return the source hardware address of the packet as a Python
174+
bytestring, or ``None`` if the source hardware address was not
175+
captured (packets captured by the ``OUTPUT`` or ``PREROUTING``
176+
hooks). For example, on Ethernet the result will be a six-byte
177+
MAC address. The destination hardware address is not available
178+
because it is determined in the kernel only after packet filtering
179+
is complete.
180+
181+
``Packet.get_timestamp()``
182+
Return the time at which this packet was received by the kernel,
183+
as a floating-point Unix timestamp with microsecond precision
184+
(comparable to the result of ``time.time()``, for example).
185+
Packets captured by the ``OUTPUT`` or ``POSTROUTING`` hooks
186+
do not have a timestamp, and ``get_timestamp()`` will return 0.0
187+
for them.
188+
189+
``Packet.id``
190+
The identifier assigned to this packet by the kernel. Typically
191+
the first packet received by your queue starts at 1 and later ones
192+
count up from there.
193+
194+
``Packet.hw_protocol``
195+
The link-layer protocol for this packet. For example, IPv4 packets
196+
on Ethernet would have this set to the EtherType for IPv4, which is
197+
``0x0800``.
198+
199+
``Packet.mark``
200+
The mark that had been assigned to this packet when it was enqueued.
201+
Unlike the result of ``get_mark()``, this does not change if you call
202+
``set_mark()``.
203+
204+
``Packet.hook``
205+
The netfilter hook (iptables chain, roughly) that diverted this packet
206+
into our queue. Values 0 through 4 correspond to PREROUTING, INPUT,
207+
FORWARD, OUTPUT, and POSTROUTING respectively.
175208

176209
``Packet.retain()``
177210
Allocate a copy of the packet payload for use after the callback
@@ -249,20 +282,39 @@ The fields are:
249282
Limitations
250283
===========
251284

252-
* Compiled with a 4096-byte buffer for packets, so it probably won't work on
253-
loopback or Ethernet with jumbo packets. If this is a problem, either lower
254-
MTU on your loopback, disable jumbo packets, or get Cython,
255-
change ``DEF BufferSize = 4096`` in ``netfilterqueue.pyx``, and rebuild.
256-
* Full libnetfilter_queue API is not yet implemented:
285+
* We use a fixed-size 4096-byte buffer for packets, so you are likely
286+
to see truncation on loopback and on Ethernet with jumbo packets.
287+
If this is a problem, either lower the MTU on your loopback, disable
288+
jumbo packets, or get Cython, change ``DEF BufferSize = 4096`` in
289+
``netfilterqueue.pyx``, and rebuild.
290+
291+
* Not all information available from libnetfilter_queue is exposed:
292+
missing pieces include packet input/output network interface names,
293+
checksum offload flags, UID/GID and security context data
294+
associated with the packet (if any).
257295

258-
* Omits methods for getting information about the interface a packet has
259-
arrived on or is leaving on
260-
* Probably other stuff is omitted too
296+
* Not all information available from the kernel is even processed by
297+
libnetfilter_queue: missing pieces include additional link-layer
298+
header data for some packets (including VLAN tags), connection-tracking
299+
state, and incoming packet length (if truncated for queueing).
300+
301+
* We do not expose the libnetfilter_queue interface for changing queue flags.
302+
Most of these pertain to other features we don't support (listed above),
303+
but there's one that could set the queue to accept (rather than dropping)
304+
packets received when it's full.
261305

262306
Source
263307
======
264308

265-
https://github.com/kti/python-netfilterqueue
309+
https://github.com/oremanj/python-netfilterqueue
310+
311+
Authorship
312+
==========
313+
314+
python-netfilterqueue was originally written by Matthew Fox of
315+
Kerkhoff Technologies, Inc. Since 2022 it has been maintained by
316+
Joshua Oreman of Hudson River Trading LLC. Both authors wish to
317+
thank their employers for their support of open source.
266318

267319
License
268320
=======

netfilterqueue.pxd

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ cdef class NetfilterQueue:
172172
cdef object user_callback # User callback
173173
cdef nfq_handle *h # Handle to NFQueue library
174174
cdef nfq_q_handle *qh # A handle to the queue
175-
cdef bint unbinding
176175

177176
cdef class Packet:
178177
cdef NetfilterQueue _queue
179178
cdef bint _verdict_is_set # True if verdict has been issued,
180179
# false otherwise
181180
cdef bint _mark_is_set # True if a mark has been given, false otherwise
182181
cdef bint _hwaddr_is_set
182+
cdef bint _timestamp_is_set
183183
cdef u_int32_t _given_mark # Mark given to packet
184184
cdef bytes _given_payload # New payload of packet, or null
185185
cdef bytes _owned_payload
@@ -197,7 +197,6 @@ cdef class Packet:
197197
cdef u_int8_t hw_addr[8]
198198

199199
# TODO: implement these
200-
#cdef u_int8_t hw_addr[8] # A eui64-formatted address?
201200
#cdef readonly u_int32_t nfmark
202201
#cdef readonly u_int32_t indev
203202
#cdef readonly u_int32_t physindev

netfilterqueue.pyx

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,6 @@ DEF SockCopySize = MaxCopySize + SockOverhead
2424
# Socket queue should hold max number of packets of copysize bytes
2525
DEF SockRcvSize = DEFAULT_MAX_QUEUELEN * SockCopySize // 2
2626

27-
cdef extern from *:
28-
"""
29-
static void do_write_unraisable(PyObject* obj) {
30-
PyObject *ty, *val, *tb;
31-
PyErr_GetExcInfo(&ty, &val, &tb);
32-
PyErr_Restore(ty, val, tb);
33-
PyErr_WriteUnraisable(obj);
34-
}
35-
"""
36-
cdef void do_write_unraisable(msg)
37-
38-
3927
from cpython.exc cimport PyErr_CheckSignals
4028

4129
# A negative return value from this callback will stop processing and
@@ -45,17 +33,15 @@ cdef int global_callback(nfq_q_handle *qh, nfgenmsg *nfmsg,
4533
"""Create a Packet and pass it to appropriate callback."""
4634
cdef NetfilterQueue nfqueue = <NetfilterQueue>data
4735
cdef object user_callback = <object>nfqueue.user_callback
36+
if user_callback is None:
37+
# Queue is being unbound; we can't send a verdict at this point
38+
# so just ignore the packet. The kernel will drop it once we
39+
# unbind.
40+
return 1
4841
packet = Packet()
4942
packet.set_nfq_data(nfqueue, nfa)
5043
try:
5144
user_callback(packet)
52-
except BaseException as exc:
53-
if nfqueue.unbinding == True:
54-
do_write_unraisable(
55-
"netfilterqueue callback during unbind"
56-
)
57-
else:
58-
raise
5945
finally:
6046
packet.drop_refs()
6147
return 1
@@ -104,7 +90,7 @@ cdef class Packet:
10490
cdef drop_refs(self):
10591
"""
10692
Called at the end of the user_callback, when the storage passed to
107-
set_nfq_data() is about to be deallocated.
93+
set_nfq_data() is about to be reused.
10894
"""
10995
self.payload = NULL
11096

@@ -139,7 +125,11 @@ cdef class Packet:
139125
self._verdict_is_set = True
140126

141127
def get_hw(self):
142-
"""Return the hardware address as Python string."""
128+
"""Return the packet's source MAC address as a Python bytestring, or
129+
None if it's not available.
130+
"""
131+
if not self._hwaddr_is_set:
132+
return None
143133
cdef object py_string
144134
py_string = PyBytes_FromStringAndSize(<char*>self.hw_addr, 8)
145135
return py_string
@@ -209,11 +199,17 @@ cdef class NetfilterQueue:
209199
if nfq_bind_pf(self.h, af) < 0:
210200
raise OSError("Failed to bind family %s. Are you root?" % af)
211201

212-
def __dealloc__(self):
202+
def __del__(self):
203+
# unbind() can result in invocations of global_callback, so we
204+
# must do it in __del__ (when this is still a valid
205+
# NetfilterQueue object) rather than __dealloc__
213206
self.unbind()
207+
208+
def __dealloc__(self):
214209
# Don't call nfq_unbind_pf unless you want to disconnect any other
215210
# processes using this libnetfilter_queue on this protocol family!
216-
nfq_close(self.h)
211+
if self.h != NULL:
212+
nfq_close(self.h)
217213

218214
def bind(self, int queue_num, object user_callback,
219215
u_int32_t max_len=DEFAULT_MAX_QUEUELEN,
@@ -254,12 +250,9 @@ cdef class NetfilterQueue:
254250

255251
def unbind(self):
256252
"""Destroy the queue."""
253+
self.user_callback = None
257254
if self.qh != NULL:
258-
self.unbinding = True
259-
try:
260-
nfq_destroy_queue(self.qh)
261-
finally:
262-
self.unbinding = False
255+
nfq_destroy_queue(self.qh)
263256
self.qh = NULL
264257
# See warning about nfq_unbind_pf in __dealloc__ above.
265258

@@ -288,9 +281,7 @@ cdef class NetfilterQueue:
288281
PyErr_CheckSignals()
289282
continue
290283
raise OSError(errno, "recv failed")
291-
rv = nfq_handle_packet(self.h, buf, rv)
292-
if rv < 0:
293-
raise OSError(errno, "nfq_handle_packet failed")
284+
nfq_handle_packet(self.h, buf, rv)
294285

295286
def run_socket(self, s):
296287
"""Accept packets using socket.recv so that, for example, gevent can monkeypatch it."""
@@ -299,11 +290,6 @@ cdef class NetfilterQueue:
299290
while True:
300291
try:
301292
buf = s.recv(BufferSize)
302-
rv = len(buf)
303-
if rv >= 0:
304-
nfq_handle_packet(self.h, buf, rv)
305-
else:
306-
break
307293
except socket.error as e:
308294
err = e.args[0]
309295
if err == ENOBUFS:
@@ -315,6 +301,8 @@ cdef class NetfilterQueue:
315301
else:
316302
# This is bad. Let the caller handle it.
317303
raise e
304+
else:
305+
nfq_handle_packet(self.h, buf, len(buf))
318306

319307
PROTOCOLS = {
320308
0: "HOPOPT",

0 commit comments

Comments
 (0)