Files
pf-linux-port/module/compat/pfk_mbuf.c
T
lgcosta 5323ee2c71 M2: live netfilter data path (pf_test per direction), pfattach bring-up, NET_LOCK reentrancy fix, static pfctl
- pfk_hooks: PRE→PF_IN, LOCAL_OUT/POST→PF_OUT v4+v6; replacement/buffer
  handling with NF_STOLEN + re-injection (documented divergence: re-entry)
- pfk_mod: pf_status_init/pfattach + pfi attach via pfk_if event callback
- pfk_kern: NET_LOCK holder reentrancy (fix deadlock when notifier fires
  under the write lock — observed as full-VM network hang)
- tools: static pfctl link for musl VMs
2026-11-11 15:20:01 -03:00

610 lines
12 KiB
C

/*
* PF-on-Linux mbuf shim implementation (ISC, (c) 2026 PF-on-Linux contributors)
* See pfk_mbuf.h for the invariants. All functions run under pfk NET_LOCK
* (read side) — never in hardirq context.
*/
#include <pfk.h>
static struct kmem_cache *pfk_mbuf_cache;
struct mbuf *
m_get(int how, short type)
{
return m_gethdr(how, type); /* PF always uses pkthdr packets */
}
struct mbuf *
m_gethdr(int how, short type)
{
struct mbuf *m;
struct sk_buff *skb;
m = kmem_cache_zalloc(pfk_mbuf_cache,
(how & (M_WAITOK|M_CANFAIL)) == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC);
if (!m)
return NULL;
skb = alloc_skb(256 + NET_SKB_PAD + NET_IP_ALIGN,
(how & (M_WAITOK|M_CANFAIL)) == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC);
if (!skb) {
kmem_cache_free(pfk_mbuf_cache, m);
return NULL;
}
skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
m->skb = skb;
m->m_data = skb->data;
m->m_len = skb->len;
m->m_type = type;
m->m_flags = M_PKTHDR;
m->m_refcnt = 1;
m->m_maxsize = skb_tailroom(skb);
pfk_m_setpkthdr(m);
return m;
}
void
pfk_m_setpkthdr(struct mbuf *m)
{
m->m_flags |= M_PKTHDR;
m->m_pkthdr.len = m->m_len;
m->m_pkthdr.pf.tag = 0;
m->m_pkthdr.tags = NULL;
}
/*
* Ingress wrapper for netfilter hook skbs: takes an EXTRA skb reference so
* the wrapper can be consumed (m_freem) without disturbing netfilter's own
* reference; fills the pkthdr the way OpenBSD's ip_input/ip_output does.
*/
struct mbuf *
pfk_m_wrap_skb(struct sk_buff *skb, int af)
{
struct mbuf *m;
skb_get(skb);
m = m_alloc_skb(skb, M_NOWAIT);
if (!m)
return NULL;
m->m_pkthdr.af = af;
m->m_pkthdr.proto = (af == AF_INET) ?
((struct pf_ip *)skb->data)->ip_p : -1;
m->m_pkthdr.ph_ifidx = skb->dev ? skb->dev->ifindex : 0;
m->m_pkthdr.ifp = m->m_pkthdr.rcvif =
skb->dev ? pfk_ifp_bydev(skb->dev) : NULL;
m->m_pkthdr.ph_rtableid = 0;
return m;
}
struct mbuf *
m_alloc_skb(struct sk_buff *skb, int how)
{
struct mbuf *m;
m = kmem_cache_zalloc(pfk_mbuf_cache,
(how & (M_WAITOK|M_CANFAIL)) == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC);
if (!m) {
kfree_skb(skb);
return NULL;
}
m->skb = skb;
m->m_data = skb->data;
m->m_len = skb->len;
m->m_type = MT_HEADER;
m->m_refcnt = 1;
pfk_m_setpkthdr(m);
return m;
}
/* reverse map: stash mbuf ptr in skb->cb (documented exclusivity in spec §4.1) */
struct pfk_skb_cb {
struct mbuf *m;
};
#define PFK_CB(skb) ((struct pfk_skb_cb *)((skb)->cb))
struct mbuf *
m_dtom(struct sk_buff *skb)
{
return PFK_CB(skb)->m;
}
struct mbuf *
m_devget(char *buf, int totlen, int off, struct pfk_ifnet *ifp,
void (*copyin_fc)(caddr_t, int))
{
struct mbuf *m;
if (off) {
buf += off;
totlen -= off;
}
if (totlen < 0)
return NULL;
m = m_gethdr(M_NOWAIT, MT_DATA);
if (!m)
return NULL;
if (skb_put(m->skb, totlen) == NULL) {
m_freem(m);
return NULL;
}
if (copyin_fc)
copyin_fc(mtod(m, caddr_t), totlen);
else
memcpy(m->skb->data, buf, totlen);
pfk_mbuf_refresh(m);
return m;
}
int
pfk_mbuf_linearize(struct mbuf *m)
{
if (skb_is_nonlinear(m->skb)) {
struct sk_buff *ns;
ns = skb_copy(m->skb, GFP_ATOMIC);
if (!ns)
return ENOBUFS;
consume_skb(m->skb);
m->skb = ns;
}
return 0;
}
void
pfk_mbuf_refresh(struct mbuf *m)
{
m->m_data = m->skb->data;
m->m_len = m->skb->len;
if (m->m_flags & M_PKTHDR)
m->m_pkthdr.len = m->m_len;
}
struct mbuf *
m_copym(struct mbuf *m, int off, int len, int wait)
{
struct mbuf *n;
struct sk_buff *ns;
if (len == 0)
len = m->m_len - off;
if (off > m->m_len || len > m->m_len - off)
return NULL;
ns = alloc_skb(len + NET_SKB_PAD + NET_IP_ALIGN,
wait == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC);
if (!ns)
return NULL;
skb_reserve(ns, NET_SKB_PAD + NET_IP_ALIGN);
if (skb_copy_bits(m->skb, off, skb_put(ns, len), len)) {
kfree_skb(ns);
return NULL;
}
n = m_alloc_skb(ns, wait);
if (!n)
return NULL;
if (m->m_flags & M_PKTHDR) {
n->m_pkthdr = m->m_pkthdr;
n->m_pkthdr.len = len;
n->m_pkthdr.tags = NULL;
if (m_tag_copy_chain(n, m, wait) != 0) {
m_freem(n);
return NULL;
}
}
return n;
}
struct mbuf *
m_dup(struct mbuf *m, int wait)
{
struct mbuf *n = m_copym(m, 0, m->m_len, wait);
if (n) {
struct sk_buff *ns = n->skb;
int need = 256 - skb_headroom(ns);
if (need > 0 &&
pskb_expand_head(ns, ALIGN(need, 64), 0,
wait == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC)) {
m_freem(n);
return NULL;
}
pfk_mbuf_refresh(n);
}
return n;
}
/*
* m_prepend(m, len, how): make room for `len` bytes at the front of the
* packet (contents moved, not initialised — BSD semantics).
*/
struct mbuf *
m_prepend(struct mbuf *m, int len, int how)
{
if (!m || len <= 0)
return m;
if (skb_headroom(m->skb) < len) {
int extra = ALIGN(len - skb_headroom(m->skb), 64);
gfp_t g = (how & M_WAITOK) ? GFP_ATOMIC : GFP_ATOMIC;
if ((how & (M_WAITOK|M_CANFAIL)) == M_WAITOK)
g = GFP_KERNEL;
if (pskb_expand_head(m->skb, extra, 0, g)) {
m_freem(m);
return NULL;
}
}
skb_push(m->skb, len);
pfk_mbuf_refresh(m);
if (m->m_flags & M_PKTHDR)
m->m_pkthdr.len += len;
return m;
}
/*
* m_split(m, len, flags): cut m at `len`, return the tail (second half).
* m keeps the first `len` bytes. On failure returns NULL and leaves m
* untouched (OpenBSD semantics: m unchanged).
*/
struct mbuf *
m_split(struct mbuf *m, int len, int flags)
{
struct mbuf *tail;
if (len >= m->m_len)
return NULL; /* nothing to split off */
tail = m_copym(m, len, m->m_len - len, flags);
if (!tail)
return NULL;
skb_trim(m->skb, len);
pfk_mbuf_refresh(m);
if (m->m_flags & M_PKTHDR)
m->m_pkthdr.len = len;
return tail;
}
/*
* m_dup_pkt(m, hdr, how): full duplicate of m with at least `hdr` bytes of
* headroom in front of the data (for link-layer rewriting downstream).
*/
struct mbuf *
m_dup_pkt(struct mbuf *m, u_int hdr, int how)
{
struct mbuf *n = m_dup(m, how);
if (!n)
return NULL;
if ((unsigned int)skb_headroom(n->skb) < hdr) {
int extra = ALIGN((int)hdr - skb_headroom(n->skb), 64);
if (pskb_expand_head(n->skb, extra, 0,
(how & (M_WAITOK|M_CANFAIL)) == M_WAITOK ?
GFP_KERNEL : GFP_ATOMIC)) {
m_freem(n);
return NULL;
}
pfk_mbuf_refresh(n);
}
return n;
}
void
m_adj(struct mbuf *m, int dlen)
{
int off = dlen;
if (off < 0) { /* trim tail */
skb_trim(m->skb, m->skb->len + off);
} else if (off > 0) {
pskb_pull(m->skb, off > m->skb->len ? m->skb->len : off);
}
pfk_mbuf_refresh(m);
}
void
m_trim(struct mbuf *m, int dlen)
{
skb_trim(m->skb, dlen);
pfk_mbuf_refresh(m);
}
int
m_cat(struct mbuf *to, struct mbuf *from)
{
int len = from->m_len;
if (skb_tailroom(to->skb) < len) {
if (pskb_expand_head(to->skb, 0, ALIGN(len, 64), GFP_ATOMIC))
return EINVAL;
pfk_mbuf_refresh(to);
}
memcpy(skb_put(to->skb, len), from->m_data, len);
pfk_mbuf_refresh(to);
m_freem(from);
return 0;
}
int
m_copyback(struct mbuf *m, int off, int len, const void *copied, int type)
{
int grow = off + len - m->m_len;
caddr_t p;
if (off < 0 || len < 0)
return EINVAL;
if (grow > 0) {
if (skb_tailroom(m->skb) < grow) {
if (pskb_expand_head(m->skb, 0, ALIGN(grow, 64), GFP_ATOMIC))
return ENOBUFS;
pfk_mbuf_refresh(m);
}
memset(m->m_data + m->m_len, 0, grow);
skb_put(m->skb, grow);
pfk_mbuf_refresh(m);
}
p = m->m_data + off;
memcpy(p, copied, len);
return 0;
}
void
m_copydata(const struct mbuf *m, int off, int len, caddr_t dest)
{
if (len <= 0)
return;
if (skb_copy_bits(m->skb, off, dest, len))
memcpy(dest, m->m_data + off,
len > m->m_len - off ? m->m_len - off : len);
}
struct mbuf *
m_getptr(struct mbuf *m, int loc, int *offp)
{
*offp = 0;
if (loc < 0 || loc >= m->m_len)
return NULL;
return m;
}
int
m_clump(struct mbuf **m0p, int len, int *mp_off, int *mp_len, caddr_t *doff)
{
struct mbuf *m = *m0p;
if (!pskb_may_pull(m->skb, *mp_off + len))
return -1;
pfk_mbuf_refresh(m);
*mp_len = len;
*doff = m->m_data + *mp_off;
*m0p = m;
return 0;
}
struct mbuf *
m_free(struct mbuf *m)
{
struct mbuf *n = m->m_next;
m_tag_delete_chain(m, NULL);
if (m->skb) {
if (PFK_CB(m->skb)->m == m)
PFK_CB(m->skb)->m = NULL;
kfree_skb(m->skb);
}
kmem_cache_free(pfk_mbuf_cache, m);
return n;
}
void
m_freem(struct mbuf *m)
{
while (m)
m = m_free(m);
}
void
m_free_ext(struct mbuf *m)
{
/* PF never attaches external mbuf refs; no-op like upstream ext free */
}
/* BSD m_removehdr: drop the packet header (length in m_pkthdr.hdr_len) */
int
m_removehdr(struct mbuf *m)
{
int len = m->m_pkthdr.hdr_len;
if (len <= 0)
return 0;
if (len > m->m_len)
return -1;
m_adj(m, len);
m->m_pkthdr.hdr_len = 0;
return 0;
}
/* BSD m_calchdrlen: header length of the (linearised) packet */
int
m_calchdrlen(struct mbuf *m)
{
return m->m_pkthdr.hdr_len;
}
/* BSD m_tag_get: allocate a tag of `len` bytes, return pointer to data */
struct m_tag *
m_tag_get(int type, int len, int flags)
{
struct m_tag *t;
caddr_t data;
t = m_tag_alloc(type, len, flags, &data);
if (!t)
return NULL;
return (struct m_tag *)data;
}
/* BSD frag6_deletefraghdr: remove the IPv6 fragment header at `off` */
int
frag6_deletefraghdr(struct mbuf *m, int off)
{
int hdrlen = sizeof(struct ip6_frag);
if (off < 0 || off + hdrlen > m->m_len)
return -1;
memmove(m->m_data + off, m->m_data + off + hdrlen,
m->m_len - off - hdrlen);
pfk_mbuf_refresh(m);
return 0;
}
/* ---------------- m_tag ---------------- */
struct m_tag *
m_tag_alloc(int type, int len, int wait, caddr_t *align_data)
{
struct m_tag *t;
t = kmalloc(sizeof(*t) + len + 4,
wait == M_WAITOK ? GFP_KERNEL : GFP_ATOMIC);
if (!t)
return NULL;
t->m_tag_next = NULL;
t->m_tag_type = type;
t->m_tag_len = len;
if (align_data)
*align_data = PFK_PTR_ALIGN(&t[1]);
return t;
}
void
m_tag_free(struct m_tag *t)
{
kfree(t);
}
void
m_tag_attach(struct mbuf *m, struct m_tag *t)
{
t->m_tag_next = NULL;
if (m->m_pkthdr.tags) {
struct m_tag *e = m->m_pkthdr.tags;
while (e->m_tag_next)
e = e->m_tag_next;
e->m_tag_next = t;
} else
m->m_pkthdr.tags = t;
}
void
m_tag_prepend(struct mbuf *m, struct m_tag *t)
{
t->m_tag_next = m->m_pkthdr.tags;
m->m_pkthdr.tags = t;
}
void
m_tag_prepend_packet(struct mbuf *m, struct m_tag *t)
{
if (m->m_flags & M_PKTHDR)
m_tag_prepend(m, t);
else
m_tag_free(t);
}
struct m_tag *
m_tag_find(struct mbuf *m, int type, struct m_tag *start)
{
struct m_tag *t;
if (start == NULL) {
if (!(m->m_flags & M_PKTHDR))
return NULL;
t = m->m_pkthdr.tags;
} else
t = start->m_tag_next;
for (; t; t = t->m_tag_next)
if (t->m_tag_type == type)
break;
return t;
}
void
m_tag_delete(struct mbuf *m, struct m_tag *t)
{
struct m_tag **pp = &m->m_pkthdr.tags, *e;
for (e = m->m_pkthdr.tags; e; e = e->m_tag_next) {
if (e == t) {
*pp = t->m_tag_next;
m_tag_free(t);
return;
}
pp = &e->m_tag_next;
}
}
struct m_tag *
m_tag_copy(struct m_tag *tag, int how)
{
struct m_tag *t;
caddr_t d;
t = m_tag_alloc(tag->m_tag_type, tag->m_tag_len, how, &d);
if (t)
memcpy(d, tag->m_tag_data, tag->m_tag_len);
return t;
}
int
m_tag_copy_chain(struct mbuf *to, struct mbuf *from, int how)
{
struct m_tag *t;
for (t = m_tag_first(from); t; t = m_tag_next(from, t)) {
struct m_tag *n = m_tag_copy(t, how);
if (!n)
return ENOBUFS;
m_tag_attach(to, n);
}
return 0;
}
void
m_tag_delete_chain(struct mbuf *m, struct m_tag *end)
{
struct m_tag *t, *n;
if (!(m->m_flags & M_PKTHDR))
return;
for (t = m->m_pkthdr.tags; t && t != end; t = n) {
n = t->m_tag_next;
m_tag_free(t);
}
m->m_pkthdr.tags = end;
}
struct m_tag *
m_tag_first(struct mbuf *m)
{
return (m->m_flags & M_PKTHDR) ? m->m_pkthdr.tags : NULL;
}
struct m_tag *
m_tag_next(struct mbuf *m, struct m_tag *t)
{
return t->m_tag_next;
}
int
pfk_mbuf_init(void)
{
pfk_mbuf_cache = KMEM_CACHE(mbuf, 0);
if (!pfk_mbuf_cache)
return -ENOMEM;
return 0;
}
void
pfk_mbuf_exit(void)
{
kmem_cache_destroy(pfk_mbuf_cache);
}