From 0c08a05bce97b32a2a05275651c01fd7f531e281 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Sun, 27 Feb 2022 12:03:28 -0500 Subject: [PATCH] SSU2: Hook in new classes to EstablishmentManager Implement handshake retransmissions Fix up calls to IES2/OES2 split() TODO not hooked in to PacketHandler yet WIP, untested --- .../transport/udp/EstablishmentManager.java | 353 +++++++++++++++--- .../transport/udp/InboundEstablishState2.java | 66 +++- .../udp/OutboundEstablishState2.java | 142 +++++-- .../router/transport/udp/PacketBuilder2.java | 14 +- .../i2p/router/transport/udp/PeerState.java | 6 +- .../i2p/router/transport/udp/PeerState2.java | 32 ++ .../router/transport/udp/UDPTransport.java | 2 +- 7 files changed, 532 insertions(+), 83 deletions(-) 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 44956a05fe..8bea1842e7 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -2,6 +2,7 @@ package net.i2p.router.transport.udp; import java.net.InetAddress; import java.net.UnknownHostException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -402,10 +403,20 @@ class EstablishmentManager { // don't ask if they are indirect boolean requestIntroduction = allowExtendedOptions && !isIndirect && _transport.introducersMaybeRequired(TransportUtil.isIPv6(ra)); - state = new OutboundEstablishState(_context, maybeTo, to, + int version = _transport.getSSUVersion(ra); + if (version == 1) { + state = new OutboundEstablishState(_context, maybeTo, to, toIdentity, allowExtendedOptions, requestIntroduction, sessionKey, addr, _transport.getDHFactory()); + } else if (version == 2) { + state = new OutboundEstablishState2(_context, _transport, maybeTo, to, + toIdentity, requestIntroduction, sessionKey, ra, addr); + } else { + // shouldn't happen + _transport.failed(msg, "OB to bad addr? " + ra); + return; + } OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state); boolean isNew = oldState == null; if (isNew) { @@ -471,6 +482,7 @@ class EstablishmentManager { /** * Got a SessionRequest (initiates an inbound establishment) * + * SSU 1 only. */ void receiveSessionRequest(RemoteHostId from, UDPPacketReader reader) { if (!TransportUtil.isValidPort(from.getPort()) || !_transport.isValid(from.getIP())) { @@ -544,9 +556,92 @@ class EstablishmentManager { notifyActivity(); } + /** + * Got a SessionRequest OR a TokenRequest (initiates an inbound establishment) + * + * SSU 2 only. + * @since 0.9.54 + */ + void receiveSessionRequest(RemoteHostId from, UDPPacket packet) { + if (!TransportUtil.isValidPort(from.getPort()) || !_transport.isValid(from.getIP())) { + if (_log.shouldWarn()) + _log.warn("Receive session request from invalid: " + from); + return; + } + boolean isNew = false; + InboundEstablishState state = _inboundStates.get(from); + if (state == null) { + // TODO this is insufficient to prevent DoSing, especially if + // IP spoofing is used. For further study. + if (!shouldAllowInboundEstablishment()) { + if (_log.shouldWarn()) + _log.warn("Dropping inbound establish, increase " + PROP_MAX_CONCURRENT_ESTABLISH); + _context.statManager().addRateData("udp.establishDropped", 1); + return; // drop the packet + } + if (_context.blocklist().isBlocklisted(from.getIP())) { + if (_log.shouldWarn()) + _log.warn("Receive session request from blocklisted IP: " + from); + _context.statManager().addRateData("udp.establishBadIP", 1); + return; // drop the packet + } + if (!_transport.allowConnection()) + return; // drop the packet + try { + state = new InboundEstablishState2(_context, _transport, packet); + } catch (GeneralSecurityException gse) { + if (_log.shouldWarn()) + _log.warn("Corrupt Session/Token Request from: " + from, gse); + _context.statManager().addRateData("udp.establishDropped", 1); + return; + } + + /**** TODO + if (_replayFilter.add(state.getReceivedX(), 0, 8)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Duplicate X in session request from: " + from); + _context.statManager().addRateData("udp.dupDHX", 1); + return; // drop the packet + } + ****/ + + InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state); + isNew = oldState == null; + if (!isNew) + // whoops, somebody beat us to it, throw out the state we just created + state = oldState; + } + + if (isNew) { + /**** TODO + // 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 extended options, only if they asked for it + if (state.isIntroductionRequested() && + state.getSentPort() >= 1024 && + _transport.canIntroduce(state.getSentIP().length == 16)) { + // ensure > 0 + long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE); + state.setSentRelayTag(tag); + } else { + // we got an IB even though we were firewalled, hidden, not high cap, etc. + } + ****/ + if (_log.shouldInfo()) + _log.info("Received NEW session request " + state); + } else { + if (_log.shouldDebug()) + _log.debug("Receive DUP session request from: " + state); + } + notifyActivity(); + } + /** * got a SessionConfirmed (should only happen as part of an inbound * establishment) + * + * SSU 1 only. */ void receiveSessionConfirmed(RemoteHostId from, UDPPacketReader reader) { InboundEstablishState state = _inboundStates.get(from); @@ -561,9 +656,42 @@ class EstablishmentManager { } } + /** + * got a SessionConfirmed (should only happen as part of an inbound + * establishment) + * + * SSU 2 only. + * @since 0.9.54 + */ + void receiveSessionConfirmed(RemoteHostId from, UDPPacket packet) { + InboundEstablishState state = _inboundStates.get(from); + if (state != null) { + if (state.getVersion() != 2) + return; + InboundEstablishState2 state2 = (InboundEstablishState2) state; + try { + state2.receiveSessionConfirmed(packet); + } catch (GeneralSecurityException gse) { + if (_log.shouldWarn()) + _log.warn("Corrupt Session Confirmed from: " + from, gse); + state.fail(); + return; + } + // we are done, go right to ps2 + handleCompletelyEstablished(state2); + notifyActivity(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive session confirmed from: " + state); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Receive (DUP?) session confirmed from: " + from); + } + } + /** * Got a SessionCreated (in response to our outbound SessionRequest) * + * SSU 1 only. */ void receiveSessionCreated(RemoteHostId from, UDPPacketReader reader) { OutboundEstablishState state = _outboundStates.get(from); @@ -577,6 +705,64 @@ class EstablishmentManager { _log.warn("Receive (DUP?) session created from: " + from); } } + + /** + * Got a SessionCreated (in response to our outbound SessionRequest) + * + * SSU 2 only. + * @since 0.9.54 + */ + void receiveSessionCreated(RemoteHostId from, UDPPacket packet) { + OutboundEstablishState state = _outboundStates.get(from); + if (state != null) { + if (state.getVersion() != 2) + return; + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + try { + state2.receiveSessionCreated(packet); + } catch (GeneralSecurityException gse) { + if (_log.shouldWarn()) + _log.warn("Corrupt Session Created from: " + from, gse); + state.fail(); + return; + } + notifyActivity(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive session created from: " + state); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Receive (DUP?) session created from: " + from); + } + } + + /** + * Got a Retry (in response to our outbound SessionRequest or TokenRequest) + * + * SSU 2 only. + * @since 0.9.54 + */ + void receiveRetry(RemoteHostId from, UDPPacket packet) { + OutboundEstablishState state = _outboundStates.get(from); + if (state != null) { + if (state.getVersion() != 2) + return; + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + try { + state2.receiveSessionCreated(packet); + } catch (GeneralSecurityException gse) { + if (_log.shouldWarn()) + _log.warn("Corrupt Retry from: " + from, gse); + state.fail(); + return; + } + notifyActivity(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Receive retry from: " + state); + } else { + if (_log.shouldLog(Log.WARN)) + _log.warn("Receive (DUP?) retry from: " + from); + } + } /** * Got a SessionDestroy on an established conn @@ -719,25 +905,35 @@ class EstablishmentManager { if (state.isComplete()) return; RouterIdentity remote = state.getConfirmedIdentity(); - PeerState peer = new PeerState(_context, _transport, - state.getSentIP(), state.getSentPort(), remote.calculateHash(), true, state.getRTT()); - peer.setCurrentCipherKey(state.getCipherKey()); - peer.setCurrentMACKey(state.getMACKey()); - peer.setWeRelayToThemAs(state.getSentRelayTag()); - // Lookup the peer's MTU from the netdb, since it isn't included in the protocol setup (yet) - // TODO if we don't have RI then we will get it shortly, but too late. - // Perhaps netdb should notify transport when it gets a new RI... - RouterInfo info = _context.netDb().lookupRouterInfoLocally(remote.calculateHash()); - if (info != null) { - RouterAddress addr = _transport.getTargetAddress(info); - if (addr != null) { - String smtu = addr.getOption(UDPAddress.PROP_MTU); - if (smtu != null) { - try { - boolean isIPv6 = state.getSentIP().length == 16; - int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu)); - peer.setHisMTU(mtu); - } catch (NumberFormatException nfe) {} + PeerState peer; + int version = state.getVersion(); + if (version == 1) { + peer = new PeerState(_context, _transport, + state.getSentIP(), state.getSentPort(), remote.calculateHash(), true, state.getRTT()); + peer.setCurrentCipherKey(state.getCipherKey()); + peer.setCurrentMACKey(state.getMACKey()); + peer.setWeRelayToThemAs(state.getSentRelayTag()); + } else { + InboundEstablishState2 state2 = (InboundEstablishState2) state; + peer = state2.getPeerState(); + } + + if (version == 1) { + // Lookup the peer's MTU from the netdb, since it isn't included in the protocol setup (yet) + // TODO if we don't have RI then we will get it shortly, but too late. + // Perhaps netdb should notify transport when it gets a new RI... + RouterInfo info = _context.netDb().lookupRouterInfoLocally(remote.calculateHash()); + if (info != null) { + RouterAddress addr = _transport.getTargetAddress(info); + if (addr != null) { + String smtu = addr.getOption(UDPAddress.PROP_MTU); + if (smtu != null) { + try { + boolean isIPv6 = state.getSentIP().length == 16; + int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu)); + peer.setHisMTU(mtu); + } catch (NumberFormatException nfe) {} + } } } } @@ -838,11 +1034,18 @@ class EstablishmentManager { if (claimed != null) _outboundByClaimedAddress.remove(claimed, state); _outboundByHash.remove(remote.calculateHash(), state); - PeerState peer = new PeerState(_context, _transport, - state.getSentIP(), state.getSentPort(), remote.calculateHash(), false, state.getRTT()); - peer.setCurrentCipherKey(state.getCipherKey()); - peer.setCurrentMACKey(state.getMACKey()); - peer.setTheyRelayToUsAs(state.getReceivedRelayTag()); + int version = state.getVersion(); + PeerState peer; + if (version == 1) { + peer = new PeerState(_context, _transport, + state.getSentIP(), state.getSentPort(), remote.calculateHash(), false, state.getRTT()); + peer.setCurrentCipherKey(state.getCipherKey()); + peer.setCurrentMACKey(state.getMACKey()); + peer.setTheyRelayToUsAs(state.getReceivedRelayTag()); + } else { + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + peer = state2.getPeerState(); + } int mtu = state.getRemoteAddress().getMTU(); if (mtu > 0) peer.setHisMTU(mtu); @@ -859,10 +1062,10 @@ class EstablishmentManager { _context.statManager().addRateData("udp.outboundEstablishTime", state.getLifetime()); DatabaseStoreMessage dbsm = null; - if (!state.isFirstMessageOurDSM()) { - dbsm = getOurInfo(); - } else if (_log.shouldLog(Log.INFO)) { - _log.info("Skipping publish: " + state); + if (version == 1) { + if (!state.isFirstMessageOurDSM()) { + dbsm = getOurInfo(); + } } List<OutNetMessage> msgs = new ArrayList<OutNetMessage>(8); @@ -912,18 +1115,30 @@ class EstablishmentManager { if (_log.shouldLog(Log.DEBUG)) _log.debug("Send created to: " + state); - try { - state.generateSessionKey(); - } catch (DHSessionKeyBuilder.InvalidPublicParameterException ippe) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Peer " + state + " sent us an invalid DH parameter", ippe); - _inboundStates.remove(state.getRemoteHostId()); - state.fail(); - return; + int version = state.getVersion(); + UDPPacket pkt; + if (version == 1) { + try { + state.generateSessionKey(); + } catch (DHSessionKeyBuilder.InvalidPublicParameterException ippe) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Peer " + state + " sent us an invalid DH parameter", ippe); + _inboundStates.remove(state.getRemoteHostId()); + state.fail(); + return; + } + pkt = _builder.buildSessionCreatedPacket(state, + _transport.getExternalPort(state.getSentIP().length == 16), + _transport.getIntroKey()); + } else { + // if already sent, get from the state to retx + InboundEstablishState2 state2 = (InboundEstablishState2) state; + InboundEstablishState.InboundState istate = state2.getState(); + if (istate == IB_STATE_CREATED_SENT) + pkt = state2.getRetransmitSessionCreatedPacket(); + else + pkt = _builder2.buildSessionCreatedPacket((InboundEstablishState2) state); } - UDPPacket pkt = _builder.buildSessionCreatedPacket(state, - _transport.getExternalPort(state.getSentIP().length == 16), - _transport.getIntroKey()); if (pkt == null) { if (_log.shouldLog(Log.WARN)) _log.warn("Peer " + state + " sent us an invalid IP?"); @@ -932,7 +1147,9 @@ class EstablishmentManager { return; } _transport.send(pkt); - state.createdPacketSent(); + if (version == 1) + state.createdPacketSent(); + // else PacketBuilder2 told the state } /** @@ -941,14 +1158,28 @@ class EstablishmentManager { private void sendRequest(OutboundEstablishState state) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Send SessionRequest to: " + state); - UDPPacket packet = _builder.buildSessionRequestPacket(state); + int version = state.getVersion(); + UDPPacket packet; + if (version == 1) { + packet = _builder.buildSessionRequestPacket(state); + } else { + // if already sent, get from the state to retx + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + OutboundEstablishState.OutboundState ostate = state2.getState(); + if (ostate == OB_STATE_REQUEST_SENT) + packet = state2.getRetransmitSessionRequestPacket(); + else + packet = _builder2.buildSessionRequestPacket(state2); + } if (packet != null) { _transport.send(packet); } else { if (_log.shouldLog(Log.WARN)) _log.warn("Unable to build a session request packet for " + state); } - state.requestSent(); + if (version == 1) + state.requestSent(); + // else PacketBuilder2 told the state } /** @@ -1105,19 +1336,41 @@ class EstablishmentManager { // gives us the opportunity to "detect" our external addr _transport.externalAddressReceived(state.getRemoteIdentity().calculateHash(), state.getReceivedIP(), state.getReceivedPort()); - // signs if we havent signed yet - state.prepareSessionConfirmed(); - - // BUG - handle null return - UDPPacket packets[] = _builder.buildSessionConfirmedPackets(state, _context.router().getRouterInfo().getIdentity()); + int version = state.getVersion(); + UDPPacket packets[]; + if (version == 1) { + // signs if we havent signed yet + state.prepareSessionConfirmed(); + packets = _builder.buildSessionConfirmedPackets(state, _context.router().getRouterInfo().getIdentity()); + } else { + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + OutboundEstablishState.OutboundState ostate = state2.getState(); + // shouldn't happen, we go straight to confirmed after sending + if (ostate == OB_STATE_CONFIRMED_COMPLETELY) + return; + packets = _builder2.buildSessionConfirmedPackets(state2, _context.router().getRouterInfo()); + } + if (packets == null) { + state.fail(); + return; + } if (_log.shouldLog(Log.DEBUG)) _log.debug("Send confirm to: " + state); - for (int i = 0; i < packets.length; i++) + for (int i = 0; i < packets.length; i++) { _transport.send(packets[i]); + } - state.confirmedPacketsSent(); + if (version == 1) { + state.confirmedPacketsSent(); + } else { + // save for retx + OutboundEstablishState2 state2 = (OutboundEstablishState2) state; + state2.confirmedPacketsSent(packets); + // we are done, go right to ps2 + handleCompletelyEstablished(state2); + } } /** diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java index 5317046b78..dca920afe6 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java @@ -2,8 +2,10 @@ package net.i2p.router.transport.udp; import java.io.IOException; import java.net.DatagramPacket; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.List; @@ -44,6 +46,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa private final byte[] _rcvHeaderEncryptKey1; private byte[] _sendHeaderEncryptKey2; private byte[] _rcvHeaderEncryptKey2; + private byte[] _sessCrForReTX; // testing private static final boolean ENFORCE_TOKEN = false; @@ -52,7 +55,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa /** * @param localPort Must be our external port, otherwise the signature of the * SessionCreated message will be bad if the external port != the internal port. - * @param packet with all header encryption removed + * @param packet with all header encryption removed, + * either a SessionRequest OR a TokenRequest. */ public InboundEstablishState2(RouterContext ctx, UDPTransport transport, UDPPacket packet) throws GeneralSecurityException { @@ -279,6 +283,13 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa // end payload callbacks ///////////////////////////////////////////////////////// + // SSU 1 unsupported things + + @Override + public void generateSessionKey() { throw new UnsupportedOperationException(); } + + // SSU 2 things + public long getSendConnID() { return _sendConnID; } public long getRcvConnID() { return _rcvConnID; } public long getToken() { return _token; } @@ -406,6 +417,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa if (_log.shouldDebug()) _log.debug("State after sess conf: " + _handshakeState); processPayload(payload, payload.length, false); + _sessCrForReTX = null; // TODO split, calculate keys @@ -426,6 +438,58 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa packetReceived(); } + + /** + * note that we just sent the SessionCreated packet + * and save it for retransmission + */ + public synchronized void createdPacketSent(DatagramPacket pkt) { + if (_sessCrForReTX == null) { + // store pkt for retx + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + _sessCrForReTX = new byte[len]; + System.arraycopy(data, off, _sessCrForReTX, 0, len); + } + createdPacketSent(); + } + + /** + * @return null if not sent or already got the session created + */ + public synchronized UDPPacket getRetransmitSessionCreatedPacket() { + if (_sessCrForReTX == null) + return null; + UDPPacket packet = UDPPacket.acquire(_context, false); + DatagramPacket pkt = packet.getPacket(); + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + System.arraycopy(_sessCrForReTX, 0, data, off, _sessCrForReTX.length); + InetAddress to; + try { + to = InetAddress.getByAddress(_aliceIP); + } catch (UnknownHostException uhe) { + if (_log.shouldLog(Log.ERROR)) + _log.error("How did we think this was a valid IP? " + _remoteHostId); + packet.release(); + return null; + } + pkt.setAddress(to); + pkt.setPort(_alicePort); + packet.setMessageType(PacketBuilder2.TYPE_CONF); + packet.setPriority(PacketBuilder2.PRIORITY_HIGH); + createdPacketSent(); + return packet; + } + + /** + * @return null we have not received the session confirmed + */ + public synchronized PeerState2 getPeerState() { + // TODO + return null; + } @Override public String toString() { diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java index 0628cb63bc..ffe283a75e 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java @@ -16,6 +16,7 @@ import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.router.RouterContext; @@ -36,6 +37,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl private final UDPTransport _transport; private final long _sendConnID; private final long _rcvConnID; + private final RouterAddress _routerAddress; private long _token; private HandshakeState _handshakeState; private final byte[] _sendHeaderEncryptKey1; @@ -44,6 +46,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl private byte[] _rcvHeaderEncryptKey2; private final byte[] _rcvRetryHeaderEncryptKey2; private int _mtu; + private byte[] _sessReqForReTX; + private byte[] _sessConfForReTX; private static final boolean SET_TOKEN = false; /** @@ -56,10 +60,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl * @param addr non-null */ public OutboundEstablishState2(RouterContext ctx, UDPTransport transport, RemoteHostId claimedAddress, - RemoteHostId remoteHostId, int mtu, - RouterIdentity remotePeer, byte[] publicKey, - boolean needIntroduction, - SessionKey introKey, UDPAddress addr) { + RemoteHostId remoteHostId, RouterIdentity remotePeer, + boolean needIntroduction, + SessionKey introKey, RouterAddress ra, UDPAddress addr) throws IllegalArgumentException { super(ctx, claimedAddress, remoteHostId, remotePeer, needIntroduction, introKey, addr); _transport = transport; if (claimedAddress != null) { @@ -68,10 +71,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl } catch (UnknownHostException uhe) { throw new IllegalArgumentException("bad IP", uhe); } - _mtu = mtu; - } else { - _mtu = PeerState.MIN_IPV6_MTU; } + _mtu = addr.getMTU(); if (addr.getIntroducerCount() > 0) { if (_log.shouldLog(Log.DEBUG)) _log.debug("new outbound establish to " + remotePeer.calculateHash() + ", with address: " + addr); @@ -80,20 +81,18 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl _currentState = OutboundState.OB_STATE_UNKNOWN; } - // SSU2 - createNewState(publicKey); - _sendConnID = ctx.random().nextLong(); // rcid == scid is not allowed long rcid; do { rcid = ctx.random().nextLong(); } while (_sendConnID == rcid); - if (SET_TOKEN) { - do { - _token = ctx.random().nextLong(); - } while (_token == 0); - } + + _token = _transport.getEstablisher().getOutboundToken(_remotePeer.calculateHash()); + _routerAddress = ra; + if (_token != 0) + createNewState(ra); + _rcvConnID = rcid; byte[] ik = introKey.getData(); _sendHeaderEncryptKey1 = ik; @@ -103,7 +102,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl _rcvRetryHeaderEncryptKey2 = ik; } - private void createNewState(byte[] publicKey) { + private void createNewState(RouterAddress addr) { + String ss = addr.getOption("s"); + if (ss == null) + throw new IllegalArgumentException("no SSU2 S"); + byte[] publicKey = Base64.decode(ss); + if (publicKey == null) + throw new IllegalArgumentException("bad SSU2 S"); + if (publicKey.length != 32) + throw new IllegalArgumentException("bad SSU2 S len"); try { _handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.INITIATOR, _transport.getXDHFactory()); } catch (GeneralSecurityException gse) { @@ -119,8 +126,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl HandshakeState old = _handshakeState; byte[] pub = new byte[32]; old.getRemotePublicKey().getPublicKey(pub, 0); - createNewState(pub); - old.destroy(); + createNewState(_routerAddress); + if (old != null) + old.destroy(); //_rcvHeaderEncryptKey2 will be set after the Session Request message is created _rcvHeaderEncryptKey2 = null; } @@ -201,6 +209,11 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl // end payload callbacks ///////////////////////////////////////////////////////// + // SSU 1 unsupported things + + + // SSU 2 things + @Override public int getVersion() { return 2; } public long getSendConnID() { return _sendConnID; } @@ -226,6 +239,12 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl /** what is the largest packet we can send to the peer? */ public int getMTU() { return _mtu; } + public synchronized void receiveRetry(UDPPacket packet) throws GeneralSecurityException { + ////// TODO state check + createNewState(_routerAddress); + ////// TODO state change + } + public synchronized void receiveSessionCreated(UDPPacket packet) throws GeneralSecurityException { ////// todo fix state check if (_currentState == OutboundState.OB_STATE_VALIDATION_FAILED) { @@ -256,6 +275,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl if (_log.shouldDebug()) _log.debug("State after sess cr: " + _handshakeState); processPayload(payload, payload.length, true); + _sessReqForReTX = null; _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed"); if (_currentState == OutboundState.OB_STATE_UNKNOWN || @@ -270,15 +290,93 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl packetReceived(); } + /** + * note that we just sent the SessionConfirmed packets + * and save them for retransmission + */ + public synchronized void tokenRequestSent(DatagramPacket packet) { + if (_currentState == OutboundState.OB_STATE_UNKNOWN) + _currentState = OutboundState.OB_STATE_TOKEN_REQUEST_SENT; + else if (_currentState == OutboundState.OB_STATE_RETRY_RECEIVED) + _currentState = OutboundState.OB_STATE_REQUEST_SENT_NEW_TOKEN; + // don't bother saving for retx, just make a new one every time + } + /** * note that we just sent the SessionRequest packet + * and save it for retransmission */ - @Override - public synchronized void requestSent() { - /// TODO store pkt for retx + public synchronized void requestSent(DatagramPacket pkt) { + if (_sessReqForReTX == null) { + // store pkt for retx + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + _sessReqForReTX = new byte[len]; + System.arraycopy(data, off, _sessReqForReTX, 0, len); + } if (_rcvHeaderEncryptKey2 == null) _rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader"); - super.requestSent(); + requestSent(); + } + + /** + * note that we just sent the SessionConfirmed packets + * and save them for retransmission + */ + public synchronized void confirmedPacketsSent(UDPPacket[] packets) { + if (_sessConfForReTX == null) { + // store pkt for retx + // only one supported right now + DatagramPacket pkt = packets[0].getPacket(); + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + int len = pkt.getLength(); + _sessConfForReTX = new byte[len]; + System.arraycopy(data, off, _sessConfForReTX, 0, len); + if (_rcvHeaderEncryptKey2 == null) + _rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader"); + + // TODO split(), create PeerState2 + } + confirmedPacketsSent(); + } + + /** + * @return null if not sent or already got the session created + */ + public synchronized UDPPacket getRetransmitSessionRequestPacket() { + if (_sessReqForReTX == null) + return null; + UDPPacket packet = UDPPacket.acquire(_context, false); + DatagramPacket pkt = packet.getPacket(); + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + System.arraycopy(_sessReqForReTX, 0, data, off, _sessReqForReTX.length); + InetAddress to; + try { + to = InetAddress.getByAddress(_bobIP); + } catch (UnknownHostException uhe) { + if (_log.shouldLog(Log.ERROR)) + _log.error("How did we think this was a valid IP? " + _remoteHostId); + packet.release(); + return null; + } + pkt.setAddress(to); + pkt.setPort(_bobPort); + packet.setMessageType(PacketBuilder2.TYPE_SREQ); + packet.setPriority(PacketBuilder2.PRIORITY_HIGH); + requestSent(); + return packet; + } + + /** + * @return null we have not sent the session confirmed + */ + public synchronized PeerState2 getPeerState() { + // TODO + // set confirmed pkt data + return null; } @Override diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java index 5b7170d702..19b08c97df 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java @@ -292,7 +292,7 @@ class PacketBuilder2 { packet.release(); return null; } - InetAddress to = null; + InetAddress to; try { to = InetAddress.getByAddress(toIP); } catch (UnknownHostException uhe) { @@ -309,6 +309,7 @@ class PacketBuilder2 { setTo(packet, to, state.getSentPort()); packet.setMessageType(TYPE_SREQ); packet.setPriority(PRIORITY_HIGH); + state.tokenRequestSent(pkt); return packet; } @@ -328,7 +329,7 @@ class PacketBuilder2 { packet.release(); return null; } - InetAddress to = null; + InetAddress to; try { to = InetAddress.getByAddress(toIP); } catch (UnknownHostException uhe) { @@ -345,6 +346,7 @@ class PacketBuilder2 { setTo(packet, to, state.getSentPort()); packet.setMessageType(TYPE_SREQ); packet.setPriority(PRIORITY_HIGH); + state.requestSent(pkt); return packet; } @@ -365,10 +367,10 @@ class PacketBuilder2 { encryptSessionCreated(packet, state.getHandshakeState(), state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), state.getSentRelayTag(), state.getNextToken(), sentIP, port); - state.createdPacketSent(); pkt.setSocketAddress(state.getSentAddress()); packet.setMessageType(TYPE_CREAT); packet.setPriority(PRIORITY_HIGH); + state.createdPacketSent(pkt); return packet; } @@ -390,10 +392,10 @@ class PacketBuilder2 { encryptRetry(packet, state.getSendHeaderEncryptKey1(), n, state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), sentIP, port); - state.retryPacketSent(); pkt.setSocketAddress(state.getSentAddress()); packet.setMessageType(TYPE_CREAT); packet.setPriority(PRIORITY_HIGH); + state.retryPacketSent(); return packet; } @@ -460,7 +462,7 @@ class PacketBuilder2 { // TODO numFragments > 1 requires shift to data phase throw new IllegalArgumentException("TODO"); } - state.confirmedPacketsSent(); + state.confirmedPacketsSent(packets); return packets; } @@ -473,7 +475,7 @@ class PacketBuilder2 { UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 1, SESSION_CONFIRMED_FLAG_BYTE); DatagramPacket pkt = packet.getPacket(); - InetAddress to = null; + InetAddress to; try { to = InetAddress.getByAddress(state.getSentIP()); } catch (UnknownHostException uhe) { diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index 23ba705327..58f933663a 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -139,11 +139,11 @@ public class PeerState { /** what IP is the peer sending and receiving packets on? */ protected final byte[] _remoteIP; /** cached IP address */ - private volatile InetAddress _remoteIPAddress; + protected volatile InetAddress _remoteIPAddress; /** what port is the peer sending and receiving packets on? */ - private volatile int _remotePort; + protected volatile int _remotePort; /** cached RemoteHostId, used to find the peerState by remote info */ - private volatile RemoteHostId _remoteHostId; + protected volatile RemoteHostId _remoteHostId; /** if we need to contact them, do we need to talk to an introducer? */ //private boolean _remoteRequiresIntroduction; diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState2.java b/router/java/src/net/i2p/router/transport/udp/PeerState2.java index d8e7e14673..9db55bb9a5 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java @@ -1,7 +1,9 @@ package net.i2p.router.transport.udp; import java.net.DatagramPacket; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -44,6 +46,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback private final byte[] _rcvHeaderEncryptKey2; private final SSU2Bitfield _receivedMessages; private final SSU2Bitfield _ackedMessages; + private byte[] _sessConfForReTX; public static final int MIN_MTU = 1280; @@ -333,4 +336,33 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback void fragmentsSent(long pktNum, List<PacketBuilder.Fragment> fragments) { } + + /** + * note that we just sent the SessionConfirmed packets + * and save them for retransmission + */ + public synchronized void confirmedPacketsSent(byte[] data) { + if (_sessConfForReTX == null) + _sessConfForReTX = data; + } + + /** + * @return null if not sent or already got the ack + */ + public synchronized UDPPacket[] getRetransmitSessionConfirmedPackets() { + if (_sessConfForReTX == null) + return null; + UDPPacket packet = UDPPacket.acquire(_context, false); + UDPPacket[] rv = new UDPPacket[1]; + rv[0] = packet; + DatagramPacket pkt = packet.getPacket(); + byte data[] = pkt.getData(); + int off = pkt.getOffset(); + System.arraycopy(_sessConfForReTX, 0, data, off, _sessConfForReTX.length); + pkt.setAddress(_remoteIPAddress); + pkt.setPort(_remotePort); + packet.setMessageType(PacketBuilder2.TYPE_CONF); + packet.setPriority(PacketBuilder2.PRIORITY_HIGH); + return rv; + } } 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 73152c85cb..771c3f85d5 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -906,7 +906,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority * @return the valid version 1 or 2, or 0 if unusable * @since 0.9.54 */ - private int getSSUVersion(RouterAddress addr) { + int getSSUVersion(RouterAddress addr) { int rv; String style = addr.getTransportStyle(); if (style.equals(STYLE)) { -- GitLab