Skip to content

nflog

nflog

Read denied packets via AF_NETLINK NFLOG and emit watch events.

Subscribes to the kernel's nflog group to receive copies of packets that matched log group rules in the nft ruleset. Extracts destination IP, port, and log prefix from each message.

Optional — NflogWatcher.create() returns None if netlink is unavailable (missing kernel module, insufficient permissions).

NflogWatcher(sock, container)

Read NFLOG messages via AF_NETLINK and yield events for denied packets.

Wrap an already-bound NFLOG netlink socket.

Use :meth:create instead of calling this directly.

Source code in src/terok_shield/lib/watchers/nflog.py
def __init__(self, sock: socket.socket, container: str) -> None:
    """Wrap an already-bound NFLOG netlink socket.

    Use :meth:`create` instead of calling this directly.
    """
    self._sock = sock
    self._container = container

create(container, group=NFLOG_GROUP) classmethod

Create and bind an NFLOG watcher, or return None on failure.

Failure is expected in environments without AF_NETLINK support, unprivileged containers, or missing kernel modules.

Parameters:

Name Type Description Default
container str

Container name (for event metadata).

required
group int

NFLOG group number to subscribe to.

NFLOG_GROUP
Source code in src/terok_shield/lib/watchers/nflog.py
@classmethod
def create(cls, container: str, group: int = NFLOG_GROUP) -> "NflogWatcher | None":
    """Create and bind an NFLOG watcher, or return ``None`` on failure.

    Failure is expected in environments without ``AF_NETLINK`` support,
    unprivileged containers, or missing kernel modules.

    Args:
        container: Container name (for event metadata).
        group: NFLOG group number to subscribe to.
    """
    sock: socket.socket | None = None
    try:
        sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, _NETLINK_NETFILTER)
        sock.bind((0, 0))

        # Handshake in blocking mode so the bind ACK is reliably received.
        sock.settimeout(2.0)
        sock.send(_build_nflog_bind_msg(group))
        ack = sock.recv(4096)
        if len(ack) >= _NLMSG_HDR.size + 4:
            err = struct.unpack_from("=i", ack, _NLMSG_HDR.size)[0]
            if err < 0:
                sock.close()
                logger.debug("NFLOG bind rejected (errno %d) — skipping", -err)
                return None

        # Switch to non-blocking for the poll() loop
        sock.setblocking(False)
        return cls(sock, container)
    except (OSError, AttributeError):
        # OSError: netlink/timeout unavailable; AttributeError: AF_NETLINK
        # missing on non-Linux platforms.
        logger.debug("NFLOG socket unavailable — skipping packet events")
        if sock is not None:
            sock.close()
        return None

fileno()

Return the file descriptor for select.select() multiplexing.

Source code in src/terok_shield/lib/watchers/nflog.py
def fileno(self) -> int:
    """Return the file descriptor for ``select.select()`` multiplexing."""
    return self._sock.fileno()

close()

Close the netlink socket.

Source code in src/terok_shield/lib/watchers/nflog.py
def close(self) -> None:
    """Close the netlink socket."""
    self._sock.close()

poll()

Read pending NFLOG messages and return watch events.

Source code in src/terok_shield/lib/watchers/nflog.py
def poll(self) -> list[WatchEvent]:
    """Read pending NFLOG messages and return watch events."""
    events: list[WatchEvent] = []
    while True:
        try:
            data = self._sock.recv(65535)
        except OSError:
            break
        if not data:
            break
        events.extend(self._parse_messages(data))
    return events