NetfilterQueue

repository·master·Indexed 42 days ago

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

A Python wrapper for libnetfilter_queue that allows developers to intercept, inspect, and modify Linux network packets matched by iptables rules. It provides a Packet API for payload manipulation and verdicts (accept, drop, repeat), and supports integration with async event loops like gevent and asyncio via file descriptors.

Tokens
2.5K
Snippets
6
Records
12
Agent score
5%

What's inside NetfilterQueue

  1. How callback functions work

    master

    A callback is any one-argument callable invoked with a Packet object.

    Key Lifecycle Rules:

    1. Payload Persistence: If you need to access the packet payload after the callback function has returned, you must call Packet.retain() within the callback. Otherwise, get_payload() will raise an exception.
    2. Queue Management: You can hold onto Packet objects and resolve them later, but they continue to count against the max_len limit of the queue until a verdict (accept(), drop(), or repeat()) is issued.
    3. Performance: The kernel stores enqueued packets in a linked list. Keeping a large number of packets outstanding can adversely impact system performance.
  2. Understand library limitations and missing data

    master

    When using python-netfilterqueue, be aware of the following technical limitations:

    Missing Packet Metadata:

    • Network interface names (input/output).
    • Checksum offload flags.
    • UID/GID and security context data.
    • Additional link-layer header data (e.g., VLAN tags).
    • Connection-tracking state.
    • Original incoming packet length (if truncated during queueing).

    Feature Limitations:

    • Queue Flags: The library does not expose the interface for changing queue flags (e.g., you cannot configure the queue to accept packets instead of dropping them when the queue is full).
    • Buffer Size: Fixed at 4096 bytes unless manually rebuilt via Cython.
  3. Monitoring a different network namespace

    master

    Netfilter queue state is isolated per Linux network namespace. To monitor a different namespace from your current process:

    1. In the target namespace, run a process that calls socket(AF_NETLINK, SOCK_RAW, 12) (where 12 is NETLINK_NETFILTER).
    2. Pass the resulting file descriptor to your monitoring process (e.g., via a Unix domain socket using socket.send_fds()).
    3. In your monitoring process, instantiate NetfilterQueue using the special constructor: NetfilterQueue(sockfd=N), where N is the received file descriptor.

    Because the socket was created in the target namespace, the kernel treats it as part of that namespace, allowing you to access its traffic.

  4. Configure iptables to route packets to NetfilterQueue

    master

    To send specific network traffic to your Python script, you must configure an iptables rule to divert packets to a Netfilter queue. The --queue-num in your iptables command must match the queue_num provided to NetfilterQueue.bind().

    Example: To send packets destined for the 192.168.0.0/24 subnet to queue number 1:

    iptables -I INPUT -d 192.168.0.0/24 -j NFQUEUE --queue-num 1
    iptables -I INPUT -d 192.168.0.0/24 -j NFQUEUE --queue-num 1
  5. Install NetfilterQueue

    master

    NetfilterQueue is a C extension module that links against libnetfilter_queue.

    Prerequisites

    Before installing, ensure you have:

    1. A C compiler
    2. Python development files
    3. libnetfilter_queue development files and associated dependencies

    On Debian or Ubuntu, install dependencies using:

    apt-get install build-essential python3-dev libnetfilter-queue-dev

    Installation Methods

    From PyPI

    pip install NetfilterQueue

    From source

    pip install cython
    git clone https://github.com/oremanj/python-netfilterqueue
    cd python-netfilterqueue
    pip install .
    apt-get install build-essential python3-dev libnetfilter-queue-dev
  6. Configure iptables to send packets to a NetfilterQueue

    master

    To intercept network traffic and send it to your Python application, you must use iptables to redirect packets to an NFQUEUE target. You can apply this to any table or chain with any match specification. The critical part is specifying the --queue-num to match the queue ID your Python script is listening on.

    Valid queue numbers are integers from 0 to 65,535 inclusive.

    # Syntax:
    # iptables -I <table or chain> <match specification> -j NFQUEUE --queue-num <queue number>
    
    # Example: Redirect all traffic from the 192.168.0.0/24 subnet in the INPUT chain to queue 1
    iptables -I INPUT -d 192.168.0.0/24 -j NFQUEUE --queue-num 1
  7. Handle packet truncation and buffer limitations

    master

    The library uses a fixed-size 4096-byte buffer for packets. This can cause packet truncation, particularly on loopback interfaces or Ethernet networks using jumbo packets.

    Solutions if truncation occurs:

    1. Lower the MTU on your loopback interface.
    2. Disable jumbo packets on your network.
    3. Rebuild with a larger buffer: If you have Cython installed, you can modify DEF BufferSize = 4096 in netfilterqueue.pyx and rebuild the package to increase the buffer size.
  8. Integrate NetfilterQueue with async event loops (gevent/asyncio)

    master

    You can integrate NetfilterQueue with async event loops by using its file descriptor or by providing your own socket.

    Option 1: Using the file descriptor Use nfqueue.get_fd() to get the socket file descriptor. You can poll this FD for readability and call nfqueue.run(block=False) whenever data is available.

    Option 2: Using a custom socket (e.g., for gevent) You can create a socket from the file descriptor and pass it to run_socket().

    from netfilterqueue import NetfilterQueue
    import socket
    
    def print_and_accept(pkt):
        print(pkt)
        pkt.accept()
    
    nfqueue = NetfilterQueue()
    nfqueue.bind(1, print_and_accept)
    s = socket.fromfd(nfqueue.get_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        nfqueue.run_socket(s)
    except KeyboardInterrupt:
        print('')
    
    s.close()
    nfqueue.unbind()
    from netfilterqueue import NetfilterQueue
    import socket
    
    def print_and_accept(pkt):
        print(pkt)
        pkt.accept()
    
    nfqueue = NetfilterQueue()
    nfqueue.bind(1, print_and_accept)
    s = socket.fromfd(nfqueue.get_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        nfqueue.run_socket(s)
    except KeyboardInterrupt:
        print('')
    
    s.close()
    nfqueue.unbind()
  9. Basic usage of NetfilterQueue

    master

    To use NetfilterQueue, create a NetfilterQueue object, bind it to a specific queue number with a callback function, and then call run() to start processing packets.

    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()
    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()
  10. NetfilterQueue API Reference

    master

    The NetfilterQueue class manages a single queue.

    Methods

    • bind(queue_num, callback, max_len=1024, mode=COPY_PACKET, range=65535, sock_len=...): Binds to a specific queue.
      • queue_num: Must match the --queue-num in your iptables rule.
      • callback: A function receiving a Packet object.
      • max_len: Maximum number of packets in the queue before new packets are dropped.
      • mode: Determines how much packet data is provided (see constants).
      • range: Number of bytes of the packet to retrieve.
    • unbind(): Removes the queue. Packets matched by the iptables rule will be dropped.
    • get_fd(): Returns the file descriptor of the socket used for receiving packets and sending verdicts.
    • run(block=True): Starts the packet processing loop. Set block=False for non-blocking integration with async loops.
    • run_socket(socket): Uses a supplied socket instead of the internal one (useful for gevent monkeypatching).

    Constants (Modes)

    • NetfilterQueue.COPY_NONE: No packet data provided.
    • NetfilterQueue.COPY_META: Only metadata provided.
    • NetfilterQueue.COPY_PACKET: The entire packet is provided.
  11. Packet API Reference

    master

    The Packet object is passed to your callback function. It allows you to inspect and modify packets.

    Payload Manipulation

    • get_payload(): Returns the packet's payload as bytes (starting with the IP header). Note: You must call retain() if you want to access the payload after the callback returns.
    • set_payload(payload): Sets a new payload. Use this to alter packet contents before calling accept().
    • get_payload_len(): Returns the size of the payload.
    • retain(): Allocates a copy of the payload so get_payload() works after the callback returns.

    Verdicts

    • accept(): Accepts the packet (allows it to pass through).
    • drop(): Drops the packet.
    • repeat(): Restarts processing of this packet from the beginning of its Netfilter hook.

    Metadata and Inspection

    • set_mark(mark) / get_mark(): Set or get a 32-bit kernel mark for use in future iptables rules.
    • get_hw(): Returns the source hardware address (e.g., MAC address) as bytes or None.
    • get_timestamp(): Returns a floating-point Unix timestamp of when the packet was received.
    • id: The kernel-assigned identifier for the packet.
    • hw_protocol: The link-layer protocol (e.g., 0x0800 for IPv4 on Ethernet).
    • hook: The netfilter hook index (0: PREROUTING, 1: INPUT, 2: FORWARD, 3: OUTPUT, 4: POSTROUTING).
    • indev, outdev, physindev, physoutdev: Interface indices. Convert to names using socket.if_indextoname().
    • mark: The mark assigned to the packet when it was enqueued (does not change if set_mark() is called).
  12. Monitor libnetfilter_queue statistics

    master

    You can inspect the status of the underlying libnetfilter_queue by reading /proc/net/netfilter/nfnetlink_queue. This is useful for debugging packet drops or monitoring queue load.

    Field Mapping for /proc/net/netfilter/nfnetlink_queue:

    1. Queue ID
    2. Bound process ID
    3. Number of currently queued packets
    4. Copy mode
    5. Copy size
    6. Number of packets dropped due to reaching max queue size
    7. Number of packets dropped due to netlink socket failure
    8. Total number of packets sent to queue
    9. Internal libnetfilter_queue data
    cat /proc/net/netfilter/nfnetlink_queue