Adding a method to change the initial DTLS retransmission timer value.

This allows an application to override the default of 1 second, which
is what's instructed in RFC 6347 but is not an absolute requirement.

Change-Id: I0bbb16e31990fbcab44a29325b6ec7757d5789e5
Reviewed-on: https://boringssl-review.googlesource.com/7930
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 1d0b486..c7b2581 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -487,6 +487,16 @@
  * and zero on failure. */
 OPENSSL_EXPORT int SSL_set_mtu(SSL *ssl, unsigned mtu);
 
+/* DTLSv1_set_initial_timeout_duration sets the initial duration for a DTLS
+ * handshake timeout.
+ *
+ * This duration overrides the default of 1 second, which is the strong
+ * recommendation of RFC 6347 (see section 4.2.4.1). However, there may exist
+ * situations where a shorter timeout would be beneficial, such as for
+ * time-sensitive applications. */
+OPENSSL_EXPORT void DTLSv1_set_initial_timeout_duration(SSL *ssl,
+                                                        unsigned duration_ms);
+
 /* DTLSv1_get_timeout queries the next DTLS handshake timeout. If there is a
  * timeout in progress, it sets |*out| to the time remaining and returns one.
  * Otherwise, it returns zero.
@@ -3882,6 +3892,10 @@
   struct ssl3_state_st *s3;  /* SSLv3 variables */
   struct dtls1_state_st *d1; /* DTLSv1 variables */
 
+  /* initial_timeout_duration_ms is the default DTLS timeout duration in
+   * milliseconds. It's used to initialize the timer any time it's restarted. */
+  unsigned initial_timeout_duration_ms;
+
   /* callback that allows applications to peek at protocol messages */
   void (*msg_callback)(int write_p, int version, int content_type,
                        const void *buf, size_t len, SSL *ssl, void *arg);
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index e48fbf1..dc3c640 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -157,17 +157,26 @@
   return cipher->algorithm_enc != SSL_RC4 && cipher->algorithm_enc != SSL_eNULL;
 }
 
+void DTLSv1_set_initial_timeout_duration(SSL *ssl, unsigned int duration_ms) {
+  ssl->initial_timeout_duration_ms = duration_ms;
+}
+
 void dtls1_start_timer(SSL *ssl) {
-  /* If timer is not set, initialize duration with 1 second */
+  /* If timer is not set, initialize duration (by default, 1 second) */
   if (ssl->d1->next_timeout.tv_sec == 0 && ssl->d1->next_timeout.tv_usec == 0) {
-    ssl->d1->timeout_duration = 1;
+    ssl->d1->timeout_duration_ms = ssl->initial_timeout_duration_ms;
   }
 
   /* Set timeout to current time */
   get_current_time(ssl, &ssl->d1->next_timeout);
 
   /* Add duration to current time */
-  ssl->d1->next_timeout.tv_sec += ssl->d1->timeout_duration;
+  ssl->d1->next_timeout.tv_sec += ssl->d1->timeout_duration_ms / 1000;
+  ssl->d1->next_timeout.tv_usec += (ssl->d1->timeout_duration_ms % 1000) * 1000;
+  if (ssl->d1->next_timeout.tv_usec >= 1000000) {
+    ssl->d1->next_timeout.tv_sec++;
+    ssl->d1->next_timeout.tv_usec -= 1000000;
+  }
   BIO_ctrl(SSL_get_rbio(ssl), BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
            &ssl->d1->next_timeout);
 }
@@ -230,9 +239,9 @@
 }
 
 void dtls1_double_timeout(SSL *ssl) {
-  ssl->d1->timeout_duration *= 2;
-  if (ssl->d1->timeout_duration > 60) {
-    ssl->d1->timeout_duration = 60;
+  ssl->d1->timeout_duration_ms *= 2;
+  if (ssl->d1->timeout_duration_ms > 60000) {
+    ssl->d1->timeout_duration_ms = 60000;
   }
   dtls1_start_timer(ssl);
 }
@@ -241,7 +250,7 @@
   /* Reset everything */
   ssl->d1->num_timeouts = 0;
   memset(&ssl->d1->next_timeout, 0, sizeof(struct timeval));
-  ssl->d1->timeout_duration = 1;
+  ssl->d1->timeout_duration_ms = ssl->initial_timeout_duration_ms;
   BIO_ctrl(SSL_get_rbio(ssl), BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
            &ssl->d1->next_timeout);
   /* Clear retransmission buffer */
diff --git a/ssl/internal.h b/ssl/internal.h
index ce9175c..2c0bda3 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -956,8 +956,8 @@
    * timeout. */
   struct timeval next_timeout;
 
-  /* Timeout duration */
-  unsigned short timeout_duration;
+  /* timeout_duration_ms is the timeout duration in milliseconds. */
+  unsigned timeout_duration_ms;
 } DTLS1_STATE;
 
 extern const SSL3_ENC_METHOD TLSv1_enc_data;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 3135db6..1713c08 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -369,6 +369,10 @@
   ssl->min_version = ctx->min_version;
   ssl->max_version = ctx->max_version;
 
+  /* RFC 6347 states that implementations SHOULD use an initial timer value of
+   * 1 second. */
+  ssl->initial_timeout_duration_ms = 1000;
+
   ssl->options = ctx->options;
   ssl->mode = ctx->mode;
   ssl->max_cert_list = ctx->max_cert_list;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 5effa58..c3ff33b 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1317,6 +1317,10 @@
       return false;
     }
   }
+  if (config->initial_timeout_duration_ms > 0) {
+    DTLSv1_set_initial_timeout_duration(ssl.get(),
+                                        config->initial_timeout_duration_ms);
+  }
 
   int sock = Connect(config->port);
   if (sock == -1) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 5b746c6..e212108 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4583,6 +4583,24 @@
 	60 * time.Second,
 }
 
+// shortTimeouts is an alternate set of timeouts which would occur if the
+// initial timeout duration was set to 250ms.
+var shortTimeouts = []time.Duration{
+	250 * time.Millisecond,
+	500 * time.Millisecond,
+	1 * time.Second,
+	2 * time.Second,
+	4 * time.Second,
+	8 * time.Second,
+	16 * time.Second,
+	32 * time.Second,
+	60 * time.Second,
+	60 * time.Second,
+	60 * time.Second,
+	60 * time.Second,
+	60 * time.Second,
+}
+
 func addDTLSRetransmitTests() {
 	// Test that this is indeed the timeout schedule. Stress all
 	// four patterns of handshake.
@@ -4659,6 +4677,31 @@
 		},
 		flags: []string{"-async"},
 	})
+
+	// Test the timeout schedule when a shorter initial timeout duration is set.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "DTLS-Retransmit-Short-Client",
+		config: Config{
+			Bugs: ProtocolBugs{
+				TimeoutSchedule: shortTimeouts[:len(shortTimeouts)-1],
+			},
+		},
+		resumeSession: true,
+		flags:         []string{"-async", "-initial-timeout-duration-ms", "250"},
+	})
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		testType: serverTest,
+		name:     "DTLS-Retransmit-Short-Server",
+		config: Config{
+			Bugs: ProtocolBugs{
+				TimeoutSchedule: shortTimeouts[:len(shortTimeouts)-1],
+			},
+		},
+		resumeSession: true,
+		flags:         []string{"-async", "-initial-timeout-duration-ms", "250"},
+	})
 }
 
 func addExportKeyingMaterialTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 67a017d..81b34d3 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -148,6 +148,7 @@
     &TestConfig::expect_server_key_exchange_hash },
   { "-expect-key-exchange-info",
     &TestConfig::expect_key_exchange_info },
+  { "-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms },
 };
 
 }  // namespace
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index fe117d8..919fc29 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -104,6 +104,7 @@
   bool use_sparse_dh_prime = false;
   int expect_key_exchange_info = 0;
   bool use_old_client_cert_callback = false;
+  int initial_timeout_duration_ms = 0;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);