* 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
This commit is contained in:
zzz
2013-05-07 22:46:55 +00:00
parent 60336c9555
commit af27c76b2c
8 changed files with 106 additions and 5 deletions

View File

@ -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[]) {

View File

@ -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) {

View File

@ -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));

View File

@ -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:
<p>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.</p>
<p>
Alice first connects to introducer Bob, who relays the request to Charlie.
</p>
<pre>
Alice Bob Charlie
RelayRequest ----------------------&gt;
&lt;-------------- RelayResponse RelayIntro -----------&gt;
&lt;-------------------------------------------- HolePunch (data ignored)
SessionRequest --------------------------------------------&gt;
&lt;-------------------------------------------- SessionCreated
SessionConfirmed ------------------------------------------&gt;
&lt;-------------------------------------------- DeliveryStatusMessage
&lt;-------------------------------------------- DatabaseStoreMessage
DatabaseStoreMessage --------------------------------------&gt;
Data &lt;--------------------------------------------------&gt; Data
</pre>
<p>
After the hole punch, the session is established between Alice and Charlie as in a direct establishment.
</p>
*/
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));

View File

@ -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++;

View File

@ -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]; }
/**

View File

@ -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);

View File

@ -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");
}