Dimitris,

On 11/11/25 5:46 PM, Dimitris Soumis wrote:
On Tue, Nov 11, 2025 at 9:47 PM Christopher Schultz <
[email protected]> wrote:

Mark,

On 11/11/25 1:17 PM, Christopher Schultz wrote:
Mark,

On 11/11/25 10:53 AM, Christopher Schultz wrote:
Mark,

On 11/10/25 10:08 AM, Mark Thomas wrote:
On 10/11/2025 15:01, Christopher Schultz wrote:
Mark,

On 11/10/25 9:48 AM, Mark Thomas wrote:
On 10/11/2025 14:15, Christopher Schultz wrote:
All,

I've been looking into this, and I think the problem is that all
of the link-local interfaces detected during this test are not
actually routable:

fe80:0:0:0:2609:93a5:c4ac:15ea%utun7
fe80:0:0:0:926b:f264:7ec7:283e%utun6
fe80:0:0:0:4ab0:1734:75f8:c236%utun5
fe80:0:0:0:edc2:bbe0:ab0e:db71%utun4
fe80:0:0:0:898:f4ff:feb6:6b87%llw0
fe80:0:0:0:898:f4ff:feb6:6b87%awdl0
fe80:0:0:0:ce81:b1c:bd2c:69e%utun3
fe80:0:0:0:96b7:f229:b97f:3887%utun2
fe80:0:0:0:c055:a248:e218:ba6b%utun1
fe80:0:0:0:310c:b555:9669:572a%utun0

So even though Tomcat can bind to them, and you can create a URL
to try to connect, the OS will never route the packets as expected.

When checking all available interfaces on my machine, they all
seem to be IPv6 and all seem to be .. unusable to Tomcat?

If I hard-code the interface "fe80::1%lo0" into the unit test, it
passes immediately and successfully. But when left to its own
devices, the test will randomly pick one of the other interfaces
and hang.

I think the testing environment is being sandboxed by the OS and
so it doesn't even see those other interfaces that I know exist.
Instead, I only see these useless tunneling interfaces. For
example, "lo0" doesn't show up, and "en0" doesn't show up -- the
primary useful interfaces on this machine.

So I wonder if we need some kind of workaround for MacOS.

I think we need a more general work-around. Seeing a number of
those were tunnels reminded me of the change we made to the unlock
accept code to skip point to point interfaces.

Something like:

diff --git a/test/org/apache/catalina/startup/
TestStartupIPv6Connectors.java b/test/org/apache/catalina/startup/
TestStartupIPv6Connectors.java
index c3c4b9a..2212e39 100644
--- a/test/org/apache/catalina/startup/TestStartupIPv6Connectors.java
+++ b/test/org/apache/catalina/startup/TestStartupIPv6Connectors.java
@@ -68,6 +68,9 @@
           while (interfaces.hasMoreElements()) {
               NetworkInterface interf = interfaces.nextElement();
               Enumeration<InetAddress> addresses =
interf.getInetAddresses();
+            if (interf.isPointToPoint()) {
+                continue;
+            }
               while (addresses.hasMoreElements()) {
                   InetAddress address = addresses.nextElement();
                   if (address instanceof Inet6Address inet6Address) {

+1

This is one of the patches I had considered, but when I started
adding debug code to print stuff out, I had a in it and it wasn't
printing the %en interface (which would work) so I thought it would
always fail.

I've fixed it and I think we basically have the same patch at this
point.

But it still doesn't work for me, since %lo0 doesn't appear in the
list. %en0 not does appear

This should have been "%en0 DOES appear..."

, but it's not link-local and so it doesn't
get chosen for this. Instead, the test chooses the %awdl0 interface
which is equally as useless for this test.

What is that interface? Is there some characteristic we can use to
filter it out as well? If the test can't find any interfaces, it will
skip the test.

I think awdl0 has something to do with AirPlay.

ChatGPT says that I need to allow Java more privileges -- like "Full
Disk Access" in order to get access to the various real network
interfaces. I was hoping for a solution that didn't require that
because while it will work for me, it won't likely work for anyone
else who just happens to want to run the unit tests.

We might have to implement an allow-list, block-list, etc.

Yuck. I'd much prefer a general solution if we can find one.

I spent some time chatting with AI and it didn't have any better ideas
other than a combination allow-list + deny-list to skip known-bad
interfaces (like %utun*) and include known-good interfaces (like %lo*
and %en*).

I would also very much like to find a better solution.

If I run the test with your patch, the test is skipped. Which is good,
I suppose. But it means the test isn't being run. I will give more
permissions to the JVM and see if it can see all interfaces and,
therefore, allow this test to run.

Something is still weird.

I wrote a short program to just dump out all the details of the network
interfaces so I could compare before/after giving privileges and the
before-state shows *everything*.

So I'll need to dig more into why the test isn't seeing these interfaces
in my environment.

This is a face-palm; I had completely forgotten that the test runs
through interfaces until it finds what it's looking for: both link-local
and "global" addresses. But it doesn't have any preference for which
one(s) of those it chooses.

So it can detect-and-choose more than one link-local address, for
example, and the last one wins. Since we don't really care about which
link-local interface is chosen, it doesn't matter to us, either. But it
also doesn't have a fixed order in which it traverses the interface
list: it just looks in the order in which the JVM handed-out the
interfaces.

So in my environment, the llw0 and awdl0 interfaces are listed first,
and are both acceptable as link-local addresses. Then later it sees the
%en0 interface -- which is "global" and stops. If it had continued
through the list, it would have seen the %lo0 interface which would work
much better.

Here are all the interfaces and their flags on this system, in the order
in which Java ends up seeing them:

Name   Addr                                        U L V P M n s l m
utun7  fe80:0:0:0:2609:93a5:c4ac:15ea%utun7        Y N N Y Y Y N N N
utun6  fe80:0:0:0:926b:f264:7ec7:283e%utun6        Y N N Y Y Y N N N
utun5  fe80:0:0:0:4ab0:1734:75f8:c236%utun5        Y N N Y Y Y N N N
utun4  fe80:0:0:0:edc2:bbe0:ab0e:db71%utun4        Y N N Y Y Y N N N
llw0   fe80:0:0:0:6473:baff:fe97:b9d%llw0          Y N N N Y Y N N N
awdl0  fe80:0:0:0:6473:baff:fe97:b9d%awdl0         Y N N N Y Y N N N
utun3  fe80:0:0:0:ce81:b1c:bd2c:69e%utun3          Y N N Y Y Y N N N
utun2  fe80:0:0:0:96b7:f229:b97f:3887%utun2        Y N N Y Y Y N N N
utun1  fe80:0:0:0:c055:a248:e218:ba6b%utun1        Y N N Y Y Y N N N
utun0  fe80:0:0:0:310c:b555:9669:572a%utun0        Y N N Y Y Y N N N
en0    fd00:0:0:0:10ce:a902:a735:c03a%en0          Y N N N Y N N N N
en0    2600:4040:452e:500:18cb:a7f7:f0ac:e9f0%en0  Y N N N Y N N N N
en0    2600:4040:452e:500:142c:94b5:4311:f3f1%en0  Y N N N Y N N N N
en0    fe80:0:0:0:1033:a61d:8b9c:3289%en0          Y N N N Y Y N N N
en0    192.168.50.225                              Y N N N Y N Y N N
lo0    fe80:0:0:0:0:0:0:1%lo0                      Y Y N N Y Y N N N
lo0    0:0:0:0:0:0:0:1%lo0                         Y Y N N Y N N Y N
lo0    127.0.0.1                                   Y Y N N Y N N Y N

The key for the flags is:
U - Up
L - Loop-back (interface)
V - Virtual
P - P-to-P
M - Multi-cast (interface)
n - Link-local
s - Site-Local
l - Loop-back (address)
m - Multi-cast (address)

I think the only usable link-local interface in my environment will be
these:

Name   Addr                                        U L V P M n s l m
en0    fe80:0:0:0:1033:a61d:8b9c:3289%en0          Y N N N Y Y N N N
lo0    fe80:0:0:0:0:0:0:1%lo0                      Y Y N N Y Y N N N

Both of those have the link-local flag set to TRUE, but these do as well:

Name   Addr                                        U L V P M n s l m
utun7  fe80:0:0:0:2609:93a5:c4ac:15ea%utun7        Y N N Y Y Y N N N
utun6  fe80:0:0:0:926b:f264:7ec7:283e%utun6        Y N N Y Y Y N N N
utun5  fe80:0:0:0:4ab0:1734:75f8:c236%utun5        Y N N Y Y Y N N N
utun4  fe80:0:0:0:edc2:bbe0:ab0e:db71%utun4        Y N N Y Y Y N N N
llw0   fe80:0:0:0:6473:baff:fe97:b9d%llw0          Y N N N Y Y N N N
awdl0  fe80:0:0:0:6473:baff:fe97:b9d%awdl0         Y N N N Y Y N N N
utun3  fe80:0:0:0:ce81:b1c:bd2c:69e%utun3          Y N N Y Y Y N N N
utun2  fe80:0:0:0:96b7:f229:b97f:3887%utun2        Y N N Y Y Y N N N
utun1  fe80:0:0:0:c055:a248:e218:ba6b%utun1        Y N N Y Y Y N N N
utun0  fe80:0:0:0:310c:b555:9669:572a%utun0        Y N N Y Y Y N N N

Further removing the point-to-point ones leaves these:

Name   Addr                                        U L V P M n s l m
llw0   fe80:0:0:0:6473:baff:fe97:b9d%llw0          Y N N N Y Y N N N
awdl0  fe80:0:0:0:6473:baff:fe97:b9d%awdl0         Y N N N Y Y N N N

Maybe we want link-local PLUS interface-loopback?

I think that gets us only these:
Name   Addr                                        U L V P M n s l m
lo0    fe80:0:0:0:0:0:0:1%lo0                      Y Y N N Y Y N N N

This patch (relative to the previous release) chooses %lo0 for the
link-local IPv6 address and %en0 for the global IPv6 address. And then
both unit tests pass.

@@ -67,16 +67,22 @@ public class TestStartupIPv6Connectors extends
TomcatBaseTest {
           Enumeration<NetworkInterface> interfaces =
NetworkInterface.getNetworkInterfaces();
           while (interfaces.hasMoreElements()) {
               NetworkInterface interf = interfaces.nextElement();
+            if(interf.isPointToPoint()) {
+                continue;
+            }
               Enumeration<InetAddress> addresses =
interf.getInetAddresses();
               while (addresses.hasMoreElements()) {
                   InetAddress address = addresses.nextElement();
+                System.out.println("Detected interface " + address);
                   if (address instanceof Inet6Address) {
                       Inet6Address inet6Address = (Inet6Address) address;
-                    if (inet6Address.isLinkLocalAddress()) {
+                    if (inet6Address.isLinkLocalAddress() &&
interf.isLoopback()) {
                           linklocalAddress = inet6Address.getHostAddress();
+                        System.out.println("Chose " + address + " as
link-local address");
                       }
                       if (!inet6Address.isAnyLocalAddress() &&
!inet6Address.isLoopbackAddress() && !inet6Address.isLinkLocalAddress()
&& !inet6Address.isMulticastAddress()) {
                           globalAddress = inet6Address.getHostAddress();
+                        System.out.println("Chose global address: " +
address);
                       }
                       if (linklocalAddress != null && globalAddress !=
null) {
                           return;

Obviously the System.outs will be removed.

WDYT?

-chris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Kind of late to the discussion, but the link-local IPv6 to be on the
loopback interface is not the default on Linux and the test gets skipped. I
propose the following patch:

--- a/test/org/apache/catalina/startup/TestStartupIPv6Connectors.java
+++ b/test/org/apache/catalina/startup/TestStartupIPv6Connectors.java
@@ -64,28 +64,62 @@ public class TestStartupIPv6Connectors extends
TomcatBaseTest {

      @BeforeClass
      public static void initializeTestIpv6Addresses() throws Exception {
+        Inet6Address possibleLinkLocalLoopback = null;
+        Inet6Address possibleLinkLocalOnGlobal = null;
+        Inet6Address possibleLinkLocalAny = null;
+
          Enumeration<NetworkInterface> interfaces =
NetworkInterface.getNetworkInterfaces();
          while (interfaces.hasMoreElements()) {
              NetworkInterface interf = interfaces.nextElement();
              Enumeration<InetAddress> addresses = interf.getInetAddresses();
-            if (interf.isPointToPoint()) {
+            if (!interf.isUp() || interf.isVirtual() ||
interf.isPointToPoint()) {
                  continue;
              }
+            boolean globalOnInterface = false;
              while (addresses.hasMoreElements()) {
                  InetAddress address = addresses.nextElement();
                  if (address instanceof Inet6Address inet6Address) {
-                    if (inet6Address.isLinkLocalAddress()) {
-                        linklocalAddress = inet6Address.getHostAddress();
-                    }
                      if (!inet6Address.isAnyLocalAddress() &&
!inet6Address.isLoopbackAddress() && !inet6Address.isLinkLocalAddress() &&
!inet6Address.isMulticastAddress()) {
-                        globalAddress = inet6Address.getHostAddress();
+                        globalOnInterface = true;
+                        if (!interf.isLoopback()) {
+                            globalAddress = inet6Address.getHostAddress();
+                            break;
+                        }
                      }
-                    if (linklocalAddress != null && globalAddress != null)
{
-                        return;
+                }
+            }
+
+            // Second pass to get link-local results with specific order
+            addresses = interf.getInetAddresses();
+            while (addresses.hasMoreElements()) {
+                InetAddress address = addresses.nextElement();
+                if (address instanceof Inet6Address inet6Address) {
+                    if (inet6Address.isLinkLocalAddress()) {
+                        if (interf.isLoopback()) {
+                            // Best option for mac
+                            possibleLinkLocalLoopback = inet6Address;
+                        } else if (globalOnInterface &&
possibleLinkLocalOnGlobal == null) {
+                            // link-local on an interface that also has a
global IPv6 (e.g. en0)
+                            possibleLinkLocalOnGlobal = inet6Address;
+                        } else if (possibleLinkLocalAny == null) {
+                            possibleLinkLocalAny = inet6Address;
+                        }
                      }
                  }
              }
          }
+
+        if (possibleLinkLocalLoopback != null) {
+            linklocalAddress = possibleLinkLocalLoopback.getHostAddress();
+        } else if (possibleLinkLocalOnGlobal != null) {
+            linklocalAddress = possibleLinkLocalOnGlobal.getHostAddress();
+        } else if (possibleLinkLocalAny != null) {
+            linklocalAddress = possibleLinkLocalAny.getHostAddress();
+        }
      }

Looks good to me. Mark, does this look like Windows will have any issues? I suspect you wouldn't have +1'd if it did.

I can also post my interface info class source if anyone is interested in what their system interfaces loo like to Windows.

-chris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to