- hooks: packets are queued to pfk_netisr ordered wq (process context) and re-injected with an skb->mark guard (PFK_MARK_PASSED); softirq-safe, documented divergence vs OpenBSD in-place ip_input continuation - pfk_rwlock: per-task writer recursion (NET/PF lock nesting like OpenBSD) - pfk_net: holder-task+depth reentrancy (pid-based hack removed) - ifnet events deferred to ordered workqueue (rtnl AB-BA fix) - idr iteration start index fix (kifs were never attached -> kif==NULL drop) - pfk_lock_trace module param for lock diagnostics - tests/vm: 01-state 02-block 03-tables 04-anchors + lib; vm-run-tests.sh - docs/en/install.md: apply/build guide (core patch set currently EMPTY)
506 lines
12 KiB
C
506 lines
12 KiB
C
/*
|
|
* 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 void pfk_if_event_call(int added, struct pfk_ifnet *ifp);
|
|
|
|
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);
|
|
/* BSD TAILQ heads are self-referential when empty (tqh_last points
|
|
* at &tqh_first); kzalloc leaves tqh_last == NULL, which the first
|
|
* TAILQ_INSERT_TAIL would dereference. */
|
|
TAILQ_INIT(&ifp->if_addrlist);
|
|
TAILQ_INIT(&ifp->if_groups);
|
|
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);
|
|
if (ifp->dev->hard_header_len > max_linkhdr)
|
|
max_linkhdr = ifp->dev->hard_header_len;
|
|
/* PFLINUX: sync the BSD sysctl mirrors on device events */
|
|
pfk_sysctl_sync();
|
|
/* address snapshot: rebuild list from Linux in_ifaddr/inet6_ifaddr */
|
|
pfk_if_addrlist_refresh(ifp);
|
|
}
|
|
|
|
/*
|
|
* OpenBSD if_get/if_put: registry entries are refcounted there; the port's
|
|
* pfk_ifnet objects are freed via call_rcu on UNREGISTER, so RCU/NET_LOCK
|
|
* readers can use them without an extra count (PFLINUX: if_put is a no-op).
|
|
*/
|
|
struct pfk_ifnet *
|
|
if_get(int ifidx)
|
|
{
|
|
return pfk_ifp_byindex(ifidx);
|
|
}
|
|
|
|
struct pfk_ifnet *
|
|
if_get_smr(int ifidx)
|
|
{
|
|
return pfk_ifp_byindex(ifidx);
|
|
}
|
|
|
|
void
|
|
if_put(struct pfk_ifnet *ifp)
|
|
{
|
|
(void)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 -------- */
|
|
/* Build a BSD-style ifaddr for the given interface address. */
|
|
static struct ifaddr *
|
|
pfkifa_new(struct pfk_ifnet *ifp, int family)
|
|
{
|
|
struct ifaddr *ifa = kzalloc(sizeof(*ifa), GFP_KERNEL);
|
|
|
|
if (!ifa)
|
|
return NULL;
|
|
ifa->ifa_ifp = ifp;
|
|
refcnt_init(&ifa->ifa_refcnt);
|
|
return ifa;
|
|
}
|
|
|
|
int
|
|
pfk_if_addrlist_refresh(struct pfk_ifnet *ifp)
|
|
{
|
|
struct ifaddr *ifa;
|
|
struct net_device *dev = ifp->dev;
|
|
|
|
/* free old */
|
|
while (!TAILQ_EMPTY(&ifp->if_addrlist)) {
|
|
ifa = TAILQ_FIRST(&ifp->if_addrlist);
|
|
TAILQ_REMOVE(&ifp->if_addrlist, ifa, ifa_list);
|
|
kfree(ifa->ifa_addr);
|
|
kfree(ifa);
|
|
}
|
|
|
|
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->ifa_addr = kmalloc(sizeof(struct sockaddr_in),
|
|
GFP_KERNEL);
|
|
if (!ifa->ifa_addr) {
|
|
kfree(ifa);
|
|
continue;
|
|
}
|
|
{
|
|
struct sockaddr_in *sin =
|
|
sintosa(ifa->ifa_addr);
|
|
memset(sin, 0, sizeof(*sin));
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = ifa4->ifa_address;
|
|
}
|
|
TAILQ_INSERT_TAIL(&ifp->if_addrlist, ifa, ifa_list);
|
|
}
|
|
}
|
|
/* 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->ifa_addr = kmalloc(sizeof(struct sockaddr_in6),
|
|
GFP_KERNEL);
|
|
if (!ifa->ifa_addr) {
|
|
kfree(ifa);
|
|
continue;
|
|
}
|
|
{
|
|
struct sockaddr_in6 *sin6 =
|
|
sin6tosa(ifa->ifa_addr);
|
|
memset(sin6, 0, sizeof(*sin6));
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_addr = ifa6->addr;
|
|
}
|
|
TAILQ_INSERT_TAIL(&ifp->if_addrlist, ifa, ifa_list);
|
|
}
|
|
}
|
|
#endif
|
|
rcu_read_unlock();
|
|
return 0;
|
|
}
|
|
|
|
struct ifaddr *
|
|
pfk_ifwithaddr(const struct sockaddr *sa)
|
|
{
|
|
struct pfk_ifnet *ifp;
|
|
struct ifaddr *ifa;
|
|
|
|
for (ifp = pfk_if_first(); ifp; ifp = pfk_if_next(ifp)) {
|
|
TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
|
|
if (!ifa->ifa_addr ||
|
|
ifa->ifa_addr->sa_family != sa->sa_family)
|
|
continue;
|
|
if (sa->sa_family == AF_INET &&
|
|
sintosa(ifa->ifa_addr)->sin_addr.s_addr ==
|
|
sintosa(sa)->sin_addr.s_addr)
|
|
return ifa;
|
|
if (sa->sa_family == AF_INET6 &&
|
|
!memcmp(&sin6tosa(ifa->ifa_addr)->sin6_addr,
|
|
&sin6tosa(sa)->sin6_addr,
|
|
sizeof(struct in6_addr)))
|
|
return ifa;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
pfk_if_free(struct pfk_ifnet *ifp)
|
|
{
|
|
struct ifaddr *ifa;
|
|
|
|
while (!TAILQ_EMPTY(&ifp->if_addrlist)) {
|
|
ifa = TAILQ_FIRST(&ifp->if_addrlist);
|
|
TAILQ_REMOVE(&ifp->if_addrlist, ifa, ifa_list);
|
|
kfree(ifa->ifa_addr);
|
|
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);
|
|
pfk_if_event_call(1, ifp);
|
|
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);
|
|
pfk_if_event_call(0, ifp);
|
|
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,
|
|
};
|
|
|
|
static void (*pfk_if_event_cb)(int added, struct pfk_ifnet *ifp);
|
|
|
|
/*
|
|
* PFLINUX: netdev notifiers run with rtnl held; taking NET_LOCK there
|
|
* deadlocks (AB-BA) against control paths that hold NET_LOCK and touch
|
|
* rtnl (dev_get_by_name etc.). Interface attach/detach is therefore
|
|
* deferred to an ordered workqueue (rtnl-free context) preserving order.
|
|
*/
|
|
struct pfk_if_event {
|
|
struct work_struct work;
|
|
int added;
|
|
struct pfk_ifnet *ifp;
|
|
};
|
|
|
|
static struct workqueue_struct *pfk_if_wq;
|
|
|
|
static void
|
|
pfk_if_event_work(struct work_struct *w)
|
|
{
|
|
struct pfk_if_event *ev = container_of(w, struct pfk_if_event, work);
|
|
|
|
if (pfk_if_event_cb)
|
|
pfk_if_event_cb(ev->added, ev->ifp);
|
|
kfree(ev);
|
|
}
|
|
|
|
static void
|
|
pfk_if_event_call(int added, struct pfk_ifnet *ifp)
|
|
{
|
|
struct pfk_if_event *ev;
|
|
|
|
if (!pfk_if_event_cb || !pfk_if_wq)
|
|
return;
|
|
ev = kmalloc(sizeof(*ev), GFP_ATOMIC);
|
|
if (!ev)
|
|
return;
|
|
INIT_WORK(&ev->work, pfk_if_event_work);
|
|
ev->added = added;
|
|
ev->ifp = ifp;
|
|
queue_work(pfk_if_wq, &ev->work);
|
|
}
|
|
|
|
void
|
|
pfk_if_set_event_cb(void (*cb)(int added, struct pfk_ifnet *ifp))
|
|
{
|
|
pfk_if_event_cb = cb;
|
|
}
|
|
|
|
void
|
|
pfk_if_iterate(void (*cb)(struct pfk_ifnet *ifp))
|
|
{
|
|
struct pfk_ifnet *ifp;
|
|
int id = 0;
|
|
|
|
while ((ifp = pfk_if_next_from(&id)) != NULL)
|
|
cb(ifp);
|
|
}
|
|
|
|
/* -------- 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 = 0;
|
|
|
|
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 : 0;
|
|
|
|
return pfk_if_next_from(&id);
|
|
}
|
|
|
|
int
|
|
pfk_if_init(void)
|
|
{
|
|
struct net_device *dev;
|
|
int err;
|
|
|
|
pfk_if_wq = alloc_ordered_workqueue("pfk_if", 0);
|
|
if (!pfk_if_wq)
|
|
return -ENOMEM;
|
|
err = register_netdevice_notifier(&pfk_netdev_nb);
|
|
if (err) {
|
|
destroy_workqueue(pfk_if_wq);
|
|
pfk_if_wq = NULL;
|
|
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);
|
|
pfk_if_event_call(1, ifp);
|
|
}
|
|
}
|
|
rtnl_unlock();
|
|
pfk_egress_refresh();
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pfk_if_exit(void)
|
|
{
|
|
if (pfk_if_wq) {
|
|
flush_workqueue(pfk_if_wq);
|
|
destroy_workqueue(pfk_if_wq);
|
|
pfk_if_wq = NULL;
|
|
}
|
|
struct pfk_ifnet *ifp;
|
|
int id = 0;
|
|
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 = 0;
|
|
}
|
|
spin_unlock_irqrestore(&pfk_ifnet_lock, flags);
|
|
}
|