/* * Copyright (C) Roman Arutyunyan */ #include #include #include #include #include "ngx_rtmp.h" static void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf); static void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf); static char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static void *ngx_rtmp_core_create_app_conf(ngx_conf_t *cf); static char *ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 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); ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; static ngx_conf_deprecated_t ngx_conf_deprecated_so_keepalive = { ngx_conf_deprecated, "so_keepalive", "so_keepalive\" parameter of the \"listen" }; static ngx_command_t ngx_rtmp_core_commands[] = { { ngx_string("server"), NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_rtmp_core_server, 0, 0, NULL }, { ngx_string("listen"), NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, ngx_rtmp_core_listen, NGX_RTMP_SRV_CONF_OFFSET, 0, NULL }, { ngx_string("application"), NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, ngx_rtmp_core_application, NGX_RTMP_SRV_CONF_OFFSET, 0, NULL }, { ngx_string("so_keepalive"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, so_keepalive), &ngx_conf_deprecated_so_keepalive }, { ngx_string("timeout"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, timeout), NULL }, { ngx_string("ping"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, ping), NULL }, { ngx_string("ping_timeout"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, ping_timeout), NULL }, { ngx_string("max_streams"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, max_streams), NULL }, { ngx_string("ack_window"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, ack_window), NULL }, { ngx_string("chunk_size"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, chunk_size), NULL }, { ngx_string("max_message"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, max_message), NULL }, { ngx_string("out_queue"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, out_queue), NULL }, { ngx_string("out_cork"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, out_cork), NULL }, { ngx_string("busy"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_flag_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, busy), NULL }, /* time fixes are needed for flash clients */ { ngx_string("play_time_fix"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, ngx_conf_set_flag_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix), NULL }, { ngx_string("publish_time_fix"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, ngx_conf_set_flag_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix), NULL }, { ngx_string("buflen"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_RTMP_SRV_CONF_OFFSET, offsetof(ngx_rtmp_core_srv_conf_t, buflen), NULL }, ngx_null_command }; static ngx_rtmp_module_t ngx_rtmp_core_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ ngx_rtmp_core_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ ngx_rtmp_core_create_srv_conf, /* create server configuration */ ngx_rtmp_core_merge_srv_conf, /* merge server configuration */ ngx_rtmp_core_create_app_conf, /* create app configuration */ ngx_rtmp_core_merge_app_conf /* merge app configuration */ }; ngx_module_t ngx_rtmp_core_module = { NGX_MODULE_V1, &ngx_rtmp_core_module_ctx, /* module context */ ngx_rtmp_core_commands, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_rtmp_core_create_main_conf(ngx_conf_t *cf) { ngx_rtmp_core_main_conf_t *cmcf; cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_main_conf_t)); if (cmcf == NULL) { return NULL; } ngx_rtmp_core_main_conf = cmcf; if (ngx_array_init(&cmcf->servers, cf->pool, 4, sizeof(ngx_rtmp_core_srv_conf_t *)) != NGX_OK) { return NULL; } if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_rtmp_listen_t)) != NGX_OK) { return NULL; } return cmcf; } static void * ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf) { ngx_rtmp_core_srv_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_conf_t)); if (conf == NULL) { return NULL; } if (ngx_array_init(&conf->applications, cf->pool, 4, sizeof(ngx_rtmp_core_app_conf_t *)) != NGX_OK) { return NULL; } conf->timeout = NGX_CONF_UNSET_MSEC; conf->ping = NGX_CONF_UNSET_MSEC; conf->ping_timeout = NGX_CONF_UNSET_MSEC; conf->so_keepalive = NGX_CONF_UNSET; conf->max_streams = NGX_CONF_UNSET; conf->chunk_size = NGX_CONF_UNSET; conf->ack_window = NGX_CONF_UNSET_UINT; conf->max_message = NGX_CONF_UNSET_SIZE; conf->out_queue = NGX_CONF_UNSET_SIZE; conf->out_cork = NGX_CONF_UNSET_SIZE; conf->play_time_fix = NGX_CONF_UNSET; conf->publish_time_fix = NGX_CONF_UNSET; conf->buflen = NGX_CONF_UNSET_MSEC; conf->busy = NGX_CONF_UNSET; return conf; } static char * ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_rtmp_core_srv_conf_t *prev = parent; ngx_rtmp_core_srv_conf_t *conf = child; ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_msec_value(conf->ping, prev->ping, 60000); ngx_conf_merge_msec_value(conf->ping_timeout, prev->ping_timeout, 30000); ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0); ngx_conf_merge_value(conf->max_streams, prev->max_streams, 32); ngx_conf_merge_value(conf->chunk_size, prev->chunk_size, 4096); ngx_conf_merge_uint_value(conf->ack_window, prev->ack_window, 5000000); ngx_conf_merge_size_value(conf->max_message, prev->max_message, 1 * 1024 * 1024); ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 256); ngx_conf_merge_size_value(conf->out_cork, prev->out_cork, conf->out_queue / 8); ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1); ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1); ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 1000); ngx_conf_merge_value(conf->busy, prev->busy, 0); if (prev->pool == NULL) { prev->pool = ngx_create_pool(4096, &cf->cycle->new_log); if (prev->pool == NULL) { return NGX_CONF_ERROR; } } conf->pool = prev->pool; return NGX_CONF_OK; } static void * ngx_rtmp_core_create_app_conf(ngx_conf_t *cf) { ngx_rtmp_core_app_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_app_conf_t)); if (conf == NULL) { return NULL; } if (ngx_array_init(&conf->applications, cf->pool, 1, sizeof(ngx_rtmp_core_app_conf_t *)) != NGX_OK) { return NULL; } return conf; } static char * 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; return NGX_CONF_OK; } static char * ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; void *mconf; ngx_uint_t m; ngx_conf_t pcf; ngx_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx; ngx_rtmp_core_srv_conf_t *cscf, **cscfp; ngx_rtmp_core_main_conf_t *cmcf; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } rtmp_ctx = cf->ctx; ctx->main_conf = rtmp_ctx->main_conf; /* the server{}'s srv_conf */ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); if (ctx->srv_conf == NULL) { return NGX_CONF_ERROR; } ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); if (ctx->app_conf == NULL) { return NGX_CONF_ERROR; } #if defined(nginx_version) && nginx_version >= 1009011 modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[modules[m]->ctx_index] = mconf; } if (module->create_app_conf) { mconf = module->create_app_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->app_conf[modules[m]->ctx_index] = mconf; } } /* the server configuration context */ cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; cscf->ctx = ctx; cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; cscfp = ngx_array_push(&cmcf->servers); if (cscfp == NULL) { return NGX_CONF_ERROR; } *cscfp = cscf; /* parse inside server{} */ pcf = *cf; cf->ctx = ctx; cf->cmd_type = NGX_RTMP_SRV_CONF; rv = ngx_conf_parse(cf, NULL); *cf = pcf; return rv; } static char * ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; ngx_int_t i; ngx_str_t *value; ngx_conf_t save; ngx_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_conf_ctx_t *ctx, *pctx; ngx_rtmp_core_srv_conf_t *cscf; ngx_rtmp_core_app_conf_t *cacf, **cacfp; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } pctx = cf->ctx; ctx->main_conf = pctx->main_conf; ctx->srv_conf = pctx->srv_conf; ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); if (ctx->app_conf == NULL) { return NGX_CONF_ERROR; } #if defined(nginx_version) && nginx_version >= 1009011 modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (i = 0; modules[i]; i++) { if (modules[i]->type != NGX_RTMP_MODULE) { continue; } module = modules[i]->ctx; if (module->create_app_conf) { ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); if (ctx->app_conf[modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } cacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index]; cacf->app_conf = ctx->app_conf; value = cf->args->elts; cacf->name = value[1]; cscf = pctx->srv_conf[ngx_rtmp_core_module.ctx_index]; cacfp = ngx_array_push(&cscf->applications); if (cacfp == NULL) { return NGX_CONF_ERROR; } *cacfp = cacf; save = *cf; cf->ctx = ctx; cf->cmd_type = NGX_RTMP_APP_CONF; rv = ngx_conf_parse(cf, NULL); *cf= save; return rv; } static char * ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { size_t len, off; in_port_t port; ngx_str_t *value; ngx_url_t u; ngx_uint_t i; struct sockaddr *sa; ngx_rtmp_listen_t *ls; struct sockaddr_in *sin; ngx_rtmp_core_main_conf_t *cmcf; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; #endif value = cf->args->elts; ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.listen = 1; if (ngx_parse_url(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in \"%V\" of the \"listen\" directive", u.err, &u.url); } return NGX_CONF_ERROR; } cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); ls = cmcf->listen.elts; for (i = 0; i < cmcf->listen.nelts; i++) { sa = (struct sockaddr *) ls[i].sockaddr; if (sa->sa_family != u.family) { continue; } switch (sa->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: off = offsetof(struct sockaddr_in6, sin6_addr); len = 16; sin6 = (struct sockaddr_in6 *) sa; port = sin6->sin6_port; break; #endif default: /* AF_INET */ off = offsetof(struct sockaddr_in, sin_addr); len = 4; sin = (struct sockaddr_in *) sa; port = sin->sin_port; break; } #if (nginx_version >= 1011000) if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len) != 0) { #else if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) { #endif continue; } if (port != u.port) { continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate \"%V\" address and port pair", &u.url); return NGX_CONF_ERROR; } ls = ngx_array_push(&cmcf->listen); if (ls == NULL) { return NGX_CONF_ERROR; } ngx_memzero(ls, sizeof(ngx_rtmp_listen_t)); #if (nginx_version >= 1011000) ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen); #else ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen); #endif ls->socklen = u.socklen; ls->wildcard = u.wildcard; ls->ctx = cf->ctx; for (i = 2; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "bind") == 0) { ls->bind = 1; continue; } if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) u_char buf[NGX_SOCKADDR_STRLEN]; sa = (struct sockaddr *) ls->sockaddr; if (sa->sa_family == AF_INET6) { if (ngx_strcmp(&value[i].data[10], "n") == 0) { ls->ipv6only = 1; } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { ls->ipv6only = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid ipv6only flags \"%s\"", &value[i].data[9]); return NGX_CONF_ERROR; } ls->bind = 1; } else { len = ngx_sock_ntop(sa, #if (nginx_version >= 1005003) ls->socklen, #endif buf, NGX_SOCKADDR_STRLEN, 1); ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "ipv6only is not supported " "on addr \"%*s\", ignored", len, buf); } continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "bind ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif } if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { ls->so_keepalive = 1; } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { ls->so_keepalive = 2; } else { #if (NGX_HAVE_KEEPALIVE_TUNABLE) u_char *p, *end; ngx_str_t s; end = value[i].data + value[i].len; s.data = value[i].data + 13; p = ngx_strlchr(s.data, end, ':'); if (p == NULL) { p = end; } if (p > s.data) { s.len = p - s.data; ls->tcp_keepidle = ngx_parse_time(&s, 1); if (ls->tcp_keepidle == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } s.data = (p < end) ? (p + 1) : end; p = ngx_strlchr(s.data, end, ':'); if (p == NULL) { p = end; } if (p > s.data) { s.len = p - s.data; ls->tcp_keepintvl = ngx_parse_time(&s, 1); if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } s.data = (p < end) ? (p + 1) : end; if (s.data < end) { s.len = end - s.data; ls->tcp_keepcnt = ngx_atoi(s.data, s.len); if (ls->tcp_keepcnt == NGX_ERROR) { goto invalid_so_keepalive; } } if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 && ls->tcp_keepcnt == 0) { goto invalid_so_keepalive; } ls->so_keepalive = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"so_keepalive\" parameter accepts " "only \"on\" or \"off\" on this platform"); return NGX_CONF_ERROR; #endif } ls->bind = 1; continue; #if (NGX_HAVE_KEEPALIVE_TUNABLE) invalid_so_keepalive: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid so_keepalive value: \"%s\"", &value[i].data[13]); return NGX_CONF_ERROR; #endif } if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { ls->proxy_protocol = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the invalid \"%V\" parameter", &value[i]); return NGX_CONF_ERROR; } return NGX_CONF_OK; }