# HG changeset patch
# User Vladimir Homutov <vl@nginx.com>
# Date 1494676066 -10800
#      Sat May 13 14:47:46 2017 +0300
# Node ID 277d16d38438fa107edb35dbbcc5476ed63890aa
# Parent  eb5d119323d8cfae94aa01367935e6f9324a623e
Stream: experimental DTLS support.

With the patch, the "listen" directive in the "stream" block now accepts
both "udp" and "ssl" directives.

The "ssl_protocols" and "proxy_ssl_protocols" directives now accepts "DTLSv1"
and "DTLSv1.2" parameters that enable support of corresponding protocols.

DTLS termination:

stream {
    # please enable debug log
    error_log logs/error.log debug;

    server {
        # add 'udp' and 'ssl' simultaneously to the listen directive
        listen 127.0.0.1:4443 udp ssl;

        # enable DTLSv1 or DTLSv1.2 or both protocols
        ssl_protocols DTLSv1;

        # setup other SSL options as usually
        ssl_certificate ...;
        ssl_certificate_key ...;

        proxy_pass ...;
    }
}

DTLS to backends:

stream {
    # please enable debug log
    error_log logs/error.log debug;

    server {
        listen 127.0.0.1:5555 udp;

        # enable SSL to proxy
        proxy_ssl on;
        # enable DTLSv1 or DTLSv1.2 or both protocols
        proxy_ssl_protocols DTLSv1;

        # setup other proxy SSL options as usually
        proxy_ssl_certificate ...;
        proxy_ssl_certificate_key ...;

        # the backend is a DTLS server
        proxy_pass 127.0.0.1:4433;
    }
}

diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h
--- a/src/event/ngx_event.h
+++ b/src/event/ngx_event.h
@@ -507,6 +507,7 @@ void ngx_event_accept(ngx_event_t *ev);
 #if !(NGX_WIN32)
 void ngx_event_recvmsg(ngx_event_t *ev);
 #endif
+ngx_int_t ngx_event_udp_accept(ngx_connection_t *c);
 ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
 u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
 
diff --git a/src/event/ngx_event_accept.c b/src/event/ngx_event_accept.c
--- a/src/event/ngx_event_accept.c
+++ b/src/event/ngx_event_accept.c
@@ -636,6 +636,81 @@ ngx_event_recvmsg(ngx_event_t *ev)
 
 
 ngx_int_t
+ngx_event_udp_accept(ngx_connection_t *c)
+{
+    int          on, rc;
+    ngx_socket_t fd;
+
+    fd = ngx_socket(c->listening->sockaddr->sa_family, SOCK_DGRAM, 0);
+    if (fd == (ngx_socket_t) -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+    if (ngx_nonblocking(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+        goto failed;
+    }
+
+    on = 1;
+    rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(int));
+    if (rc == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      "setsockopt(SO_REUSEADDR, 1) failed");
+        goto failed;
+    }
+
+#if (NGX_HAVE_REUSEPORT && NGX_FREEBSD)
+    on = 1;
+    rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &on, sizeof(int));
+    if (rc == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      "setsockopt(SO_REUSEPORT, 1) failed");
+        goto failed;
+    }
+#endif
+
+    rc = bind(fd, c->listening->sockaddr, c->listening->socklen);
+    if (-1 == rc) {
+        ngx_log_error(NGX_LOG_EMERG, c->log, ngx_socket_errno,
+                      "bind() to %V failed", &c->listening->addr_text);
+        goto failed;
+    }
+
+    if (connect(fd, c->sockaddr, c->socklen) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      "connect() failed");
+        goto failed;
+    }
+
+    c->fd = fd;
+    c->shared = 0;
+    c->recv = ngx_udp_recv;
+
+    if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+             goto failed;
+         }
+     }
+
+    return NGX_OK;
+
+failed:
+
+    if (ngx_close_socket(fd) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, c->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+
+    c->fd = (ngx_socket_t) -1;
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
 ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
 {
     if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -69,6 +69,22 @@ static void *ngx_openssl_create_conf(ngx
 static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 static void ngx_openssl_exit(ngx_cycle_t *cycle);
 
+#if defined(NGX_HAVE_DTLS)
+static int ngx_dtls_client_hmac(SSL *ssl, u_char res[EVP_MAX_MD_SIZE],
+    unsigned int *rlen);
+static int ngx_dtls_generate_cookie_cb(SSL *ssl, unsigned char *cookie,
+    unsigned int *cookie_len);
+static int ngx_dtls_verify_cookie_cb(SSL *ssl,
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    const
+#endif
+unsigned char *cookie, unsigned int cookie_len);
+static ngx_int_t ngx_dtls_handshake(ngx_connection_t *c);
+
+
+#define COOKIE_SECRET_LENGTH 32
+static u_char ngx_dtls_cookie_secret[COOKIE_SECRET_LENGTH];
+#endif
 
 static ngx_command_t  ngx_openssl_commands[] = {
 
@@ -232,13 +248,71 @@ ngx_ssl_init(ngx_log_t *log)
 ngx_int_t
 ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
 {
-    ssl->ctx = SSL_CTX_new(SSLv23_method());
+    if (protocols & NGX_SSL_DTLSv1 || protocols & NGX_SSL_DTLSv1_2) {
+
+#if defined(NGX_HAVE_DTLS)
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+        if (protocols & NGX_SSL_DTLSv1_2) {
+
+            /* DTLS 1.2 is only supported since 1.0.2 */
+
+            /* DTLSv1_x_method()  functions are deprecated in 1.1.0 */
+
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+
+            /* ancient ... 1.0.2 */
+            ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "DTLSv1.2 is not supported by "
+                          "the used version of OpenSSL");
+            return NGX_ERROR;
+
+#else
+            /* 1.0.2 ... 1.1 */
+            ssl->ctx = SSL_CTX_new(DTLSv1_2_method());
+#endif
+        }
+
+        /* note: either 1.2 or 1.1 methods may be initialized, not both,
+         * preferred is 1.2 if both specified in ssl_protocols
+         */
+
+        if (protocols & NGX_SSL_DTLSv1 && ssl->ctx == NULL) {
+            ssl->ctx = SSL_CTX_new(DTLSv1_method());
+        }
+#else
+        ssl->ctx = SSL_CTX_new(DTLS_method());
+#endif
+
+#else
+        ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "OpenSSL is built without DTLS support");
+        return NGX_ERROR;
+#endif
+
+    } else {
+        ssl->ctx = SSL_CTX_new(SSLv23_method());
+    }
 
     if (ssl->ctx == NULL) {
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed");
         return NGX_ERROR;
     }
 
+#if defined(NGX_HAVE_DTLS)
+    if (protocols & NGX_SSL_DTLSv1 || protocols & NGX_SSL_DTLSv1_2) {
+
+        SSL_CTX_set_cookie_generate_cb(ssl->ctx, ngx_dtls_generate_cookie_cb);
+        SSL_CTX_set_cookie_verify_cb(ssl->ctx, ngx_dtls_verify_cookie_cb);
+
+        /* TODO: probably this should be rotated regularly */
+        if (!RAND_bytes(ngx_dtls_cookie_secret, COOKIE_SECRET_LENGTH)) {
+            return NGX_ERROR;
+        }
+    }
+#endif
+
     if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                       "SSL_CTX_set_ex_data() failed");
@@ -1190,6 +1264,7 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl
 
     if (flags & NGX_SSL_CLIENT) {
         SSL_set_connect_state(sc->connection);
+        sc->client = 1;
 
     } else {
         SSL_set_accept_state(sc->connection);
@@ -1226,6 +1301,19 @@ ngx_ssl_handshake(ngx_connection_t *c)
     int        n, sslerr;
     ngx_err_t  err;
 
+#if defined(NGX_HAVE_DTLS)
+    ngx_int_t  rc;
+
+    if (c->type == SOCK_DGRAM && !c->ssl->client
+        && !c->ssl->dtls_cookie_accepted)
+    {
+        rc = ngx_dtls_handshake(c);
+        if (rc != NGX_OK) {
+            return rc;
+        }
+    }
+#endif
+
     ngx_ssl_clear_error(c->log);
 
     n = SSL_do_handshake(c->ssl->connection);
@@ -1327,6 +1415,17 @@ ngx_ssl_handshake(ngx_connection_t *c)
             return NGX_ERROR;
         }
 
+        if (c->ssl->bio_is_mem) {
+            SSL_set_rfd(c->ssl->connection, c->fd);
+            c->ssl->bio_is_mem = 0;
+
+            /* buffer is consumed by openssl, we don't want to proxy it */
+            c->buffer->pos = c->buffer->last;
+
+            /* continue with handshake with socket */
+            return ngx_ssl_handshake(c);
+        }
+
         return NGX_AGAIN;
     }
 
@@ -1390,6 +1489,215 @@ ngx_ssl_handshake_handler(ngx_event_t *e
 }
 
 
+#if defined(NGX_HAVE_DTLS)
+
+/*
+ * RFC 6347, 4.2.1:
+ *
+ * When responding to a HelloVerifyRequest, the client MUST use the same
+ * parameter values (version, random, session_id, cipher_suites,
+ * compression_method) as it did in the original ClientHello.  The
+ * server SHOULD use those values to generate its cookie and verify that
+ * they are correct upon cookie receipt.
+ */
+
+static int
+ngx_dtls_client_hmac(SSL *ssl, u_char res[EVP_MAX_MD_SIZE], unsigned int *rlen)
+{
+    u_char            *p;
+    size_t             len;
+    ngx_connection_t  *c;
+
+    u_char             buffer[64];
+
+    c = ngx_ssl_get_connection(ssl);
+
+    p = buffer;
+
+    p = ngx_cpymem(p, c->addr_text.data, c->addr_text.len);
+    p = ngx_sprintf(p, "%d", ngx_inet_get_port(c->sockaddr));
+
+    len = p - buffer;
+
+    HMAC(EVP_sha1(), (const void*) ngx_dtls_cookie_secret,
+         COOKIE_SECRET_LENGTH, (const u_char*) buffer, len, res, rlen);
+
+    return NGX_OK;
+}
+
+
+static int
+ngx_dtls_generate_cookie_cb(SSL *ssl, unsigned char *cookie,
+    unsigned int *cookie_len)
+{
+    unsigned int  rlen;
+    u_char        res[EVP_MAX_MD_SIZE];
+
+    if (ngx_dtls_client_hmac(ssl, res, &rlen) != NGX_OK) {
+        return 0;
+    }
+
+    ngx_memcpy(cookie, res, rlen);
+    *cookie_len = rlen;
+
+    return 1;
+}
+
+
+static int
+ngx_dtls_verify_cookie_cb(SSL *ssl,
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    const
+#endif
+    unsigned char *cookie, unsigned int cookie_len)
+{
+    unsigned int  rlen;
+    u_char        res[EVP_MAX_MD_SIZE];
+
+    if (ngx_dtls_client_hmac(ssl, res, &rlen) != NGX_OK) {
+        return 0;
+    }
+
+    if (cookie_len == rlen && ngx_memcmp(res, cookie, rlen) == 0) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_int_t
+ngx_dtls_handshake(ngx_connection_t *c)
+{
+    int               n, rd;
+    BIO              *rbio, *wbio;
+    ngx_int_t         rc;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    BIO_ADDR         *peer;
+#else
+    SSL              *ssl;
+    struct sockaddr  *peer;
+#endif
+
+    wbio = BIO_new(BIO_s_mem());
+    if (wbio == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, c->log, 0, "BIO_new");
+        return NGX_ERROR;
+    }
+
+    rbio = BIO_new_mem_buf(c->buffer->pos, c->buffer->last - c->buffer->pos);
+    if (rbio == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, c->log, 0, "BIO_new_mem_buf");
+        return NGX_ERROR;
+    }
+
+    BIO_set_mem_eof_return(rbio, -1);
+
+    SSL_set_bio(c->ssl->connection, rbio, wbio);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+
+    peer = BIO_ADDR_new();
+
+    if (peer == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, c->log, 0, "BIO_ADDR_new");
+        return NGX_ERROR;
+    }
+
+#else
+
+    peer = ngx_palloc(c->pool, c->socklen);
+    if (peer == NULL) {
+        return NGX_ERROR;
+    }
+
+    ssl = c->ssl->connection;
+    SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE);
+
+#endif
+
+    rc = DTLSv1_listen(c->ssl->connection, peer);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    BIO_ADDR_free(peer);
+#endif
+
+    if (rc < 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, c->log, 0,
+                      "DTLSv1_listen error %d", rc);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+        return NGX_ERROR;
+#else
+        /* no way to distinguish SSL error from NBIO */
+        if (ERR_peek_last_error() != 0) {
+            return NGX_ERROR;
+        }
+
+        /* assume -1 comes from NBIO and act accordingly */
+        rc = 0;
+#endif
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "DTLSv1_listen: %i", rc);
+
+    if (rc == 0) {
+        /* non-blocking IO: need to send hello-verify request */
+        n = BIO_ctrl_pending(wbio);
+        if (n > 0) {
+            /* openssl provided some data to send */
+            rd = BIO_read(wbio, c->buffer->start, n);
+            if (rd != n) {
+                ngx_log_error(NGX_LOG_EMERG, c->log, 0, "DTLS BIO_read failed");
+                return NGX_ERROR;
+            }
+
+            rc = ngx_udp_send(c, c->buffer->start, n);
+            if (rc != n) {
+                return NGX_ERROR;
+            }
+
+            /* ok, we sent response, session is over,
+             * waiting for helllo with cookie
+             */
+
+        } else {
+            /* renegotiation or other unexpected result */
+            return NGX_ERROR;
+        }
+
+        /* this session is no longer required, new will be created */
+
+        return NGX_ABORT; /* drop this session*/
+    }
+
+    /* rc >= 1: client with a valid cookie */
+
+    /* DTLSv1_listen PEEK'ed the data, SSL_accept() needs to read from start */
+    if (BIO_reset(rbio) != 1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "BIO_reset");
+        return NGX_ERROR;
+    }
+
+    if (c->shared) {
+        if (ngx_event_udp_accept(c) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    /* to be reset by handshake when mem buf content is consumed */
+    c->ssl->bio_is_mem = 1;
+
+    /* write BIO is real socket, to start sending server hello */
+    SSL_set_wfd(c->ssl->connection, c->fd);
+
+    c->ssl->dtls_cookie_accepted = 1;
+
+    return NGX_OK;
+}
+
+#endif
+
 ssize_t
 ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit)
 {
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -49,6 +49,9 @@
 
 #endif
 
+#if !defined(OPENSSL_NO_DTLS) && OPENSSL_VERSION_NUMBER >= 0x009080dfL
+#define NGX_HAVE_DTLS
+#endif
 
 #define ngx_ssl_session_t       SSL_SESSION
 #define ngx_ssl_conn_t          SSL
@@ -85,6 +88,9 @@ struct ngx_ssl_connection_s {
     unsigned                    no_wait_shutdown:1;
     unsigned                    no_send_shutdown:1;
     unsigned                    handshake_buffer_set:1;
+    unsigned                    dtls_cookie_accepted:1;
+    unsigned                    bio_is_mem:1;
+    unsigned                    client:1;
 };
 
 
@@ -137,6 +143,8 @@ typedef struct {
 #define NGX_SSL_TLSv1_1  0x0010
 #define NGX_SSL_TLSv1_2  0x0020
 #define NGX_SSL_TLSv1_3  0x0040
+#define NGX_SSL_DTLSv1   0x0080
+#define NGX_SSL_DTLSv1_2 0x0100
 
 
 #define NGX_SSL_BUFFER   1
diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c
--- a/src/stream/ngx_stream_core_module.c
+++ b/src/stream/ngx_stream_core_module.c
@@ -862,12 +862,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, n
             return "\"backlog\" parameter is incompatible with \"udp\"";
         }
 
-#if (NGX_STREAM_SSL)
-        if (ls->ssl) {
-            return "\"ssl\" parameter is incompatible with \"udp\"";
-        }
-#endif
-
         if (ls->so_keepalive) {
             return "\"so_keepalive\" parameter is incompatible with \"udp\"";
         }
diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c
--- a/src/stream/ngx_stream_proxy_module.c
+++ b/src/stream/ngx_stream_proxy_module.c
@@ -27,6 +27,7 @@ typedef struct {
     size_t                           upload_rate;
     size_t                           download_rate;
     ngx_uint_t                       responses;
+    ngx_uint_t                       requests;
     ngx_uint_t                       next_upstream_tries;
     ngx_flag_t                       next_upstream;
     ngx_flag_t                       proxy_protocol;
@@ -95,6 +96,8 @@ static void ngx_stream_proxy_ssl_handsha
 static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s);
 static ngx_int_t ngx_stream_proxy_set_ssl(ngx_conf_t *cf,
     ngx_stream_proxy_srv_conf_t *pscf);
+static char *ngx_stream_proxy_set_ssl_protocols(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
 
 
 static ngx_conf_bitmask_t  ngx_stream_proxy_ssl_protocols[] = {
@@ -104,6 +107,8 @@ static ngx_conf_bitmask_t  ngx_stream_pr
     { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
     { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
     { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_string("DTLSv1"),   NGX_SSL_DTLSv1 },
+    { ngx_string("DTLSv1.2"), NGX_SSL_DTLSv1_2 },
     { ngx_null_string, 0 }
 };
 
@@ -191,6 +196,13 @@ static ngx_command_t  ngx_stream_proxy_c
       offsetof(ngx_stream_proxy_srv_conf_t, responses),
       NULL },
 
+    { ngx_string("proxy_requests"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      offsetof(ngx_stream_proxy_srv_conf_t, requests),
+      NULL },
+
     { ngx_string("proxy_next_upstream"),
       NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
       ngx_conf_set_flag_slot,
@@ -237,7 +249,7 @@ static ngx_command_t  ngx_stream_proxy_c
 
     { ngx_string("proxy_ssl_protocols"),
       NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
+      ngx_stream_proxy_set_ssl_protocols,
       NGX_STREAM_SRV_CONF_OFFSET,
       offsetof(ngx_stream_proxy_srv_conf_t, ssl_protocols),
       &ngx_stream_proxy_ssl_protocols },
@@ -398,7 +410,9 @@ ngx_stream_proxy_handler(ngx_stream_sess
         return;
     }
 
-    if (c->type == SOCK_STREAM) {
+    if (c->type == SOCK_STREAM || (c->type == SOCK_DGRAM && c->ssl)
+        || (c->shared && pscf->requests))
+    {
         p = ngx_pnalloc(c->pool, pscf->buffer_size);
         if (p == NULL) {
             ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
@@ -422,6 +436,13 @@ ngx_stream_proxy_handler(ngx_stream_sess
         }
     }
 
+    if (c->shared && pscf->requests) {
+        if (ngx_event_udp_accept(c) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
     if (u->resolved == NULL) {
 
         uscf = pscf->upstream;
@@ -767,14 +788,16 @@ ngx_stream_proxy_init_upstream(ngx_strea
 
 #if (NGX_STREAM_SSL)
 
-    if (pc->type == SOCK_STREAM && pscf->ssl) {
-
-        if (u->proxy_protocol) {
-            if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) {
-                return;
+    if (pscf->ssl) {
+
+        if (pc->type == SOCK_STREAM) {
+            if (u->proxy_protocol) {
+                if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) {
+                    return;
+                }
+
+                u->proxy_protocol = 0;
             }
-
-            u->proxy_protocol = 0;
         }
 
         if (pc->ssl == NULL) {
@@ -1057,6 +1080,8 @@ static void
 ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc)
 {
     long                          rc;
+    u_char                       *p;
+    ngx_connection_t             *c;
     ngx_stream_session_t         *s;
     ngx_stream_upstream_t        *u;
     ngx_stream_proxy_srv_conf_t  *pscf;
@@ -1096,6 +1121,29 @@ ngx_stream_proxy_ssl_handshake(ngx_conne
             ngx_del_timer(pc->write);
         }
 
+        c = s->connection;
+
+        if (c->shared && pscf->requests) {
+
+            if (ngx_event_udp_accept(c) != NGX_OK) {
+                ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            p = ngx_pnalloc(c->pool, pscf->buffer_size);
+            if (p == NULL) {
+                ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            u = s->upstream;
+
+            u->downstream_buf.start = p;
+            u->downstream_buf.end = p + pscf->buffer_size;
+            u->downstream_buf.pos = p;
+            u->downstream_buf.last = p;
+        }
+
         ngx_stream_proxy_init_upstream(s);
 
         return;
@@ -1847,6 +1895,7 @@ ngx_stream_proxy_create_srv_conf(ngx_con
     conf->upload_rate = NGX_CONF_UNSET_SIZE;
     conf->download_rate = NGX_CONF_UNSET_SIZE;
     conf->responses = NGX_CONF_UNSET_UINT;
+    conf->requests = NGX_CONF_UNSET_UINT;
     conf->next_upstream_tries = NGX_CONF_UNSET_UINT;
     conf->next_upstream = NGX_CONF_UNSET;
     conf->proxy_protocol = NGX_CONF_UNSET;
@@ -1892,6 +1941,9 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf
     ngx_conf_merge_uint_value(conf->responses,
                               prev->responses, NGX_MAX_INT32_VALUE);
 
+    ngx_conf_merge_uint_value(conf->requests,
+                              prev->requests, NGX_MAX_INT32_VALUE);
+
     ngx_conf_merge_uint_value(conf->next_upstream_tries,
                               prev->next_upstream_tries, 0);
 
@@ -2018,6 +2070,34 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf,
     return NGX_OK;
 }
 
+
+static
+char *ngx_stream_proxy_set_ssl_protocols(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    ngx_stream_proxy_srv_conf_t *pscf = conf;
+
+    char  *rv;
+
+    rv = ngx_conf_set_bitmask_slot(cf, cmd, conf);
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    /* DTLS protocol requires corresponding TLS version to be set */
+
+    if (pscf->ssl_protocols & NGX_SSL_DTLSv1) {
+        pscf->ssl_protocols |= NGX_SSL_TLSv1;
+    }
+
+    if (pscf->ssl_protocols & NGX_SSL_DTLSv1_2) {
+        pscf->ssl_protocols |= NGX_SSL_TLSv1_2;
+    }
+
+    return NGX_CONF_OK;
+}
+
 #endif
 
 
diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c
--- a/src/stream/ngx_stream_ssl_module.c
+++ b/src/stream/ngx_stream_ssl_module.c
@@ -34,6 +34,8 @@ static char *ngx_stream_ssl_merge_conf(n
 
 static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_stream_set_ssl_protocols(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
 static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf);
@@ -46,6 +48,8 @@ static ngx_conf_bitmask_t  ngx_stream_ss
     { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
     { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
     { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 },
+    { ngx_string("DTLSv1"),   NGX_SSL_DTLSv1 },
+    { ngx_string("DTLSv1.2"), NGX_SSL_DTLSv1_2 },
     { ngx_null_string, 0 }
 };
 
@@ -105,7 +109,7 @@ static ngx_command_t  ngx_stream_ssl_com
 
     { ngx_string("ssl_protocols"),
       NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
+      ngx_stream_set_ssl_protocols,
       NGX_STREAM_SRV_CONF_OFFSET,
       offsetof(ngx_stream_ssl_conf_t, protocols),
       &ngx_stream_ssl_protocols },
@@ -378,6 +382,11 @@ ngx_stream_ssl_init_connection(ngx_ssl_t
         return NGX_AGAIN;
     }
 
+    if (rc == NGX_ABORT) {
+        /* DTLS handshake sent the cookie to client */
+        return NGX_ERROR;
+    }
+
     /* rc == NGX_OK */
 
     return NGX_OK;
@@ -710,6 +719,33 @@ ngx_stream_ssl_password_file(ngx_conf_t 
 
 
 static char *
+ngx_stream_set_ssl_protocols(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_ssl_conf_t  *scf = conf;
+
+    char  *rv;
+
+    rv = ngx_conf_set_bitmask_slot(cf, cmd, conf);
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    /* DTLS protocol requires corresponding TLS version to be set */
+
+    if (scf->protocols & NGX_SSL_DTLSv1) {
+        scf->protocols |= NGX_SSL_TLSv1;
+    }
+
+    if (scf->protocols & NGX_SSL_DTLSv1_2) {
+        scf->protocols |= NGX_SSL_TLSv1_2;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
 ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_stream_ssl_conf_t  *scf = conf;
@@ -824,8 +860,13 @@ invalid:
 static ngx_int_t
 ngx_stream_ssl_init(ngx_conf_t *cf)
 {
-    ngx_stream_handler_pt        *h;
-    ngx_stream_core_main_conf_t  *cmcf;
+    ngx_uint_t                     i;
+    ngx_stream_listen_t           *ls;
+    ngx_stream_handler_pt         *h;
+    ngx_stream_conf_ctx_t        *sctx;
+    ngx_stream_ssl_conf_t        **sscfp, *sscf;
+    ngx_stream_core_srv_conf_t   **cscfp, *cscf;
+    ngx_stream_core_main_conf_t   *cmcf;
 
     cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
 
@@ -836,5 +877,52 @@ ngx_stream_ssl_init(ngx_conf_t *cf)
 
     *h = ngx_stream_ssl_handler;
 
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    ls = cmcf->listen.elts;
+
+    for (i = 0; i < cmcf->listen.nelts; i++) {
+        if (ls[i].ssl) {
+            sctx = ls[i].ctx;
+
+            sscfp = (ngx_stream_ssl_conf_t **)sctx->srv_conf;
+            cscfp = (ngx_stream_core_srv_conf_t **)sctx->srv_conf;
+
+            sscf = sscfp[ngx_stream_ssl_module.ctx_index];
+            cscf = cscfp[ngx_stream_core_module.ctx_index];
+
+            if (sscf->certificates == NULL) {
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                              "no \"ssl_certificate\" is defined "
+                              "in server listening on SSL port at %s:%ui",
+                              cscf->file_name, cscf->line);
+                return NGX_ERROR;
+            }
+
+            if (ls[i].type == SOCK_DGRAM) {
+                if (!(sscf->protocols & NGX_SSL_DTLSv1
+                      || sscf->protocols & NGX_SSL_DTLSv1_2))
+                {
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "\"ssl_protocols\" does not enable DTLS in a "
+                                  "server listening on UDP SSL port at %s:%ui",
+                                   cscf->file_name, cscf->line);
+                    return NGX_ERROR;
+                }
+
+            } else {
+                if (sscf->protocols & NGX_SSL_DTLSv1
+                    || sscf->protocols & NGX_SSL_DTLSv1_2 )
+                {
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "\"ssl_protocols\" includes DTLS in a server "
+                                  "listening on SSL port at %s:%ui",
+                                  cscf->file_name, cscf->line);
+                    return NGX_ERROR;
+                }
+            }
+        }
+    }
+
     return NGX_OK;
 }
