diff --git a/src/stream/ngx_stream_ssl_preread_module.c b/src/stream/ngx_stream_ssl_preread_module.c index e3d11fd93..1d543005e 100644 --- a/src/stream/ngx_stream_ssl_preread_module.c +++ b/src/stream/ngx_stream_ssl_preread_module.c @@ -17,10 +17,12 @@ typedef struct { typedef struct { size_t left; size_t size; + size_t ext; u_char *pos; u_char *dst; u_char buf[4]; ngx_str_t host; + ngx_str_t alpn; ngx_log_t *log; ngx_pool_t *pool; ngx_uint_t state; @@ -32,6 +34,8 @@ static ngx_int_t ngx_stream_ssl_preread_parse_record( ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); static ngx_int_t ngx_stream_ssl_preread_server_name_variable( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_ssl_preread_alpn_protocols_variable( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf); static void *ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, @@ -85,6 +89,9 @@ static ngx_stream_variable_t ngx_stream_ssl_preread_vars[] = { { ngx_string("ssl_preread_server_name"), NULL, ngx_stream_ssl_preread_server_name_variable, 0, 0, 0 }, + { ngx_string("ssl_preread_alpn_protocols"), NULL, + ngx_stream_ssl_preread_alpn_protocols_variable, 0, 0, 0 }, + ngx_stream_null_variable }; @@ -139,12 +146,14 @@ ngx_stream_ssl_preread_handler(ngx_stream_session_t *s) if (p[0] != 0x16) { ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: not a handshake"); + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); return NGX_DECLINED; } if (p[1] != 3) { ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: unsupported SSL version"); + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); return NGX_DECLINED; } @@ -158,6 +167,12 @@ ngx_stream_ssl_preread_handler(ngx_stream_session_t *s) p += 5; rc = ngx_stream_ssl_preread_parse_record(ctx, p, p + len); + + if (rc == NGX_DECLINED) { + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); + return NGX_DECLINED; + } + if (rc != NGX_AGAIN) { return rc; } @@ -175,7 +190,7 @@ static ngx_int_t ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last) { - size_t left, n, size; + size_t left, n, size, ext; u_char *dst, *p; enum { @@ -192,7 +207,10 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, sw_ext_header, /* extension_type, extension_data length */ sw_sni_len, /* SNI length */ sw_sni_host_head, /* SNI name_type, host_name length */ - sw_sni_host /* SNI host_name */ + sw_sni_host, /* SNI host_name */ + sw_alpn_len, /* ALPN length */ + sw_alpn_proto_len, /* ALPN protocol_name length */ + sw_alpn_proto_data /* ALPN protocol_name */ } state; ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0, @@ -201,6 +219,7 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, state = ctx->state; size = ctx->size; left = ctx->left; + ext = ctx->ext; dst = ctx->dst; p = ctx->buf; @@ -299,10 +318,18 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, break; case sw_ext_header: - if (p[0] == 0 && p[1] == 0) { + if (p[0] == 0 && p[1] == 0 && ctx->host.data == NULL) { /* SNI extension */ state = sw_sni_len; - dst = NULL; + dst = p; + size = 2; + break; + } + + if (p[0] == 0 && p[1] == 16 && ctx->alpn.data == NULL) { + /* ALPN extension */ + state = sw_alpn_len; + dst = p; size = 2; break; } @@ -313,6 +340,7 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, break; case sw_sni_len: + ext = (p[0] << 8) + p[1]; state = sw_sni_host_head; dst = p; size = 3; @@ -325,14 +353,21 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, return NGX_DECLINED; } - state = sw_sni_host; size = (p[1] << 8) + p[2]; + if (ext < 3 + size) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: SNI format error"); + return NGX_DECLINED; + } + ext -= 3 + size; + ctx->host.data = ngx_pnalloc(ctx->pool, size); if (ctx->host.data == NULL) { return NGX_ERROR; } + state = sw_sni_host; dst = ctx->host.data; break; @@ -341,7 +376,64 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: SNI hostname \"%V\"", &ctx->host); - return NGX_OK; + + state = sw_ext; + dst = NULL; + size = ext; + break; + + case sw_alpn_len: + ext = (p[0] << 8) + p[1]; + + ctx->alpn.data = ngx_pnalloc(ctx->pool, ext); + if (ctx->alpn.data == NULL) { + return NGX_ERROR; + } + + state = sw_alpn_proto_len; + dst = p; + size = 1; + break; + + case sw_alpn_proto_len: + size = p[0]; + + if (size == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN empty protocol"); + return NGX_DECLINED; + } + + if (ext < 1 + size) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN format error"); + return NGX_DECLINED; + } + ext -= 1 + size; + + state = sw_alpn_proto_data; + dst = ctx->alpn.data + ctx->alpn.len; + break; + + case sw_alpn_proto_data: + ctx->alpn.len += p[0]; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN protocols \"%V\"", &ctx->alpn); + + if (ext) { + ctx->alpn.data[ctx->alpn.len++] = ','; + + state = sw_alpn_proto_len; + dst = p; + size = 1; + break; + } + + state = sw_ext; + dst = NULL; + size = 0; + break; } if (left < size) { @@ -354,6 +446,7 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, ctx->state = state; ctx->size = size; ctx->left = left; + ctx->ext = ext; ctx->dst = dst; return NGX_AGAIN; @@ -383,6 +476,29 @@ ngx_stream_ssl_preread_server_name_variable(ngx_stream_session_t *s, } +static ngx_int_t +ngx_stream_ssl_preread_alpn_protocols_variable(ngx_stream_session_t *s, + ngx_variable_value_t *v, uintptr_t data) +{ + ngx_stream_ssl_preread_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module); + + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ctx->alpn.len; + v->data = ctx->alpn.data; + + return NGX_OK; +} + + static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf) {