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:

NetBSD Home
NetBSD PR Database Search

(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.