close
Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9587d75
Modernize, add tests, allow Packet to outlive the callback it's passe…
oremanj Jan 12, 2022
a53c4d0
Make ci.sh executable
oremanj Jan 12, 2022
efcf94c
Fix package name
oremanj Jan 12, 2022
5d1d123
Use newer Cython when building
oremanj Jan 12, 2022
e7d4510
Use an actual warning, and avoid it in CI
oremanj Jan 12, 2022
99c13b3
Improve & test error handling
oremanj Jan 12, 2022
ba0e8be
Suppress warning again + update README
oremanj Jan 12, 2022
c2d7ce8
Suppress warning harder
oremanj Jan 12, 2022
afcee0d
Merge pull request #74 from oremanj/updates
oremanj Jan 12, 2022
a5578d3
Misc CI and packaging updates
oremanj Jan 13, 2022
ed4a63d
Merge pull request #75 from oremanj/ci-updates
oremanj Jan 13, 2022
8d3193f
Release v0.9.0
oremanj Jan 13, 2022
ddbc12a
Merge pull request #76 from oremanj/release-v0.9.0
oremanj Jan 13, 2022
0187c89
Fixes for several open issues
oremanj Jan 13, 2022
2c782b9
CI fixes
oremanj Jan 13, 2022
62da18e
Document and test remaining attributes/methods; fix PyPy tests by not…
oremanj Jan 14, 2022
305b258
Fix CI, make COPY_META mode work
oremanj Jan 14, 2022
c7fd3e5
Fix CI on 3.8+
oremanj Jan 14, 2022
6fb345e
Merge pull request #77 from oremanj/fixes
oremanj Jan 14, 2022
53e2db3
Add a parameter NetfilterQueue(sockfd=N) for using an externally-allo…
oremanj Jan 14, 2022
6faaa7f
Add docs
oremanj Jan 14, 2022
a935aad
Merge pull request #78 from oremanj/external-fd
oremanj Jan 14, 2022
1cbea51
Make netfilterqueue a package and add type hints
oremanj Jan 14, 2022
541c9e7
Switch back to _impl and fix names
oremanj Jan 14, 2022
db80c85
Fix stub
oremanj Jan 14, 2022
e1e20d4
Don't install _impl.c
oremanj Jan 14, 2022
ebeb8a7
Merge pull request #79 from oremanj/packagify
oremanj Jan 14, 2022
c03aec2
Release v1.0.0
oremanj Jan 14, 2022
69436c1
Bump version to 1.0.0+dev post release
oremanj Jan 14, 2022
a3dedae
Merge pull request #80 from oremanj/release-v1.0.0
oremanj Jan 14, 2022
9f460d2
Check whether payload is NULL before accessing it in __str__
oremanj Apr 19, 2022
0bb948d
Merge pull request #89 from oremanj/fix-str-segfault
oremanj Apr 19, 2022
49e1681
Add Packet accessors for interface indices
oremanj Mar 1, 2023
2b6849b
Merge pull request #93 from oremanj/inoutdev
oremanj Mar 1, 2023
8467650
Update README: python-dev is no more
oremanj Mar 1, 2023
e385572
Bump version to 1.1.0 for release
oremanj Mar 1, 2023
e11aaf6
Bump version to 1.1.0+dev post release
oremanj Mar 1, 2023
542159a
Merge pull request #94 from oremanj/release-v1.1.0
oremanj May 3, 2023
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
Prev Previous commit
Next Next commit
Make netfilterqueue a package and add type hints
  • Loading branch information
oremanj committed Jan 14, 2022
commit 1cbea513e6fe1d9cd8ec5fffffda2bc3fc1e53a2
4 changes: 3 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
v1.0.0, unreleased
Propagate exceptions raised by the user's packet callback
Warn about exceptions raised by the packet callback during queue unbinding
Avoid calls to 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()
Accept packets in COPY_META mode, only failing on an attempt to access the payload
Add a parameter NetfilterQueue(sockfd=N) that uses an already-opened Netlink socket
Add type hints
Remove the Packet.payload attribute; it was never safe (treated as a char* but not NUL-terminated) nor documented, but was exposed in the API (perhaps inadvertently).

v0.9.0, 12 Jan 2021
Improve usability when Packet objects are retained past the callback
Expand Down
9 changes: 3 additions & 6 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
include *.txt
include *.rst
include *.c
include *.pyx
include *.pxd
recursive-include tests/ *.py
include LICENSE.txt README.rst CHANGES.txt
recursive-include netfilterqueue *.py *.pyx *.pxd *.c *.pyi py.typed
recursive-include tests *.py
26 changes: 17 additions & 9 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,42 @@ python setup.py sdist --formats=zip

# ... but not to install it
pip uninstall -y cython
python setup.py build_ext
pip install dist/*.zip

pip install -Ur test-requirements.txt

if [ "$CHECK_LINT" = "1" ]; then
error=0
if ! black --check setup.py tests; then
black_files="setup.py tests netfilterqueue"
if ! black --check $black_files; then
error=$?
black --diff $black_files
fi
mypy --strict -p netfilterqueue || error=$?
( mkdir empty; cd empty; python -m mypy.stubtest netfilterqueue ) || error=$?

if [ $error -ne 0 ]; then
cat <<EOF
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Formatting problems were found (listed above). To fix them, run
Problems were found by static analysis (listed above).
To fix formatting and see remaining errors, run:

pip install -r test-requirements.txt
black setup.py tests
black $black_files
mypy --strict -p netfilterqueue
( mkdir empty; cd empty; python -m mypy.stubtest netfilterqueue )

in your local checkout.

EOF
error=1
fi
if [ "$error" = "1" ]; then
cat <<EOF
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
EOF
exit 1
fi
exit $error
exit 0
fi

cd tests
Expand Down
2 changes: 1 addition & 1 deletion netfilterqueue.pxd → netfilterqueue/__init__.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ cdef class Packet:

# Packet details:
cdef Py_ssize_t payload_len
cdef readonly unsigned char *payload
cdef unsigned char *payload
cdef timeval timestamp
cdef u_int8_t hw_addr[8]

Expand Down
45 changes: 45 additions & 0 deletions netfilterqueue/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import socket
from enum import IntEnum
from typing import Callable, Dict, Optional, Tuple

__version__: str
VERSION: Tuple[int, ...]

COPY_NONE: int
COPY_META: int
COPY_PACKET: int

class Packet:
hook: int
hw_protocol: int
id: int
mark: int
def get_hw(self) -> Optional[bytes]: ...
def get_payload(self) -> bytes: ...
def get_payload_len(self) -> int: ...
def get_timestamp(self) -> float: ...
def get_mark(self) -> int: ...
def set_payload(self, payload: bytes) -> None: ...
def set_mark(self, mark: int) -> None: ...
def retain(self) -> None: ...
def accept(self) -> None: ...
def drop(self) -> None: ...
def repeat(self) -> None: ...

class NetfilterQueue:
def __new__(self, *, af: int = ..., sockfd: int = ...) -> NetfilterQueue: ...
def bind(
self,
queue_num: int,
user_callback: Callable[[Packet], None],
max_len: int = ...,
mode: int = COPY_PACKET,
range: int = ...,
sock_len: int = ...,
) -> None: ...
def unbind(self) -> None: ...
def get_fd(self) -> int: ...
def run(self, block: bool = ...) -> None: ...
def run_socket(self, s: socket.socket) -> None: ...

PROTOCOLS: Dict[int, str]
5 changes: 4 additions & 1 deletion netfilterqueue.pyx → netfilterqueue/__init__.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ function.
Copyright: (c) 2011, Kerkhoff Technologies Inc.
License: MIT; see LICENSE.txt
"""
VERSION = (0, 9, 0)

# Constants for module users
COPY_NONE = 0
Expand All @@ -24,6 +23,10 @@ DEF SockCopySize = MaxCopySize + SockOverhead
# Socket queue should hold max number of packets of copysize bytes
DEF SockRcvSize = DEFAULT_MAX_QUEUELEN * SockCopySize // 2

__package__ = "netfilterqueue"

from ._version import __version__, VERSION

from cpython.exc cimport PyErr_CheckSignals

# A negative return value from this callback will stop processing and
Expand Down
4 changes: 4 additions & 0 deletions netfilterqueue/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file is imported from __init__.py and exec'd from setup.py

__version__ = "0.9.0+dev"
VERSION = (0, 9, 0)
Empty file added netfilterqueue/py.typed
Empty file.
26 changes: 17 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os, sys
from setuptools import setup, Extension

VERSION = "0.9.0" # Remember to change CHANGES.txt and netfilterqueue.pyx when version changes.
exec(open("netfilterqueue/_version.py", encoding="utf-8").read())

setup_requires = []
try:
Expand All @@ -10,7 +10,9 @@

ext_modules = cythonize(
Extension(
"netfilterqueue", ["netfilterqueue.pyx"], libraries=["netfilter_queue"]
"netfilterqueue.__init__",
["netfilterqueue/__init__.pyx"],
libraries=["netfilter_queue"],
),
compiler_directives={"language_level": "3str"},
)
Expand All @@ -21,7 +23,7 @@
# setup_requires below.
setup_requires = ["cython"]
elif not os.path.exists(
os.path.join(os.path.dirname(__file__), "netfilterqueue.c")
os.path.join(os.path.dirname(__file__), "netfilterqueue/__init__.c")
):
sys.stderr.write(
"You must have Cython installed (`pip install cython`) to build this "
Expand All @@ -31,21 +33,27 @@
)
sys.exit(1)
ext_modules = [
Extension("netfilterqueue", ["netfilterqueue.c"], libraries=["netfilter_queue"])
Extension(
"netfilterqueue.__init__",
["netfilterqueue/__init__.c"],
libraries=["netfilter_queue"],
)
]

setup(
ext_modules=ext_modules,
setup_requires=setup_requires,
python_requires=">=3.6",
name="NetfilterQueue",
version=VERSION,
version=__version__,
license="MIT",
author="Matthew Fox",
author_email="matt@tansen.ca",
url="https://github.com/oremanj/python-netfilterqueue",
description="Python bindings for libnetfilter_queue",
long_description=open("README.rst").read(),
long_description=open("README.rst", encoding="utf-8").read(),
packages=["netfilterqueue"],
ext_modules=ext_modules,
include_package_data=True,
setup_requires=setup_requires,
python_requires=">=3.6",
classifiers=[
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
Expand Down
1 change: 1 addition & 0 deletions test-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pytest-trio
async_generator
black
platformdirs <= 2.4.0 # needed by black; 2.4.1+ don't support py3.6
mypy; implementation_name == "cpython"
14 changes: 11 additions & 3 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ idna==3.3
# via trio
iniconfig==1.1.1
# via pytest
mypy==0.931 ; implementation_name == "cpython"
# via -r test-requirements.in
mypy-extensions==0.4.3
# via black
# via
# black
# mypy
outcome==1.1.0
# via
# pytest-trio
Expand Down Expand Up @@ -57,10 +61,14 @@ sortedcontainers==2.4.0
toml==0.10.2
# via pytest
tomli==1.2.3
# via black
# via
# black
# mypy
trio==0.19.0
# via
# -r test-requirements.in
# pytest-trio
typing-extensions==4.0.1
# via black
# via
# black
# mypy
40 changes: 21 additions & 19 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import subprocess
import sys
import trio
import unshare
import unshare # type: ignore
import netfilterqueue
from functools import partial
from typing import AsyncIterator, Callable, Optional, Tuple
from typing import Any, AsyncIterator, Callable, Dict, Optional, Tuple
from async_generator import asynccontextmanager
from pytest_trio.enable_trio_mode import *
from pytest_trio.enable_trio_mode import * # type: ignore


# We'll create three network namespaces, representing a router (which
Expand Down Expand Up @@ -45,8 +45,8 @@ def enter_netns() -> None:
subprocess.run("/sbin/ip link set lo up".split(), check=True)


@pytest.hookimpl(tryfirst=True)
def pytest_runtestloop():
@pytest.hookimpl(tryfirst=True) # type: ignore
def pytest_runtestloop() -> None:
if os.getuid() != 0:
# Create a new user namespace for the whole test session
outer = {"uid": os.getuid(), "gid": os.getgid()}
Expand Down Expand Up @@ -93,7 +93,9 @@ async def peer_main(idx: int, parent_fd: int) -> None:
await peer.connect((peer_ip, peer_port))

# Enter the message-forwarding loop
async def proxy_one_way(src, dest):
async def proxy_one_way(
src: trio.socket.SocketType, dest: trio.socket.SocketType
) -> None:
while src.fileno() >= 0:
try:
msg = await src.recv(4096)
Expand Down Expand Up @@ -121,13 +123,13 @@ def _default_capture_cb(


class Harness:
def __init__(self):
self._received = {}
self._conn = {}
self.dest_addr = {}
def __init__(self) -> None:
self._received: Dict[int, trio.MemoryReceiveChannel[bytes]] = {}
self._conn: Dict[int, trio.socket.SocketType] = {}
self.dest_addr: Dict[int, Tuple[str, int]] = {}
self.failed = False

async def _run_peer(self, idx: int, *, task_status):
async def _run_peer(self, idx: int, *, task_status: Any) -> None:
their_ip = PEER_IP[idx]
my_ip = ROUTER_IP[idx]
conn, child_conn = trio.socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
Expand Down Expand Up @@ -169,10 +171,10 @@ async def _run_peer(self, idx: int, *, task_status):
# and its netns goes away. check=False to suppress that error.
await trio.run_process(f"ip link delete veth{idx}".split(), check=False)

async def _manage_peer(self, idx: int, *, task_status):
async def _manage_peer(self, idx: int, *, task_status: Any) -> None:
async with trio.open_nursery() as nursery:
await nursery.start(self._run_peer, idx)
packets_w, packets_r = trio.open_memory_channel(math.inf)
packets_w, packets_r = trio.open_memory_channel[bytes](math.inf)
self._received[idx] = packets_r
task_status.started()
async with packets_w:
Expand All @@ -183,7 +185,7 @@ async def _manage_peer(self, idx: int, *, task_status):
await packets_w.send(msg)

@asynccontextmanager
async def run(self):
async def run(self) -> AsyncIterator[None]:
async with trio.open_nursery() as nursery:
async with trio.open_nursery() as start_nursery:
start_nursery.start_soon(nursery.start, self._manage_peer, 1)
Expand Down Expand Up @@ -258,14 +260,14 @@ async def capture_packets_to(
**options: int,
) -> AsyncIterator["trio.MemoryReceiveChannel[netfilterqueue.Packet]"]:

packets_w, packets_r = trio.open_memory_channel(math.inf)
packets_w, packets_r = trio.open_memory_channel[netfilterqueue.Packet](math.inf)
queue_num, nfq = self.bind_queue(partial(cb, packets_w), **options)
try:
async with self.enqueue_packets_to(idx, queue_num):
async with packets_w, trio.open_nursery() as nursery:

@nursery.start_soon
async def listen_for_packets():
async def listen_for_packets() -> None:
while True:
await trio.lowlevel.wait_readable(nfq.get_fd())
nfq.run(block=False)
Expand All @@ -275,7 +277,7 @@ async def listen_for_packets():
finally:
nfq.unbind()

async def expect(self, idx: int, *packets: bytes):
async def expect(self, idx: int, *packets: bytes) -> None:
for expected in packets:
with trio.move_on_after(5) as scope:
received = await self._received[idx].receive()
Expand All @@ -291,13 +293,13 @@ async def expect(self, idx: int, *packets: bytes):
f"received {received!r}"
)

async def send(self, idx: int, *packets: bytes):
async def send(self, idx: int, *packets: bytes) -> None:
for packet in packets:
await self._conn[3 - idx].send(packet)


@pytest.fixture
async def harness() -> Harness:
async def harness() -> AsyncIterator[Harness]:
h = Harness()
async with h.run():
yield h
Expand Down