NetBSD Problem Report #60191
From www@netbsd.org Mon Apr 13 16:10:40 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits)
client-signature RSA-PSS (2048 bits))
(Client CN "mail.netbsd.org", Issuer "R12" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id B95911A923A
for <gnats-bugs@gnats.NetBSD.org>; Mon, 13 Apr 2026 16:10:40 +0000 (UTC)
Message-Id: <20260413161039.730A21A923E@mollari.NetBSD.org>
Date: Mon, 13 Apr 2026 16:10:39 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: npf: map dynamic to localhost works for v4 but not for v6
X-Send-Pr-Version: www-1.0
X-From4GNATS: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>
>Number: 60191
>Notify-List: joe@NetBSD.org
>Category: kern
>Synopsis: npf: map dynamic to localhost works for v4 but not for v6
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Mon Apr 13 16:15:00 +0000 2026
>Last-Modified: Wed Apr 29 13:35:01 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, 9, ...
>Organization:
The NetBSDv6 Forwarding
>Environment:
>Description:
With npf, a dynamic inbound NAT mapping of
map $if dynamic proto tcp 127.0.0.1 port 8000 <- $v4 port 80
works, but
map $if dynamic proto tcp ::1 port 8000 <- $v6 port 80
does not -- the packets arrive but don't seem to go anywhere.
>How-To-Repeat:
Host has a public IPv4 address and a public IPv6 address.
httpd listens on 127.0.0.1:8000 and [::1]:8000, as confirmed by
sockstat:
# sockstat -n | grep 'httpd.*tcp'
1001 httpd 4024 3 tcp 127.0.0.1.8000 *.*
1001 httpd 4024 4 tcp6 ::1.8000 *.*
1001 httpd 5965 3 tcp 127.0.0.1.8000 *.*
1001 httpd 5965 4 tcp6 ::1.8000 *.*
1001 httpd 12899 3 tcp 127.0.0.1.8000 *.*
1001 httpd 12899 4 tcp6 ::1.8000 *.*
1001 httpd 21211 3 tcp 127.0.0.1.8000 *.*
1001 httpd 21211 4 tcp6 ::1.8000 *.*
Complete npf.conf:
$primary_if = "xennet0"
$primary_addrs = ifaddrs($primary_if)
$primary_v4 = { inet4($primary_if) }
$primary_v6 = { inet6($primary_if) }
procedure "log" {
log: npflog0
}
map $primary_if dynamic proto tcp 127.0.0.1 port 8000 <- $primary_v4 port 80
map $primary_if dynamic proto tcp ::1 port 8000 <- $primary_v6 port 80
group default {
pass final on lo0 all
pass final on $primary_if all
block all apply "log"
}
Querying the public address over IPv4 on port 80 works:
$ curl -4 --head 'http://...'
HTTP/1.1 200 ...
But querying the public address over IPv6 on port 80 just hangs:
$ curl -6 --head 'http://...'
`tcpdump -i lo0 -s 0 -vvv -n tcp port 8000 or tcp port 80'
produces no output in either case.
`tcpdump -i xennet0 -s 0 -vvv -n tcp port 8000 or tcp port 80'
in the IPv4 case shows the whole conversation as expected.
`tcpdump -i xennet0 -s 0 -vvv -n tcp port 8000 or tcp port 80'
in the IPv6 case shows incoming TCP SYN packets and nothing
else.
>Fix:
Yes, please!
>Release-Note:
>Audit-Trail:
From: Taylor R Campbell <riastradh@NetBSD.org>
To: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Cc:
Subject: Re: kern/60191: npf: map dynamic to localhost works for v4 but not for v6
Date: Wed, 29 Apr 2026 00:16:24 +0000
Traffic _from the local host_ to its own public addresses is also
troublesome.
With the rules above, _remote hosts_ can do, e.g.,
ftp -4 http://example.com/robots.txt
and it works fine, while
ftp -6 http://example.com/robots.txt
does not (packets arrive at $primary_if but go nowhere), where
example.com has those npf rules -- that's the original issue.
I can work around this by changing the v6 rule to
map $primary_if dynamic proto tcp $primary_addrs port 8000 \
<- $primary_v6 port 80
at the cost of inadvertently exposing public access via port 8000 too
(not a big deal but kind of sloppy).
On the _local host_ (i.e., on example.com),
ftp -4 http://example.com/robots.txt
gives connection refused, via TCP RST that shows up on lo0 with
tcpdump, and leads to no output in `npfctl list -n'. I can make this
work by adding:
map lo0 dynamic proto tcp 127.0.0.1 port 8000 \
<- $primary_v4 port 80
If the v4 address is 192.0.2.123, then with this additional map rule,
`npfctl list -n' shows (after running ftp -4):
# src-addr:port dst-addr:port interface nat-addr:port
192.0.2.123:65519 192.0.2.123:80 lo0 127.0.0.1:8000
But I can't find how to make
ftp -6 http://example.com/robots.txt
work _on the local host_ (i.e., on example.com).
I tried various combinations of map lo0 and map $primary_if with
sources and destinations of $primary_v6, $primary_addrs, and
hard-coded explicit addresses -- couldn't find anything that worked;
everything I tried went nowhere with zero output from tcpdump on
either lo0 or $primary_if.
If the v6 addresses are 2001:db8::123 and fe80::123, and $primary_if
is xennet0, I've gotten `npfctl list -n' to show alternately
# src-addr:port dst-addr:port interface nat-addr:port
[2001:db8::123]:65478 [2001:db8::123]:80 xennet0 [fe80::123]:8000
or
# src-addr:port dst-addr:port interface nat-addr:port
[2001:db8::123]:65478 [2001:db8::123]:80 xennet0 [2001:db8::123]:8000
(but I was flailing around a lot and don't remember exactly which set
of options led to which output). Note that in all my attempts, it
showed xennet0 ($primary_if), never lo0! Perhaps that is a clue that
will lead to the source of the problem: v4 is getting mapped through
lo0, but v6 is getting mapped through $primary_if instead.
From: Emmanuel Nyarko <emmankoko519@gmail.com>
To: gnats-bugs@netbsd.org
Cc: kern-bug-people@netbsd.org,
gnats-admin@netbsd.org,
netbsd-bugs@netbsd.org,
campbell+netbsd@mumble.net,
"joe@netbsd.org" <joe@NetBSD.org>
Subject: Re: kern/60191: npf: map dynamic to localhost works for v4 but not
for v6
Date: Wed, 29 Apr 2026 13:30:49 +0000
I reproduced your case and this is what I found in dmesg.=20
Apparently it is from ip6_input. RFC 4007
Joe$ dmesg
loopback address ::1
[ 74.409134] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 75.409142] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 76.412796] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 77.412841] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 78.412919] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 80.412930] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
[ 84.412913] in6_setscope: can't set scope for not loopback interface =
wm0 and loopback address ::1
Joe$=20
I traced in Kernel and looks like scope is violated=20
for the not lookbak loopback interface for ::1.
This happens right after filtering.
if (in6_setscope(&ip6->ip6_src, rcvif, NULL) ||
in6_setscope(&ip6->ip6_dst, rcvif, NULL)) {
IP6_STATINC(IP6_STAT_BADSCOPE);
goto bad;
}
Hence a valid scope violation drop by the kernel.
in in6_setscope
if (IN6_IS_ADDR_LOOPBACK(in6)) {
if (!(ifp->if_flags & IFF_LOOPBACK)) {
char ip6buf[INET6_ADDRSTRLEN];
log(LOG_NOTICE, "%s: can't set scope for not =
loopback "
"interface %s and loopback address %s\n",
__func__, if_name(ifp), IN6_PRINT(ip6buf, =
in6));
return EINVAL;
} else {
if (ret_id !=3D NULL)
*ret_id =3D 0; /* there's no ambiguity =
*/
return 0;
}
}
Possible hack:
Unless we hack it in NPF to check if NATed address is loopback
then we transmit it ourself by the firewall. I don=E2=80=99t know how =
good=20
of a solution that is. otherwise, you just cannot do.
Emmanuel
>Unformatted:
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.