divert: end-to-end working (network-order port, direct IN-verdict handoff, L2-synth reinjection, skip-mark anti-loop, pdivert ICMP checksum fix)

This commit is contained in:
lgcosta committed 2026-11-11 19:45:29 -03:00
1 parent 4728648a0c
commit 57ecc15606
7 files changed
+82 -50

No files matched your search

+2
View File
@@ -7,6 +7,8 @@
long pfk_ioctl(struct file *f, unsigned int cmd, unsigned long addr);
int pfk_enable(int on);
void pfk_status(struct pf_status *s);
#define PFK_SKIP_PF 0x50460040u /* skb->mark: reinjected, bypass pf */
int pfk_hooks_register(void);
void pfk_hooks_unregister(void);
/* AF_DIVERT family (module-internal socket protocol, PF_DIVERT=32) */
+29 -2
View File
@@ -63,6 +63,7 @@ struct pfk_divert_sock {
spinlock_t lock;
u16 port;
u32 qlen;
int last_ifindex; /* device of last queued packet */
wait_queue_head_t wq;
u32 qspace;
struct sock *sk;
@@ -146,6 +147,7 @@ pfk_divert_queue(struct sk_buff *skb, int dir, int type, u16 port)
list_add_tail(&dp->node, &ds->q);
ds->qlen++;
ds->stats.divs_ipackets++;
ds->last_ifindex = dp->ifindex;
spin_unlock(&ds->lock);
spin_unlock_irqrestore(&pfk_divert_lock, flags);
@@ -235,7 +237,9 @@ pfk_divert_bind(struct socket *sock, struct sockaddr_unsized *uaddr,
if (addr_len < (int)sizeof(struct sockaddr_in))
return -EINVAL;
ds->port = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
/* BSD divsock stores the port in network order (compared against
* inp_lport / pf_divert.port as-is). */
ds->port = ((struct sockaddr_in *)uaddr)->sin_port;
if (ds->port == 0)
return -EADDRNOTAVAIL; /* divert port 0 not allowed */
@@ -356,6 +360,8 @@ pfk_divert_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
return -EFAULT;
}
skb->protocol = htons(ETH_P_IP);
if (ifindex <= 0)
ifindex = ds->last_ifindex; /* same path the packet came in */
if (type == PF_DIVERT_TO) {
/* deliver locally as if arrived on ifindex (default: last
@@ -364,10 +370,31 @@ pfk_divert_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
dev_get_by_index(&init_net, ifindex) : NULL;
if (dev) {
struct ethhdr *eh;
skb->dev = dev;
dev_put(dev);
skb->mark |= PFK_SKIP_PF; /* avoid divert loop */
/*
* The receive path of an Ethernet device expects an
* L2 header: synthesise one (the packet keeps its
* original addresses; the stack only needs the
* ethertype to demultiplex).
*/
eh = skb_push(skb, ETH_HLEN);
if (!eh) {
kfree_skb(skb);
return -ENOMEM;
}
memset(eh, 0, ETH_HLEN);
memcpy(eh->h_dest, dev->dev_addr, ETH_ALEN);
memcpy(eh->h_source, dev->dev_addr, ETH_ALEN);
eh->h_proto = htons(ETH_P_IP);
skb_reset_mac_header(skb);
skb->protocol = htons(ETH_P_IP);
skb_pull(skb, ETH_HLEN); /* like eth_type_trans() */
/* data now points at the IP header again */
skb_reset_network_header(skb);
/* netif_rx wants data at L3 */
netif_rx(skb);
return (int)len;
}
+13 -9
View File
@@ -8283,6 +8283,10 @@ pf_test(sa_family_t af, int fwdir, struct pfk_ifnet *ifp, struct mbuf **m0)
if (!pf_status.running)
return (PF_PASS);
if (m0 && *m0 && (*m0)->m_pkthdr.len >= 20 &&
((*m0)->m_data[9] == IPPROTO_ICMP))
pr_info_ratelimited("pfk: T1 enter dir=%d ifp=%px kif=%px\n",
fwdir, ifp, ifp ? ifp->if_pf_kif : NULL);
#if NCARP > 0
if (ifp->if_type == IFT_CARP) {
@@ -8639,18 +8643,18 @@ done:
if (pd.destchg && pd.dir == PF_OUT)
pd.m->m_pkthdr.pf.flags |= PF_TAG_REROUTE;
/*
* PFLINUX: OpenBSD tags the mbuf here (PACKET_TAG_PF_DIVERT) and
* netinet/ip_input.c diverts it right after pf_test(). This port has
* no ip_input layer to hook; the verdict is mbuf-exclusive at this
* point, so hand the packet to the divert socket immediately -
* same observable semantics, no tag round-trip.
*/
if (pd.dir == PF_IN && action == PF_PASS &&
(r->divert.type == PF_DIVERT_TO ||
r->divert.type == PF_DIVERT_REPLY)) {
struct pf_divert *divert;
if ((divert = pf_get_divert(pd.m))) {
pd.m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED;
divert->addr = r->divert.addr;
divert->port = r->divert.port;
divert->rdomain = pd.rdomain;
divert->type = r->divert.type;
}
divert_packet(pd.m, pd.dir, r->divert.port);
pd.m = NULL;
}
if (action == PF_PASS && r->divert.type == PF_DIVERT_PACKET)
-2
View File
@@ -2275,13 +2275,11 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
error = ENOMEM;
goto fail;
}
if ((error = pf_rule_copyin(&pr->rule, rule))) {
pf_rule_free(rule);
rule = NULL;
goto fail;
}
if (pr->rule.return_icmp >> 8 > ICMP_MAXTYPE) {
error = EINVAL;
pf_rule_free(rule);
+5 -10
View File
@@ -23,6 +23,7 @@
extern int pf_test(sa_family_t, int, struct pfk_ifnet *, struct mbuf **);
extern void divert_packet(struct mbuf *, int, u_int16_t);
extern struct pf_divert *pf_find_divert(struct mbuf *);
extern struct workqueue_struct *pfk_netisr_wq;
/* PFLINUX: netisr work item — carries ownership of one sk_buff */
@@ -92,16 +93,6 @@ pfk_pkt_work(struct work_struct *w)
* and packet walk run concurrently from here on — documented. */
action = pf_test(pk->af, pk->dir, pk->ifp, &m);
/* PF_IN rules with `divert-to` only TAG the packet (BSD
* netinet/ip_input.c delivers tagged packets to the divert socket);
* do that here. divert_packet() consumes the mbuf wrapper and holds
* its own skb reference, so our wrapper pointer becomes invalid. */
if (action == PF_PASS && m != NULL &&
(m->m_pkthdr.pf.flags & PF_TAG_DIVERTED)) {
m->m_pkthdr.pf.flags &= ~PF_TAG_DIVERTED;
divert_packet(m, pk->dir, 0);
m = NULL;
}
if (m == m0) {
/* same buffer, possibly rewritten in place: our wrapper
@@ -156,6 +147,10 @@ pfk_enqueue_verdict(int af, struct sk_buff *skb,
default:
return NF_ACCEPT; /* LOCAL_IN/FORWARD */
}
af, skb->len, ntohs(skb->protocol), skb->dev ? skb->dev->name : "?",
skb->mark);
if (skb->mark & PFK_SKIP_PF)
return NF_ACCEPT; /* reinjected by divert: don't re-filter */
if (skb->mark & (dir == PF_IN ? PFK_DONE_IN : PFK_DONE_OUT))
return NF_ACCEPT;