nat: re-route inbound packets whose destination pf rewrote (reverse NAT) instead of delivering them locally

This commit is contained in:
lgcosta committed 2026-11-11 20:17:27 -03:00
1 parent 9c3c4b6bbc
commit d520172753
2 files changed
+50 -1

No files matched your search

+46
View File
@@ -71,14 +71,50 @@ pfk_pkt_reinject(struct pfk_pkt *pk)
kfree(pk); kfree(pk);
} }
/*
* Reverse NAT: pf rewrote the destination of an inbound packet (the reply
* of a translated flow). OpenBSD re-routes such a packet in ip_input()
* (PF_TAG_REROUTE); Linux has no such layer, so re-route it here and push
* it out through the new egress device.
*/
static void
pfk_reroute(struct sk_buff *skb)
{
struct flowi4 fl4 = { };
struct rtable *rt;
struct iphdr *iph = ip_hdr(skb);
fl4.daddr = iph->daddr;
fl4.saddr = iph->saddr;
fl4.flowi4_oif = 0;
fl4.flowi4_mark = 0;
fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
rt = ip_route_output_key(&init_net, &fl4);
if (IS_ERR(rt) || rt == NULL) {
kfree_skb(skb);
return;
}
skb_dst_set(skb, &rt->dst);
skb->dev = rt->dst.dev;
skb_reset_network_header(skb);
rt->dst.output(&init_net, skb->sk, skb);
}
static void static void
pfk_pkt_work(struct work_struct *w) pfk_pkt_work(struct work_struct *w)
{ {
struct pfk_pkt *pk = container_of(w, struct pfk_pkt, work); struct pfk_pkt *pk = container_of(w, struct pfk_pkt, work);
struct mbuf *m, *m0; struct mbuf *m, *m0;
struct sk_buff *orig; struct sk_buff *orig;
__be32 orig_dst = 0;
int action; int action;
if (pk->af == AF_INET && pk->dir == PF_IN &&
pskb_may_pull(pk->skb, sizeof(struct iphdr)))
orig_dst = ip_hdr(pk->skb)->daddr;
m = pfk_m_wrap_skb(pk->skb, pk->af); m = pfk_m_wrap_skb(pk->skb, pk->af);
if (!m) { if (!m) {
pfk_pkt_reinject(pk); /* like "pf not consulted" */ pfk_pkt_reinject(pk); /* like "pf not consulted" */
@@ -98,6 +134,16 @@ pfk_pkt_work(struct work_struct *w)
/* same buffer, possibly rewritten in place: our wrapper /* same buffer, possibly rewritten in place: our wrapper
* held one extra skb reference — release it */ * held one extra skb reference — release it */
m_freem(m); m_freem(m);
if (action == PF_PASS && orig_dst != 0 &&
pk->af == AF_INET && pk->dir == PF_IN &&
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
ip_hdr(pk->skb)->daddr != orig_dst) {
/* pf translated the destination (reverse NAT):
* re-route instead of delivering locally */
pfk_reroute(pk->skb);
kfree(pk);
return;
}
if (action == PF_PASS) { if (action == PF_PASS) {
pfk_pkt_reinject(pk); pfk_pkt_reinject(pk);
return; return;
+4 -1
View File
@@ -17,8 +17,11 @@ EGRESS_IP=$(pf0 "ip -4 addr show $EGRESS_DEV | grep -o 'inet [0-9.]*' | head -1
pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true' pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true'
pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null
# OpenBSD expands "(egress)" (an interface *group*) into the addresses of all
# its members - on a multi-NIC host that is ambiguous, so the gate uses the
# concrete transit interface (the OpenBSD-recommended form for NAT pools).
cat > /tmp/g05.conf <<C cat > /tmp/g05.conf <<C
pass out quick on egress from 10.0.1.0/24 to any nat-to (egress) pass out quick on egress from 10.0.1.0/24 to any nat-to ($EGRESS_DEV)
pass in quick on ingress proto icmp keep state pass in quick on ingress proto icmp keep state
pass out quick on egress proto icmp keep state pass out quick on egress proto icmp keep state
C C