forked from I2P_Developers/i2p.i2p
* 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:
@ -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[]) {
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
@ -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 ---------------------->
|
||||
<-------------- RelayResponse RelayIntro ----------->
|
||||
<-------------------------------------------- HolePunch (data ignored)
|
||||
SessionRequest -------------------------------------------->
|
||||
<-------------------------------------------- SessionCreated
|
||||
SessionConfirmed ------------------------------------------>
|
||||
<-------------------------------------------- DeliveryStatusMessage
|
||||
<-------------------------------------------- DatabaseStoreMessage
|
||||
DatabaseStoreMessage -------------------------------------->
|
||||
Data <--------------------------------------------------> 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));
|
||||
|
@ -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++;
|
||||
|
@ -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]; }
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
Reference in New Issue
Block a user