diff --git a/README.rst b/README.rst index a8dc530..b88833e 100644 --- a/README.rst +++ b/README.rst @@ -3,9 +3,10 @@ NetfilterQueue ============== NetfilterQueue provides access to packets matched by an iptables rule in -Linux. Packets so matched can be accepted, dropped, altered, or given a mark. +Linux. Packets so matched can be accepted, dropped, altered, reordered, +or given a mark. -Libnetfilter_queue (the netfilter library, not this module) is part of the +libnetfilter_queue (the netfilter library, not this module) is part of the `Netfilter project `_. Example @@ -15,18 +16,18 @@ The following script prints a short description of each packet before accepting it. :: from netfilterqueue import NetfilterQueue - + def print_and_accept(pkt): print(pkt) pkt.accept() - + nfqueue = NetfilterQueue() nfqueue.bind(1, print_and_accept) try: nfqueue.run() except KeyboardInterrupt: print('') - + nfqueue.unbind() You can also make your own socket so that it can be used with gevent, for example. :: @@ -56,7 +57,7 @@ To send packets destined for your LAN to the script, type something like:: Installation ============ -NetfilterQueue is a C extention module that links against libnetfilter_queue. +NetfilterQueue is a C extention module that links against libnetfilter_queue. Before installing, ensure you have: 1. A C compiler @@ -81,9 +82,9 @@ From source To install from source:: - git clone git@github.com:kti/python-netfilterqueue.git + git clone https://github.com/oremanj/python-netfilterqueue cd python-netfilterqueue - python setup.py install + pip install . If Cython is installed, Distutils will use it to regenerate the .c source from the .pyx. It will then compile the .c into a .so. @@ -104,9 +105,12 @@ 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]]]])`` - Create and bind to the queue. ``queue_num`` must match the number in your - iptables rule. ``callback`` is a function or method that takes one +``QueueHandler.bind(queue_num, callback[, max_len[, mode[, range[, 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()`` + first or set up the iptables rule first. + ``callback`` is a function or method that takes one argument, a Packet object (see below). ``max_len`` sets the largest number of packets that can be in the queue; new packets are dropped if the size of the queue reaches this number. ``mode`` determines how much of the packet @@ -119,17 +123,21 @@ a call to ``bind``, then start receiving packets with a call to ``run``. Remove the queue. Packets matched by your iptables rule will be dropped. ``QueueHandler.get_fd()`` - Get the file descriptor of the queue handler. + 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])`` - Send packets to your callback. By default, this method blocks. Set - block=False to let your thread continue. You can get the file descriptor - of the socket with the ``get_fd`` method. + 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. ``QueueHandler.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_UNIX, socket.SOCK_STREAM)`` + socket with ``socket.fromfd(nfqueue.get_fd(), socket.AF_NETLINK, socket.SOCK_RAW)`` and optionally make it non-blocking with ``socket.setblocking(False)``. Packet objects @@ -138,42 +146,65 @@ Packet objects Objects of this type are passed to your callback. ``Packet.get_payload()`` - Return the packet's payload as a string (Python 2) or bytes (Python 3). + 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. ``Packet.set_payload(payload)`` - Set the packet payload. ``payload`` is a bytes. + Set the packet payload. Call this before ``accept()`` if you want to + change the contents of the packet before allowing it to be released. + Don't forget to update the transport-layer checksum (or clear it, + if you're using UDP), or else the recipient is likely to drop the + packet. If you're changing the length of the packet, you'll also need + to update the IP length, IP header checksum, and probably some + transport-level fields (such as UDP length for UDP). ``Packet.get_payload_len()`` Return the size of the payload. ``Packet.set_mark(mark)`` - Give the packet a kernel mark. ``mark`` is a 32-bit number. + Give the packet a kernel mark, which can be used in future iptables + rules. ``mark`` is a 32-bit number. ``Packet.get_mark()`` - Get the mark already on the packet. + Get the mark already 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. +``Packet.retain()`` + Allocate a copy of the packet payload for use after the callback + has returned. ``get_payload()`` will raise an exception at that + point if you didn't call ``retain()``. + ``Packet.accept()`` - Accept the packet. + Accept the packet. You can reorder packets by accepting them + in a different order than the order in which they were passed + to your callback. ``Packet.drop()`` Drop the packet. - + ``Packet.repeat()`` - Iterate the same cycle once more. - + Restart processing of this packet from the beginning of its + Netfilter hook (iptables chain, roughly). Any changes made + using ``set_payload()`` or ``set_mark()`` are preserved; in the + absence of such changes, the packet will probably come right + back to the same queue. + Callback objects ---------------- -Your callback can be function or a method and must accept one argument, a -Packet object. You must call either Packet.accept() or Packet.drop() before -returning. - -``callback(packet)`` or ``callback(self, packet)`` - Handle a single packet from the queue. You must call either - ``packet.accept()`` or ``packet.drop()``. +Your callback can be any one-argument callable and will be invoked with +a ``Packet`` object as argument. You must call ``retain()`` within the +callback if you want to be able to ``get_payload()`` after the callback +has returned. You can hang onto ``Packet`` objects and resolve them later, +but note that packets continue to count against the queue size limit +until they've been given a verdict (accept, drop, or repeat). Also, the +kernel stores the enqueued packets in a linked list, so keeping lots of packets +outstanding is likely to adversely impact performance. Usage ===== @@ -181,12 +212,12 @@ Usage To send packets to the queue:: iptables -I -j NFQUEUE --queue-num - + For example:: iptables -I INPUT -d 192.168.0.0/24 -j NFQUEUE --queue-num 1 - -The only special part of the rule is the target. Rules can have any match and + +The only special part of the rule is the target. Rules can have any match and can be added to any table or chain. Valid queue numbers are integers from 0 to 65,535 inclusive. @@ -228,7 +259,7 @@ Limitations * Omits methods for getting information about the interface a packet has arrived on or is leaving on * Probably other stuff is omitted too - + Source ====== @@ -237,7 +268,7 @@ https://github.com/kti/python-netfilterqueue License ======= -Copyright (c) 2011, Kerkhoff Technologies, Inc. +Copyright (c) 2011, Kerkhoff Technologies, Inc, and contributors. `MIT licensed `_ diff --git a/tests/test_basic.py b/tests/test_basic.py index ef68784..dbc39b2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -90,6 +90,6 @@ async def test_errors(harness): from netfilterqueue import NetfilterQueue nfq = NetfilterQueue() - nfq.bind(1, lambda p: None) + nfq.bind(1, lambda p: None, sock_len=1024) with pytest.raises(RuntimeError, match="A queue is already bound"): - nfq.bind(2, lambda p: None) + nfq.bind(2, lambda p: None, sock_len=1024)