diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c0ce29..e181cea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,13 +15,14 @@ jobs: fail-fast: false matrix: python: - - '3.6' - '3.7' - '3.8' - '3.9' - '3.10' + - '3.11' - 'pypy-3.7' - 'pypy-3.8' + - 'pypy-3.9' check_lint: ['0'] extra_name: [''] include: diff --git a/CHANGES.txt b/CHANGES.txt index 37c092f..efa6dec 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +v1.1.0, unreleased + Add Packet accessors for {indev, outdev, physindev, physoutdev} interface indices + v1.0.0, 14 Jan 2022 Propagate exceptions raised by the user's packet callback Avoid calls to the packet callback during queue unbinding diff --git a/README.rst b/README.rst index 275e173..1ddafda 100644 --- a/README.rst +++ b/README.rst @@ -217,6 +217,18 @@ Objects of this type are passed to your callback. into our queue. Values 0 through 4 correspond to PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING respectively. +``Packet.indev``, ``Packet.outdev``, ``Packet.physindev``, ``Packet.physoutdev`` + The interface indices on which the packet arrived (``indev``) or is slated + to depart (``outdev``). These are integers, which can be converted to + names like "eth0" by using ``socket.if_indextoname()``. Zero means + no interface is applicable, either because the packet was locally generated + or locally received, or because the interface information wasn't available + when the packet was queued (for example, ``PREROUTING`` rules don't yet + know the ``outdev``). If the ``indev`` or ``outdev`` refers to a bridge + device, then the corresponding ``physindev`` or ``physoutdev`` will name + the bridge member on which the actual traffic occurred; otherwise + ``physindev`` and ``physoutdev`` will be zero. + ``Packet.retain()`` Allocate a copy of the packet payload for use after the callback has returned. ``get_payload()`` will raise an exception at that diff --git a/netfilterqueue/_impl.pxd b/netfilterqueue/_impl.pxd index 54c2fa3..d202b41 100644 --- a/netfilterqueue/_impl.pxd +++ b/netfilterqueue/_impl.pxd @@ -153,7 +153,11 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h": int nfq_get_payload(nfq_data *nfad, unsigned char **data) int nfq_get_timestamp(nfq_data *nfad, timeval *tv) nfqnl_msg_packet_hw *nfq_get_packet_hw(nfq_data *nfad) - int nfq_get_nfmark (nfq_data *nfad) + int nfq_get_nfmark(nfq_data *nfad) + u_int32_t nfq_get_indev(nfq_data *nfad) + u_int32_t nfq_get_outdev(nfq_data *nfad) + u_int32_t nfq_get_physindev(nfq_data *nfad) + u_int32_t nfq_get_physoutdev(nfq_data *nfad) nfnl_handle *nfq_nfnlh(nfq_handle *h) # Dummy defines from linux/socket.h: @@ -184,8 +188,7 @@ cdef class NetfilterQueue: cdef class Packet: cdef NetfilterQueue _queue - cdef bint _verdict_is_set # True if verdict has been issued, - # false otherwise + 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 @@ -204,13 +207,10 @@ cdef class Packet: cdef unsigned char *payload cdef timeval timestamp cdef u_int8_t hw_addr[8] - - # TODO: implement these - #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 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, NetfilterQueue queue, nfq_data *nfa) cdef drop_refs(self) diff --git a/netfilterqueue/_impl.pyi b/netfilterqueue/_impl.pyi index 3cba8fc..28c3080 100644 --- a/netfilterqueue/_impl.pyi +++ b/netfilterqueue/_impl.pyi @@ -11,6 +11,11 @@ class Packet: hw_protocol: int id: int mark: int + # These are ifindexes, pass to socket.if_indextoname() to get names: + indev: int + outdev: int + physindev: int + physoutdev: int def get_hw(self) -> Optional[bytes]: ... def get_payload(self) -> bytes: ... def get_payload_len(self) -> int: ... diff --git a/netfilterqueue/_impl.pyx b/netfilterqueue/_impl.pyx index a8e1960..24b0bed 100644 --- a/netfilterqueue/_impl.pyx +++ b/netfilterqueue/_impl.pyx @@ -99,6 +99,10 @@ cdef class Packet: nfq_get_timestamp(nfa, &self.timestamp) self.mark = nfq_get_nfmark(nfa) + self.indev = nfq_get_indev(nfa) + self.outdev = nfq_get_outdev(nfa) + self.physindev = nfq_get_physindev(nfa) + self.physoutdev = nfq_get_physoutdev(nfa) cdef drop_refs(self): """ diff --git a/tests/test_basic.py b/tests/test_basic.py index 99a331b..dbcafa1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -119,11 +119,13 @@ async def test_mark_repeat(harness): assert t0 < timestamps[0] < t1 -async def test_hwaddr(harness): +async def test_hwaddr_and_inoutdev(harness): hwaddrs = [] + inoutdevs = [] def cb(pkt): hwaddrs.append((pkt.get_hw(), pkt.hook, pkt.get_payload()[28:])) + inoutdevs.append((pkt.indev, pkt.outdev)) pkt.accept() queue_num, nfq = harness.bind_queue(cb) @@ -162,6 +164,20 @@ async def test_hwaddr(harness): (None, OUTPUT, b"four"), ] + if sys.implementation.name != "pypy": + # pypy doesn't appear to provide if_nametoindex() + iface1 = socket.if_nametoindex("veth1") + iface2 = socket.if_nametoindex("veth2") + else: + iface1, iface2 = inoutdevs[0] + assert 0 != iface1 != iface2 != 0 + assert inoutdevs == [ + (iface1, iface2), + (iface1, iface2), + (0, iface2), + (0, iface2), + ] + async def test_errors(harness): with pytest.warns(RuntimeWarning, match="rcvbuf limit is") as record: