Skip to content

Commit

Permalink
Add handshake probe.
Browse files Browse the repository at this point in the history
Signed-off-by: Achim Kraus <achim.kraus@bosch-si.com>
  • Loading branch information
Achim Kraus committed Jan 2, 2020
1 parent 671fc91 commit 8d23e72
Show file tree
Hide file tree
Showing 5 changed files with 416 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public class DtlsEndpointContext extends MapBasedEndpointContext {
* the connector is configured to act as server only.
*/
public static final String HANDSHAKE_MODE_FORCE = "force";
/**
* Force handshake probe before send this message. Doesn't start a
* handshake, if the connector is configured to act as server only.
*/
public static final String HANDSHAKE_MODE_PROBE = "probe";
/**
* Don't start a handshake, even, if no session is available.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ public void handshakeFailed(Handshaker handshaker, Throwable error) {
Connection connection = handshaker.getConnection();
if (handshaker.isRemovingConnection()) {
connectionStore.remove(connection, false);
} else if (handshaker.isProbing()) {
LOGGER.debug("Handshake with [{}] failed within probe!", handshaker.getPeerAddress());
} else if (connection.getEstablishedSession() == handshaker.getSession()) {
// failure after established (last FINISH),
// but before completed (first data)
Expand All @@ -445,8 +447,7 @@ public void handshakeFailed(Handshaker handshaker, Throwable error) {
LOGGER.warn("Handshake with [{}] failed, but has an established session!",
handshaker.getPeerAddress());
} else {
LOGGER.warn("Handshake with [{}] failed, connection preserved!",
handshaker.getPeerAddress());
LOGGER.warn("Handshake with [{}] failed, connection preserved!", handshaker.getPeerAddress());
}
}
};
Expand Down Expand Up @@ -1237,6 +1238,16 @@ public void processRecord(Record record, Connection connection) {

record.applySession(session);

if (handshaker != null && handshaker.isProbing()) {
// received record, probe successful
if (connection.hasEstablishedSession()) {
connectionStore.removeFromEstablishedSessions(connection.getEstablishedSession(), connection);
}
connection.resetSession();
handshaker.resetProbing();
LOGGER.debug("handshake probe successful {}", connection.getPeerAddress());
}

switch (record.getType()) {
case APPLICATION_DATA:
processApplicationDataRecord(record, connection);
Expand Down Expand Up @@ -2091,10 +2102,18 @@ private void sendMessage(final long nanos, final RawData message, final Connecti
LOGGER.debug("Sending application layer message to [{}]", message.getEndpointContext());

Handshaker handshaker = connection.getOngoingHandshake();
if (handshaker != null && handshaker.isExpired()) {
// handshake expired during Android / OS "deep sleep"
// on sending, abort, keep connection for new handshake
handshaker.handshakeAborted(new Exception("handshake already expired!"));
if (handshaker != null) {
if (handshaker.isExpired()) {
// handshake expired during Android / OS "deep sleep"
// on sending, abort, keep connection for new handshake
handshaker.handshakeAborted(new Exception("handshake already expired!"));
} else if (handshaker.isProbing()) {
if (checkOutboundEndpointContext(message, null)) {
message.onConnecting();
handshaker.addApplicationDataForDeferredProcessing(message);
}
return;
}
}

if (connection.isActive()) {
Expand Down Expand Up @@ -2172,8 +2191,9 @@ private void sendMessageWithSession(final RawData message, final Connection conn
return;
}
} else {
boolean probing = DtlsEndpointContext.HANDSHAKE_MODE_PROBE.equals(handshakeMode);
boolean full = DtlsEndpointContext.HANDSHAKE_MODE_FORCE_FULL.equals(handshakeMode);
boolean force = full || DtlsEndpointContext.HANDSHAKE_MODE_FORCE.equals(handshakeMode);
boolean force = probing || full || DtlsEndpointContext.HANDSHAKE_MODE_FORCE.equals(handshakeMode);
if (force || connection.isAutoResumptionRequired(getAutResumptionTimeout(message))) {
// create the session to resume from the previous one.
if (serverOnly) {
Expand All @@ -2185,17 +2205,28 @@ private void sendMessageWithSession(final RawData message, final Connection conn
}
message.onConnecting();
Handshaker previousHandshaker = connection.getOngoingHandshake();
SessionTicket ticket;
SessionId sessionId;
SessionTicket ticket = null;
SessionId sessionId = null;
if (session != null) {
sessionId = session.getSessionIdentifier();
ticket = session.getSessionTicket();
connectionStore.removeFromEstablishedSessions(session, connection);
if (!probing) {
connectionStore.removeFromEstablishedSessions(session, connection);
}
} else {
sessionId = connection.getSessionIdentity();
ticket = connection.getSessionTicket();
if (!full) {
sessionId = connection.getSessionIdentity();
ticket = connection.getSessionTicket();
}
probing = false;
}
if (probing) {
// Only reset the resumption trigger, but keep the session for now
// the session will be reseted with the first received data
connection.setResumptionRequired(false);
} else {
connection.resetSession();
}
connection.resetSession();
Handshaker newHandshaker;
if (full || sessionId.isEmpty()) {
// server may use a empty session id to indicate,
Expand All @@ -2210,7 +2241,7 @@ private void sendMessageWithSession(final RawData message, final Connection conn
SecretUtil.destroy(ticket);
resumableSession.setHostName(message.getEndpointContext().getVirtualHost());
newHandshaker = new ResumingClientHandshaker(resumableSession, this, connection, config,
maximumTransmissionUnit);
maximumTransmissionUnit, probing);
}
initializeHandshaker(newHandshaker);
if (previousHandshaker != null) {
Expand Down Expand Up @@ -2391,9 +2422,12 @@ protected void sendNextDatagramOverNetwork(final DatagramPacket datagramPacket)

private void handleTimeout(DTLSFlight flight, Connection connection) {

if (!flight.isResponseCompleted() && !connection.hasEstablishedSession()) {
if (!flight.isResponseCompleted()) {
Handshaker handshaker = connection.getOngoingHandshake();
if (null != handshaker) {
if (!handshaker.isProbing() && connection.hasEstablishedSession()) {
return;
}
Exception cause = null;
String message = "";
if (!connection.isExecuting() || !running.get()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,31 @@ public final void handshakeAborted(Throwable cause) {
handshakeFailed(cause);
}

/**
* Test, if handshake was started in probing mode.
*
* Usually a resuming client handshake removes the session from the
* connection store with the start. Probing removes the session only with
* the first data received back.
*
* @return {@code true}, if handshake is in probing mode, {@code false},
* otherwise.
* @see ResumingClientHandshaker
*/
public boolean isProbing() {
// intended to be overriden by the ResumingClientHandshaker
return false;
}

/**
* Reset probing mode, when data is received during.
*
* @see ResumingClientHandshaker
*/
public void resetProbing() {
// intended to be overriden by the ResumingClientHandshaker
}

/**
* Test, if handshake is expired according nano realtime.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.security.GeneralSecurityException;
import java.security.MessageDigest;

import org.eclipse.californium.elements.util.NoPublicAPI;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription;
import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel;
Expand All @@ -48,15 +49,46 @@
* The resuming client handshaker executes a abbreviated handshake by adding a
* valid session identifier into its ClientHello message. The message flow is
* depicted in <a href="http://tools.ietf.org/html/rfc5246#section-7.3">Figure
* 2</a>. The new keys will be generated from the master secret established from a
* previous full handshake.
* 2</a>. The new keys will be generated from the master secret established from
* a previous full handshake.
* <p>
* This implementation offers a probing mode.
*
* If a mobile peer doesn't get a ACK or response that may have two different
* causes:
*
* <ol>
* <li>server has lost session (association)</li>
* <li>connectivity is lost</li>
* </ol>
*
* The second is sometime hard to detect; the peer's state is connected, but
* effectively it's not working. In that case, after some retransmissions, the
* peer starts a handshake. Without the probing mode starting a handshake
* removes on the client the session. If the handshake timesout (though the
* connection is not working), the peer still requires a new handshake after the
* connectivity is established again.
*
* With probing mode, the handshake starts without removing the session. If some
* data is received, the session is removed and the handshake gets completed. If
* no data is received, the peer assumes, that the connectivity is lost (even if
* it's own state indicates connectivity) and just timesout the request. if the
* connectivity is established again, just a new request could be send without a
* handshake.
* </p>
*/
@NoPublicAPI
public class ResumingClientHandshaker extends ClientHandshaker {

private static HandshakeState[] RESUME = { new HandshakeState(HandshakeType.HELLO_VERIFY_REQUEST, true),
new HandshakeState(HandshakeType.SERVER_HELLO), new HandshakeState(ContentType.CHANGE_CIPHER_SPEC),
new HandshakeState(HandshakeType.FINISHED) };

/**
* Indicates probing for this handshake.
*/
private boolean probe;

// flag to indicate if we must do a full handshake or an abbreviated one
private boolean fullHandshake = false;

Expand All @@ -75,6 +107,8 @@ public class ResumingClientHandshaker extends ClientHandshaker {
* the DTLS configuration parameters to use for the handshake.
* @param maxTransmissionUnit
* the MTU value reported by the network interface the record layer is bound to.
* @param probe {@code true} enable probing for this resumption handshake,
* {@code false}, not probing handshake.
* @throws IllegalArgumentException
* if the given session does not contain an identifier.
* @throws IllegalStateException
Expand All @@ -83,11 +117,12 @@ public class ResumingClientHandshaker extends ClientHandshaker {
* if session, recordLayer or config is <code>null</code>
*/
public ResumingClientHandshaker(DTLSSession session, RecordLayer recordLayer, Connection connection,
DtlsConnectorConfig config, int maxTransmissionUnit) {
DtlsConnectorConfig config, int maxTransmissionUnit, boolean probe) {
super(session, recordLayer, connection, config, maxTransmissionUnit);
if (session.getSessionIdentifier() == null) {
throw new IllegalArgumentException("Session must contain the ID of the session to resume");
}
this.probe = probe;
}

// Methods ////////////////////////////////////////////////////////
Expand Down Expand Up @@ -241,4 +276,25 @@ public void startHandshake() throws HandshakeException {
states = RESUME;
statesIndex = 0;
}

@Override
public boolean isProbing() {
return probe;
}

@Override
public void resetProbing() {
probe = false;
}

/**
* {@inheritDoc}
*
* Connections of probing handshakes are not intended to be removed.
*/
@Override
public boolean isRemovingConnection() {
return !probe && super.isRemovingConnection();
}

}
Loading

0 comments on commit 8d23e72

Please sign in to comment.