From af27c76b2cb8684f721867ce803bec600fefed5c Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 7 May 2013 22:46:55 +0000 Subject: [PATCH] * Only treat IPv6 addresses as valid if we have a public IPv6 address * SSU Introduction: - Document that Alice-Bob RelayRequest/RelayResponse may be IPv4 or IPv6, but don't implement IPv6 yet. Changes required in IntroductionManager and PacketBuilder to send Alice's IPv4 address in the RelayRequest packet over IPv6, and to publish IPv6 introducer IPs. - Bob-Charlie RelayIntro must be IPv4 - Only offer/accept relay tags as Bob or Charlie if the Bob-Charlie session is IPv4 - Alice-Charlie communication must be IPv4 - javadocs --- .../i2p/router/transport/TransportImpl.java | 2 + .../router/transport/TransportManager.java | 2 +- .../transport/udp/EstablishmentManager.java | 12 +++- .../transport/udp/IntroductionManager.java | 58 +++++++++++++++++++ .../router/transport/udp/PacketBuilder.java | 10 ++++ .../i2p/router/transport/udp/UDPAddress.java | 5 ++ .../router/transport/udp/UDPPacketReader.java | 3 + .../router/transport/udp/UDPTransport.java | 19 +++++- 8 files changed, 106 insertions(+), 5 deletions(-) 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"); }