diff --git a/sys/net/pfil.h b/sys/net/pfil.h --- a/sys/net/pfil.h +++ b/sys/net/pfil.h @@ -136,6 +136,7 @@ /* Public functions for pfil hook management by packet filters. */ pfil_hook_t pfil_add_hook(struct pfil_hook_args *); void pfil_remove_hook(pfil_hook_t); +void pfil_mbuf_skip_hook(struct mbuf *, pfil_hook_t); /* Argument structure used by ioctl() and packet filters to set filters. */ struct pfil_link_args { @@ -192,5 +193,8 @@ #define PFIL_HOOKED_IN(p) (((struct _pfil_head *)(p))->head_nhooksin > 0) #define PFIL_HOOKED_OUT(p) (((struct _pfil_head *)(p))->head_nhooksout > 0) +#define MTAG_PFIL 0x7066696c /* hex for ascii pfil */ +#define MTAG_PFIL_NEXT_HOOK 0 + #endif /* _KERNEL */ #endif /* _NET_PFIL_H_ */ diff --git a/sys/net/pfil.c b/sys/net/pfil.c --- a/sys/net/pfil.c +++ b/sys/net/pfil.c @@ -193,12 +193,33 @@ return (pfil_mem_common(&head->head_out, mem, len, PFIL_OUT, ifp, m)); } +static __always_inline struct pfil_hook** +pfil_mbuf_get_skip_mtag(struct mbuf **m) { + struct m_tag *mtag; + + mtag = m_tag_locate(*m, MTAG_PFIL, MTAG_PFIL_NEXT_HOOK, NULL); + if (mtag != NULL) + return (struct pfil_hook**)(mtag + 1); + + mtag = m_tag_alloc(MTAG_PFIL, MTAG_PFIL_NEXT_HOOK, sizeof(void*), + M_ZERO | M_NOWAIT); + if (mtag == NULL) { + /* Drop the packet to prevent looping */ + m_freem(*m); + *m = NULL; + return NULL; + } + m_tag_prepend(*m, mtag); + return (struct pfil_hook**)(mtag + 1); +} + static __always_inline int pfil_mbuf_common(pfil_chain_t *pch, struct mbuf **m, struct ifnet *ifp, int flags, struct inpcb *inp) { - struct pfil_link *link; - pfil_return_t rv; + struct pfil_hook **last_hook; + struct pfil_link *link; + pfil_return_t rv; NET_EPOCH_ASSERT(); KASSERT((flags & ~(PFIL_IN|PFIL_OUT|PFIL_FWD)) == 0, @@ -207,17 +228,45 @@ (flags & ~PFIL_FWD) == PFIL_OUT, ("%s: conflicting directions %#x", __func__, flags)); + if ((last_hook = pfil_mbuf_get_skip_mtag(m)) == NULL) + return PFIL_DROPPED; + rv = PFIL_PASS; CK_STAILQ_FOREACH(link, pch, link_chain) { + /* + * We can't use CK_STAILQ_FOREACH_FROM because list of pfil + * hooks could have been modified, so we can't trust that the + * pointer is still valid. + */ + if (*last_hook != NULL) { + if (*last_hook == link->link_hook) + *last_hook = NULL; + continue; + } + /* + * Set the last_hook link before link_mbuf_chk() in case + * the hook consumes and then re-injects the packet, like + * for example dummynet does. + */ + *last_hook = link->link_hook; rv = link->link_mbuf_chk(m, ifp, flags, link->link_ruleset, inp); if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED) { MPASS(*m == NULL); - break; + return (rv); } else { MPASS(*m != NULL); } + /* + * Find the last_hook tag again, in case the pfil hook + * has changed *m, like for example happens for mbufs + * reassempled by pf. + */ + if ((last_hook = pfil_mbuf_get_skip_mtag(m)) == NULL) + return PFIL_DROPPED; + *last_hook = NULL; } + *last_hook = NULL; return (rv); } @@ -508,6 +557,18 @@ free(hook, M_PFIL); } +/* + * Modify last hook tracking of pfil_mbuf_common to skip to after the given hook + */ +void pfil_mbuf_skip_hook(struct mbuf *m, pfil_hook_t hook) +{ + struct m_tag *pfil_mtag; + + pfil_mtag = m_tag_locate(m, MTAG_PFIL, MTAG_PFIL_NEXT_HOOK, NULL); + MPASS(pfil_mtag != NULL); + *(pfil_hook_t *)(pfil_mtag + 1) = hook; +} + /* * Internal: Remove a pfil hook from a hook chain. */