diff --git a/doc/directives.md b/doc/directives.md index 9ad1f25db..b415061b6 100644 --- a/doc/directives.md +++ b/doc/directives.md @@ -69,6 +69,13 @@ Table of Contents * [push](#push) * [push_reconnect](#push_reconnect) * [session_relay](#session_relay) + * [rtmp_relay_ssl_protocols](#rtmp_relay_ssl_protocols) + * [rtmp_relay_ssl_ciphers](#rtmp_relay_ssl_ciphers) + * [rtmp_relay_ssl_server_name](#rtmp_relay_ssl_server_name) + * [rtmp_relay_ssl_verify](#rtmp_relay_ssl_verify) + * [rtmp_relay_ssl_verify_depth](#rtmp_relay_ssl_verify_depth) + * [rtmp_relay_ssl_trusted_certificate](#rtmp_relay_ssl_trusted_certificate) + * [rtmp_relay_ssl_crl](#rtmp_relay_ssl_crl) * [Notify](#notify) * [on_connect](#on_connect) * [on_play](#on_play) @@ -913,6 +920,8 @@ all local streams within application are pulled * start - start time in seconds * stop - stop time in seconds * static - makes pull static, such pull is created at nginx start +* ssl_server_name - overrides rtmp_relay_ssl_server_name directive, values: on, off +* ssl_verify - overrides rtmp_relay_ssl_verify directive, values: on, off If a value for a parameter contains spaces then you should use quotes around the **WHOLE** key=value pair like this : `'pageUrl=FAKE PAGE URL'`. @@ -950,6 +959,71 @@ could possibly be created later. Default is off. session_relay on; ``` +#### rtmp_relay_ssl_protocols +Syntax: `rtmp_relay_ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3];` +Context: rtmp, server, application + +Enables the specified protocols for streams pushed/pulled to/from the RTMPS server. Default is TLSv1 TLSv1.1 TLSv1.2. +```sh +rtmp_relay_ssl_protocols TLSv1.2 TLSv1.3; +``` + +#### rtmp_relay_ssl_ciphers +Syntax: `rtmp_relay_ssl_ciphers ciphers;` +Context: rtmp, server, application + +Specifies the enabled ciphers for for streams pushed/pulled to/from the RTMPS server. The ciphers are specified in the format understood by the OpenSSL library. Default is openssl `DEFAULT`. + +The full list can be viewed using the `openssl ciphers` command. +```sh +rtmp_relay_ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; +``` + +#### rtmp_relay_ssl_server_name +Syntax: `rtmp_relay_ssl_server_name on | off;` +Context: rtmp, server, application + +Enables or disables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066) when establishing a connection with the RTMPS server. Default is on. +```sh +rtmp_relay_ssl_server_name on; +``` + +#### rtmp_relay_ssl_verify +Syntax: `rtmp_relay_ssl_verify on | off;` +Context: rtmp, server, application + +Enables or disables verification of the RTMPS server certificate. Note you must set the rtmp_relay_ssl_trusted_certificate. Default is on. +```sh +rtmp_relay_ssl_verify on; +``` + +#### rtmp_relay_ssl_verify_depth +Syntax: `rtmp_relay_ssl_verify_depth number;` +Context: rtmp, server, application + +Sets the verification depth in the RTMPS server certificates chain. Default is 1. +```sh +rtmp_relay_ssl_verify_depth 1; +``` + +#### rtmp_relay_ssl_trusted_certificate +Syntax: `rtmp_relay_ssl_trusted_certificate file;` +Context: rtmp, server, application + +Specifies a file with trusted CA certificates in the PEM format used to verify the certificate of the RTMPS server. No default set. +```sh +rtmp_relay_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; +``` + +#### rtmp_relay_ssl_crl +Syntax: `rtmp_relay_ssl_crl file;` +Context: rtmp, server, application + +Specifies a file with revoked certificates (CRL) in the PEM format used to verify the certificate of the RTMPS server. No default set. +```sh +rtmp_relay_ssl_crl /etc/ssl/certs/crl.crt; +``` + ## Notify #### on_connect diff --git a/ngx_rtmp.h b/ngx_rtmp.h index f3a3d6f18..e38f6e942 100644 --- a/ngx_rtmp.h +++ b/ngx_rtmp.h @@ -338,6 +338,8 @@ typedef struct ngx_rtmp_core_srv_conf_s { typedef struct { ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */ ngx_str_t name; + ngx_resolver_t *resolver; + ngx_msec_t resolver_timeout; void **app_conf; } ngx_rtmp_core_app_conf_t; @@ -365,6 +367,7 @@ typedef struct { } ngx_rtmp_module_t; #define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */ +#define NGX_RTMP_SSL NGX_OPENSSL #define NGX_RTMP_MAIN_CONF 0x02000000 #define NGX_RTMP_SRV_CONF 0x04000000 diff --git a/ngx_rtmp_core_module.c b/ngx_rtmp_core_module.c index 1037f823f..4f03cef64 100644 --- a/ngx_rtmp_core_module.c +++ b/ngx_rtmp_core_module.c @@ -24,6 +24,8 @@ static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_rtmp_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; @@ -157,6 +159,20 @@ static ngx_command_t ngx_rtmp_core_commands[] = { offsetof(ngx_rtmp_core_srv_conf_t, buflen), NULL }, + { ngx_string("resolver"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_core_resolver, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("resolver_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_core_app_conf_t, resolver_timeout), + NULL }, + ngx_null_command }; @@ -308,6 +324,8 @@ ngx_rtmp_core_create_app_conf(ngx_conf_t *cf) return NULL; } + conf->resolver_timeout = NGX_CONF_UNSET_MSEC; + return conf; } @@ -318,8 +336,26 @@ ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_rtmp_core_app_conf_t *prev = parent; ngx_rtmp_core_app_conf_t *conf = child; - (void)prev; - (void)conf; + ngx_conf_merge_msec_value(conf->resolver_timeout, + prev->resolver_timeout, 30000); + + if (conf->resolver == NULL) { + + if (prev->resolver == NULL) { + + /* + * create dummy resolver in rtmp {} context + * to inherit it in all servers + */ + + prev->resolver = ngx_resolver_create(cf, NULL, 0); + if (prev->resolver == NULL) { + return NGX_CONF_ERROR; + } + } + + conf->resolver = prev->resolver; + } return NGX_CONF_OK; } @@ -743,3 +779,25 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } + + +static char * +ngx_rtmp_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_core_app_conf_t *cacf = conf; + + ngx_str_t *value; + + if (cacf->resolver) { + return "is duplicate"; + } + + value = cf->args->elts; + + cacf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1); + if (cacf->resolver == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/ngx_rtmp_init.c b/ngx_rtmp_init.c index 9da711ab7..59e89ae2c 100644 --- a/ngx_rtmp_init.c +++ b/ngx_rtmp_init.c @@ -257,6 +257,15 @@ ngx_rtmp_close_connection(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection"); +#if (NGX_RTMP_SSL) + + if (c->ssl) { + c->ssl->no_wait_shutdown = 1; + (void) ngx_ssl_shutdown(c); + } + +#endif + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif diff --git a/ngx_rtmp_notify_module.c b/ngx_rtmp_notify_module.c index 04ee64a5b..ca2797165 100644 --- a/ngx_rtmp_notify_module.c +++ b/ngx_rtmp_notify_module.c @@ -41,6 +41,8 @@ static char *ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_rtmp_notify_done(ngx_rtmp_session_t *s, char *cbname, ngx_uint_t url_idx); +static void ngx_rtmp_notify_reset_relay_target( + ngx_rtmp_relay_target_t *target); ngx_str_t ngx_rtmp_notify_urlencoded = @@ -80,6 +82,7 @@ typedef struct { ngx_msec_t update_timeout; ngx_flag_t update_strict; ngx_flag_t relay_redirect; + ngx_rtmp_relay_target_t *publish_relay_target; } ngx_rtmp_notify_app_conf_t; @@ -105,6 +108,13 @@ typedef struct { } ngx_rtmp_notify_done_t; +typedef struct { + ngx_rtmp_session_t *session; + ngx_str_t name; + ngx_rtmp_relay_target_t *target; +} ngx_rtmp_notify_push_pull_ctx_t; + + static ngx_command_t ngx_rtmp_notify_commands[] = { { ngx_string("on_connect"), @@ -283,6 +293,7 @@ ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_rtmp_notify_app_conf_t *prev = parent; ngx_rtmp_notify_app_conf_t *conf = child; + ngx_rtmp_relay_app_conf_t *racf; ngx_uint_t n; for (n = 0; n < NGX_RTMP_NOTIFY_APP_MAX; ++n) { @@ -296,6 +307,29 @@ ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) prev->active = 1; } + racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module); + + if (conf->url[NGX_RTMP_NOTIFY_PUBLISH]) { + conf->publish_relay_target = + ngx_pcalloc(cf->pool, sizeof(*(conf->publish_relay_target))); + if (conf->publish_relay_target == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_RTMP_SSL) + conf->publish_relay_target->is_rtmps = 1; + conf->publish_relay_target->ssl_server_name = NGX_CONF_UNSET; + conf->publish_relay_target->ssl_verify = NGX_CONF_UNSET; + + if (ngx_rtmp_relay_configure_ssl(cf, racf, conf->publish_relay_target) + != NGX_OK) + { + return NGX_CONF_ERROR; + } +#endif + } + + ngx_conf_merge_uint_value(conf->method, prev->method, NGX_RTMP_NETCALL_HTTP_POST); ngx_conf_merge_value(conf->send_redirect, prev->send_redirect, 0); @@ -1312,6 +1346,120 @@ ngx_rtmp_notify_set_name(u_char *dst, size_t dst_len, u_char *src, } +static void +ngx_rtmp_notify_reset_relay_target(ngx_rtmp_relay_target_t *target) +{ +#if (NGX_RTMP_SSL) + ngx_ssl_t *ssl; + ngx_flag_t ssl_server_name; + ngx_flag_t ssl_verify; + + ssl = target->ssl; + ssl_server_name = target->ssl_server_name; + ssl_verify = target->ssl_verify; +#endif + + ngx_memzero(target, sizeof(*target)); + +#if (NGX_RTMP_SSL) + target->ssl = ssl; + target->ssl_server_name = ssl_server_name; + target->ssl_verify = ssl_verify; +#endif +} + + +static void +ngx_rtmp_notify_resolve_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_rtmp_notify_push_pull_ctx_t *push_pull_ctx; + ngx_rtmp_session_t *s; + ngx_rtmp_relay_target_t *target; + ngx_uint_t naddrs, i; + ngx_resolver_addr_t *addrs; + struct sockaddr *sa; + u_char *p; + size_t len; + + + push_pull_ctx = ctx->data; + s = push_pull_ctx->session; + target = push_pull_ctx->target; + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "rtmp notify resolve"); + + if (ctx->state) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "%V could not be resolved (%i: %s)", + &ctx->name, ctx->state, + ngx_resolver_strerror(ctx->state)); + ngx_rtmp_finalize_session(s); + return; + } + + naddrs = ctx->naddrs; + addrs = ctx->addrs; + +#if (NGX_DEBUG) + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; + + addr.data = text; + + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(addrs[i].sockaddr, addrs[i].socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "name was resolved to %V", &addr); + } + } +#endif + + target->url.naddrs = naddrs; + target->url.addrs = ngx_pcalloc(s->connection->pool, + sizeof(ngx_addr_t) * naddrs); + if (target->url.addrs == NULL) { + goto error; + } + + for (i = 0; i < naddrs; i++) { + sa = ngx_pcalloc(s->connection->pool, addrs[i].socklen); + if (sa == NULL) { + goto error; + } + ngx_memcpy(sa, addrs[i].sockaddr, addrs[i].socklen); + + ngx_inet_set_port(sa, target->url.port); + target->url.addrs[i].sockaddr = sa; + target->url.addrs[i].socklen = addrs[i].socklen; + + p = ngx_pnalloc(s->connection->pool, NGX_SOCKADDR_STRLEN); + if (p == NULL) { + goto error; + } + + len = ngx_sock_ntop(sa, addrs[i].socklen, p, NGX_SOCKADDR_STRLEN, 1); + + target->url.addrs[i].name.len = len; + target->url.addrs[i].name.data = p; + } + + ngx_resolve_name_done(ctx); + + ngx_rtmp_relay_push(s, &push_pull_ctx->name, target); + return; + +error: + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "error processing resolver response"); + ngx_rtmp_finalize_session(s); +} + + static ngx_int_t ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) @@ -1319,9 +1467,12 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v = arg; ngx_int_t rc, send; ngx_str_t local_name; - ngx_rtmp_relay_target_t target; + ngx_rtmp_relay_target_t *target; ngx_url_t *u; + ngx_rtmp_core_app_conf_t *cacf; ngx_rtmp_notify_app_conf_t *nacf; + ngx_resolver_ctx_t *ctx, temp; + ngx_rtmp_notify_push_pull_ctx_t *push_pull_ctx; u_char name[NGX_RTMP_MAX_NAME]; static ngx_str_t location = ngx_string("location"); @@ -1370,7 +1521,12 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, goto next; } - if (ngx_strncasecmp(name, (u_char *) "rtmp://", 7)) { + if ((ngx_strncasecmp(name, (u_char *) "rtmp://", 7) != 0) +#if (NGX_RTMP_SSL) + && (ngx_strncasecmp(name, (u_char *) "rtmps://", 8) != 0) +#endif + ) + { *ngx_cpymem(v->name, name, rc) = 0; ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "notify: publish redirect to '%s'", v->name); @@ -1379,6 +1535,7 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, /* push */ + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); if (nacf->send_redirect) { @@ -1434,23 +1591,97 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, local_name.data = v->name; local_name.len = ngx_strlen(v->name); - ngx_memzero(&target, sizeof(target)); + target = nacf->publish_relay_target; - u = &target.url; - u->url = local_name; - u->url.data = name + 7; - u->url.len = rc - 7; - u->default_port = 1935; + ngx_rtmp_notify_reset_relay_target(target); + + u = &target->url; u->uri_part = 1; u->no_resolve = 1; /* want ip here */ +#if (NGX_RTMP_SSL) + if (ngx_strncasecmp(name, (u_char *) "rtmps://", 8) == 0) { + u->url.data = name + 8; + u->url.len = rc - 8; + u->default_port = 443; + + target->is_rtmps = 1; + } else +#endif + { + u->url.data = name + 7; + u->url.len = rc - 7; + u->default_port = 1935; + + target->is_rtmps = 0; + } + + u->url.data = ngx_pstrdup(s->connection->pool, &u->url); + if (u->url.data == NULL) { + return NGX_ERROR; + } + if (ngx_parse_url(s->connection->pool, u) != NGX_OK) { ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "notify: push failed '%V'", &local_name); return NGX_ERROR; } - ngx_rtmp_relay_push(s, &local_name, &target); +#if (NGX_RTMP_SSL) + if (target->is_rtmps) { + /* ssl_name must be NULL terminated, so duplicate it */ + target->ssl_name.data = ngx_pnalloc(s->connection->pool, u->host.len + 1); + if (target->ssl_name.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(target->ssl_name.data, u->host.data, u->host.len); + target->ssl_name.data[u->host.len] = '\0'; + target->ssl_name.len = u->host.len; + } +#endif + + if (u->naddrs == 0) { + /* need to resolve the name */ + temp.name = u->host; + ctx = ngx_resolve_start(cacf->resolver, &temp); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx == NGX_NO_RESOLVER) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "no resolver defined to resolve %V", &u->host); + return NGX_ERROR; + } + + push_pull_ctx = ngx_pcalloc(s->connection->pool, + sizeof(*push_pull_ctx)); + if (push_pull_ctx == NULL) { + return NGX_ERROR; + } + + push_pull_ctx->session = s; + push_pull_ctx->name.data = ngx_pstrdup(s->connection->pool, + &local_name); + push_pull_ctx->name.len = local_name.len; + push_pull_ctx->target = target; + + if (push_pull_ctx->name.data == NULL) { + return NGX_ERROR; + } + + ctx->name = u->host; + ctx->handler = ngx_rtmp_notify_resolve_handler; + ctx->data = push_pull_ctx; + ctx->timeout = cacf->resolver_timeout; + + if (ngx_resolve_name(ctx) != NGX_OK) { + return NGX_ERROR; + } + } else { + ngx_rtmp_relay_push(s, &local_name, target); + } next: diff --git a/ngx_rtmp_relay_module.c b/ngx_rtmp_relay_module.c index e38a3699b..8c05acf09 100644 --- a/ngx_rtmp_relay_module.c +++ b/ngx_rtmp_relay_module.c @@ -30,6 +30,17 @@ static ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection( ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, ngx_rtmp_relay_target_t *target); +#if (NGX_RTMP_SSL) + +static void ngx_rtmp_ssl_init_connection(ngx_rtmp_session_t *rs, + ngx_rtmp_relay_target_t *target); +static void ngx_rtmp_ssl_handshake_handler(ngx_connection_t *c); +static void ngx_rtmp_ssl_handshake(ngx_rtmp_session_t *rs); +static ngx_int_t ngx_rtmp_ssl_name(ngx_rtmp_session_t *rs, + ngx_rtmp_relay_target_t *target); + +#endif + /* _____ * =push= | |---publish---> @@ -45,21 +56,6 @@ static ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection( */ -typedef struct { - ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */ - ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */ - ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */ - ngx_array_t static_events; /* ngx_event_t * */ - ngx_log_t *log; - ngx_uint_t nbuckets; - ngx_msec_t buflen; - ngx_flag_t session_relay; - ngx_msec_t push_reconnect; - ngx_msec_t pull_reconnect; - ngx_rtmp_relay_ctx_t **ctx; -} ngx_rtmp_relay_app_conf_t; - - typedef struct { ngx_rtmp_conf_ctx_t cctx; ngx_rtmp_relay_target_t *target; @@ -79,6 +75,21 @@ typedef struct { #define NGX_RTMP_RELAY_FLASHVER "LNX.11,1,102,55" +#if (NGX_RTMP_SSL) + +static ngx_conf_bitmask_t ngx_rtmp_relay_ssl_protocols[] = { + { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, + { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, + { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, + { 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_null_string, 0 } +}; + +#endif + + static ngx_command_t ngx_rtmp_relay_commands[] = { { ngx_string("push"), @@ -123,6 +134,58 @@ static ngx_command_t ngx_rtmp_relay_commands[] = { offsetof(ngx_rtmp_relay_app_conf_t, session_relay), NULL }, +#if (NGX_RTMP_SSL) + + { ngx_string("rtmp_relay_ssl_protocols"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_protocols), + &ngx_rtmp_relay_ssl_protocols }, + + { ngx_string("rtmp_relay_ssl_ciphers"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_ciphers), + NULL }, + + { ngx_string("rtmp_relay_ssl_server_name"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_server_name), + NULL }, + + { ngx_string("rtmp_relay_ssl_verify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_verify), + NULL }, + + { ngx_string("rtmp_relay_ssl_verify_depth"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_verify_depth), + NULL }, + + { ngx_string("rtmp_relay_ssl_trusted_certificate"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_trusted_certificate), + NULL }, + + { ngx_string("rtmp_relay_ssl_crl"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, ssl_crl), + NULL }, + +#endif ngx_null_command }; @@ -193,6 +256,23 @@ ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf) racf->push_reconnect = NGX_CONF_UNSET_MSEC; racf->pull_reconnect = NGX_CONF_UNSET_MSEC; +#if (NGX_RTMP_SSL) + + /* + * the following are automatically zeroed out by pcalloc + * + * racf->ssl_protocols + * racf->ssl_ciphers + * racf->ssl_trusted_certificate + * racf->ssl_crl + */ + + racf->ssl_server_name = NGX_CONF_UNSET; + racf->ssl_verify = NGX_CONF_UNSET; + racf->ssl_verify_depth = NGX_CONF_UNSET_UINT; + +#endif + return racf; } @@ -213,6 +293,41 @@ ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->pull_reconnect, prev->pull_reconnect, 3000); +#if (NGX_RTMP_SSL) + + ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, + (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 + |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); + ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 1); + ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 1); + ngx_conf_merge_uint_value(conf->ssl_verify_depth, prev->ssl_verify_depth, + 1); + ngx_conf_merge_str_value(conf->ssl_trusted_certificate, + prev->ssl_trusted_certificate, ""); + ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, ""); + +#define ngx_rtmp_configure_ssl_for_arr(arr) \ + { \ + ngx_uint_t i; \ + ngx_rtmp_relay_target_t *target; \ + for (i = 0; i < (arr).nelts; i++) { \ + target = *((ngx_rtmp_relay_target_t **)((arr).elts) + i); \ + if (ngx_rtmp_relay_configure_ssl(cf, conf, target) \ + != NGX_OK) { \ + return NGX_CONF_ERROR; \ + } \ + } \ + } + + ngx_rtmp_configure_ssl_for_arr(conf->pulls); + ngx_rtmp_configure_ssl_for_arr(conf->pushes); + ngx_rtmp_configure_ssl_for_arr(conf->static_pulls); + +#undef ngx_rtmp_configure_ssl_for_arr + +#endif + return NGX_CONF_OK; } @@ -397,6 +512,14 @@ ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, #undef NGX_RTMP_RELAY_STR_COPY +#if (NGX_RTMP_SSL) + + rctx->ssl_name = target->ssl_name; + rctx->ssl_server_name = target->ssl_server_name; + rctx->ssl_verify = target->ssl_verify; + +#endif + if (rctx->app.len == 0 || rctx->play_path.len == 0) { /* parse uri */ uri = &target->url.uri; @@ -507,6 +630,14 @@ ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, (void) ngx_atomic_fetch_add(ngx_stat_active, 1); #endif +#if (NGX_RTMP_SSL) + if (target->is_rtmps) { + c->data = rs; + ngx_rtmp_ssl_init_connection(rs, target); + return rctx; + } +#endif + ngx_rtmp_client_handshake(rs, 1); return rctx; @@ -677,6 +808,10 @@ ngx_rtmp_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) name.len = ngx_strlen(v->name); name.data = v->name; + if (ctx == NULL) { + ctx = ngx_rtmp_relay_create_local_ctx(s, &name, NULL); + } + t = racf->pushes.elts; for (n = 0; n < racf->pushes.nelts; ++n, ++t) { target = *t; @@ -1677,6 +1812,21 @@ ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) u->url.len -= 7; } +#if (NGX_RTMP_SSL) + + if (ngx_strncasecmp(u->url.data, (u_char *) "rtmps://", 8) == 0) { + u->url.data += 8; + u->url.len -= 8; + + u->default_port = 443; + + target->is_rtmps = 1; + target->ssl_server_name = NGX_CONF_UNSET; + target->ssl_verify = NGX_CONF_UNSET; + } + +#endif + if (ngx_parse_url(cf->pool, u) != NGX_OK) { if (u->err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -1685,6 +1835,22 @@ ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } +#if (NGX_RTMP_SSL) + + if (target->is_rtmps) { + /* ssl_name must be NULL terminated, so duplicate it */ + target->ssl_name.data = ngx_pnalloc(cf->pool, u->host.len + 1); + if (target->ssl_name.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(target->ssl_name.data, u->host.data, u->host.len); + target->ssl_name.data[u->host.len] = '\0'; + target->ssl_name.len = u->host.len; + } + +#endif + value += 2; for (i = 2; i < cf->args->nelts; ++i, ++value) { p = ngx_strlchr(value->data, value->data + value->len, '='); @@ -1728,6 +1894,36 @@ ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) NGX_RTMP_RELAY_NUM_PAR("start", start); NGX_RTMP_RELAY_NUM_PAR("stop", stop); +#if (NGX_RTMP_SSL) + +#define NGX_RTMP_RELAY_FLAG_PAR(name, var) \ + if (n.len == sizeof(name) - 1 \ + && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \ + { \ + if (ngx_strncasecmp(v.data, (u_char *) "on", v.len) == 0) { \ + target->var = 1; \ + } else if (ngx_strncasecmp(v.data, (u_char *) "off", v.len) \ + == 0) \ + { \ + target->var = 0; \ + } else { \ + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, \ + "invalid value \"%V\" in \"%V\" directive, " \ + "it must be \"on\" or \"off\"", &v, &n); \ + return NGX_CONF_ERROR; \ + } \ + continue; \ + } + + if (target->is_rtmps) { + NGX_RTMP_RELAY_FLAG_PAR("ssl_server_name", ssl_server_name); + NGX_RTMP_RELAY_FLAG_PAR("ssl_verify", ssl_verify); + } + +#undef NGX_RTMP_RELAY_FLAG_PAR + +#endif + #undef NGX_RTMP_RELAY_STR_PAR #undef NGX_RTMP_RELAY_NUM_PAR @@ -1897,3 +2093,197 @@ ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf) return NGX_OK; } + + +#if (NGX_RTMP_SSL) + +static void +ngx_rtmp_ssl_init_connection(ngx_rtmp_session_t *rs, + ngx_rtmp_relay_target_t *target) +{ + ngx_int_t rc; + ngx_connection_t *c; + + c = rs->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, + "relay: initialize SSL connection"); + + if (ngx_ssl_create_connection(target->ssl, c, + NGX_SSL_BUFFER|NGX_SSL_CLIENT) + != NGX_OK) + { + ngx_rtmp_finalize_session(rs); + return; + } + + c->sendfile = 0; + + if (target->ssl_server_name && ngx_rtmp_ssl_name(rs, target) != NGX_OK) { + ngx_rtmp_finalize_session(rs); + return; + } + + rc = ngx_ssl_handshake(c); + + if (rc == NGX_AGAIN) { + + if (!c->write->timer_set) { + ngx_add_timer(c->write, rs->timeout); + } + + c->ssl->handler = ngx_rtmp_ssl_handshake_handler; + return; + } + + ngx_rtmp_ssl_handshake(rs); +} + + +static void +ngx_rtmp_ssl_handshake_handler(ngx_connection_t *c) +{ + ngx_rtmp_session_t *rs; + + rs = c->data; + ngx_rtmp_ssl_handshake(rs); +} + + +static void +ngx_rtmp_ssl_handshake(ngx_rtmp_session_t *rs) +{ + ngx_connection_t *c; + ngx_rtmp_relay_ctx_t *rctx; + long rc; + + c = rs->connection; + rctx = ngx_rtmp_get_module_ctx(rs, ngx_rtmp_relay_module); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "relay: SSL handshaking"); + + if (c->ssl->handshaked) { + if (rctx->ssl_verify && rctx->ssl_name.len != 0) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "relay: SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + goto err; + } + + if (ngx_ssl_check_host(c, &rctx->ssl_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "relay: SSL certificate does not match \"%V\"", + &rctx->ssl_name); + goto err; + } + } + + ngx_rtmp_client_handshake(rs, 1); + return; + } + + if (c->write->timedout) { + goto err; + } + +err: + ngx_rtmp_finalize_session(rs); +} + + +static ngx_int_t +ngx_rtmp_ssl_name(ngx_rtmp_session_t *rs, ngx_rtmp_relay_target_t *target) +{ + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + + ngx_connection_t *c; + + c = rs->connection; + + if (target->ssl_name.len == 0) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, + "relay: SSL server name: \"%V\"", &target->ssl_name); + + if (SSL_set_tlsext_host_name(c->ssl->connection, target->ssl_name.data) + == 0) + { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_set_tlsext_host_name(\"%V\") failed", + &target->ssl_name); + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_relay_configure_ssl(ngx_conf_t *cf, ngx_rtmp_relay_app_conf_t *racf, + ngx_rtmp_relay_target_t *target) +{ + ngx_pool_cleanup_t *cln; + + if (target->is_rtmps) { + target->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (target->ssl == NULL) { + return NGX_ERROR; + } + + target->ssl->log = cf->log; + + if (ngx_ssl_create(target->ssl, racf->ssl_protocols, NULL) != NGX_OK) { + return NGX_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + ngx_ssl_cleanup_ctx(target->ssl); + return NGX_ERROR; + } + + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = target->ssl; + + if (ngx_ssl_ciphers(cf, target->ssl, &racf->ssl_ciphers, 0) + != NGX_OK) { + return NGX_ERROR; + } + + ngx_conf_merge_value(target->ssl_server_name, + racf->ssl_server_name, 0); + ngx_conf_merge_value(target->ssl_verify, racf->ssl_verify, 0); + + if (target->ssl_verify) { + if (racf->ssl_trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "relay: no rtmp_relay_ssl_trusted_certificate for rtmp_relay_ssl_verify"); + return NGX_ERROR; + } + + if (ngx_ssl_trusted_certificate(cf, target->ssl, + &racf->ssl_trusted_certificate, + racf->ssl_verify_depth) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_ssl_crl(cf, target->ssl, &racf->ssl_crl) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + +#endif diff --git a/ngx_rtmp_relay_module.h b/ngx_rtmp_relay_module.h index d1baeae61..dc8adfe3c 100644 --- a/ngx_rtmp_relay_module.h +++ b/ngx_rtmp_relay_module.h @@ -26,6 +26,16 @@ typedef struct { ngx_int_t start; ngx_int_t stop; +#if (NGX_RTMP_SSL) + + ngx_int_t is_rtmps; + ngx_ssl_t *ssl; + ngx_str_t ssl_name; + ngx_flag_t ssl_server_name; + ngx_flag_t ssl_verify; + +#endif + void *tag; /* usually module reference */ void *data; /* module-specific data */ ngx_uint_t counter; /* mutable connection counter */ @@ -53,6 +63,14 @@ struct ngx_rtmp_relay_ctx_s { ngx_int_t start; ngx_int_t stop; +#if (NGX_RTMP_SSL) + + ngx_str_t ssl_name; + ngx_flag_t ssl_server_name; + ngx_flag_t ssl_verify; + +#endif + ngx_event_t push_evt; ngx_event_t *static_evt; void *tag; @@ -60,6 +78,34 @@ struct ngx_rtmp_relay_ctx_s { }; +typedef struct { + ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */ + ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */ + ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */ + ngx_array_t static_events; /* ngx_event_t * */ + ngx_log_t *log; + ngx_uint_t nbuckets; + ngx_msec_t buflen; + ngx_flag_t session_relay; + ngx_msec_t push_reconnect; + ngx_msec_t pull_reconnect; + ngx_rtmp_relay_ctx_t **ctx; + +#if (NGX_RTMP_SSL) + + ngx_uint_t ssl_protocols; + ngx_str_t ssl_ciphers; + ngx_flag_t ssl_server_name; + ngx_flag_t ssl_verify; + ngx_uint_t ssl_verify_depth; + ngx_str_t ssl_trusted_certificate; + ngx_str_t ssl_crl; + +#endif + +} ngx_rtmp_relay_app_conf_t; + + extern ngx_module_t ngx_rtmp_relay_module; @@ -68,5 +114,13 @@ ngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name, ngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name, ngx_rtmp_relay_target_t *target); +#if (NGX_RTMP_SSL) + +ngx_int_t ngx_rtmp_relay_configure_ssl(ngx_conf_t *cf, + ngx_rtmp_relay_app_conf_t *racf, + ngx_rtmp_relay_target_t *target); + +#endif + #endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */