mirror of
https://github.com/lgcosta/pf-linux-port.git
synced 2026-11-13 16:28:03 -03:00
pflog: /dev/pflog chardev backend (BSD pflog_hdr layout, DLT_PFLOG) + tools/pflogd + gate 12
This commit is contained in:
1 parent
dbb21048e4
commit
7a81076c6a
11 files changed
+2401
-17
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
savedcmd_pf.mod := printf '%s\n' pfsrc/pfk_mod.o pfsrc/pfk_hooks.o compat/pfk_mbuf.o compat/pfk_kern.o compat/pfk_if.o compat/pfk_xmit.o compat/pfk_pcb.o compat/pfk_ip4.o compat/siphash.o compat/toeplitz.o compat/md5.o compat/sha2.o compat/radix.o compat/pfk_rtable.o compat/pfk_ip6.o compat/subr_tree.o pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o compat/pfk_ifq.o | awk '!x[$$0]++ { print("./"$$0) }' > pf.mod
|
||||
savedcmd_pf.mod := printf '%s\n' pfsrc/pfk_mod.o pfsrc/pfk_hooks.o compat/pfk_mbuf.o compat/pfk_kern.o compat/pfk_if.o compat/pfk_xmit.o compat/pfk_pcb.o compat/pfk_ip4.o compat/siphash.o compat/toeplitz.o compat/md5.o compat/sha2.o compat/radix.o compat/pfk_rtable.o compat/pfk_ip6.o compat/subr_tree.o pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o compat/pfk_ifq.o | awk '!x[$$0]++ { print("./"$$0) }' > pf.mod
|
||||
+1
-1
@@ -20,7 +20,7 @@ pf-objs := pfsrc/pfk_mod.o pfsrc/pfk_hooks.o \
|
||||
# ported OpenBSD 8.0 engine
|
||||
pf-objs += pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o \
|
||||
pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o \
|
||||
pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o
|
||||
pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o
|
||||
|
||||
# queueing framework (compat/pfk_ifq.c implements the ifq API with priq
|
||||
# semantics; the OpenBSD hfsc/fq_codel back ends land in milestone M5)
|
||||
|
||||
@@ -619,16 +619,5 @@ if_congested(void)
|
||||
}
|
||||
|
||||
|
||||
/* ---------------- pflog (netlink backend, milestone M4) ---------------- */
|
||||
/* NPFLOG=1: the staged if_pflog.c needs the OpenBSD ifpf layer; until the
|
||||
* netlink-backed pflog lands (M4), pflog_packet() consumes nothing and
|
||||
* reports the missing backend at least once so logged packets are never
|
||||
* silently vanished (see docs/porting-notes.md). */
|
||||
int
|
||||
pflog_packet(struct pf_pdesc *pd, u_int8_t reason, struct pf_rule *rm,
|
||||
struct pf_rule *am, struct pf_ruleset *ruleset, struct pf_rule *cm)
|
||||
{
|
||||
(void)pd; (void)reason; (void)rm; (void)am; (void)ruleset; (void)cm;
|
||||
pr_warn_once("pfk: pflog backend pending M4; packet not logged\n");
|
||||
return (0);
|
||||
}
|
||||
/* pflog_packet() lives in pfsrc/pfk_pflog.c (the /dev/pflog backend). */
|
||||
|
||||
@@ -1203,4 +1203,14 @@ int pf_anchor_stack_pop(struct pf_ruleset **, struct pf_rule **,
|
||||
struct inpcb;
|
||||
void pf_remove_divert_state(struct inpcb *);
|
||||
|
||||
/* pflog backend (/dev/pflog) - 'L' keeps the namespace clear of DIOC* ('P') */
|
||||
struct pfk_pflog_stat {
|
||||
u_int64_t queued; /* records currently queued */
|
||||
u_int64_t bytes; /* bytes queued */
|
||||
u_int64_t dropped; /* records dropped (queue full) */
|
||||
u_int64_t seq; /* records produced since load */
|
||||
};
|
||||
#define PFKIOCGETPFLOGSTAT _IOR('L', 1, struct pfk_pflog_stat)
|
||||
|
||||
#endif /* _PFK_H */
|
||||
|
||||
@@ -12,6 +12,8 @@ void pfk_status(struct pf_status *s);
|
||||
int pfk_hooks_register(void);
|
||||
void pfk_hooks_unregister(void);
|
||||
/* AF_DIVERT family (module-internal socket protocol, PF_DIVERT=32) */
|
||||
int pfk_pflog_register(void);
|
||||
void pfk_pflog_unregister(void);
|
||||
int pfk_divert_sock_register(void);
|
||||
void pfk_divert_sock_unregister(void);
|
||||
void divert_packet(struct mbuf *, int, u_int16_t);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -94,6 +94,8 @@ static struct miscdevice pfk_dev = {
|
||||
extern void pf_status_init(void);
|
||||
extern int pfattach(int);
|
||||
extern int pfk_divert_sock_register(void);
|
||||
extern int pfk_pflog_register(void);
|
||||
extern void pfk_pflog_unregister(void);
|
||||
extern void pfk_divert_sock_unregister(void);
|
||||
|
||||
static void
|
||||
@@ -133,9 +135,12 @@ static int __init pfk_init(void)
|
||||
pfattach(0);
|
||||
pfk_if_set_event_cb(pfk_pfi_event);
|
||||
pfk_if_iterate(pfk_pfi_attach_one);
|
||||
err = pfk_divert_sock_register();
|
||||
err = pfk_pflog_register();
|
||||
if (err)
|
||||
goto err_if;
|
||||
err = pfk_divert_sock_register();
|
||||
if (err)
|
||||
goto err_pflog;
|
||||
err = pfk_hooks_register();
|
||||
if (err)
|
||||
goto err_divert;
|
||||
@@ -147,6 +152,8 @@ static int __init pfk_init(void)
|
||||
err_hooks:
|
||||
pfk_divert_sock_unregister();
|
||||
err_divert:
|
||||
pfk_pflog_unregister();
|
||||
err_pflog:
|
||||
pfk_hooks_unregister();
|
||||
err_if:
|
||||
pfk_if_exit();
|
||||
@@ -166,6 +173,7 @@ static void __exit pfk_exit(void)
|
||||
* workqueues non-empty forever (observed rmmod hang). */
|
||||
misc_deregister(&pfk_dev);
|
||||
pfk_divert_sock_unregister();
|
||||
pfk_pflog_unregister();
|
||||
pfk_net_lock();
|
||||
pf_status.running = 0;
|
||||
pfk_net_unlock();
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* PF-on-Linux: pflog backend (ISC 2026)
|
||||
*
|
||||
* OpenBSD logs matched packets to the pflog(4) *network interface*, which
|
||||
* pflogd(8) reads through bpf(4). Linux has neither if_pflog-style virtual
|
||||
* interfaces exposed to bpf nor a DLT_PFLOG consumer, so this port offers the
|
||||
* same record format over a character device:
|
||||
*
|
||||
* /dev/pflog (0600) - read() returns one record at a time:
|
||||
* struct pflog_hdr followed by the captured
|
||||
* packet bytes (truncated to PFLOG_COPY_SIZE).
|
||||
*
|
||||
* poll() reports readable when a record is queued. A single consumer is the
|
||||
* real use case (pflogd), which is why a chardev - not netlink multicast -
|
||||
* was chosen; the deviation is documented in docs/porting-notes.md.
|
||||
*
|
||||
* The records are byte-compatible with OpenBSD's pflog(4) so tcpdump/wireshark
|
||||
* read the pcap with linktype DLT_PFLOG (117).
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
#include <pfk.h>
|
||||
#include <pfk_api.h>
|
||||
|
||||
/* OpenBSD sys/net/pflog.h */
|
||||
#define PFLOG_IFNAMSIZ 16
|
||||
#define PFLOG_RULESET_NAME_SIZE 16
|
||||
#define PFLOG_HDRLEN 64 /* sizeof(struct pfk_pflog_hdr) */
|
||||
#define PFLOG_COPY_SIZE 160
|
||||
|
||||
/* byte-compatible with OpenBSD's <net/pflog.h> (sizeof == 64) */
|
||||
struct pfk_pflog_hdr {
|
||||
struct {
|
||||
s64 tv_sec;
|
||||
s64 tv_usec;
|
||||
} pflog_ts;
|
||||
u8 pflog_af;
|
||||
u8 pflog_reason;
|
||||
u8 pflog_ifname[PFLOG_IFNAMSIZ];
|
||||
u8 pflog_ruleset[PFLOG_RULESET_NAME_SIZE];
|
||||
u8 pflog_nr;
|
||||
u8 pflog_anc;
|
||||
u8 pflog_flags;
|
||||
u8 pflog_pad[9];
|
||||
u16 pflog_len; /* captured packet length */
|
||||
} __packed;
|
||||
|
||||
struct pfk_pflog_rec {
|
||||
struct list_head list;
|
||||
size_t len; /* header + payload */
|
||||
char data[]; /* pfk_pflog_hdr, then the packet */
|
||||
};
|
||||
|
||||
#define PFK_PFLOG_MAX_QUEUE 256u
|
||||
#define PFK_PFLOG_MAX_BYTES (1u << 20)
|
||||
|
||||
static LIST_HEAD(pflog_list);
|
||||
static DEFINE_SPINLOCK(pflog_lock);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(pflog_wq);
|
||||
static unsigned long pflog_queued, pflog_bytes, pflog_dropped;
|
||||
static u64 pflog_seq;
|
||||
|
||||
/*
|
||||
* Queue one record. Called from the packet path (process context here: the
|
||||
* data path runs in pfk_netisr_wq), so a GFP_ATOMIC allocation and a bounded
|
||||
* queue keep it safe even if a future caller appears in softirq context.
|
||||
*/
|
||||
static void
|
||||
pfk_pflog_put(struct pfk_pflog_hdr *hdr, const void *pkt, size_t pktlen)
|
||||
{
|
||||
struct pfk_pflog_rec *rec;
|
||||
size_t len = sizeof(*hdr) + pktlen;
|
||||
unsigned long flags;
|
||||
|
||||
if (READ_ONCE(pflog_queued) >= PFK_PFLOG_MAX_QUEUE ||
|
||||
READ_ONCE(pflog_bytes) + len > PFK_PFLOG_MAX_BYTES) {
|
||||
pflog_dropped++;
|
||||
return;
|
||||
}
|
||||
|
||||
rec = kmalloc(struct_size(rec, data, len), GFP_ATOMIC);
|
||||
if (rec == NULL) {
|
||||
pflog_dropped++;
|
||||
return;
|
||||
}
|
||||
rec->len = len;
|
||||
memcpy(rec->data, hdr, sizeof(*hdr));
|
||||
memcpy(rec->data + sizeof(*hdr), pkt, pktlen);
|
||||
|
||||
spin_lock_irqsave(&pflog_lock, flags);
|
||||
list_add_tail(&rec->list, &pflog_list);
|
||||
pflog_queued++;
|
||||
pflog_bytes += len;
|
||||
pflog_seq++;
|
||||
spin_unlock_irqrestore(&pflog_lock, flags);
|
||||
|
||||
wake_up_interruptible(&pflog_wq);
|
||||
}
|
||||
|
||||
/* copy the leading bytes of the packet (the mbuf chain may be a wrapper) */
|
||||
static size_t
|
||||
pfk_pflog_pull(struct mbuf *m, char *buf, size_t want)
|
||||
{
|
||||
struct sk_buff *skb = m ? m_to_skb(m) : NULL;
|
||||
size_t n;
|
||||
|
||||
if (skb == NULL)
|
||||
return 0;
|
||||
n = min_t(size_t, want, skb->len);
|
||||
if (skb_copy_bits(skb, 0, buf, n))
|
||||
return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ---------------- pflog_packet() (called by the ported pf.c) ---------------- */
|
||||
|
||||
int
|
||||
pflog_packet(struct pf_pdesc *pd, u_int8_t reason, struct pf_rule *rm,
|
||||
struct pf_rule *am, struct pf_ruleset *ruleset, struct pf_rule *cm)
|
||||
{
|
||||
struct pfk_pflog_hdr hdr;
|
||||
char pkt[PFLOG_COPY_SIZE];
|
||||
struct pf_rule *r;
|
||||
size_t n;
|
||||
|
||||
if (pd == NULL || pd->m == NULL)
|
||||
return (0);
|
||||
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
{
|
||||
struct timespec64 ts;
|
||||
|
||||
ktime_get_real_ts64(&ts);
|
||||
hdr.pflog_ts.tv_sec = ts.tv_sec;
|
||||
hdr.pflog_ts.tv_usec = ts.tv_nsec / 1000;
|
||||
}
|
||||
hdr.pflog_af = pd->af;
|
||||
hdr.pflog_reason = reason;
|
||||
|
||||
r = cm ? cm : (am ? am : rm);
|
||||
if (r) {
|
||||
strscpy((char *)hdr.pflog_ifname,
|
||||
pd->kif ? pd->kif->pfik_name : "",
|
||||
sizeof(hdr.pflog_ifname));
|
||||
strscpy((char *)hdr.pflog_ruleset,
|
||||
(ruleset && ruleset->anchor) ? ruleset->anchor->name : "",
|
||||
sizeof(hdr.pflog_ruleset));
|
||||
hdr.pflog_nr = r->nr;
|
||||
hdr.pflog_anc = 0;
|
||||
hdr.pflog_flags = r->log;
|
||||
}
|
||||
|
||||
n = pfk_pflog_pull(pd->m, pkt, sizeof(pkt));
|
||||
hdr.pflog_len = cpu_to_be16(n); /* BSD stores it in network order */
|
||||
|
||||
pfk_pflog_put(&hdr, pkt, n);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* ---------------- /dev/pflog ---------------- */
|
||||
|
||||
static int
|
||||
pfk_pflog_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
filp->private_data = NULL;
|
||||
return nonseekable_open(inode, filp);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
pfk_pflog_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct pfk_pflog_rec *rec = filp->private_data;
|
||||
unsigned long flags;
|
||||
size_t off, n;
|
||||
|
||||
spin_lock_irqsave(&pflog_lock, flags);
|
||||
if (rec == NULL) {
|
||||
if (list_empty(&pflog_list)) {
|
||||
spin_unlock_irqrestore(&pflog_lock, flags);
|
||||
if (filp->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
if (wait_event_interruptible(pflog_wq,
|
||||
!list_empty(&pflog_list)))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irqsave(&pflog_lock, flags);
|
||||
if (list_empty(&pflog_list)) {
|
||||
spin_unlock_irqrestore(&pflog_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
rec = list_first_entry(&pflog_list, struct pfk_pflog_rec, list);
|
||||
list_del(&rec->list);
|
||||
pflog_queued--;
|
||||
pflog_bytes -= rec->len;
|
||||
filp->private_data = rec;
|
||||
}
|
||||
spin_unlock_irqrestore(&pflog_lock, flags);
|
||||
|
||||
/* partial reads resume through *ppos (record is pinned to the file) */
|
||||
off = *ppos;
|
||||
if (off >= rec->len) {
|
||||
kfree(rec);
|
||||
filp->private_data = NULL;
|
||||
*ppos = 0;
|
||||
return 0;
|
||||
}
|
||||
n = min(count, rec->len - off);
|
||||
if (copy_to_user(buf, rec->data + off, n))
|
||||
return -EFAULT;
|
||||
*ppos += n;
|
||||
if (*ppos == rec->len) {
|
||||
kfree(rec);
|
||||
filp->private_data = NULL;
|
||||
*ppos = 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static __poll_t
|
||||
pfk_pflog_poll(struct file *filp, poll_table *wait)
|
||||
{
|
||||
__poll_t mask = 0;
|
||||
|
||||
poll_wait(filp, &pflog_wq, wait);
|
||||
if (filp->private_data != NULL)
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
else if (!list_empty(&pflog_list))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
return mask;
|
||||
}
|
||||
|
||||
static long
|
||||
pfk_pflog_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct pfk_pflog_stat st;
|
||||
|
||||
switch (cmd) {
|
||||
case PFKIOCGETPFLOGSTAT:
|
||||
st.queued = pflog_queued;
|
||||
st.bytes = pflog_bytes;
|
||||
st.dropped = pflog_dropped;
|
||||
st.seq = pflog_seq;
|
||||
if (copy_to_user((void __user *)arg, &st, sizeof(st)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct file_operations pfk_pflog_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = pfk_pflog_open,
|
||||
.read = pfk_pflog_read,
|
||||
.poll = pfk_pflog_poll,
|
||||
.unlocked_ioctl = pfk_pflog_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice pfk_pflog_dev = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "pflog",
|
||||
.fops = &pfk_pflog_fops,
|
||||
.mode = 0600,
|
||||
};
|
||||
|
||||
int
|
||||
pfk_pflog_register(void)
|
||||
{
|
||||
return misc_register(&pfk_pflog_dev);
|
||||
}
|
||||
|
||||
void
|
||||
pfk_pflog_unregister(void)
|
||||
{
|
||||
struct pfk_pflog_rec *rec, *tmp;
|
||||
|
||||
misc_deregister(&pfk_pflog_dev);
|
||||
spin_lock(&pflog_lock);
|
||||
list_for_each_entry_safe(rec, tmp, &pflog_list, list) {
|
||||
list_del(&rec->list);
|
||||
kfree(rec);
|
||||
}
|
||||
pflog_queued = 0;
|
||||
pflog_bytes = 0;
|
||||
spin_unlock(&pflog_lock);
|
||||
}
|
||||
Reference in new issue
Block a user