SSU2: Start of packet handling

Store CipherStates in PeerState2
Add missing getVersion() overrides
This commit is contained in:
zzz
2022-02-26 08:25:55 -05:00
parent dc40755e7c
commit 6bb3657de2
8 changed files with 229 additions and 40 deletions

View File

@@ -211,6 +211,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
// end payload callbacks
/////////////////////////////////////////////////////////
@Override
public int getVersion() { return 2; }
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public long getToken() { return _token; }

View File

@@ -12,6 +12,7 @@ import java.util.Iterator;
import java.util.List;
import com.southernstorm.noise.protocol.ChaChaPolyCipherState;
import com.southernstorm.noise.protocol.CipherState;
import com.southernstorm.noise.protocol.HandshakeState;
import net.i2p.crypto.ChaCha20;
@@ -317,7 +318,7 @@ class PacketBuilder2 {
SSU2Payload.writePayload(data, SHORT_HEADER_SIZE, blocks);
pkt.setLength(off);
encryptDataPacket(packet, peer.getSendEncryptKey(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
// FIXME ticket #2675
@@ -439,7 +440,7 @@ class PacketBuilder2 {
*/
pkt.setLength(off);
encryptDataPacket(packet, peer.getSendEncryptKey(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
packet.setMessageType(TYPE_ACK);
packet.setPriority((fullACKCount > 0 || partialACKCount > 0) ? PRIORITY_HIGH : PRIORITY_LOW);
@@ -1131,20 +1132,20 @@ class PacketBuilder2 {
* length set to the end of the data.
* This will extend the length by 16 for the MAC.
*/
private void encryptDataPacket(UDPPacket packet, byte[] chachaKey, long n,
private void encryptDataPacket(UDPPacket packet, CipherState chacha, long n,
byte[] hdrKey1, byte[] hdrKey2) {
DatagramPacket pkt = packet.getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
int len = pkt.getLength();
ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
chacha.initializeKey(chachaKey, 0);
chacha.setNonce(n);
try {
chacha.encryptWithAd(data, off, SHORT_HEADER_SIZE,
data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Bad data msg", e);
synchronized(chacha) {
chacha.setNonce(n);
try {
chacha.encryptWithAd(data, off, SHORT_HEADER_SIZE,
data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Bad data msg", e);
}
}
pkt.setLength(len + MAC_LEN);
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);

View File

@@ -40,6 +40,7 @@ class PacketHandler {
private final Map<RemoteHostId, Object> _failCache;
private final BlockingQueue<UDPPacket> _inboundQueue;
private static final Object DUMMY = new Object();
private final boolean _enableSSU2;
private static final int TYPE_POISON = -99999;
private static final int MIN_QUEUE_SIZE = 16;
@@ -56,11 +57,12 @@ class PacketHandler {
private enum AuthType { NONE, INTRO, BOBINTRO, SESSION }
PacketHandler(RouterContext ctx, UDPTransport transport, EstablishmentManager establisher,
PacketHandler(RouterContext ctx, UDPTransport transport, boolean enableSSU2, EstablishmentManager establisher,
InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) {
_context = ctx;
_log = ctx.logManager().getLog(PacketHandler.class);
_transport = transport;
_enableSSU2 = enableSSU2;
_establisher = establisher;
_inbound = inbound;
_testManager = testManager;
@@ -222,8 +224,8 @@ class PacketHandler {
* Classify the packet by source IP/port, into 4 groups:
*<ol>
*<li>Established session
*<li>Pending inbound establishement
*<li>Pending outbound establishement
*<li>Pending inbound establishment
*<li>Pending outbound establishment
*<li>No established or pending session found
*</ol>
*/
@@ -238,7 +240,10 @@ class PacketHandler {
// Group 2: Inbound Establishment
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an inbound establishment");
receivePacket(reader, packet, est);
if (est.getVersion() == 2)
receiveSSU2Packet(packet, (InboundEstablishState2) est);
else
receivePacket(reader, packet, est);
} else {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Packet received is not for an inbound establishment");
@@ -247,7 +252,10 @@ class PacketHandler {
// Group 3: Outbound Establishment
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an outbound establishment");
receivePacket(reader, packet, oest);
if (oest.getVersion() == 2)
receiveSSU2Packet(packet, (OutboundEstablishState2) oest);
else
receivePacket(reader, packet, oest);
} else {
// Group 4: New conn or needs fallback
if (_log.shouldLog(Log.DEBUG))
@@ -259,9 +267,12 @@ class PacketHandler {
}
} else {
// Group 1: Established
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an existing peer");
receivePacket(reader, packet, state);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Packet received IS for an existing peer");
if (state.getVersion() == 2)
receiveSSU2Packet(packet, (PeerState2) state);
else
receivePacket(reader, packet, state);
}
}
@@ -520,6 +531,8 @@ class PacketHandler {
* and send it to one of four places: The EstablishmentManager, IntroductionManager,
* PeerTestManager, or InboundMessageFragments.
*
* SSU1 only.
*
* @param state non-null if fully established
* @param outState non-null if outbound establishing in process
* @param inState unused always null, TODO use for 48-byte destroys during inbound establishment
@@ -743,6 +756,50 @@ class PacketHandler {
}
}
//// Begin SSU2 Handling ////
/**
* Hand off to the state for processing.
* Packet is decrypted in-place, no fallback
* processing is possible.
*
* @param state must be version 2
* @since 0.9.54
*/
private void receiveSSU2Packet(UDPPacket packet, PeerState2 state) {
state.receivePacket(packet);
}
/**
* Hand off to the state for processing.
* Packet is decrypted in-place, no fallback
* processing is possible.
*
* @param state must be version 2
* @since 0.9.54
*/
private void receiveSSU2Packet(UDPPacket packet, InboundEstablishState2 state) {
}
/**
* Hand off to the state for processing.
* Packet is decrypted in-place, no fallback
* processing is possible.
*
* @param state must be version 2
* @since 0.9.54
*/
private void receiveSSU2Packet(UDPPacket packet, OutboundEstablishState2 state) {
}
//// End SSU2 Handling ////
/**
* Mark a string for extraction by xgettext and translation.
* Use this only in static initializers.

View File

@@ -38,8 +38,8 @@ import net.i2p.util.SimpleTimer2;
*
*/
public class PeerState {
private final RouterContext _context;
private final Log _log;
protected final RouterContext _context;
protected final Log _log;
/**
* The peer are we talking to. This should be set as soon as this
* state is created if we are initiating a connection, but if we are

View File

@@ -1,14 +1,22 @@
package net.i2p.router.transport.udp;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.util.concurrent.atomic.AtomicInteger;
import com.southernstorm.noise.protocol.CipherState;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.RouterContext;
import static net.i2p.router.transport.udp.SSU2Util.*;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Contain all of the state about a UDP connection to a peer.
@@ -20,17 +28,18 @@ import net.i2p.util.SimpleTimer2;
*
* @since 0.9.54
*/
public class PeerState2 extends PeerState {
public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback {
private final long _sendConnID;
private final long _rcvConnID;
private final AtomicInteger _packetNumber = new AtomicInteger();
private final byte[] _sendEncryptKey;
private final byte[] _rcvEncryptKey;
private final CipherState _sendCha;
private final CipherState _rcvCha;
private final byte[] _sendHeaderEncryptKey1;
private final byte[] _rcvHeaderEncryptKey1;
private final byte[] _sendHeaderEncryptKey2;
private final byte[] _rcvHeaderEncryptKey2;
private final SSU2Bitfield _receivedMessages;
private final SSU2Bitfield _sentMessages;
public static final int MIN_MTU = 1280;
@@ -39,30 +48,146 @@ public class PeerState2 extends PeerState {
*/
public PeerState2(RouterContext ctx, UDPTransport transport,
InetSocketAddress remoteAddress, Hash remotePeer, boolean isInbound, int rtt,
byte[] sendKey, byte[] rcvKey, long sendID, long rcvID,
CipherState sendCha, CipherState rcvCha, long sendID, long rcvID,
byte[] sendHdrKey1, byte[] sendHdrKey2, byte[] rcvHdrKey2) {
super(ctx, transport, remoteAddress, remotePeer, isInbound, rtt);
_sendConnID = sendID;
_rcvConnID = rcvID;
_sendEncryptKey = sendKey;
_rcvEncryptKey = rcvKey;
_sendCha = sendCha;
_rcvCha = rcvCha;
_sendHeaderEncryptKey1 = sendHdrKey1;
_rcvHeaderEncryptKey1 = transport.getSSU2StaticIntroKey();
_sendHeaderEncryptKey2 = sendHdrKey2;
_rcvHeaderEncryptKey2 = rcvHdrKey2;
_receivedMessages = new SSU2Bitfield(256, 0);
_sentMessages = new SSU2Bitfield(256, 0);
}
// SSU2
@Override
public int getVersion() { return 2; }
long getNextPacketNumber() { return _packetNumber.incrementAndGet(); }
public long getSendConnID() { return _sendConnID; }
public long getRcvConnID() { return _rcvConnID; }
public byte[] getSendEncryptKey() { return _sendEncryptKey; }
public byte[] getRcvEncryptKey() { return _rcvEncryptKey; }
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
public byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
public byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
public SSU2Bitfield getReceivedMessages() { return _receivedMessages; }
long getSendConnID() { return _sendConnID; }
long getRcvConnID() { return _rcvConnID; }
/** caller must sync on returned object when encrypting */
CipherState getSendCipher() { return _sendCha; }
byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
byte[] getSendHeaderEncryptKey2() { return _sendHeaderEncryptKey2; }
byte[] getRcvHeaderEncryptKey2() { return _rcvHeaderEncryptKey2; }
SSU2Bitfield getReceivedMessages() { return _receivedMessages; }
SSU2Bitfield getSentMessages() { return _sentMessages; }
void receivePacket(UDPPacket packet) {
DatagramPacket dpacket = packet.getPacket();
byte[] data = dpacket.getData();
int off = dpacket.getOffset();
int len = dpacket.getLength();
try {
if (len < MIN_DATA_LEN) {
if (_log.shouldWarn())
_log.warn("Inbound packet too short " + len + " on " + this);
return;
}
SSU2Header.Header header = SSU2Header.trialDecryptShortHeader(packet, _rcvHeaderEncryptKey1, _rcvHeaderEncryptKey2);
if (header == null) {
if (_log.shouldWarn())
_log.warn("bad data header on " + this);
return;
}
if (header.getDestConnID() != _rcvConnID) {
if (_log.shouldWarn())
_log.warn("bad Dest Conn id " + header.getDestConnID() + " on " + this);
return;
}
if (header.getType() != DATA_FLAG_BYTE) {
if (_log.shouldWarn())
_log.warn("bad data pkt type " + (header.getType() & 0xff) + " on " + this);
return;
}
long n = header.getPacketNumber();
SSU2Header.acceptTrialDecrypt(packet, header);
synchronized (_rcvCha) {
_rcvCha.setNonce(n);
// decrypt in-place
_rcvCha.decryptWithAd(header.data, data, off + SHORT_HEADER_SIZE, data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE);
if (_receivedMessages.set(n)) {
if (_log.shouldWarn())
_log.warn("dup pkt rcvd " + n + " on " + this);
return;
}
}
processPayload(data, off + SHORT_HEADER_SIZE, len - (SHORT_HEADER_SIZE + MAC_LEN));
} catch (GeneralSecurityException gse) {
if (_log.shouldWarn())
_log.warn("Bad encrypted packet:\n" + HexDump.dump(data, off, len), gse);
} catch (IndexOutOfBoundsException ioobe) {
if (_log.shouldWarn())
_log.warn("Bad encrypted packet:\n" + HexDump.dump(data, off, len), ioobe);
} finally {
packet.release();
}
}
private void processPayload(byte[] payload, int offset, int length) throws GeneralSecurityException {
try {
int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, false);
} catch (Exception e) {
throw new GeneralSecurityException("Session Created payload error", e);
}
}
/////////////////////////////////////////////////////////
// begin payload callbacks
/////////////////////////////////////////////////////////
public void gotDateTime(long time) {
}
public void gotOptions(byte[] options, boolean isHandshake) {
}
public void gotRI(RouterInfo ri, boolean isHandshake, boolean flood) throws DataFormatException {
}
public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) {
throw new IllegalStateException("RI fragment in Data phase");
}
public void gotAddress(byte[] ip, int port) {
}
public void gotIntroKey(byte[] key) {
}
public void gotRelayTagRequest() {
}
public void gotRelayTag(long tag) {
}
public void gotToken(long token, long expires) {
}
public void gotI2NP(I2NPMessage msg) {
}
public void gotFragment(byte[] data, long messageID, int type, long expires, int frag, boolean isLast) throws DataFormatException {
}
public void gotACK(long ackThru, int acks, byte[] ranges) {
}
public void gotTermination(int reason, long count) {
}
public void gotUnknown(int type, int len) {
}
public void gotPadding(int paddingLength, int frameLength) {
}
/////////////////////////////////////////////////////////
// end payload callbacks
/////////////////////////////////////////////////////////
}

View File

@@ -59,9 +59,10 @@ public class SSU2Bitfield {
* the offset shifts up and the lowest set bits are lost.
*
* @throws IndexOutOfBoundsException if bit is smaller then zero
* OR if the shift is too big
* @return previous value, true if previously set or unknown
*/
public boolean set(long bit) {
public boolean set(long bit) throws IndexOutOfBoundsException {
if (bit < 0)
throw new IndexOutOfBoundsException(Long.toString(bit));
boolean rv;

View File

@@ -316,6 +316,9 @@ class UDPPacket implements CDPQEntry {
* Decrypt this valid packet, overwriting the _data buffer's payload
* with the decrypted data (leaving the MAC and IV unaltered)
*
* SSU 1 only.
* SSU 2 decryption is in PacketHandler.
*
*/
public synchronized void decrypt(SessionKey cipherKey) {
verifyNotReleased();

View File

@@ -655,7 +655,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_establisher = new EstablishmentManager(_context, this);
if (_handler == null)
_handler = new PacketHandler(_context, this, _establisher, _inboundFragments, _testManager, _introManager);
_handler = new PacketHandler(_context, this, _enableSSU2, _establisher, _inboundFragments, _testManager, _introManager);
// See comments in DummyThrottle.java
if (USE_PRIORITY && _refiller == null)