Skip to content

Commit a935aad

Browse files
authored
Merge pull request oremanj#78 from oremanj/external-fd
Add a parameter NetfilterQueue(sockfd=N) for using an externally-allocated netlink socket
2 parents 6fb345e + 6faaa7f commit a935aad

File tree

5 files changed

+139
-13
lines changed

5 files changed

+139
-13
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ v1.0.0, unreleased
44
Raise an error if a packet verdict is set after its parent queue is closed
55
set_payload() now affects the result of later get_payload()
66
Handle signals received when run() is blocked in recv()
7+
Accept packets in COPY_META mode, only failing on an attempt to access the payload
8+
Add a parameter NetfilterQueue(sockfd=N) that uses an already-opened Netlink socket
79

810
v0.9.0, 12 Jan 2021
911
Improve usability when Packet objects are retained past the callback

README.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,31 @@ until they've been given a verdict (accept, drop, or repeat). Also, the
238238
kernel stores the enqueued packets in a linked list, so keeping lots of packets
239239
outstanding is likely to adversely impact performance.
240240

241+
Monitoring a different network namespace
242+
----------------------------------------
243+
244+
If you are using Linux network namespaces (``man 7
245+
network_namespaces``) in some kind of containerization system, all of
246+
the Netfilter queue state is kept per-namespace; queue 1 in namespace
247+
X is not the same as queue 1 in namespace Y. NetfilterQueue will
248+
ordinarily pass you the traffic for the network namespace you're a
249+
part of. If you want to monitor a different one, you can do so with a
250+
bit of trickery and cooperation from a process in that
251+
namespace; this section describes how.
252+
253+
You'll need to arrange for a process in the network namespace you want
254+
to monitor to call ``socket(AF_NETLINK, SOCK_RAW, 12)`` and pass you
255+
the resulting file descriptor using something like
256+
``socket.send_fds()`` over a Unix domain socket. (12 is
257+
``NETLINK_NETFILTER``, a constant which is not exposed by the Python
258+
``socket`` module.) Once you've received that file descriptor in your
259+
process, you can create a NetfilterQueue object using the special
260+
constructor ``NetfilterQueue(sockfd=N)`` where N is the file
261+
descriptor you received. Because the socket was originally created
262+
in the other network namespace, the kernel treats it as part of that
263+
namespace, and you can use it to access that namespace even though it's
264+
not the namespace you're in yourself.
265+
241266
Usage
242267
=====
243268

netfilterqueue.pxd

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
cdef extern from "sys/types.h":
1+
cdef extern from "<sys/types.h>":
22
ctypedef unsigned char u_int8_t
33
ctypedef unsigned short int u_int16_t
44
ctypedef unsigned int u_int32_t
55

6+
cdef extern from "<unistd.h>":
7+
int dup2(int oldfd, int newfd)
8+
69
cdef extern from "<errno.h>":
710
int errno
811

@@ -13,7 +16,7 @@ cdef enum:
1316
EWOULDBLOCK = EAGAIN
1417
ENOBUFS = 105 # No buffer space available
1518

16-
cdef extern from "netinet/ip.h":
19+
cdef extern from "<netinet/ip.h>":
1720
struct iphdr:
1821
u_int8_t tos
1922
u_int16_t tot_len
@@ -60,15 +63,15 @@ cdef extern from "Python.h":
6063
object PyBytes_FromStringAndSize(char *s, Py_ssize_t len)
6164
object PyString_FromStringAndSize(char *s, Py_ssize_t len)
6265

63-
cdef extern from "sys/time.h":
66+
cdef extern from "<sys/time.h>":
6467
ctypedef long time_t
6568
struct timeval:
6669
time_t tv_sec
6770
time_t tv_usec
6871
struct timezone:
6972
pass
7073

71-
cdef extern from "netinet/in.h":
74+
cdef extern from "<netinet/in.h>":
7275
u_int32_t ntohl (u_int32_t __netlong) nogil
7376
u_int16_t ntohs (u_int16_t __netshort) nogil
7477
u_int32_t htonl (u_int32_t __hostlong) nogil
@@ -83,6 +86,9 @@ cdef extern from "libnfnetlink/linux_nfnetlink.h":
8386
cdef extern from "libnfnetlink/libnfnetlink.h":
8487
struct nfnl_handle:
8588
pass
89+
nfnl_handle *nfnl_open()
90+
void nfnl_close(nfnl_handle *h)
91+
int nfnl_fd(nfnl_handle *h)
8692
unsigned int nfnl_rcvbufsiz(nfnl_handle *h, unsigned int size)
8793

8894
cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h":
@@ -106,6 +112,7 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
106112
u_int8_t hw_addr[8]
107113

108114
nfq_handle *nfq_open()
115+
nfq_handle *nfq_open_nfnl(nfnl_handle *h)
109116
int nfq_close(nfq_handle *h)
110117

111118
int nfq_bind_pf(nfq_handle *h, u_int16_t pf)
@@ -153,8 +160,9 @@ cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
153160
cdef enum: # Protocol families, same as address families.
154161
PF_INET = 2
155162
PF_INET6 = 10
163+
PF_NETLINK = 16
156164

157-
cdef extern from "sys/socket.h":
165+
cdef extern from "<sys/socket.h>":
158166
ssize_t recv(int __fd, void *__buf, size_t __n, int __flags) nogil
159167
int MSG_DONTWAIT
160168

netfilterqueue.pyx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,47 @@ cdef class Packet:
193193

194194
cdef class NetfilterQueue:
195195
"""Handle a single numbered queue."""
196-
def __cinit__(self, *args, **kwargs):
197-
cdef u_int16_t af # Address family
198-
af = kwargs.get("af", PF_INET)
196+
def __cinit__(self, *, u_int16_t af = PF_INET, int sockfd = -1):
197+
cdef nfnl_handle *nlh = NULL
198+
try:
199+
if sockfd >= 0:
200+
# This is a hack to use the given Netlink socket instead
201+
# of the one allocated by nfq_open(). Intended use case:
202+
# the given socket was opened in a different network
203+
# namespace, and you want to monitor traffic in that
204+
# namespace from this process running outside of it.
205+
# Call socket(AF_NETLINK, SOCK_RAW, /*NETLINK_NETFILTER*/ 12)
206+
# in the other namespace and pass that fd here (via Unix
207+
# domain socket or similar).
208+
nlh = nfnl_open()
209+
if nlh == NULL:
210+
raise OSError(errno, "Failed to open nfnetlink handle")
211+
212+
# At this point nfnl_get_fd(nlh) is a new netlink socket
213+
# and has been bound to an automatically chosen port id.
214+
# This dup2 will close it, freeing up that address.
215+
if dup2(sockfd, nfnl_fd(nlh)) < 0:
216+
raise OSError(errno, "dup2 failed")
217+
218+
# Opening the netfilterqueue subsystem will rebind
219+
# the socket, using the same portid from the old socket,
220+
# which is hopefully now free. An alternative approach,
221+
# theoretically more robust against concurrent binds,
222+
# would be to autobind the new socket and write the chosen
223+
# address to nlh->local. nlh is an opaque type so this
224+
# would need to be done using memcpy (local starts
225+
# 4 bytes into the structure); let's avoid that unless
226+
# we really need it.
227+
self.h = nfq_open_nfnl(nlh)
228+
else:
229+
self.h = nfq_open()
230+
if self.h == NULL:
231+
raise OSError(errno, "Failed to open NFQueue.")
232+
except:
233+
if nlh != NULL:
234+
nfnl_close(nlh)
235+
raise
199236

200-
self.h = nfq_open()
201-
if self.h == NULL:
202-
raise OSError("Failed to open NFQueue.")
203237
nfq_unbind_pf(self.h, af) # This does NOT kick out previous queues
204238
if nfq_bind_pf(self.h, af) < 0:
205239
raise OSError("Failed to bind family %s. Are you root?" % af)

tests/test_basic.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import gc
22
import struct
3-
import trio
4-
import trio.testing
3+
import os
54
import pytest
65
import signal
76
import socket
87
import sys
98
import time
9+
import trio
10+
import trio.testing
1011
import weakref
1112

1213
from netfilterqueue import NetfilterQueue, COPY_META
@@ -261,5 +262,61 @@ def raise_alarm(sig, frame):
261262
nfq.run()
262263
assert any("NetfilterQueue.run" in line.name for line in exc_info.traceback)
263264
finally:
265+
nfq.unbind()
264266
signal.setitimer(signal.ITIMER_REAL, *old_timer)
265267
signal.signal(signal.SIGALRM, old_handler)
268+
269+
270+
async def test_external_fd(harness):
271+
child_prog = """
272+
import os, sys, unshare
273+
from netfilterqueue import NetfilterQueue
274+
unshare.unshare(unshare.CLONE_NEWNET)
275+
nfq = NetfilterQueue(sockfd=int(sys.argv[1]))
276+
def cb(pkt):
277+
pkt.accept()
278+
sys.exit(pkt.get_payload()[28:].decode("ascii"))
279+
nfq.bind(1, cb, sock_len=131072)
280+
os.write(1, b"ok\\n")
281+
try:
282+
nfq.run()
283+
finally:
284+
nfq.unbind()
285+
"""
286+
async with trio.open_nursery() as nursery:
287+
288+
async def monitor_in_child(task_status):
289+
with trio.fail_after(5):
290+
r, w = os.pipe()
291+
# 12 is NETLINK_NETFILTER family
292+
nlsock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 12)
293+
294+
@nursery.start_soon
295+
async def wait_started():
296+
await trio.lowlevel.wait_readable(r)
297+
assert b"ok\n" == os.read(r, 16)
298+
nlsock.close()
299+
os.close(w)
300+
os.close(r)
301+
task_status.started()
302+
303+
result = await trio.run_process(
304+
[sys.executable, "-c", child_prog, str(nlsock.fileno())],
305+
stdout=w,
306+
capture_stderr=True,
307+
check=False,
308+
pass_fds=(nlsock.fileno(),),
309+
)
310+
assert result.stderr == b"this is a test\n"
311+
312+
await nursery.start(monitor_in_child)
313+
async with harness.enqueue_packets_to(2, queue_num=1):
314+
await harness.send(2, b"this is a test")
315+
await harness.expect(2, b"this is a test")
316+
317+
with pytest.raises(OSError, match="dup2 failed"):
318+
NetfilterQueue(sockfd=1000)
319+
320+
with pytest.raises(OSError, match="Failed to open NFQueue"):
321+
with open("/dev/null") as fp:
322+
NetfilterQueue(sockfd=fp.fileno())

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy