mirror of
https://github.com/lgcosta/pf-linux-port.git
synced 2026-11-13 16:28:03 -03:00
M1: compat layer + pf.ko skeleton builds against Linux 7.3.4
- pfk.h: BSD types/ioctl encoding (OpenBSD bit layout), locks (NET_LOCK=rwsem), pools, timeouts, mbuf shim over sk_buff, ifnet registry, toeplitz/siphash - pfk_mod/pfk_hooks: misc /dev/pf (0600), IPv4 netfilter hook skeleton, DIOCGETSTATUS answered (M1 skeleton; engine ioctls land with pf_ioctl.c) - vendorize/stage-port scripts: deterministic upstream staging with seds (struct renames, IFF_*->PFIFF_*, sa_len drops) logged to docs/porting-notes - fixes: modules_prepare+Module.symvers for out-of-tree builds
This commit is contained in:
1 parent
ba81284bd3
commit
da2e9b679a
87 files changed
+47256
-3
No files matched your search
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* PF-on-Linux ifnet/address registry (ISC 2026).
|
||||
* Maintains BSD-flavored struct pfk_ifnet views over net_device, refreshed
|
||||
* address snapshots, egress tracking, and name/index lookups. All ops under
|
||||
* NET_LOCK (read for lookups, write for list mutation) except notifier which
|
||||
* uses its own spinlock + work.
|
||||
*/
|
||||
#include <pfk.h>
|
||||
|
||||
static DEFINE_IDR(pfk_ifnet_idr);
|
||||
static DEFINE_SPINLOCK(pfk_ifnet_lock);
|
||||
static struct pfk_ifnet *pfk_egress;
|
||||
|
||||
static void pfk_ifnet_release(struct rcu_head *rhp)
|
||||
{
|
||||
struct pfk_ifnet *ifp = container_of(rhp, struct pfk_ifnet, rcu);
|
||||
|
||||
pfk_if_free(ifp);
|
||||
}
|
||||
|
||||
static struct pfk_ifnet *
|
||||
pfk_ifnet_new(struct net_device *dev)
|
||||
{
|
||||
struct pfk_ifnet *ifp = kzalloc(sizeof(*ifp), GFP_KERNEL);
|
||||
|
||||
if (!ifp)
|
||||
return NULL;
|
||||
strscpy(ifp->if_xname, dev->name, sizeof(ifp->if_xname));
|
||||
ifp->if_index = dev->ifindex;
|
||||
ifp->dev = dev;
|
||||
ifp->alive = 1;
|
||||
INIT_LIST_HEAD(&ifp->link);
|
||||
ifp->if_type = IFT_OTHER;
|
||||
switch (dev->type) {
|
||||
case ARPHRD_ETHER: ifp->if_type = IFT_ETHER; break;
|
||||
case ARPHRD_LOOPBACK: ifp->if_type = IFT_LOOP; break;
|
||||
case ARPHRD_PPP: ifp->if_type = IFT_PPPOINT; break;
|
||||
case ARPHRD_SIT: case ARPHRD_TUNNEL: case ARPHRD_IP6GRE:
|
||||
ifp->if_type = IFT_GIF; break;
|
||||
}
|
||||
return ifp;
|
||||
}
|
||||
|
||||
static int
|
||||
pfk_dev_flags_bsd(struct net_device *dev)
|
||||
{
|
||||
int f = 0;
|
||||
|
||||
if (dev->flags & IFF_UP) f |= PFIFF_UP;
|
||||
if (dev->flags & IFF_BROADCAST) f |= PFIFF_BROADCAST;
|
||||
if (dev->flags & IFF_DEBUG) f |= PFIFF_DEBUG;
|
||||
if (dev->flags & IFF_LOOPBACK) f |= PFIFF_LOOPBACK;
|
||||
if (dev->flags & IFF_POINTOPOINT) f |= PFIFF_POINTOPOINT;
|
||||
if (netif_running(dev)) f |= PFIFF_RUNNING;
|
||||
if (dev->flags & IFF_PROMISC) f |= PFIFF_PROMISC;
|
||||
if (dev->flags & IFF_ALLMULTI) f |= PFIFF_ALLMULTI;
|
||||
if (dev->flags & IFF_MULTICAST) f |= PFIFF_MULTICAST;
|
||||
if (!netif_running(dev)) f |= PFIFF_RESOLVE;
|
||||
return f;
|
||||
}
|
||||
|
||||
void
|
||||
pfk_if_refresh(struct pfk_ifnet *ifp)
|
||||
{
|
||||
ifp->if_mtu = ifp->dev->mtu;
|
||||
ifp->if_flags = pfk_dev_flags_bsd(ifp->dev);
|
||||
/* address snapshot: rebuild list from Linux in_ifaddr/inet6_ifaddr */
|
||||
pfk_if_addrlist_refresh(ifp);
|
||||
}
|
||||
|
||||
struct pfk_ifnet *
|
||||
pfk_ifp_byindex(int idx)
|
||||
{
|
||||
struct pfk_ifnet *ifp;
|
||||
|
||||
if (idx <= 0)
|
||||
return NULL;
|
||||
ifp = idr_find(&pfk_ifnet_idr, idx);
|
||||
return ifp && ifp->alive ? ifp : NULL;
|
||||
}
|
||||
|
||||
struct pfk_ifnet *
|
||||
pfk_ifp_bydev(struct net_device *dev)
|
||||
{
|
||||
return dev ? pfk_ifp_byindex(dev->ifindex) : NULL;
|
||||
}
|
||||
|
||||
struct pfk_ifnet *
|
||||
pfk_ifp_byname(const char *name)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct pfk_ifnet *ifp;
|
||||
|
||||
dev = dev_get_by_name(&init_net, name);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
ifp = pfk_ifp_bydev(dev);
|
||||
dev_put(dev);
|
||||
return ifp;
|
||||
}
|
||||
|
||||
void
|
||||
pfk_if_stats(struct pfk_ifnet *ifp, struct pfk_if_data *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->ifi_ibytes = ifp->dev->stats.rx_bytes;
|
||||
out->ifi_obytes = ifp->dev->stats.tx_bytes;
|
||||
}
|
||||
|
||||
/* -------- address snapshots -------- */
|
||||
static struct pfk_ifaddr *
|
||||
pfkifa_new(struct pfk_ifnet *ifp, int family)
|
||||
{
|
||||
struct pfk_ifaddr *ifa = kzalloc(sizeof(*ifa), GFP_KERNEL);
|
||||
|
||||
if (!ifa)
|
||||
return NULL;
|
||||
ifa->ifa_ifp = ifp;
|
||||
ifa->dev = ifp->dev;
|
||||
ifa->family = family;
|
||||
ifa->ifa_refcnt = 1;
|
||||
ifa->ifa_addr = (struct sockaddr *)&ifa->ifa_addr_storage;
|
||||
return ifa;
|
||||
}
|
||||
|
||||
int
|
||||
pfk_if_addrlist_refresh(struct pfk_ifnet *ifp)
|
||||
{
|
||||
struct pfk_ifaddr *ifa, *next;
|
||||
struct net_device *dev = ifp->dev;
|
||||
|
||||
/* free old */
|
||||
for (ifa = ifp->if_addrlist; ifa; ifa = next) {
|
||||
next = ifa->ifa_next;
|
||||
kfree(ifa);
|
||||
}
|
||||
ifp->if_addrlist = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
/* IPv4 */
|
||||
{
|
||||
struct in_device *idev = __in_dev_get_rcu(dev);
|
||||
struct in_ifaddr *ifa4;
|
||||
|
||||
if (idev)
|
||||
for (ifa4 = rcu_dereference(idev->ifa_list); ifa4;
|
||||
ifa4 = rcu_dereference(ifa4->ifa_next)) {
|
||||
ifa = pfkifa_new(ifp, AF_INET);
|
||||
if (!ifa)
|
||||
continue;
|
||||
ifa->addr4 = ifa4->ifa_address;
|
||||
ifa->ifa_addr->sa_family = AF_INET;
|
||||
{
|
||||
struct sockaddr_in *sin =
|
||||
sintosa(ifa->ifa_addr);
|
||||
sin->sin_addr.s_addr = ifa4->ifa_address;
|
||||
sin->sin_family = AF_INET;
|
||||
}
|
||||
ifa->ifa_next = ifp->if_addrlist;
|
||||
ifp->if_addrlist = ifa;
|
||||
}
|
||||
}
|
||||
/* IPv6 */
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
{
|
||||
struct inet6_dev *idev6 = __in6_dev_get(dev);
|
||||
struct inet6_ifaddr *ifa6;
|
||||
|
||||
if (idev6)
|
||||
list_for_each_entry(ifa6, &idev6->addr_list, if_list) {
|
||||
ifa = pfkifa_new(ifp, AF_INET6);
|
||||
if (!ifa)
|
||||
continue;
|
||||
ifa->addr6 = ifa6->addr;
|
||||
ifa->scope = ifa6->scope;
|
||||
ifa->ifa_addr->sa_family = AF_INET6;
|
||||
{
|
||||
struct sockaddr_in6 *sin6 =
|
||||
sin6tosa(ifa->ifa_addr);
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_addr = ifa6->addr;
|
||||
}
|
||||
ifa->ifa_next = ifp->if_addrlist;
|
||||
ifp->if_addrlist = ifa;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pfk_ifaddr *
|
||||
pfk_ifwithaddr(const struct sockaddr *sa)
|
||||
{
|
||||
struct pfk_ifnet *ifp;
|
||||
struct pfk_ifaddr *ifa;
|
||||
|
||||
for (ifp = pfk_if_first(); ifp; ifp = pfk_if_next(ifp)) {
|
||||
for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) {
|
||||
if (ifa->family != sa->sa_family)
|
||||
continue;
|
||||
if (sa->sa_family == AF_INET &&
|
||||
ifa->addr4 == sintosa(sa)->sin_addr.s_addr)
|
||||
return ifa;
|
||||
if (sa->sa_family == AF_INET6 &&
|
||||
!memcmp(&ifa->addr6, &sin6tosa(sa)->sin6_addr,
|
||||
sizeof(struct in6_addr)))
|
||||
return ifa;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
pfk_if_free(struct pfk_ifnet *ifp)
|
||||
{
|
||||
struct pfk_ifaddr *ifa, *next;
|
||||
|
||||
for (ifa = ifp->if_addrlist; ifa; ifa = next) {
|
||||
next = ifa->ifa_next;
|
||||
kfree(ifa);
|
||||
}
|
||||
kfree(ifp);
|
||||
}
|
||||
|
||||
/* -------- netdev events -------- */
|
||||
static int
|
||||
pfk_netdev_event(struct notifier_block *nb, unsigned long ev, void *ptr)
|
||||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
struct pfk_ifnet *ifp;
|
||||
unsigned long flags;
|
||||
|
||||
switch (ev) {
|
||||
case NETDEV_REGISTER:
|
||||
ifp = pfk_ifnet_new(dev);
|
||||
if (!ifp)
|
||||
return NOTIFY_DONE;
|
||||
pfk_if_refresh(ifp);
|
||||
spin_lock_irqsave(&pfk_ifnet_lock, flags);
|
||||
if (idr_find(&pfk_ifnet_idr, dev->ifindex))
|
||||
idr_remove(&pfk_ifnet_idr, dev->ifindex);
|
||||
if (idr_alloc(&pfk_ifnet_idr, ifp, dev->ifindex,
|
||||
dev->ifindex + 1, GFP_ATOMIC) < 0) {
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
kfree(ifp);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
break;
|
||||
case NETDEV_UNREGISTER:
|
||||
spin_lock_irqsave(&pfk_ifnet_lock, flags);
|
||||
ifp = idr_find(&pfk_ifnet_idr, dev->ifindex);
|
||||
if (ifp) {
|
||||
ifp->alive = 0;
|
||||
idr_remove(&pfk_ifnet_idr, dev->ifindex);
|
||||
call_rcu(&ifp->rcu, pfk_ifnet_release);
|
||||
}
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
break;
|
||||
case NETDEV_CHANGE:
|
||||
case NETDEV_CHANGENAME:
|
||||
case NETDEV_CHANGEMTU:
|
||||
ifp = pfk_ifp_bydev(dev);
|
||||
if (ifp)
|
||||
pfk_if_refresh(ifp);
|
||||
break;
|
||||
case NETDEV_DOWN:
|
||||
case NETDEV_UP: {
|
||||
ifp = pfk_ifp_bydev(dev);
|
||||
if (ifp)
|
||||
pfk_if_refresh(ifp);
|
||||
pfk_egress_refresh();
|
||||
break;
|
||||
}
|
||||
}
|
||||
pfk_egress_refresh();
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block pfk_netdev_nb = {
|
||||
.notifier_call = pfk_netdev_event,
|
||||
};
|
||||
|
||||
/* -------- egress -------- */
|
||||
void
|
||||
pfk_egress_refresh(void)
|
||||
{
|
||||
struct pfk_ifnet *ifp = NULL;
|
||||
struct net_device *dev;
|
||||
|
||||
/* PFLINUX: default route lookup for 0.0.0.0 via FIB */
|
||||
rcu_read_lock();
|
||||
dev = ip_rt_get_default_dev(&init_net); /* helper, P1 export */
|
||||
rcu_read_unlock();
|
||||
ifp = pfk_ifp_bydev(dev);
|
||||
if (ifp)
|
||||
ifp->if_xflags |= 0x00000001; /* BSD IFFX_EGRESS value */
|
||||
pfk_egress = ifp;
|
||||
}
|
||||
|
||||
struct pfk_ifnet *
|
||||
pfk_egress_if(void)
|
||||
{
|
||||
return pfk_egress;
|
||||
}
|
||||
|
||||
/* -------- iteration -------- */
|
||||
struct pfk_ifnet *
|
||||
pfk_if_first(void)
|
||||
{
|
||||
int id = -1;
|
||||
|
||||
return pfk_if_next_from(&id);
|
||||
}
|
||||
/* callers keep id on stack; helper returns next or NULL */
|
||||
struct pfk_ifnet *
|
||||
pfk_if_next_from(int *idp)
|
||||
{
|
||||
struct pfk_ifnet *ifp;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pfk_ifnet_lock, flags);
|
||||
ifp = idr_get_next(&pfk_ifnet_idr, idp);
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
if (ifp)
|
||||
(*idp)++;
|
||||
return ifp && ifp->alive ? ifp : (ifp ? pfk_if_next_from(idp) : NULL);
|
||||
}
|
||||
|
||||
struct pfk_ifnet *
|
||||
pfk_if_next(struct pfk_ifnet *ifp)
|
||||
{
|
||||
int id = ifp ? ifp->if_index : -1;
|
||||
|
||||
return pfk_if_next_from(&id);
|
||||
}
|
||||
|
||||
int
|
||||
pfk_if_init(void)
|
||||
{
|
||||
struct net_device *dev;
|
||||
int err;
|
||||
|
||||
err = register_netdevice_notifier(&pfk_netdev_nb);
|
||||
if (err)
|
||||
return err;
|
||||
/* populate existing devices */
|
||||
rtnl_lock();
|
||||
for_each_netdev(&init_net, dev) {
|
||||
struct pfk_ifnet *ifp = pfk_ifnet_new(dev);
|
||||
unsigned long flags;
|
||||
|
||||
if (!ifp)
|
||||
continue;
|
||||
spin_lock_irqsave(&pfk_ifnet_lock, flags);
|
||||
if (!idr_find(&pfk_ifnet_idr, dev->ifindex))
|
||||
idr_alloc(&pfk_ifnet_idr, ifp, dev->ifindex,
|
||||
dev->ifindex + 1, GFP_KERNEL);
|
||||
else
|
||||
ifp = NULL; /* already tracked by notifier */
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
if (ifp)
|
||||
pfk_if_refresh(ifp);
|
||||
}
|
||||
rtnl_unlock();
|
||||
pfk_egress_refresh();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
pfk_if_exit(void)
|
||||
{
|
||||
struct pfk_ifnet *ifp;
|
||||
int id = -1;
|
||||
unsigned long flags;
|
||||
|
||||
unregister_netdevice_notifier(&pfk_netdev_nb);
|
||||
spin_lock_irqsave(&pfk_ifnet_lock, flags);
|
||||
while ((ifp = idr_get_next(&pfk_ifnet_idr, &id)) != NULL) {
|
||||
ifp->alive = 0;
|
||||
idr_remove(&pfk_ifnet_idr, id);
|
||||
kfree(ifp);
|
||||
id = -1;
|
||||
}
|
||||
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* PF-on-Linux kernel API compatibility (log/lock/malloc/pool/timeout/tsleep)
|
||||
* ISC (c) 2026 PF-on-Linux contributors
|
||||
*/
|
||||
#include <pfk.h>
|
||||
|
||||
/* ---------------- logging ---------------- */
|
||||
void
|
||||
pfk_log(int level, const char *fmt, ...)
|
||||
{
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
|
||||
if (level < 0 || level > LOG_DEBUG)
|
||||
level = LOG_INFO;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
switch (level) {
|
||||
case LOG_EMERG: pr_emerg("%s\n", buf); break;
|
||||
case LOG_ALERT: pr_alert("%s\n", buf); break;
|
||||
case LOG_CRIT: pr_crit("%s\n", buf); break;
|
||||
case LOG_ERR: pr_err("%s\n", buf); break;
|
||||
case LOG_WARNING: pr_warn("%s\n", buf); break;
|
||||
case LOG_NOTICE: pr_notice("%s\n", buf); break;
|
||||
default:
|
||||
pr_info_ratelimited("%s\n", buf); break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
pfk_printk_compat(const char *fmt, ...)
|
||||
{
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
int n;
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
if (n > 0)
|
||||
pr_info("%s", buf);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ---------------- NET_LOCK ---------------- */
|
||||
static DECLARE_RWSEM(pfk_net_sem);
|
||||
static atomic_t pfk_net_readers = ATOMIC_INIT(0);
|
||||
|
||||
void pfk_net_lock_init(void) { }
|
||||
void pfk_net_lock(void) { down_write(&pfk_net_sem); }
|
||||
void pfk_net_unlock(void) { up_write(&pfk_net_sem); }
|
||||
void pfk_net_read_enter(void) { down_read(&pfk_net_sem);
|
||||
atomic_inc(&pfk_net_readers); }
|
||||
void pfk_net_read_release(void){ atomic_dec(&pfk_net_readers);
|
||||
up_read(&pfk_net_sem); }
|
||||
|
||||
void
|
||||
rw_init(struct rwlock *l, const char *name)
|
||||
{
|
||||
init_rwsem(&l->sem);
|
||||
l->name = name;
|
||||
l->is_writer = 0;
|
||||
atomic_set(&l->readers, 0);
|
||||
}
|
||||
|
||||
void
|
||||
rw_enter(struct rwlock *l, int mode)
|
||||
{
|
||||
if (mode == RW_WRITER) {
|
||||
down_write(&l->sem);
|
||||
l->is_writer = 1;
|
||||
} else {
|
||||
down_read(&l->sem);
|
||||
atomic_inc(&l->readers);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rw_exit(struct rwlock *l)
|
||||
{
|
||||
if (l->is_writer) {
|
||||
l->is_writer = 0;
|
||||
up_write(&l->sem);
|
||||
} else {
|
||||
atomic_dec(&l->readers);
|
||||
up_read(&l->sem);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
rw_status(struct rwlock *l)
|
||||
{
|
||||
return l->is_writer ? RW_WRITER :
|
||||
atomic_read(&l->readers) ? RW_READER : RW_NAPUTS;
|
||||
}
|
||||
|
||||
/* ---------------- malloc ---------------- */
|
||||
void *
|
||||
pfk_malloc(size_t sz, int flags)
|
||||
{
|
||||
gfp_t g = (flags & M_WAITOK) ? GFP_KERNEL : GFP_ATOMIC;
|
||||
|
||||
if (flags & M_ZERO)
|
||||
return kzalloc(sz, g);
|
||||
return kmalloc(sz, g);
|
||||
}
|
||||
|
||||
void
|
||||
pfk_free(void *p)
|
||||
{
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
/* ---------------- pools ---------------- */
|
||||
static atomic64_t pfk_pool_id = ATOMIC64_INIT(0);
|
||||
|
||||
int
|
||||
pool_init(struct pool *pl, size_t size, int align, int ipl, const char *use,
|
||||
void *p, int flags, void (*ih)(void *, void *), void *arg,
|
||||
struct srcptr_static *sp)
|
||||
{
|
||||
memset(pl, 0, sizeof(*pl));
|
||||
pl->p_size = size;
|
||||
return 0; /* slab lazily created on first pool_get */
|
||||
}
|
||||
|
||||
static int
|
||||
pool_get_cache(struct pool *pl)
|
||||
{
|
||||
char nm[64];
|
||||
struct kmem_cache *c;
|
||||
|
||||
snprintf(nm, sizeof(nm), "pfk_pool_%llu",
|
||||
(unsigned long long)atomic64_inc_return(&pfk_pool_id));
|
||||
c = kmem_cache_create_usercopy(nm, pl->p_size, 0, SLAB_HWCACHE_ALIGN,
|
||||
0, pl->p_size, NULL);
|
||||
pl->slab = c;
|
||||
return c ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
void
|
||||
pool_destroy(struct pool *pl)
|
||||
{
|
||||
if (pl->slab) {
|
||||
kmem_cache_destroy(pl->slab);
|
||||
pl->slab = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
pool_get(struct pool *pl, int flags)
|
||||
{
|
||||
void *v;
|
||||
|
||||
if (!pl->slab && pool_get_cache(pl))
|
||||
return NULL;
|
||||
v = kmem_cache_alloc(pl->slab,
|
||||
(flags & PR_WAITOK) ? GFP_KERNEL :
|
||||
(flags & PR_NOWAIT) ? GFP_ATOMIC :
|
||||
__GFP_RETRY_MAYFAIL | GFP_KERNEL);
|
||||
if (v) {
|
||||
pl->p_used++;
|
||||
if (flags & PR_ZERO)
|
||||
memset(v, 0, pl->p_size);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void
|
||||
pool_put(struct pool *pl, void *v)
|
||||
{
|
||||
if (pl->slab && v) {
|
||||
kmem_cache_free(pl->slab, v);
|
||||
if (pl->p_used)
|
||||
pl->p_used--;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
pool_sethardlimit(struct pool *pl, unsigned long lim, const char *msg, int lvl)
|
||||
{
|
||||
pl->p_hardlimit = lim;
|
||||
}
|
||||
|
||||
/* ---------------- timeout ---------------- */
|
||||
static void
|
||||
pfk_timeout_work(struct work_struct *w)
|
||||
{
|
||||
struct timeout *to = container_of(w, struct timeout, work);
|
||||
|
||||
to->running = 1;
|
||||
to->pending = 0;
|
||||
if (to->fn)
|
||||
to->fn(to->arg);
|
||||
to->running = 0;
|
||||
if (to->period_ms >= 0)
|
||||
mod_timer(&to->timer, jiffies + msecs_to_jiffies(to->period_ms));
|
||||
}
|
||||
|
||||
static void
|
||||
pfk_timeout_fire(struct timer_list *tl)
|
||||
{
|
||||
struct timeout *to = container_of(tl, struct timeout, timer);
|
||||
|
||||
schedule_work(&to->work);
|
||||
}
|
||||
|
||||
void
|
||||
timeout_set(struct timeout *to, timeout_fn *fn, void *arg)
|
||||
{
|
||||
if (!to->fn) { /* one-time setup */
|
||||
timer_setup(&to->timer, pfk_timeout_fire, 0);
|
||||
INIT_WORK(&to->work, pfk_timeout_work);
|
||||
to->period_ms = -1;
|
||||
}
|
||||
to->fn = fn;
|
||||
to->arg = arg;
|
||||
}
|
||||
|
||||
void
|
||||
timeout_set_micro(struct timeout *to, timeout_fn *fn, void *arg)
|
||||
{
|
||||
timeout_set(to, fn, arg);
|
||||
}
|
||||
|
||||
int
|
||||
timeout_add(struct timeout *to, long ms)
|
||||
{
|
||||
if (ms < 0)
|
||||
ms = 0;
|
||||
to->pending = 1;
|
||||
to->deadline_jif = jiffies + msecs_to_jiffies(ms);
|
||||
return mod_timer(&to->timer, to->deadline_jif);
|
||||
}
|
||||
|
||||
int
|
||||
timeout_add_sec(struct timeout *to, int secs)
|
||||
{
|
||||
return timeout_add(to, (long)secs * 1000);
|
||||
}
|
||||
|
||||
int
|
||||
timeout_add_usec(struct timeout *to, int us)
|
||||
{
|
||||
return timeout_add(to, us > 1000 ? us / 1000 : (us ? 1 : 0));
|
||||
}
|
||||
|
||||
int
|
||||
timeout_pending(struct timeout *to)
|
||||
{
|
||||
return to->pending;
|
||||
}
|
||||
|
||||
int
|
||||
timeout_del(struct timeout *to)
|
||||
{
|
||||
to->pending = 0;
|
||||
return timer_delete(&to->timer);
|
||||
}
|
||||
|
||||
void
|
||||
timeout_del_sync(struct timeout *to)
|
||||
{
|
||||
to->pending = 0;
|
||||
timer_delete_sync(&to->timer);
|
||||
cancel_work_sync(&to->work);
|
||||
}
|
||||
|
||||
void
|
||||
timeout_barrier(struct timeout *to, int flags)
|
||||
{
|
||||
timer_delete_sync(&to->timer);
|
||||
cancel_work_sync(&to->work);
|
||||
}
|
||||
|
||||
/* ---------------- tsleep/wakeup (control path only) ---------------- */
|
||||
struct pfk_wait {
|
||||
struct list_head link;
|
||||
struct wait_queue_head wq;
|
||||
int done;
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(pfk_tsleep_lock);
|
||||
/* PFLINUX: BSD queue.h shadows LIST_HEAD (2-arg) — use raw initializer */
|
||||
static struct list_head pfk_tsleep_list = {
|
||||
.next = &pfk_tsleep_list,
|
||||
.prev = &pfk_tsleep_list,
|
||||
};
|
||||
|
||||
int
|
||||
tsleep(void *p, int pr, const char *w, int timo)
|
||||
{
|
||||
struct pfk_wait *pw;
|
||||
long left;
|
||||
|
||||
pw = kmalloc(sizeof(*pw), GFP_KERNEL);
|
||||
if (!pw)
|
||||
return EINTR;
|
||||
init_waitqueue_head(&pw->wq);
|
||||
pw->done = 0;
|
||||
spin_lock(&pfk_tsleep_lock);
|
||||
list_add_tail(&pw->link, &pfk_tsleep_list);
|
||||
pw->link.next = (void *)p; /* stash ident */
|
||||
spin_unlock(&pfk_tsleep_lock);
|
||||
left = wait_event_interruptible_timeout(pw->wq, pw->done,
|
||||
timo ? msecs_to_jiffies(timo) : MAX_JIFFY_OFFSET);
|
||||
spin_lock(&pfk_tsleep_lock);
|
||||
list_del(&pw->link);
|
||||
spin_unlock(&pfk_tsleep_lock);
|
||||
kfree(pw);
|
||||
if (!left)
|
||||
return EWOULDBLOCK;
|
||||
if (left == -ERESTARTSYS)
|
||||
return EINTR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
pfk_wakeup(void *p)
|
||||
{
|
||||
struct pfk_wait *pw;
|
||||
|
||||
spin_lock(&pfk_tsleep_lock);
|
||||
list_for_each_entry(pw, &pfk_tsleep_list, link) {
|
||||
if (pw->link.next == (void *)p) {
|
||||
pw->done = 1;
|
||||
wake_up_interruptible(&pw->wq);
|
||||
}
|
||||
}
|
||||
spin_unlock(&pfk_tsleep_lock);
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* 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 = NULL;
|
||||
m->m_pkthdr.tags = NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
caddr_t
|
||||
m_copyback(struct mbuf *m, int off, int len, const void *copied)
|
||||
{
|
||||
int grow = off + len - m->m_len;
|
||||
caddr_t p;
|
||||
|
||||
if (off < 0 || len < 0)
|
||||
return mtod(m, caddr_t) + off; /* BSD clamps; PF never does this */
|
||||
if (grow > 0) {
|
||||
if (skb_tailroom(m->skb) < grow) {
|
||||
if (pskb_expand_head(m->skb, 0, ALIGN(grow, 64), GFP_ATOMIC))
|
||||
return NULL;
|
||||
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 p;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
caddr_t
|
||||
m_getptr(struct mbuf *m, int loc, int *offp)
|
||||
{
|
||||
*offp = 0;
|
||||
if (loc < 0 || loc >= m->m_len)
|
||||
return NULL;
|
||||
return m->m_data + loc;
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
/* ---------------- 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);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* PF-on-Linux transmit + return-packet helpers (ISC 2026).
|
||||
* if_output_tso/ml + if_enqueue mirror OpenBSD calls; route-to uses them
|
||||
* with a gateway dst (see pf_route.c, T14). Return errors use Linux
|
||||
* ipstack helpers (exported: icmp_send; P1 exports ip_route_output_key,
|
||||
* ip6_route_output, tcp_v4_send_reset if needed).
|
||||
*/
|
||||
#include <pfk.h>
|
||||
|
||||
static struct dst_entry *
|
||||
pfk_route4(__be32 daddr, __be32 saddr, int oif)
|
||||
{
|
||||
struct flowi4 fl4 = { };
|
||||
struct rtable *rt;
|
||||
|
||||
fl4.daddr = daddr;
|
||||
fl4.saddr = saddr;
|
||||
if (oif)
|
||||
fl4.flowi4_oif = oif;
|
||||
rt = ip_route_output_key(&init_net, &fl4);
|
||||
if (IS_ERR(rt))
|
||||
return NULL;
|
||||
return &rt->dst;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
static struct dst_entry *
|
||||
pfk_route6(const struct in6_addr *daddr, const struct in6_addr *saddr, int oif)
|
||||
{
|
||||
struct flowi6 fl6 = { };
|
||||
struct dst_entry *d;
|
||||
|
||||
fl6.daddr = *daddr;
|
||||
if (saddr)
|
||||
fl6.saddr = *saddr;
|
||||
if (oif)
|
||||
fl6.flowi6_oif = oif;
|
||||
d = ip6_route_output(&init_net, NULL, &fl6);
|
||||
if (!d || d->error) {
|
||||
if (d)
|
||||
dst_release(d);
|
||||
return NULL;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* xmit: BSD semantics — caller hands fully-formed IP packet + dst sock.
|
||||
* rt arg is OpenBSD's cached route (opaque to us). Returns 0/errno. */
|
||||
int
|
||||
if_output_tso(struct pfk_ifnet *ifp, struct mbuf **mp, struct sockaddr *dst,
|
||||
void *rt, int mtu)
|
||||
{
|
||||
struct mbuf *m = *mp;
|
||||
struct sk_buff *skb = m_to_skb(m);
|
||||
struct dst_entry *d = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (!pskb_may_pull(skb, sizeof(struct pf_ip))) {
|
||||
ret = EINVAL;
|
||||
goto drop;
|
||||
}
|
||||
if (skb_ensure_writable(skb, 0)) {
|
||||
ret = ENOBUFS;
|
||||
goto drop;
|
||||
}
|
||||
switch (dst->sa_family) {
|
||||
case AF_INET: {
|
||||
struct pf_ip *ip = mtod(m, struct pf_ip *);
|
||||
__be32 gw = 0;
|
||||
|
||||
/* if route was forced (route-to), dst holds gw sockaddr
|
||||
* stored in sin_addr by pf_route; normal case dst==final */
|
||||
gw = sintosa(dst)->sin_addr.s_addr;
|
||||
d = pfk_route4(gw, ip->ip_src.s_addr, ifp->if_index);
|
||||
if (!d) {
|
||||
ret = EHOSTUNREACH;
|
||||
goto drop;
|
||||
}
|
||||
skb_dst_set(skb, d);
|
||||
break;
|
||||
}
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
case AF_INET6: {
|
||||
struct pf_ip6_hdr *ip6 = mtod(m, struct pf_ip6_hdr *);
|
||||
const struct in6_addr *gw = &sin6tosa(dst)->sin6_addr;
|
||||
|
||||
if (!d)
|
||||
d = pfk_route6(gw,
|
||||
(struct in6_addr *)&ip6->ip6_src,
|
||||
ifp->if_index);
|
||||
if (!d) {
|
||||
ret = EHOSTUNREACH;
|
||||
goto drop;
|
||||
}
|
||||
skb_dst_set(skb, d);
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
ret = EAFNOSUPPORT;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
skb->dev = skb_dst(skb)->dev;
|
||||
skb_reset_network_header(skb);
|
||||
if (skb->protocol == 0)
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
/* push through network layer exactly like OpenBSD's if_output:
|
||||
* dst->output = ip_output/ip6_output (headers already built) */
|
||||
if (!netif_running(skb->dev)) {
|
||||
ret = ENETDOWN;
|
||||
goto drop_dst_nofree;
|
||||
}
|
||||
ret = skb_dst(skb)->output(&init_net, NULL, skb);
|
||||
/* output consumed skb (and wrapper); avoid double free */
|
||||
if (ret == 0) {
|
||||
m->skb = NULL; /* ownership moved */
|
||||
m_freem(m);
|
||||
*mp = NULL;
|
||||
}
|
||||
return ret;
|
||||
|
||||
drop_dst_nofree:
|
||||
ret = ENETDOWN;
|
||||
drop:
|
||||
if (m) {
|
||||
m_freem(m);
|
||||
*mp = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
if_output_ml(struct pfk_ifnet *ifp, struct mbuf_list *ml,
|
||||
struct sockaddr *dst, void *rt)
|
||||
{
|
||||
struct mbuf *m;
|
||||
int err = 0, n;
|
||||
|
||||
while ((m = ml->ml_first) != NULL) {
|
||||
ml->ml_first = m->m_next;
|
||||
m->m_next = NULL;
|
||||
n = if_output_tso(ifp, &m, dst, rt, ifp->if_mtu);
|
||||
if (n)
|
||||
err = n;
|
||||
}
|
||||
ml->ml_len = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
if_enqueue(struct pfk_ifnet *ifp, struct mbuf *m)
|
||||
{
|
||||
/* PFLINUX M1-M4: no qdisc attached → direct xmit via same path;
|
||||
* M5 replaces this with the pfq enqueue (spec §4.3). */
|
||||
struct sockaddr sa = { .sa_family = AF_INET };
|
||||
struct mbuf *mp = m;
|
||||
|
||||
if (m_to_skb(m)->protocol == htons(ETH_P_IPV6))
|
||||
sa.sa_family = AF_INET6;
|
||||
if_output_tso(ifp, &mp, &sa, NULL, ifp->if_mtu);
|
||||
if (mp == NULL)
|
||||
return 0;
|
||||
m_freem(mp);
|
||||
return ENOBUFS;
|
||||
}
|
||||
|
||||
/* default-route egress device helper for pfk_egress_refresh() */
|
||||
struct net_device *
|
||||
ip_rt_get_default_dev(struct net *net)
|
||||
{
|
||||
struct dst_entry *d = pfk_route4(htonl(0x08080808), 0, 0);
|
||||
struct net_device *dev = NULL;
|
||||
|
||||
if (d) {
|
||||
dev = d->dev;
|
||||
if (dev)
|
||||
dev_hold(dev);
|
||||
dst_release(d);
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
/* ---------------- return errors ---------------- */
|
||||
u_int16_t
|
||||
pfk_in_cksum(const void *data, int len)
|
||||
{
|
||||
return csum_fold(csum_partial(data, len, 0));
|
||||
}
|
||||
|
||||
void
|
||||
pfk_send_icmp_error(int family, struct sk_buff *skb_in, int type, int code,
|
||||
__be32 info)
|
||||
{
|
||||
if (family == AF_INET) {
|
||||
icmp_send(skb_in, type, code, info);
|
||||
return;
|
||||
}
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
pr_warn_once("pfk: ICMPv6 error path pending P1 export (type %d)\n", type);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* NOTE: TCP RST generation is done by PF itself (pf.c builds the segment
|
||||
* and calls if_output_tso), so no kernel tcp_v4_send_reset export is
|
||||
* needed — matches OpenBSD's pf_send_rst() design. */
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* SipHash-2-4 implementation (PFLINUX) — reference algorithm by Jean-Philippe
|
||||
* Aumasson & Daniel J. Bernstein, public domain reference semantics.
|
||||
* ISC (c) 2026 PF-on-Linux contributors.
|
||||
*/
|
||||
#include <pfk.h>
|
||||
#include "compat/crypto/siphash.h"
|
||||
|
||||
#define ROTL64(x, b) (u64)(((x) << (b)) | ((x) >> (64 - (b))))
|
||||
|
||||
#define SIPROUND \
|
||||
do { \
|
||||
v0 += v1; v1 = ROTL64(v1, 13); v1 ^= v0; v0 = ROTL64(v0, 32); \
|
||||
v2 += v3; v3 = ROTL64(v3, 16); v3 ^= v2; \
|
||||
v0 += v3; v3 = ROTL64(v3, 21); v3 ^= v0; \
|
||||
v2 += v1; v1 = ROTL64(v1, 17); v1 ^= v2; v2 = ROTL64(v2, 32); \
|
||||
} while (0)
|
||||
|
||||
#define U8TO64_LE(p) \
|
||||
(((u64)((p)[0])) | ((u64)((p)[1]) << 8) | ((u64)((p)[2]) << 16) | \
|
||||
((u64)((p)[3]) << 24) | ((u64)((p)[4]) << 32) | ((u64)((p)[5]) << 40) | \
|
||||
((u64)((p)[6]) << 48) | ((u64)((p)[7]) << 56))
|
||||
|
||||
#define U64TO8_LE(p, v) \
|
||||
do { \
|
||||
(p)[0] = (u8)((v) & 0xff); (p)[1] = (u8)((v) >> 8); \
|
||||
(p)[2] = (u8)((v) >> 16); (p)[3] = (u8)((v) >> 24); \
|
||||
(p)[4] = (u8)((v) >> 32); (p)[5] = (u8)((v) >> 40); \
|
||||
(p)[6] = (u8)((v) >> 48); (p)[7] = (u8)((v) >> 56); \
|
||||
} while (0)
|
||||
|
||||
void
|
||||
SipHash24_Init(SIPHASH_CTX *ctx, const SIPHASH_KEY *key)
|
||||
{
|
||||
ctx->v[0] = 0x736f6d6570736575ULL ^ key->v[0];
|
||||
ctx->v[1] = 0x646f72616e646f6dULL ^ key->v[1];
|
||||
ctx->v[2] = 0x6c7967656e657261ULL ^ key->v[0];
|
||||
ctx->v[3] = 0x7465646279746573ULL ^ key->v[1];
|
||||
ctx->total_len = 0;
|
||||
ctx->block_len = 0;
|
||||
memset(ctx->block, 0, sizeof(ctx->block));
|
||||
}
|
||||
|
||||
void
|
||||
SipHash24_Update(SIPHASH_CTX *ctx, const void *in, __u64 inlen)
|
||||
{
|
||||
const unsigned char *ni = in;
|
||||
u64 total = ctx->total_len + inlen;
|
||||
int left = ctx->block_len;
|
||||
u64 i;
|
||||
|
||||
ctx->total_len = total;
|
||||
if (left) {
|
||||
unsigned int take = 8 - left;
|
||||
|
||||
if (take > inlen)
|
||||
take = inlen;
|
||||
memcpy(ctx->block + left, ni, take);
|
||||
left += take;
|
||||
ni += take;
|
||||
inlen -= take;
|
||||
if (left == 8) {
|
||||
u64 m = U8TO64_LE(ctx->block);
|
||||
u64 v0 = ctx->v[0], v1 = ctx->v[1];
|
||||
u64 v2 = ctx->v[2], v3 = ctx->v[3];
|
||||
|
||||
v3 ^= m;
|
||||
SIPROUND; SIPROUND;
|
||||
v0 ^= m;
|
||||
ctx->v[0] = v0; ctx->v[1] = v1;
|
||||
ctx->v[2] = v2; ctx->v[3] = v3;
|
||||
ctx->block_len = 0;
|
||||
}
|
||||
}
|
||||
while (inlen >= 8) {
|
||||
u64 m = U8TO64_LE(ni);
|
||||
u64 v0 = ctx->v[0], v1 = ctx->v[1];
|
||||
u64 v2 = ctx->v[2], v3 = ctx->v[3];
|
||||
|
||||
v3 ^= m;
|
||||
SIPROUND; SIPROUND;
|
||||
v0 ^= m;
|
||||
ctx->v[0] = v0; ctx->v[1] = v1;
|
||||
ctx->v[2] = v2; ctx->v[3] = v3;
|
||||
ni += 8; inlen -= 8;
|
||||
}
|
||||
if (inlen) {
|
||||
memcpy(ctx->block, ni, inlen);
|
||||
ctx->block_len = inlen;
|
||||
}
|
||||
(void)i;
|
||||
}
|
||||
|
||||
void
|
||||
SipHash24_Final(SIPHASH_CTX *ctx, unsigned char *out)
|
||||
{
|
||||
u64 b = ((u64)ctx->total_len) << 56;
|
||||
u64 v0 = ctx->v[0], v1 = ctx->v[1], v2 = ctx->v[2], v3 = ctx->v[3];
|
||||
unsigned char t[8];
|
||||
int i;
|
||||
|
||||
U64TO8_LE(t, b);
|
||||
for (i = 0; i < ctx->block_len; i++)
|
||||
t[i] = ctx->block[i];
|
||||
|
||||
v3 ^= b;
|
||||
SIPROUND; SIPROUND;
|
||||
v0 ^= b;
|
||||
v2 ^= 0xff;
|
||||
SIPROUND; SIPROUND; SIPROUND; SIPROUND;
|
||||
U64TO8_LE(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
/* wipe */
|
||||
ctx->v[0] = ctx->v[1] = ctx->v[2] = ctx->v[3] = 0;
|
||||
}
|
||||
|
||||
void
|
||||
SipHash24_Generate(const SIPHASH_KEY *key, const void *in, __u64 inlen,
|
||||
unsigned char *out)
|
||||
{
|
||||
u64 k0 = key->v[0], k1 = key->v[1];
|
||||
u64 v0, v1, v2, v3;
|
||||
|
||||
v0 = 0x736f6d6570736575ULL ^ k0;
|
||||
v1 = 0x646f72616e646f6dULL ^ k1;
|
||||
v2 = 0x6c7967656e657261ULL ^ k0;
|
||||
v3 = 0x7465646279746573ULL ^ k1;
|
||||
|
||||
while (inlen >= 8) {
|
||||
u64 m = U8TO64_LE((const unsigned char *)in);
|
||||
|
||||
v3 ^= m;
|
||||
SIPROUND; SIPROUND;
|
||||
v0 ^= m;
|
||||
in = (const unsigned char *)in + 8;
|
||||
inlen -= 8;
|
||||
}
|
||||
{
|
||||
unsigned char t[8];
|
||||
u64 b = ((u64)inlen) << 56;
|
||||
int i;
|
||||
|
||||
memset(t, 0, sizeof(t));
|
||||
memcpy(t, in, inlen);
|
||||
for (i = 0; i < 8; i++)
|
||||
t[i] ^= (unsigned char)(b >> (8 * i));
|
||||
{
|
||||
u64 m = U8TO64_LE(t);
|
||||
|
||||
v3 ^= m;
|
||||
SIPROUND; SIPROUND;
|
||||
v0 ^= m;
|
||||
}
|
||||
v2 ^= 0xff;
|
||||
SIPROUND; SIPROUND; SIPROUND; SIPROUND;
|
||||
U64TO8_LE(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Software Toeplitz hash (RFC 3305) for PF flow ids — PFLINUX.
|
||||
* The key window is SHA256(key) || key; the 16-bit result is the XOR of
|
||||
* the 16-bit windows selected by the set bits of the input.
|
||||
* ISC (c) 2026 PF-on-Linux contributors.
|
||||
*/
|
||||
#include <pfk.h>
|
||||
#include <crypto/hash.h>
|
||||
#include "compat/net/toeplitz.h"
|
||||
|
||||
struct stoeplitz_cache *stoeplitz_cache;
|
||||
|
||||
static int
|
||||
toeplitz_keyhash(const u8 *key, u8 *out /*32*/)
|
||||
{
|
||||
struct crypto_shash *tfm;
|
||||
char descbuf[sizeof(struct shash_desc) + 512];
|
||||
struct shash_desc *desc = (struct shash_desc *)descbuf;
|
||||
int err;
|
||||
|
||||
tfm = crypto_alloc_shash("sha256", 0, 0);
|
||||
if (!tfm)
|
||||
return -ENOMEM;
|
||||
desc->tfm = tfm;
|
||||
err = crypto_shash_digest(desc, key, TOEPLITZ_KEY_LEN, out);
|
||||
crypto_free_shash(tfm);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* core: hash `inlen` bytes of `in` (MSB-first bit order) into 16 bits */
|
||||
static uint16_t
|
||||
toeplitz_bits(const struct stoeplitz_cache *sc, const u8 *in, int inlen)
|
||||
{
|
||||
u8 window[64 + 32]; /* keyhash(32) || key(40) */
|
||||
int i, bit, k;
|
||||
uint16_t res = 0;
|
||||
|
||||
memcpy(window, sc->keyhash, 32);
|
||||
memcpy(window + 32, sc->key, TOEPLITZ_KEY_LEN);
|
||||
|
||||
for (i = 0; i < inlen; i++) {
|
||||
for (bit = 0; bit < 8; bit++) {
|
||||
if (!(in[i] & (0x80 >> bit)))
|
||||
continue;
|
||||
for (k = 0; k < 16; k++) {
|
||||
int pos = i * 8 + bit + k;
|
||||
int byte = pos / 8;
|
||||
int b = 7 - (pos % 8);
|
||||
|
||||
res ^= (uint16_t)((window[byte] >> b) & 1) << k;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
stoeplitz_cache_init(struct stoeplitz_cache *sc, const u8 *key)
|
||||
{
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
memcpy(sc->key, key, TOEPLITZ_KEY_LEN);
|
||||
toeplitz_keyhash(key, sc->keyhash);
|
||||
sc->initialized = 1;
|
||||
}
|
||||
|
||||
void
|
||||
stoeplitz_init(void)
|
||||
{
|
||||
static u8 key[TOEPLITZ_KEY_LEN];
|
||||
int i;
|
||||
|
||||
if (stoeplitz_cache && stoeplitz_cache->initialized)
|
||||
return;
|
||||
for (i = 0; i < TOEPLITZ_KEY_LEN; i++)
|
||||
key[i] = get_random_u8();
|
||||
stoeplitz_cache = kzalloc(sizeof(*stoeplitz_cache), GFP_KERNEL);
|
||||
if (!stoeplitz_cache)
|
||||
return;
|
||||
stoeplitz_cache_init(stoeplitz_cache, key);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_n16(const struct stoeplitz_cache *sc, u16 n16)
|
||||
{
|
||||
u8 b[2] = { (u8)(n16 >> 8), (u8)n16 };
|
||||
|
||||
return toeplitz_bits(sc, b, sizeof(b));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_n32(const struct stoeplitz_cache *sc, u32 n32)
|
||||
{
|
||||
return stoeplitz_hash_n16(sc, (u16)(n32 ^ (n32 >> 16)));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_h16(const struct stoeplitz_cache *sc, u16 h16)
|
||||
{
|
||||
u8 b[2] = { (u8)(h16 >> 8), (u8)h16 };
|
||||
|
||||
return toeplitz_bits(sc, b, sizeof(b));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_h32(const struct stoeplitz_cache *sc, u32 h32)
|
||||
{
|
||||
return stoeplitz_hash_h16(sc, (u16)(h32 ^ (h32 >> 16)));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_h64(const struct stoeplitz_cache *sc, u64 h64)
|
||||
{
|
||||
return stoeplitz_hash_h32(sc, (u32)(h64 ^ (h64 >> 32)));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_ip4(const struct stoeplitz_cache *sc,
|
||||
const struct in_addr *sa, const struct in_addr *da)
|
||||
{
|
||||
u8 buf[8];
|
||||
|
||||
memcpy(buf, &sa->s_addr, 4);
|
||||
memcpy(buf + 4, &da->s_addr, 4);
|
||||
return toeplitz_bits(sc, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_ip4port(const struct stoeplitz_cache *sc,
|
||||
const struct in_addr *sa, const struct in_addr *da, u16 sp, u16 dp)
|
||||
{
|
||||
u8 buf[12];
|
||||
|
||||
memcpy(buf, &sa->s_addr, 4);
|
||||
memcpy(buf + 4, &da->s_addr, 4);
|
||||
buf[8] = (u8)(sp >> 8); buf[9] = (u8)sp;
|
||||
buf[10] = (u8)(dp >> 8); buf[11] = (u8)dp;
|
||||
return toeplitz_bits(sc, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_ip6(const struct stoeplitz_cache *sc,
|
||||
const struct in6_addr *sa, const struct in6_addr *da)
|
||||
{
|
||||
u8 buf[32];
|
||||
|
||||
memcpy(buf, sa, 16);
|
||||
memcpy(buf + 16, da, 16);
|
||||
return toeplitz_bits(sc, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_ip6port(const struct stoeplitz_cache *sc,
|
||||
const struct in6_addr *sa, const struct in6_addr *da, u16 sp, u16 dp)
|
||||
{
|
||||
u8 buf[36];
|
||||
|
||||
memcpy(buf, sa, 16);
|
||||
memcpy(buf + 16, da, 16);
|
||||
buf[32] = (u8)(sp >> 8); buf[33] = (u8)sp;
|
||||
buf[34] = (u8)(dp >> 8); buf[35] = (u8)dp;
|
||||
return toeplitz_bits(sc, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
stoeplitz_hash_eaddr(const struct stoeplitz_cache *sc, const u8 *ea)
|
||||
{
|
||||
return toeplitz_bits(sc, ea, 6);
|
||||
}
|
||||
Reference in new issue
Block a user