Skip to content

Fixes for several open issues #77

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ jobs:
check_lint: ['0']
extra_name: ['']
include:
- python: '2.7'
extra_name: ', build only'
- python: '3.9'
check_lint: '1'
extra_name: ', check lint'
Expand Down
7 changes: 7 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
v1.0.0, unreleased
Propagate exceptions raised by the user's packet callback
Warn about exceptions raised by the packet callback during queue unbinding
Raise an error if a packet verdict is set after its parent queue is closed
set_payload() now affects the result of later get_payload()
Handle signals received when run() is blocked in recv()

v0.9.0, 12 Jan 2021
Improve usability when Packet objects are retained past the callback
Add Packet.retain() to save the packet contents in such cases
Expand Down
100 changes: 76 additions & 24 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,17 @@ To install from source::
API
===

``NetfilterQueue.COPY_NONE``

``NetfilterQueue.COPY_META``

``NetfilterQueue.COPY_PACKET``
``NetfilterQueue.COPY_NONE``, ``NetfilterQueue.COPY_META``, ``NetfilterQueue.COPY_PACKET``
These constants specify how much of the packet should be given to the
script- nothing, metadata, or the whole packet.
script: nothing, metadata, or the whole packet.

NetfilterQueue objects
----------------------

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

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

``QueueHandler.unbind()``
``NetfilterQueue.unbind()``
Remove the queue. Packets matched by your iptables rule will be dropped.

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

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

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

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

``Packet.get_mark()``
Get the mark already on the packet (either the one you set using
Get the mark on the packet (either the one you set using
``set_mark()``, or the one it arrived with if you haven't called
``set_mark()``).

``Packet.get_hw()``
Return the hardware address as a Python string.
Return the source hardware address of the packet as a Python
bytestring, or ``None`` if the source hardware address was not
captured (packets captured by the ``OUTPUT`` or ``PREROUTING``
hooks). For example, on Ethernet the result will be a six-byte
MAC address. The destination hardware address is not available
because it is determined in the kernel only after packet filtering
is complete.

``Packet.get_timestamp()``
Return the time at which this packet was received by the kernel,
as a floating-point Unix timestamp with microsecond precision
(comparable to the result of ``time.time()``, for example).
Packets captured by the ``OUTPUT`` or ``POSTROUTING`` hooks
do not have a timestamp, and ``get_timestamp()`` will return 0.0
for them.

``Packet.id``
The identifier assigned to this packet by the kernel. Typically
the first packet received by your queue starts at 1 and later ones
count up from there.

``Packet.hw_protocol``
The link-layer protocol for this packet. For example, IPv4 packets
on Ethernet would have this set to the EtherType for IPv4, which is
``0x0800``.

``Packet.mark``
The mark that had been assigned to this packet when it was enqueued.
Unlike the result of ``get_mark()``, this does not change if you call
``set_mark()``.

``Packet.hook``
The netfilter hook (iptables chain, roughly) that diverted this packet
into our queue. Values 0 through 4 correspond to PREROUTING, INPUT,
FORWARD, OUTPUT, and POSTROUTING respectively.

``Packet.retain()``
Allocate a copy of the packet payload for use after the callback
Expand Down Expand Up @@ -249,20 +282,39 @@ The fields are:
Limitations
===========

* Compiled with a 4096-byte buffer for packets, so it probably won't work on
loopback or Ethernet with jumbo packets. If this is a problem, either lower
MTU on your loopback, disable jumbo packets, or get Cython,
change ``DEF BufferSize = 4096`` in ``netfilterqueue.pyx``, and rebuild.
* Full libnetfilter_queue API is not yet implemented:
* We use a fixed-size 4096-byte buffer for packets, so you are likely
to see truncation on loopback and on Ethernet with jumbo packets.
If this is a problem, either lower the MTU on your loopback, disable
jumbo packets, or get Cython, change ``DEF BufferSize = 4096`` in
``netfilterqueue.pyx``, and rebuild.

* Not all information available from libnetfilter_queue is exposed:
missing pieces include packet input/output network interface names,
checksum offload flags, UID/GID and security context data
associated with the packet (if any).

* Omits methods for getting information about the interface a packet has
arrived on or is leaving on
* Probably other stuff is omitted too
* Not all information available from the kernel is even processed by
libnetfilter_queue: missing pieces include additional link-layer
header data for some packets (including VLAN tags), connection-tracking
state, and incoming packet length (if truncated for queueing).

* We do not expose the libnetfilter_queue interface for changing queue flags.
Most of these pertain to other features we don't support (listed above),
but there's one that could set the queue to accept (rather than dropping)
packets received when it's full.

Source
======

https://github.com/kti/python-netfilterqueue
https://github.com/oremanj/python-netfilterqueue

Authorship
==========

python-netfilterqueue was originally written by Matthew Fox of
Kerkhoff Technologies, Inc. Since 2022 it has been maintained by
Joshua Oreman of Hudson River Trading LLC. Both authors wish to
thank their employers for their support of open source.

License
=======
Expand Down
6 changes: 0 additions & 6 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ python setup.py sdist --formats=zip
pip uninstall -y cython
pip install dist/*.zip

if python --version 2>&1 | fgrep -q "Python 2.7"; then
# The testsuite doesn't run on 2.7, so do just a basic smoke test.
unshare -Urn python -c "from netfilterqueue import NetfilterQueue as NFQ; NFQ()"
exit $?
fi

pip install -Ur test-requirements.txt

if [ "$CHECK_LINT" = "1" ]; then
Expand Down
44 changes: 22 additions & 22 deletions netfilterqueue.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ cdef extern from "<errno.h>":

# dummy defines from asm-generic/errno.h:
cdef enum:
EINTR = 4
EAGAIN = 11 # Try again
EWOULDBLOCK = EAGAIN
ENOBUFS = 105 # No buffer space available
Expand Down Expand Up @@ -115,15 +116,17 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.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)
# Any function that parses Netlink replies might invoke the user
# callback and thus might need to propagate a Python exception.
# This includes nfq_handle_packet but is not limited to that --
# other functions might send a query, read until they get the reply,
# and find a packet notification before the reply which they then
# must deal with.
int nfq_destroy_queue(nfq_q_handle *qh) except? -1
int nfq_handle_packet(nfq_handle *h, char *buf, int len) except? -1
int nfq_set_mode(nfq_q_handle *qh, u_int8_t mode, unsigned int len) except? -1
int nfq_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen) except? -1

int nfq_set_verdict(nfq_q_handle *qh,
u_int32_t id,
Expand All @@ -137,7 +140,6 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
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)
Expand All @@ -146,7 +148,7 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
nfqnl_msg_packet_hw *nfq_get_packet_hw(nfq_data *nfad)
int nfq_get_nfmark (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
Expand All @@ -166,12 +168,19 @@ cdef enum:
NF_STOP
NF_MAX_VERDICT = NF_STOP

cdef class NetfilterQueue:
cdef object __weakref__
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 class Packet:
cdef nfq_q_handle *_qh
cdef NetfilterQueue _queue
cdef bint _verdict_is_set # True if verdict has been issued,
# false otherwise
cdef bint _mark_is_set # True if a mark has been given, false otherwise
cdef bint _hwaddr_is_set
cdef bint _timestamp_is_set
cdef u_int32_t _given_mark # Mark given to packet
cdef bytes _given_payload # New payload of packet, or null
cdef bytes _owned_payload
Expand All @@ -189,16 +198,15 @@ cdef class Packet:
cdef u_int8_t hw_addr[8]

# TODO: implement these
#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 set_nfq_data(self, NetfilterQueue queue, nfq_data *nfa)
cdef drop_refs(self)
cdef void verdict(self, u_int8_t verdict)
cdef int verdict(self, u_int8_t verdict) except -1
cpdef Py_ssize_t get_payload_len(self)
cpdef double get_timestamp(self)
cpdef bytes get_payload(self)
Expand All @@ -209,11 +217,3 @@ cdef class Packet:
cpdef accept(self)
cpdef drop(self)
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

Loading