Contrib: http2 per server (was re: [nginx] support http2 per server)

David Freedman david.freedman at uk.clara.net
Tue Sep 19 23:49:13 UTC 2017


Following on from the previous thread on this ( http://mailman.nginx.org/pipermail/nginx-devel/2017-June/010079.html ) , and after discussion with the original author
 < hongzhidao at gmail.com > , I'd like to present the patches we are using.

Please note the following improvements / modifications:

1. Default state is set to off during conf merge, so that existing behaviour of enabling http2 directive on the listener and having it apply to all servers is preserved.

2. Attempts to re-use a connection via the listener to access a server without ssl_h2 enabled (i.e, explicitly set to off) are met with a 421 (Misdirected Request), enforcing the intended switch behavior on the server block,  (in response to the comment made in http://mailman.nginx.org/pipermail/nginx-devel/2017-June/010091.html )
This works in much the same way as the ssl verify in that after SNI and in ngx_http_set_virtual_server(), we check for the switch state on the target server. 

3. Tests added to the test suite.

Patches follow below (decided not to patchbomb as this is across two repositories) , feedback welcome - we'd like to see this feature adopted  as it is clearly needed. 

Patch against main project
-------------------------------------

# HG changeset patch
# User David Freedman <david.freedman at uk.clara.net>
# Date 1504832866 0
#      Fri Sep 08 01:07:46 2017 +0000
# Node ID 2806e0ba8e91978ad6ce18ff1605b89bdd7806f4
# Parent  6b6e15bbda9269d03d66130efd51921dfedd93cb
Supports selective enabling of http2 for SSL/TLS sites
using new ssl_h2 option.

diff -r 6b6e15bbda92 -r 2806e0ba8e91 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c    Tue Sep 05 17:59:31 2017 +0300
+++ b/src/http/modules/ngx_http_ssl_module.c    Fri Sep 08 01:07:46 2017 +0000
@@ -234,6 +234,13 @@
       offsetof(ngx_http_ssl_srv_conf_t, stapling_verify),
       NULL },

+    { ngx_string("ssl_h2"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_http_ssl_enable,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, h2),
+      NULL },
+
       ngx_null_command
 };

@@ -354,6 +361,7 @@
 #endif
 #if (NGX_HTTP_V2)
     ngx_http_connection_t  *hc;
+    ngx_http_ssl_srv_conf_t   *sscf;
 #endif
 #if (NGX_HTTP_V2 || NGX_DEBUG)
     ngx_connection_t       *c;
@@ -372,7 +380,9 @@
 #if (NGX_HTTP_V2)
     hc = c->data;

-    if (hc->addr_conf->http2) {
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    if (hc->addr_conf->http2 && sscf->h2) {
         srv =
            (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
         srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -416,10 +426,13 @@
 #if (NGX_HTTP_V2)
     {
     ngx_http_connection_t  *hc;
+    ngx_http_ssl_srv_conf_t   *sscf;

     hc = c->data;

-    if (hc->addr_conf->http2) {
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    if (hc->addr_conf->http2 && sscf->h2) {
         *out =
             (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
         *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -559,6 +572,7 @@
     sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
     sscf->stapling = NGX_CONF_UNSET;
     sscf->stapling_verify = NGX_CONF_UNSET;
+    sscf->h2 = NGX_CONF_UNSET;

     return sscf;
 }
@@ -624,6 +638,8 @@
     ngx_conf_merge_str_value(conf->stapling_responder,
                          prev->stapling_responder, "");

+    ngx_conf_merge_value(conf->h2, prev->h2, 1);
+
     conf->ssl.log = cf->log;

     if (conf->enable) {
diff -r 6b6e15bbda92 -r 2806e0ba8e91 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h    Tue Sep 05 17:59:31 2017 +0300
+++ b/src/http/modules/ngx_http_ssl_module.h    Fri Sep 08 01:07:46 2017 +0000
@@ -57,6 +57,9 @@

     u_char                         *file;
     ngx_uint_t                      line;
+
+    ngx_flag_t                      h2;
+
 } ngx_http_ssl_srv_conf_t;


diff -r 6b6e15bbda92 -r 2806e0ba8e91 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c       Tue Sep 05 17:59:31 2017 +0300
+++ b/src/http/ngx_http_request.c       Fri Sep 08 01:07:46 2017 +0000
@@ -795,6 +795,7 @@
         unsigned int            len;
         const unsigned char    *data;
         ngx_http_connection_t  *hc;
+        ngx_http_ssl_srv_conf_t  *sscf;

         hc = c->data;

@@ -813,9 +814,15 @@
             SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
 #endif

-            if (len == 2 && data[0] == 'h' && data[1] == '2') {
-                ngx_http_v2_init(c->read);
-                return;
+            sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+            if (sscf->h2) {
+
+                if (len == 2 && data[0] == 'h' && data[1] == '2') {
+                    ngx_http_v2_init(c->read);
+                    return;
+                }
+
             }
         }
         }
@@ -2106,6 +2113,15 @@
             ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
             return NGX_ERROR;
         }
+#if (NGX_HTTP_V2)
+        if (hc->addr_conf->http2 && !sscf->h2) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client attempted to request a server name "
+                          "that does not have http2 enabled");
+            ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
+            return NGX_ERROR;
+        }
+#endif
     }

 #endif


Patch against test suite
---------------------------------

# HG changeset patch
# User David Freedman <david.freedman at uk.clara.net>
# Date 1505862083 0
#      Tue Sep 19 23:01:23 2017 +0000
# Node ID b0bcd8bc295f8bf4dc735ea8d3ab44061eb88bdb
# Parent  f373a718f6463a949e2f0fe88a078425b7ba3b73
Tests: add testing for ssl_h2 (selective http2 over ssl)

diff -r f373a718f646 -r b0bcd8bc295f h2_ssl.t
--- a/h2_ssl.t  Mon Sep 11 20:06:15 2017 +0300
+++ b/h2_ssl.t  Tue Sep 19 23:01:23 2017 +0000
@@ -50,6 +50,18 @@

         location / { }
     }
+
+    server {
+        listen       127.0.0.1:8081 http2 ssl;
+        server_name  localhost;
+
+        ssl_h2 off;
+
+        ssl_certificate_key localhost.key;
+        ssl_certificate localhost.crt;
+
+        location / { }
+    }
 }

 EOF
@@ -80,6 +92,7 @@
 open STDERR, ">&", \*OLDERR;

 plan(skip_all => 'no ALPN/NPN negotiation') unless defined getconn(port(8080));
+plan(skip_all => 'no ALPN/NPN negotiation') if defined getconn(port(8081));
 $t->plan(1);

 ###############################################################################
@@ -113,7 +126,7 @@
                my $sock = Test::Nginx::HTTP2::new_socket($port, SSL => 1,
                        alpn => 'h2');
                $s = Test::Nginx::HTTP2->new($port, socket => $sock)
-                       if $sock->alpn_selected();
+                       if ($sock->alpn_selected() && $sock->alpn_selected()=~m/h2/);
        };

        return $s if defined $s;
@@ -122,7 +135,7 @@
                my $sock = Test::Nginx::HTTP2::new_socket($port, SSL => 1,
                        npn => 'h2');
                $s = Test::Nginx::HTTP2->new($port, socket => $sock)
-                       if $sock->next_proto_negotiated();
+                       if ($sock->next_proto_negotiated() && $sock->next_proto_negotiated()=~m/h2/);
        };

        return $s;
diff -r f373a718f646 -r b0bcd8bc295f h2_ssl_verify_authority.t
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/h2_ssl_verify_authority.t Tue Sep 19 23:01:23 2017 +0000
@@ -0,0 +1,153 @@
+#!/usr/bin/perl
+
+# Tests for HTTP/2 protocol with ssl, ssl_verify_authority
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+eval { require IO::Socket::SSL; };
+plan(skip_all => 'IO::Socket::SSL not installed') if $@;
+eval { IO::Socket::SSL->can_client_sni() or die; };
+plan(skip_all => 'IO::Socket::SSL with OpenSSL SNI support required') if $@;
+eval { IO::Socket::SSL->can_alpn() or die; };
+plan(skip_all => 'OpenSSL ALPN support required') if $@;
+
+my $t = Test::Nginx->new()->has(qw/http http_ssl sni http_v2/)
+       ->has_daemon('openssl');
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    ssl_certificate_key localhost.key;
+    ssl_certificate localhost.crt;
+
+    server {
+        listen       127.0.0.1:8080 ssl http2;
+        server_name  localhost;
+
+        location / { }
+    }
+
+    server {
+        listen       127.0.0.1:8080 ssl;
+        server_name  example.com;
+
+       ssl_h2 off;
+
+        location / { }
+    }
+}
+
+EOF
+
+$t->write_file('openssl.conf', <<EOF);
+[ req ]
+default_bits = 1024
+encrypt_key = no
+distinguished_name = req_distinguished_name
+[ req_distinguished_name ]
+EOF
+
+my $d = $t->testdir();
+
+foreach my $name ('localhost') {
+       system('openssl req -x509 -new '
+               . "-config '$d/openssl.conf' -subj '/CN=$name/' "
+               . "-out '$d/$name.crt' -keyout '$d/$name.key' "
+               . ">>$d/openssl.out 2>&1") == 0
+               or die "Can't create certificate for $name: $!\n";
+}
+
+$t->write_file('t', 'SEE-THIS');
+
+open OLDERR, ">&", \*STDERR; close STDERR;
+$t->run();
+open STDERR, ">&", \*OLDERR;
+
+my $s = get_ssl_socket();
+plan(skip_all => 'no alpn') unless $s->alpn_selected();
+$t->plan(2);
+
+###############################################################################
+
+my $sess = get_sess('localhost');
+is(get($sess, 'localhost')->{':status'}, '200', 'ok');
+is(get($sess, 'example.com')->{':status'}, '421', 'misdirected');
+###############################################################################
+
+sub get_ssl_socket {
+       my ($sni) = @_;
+       my $s;
+
+       eval {
+               local $SIG{ALRM} = sub { die "timeout\n" };
+               local $SIG{PIPE} = sub { die "sigpipe\n" };
+               alarm(2);
+               $s = IO::Socket::SSL->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1',
+                       PeerPort => port(8080),
+                       SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
+                       SSL_alpn_protocols => [ 'h2' ],
+                       SSL_hostname => $sni,
+                       SSL_error_trap => sub { die $_[1] }
+               );
+               alarm(0);
+       };
+       alarm(0);
+
+       if ($@) {
+               log_in("died: $@");
+               return undef;
+       }
+
+       return $s;
+}
+
+sub get_sess {
+       my $sni = shift;
+       my $s = get_ssl_socket($sni);
+       my $sess = Test::Nginx::HTTP2->new(port(8080), socket => $s);
+
+       return $sess
+}
+
+sub get {
+       my ($sess, $host) = @_;
+
+       my $sid = $sess->new_stream({ headers => [
+               { name => ':method', value => 'GET', mode => 0 },
+               { name => ':scheme', value => 'http', mode => 0 },
+               { name => ':path', value => '/t', mode => 1 },
+               { name => ':authority', value => $host, mode => 1 }]});
+       my $frames = $sess->read(all => [{ sid => $sid, fin => 1 }]);
+
+       my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+       return $frame->{'headers'};
+}
+
+###############################################################################






More information about the nginx-devel mailing list