Opened 3 years ago

Closed 2 years ago

#1752 closed defect (fixed)

IPv6: Firewalled Status Not Detected

Reported by: Obscuratus Owned by: zzz
Priority: major Milestone: 0.9.27
Component: router/transport Version: 0.9.23
Keywords: Cc:
Parent Tickets:

Description

The I2P Router is not detecting that my IPv6 incoming traffic is firewalled.

While I have outgoing IPv6 connectivity, incoming IPv6 connections are blocked. Yet, the I2P router is setting "Prefer IPv6 over IPv4", and the network is showed simply as "OK".

While the I2P Router seems to be working OK in this configuration, when I manually disable IPv6, my I2P Router seems to get much more stable. I seem to get more participating tunnels, and have less trouble building incoming tunnels.

Subtickets

Change History (18)

comment:1 Changed 3 years ago by echelon

  • Status changed from new to assigned

Confirmed, if IPv4 working fine and IPv6 available, but not working, it shows network state: OK.

comment:2 Changed 3 years ago by zzz

Set the advanced config routerconsole.advanced=true

Then go to /peers
In the NTCP and UDP header, it will have a status, e.g. Status: IPv4: OK; IPv6: Testing.
Let me know what the status is for each transport.

comment:3 Changed 3 years ago by Obscuratus

After 12 hours of uptime, for NTCP, the status is:

Status: IPv4: OK; IPv6: Testing.

For UDP, the status is:

Status: OK.

My published router info contains both IPv4 and IPv6 addresses for NTCP and SSU.

comment:4 Changed 3 years ago by zzz

thank you, will investigate further.

comment:5 follow-up: Changed 3 years ago by Obscuratus

I've been trying to dig into this.

Once I found the proper sections of code, the commenting was really helpful in understanding how things worked. So thanks for the good commenting.

Basically, NTCP defers to UDP to figure out if a connection is firewalled.

UDP basically looks for an IPv6 address. If you have an IPv6 address, it assumes
you're not firewalled.

There are several comments indicating that future work might involve watching for
incoming IPv6 connections. But that isn't currently implemented.

I'm including a short RFC patch I've been testing on my router that will leave
the status as UNKNOWN instead of OK until an incoming IPv6 is made.

Please note: I HAVE NOT TESTED THIS ON A SYSTEM WITH WORKING IPV6 (since I don't
have that capability right now).

With the router IPv6 status as UNKNOWN, it will still publish an IPv6 address for
all the other peers to connect to, as long as the router thinks it has a good IPv6 address.
So, for the rest of your peers, there is little difference between a status of OK
and UNKNOWN.

AFAICT, the main difference between a status of UNKNOWN and OK will be a status message
on the console indicating that the IPv6 is in a state of "Testing". So the user will
have a clue that there is an IPv6 problem.

Otherwise, this RFC patch doesn't really fix the issue, but it gives some additional
insight into the issues.

From 9a500c5887d53350cc5a6949eeb010c54851a38b Mon Sep 17 00:00:00 2001
From: obscuratus <obscuratus@mail.i2p>
Date: Mon, 22 Feb 2016 09:11:47 -0600
Subject: [PATCH] [RFC:i2p/router/transport/udp] IPv6 Reachability

The I2P router is mis-identifing the reachability of an IPv6 connection
when it is firewalled.

The following changes will leave the status as UNKNOWN until an
incoming IPv6 connection is made.

On the first pass, the IPv6 address may be null, causing the assumption
that this rounter has no IPv6 capability.  So modify that test
based on the assumption that status.UNKNOWN is the initial condition
on the first pass.

Signed-off-by: obscuratus <obscuratus@mail.i2p>
---
 .../src/net/i2p/router/transport/udp/UDPTransport.java   | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index f0fa96e..f1e49c0 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -474,16 +474,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                 // isAlive() was false, so setReachabilityStatus() was not called
                 // TODO should we set both to unknown and wait for an inbound v6 conn,
                 // since there's no v6 testing?
-                if (ia.getAddress().length == 16) {
-                    // FIXME we need to check and time out after an hour of no inbound ipv6,
-                    // change to firewalled maybe? but we don't have any test to restore
-                    // a v6 address after it's removed.
-                    _lastInboundIPv6 = _context.clock().now();
-                    setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
-                } else {
-                    if (!isIPv4Firewalled())
-                        setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
-                }
+                if (!isIPv4Firewalled())
+                    setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
                 rebuildExternalAddress(ia.getHostAddress(), newPort, false);
             }
         } else if (newPort > 0 && !bindToAddrs.isEmpty()) {
@@ -3034,7 +3026,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         if (status != Status.UNKNOWN) {
             // now modify if we have no IPv6 address
             if (_currentOurV6Address == null) {
-                if (status == Status.IPV4_OK_IPV6_UNKNOWN)
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Our current IPv6 Address is null");
+                if ((status == Status.IPV4_OK_IPV6_UNKNOWN) && (old != Status.UNKNOWN))
                     status = Status.OK;
                 else if (status == Status.IPV4_FIREWALLED_IPV6_UNKNOWN)
                     status = Status.REJECT_UNSOLICITED;
-- 
2.4.10

comment:6 Changed 3 years ago by Obscuratus

I've been continuing to work on this issue, and wanted to update my status.

I've extended the patch shown above to mark IPv6 as firewalled if no incoming IPv6 connections are received after 1 hour.

I still have a few issues.

(1) The Router Transport Addresses continue to publish an IPv6 address to the rest of my peers even after the reachability for UDP IPv6 has been set to firewalled.

(2) The NCTP transport is not picking up on the change in the UDP IPv6 status as firewalled.

(3) The Router will intermittently think it has successfully made an IPv6 incoming connection every few hours. My IPv6 is solidly firewalled to incoming connections, so I'm not sure why this is happening.

comment:7 Changed 3 years ago by nextloop

Also affected by this issue.

My settings:

My internet connection:

  • IPv4 incoming NAT and no forwarded ports
  • IPv6 block all incomming (as it is advised when deploying IPv6)

I2P settings:

  • IPv4 off, IPv6 on

It says IPv4 is off and IPv6 is OK. I do get only a few (below 5) participating tunnels which are all inactive.

comment:8 Changed 3 years ago by nextloop

IPv6-only still does not give me any participating traffic in 0.9.25.

comment:9 Changed 3 years ago by nextloop

  • Priority changed from minor to major

Standard setting is "prefer IPv6 over IPv4". So a lot of clients try to reach other clients over v6 and fail because they are firewalled because they don't know. So in my opinion this issue is a major one.

comment:10 Changed 3 years ago by zzz

This is a nasty problem.

When we added IPv6 support years ago, we assumed that IPv6 was never firewalled.

More recently, in 0.9.20 May 2015, we split up v4/v6 reachability status internally (ticket #1458). See that ticket for extensive info and links.

For nextloop, who has both v4 and v6 firewalled, you can just force firewalled in the TCP configuration section on /confignet. I think that will do it.

One big issue is that we don't have peer testing for v6. It's prohibited in the SSU spec http://i2p-projekt.i2p/en/docs/transport/ssu - as I also imply in the code comments in comment 5 above. If we can't regularly test v6 reachability, we can't sensibly transition from/to the v6 reachable state. What we're left with is guessing that we are reachable if we get an inbound conn, and guessing that we aren't if we haven't gotten an inbound conn in a while. The problem is that once you declare unreachable, you don't publish your v6 IP, and then you won't get any more (after the RI expires in everybody's netdb).

#1458 is more about DS-Lite. This ticket is purely about v6. Both are difficult.

comment:11 in reply to: ↑ 5 Changed 3 years ago by Obscuratus

Replying to Obscuratus:

Please ignore the patch provided in comment 5.

I've been reviewing it, and I have a major logic flaw.

comment:12 Changed 3 years ago by Obscuratus

The complexity of this problem is daunting, but there may be some value in at least
implementing some methods for measuring our IPv6 connectivity.

As far as I can tell, we have no way of knowing how many successful IPv6 connections
are made.

I've been using this patch locally to count the incoming IPv4/IPv6 connections.

From: obscuratus <obscuratus@mail.i2p>
Date: Wed, 4 May 2016 13:49:55 +0000 (-0500)
Subject: [i2p/router] Count Total NTCP/UDP IPv4 and IPv6 Incomming Connections.
X-Git-Url: http://127.0.0.1/gitweb/?p=i2p.i2p%2F.git;a=commitdiff_plain;h=2e19a7c2720f194acb4289e0c31e19c0476f65db

[i2p/router] Count Total NTCP/UDP IPv4 and IPv6 Incomming Connections.

Implement counters to track incoming IPv4 and IPv6 connections
on NTCP and UDP.  Counters can be examined by setting logging to
debug for the NTCP and UDP transports.

Signed-off-by: obscuratus <obscuratus@mail.i2p>
---

Note: Regarding the change in the last block to the line:
       _lastInboundReceivedOn = System.currentTimeMillis(); 

This was because of trailing white-space, the code itself didn't change.

diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index d371a93..2f4b4e0 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -86,6 +86,8 @@ public class NTCPTransport extends TransportImpl {
     private boolean _haveIPv6Address;
     private long _lastInboundIPv4;
     private long _lastInboundIPv6;
+    private long _totalInboundNTCPIPv4conn = 0;
+    private long _totalInboundNTCPIPv6conn = 0;
 
     public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
     public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
@@ -213,10 +215,16 @@ public class NTCPTransport extends TransportImpl {
         synchronized (_conLock) {
             old = _conByIdent.put(peer, con);
         }
-        if (con.isIPv6())
+        if (con.isIPv6()) {
             _lastInboundIPv6 = con.getCreated();
-        else
+            _totalInboundNTCPIPv6conn++;
+        } else {
             _lastInboundIPv4 = con.getCreated();
+            _totalInboundNTCPIPv4conn++;
+        }
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("NTCP Incoming: IPv4: " + _totalInboundNTCPIPv4conn +
+                       "; IPv6: " + _totalInboundNTCPIPv6conn);
         return old;
     }
 
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index f627a5b..0e91b4b 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -95,7 +95,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      */
     private boolean _haveIPv6Address;
     private long _lastInboundIPv6;
-    
+    private long _totalInboundUDPIPv4conn = 0;
+    private long _totalInboundUDPIPv6conn = 0;
+
     /** do we need to rebuild our external router address asap? */
     private boolean _needsRebuild;
     private final Object _rebuildLock = new Object();
@@ -709,14 +711,19 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             // change to firewalled maybe? but we don't have any test to restore
             // a v6 address after it's removed.
             _lastInboundIPv6 = _context.clock().now();
+            _totalInboundUDPIPv6conn++;
             if (_currentOurV6Address != null)
                 setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
         } else {
             // Introduced connections are still inbound, this is not evidence
             // that we are not firewalled.
             // use OS clock since its an ordering thing, not a time thing
-            _lastInboundReceivedOn = System.currentTimeMillis(); 
+            _lastInboundReceivedOn = System.currentTimeMillis();
+            _totalInboundUDPIPv4conn++;
         }
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("UDP Incoming: IPv4: " + _totalInboundUDPIPv4conn +
+                       "; IPv6: " + _totalInboundUDPIPv6conn);
     }
     
     // temp prevent multiples

comment:13 Changed 3 years ago by Obscuratus

I've been testing a patch to check for incoming SSU IPv6 connections, and update the reachability accordingly.

A one-time check is made after one hour of uptime, and if no incoming IPv6 connections have been seen, the reachability is marked as FIREWALLED.

This patch depends on the patch included in comment 12 (Count Total NTCP/UDP IPv4 and IPv6 Incomming Connections).

While the patch appears to work as intended, there are two serious flaws that prevent this patch from being a solution as-is.

Flaw #1: I2P can occasionally mis-identify an incoming connection as IPv6, and therefor assume the IPv6 reachability is OK.

This seems to occur when the IPv6 is firewalled for incoming connections, but allows outgoing IPv6 connections (a common default configuration). My hypothesis is that When I2P has recently made a successful outgoing IPv6 connection to a peer, it can ignore the connection type of an incoming IPv4 connection from the same peer, and just assumes the connection is the same as the recent outgoing connection.

When IPv6 is firewalled in both incoming and outgoing directions, I don't see any false IPv6 incoming connections detected.

Flaw #2: Even though this patch can successfully update the IPv6 reachability status as FIREWALLED, there are no provisions to do anything with the updated status. The IPv6 transports remain active, and our peers are still told that we have valid IPv6 reachability.

However, at least the user will be flagged with a message on their console that IPv6 is firewalled.

From d4cd4ec93024903ecc39d148e53ef918fc7d75bb Mon Sep 17 00:00:00 2001
From: obscuratus <obscuratus@mail.i2p>
Date: Fri, 10 Jun 2016 12:17:53 -0500
Subject: [PATCH] [i2p/router] Check IPv6 Reachability Status for Firewalled

Check the IPv6 status after one hour of uptime, and update
the reachability status if no incoming UDP IPv6 connections
have been made.

TO DO:
We still don't do anything with this information.  As far as our
peers are concerned, our IPv6 address is still being broadcast.

If an incoming IPv6 connection is subsequently made, the status
will be marked as OK (because we haven't done anything to disable
our IPv6 address, so we are still trying to accept incoming
connections).

If we did remove the IPv6 address from our transports, we would
not have any method to restore them until the user restarts their
router.

Signed-off-by: obscuratus <obscuratus@mail.i2p>
---
 .../net/i2p/router/transport/udp/UDPTransport.java | 29 ++++++++++++++++++----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index b02b61b..d43f4a7 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -96,9 +96,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
      *  TODO periodically update via CSFI.NetMonitor?
      */
     private boolean _haveIPv6Address;
-    private long _lastInboundIPv6;
+    private long _lastInboundIPv6 = 0;
     private long _totalInboundUDPIPv4conn = 0;
     private long _totalInboundUDPIPv6conn = 0;
+    private boolean _IPv6ReachabilityChecked = false;
 
     /** do we need to rebuild our external router address asap? */
     private boolean _needsRebuild;
@@ -765,13 +766,19 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
     
     void inboundConnectionReceived(boolean isIPv6) {
         if (isIPv6) {
-            // FIXME we need to check and time out after an hour of no inbound ipv6,
-            // change to firewalled maybe? but we don't have any test to restore
-            // a v6 address after it's removed.
+            // FIXME although we check after an hour for no inbound ipv6,
+            // we have no provisions to do anything with that information.
+            // If we think IPv6 is firewalled, we still leave that transport
+            // as if it were good.
+            // If we did remove that IPv6 address, we would have no
+            // provisions to restore it.
             _lastInboundIPv6 = _context.clock().now();
             _totalInboundUDPIPv6conn++;
-            if (_currentOurV6Address != null)
+            if (_currentOurV6Address != null) {
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("IPv6 Incoming Connection Established.");
                 setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK);
+            }
         } else {
             // Introduced connections are still inbound, this is not evidence
             // that we are not firewalled.
@@ -779,6 +786,18 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             _lastInboundReceivedOn = System.currentTimeMillis();
             _totalInboundUDPIPv4conn++;
         }
+        // If the router has been up for one hour, confirm IPv6 Reachibility.
+        if (!_IPv6ReachabilityChecked && (_context.router().getUptime() > 60*60*1000)) {
+            TransportUtil.IPv6Config cfg = getIPv6Config();
+            if (cfg != IPV6_DISABLED) {
+                if (_totalInboundUDPIPv6conn == 0) {
+                    if (_log.shouldLog(Log.WARN))
+                        _log.warn("No IPv6 Incoming connections for 1 hour, IPv6 set to Firewalled");
+                    setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_FIREWALLED);
+                }
+            }
+            _IPv6ReachabilityChecked = true;
+        }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("UDP Incoming: IPv4: " + _totalInboundUDPIPv4conn +
                        "; IPv6: " + _totalInboundUDPIPv6conn);
-- 
2.7.3

}}}

}}}

comment:14 Changed 3 years ago by Obscuratus

I think I've run into a critical flaw with the whole approach of ascertaining reachability by waiting for an incoming connection.

For many firewalls, the default configuration is to allow outgoing connections and deny incoming connections. Except, in the case of UDP, there are no 'connections'. UDP is connectionless. Pretty much all the firewall can do is allow everything incoming from a given remote UDP host/port once you've sent an outgoing packet to that remote UDP host/port.

A ran into an old article on H-online that explains UDP hole punching pretty well:

http://www.h-online.com/security/features/How-Skype-Co-get-round-firewalls-747197.html

Even though you probably won't be NAT-ing on IPv6, the firewall principles are similar.

So, even if your firewall is configured to block incoming IPv6 UDP traffic (but allow outgoing UDP traffic), eventually, you'll punch an outgoing hole to one of your peers, and later on they'll be allowed to send you incoming UDP traffic, which will be interpreted by I2P as an incoming IPv6 connection.

This will make it nearly impossible for I2P to passively determine your IPv6 reachability by simply waiting for an incoming UDP connection.

comment:15 Changed 3 years ago by zzz

  • Milestone changed from undecided to 0.9.27

ref: http://i2p-projekt.i2p/spec/proposals/126-ipv6-peer-testing
ref: http://zzz.i2p/topics/2119

In d2c08f65fa2ae453103b1f4a6cd9a28dac58295b 0.9.26-3
Can't fully test until we get several IPv6 routers running this code, may not be possible until the release.

comment:16 Changed 3 years ago by Obscuratus

I've been testing and debugging the IPv6 peer testing code in the 0.9.26 dev builds as much as I can.

Full testing will probably require the 0.9.27 release since there are so few peers running dev builds.

But I think I've run into a problem in:
router/java/src/net/i2p/router/transport/udp/UDPTransport.java
~line 3215

PeerState pickTestPeer(PeerTestState.Role peerRole, boolean isIPv6, RemoteHostId dontInclude)

As the code iterates through the table of active peers, it rejects BOB peers who are IPv4:

            // enforce IPv4/v6 connection if we are ALICE looking for a BOB
            byte[] ip = peer.getRemoteIP();
            if (peerRole == BOB) {
                if ((!isIPv6 && ip.length != 4) ||
                    (isIPv6 && ip.length != 16))
                continue;
            }

The problem is that most peers have multiple addresses (usually NTCP and UDP for IPv4 and IPv6).

This method of rejecting BOB peers will only look at the first address, which I'm finding is nearly always the IPv4 address.

About 10 lines below this snippet of code is an example of how to iterate through the peer's address list:

            ip = null;
            List<RouterAddress> addrs = getTargetAddresses(peerInfo);
            for (RouterAddress addr : addrs) {
                ip = addr.getIP();
                if (ip != null) {
                    if ((!isIPv6 && ip.length != 4) ||
                        (isIPv6 && ip.length != 16))
                    break;
                }
            }

If you don't beat me to it, I'll play around with a patch to address this issue.

EDITED WITH FOLLOW-UP FROM DISCUSSION ON IRC

After discussing this comment with zzz on IRC, it seems like the code is correct.

The selection of a Bob is not contingent on if the peer is IPv6 capable, it needs
to be contingent on how the peer is currently connected.

The above test is for how the peer is connected, and doesn't need to care if they are IPv6 capable.

Last edited 3 years ago by Obscuratus (previous) (diff)

comment:17 Changed 3 years ago by zzz

After discussion on IRC, I've bumped up the minimum desired SSU connections if we have a v6 address, in an attempt to ensure we have v6-capable connected peers for testing.

Also, in the 2nd code block above, there's a bug, since it's setting ip to non-null even if the length check fails.

Fixed in 57f8904e34e39111f13c427cb9fc92eeeb71afcd 0.9.26-7

thanks for the testing

comment:18 Changed 2 years ago by zzz

  • Resolution set to fixed
  • Status changed from assigned to closed

Optimistically closing, however we are unable to fully test until this gets out to everybody in the .27 release. Any additional fixes will be in 0.9.28.

Note: See TracTickets for help on using tickets.