diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 44cf01ef0..310526281 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -684,6 +684,8 @@ public abstract class TransportImpl implements Transport { } /** + * Allows IPv6 only if the transport is configured for it. + * Caller must check if we actually have a public IPv6 address. * @param addr non-null */ protected boolean isPubliclyRoutable(byte addr[]) { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 6d9ea36de..e0fdcc0cf 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -103,7 +103,7 @@ public class TransportManager implements TransportEventListener { } /** - * Notify transport of ALL routable local addresses, including IPv6. + * Notify transport of ALL routable interface addresses, including IPv6. * It's the transport's job to ignore what it can't handle. */ private void initializeAddress(Transport t) { diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index ab0699c7f..1b87e24fe 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -459,8 +459,10 @@ class EstablishmentManager { if (isNew) { // Don't offer to relay to privileged ports. + // Only offer for an IPv4 session. // TODO if already we have their RI, only offer if they need it (no 'C' cap) - if (_transport.canIntroduce() && state.getSentPort() >= 1024) { + if (_transport.canIntroduce() && state.getSentPort() >= 1024 && + state.getSentIP().length == 4) { // ensure > 0 long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); state.setSentRelayTag(tag); @@ -884,6 +886,9 @@ class EstablishmentManager { state.introSent(); } + /** + * We are Alice, we sent a RelayRequest to Bob and got a response back. + */ void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) { long nonce = reader.getRelayResponseReader().readNonce(); OutboundEstablishState state = _liveIntroductions.remove(Long.valueOf(nonce)); @@ -893,6 +898,7 @@ class EstablishmentManager { return; // already established } + // Note that we ignore the Alice (us) IP/Port in the RelayResponse int sz = reader.getRelayResponseReader().readCharlieIPSize(); byte ip[] = new byte[sz]; reader.getRelayResponseReader().readCharlieIP(ip, 0); @@ -940,13 +946,15 @@ class EstablishmentManager { } /** - * Are IP and port valid? This is only for relay response. + * Are IP and port valid? This is only for checking the relay response. + * Reject all IPv6, for now, even if we are configured for it. * Refuse anybody in the same /16 * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { return port >= 1024 && port <= 65535 && + ip != null && ip.length == 4 && _transport.isValid(ip) && (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && (!_context.blocklist().isBlocklisted(ip)); diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java index 0d49d2b5b..790a0d349 100644 --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java @@ -23,6 +23,52 @@ import net.i2p.util.Log; /** * Keep track of inbound and outbound introductions. + * + * IPv6 info: Alice-Bob communication may be via IPv4 or IPv6. + * Bob-Charlie communication must be via established IPv4 session as that's the only way + * that Bob knows Charlie's IPv4 address to give it to Alice. + * Alice-Charlie communication is via IPv4. + * If Alice-Bob is over IPv6, Alice must include her IPv4 address in + * the RelayRequest message. + * + * From udp.html on the website: + +

Indirect session establishment by means of a third party introduction +is necessary for efficient NAT traversal. Charlie, a router behind a +NAT or firewall which does not allow unsolicited inbound UDP packets, +first contacts a few peers, choosing some to serve as introducers. Each +of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction +tag - a 4 byte random number - which he then makes available to the public +as methods of contacting him. Alice, a router who has Charlie's published +contact methods, first sends a RelayRequest packet to one or more of the +introducers, asking each to introduce her to Charlie (offering the +introduction tag to identify Charlie). Bob then forwards a RelayIntro +packet to Charlie including Alice's public IP and port number, then sends +Alice back a RelayResponse packet containing Charlie's public IP and port +number. When Charlie receives the RelayIntro packet, he sends off a small +random packet to Alice's IP and port (poking a hole in his NAT/firewall), +and when Alice receives Bob's RelayResponse packet, she begins a new +full direction session establishment with the specified IP and port.

+

+Alice first connects to introducer Bob, who relays the request to Charlie. +

+
+        Alice                         Bob                  Charlie
+    RelayRequest ---------------------->
+         <-------------- RelayResponse    RelayIntro ----------->
+         <-------------------------------------------- HolePunch (data ignored)
+    SessionRequest -------------------------------------------->
+         <-------------------------------------------- SessionCreated
+    SessionConfirmed ------------------------------------------>
+         <-------------------------------------------- DeliveryStatusMessage
+         <-------------------------------------------- DatabaseStoreMessage
+    DatabaseStoreMessage -------------------------------------->
+    Data <--------------------------------------------------> Data
+
+ +

+After the hole punch, the session is established between Alice and Charlie as in a direct establishment. +

*/ class IntroductionManager { private final RouterContext _context; @@ -77,6 +123,9 @@ class IntroductionManager { // let's not use an introducer on a privileged port, sounds like trouble if (peer.getRemotePort() < 1024) return; + // Only allow relay as Bob or Charlie if the Bob-Charlie session is IPv4 + if (peer.getRemoteIP().length != 4) + return; if (_log.shouldLog(Log.DEBUG)) _log.debug("Adding peer " + peer.getRemoteHostId() + ", weRelayToThemAs " + peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs()); @@ -136,6 +185,8 @@ class IntroductionManager { _log.info("Picked peer has no local routerInfo: " + cur); continue; } + // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false) + // but requires RelayRequest support, see below RouterAddress ra = _transport.getTargetAddress(ri); if (ra == null) { if (_log.shouldLog(Log.INFO)) @@ -159,6 +210,8 @@ class IntroductionManager { _log.info("Peer is idle too long: " + cur); continue; } + // FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false) + // but requires RelayRequest support, see below byte[] ip = cur.getRemoteIP(); int port = cur.getRemotePort(); if (!isValid(ip, port)) @@ -331,6 +384,9 @@ class IntroductionManager { // ip/port inside message should be 0:0, as it's unimplemented on send - // see PacketBuilder.buildRelayRequest() + // and we don't read it here. + // FIXME implement for getting Alice's IPv4 in RelayRequest sent over IPv6? + // or is that just too easy to spoof? if (!isValid(alice.getIP(), alice.getPort()) || ipSize != 0 || port != 0) { if (_log.shouldLog(Log.WARN)) { byte ip[] = new byte[ipSize]; @@ -368,12 +424,14 @@ class IntroductionManager { /** * Are IP and port valid? + * Reject all IPv6, for now, even if we are configured for it. * Refuse anybody in the same /16 * @since 0.9.3 */ private boolean isValid(byte[] ip, int port) { return port >= 1024 && port <= 65535 && + ip != null && ip.length == 4 && _transport.isValid(ip) && (!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) && (!_context.blocklist().isBlocklisted(ip)); diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 2e5f356ca..c1d977a35 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -1058,6 +1058,7 @@ class PacketBuilder { // specify these if we know what our external receive ip/port is and if its different // from what bob is going to think + // FIXME IPv4 addr must be specified when sent over IPv6 private byte[] getOurExplicitIP() { return null; } private int getOurExplicitPort() { return 0; } @@ -1077,6 +1078,8 @@ class PacketBuilder { // let's not use an introducer on a privileged port, sounds like trouble if (ikey == null || iport < 1024 || iport > 65535 || iaddr == null || tag <= 0 || + // must be IPv4 for now as we don't send Alice IP/port, see below + iaddr.getAddress().length != 4 || (!_transport.isValid(iaddr.getAddress())) || Arrays.equals(iaddr.getAddress(), _transport.getExternalIP())) { if (_log.shouldLog(_log.WARN)) @@ -1090,11 +1093,17 @@ class PacketBuilder { return rv; } + /** + * TODO Alice IP/port in packet will always be null/0, must be fixed to + * send a RelayRequest over IPv6 + * + */ public UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[], long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) { UDPPacket packet = buildPacketHeader(PEER_RELAY_REQUEST_FLAG_BYTE); byte data[] = packet.getPacket().getData(); int off = HEADER_SIZE; + // FIXME must specify these if request is going over IPv6 byte ourIP[] = getOurExplicitIP(); int ourPort = getOurExplicitPort(); @@ -1218,6 +1227,7 @@ class PacketBuilder { DataHelper.toLong(data, off, 2, charlie.getRemotePort()); off += 2; + // Alice IP/Port currently ignored on receive - see UDPPacketReader byte aliceIP[] = alice.getIP(); DataHelper.toLong(data, off, 1, aliceIP.length); off++; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index 5381bfaea..d139970e0 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -131,6 +131,7 @@ public class UDPAddress { } public String getHost() { return _host; } + InetAddress getHostAddress() { if (_hostAddress == null) { try { @@ -150,6 +151,7 @@ public class UDPAddress { byte[] getIntroKey() { return _introKey; } int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); } + InetAddress getIntroducerHost(int i) { if (_introAddresses[i] == null) { try { @@ -160,8 +162,11 @@ public class UDPAddress { } return _introAddresses[i]; } + int getIntroducerPort(int i) { return _introPorts[i]; } + byte[] getIntroducerKey(int i) { return _introKeys[i]; } + long getIntroducerTag(int i) { return _introTags[i]; } /** diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java index e8c138a1c..6a82fcebd 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java @@ -730,6 +730,7 @@ class UDPPacketReader { return (int)DataHelper.fromLong(_message, offset, 2); } + /** @deprecated unused */ public int readAliceIPSize() { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); @@ -737,6 +738,7 @@ class UDPPacketReader { offset += 2; return (int)DataHelper.fromLong(_message, offset, 1); } + /** @deprecated unused */ public void readAliceIP(byte target[], int targetOffset) { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); @@ -746,6 +748,7 @@ class UDPPacketReader { offset++; System.arraycopy(_message, offset, target, targetOffset, sz); } + /** @deprecated unused */ public int readAlicePort() { int offset = readBodyOffset(); offset += DataHelper.fromLong(_message, offset, 1); 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 4d64e1adf..48623f7eb 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -81,6 +81,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private long _lastInboundReceivedOn; private final DHSessionKeyBuilder.Factory _dhFactory; private int _mtu; + /** + * Do we have a public IPv6 address? + * TODO periodically update via CSFI.NetMonitor? + */ + private boolean _haveIPv6Address; /** do we need to rebuild our external router address asap? */ private boolean _needsRebuild; @@ -553,6 +558,10 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) { if (_log.shouldLog(Log.WARN)) _log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source); + if (source == SOURCE_INTERFACE && ip.length == 16) { + // must be set before isValid() call + _haveIPv6Address = true; + } if (explicitAddressSpecified()) return; String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES); @@ -790,10 +799,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return (rport == lport) && DataHelper.eq(laddr, raddr); } - /** @param addr may be null */ + /** + * An IPv6 address is only valid if we are configured to support IPv6 + * AND we have a public IPv6 address. + * + * @param addr may be null, returns false + */ public final boolean isValid(byte addr[]) { if (addr == null) return false; - if (isPubliclyRoutable(addr)) + if (isPubliclyRoutable(addr) && + (addr.length != 16 || _haveIPv6Address)) return true; return _context.getBooleanProperty("i2np.udp.allowLocal"); }