Add initial DTLS tests.

Change-Id: I7407261bdb2d788c879f2e67e617a615d9ff8f8b
Reviewed-on: https://boringssl-review.googlesource.com/1505
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/CMakeLists.txt b/ssl/test/CMakeLists.txt
index 5cb7e80..27d9596 100644
--- a/ssl/test/CMakeLists.txt
+++ b/ssl/test/CMakeLists.txt
@@ -5,6 +5,7 @@
 
 	async_bio.cc
 	bssl_shim.cc
+	packeted_bio.cc
 	test_config.cc
 )
 
diff --git a/ssl/test/async_bio.cc b/ssl/test/async_bio.cc
index f30f412..4861579 100644
--- a/ssl/test/async_bio.cc
+++ b/ssl/test/async_bio.cc
@@ -22,6 +22,7 @@
 extern const BIO_METHOD async_bio_method;
 
 struct async_bio {
+  bool datagram;
   size_t read_quota;
   size_t write_quota;
 };
@@ -39,6 +40,12 @@
     return 0;
   }
 
+  if (a->datagram) {
+    // Perform writes synchronously; the DTLS implementation drops any packets
+    // that failed to send.
+    return BIO_write(bio->next_bio, in, inl);
+  }
+
   BIO_clear_retry_flags(bio);
 
   if (a->write_quota == 0) {
@@ -47,7 +54,7 @@
     return -1;
   }
 
-  if ((size_t)inl > a->write_quota) {
+  if (!a->datagram && (size_t)inl > a->write_quota) {
     inl = a->write_quota;
   }
   int ret = BIO_write(bio->next_bio, in, inl);
@@ -73,14 +80,14 @@
     return -1;
   }
 
-  if ((size_t)outl > a->read_quota) {
+  if (!a->datagram && (size_t)outl > a->read_quota) {
     outl = a->read_quota;
   }
   int ret = BIO_read(bio->next_bio, out, outl);
   if (ret <= 0) {
     BIO_copy_next_retry(bio);
   } else {
-    a->read_quota -= ret;
+    a->read_quota -= (a->datagram ? 1 : ret);
   }
   return ret;
 }
@@ -144,18 +151,27 @@
   return BIO_new(&async_bio_method);
 }
 
-void async_bio_allow_read(BIO *bio, size_t bytes) {
-  async_bio *a = get_data(bio);
-  if (a == NULL) {
-    return;
+BIO *async_bio_create_datagram() {
+  BIO *ret = BIO_new(&async_bio_method);
+  if (!ret) {
+    return NULL;
   }
-  a->read_quota += bytes;
+  get_data(ret)->datagram = true;
+  return ret;
 }
 
-void async_bio_allow_write(BIO *bio, size_t bytes) {
+void async_bio_allow_read(BIO *bio, size_t count) {
   async_bio *a = get_data(bio);
   if (a == NULL) {
     return;
   }
-  a->write_quota += bytes;
+  a->read_quota += count;
+}
+
+void async_bio_allow_write(BIO *bio, size_t count) {
+  async_bio *a = get_data(bio);
+  if (a == NULL) {
+    return;
+  }
+  a->write_quota += count;
 }
diff --git a/ssl/test/async_bio.h b/ssl/test/async_bio.h
index 7bec5fe..2904036 100644
--- a/ssl/test/async_bio.h
+++ b/ssl/test/async_bio.h
@@ -18,18 +18,23 @@
 #include <openssl/bio.h>
 
 
-// async_bio_create creates a filter BIO for testing asynchronous
-// state machines. Reads and writes will fail and return EAGAIN unless
-// explicitly allowed. Each async BIO has a read quota and a write
-// quota. Initially both are zero. As each is incremented, bytes are
-// allowed to flow through the BIO.
+// async_bio_create creates a filter BIO for testing asynchronous state
+// machines which consume a stream socket. Reads and writes will fail
+// and return EAGAIN unless explicitly allowed. Each async BIO has a
+// read quota and a write quota. Initially both are zero. As each is
+// incremented, bytes are allowed to flow through the BIO.
 BIO *async_bio_create();
 
-// async_bio_allow_read increments |bio|'s read quota by |bytes|.
-void async_bio_allow_read(BIO *bio, size_t bytes);
+// async_bio_create_datagram creates a filter BIO for testing for
+// asynchronous state machines which consume datagram sockets. The read
+// and write quota count in packets rather than bytes.
+BIO *async_bio_create_datagram();
 
-// async_bio_allow_write increments |bio|'s write quota by |bytes|.
-void async_bio_allow_write(BIO *bio, size_t bytes);
+// async_bio_allow_read increments |bio|'s read quota by |count|.
+void async_bio_allow_read(BIO *bio, size_t count);
+
+// async_bio_allow_write increments |bio|'s write quota by |count|.
+void async_bio_allow_write(BIO *bio, size_t count);
 
 
 #endif  // HEADER_ASYNC_BIO
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 19c5f83..4f26bfb 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -29,6 +29,7 @@
 #include <openssl/ssl.h>
 
 #include "async_bio.h"
+#include "packeted_bio.h"
 #include "test_config.h"
 
 static int usage(const char *program) {
@@ -124,16 +125,61 @@
   return SSL_TLSEXT_ERR_OK;
 }
 
+static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, unsigned *cookie_len) {
+  *cookie_len = 32;
+  memset(cookie, 42, *cookie_len);
+  return 1;
+}
+
+static int cookie_verify_callback(SSL *ssl, uint8_t *cookie, unsigned cookie_len) {
+  if (cookie_len != 32) {
+    fprintf(stderr, "Cookie length mismatch.\n");
+    return 0;
+  }
+  for (size_t i = 0; i < cookie_len; i++) {
+    if (cookie[i] != 42) {
+      fprintf(stderr, "Cookie mismatch.\n");
+      return 0;
+    }
+  }
+  return 1;
+}
+
 static SSL_CTX *setup_ctx(const TestConfig *config) {
   SSL_CTX *ssl_ctx = NULL;
   DH *dh = NULL;
 
-  ssl_ctx = SSL_CTX_new(
-      config->is_server ? SSLv23_server_method() : SSLv23_client_method());
+  const SSL_METHOD *method;
+  if (config->is_dtls) {
+    // TODO(davidben): Get DTLS 1.2 working and test the version negotiation
+    // codepath. This doesn't currently work because
+    // - Session resumption is broken: https://crbug.com/403378
+    // - DTLS hasn't been updated for EVP_AEAD.
+    if (config->is_server) {
+      method = DTLSv1_server_method();
+    } else {
+      method = DTLSv1_client_method();
+    }
+  } else {
+    if (config->is_server) {
+      method = SSLv23_server_method();
+    } else {
+      method = SSLv23_client_method();
+    }
+  }
+  ssl_ctx = SSL_CTX_new(method);
   if (ssl_ctx == NULL) {
     goto err;
   }
 
+  if (config->is_dtls) {
+    // DTLS needs read-ahead to function on a datagram BIO.
+    //
+    // TODO(davidben): this should not be necessary. DTLS code should only
+    // expect a datagram BIO.
+    SSL_CTX_set_read_ahead(ssl_ctx, 1);
+  }
+
   if (!SSL_CTX_set_ecdh_auto(ssl_ctx, 1)) {
     goto err;
   }
@@ -156,6 +202,9 @@
   SSL_CTX_set_next_proto_select_cb(
       ssl_ctx, next_proto_select_callback, NULL);
 
+  SSL_CTX_set_cookie_generate_cb(ssl_ctx, cookie_generate_callback);
+  SSL_CTX_set_cookie_verify_cb(ssl_ctx, cookie_verify_callback);
+
   DH_free(dh);
   return ssl_ctx;
 
@@ -248,14 +297,23 @@
   if (config->no_ssl3) {
     SSL_set_options(ssl, SSL_OP_NO_SSLv3);
   }
+  if (config->cookie_exchange) {
+    SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE);
+  }
 
   BIO *bio = BIO_new_fd(fd, 1 /* take ownership */);
   if (bio == NULL) {
     BIO_print_errors_fp(stdout);
     return 1;
   }
+  if (config->is_dtls) {
+    BIO *packeted = packeted_bio_create();
+    BIO_push(packeted, bio);
+    bio = packeted;
+  }
   if (config->async) {
-    BIO *async = async_bio_create();
+    BIO *async =
+        config->is_dtls ? async_bio_create_datagram() : async_bio_create();
     BIO_push(async, bio);
     bio = async;
   }
@@ -329,6 +387,10 @@
   }
 
   if (config->write_different_record_sizes) {
+    if (config->is_dtls) {
+      fprintf(stderr, "write_different_record_sizes not supported for DTLS\n");
+      return 6;
+    }
     // This mode writes a number of different record sizes in an attempt to
     // trip up the CBC record splitting code.
     uint8_t buf[32769];
diff --git a/ssl/test/packeted_bio.cc b/ssl/test/packeted_bio.cc
new file mode 100644
index 0000000..629d6c5
--- /dev/null
+++ b/ssl/test/packeted_bio.cc
@@ -0,0 +1,130 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "packeted_bio.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <openssl/mem.h>
+
+namespace {
+
+extern const BIO_METHOD packeted_bio_method;
+
+static int packeted_write(BIO *bio, const char *in, int inl) {
+  if (bio->next_bio == NULL) {
+    return 0;
+  }
+
+  BIO_clear_retry_flags(bio);
+
+  // Write the length prefix.
+  uint8_t len_bytes[4];
+  len_bytes[0] = (inl >> 24) & 0xff;
+  len_bytes[1] = (inl >> 16) & 0xff;
+  len_bytes[2] = (inl >> 8) & 0xff;
+  len_bytes[3] = inl & 0xff;
+  int ret = BIO_write(bio->next_bio, len_bytes, sizeof(len_bytes));
+  if (ret <= 0) {
+    BIO_copy_next_retry(bio);
+    return ret;
+  }
+
+  // Write the buffer. BIOs for which this operation fails are not supported.
+  ret = BIO_write(bio->next_bio, in, inl);
+  assert(ret == inl);
+  return ret;
+}
+
+static int packeted_read(BIO *bio, char *out, int outl) {
+  if (bio->next_bio == NULL) {
+    return 0;
+  }
+
+  BIO_clear_retry_flags(bio);
+
+  // Read the length prefix.
+  uint8_t len_bytes[4];
+  int ret = BIO_read(bio->next_bio, &len_bytes, sizeof(len_bytes));
+  if (ret <= 0) {
+    BIO_copy_next_retry(bio);
+    return ret;
+  }
+  // BIOs for which a partial length comes back are not supported.
+  assert(ret == 4);
+
+  uint32_t len = (len_bytes[0] << 24) | (len_bytes[1] << 16) |
+      (len_bytes[2] << 8) | len_bytes[3];
+  char *buf = (char *)OPENSSL_malloc(len);
+  assert(buf != NULL);
+  ret = BIO_read(bio->next_bio, buf, len);
+  assert(ret == (int)len);
+
+  if (outl > (int)len) {
+    outl = len;
+  }
+  memcpy(out, buf, outl);
+  OPENSSL_free(buf);
+  return outl;
+}
+
+static long packeted_ctrl(BIO *bio, int cmd, long num, void *ptr) {
+  if (bio->next_bio == NULL) {
+    return 0;
+  }
+  BIO_clear_retry_flags(bio);
+  int ret = BIO_ctrl(bio->next_bio, cmd, num, ptr);
+  BIO_copy_next_retry(bio);
+  return ret;
+}
+
+static int packeted_new(BIO *bio) {
+  bio->init = 1;
+  return 1;
+}
+
+static int packeted_free(BIO *bio) {
+  if (bio == NULL) {
+    return 0;
+  }
+
+  bio->init = 0;
+  return 1;
+}
+
+static long packeted_callback_ctrl(BIO *bio, int cmd, bio_info_cb fp) {
+  if (bio->next_bio == NULL) {
+    return 0;
+  }
+  return BIO_callback_ctrl(bio->next_bio, cmd, fp);
+}
+
+const BIO_METHOD packeted_bio_method = {
+  BIO_TYPE_FILTER,
+  "packeted bio",
+  packeted_write,
+  packeted_read,
+  NULL /* puts */,
+  NULL /* gets */,
+  packeted_ctrl,
+  packeted_new,
+  packeted_free,
+  packeted_callback_ctrl,
+};
+
+}  // namespace
+
+BIO *packeted_bio_create() {
+  return BIO_new(&packeted_bio_method);
+}
diff --git a/ssl/test/packeted_bio.h b/ssl/test/packeted_bio.h
new file mode 100644
index 0000000..384bd64
--- /dev/null
+++ b/ssl/test/packeted_bio.h
@@ -0,0 +1,32 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef HEADER_PACKETED_BIO
+#define HEADER_PACKETED_BIO
+
+#include <openssl/bio.h>
+
+
+// packeted_bio_create creates a filter BIO for testing protocols which expect
+// datagram BIOs. It implements a reliable datagram socket and reads and writes
+// packets by prefixing each packet with a big-endian 32-bit length. It must be
+// layered over a reliable blocking stream BIO.
+//
+// Note: packeted_bio_create exists because a SOCK_DGRAM socketpair on OS X is
+// does not block the caller, unlike on Linux. Writes simply fail with
+// ENOBUFS. POSIX also does not guarantee that such sockets are reliable.
+BIO *packeted_bio_create();
+
+
+#endif  // HEADER_PACKETED_BIO
diff --git a/ssl/test/runner/packet_adapter.go b/ssl/test/runner/packet_adapter.go
new file mode 100644
index 0000000..b2f2765
--- /dev/null
+++ b/ssl/test/runner/packet_adapter.go
@@ -0,0 +1,50 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/binary"
+	"errors"
+	"net"
+)
+
+type packetAdaptor struct {
+	net.Conn
+}
+
+func newPacketAdaptor(conn net.Conn) net.Conn {
+	return &packetAdaptor{conn}
+}
+
+func (p *packetAdaptor) Read(b []byte) (int, error) {
+	var length uint32
+	if err := binary.Read(p.Conn, binary.BigEndian, &length); err != nil {
+		return 0, err
+	}
+	out := make([]byte, length)
+	n, err := p.Conn.Read(out)
+	if err != nil {
+		return 0, err
+	}
+	if n != int(length) {
+		return 0, errors.New("internal error: length mismatch!")
+	}
+	return copy(b, out), nil
+}
+
+func (p *packetAdaptor) Write(b []byte) (int, error) {
+	length := uint32(len(b))
+	if err := binary.Write(p.Conn, binary.BigEndian, length); err != nil {
+		return 0, err
+	}
+	n, err := p.Conn.Write(b)
+	if err != nil {
+		return 0, err
+	}
+	if n != len(b) {
+		return 0, errors.New("internal error: length mismatch!")
+	}
+	return len(b), nil
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4fd5a1d..1d44f99 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -63,8 +63,16 @@
 	serverTest
 )
 
+type protocol int
+
+const (
+	tls protocol = iota
+	dtls
+)
+
 type testCase struct {
 	testType      testType
+	protocol      protocol
 	name          string
 	config        Config
 	shouldFail    bool
@@ -341,18 +349,6 @@
 		expectedError: ":CCS_RECEIVED_EARLY:",
 	},
 	{
-		name: "FalseStart-SessionTicketsDisabled",
-		config: Config{
-			CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos:             []string{"foo"},
-			SessionTicketsDisabled: true,
-		},
-		flags: []string{
-			"-false-start",
-			"-select-next-proto", "foo",
-		},
-	},
-	{
 		testType: serverTest,
 		name:     "FallbackSCSV",
 		config: Config{
@@ -454,16 +450,30 @@
 }
 
 func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
-	if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
-		return err
+	if test.protocol == dtls {
+		conn = newPacketAdaptor(conn)
+	}
+
+	if test.sendPrefix != "" {
+		if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
+			return err
+		}
 	}
 
 	var tlsConn *Conn
 	if test.testType == clientTest {
-		tlsConn = Server(conn, config)
+		if test.protocol == dtls {
+			tlsConn = DTLSServer(conn, config)
+		} else {
+			tlsConn = Server(conn, config)
+		}
 	} else {
 		config.InsecureSkipVerify = true
-		tlsConn = Client(conn, config)
+		if test.protocol == dtls {
+			tlsConn = DTLSClient(conn, config)
+		} else {
+			tlsConn = Client(conn, config)
+		}
 	}
 
 	if err := tlsConn.Handshake(); err != nil {
@@ -475,6 +485,9 @@
 	}
 
 	if messageLen < 0 {
+		if test.protocol == dtls {
+			return fmt.Errorf("messageLen < 0 not supported for DTLS tests")
+		}
 		// Read until EOF.
 		_, err := io.Copy(ioutil.Discard, tlsConn)
 		return err
@@ -490,9 +503,21 @@
 	tlsConn.Write(testMessage)
 
 	buf := make([]byte, len(testMessage))
-	_, err := io.ReadFull(tlsConn, buf)
-	if err != nil {
-		return err
+	if test.protocol == dtls {
+		bufTmp := make([]byte, len(buf)+1)
+		n, err := tlsConn.Read(bufTmp)
+		if err != nil {
+			return err
+		}
+		if n != len(buf) {
+			return fmt.Errorf("bad reply; length mismatch (%d vs %d)", n, len(buf))
+		}
+		copy(buf, bufTmp)
+	} else {
+		_, err := io.ReadFull(tlsConn, buf)
+		if err != nil {
+			return err
+		}
 	}
 
 	for i, v := range buf {
@@ -568,6 +593,10 @@
 		}
 	}
 
+	if test.protocol == dtls {
+		flags = append(flags, "-dtls")
+	}
+
 	if test.resumeSession {
 		flags = append(flags, "-resume")
 	}
@@ -746,6 +775,36 @@
 					resumeSession: resumeSession,
 				})
 			}
+
+			// TODO(davidben): Fix DTLS 1.2 support and test that.
+			if ver.version == VersionTLS10 && strings.Index(suite.name, "RC4") == -1 {
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: dtls,
+					name:     "D" + ver.name + "-" + suite.name + "-client",
+					config: Config{
+						MinVersion:   ver.version,
+						MaxVersion:   ver.version,
+						CipherSuites: []uint16{suite.id},
+						Certificates: []Certificate{cert},
+					},
+					resumeSession: resumeSession,
+				})
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: dtls,
+					name:     "D" + ver.name + "-" + suite.name + "-server",
+					config: Config{
+						MinVersion:   ver.version,
+						MaxVersion:   ver.version,
+						CipherSuites: []uint16{suite.id},
+						Certificates: []Certificate{cert},
+					},
+					certFile:      certFile,
+					keyFile:       keyFile,
+					resumeSession: resumeSession,
+				})
+			}
 		}
 	}
 }
@@ -918,15 +977,18 @@
 // Adds tests that try to cover the range of the handshake state machine, under
 // various conditions. Some of these are redundant with other tests, but they
 // only cover the synchronous case.
-func addStateMachineCoverageTests(async bool, splitHandshake bool) {
+func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) {
 	var suffix string
 	var flags []string
 	var maxHandshakeRecordLength int
+	if protocol == dtls {
+		suffix = "-DTLS"
+	}
 	if async {
-		suffix = "-Async"
+		suffix += "-Async"
 		flags = append(flags, "-async")
 	} else {
-		suffix = "-Sync"
+		suffix += "-Sync"
 	}
 	if splitHandshake {
 		suffix += "-SplitHandshakeRecords"
@@ -935,7 +997,8 @@
 
 	// Basic handshake, with resumption. Client and server.
 	testCases = append(testCases, testCase{
-		name: "Basic-Client" + suffix,
+		protocol: protocol,
+		name:     "Basic-Client" + suffix,
 		config: Config{
 			Bugs: ProtocolBugs{
 				MaxHandshakeRecordLength: maxHandshakeRecordLength,
@@ -945,7 +1008,8 @@
 		resumeSession: true,
 	})
 	testCases = append(testCases, testCase{
-		name: "Basic-Client-RenewTicket" + suffix,
+		protocol: protocol,
+		name:     "Basic-Client-RenewTicket" + suffix,
 		config: Config{
 			Bugs: ProtocolBugs{
 				MaxHandshakeRecordLength: maxHandshakeRecordLength,
@@ -956,6 +1020,7 @@
 		resumeSession: true,
 	})
 	testCases = append(testCases, testCase{
+		protocol: protocol,
 		testType: serverTest,
 		name:     "Basic-Server" + suffix,
 		config: Config{
@@ -967,9 +1032,36 @@
 		resumeSession: true,
 	})
 
+	// TLS client auth.
+	testCases = append(testCases, testCase{
+		protocol: protocol,
+		testType: clientTest,
+		name:     "ClientAuth-Client" + suffix,
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+			ClientAuth:   RequireAnyClientCert,
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags,
+			"-cert-file", rsaCertificateFile,
+			"-key-file", rsaKeyFile),
+	})
+	testCases = append(testCases, testCase{
+		protocol: protocol,
+		testType: serverTest,
+		name:     "ClientAuth-Server" + suffix,
+		config: Config{
+			Certificates: []Certificate{rsaCertificate},
+		},
+		flags: append(flags, "-require-any-client-certificate"),
+	})
+
 	// No session ticket support; server doesn't send NewSessionTicket.
 	testCases = append(testCases, testCase{
-		name: "SessionTicketsDisabled-Client" + suffix,
+		protocol: protocol,
+		name:     "SessionTicketsDisabled-Client" + suffix,
 		config: Config{
 			SessionTicketsDisabled: true,
 			Bugs: ProtocolBugs{
@@ -979,6 +1071,7 @@
 		flags: flags,
 	})
 	testCases = append(testCases, testCase{
+		protocol: protocol,
 		testType: serverTest,
 		name:     "SessionTicketsDisabled-Server" + suffix,
 		config: Config{
@@ -990,88 +1083,108 @@
 		flags: flags,
 	})
 
-	// NPN on client and server; results in post-handshake message.
-	testCases = append(testCases, testCase{
-		name: "NPN-Client" + suffix,
-		config: Config{
-			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos:   []string{"foo"},
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+	if protocol == tls {
+		// NPN on client and server; results in post-handshake message.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			name:     "NPN-Client" + suffix,
+			config: Config{
+				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:   []string{"foo"},
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
 			},
-		},
-		flags: append(flags, "-select-next-proto", "foo"),
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "NPN-Server" + suffix,
-		config: Config{
-			NextProtos: []string{"bar"},
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			flags: append(flags, "-select-next-proto", "foo"),
+		})
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			testType: serverTest,
+			name:     "NPN-Server" + suffix,
+			config: Config{
+				NextProtos: []string{"bar"},
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
 			},
-		},
-		flags: append(flags,
-			"-advertise-npn", "\x03foo\x03bar\x03baz",
-			"-expect-next-proto", "bar"),
-	})
+			flags: append(flags,
+				"-advertise-npn", "\x03foo\x03bar\x03baz",
+				"-expect-next-proto", "bar"),
+		})
 
-	// Client does False Start and negotiates NPN.
-	testCases = append(testCases, testCase{
-		name: "FalseStart" + suffix,
-		config: Config{
-			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos:   []string{"foo"},
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+		// Client does False Start and negotiates NPN.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			name:     "FalseStart" + suffix,
+			config: Config{
+				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:   []string{"foo"},
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
 			},
-		},
-		flags: append(flags,
-			"-false-start",
-			"-select-next-proto", "foo"),
-		resumeSession: true,
-	})
+			flags: append(flags,
+				"-false-start",
+				"-select-next-proto", "foo"),
+			resumeSession: true,
+		})
 
-	// TLS client auth.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "ClientAuth-Client" + suffix,
-		config: Config{
-			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			ClientAuth:   RequireAnyClientCert,
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+		// False Start without session tickets.
+		testCases = append(testCases, testCase{
+			name: "FalseStart-SessionTicketsDisabled",
+			config: Config{
+				CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:             []string{"foo"},
+				SessionTicketsDisabled: true,
 			},
-		},
-		flags: append(flags,
-			"-cert-file", rsaCertificateFile,
-			"-key-file", rsaKeyFile),
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "ClientAuth-Server" + suffix,
-		config: Config{
-			Certificates: []Certificate{rsaCertificate},
-		},
-		flags: append(flags, "-require-any-client-certificate"),
-	})
+			flags: []string{
+				"-false-start",
+				"-select-next-proto", "foo",
+			},
+		})
 
-	// Client sends a V2ClientHello.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SendV2ClientHello" + suffix,
-		config: Config{
-			// Choose a cipher suite that does not involve
-			// elliptic curves, so no extensions are
-			// involved.
-			CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
-				SendV2ClientHello:        true,
+		// Client sends a V2ClientHello.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			testType: serverTest,
+			name:     "SendV2ClientHello" + suffix,
+			config: Config{
+				// Choose a cipher suite that does not involve
+				// elliptic curves, so no extensions are
+				// involved.
+				CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+					SendV2ClientHello:        true,
+				},
 			},
-		},
-		flags: flags,
-	})
+			flags: flags,
+		})
+	} else {
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			name:     "SkipHelloVerifyRequest" + suffix,
+			config: Config{
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+					SkipHelloVerifyRequest:   true,
+				},
+			},
+			flags: flags,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     "CookieExchange" + suffix,
+			config: Config{
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
+			},
+			flags: append(flags, "-cookie-exchange"),
+		})
+	}
 }
 
 func addVersionNegotiationTests() {
@@ -1169,7 +1282,9 @@
 	addVersionNegotiationTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
-			addStateMachineCoverageTests(async, splitHandshake)
+			for _, protocol := range []protocol{tls, dtls} {
+				addStateMachineCoverageTests(async, splitHandshake, protocol)
+			}
 		}
 	}
 
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index b25f7e6..9716227 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -34,6 +34,7 @@
 
 const BoolFlag kBoolFlags[] = {
   { "-server", &TestConfig::is_server },
+  { "-dtls", &TestConfig::is_dtls },
   { "-resume", &TestConfig::resume },
   { "-fallback-scsv", &TestConfig::fallback_scsv },
   { "-require-any-client-certificate",
@@ -48,6 +49,7 @@
   { "-no-tls11", &TestConfig::no_tls11 },
   { "-no-tls1", &TestConfig::no_tls1 },
   { "-no-ssl3", &TestConfig::no_ssl3 },
+  { "-cookie-exchange", &TestConfig::cookie_exchange },
 };
 
 const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]);
@@ -71,6 +73,7 @@
 
 TestConfig::TestConfig()
     : is_server(false),
+      is_dtls(false),
       resume(false),
       fallback_scsv(false),
       require_any_client_certificate(false),
@@ -82,7 +85,8 @@
       no_tls12(false),
       no_tls11(false),
       no_tls1(false),
-      no_ssl3(false) {
+      no_ssl3(false),
+      cookie_exchange(false) {
 }
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config) {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 558d033..34d720e 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -22,6 +22,7 @@
   TestConfig();
 
   bool is_server;
+  bool is_dtls;
   bool resume;
   bool fallback_scsv;
   std::string key_file;
@@ -41,6 +42,7 @@
   bool no_tls11;
   bool no_tls1;
   bool no_ssl3;
+  bool cookie_exchange;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);