diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c index 1c64d34214f..22fab5afc75 100644 --- a/modules/httpd/httpd.c +++ b/modules/httpd/httpd.c @@ -48,6 +48,8 @@ #define MIN_POST_BUF_SIZE 256 #define DEFAULT_POST_BUF_SIZE 1024 +#define DEFAULT_TLS_CIPHERS "SECURE256:+SECURE192:-VERS-ALL:+VERS-TLS1.2" +#define DEFAULT_CONN_TIMEOUT 30 /* module functions */ static int mod_init(); @@ -59,10 +61,10 @@ static mi_response_t *mi_list_root_path(const mi_params_t *params, int port = 8888; str ip = {NULL, 0}; str buffer = {NULL, 0}; -unsigned int hd_conn_timeout_s = 30; +unsigned int hd_conn_timeout_s = DEFAULT_CONN_TIMEOUT; str tls_cert_file = {NULL, 0}; str tls_key_file = {NULL, 0}; -str tls_ciphers = {"SECURE256:+SECURE192:-VERS-ALL:+VERS-TLS1.2", 45}; +str tls_ciphers = {DEFAULT_TLS_CIPHERS, sizeof(DEFAULT_TLS_CIPHERS) - 1}; int post_buf_size = DEFAULT_POST_BUF_SIZE; int receive_buf_size = DEFAULT_POST_BUF_SIZE; struct httpd_cb *httpd_cb_list = NULL; @@ -70,24 +72,128 @@ struct httpd_cb *httpd_cb_list = NULL; char *httpd_receive_buff = NULL; int httpd_receive_buff_pos=0; -static const proc_export_t mi_procs[] = { - {"HTTPD", 0, 0, httpd_proc, 1, +#define HTTPD_DEFAULT_SRV "default" + +struct httpd_server *httpd_servers = NULL; +int httpd_n_servers = 0; + +static proc_export_t mi_procs[] = { + {"HTTPD", httpd_pre_fork, httpd_post_fork, httpd_proc, 1, PROC_FLAG_INITCHILD|PROC_FLAG_HAS_IPC|PROC_FLAG_NEEDS_SCRIPT }, {NULL, 0, 0, NULL, 0, 0} }; +static void httpd_split(char *val, str *name, str *rest) +{ + char *p; + + if (val && val[0] == '[' && (p = strchr(val, ']'))) { + name->s = val + 1; + name->len = (int)(p - val - 1); + rest->s = p + 1; + rest->len = strlen(rest->s); + } else { + name->s = HTTPD_DEFAULT_SRV; + name->len = sizeof(HTTPD_DEFAULT_SRV) - 1; + rest->s = val; + rest->len = val ? strlen(val) : 0; + } +} + +static struct httpd_server *httpd_get_server(str *name) +{ + struct httpd_server *arr; + int i; + + for (i = 0; i < httpd_n_servers; i++) + if (httpd_servers[i].name.len == name->len && + memcmp(httpd_servers[i].name.s, name->s, name->len) == 0) + return &httpd_servers[i]; + + arr = realloc(httpd_servers, (httpd_n_servers + 1) * sizeof *arr); + if (!arr) { + LM_ERR("no more memory for HTTP servers\n"); + return NULL; + } + httpd_servers = arr; + arr = &httpd_servers[httpd_n_servers]; + memset(arr, 0, sizeof *arr); + arr->name.s = malloc(name->len + 1); + if (!arr->name.s) { + LM_ERR("no more memory for HTTP server name\n"); + return NULL; + } + memcpy(arr->name.s, name->s, name->len); + arr->name.s[name->len] = '\0'; + arr->name.len = name->len; + arr->listen_fd = -1; + httpd_n_servers++; + return arr; +} + +static struct httpd_server *httpd_srv_str(void *val, str *rest) +{ + str name; + + httpd_split((char *)val, &name, rest); + return httpd_get_server(&name); +} + +static int httpd_srv_int(modparam_t type, void *val, struct httpd_server **s, + int *out) +{ + str name, rest; + + if (type & STR_PARAM) { + httpd_split((char *)val, &name, &rest); + if (str2sint(&rest, out) < 0) { + LM_ERR("invalid integer modparam value '%.*s'\n", + rest.len, rest.s); + return -1; + } + } else { + name.s = HTTPD_DEFAULT_SRV; + name.len = sizeof(HTTPD_DEFAULT_SRV) - 1; + *out = (int)(long)val; + } + *s = httpd_get_server(&name); + return *s ? 0 : -1; +} + +static int set_ip(modparam_t t, void *v) +{ struct httpd_server *s; str r; s = httpd_srv_str(v, &r); if (!s) return -1; s->ip = r; return 0; } +static int set_tls_cert(modparam_t t, void *v) +{ struct httpd_server *s; str r; s = httpd_srv_str(v, &r); if (!s) return -1; s->tls_cert_file = r; return 0; } +static int set_tls_key(modparam_t t, void *v) +{ struct httpd_server *s; str r; s = httpd_srv_str(v, &r); if (!s) return -1; s->tls_key_file = r; return 0; } +static int set_tls_ciphers(modparam_t t, void *v) +{ struct httpd_server *s; str r; s = httpd_srv_str(v, &r); if (!s) return -1; s->tls_ciphers = r; return 0; } +static int set_port(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->port = n; return 0; } +static int set_buf_size(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->buf_size = n; return 0; } +static int set_conn_timeout(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->conn_timeout = n; return 0; } +static int set_post_buf_size(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->post_buf_size = n; return 0; } +static int set_receive_buf_size(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->receive_buf_size = n; return 0; } +static int set_workers(modparam_t t, void *v) +{ struct httpd_server *s; int n; if (httpd_srv_int(t, v, &s, &n) < 0) return -1; s->workers = n; return 0; } + /** Module parameters */ static const param_export_t params[] = { - {"port", INT_PARAM, &port}, - {"ip", STR_PARAM, &ip.s}, - {"buf_size", INT_PARAM, &buffer.len}, - {"conn_timeout", INT_PARAM, &hd_conn_timeout_s}, - {"post_buf_size", INT_PARAM, &post_buf_size}, - {"receive_buf_size", INT_PARAM, &receive_buf_size}, - {"tls_cert_file", STR_PARAM, &tls_cert_file.s}, - {"tls_key_file", STR_PARAM, &tls_key_file.s}, - {"tls_ciphers", STR_PARAM, &tls_ciphers.s}, + {"ip", STR_PARAM|USE_FUNC_PARAM, (void*)set_ip}, + {"port", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_port}, + {"buf_size", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_buf_size}, + {"conn_timeout", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_conn_timeout}, + {"post_buf_size", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_post_buf_size}, + {"receive_buf_size", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_receive_buf_size}, + {"tls_cert_file", STR_PARAM|USE_FUNC_PARAM, (void*)set_tls_cert}, + {"tls_key_file", STR_PARAM|USE_FUNC_PARAM, (void*)set_tls_key}, + {"tls_ciphers", STR_PARAM|USE_FUNC_PARAM, (void*)set_tls_ciphers}, + {"workers", STR_PARAM|INT_PARAM|USE_FUNC_PARAM, (void*)set_workers}, {NULL, 0, NULL} }; @@ -159,6 +265,8 @@ static long httpd_get_runtime_version(void) static int mod_init(void) { struct ip_addr *_ip; + struct httpd_server *s; + int i, total_workers = 0; #if defined MHD_VERSION && MHD_VERSION >= 0x00093500 /* Get whether epoll() is supported. If supported then @@ -172,29 +280,67 @@ static int mod_init(void) "running %s\n",MHD_get_version()); return -1; } - if (ip.s) { - ip.len = strlen(ip.s); - if ( strcmp(ip.s, "*") && !(_ip=str2ip(&ip)) && !(_ip=str2ip6(&ip))) { - LM_ERR("invalid IP [%.*s]\n", ip.len, ip.s); + + if (httpd_n_servers == 0) { + str def = {HTTPD_DEFAULT_SRV, sizeof(HTTPD_DEFAULT_SRV) - 1}; + if (!httpd_get_server(&def)) return -1; - } } - if (post_buf_size < MIN_POST_BUF_SIZE) { - LM_ERR("post_buf_size should be bigger then %d\n", - MIN_POST_BUF_SIZE); - return -1; - } - if (buffer.len == 0) - buffer.len = (pkg_mem_size/4); - LM_DBG("buf_size=[%d]\n", buffer.len); + for (i = 0; i < httpd_n_servers; i++) { + s = &httpd_servers[i]; + + if (s->port == 0) { + if (s->name.len == sizeof(HTTPD_DEFAULT_SRV) - 1 && + memcmp(s->name.s, HTTPD_DEFAULT_SRV, s->name.len) == 0) + s->port = 8888; + else { + LM_ERR("missing 'port' for HTTP server '%.*s'\n", + s->name.len, s->name.s); + return -1; + } + } - httpd_receive_buff = pkg_malloc(receive_buf_size); - if (httpd_receive_buff == NULL) { - LM_ERR("No more pkg\n"); - return -1; + if (s->ip.s) { + s->ip.len = strlen(s->ip.s); + if (strcmp(s->ip.s, "*") && !(_ip=str2ip(&s->ip)) + && !(_ip=str2ip6(&s->ip))) { + LM_ERR("invalid IP [%.*s] for HTTP server '%.*s'\n", + s->ip.len, s->ip.s, s->name.len, s->name.s); + return -1; + } + } + + if (s->workers < 1) + s->workers = 1; + if (s->post_buf_size == 0) + s->post_buf_size = DEFAULT_POST_BUF_SIZE; + if (s->post_buf_size < MIN_POST_BUF_SIZE) { + LM_ERR("post_buf_size should be bigger than %d (HTTP server '%.*s')\n", + MIN_POST_BUF_SIZE, s->name.len, s->name.s); + return -1; + } + if (s->receive_buf_size == 0) + s->receive_buf_size = DEFAULT_POST_BUF_SIZE; + if (s->conn_timeout == 0) + s->conn_timeout = DEFAULT_CONN_TIMEOUT; + if (!s->tls_ciphers.s) + s->tls_ciphers.s = DEFAULT_TLS_CIPHERS; + if ((s->tls_cert_file.s && !s->tls_key_file.s) || + (!s->tls_cert_file.s && s->tls_key_file.s)) { + LM_ERR("both tls_cert_file and tls_key_file are required for a " + "TLS HTTP server ('%.*s')\n", s->name.len, s->name.s); + return -1; + } + + total_workers += s->workers; + LM_INFO("HTTP server '%.*s' on %s:%d with %d worker(s)%s\n", + s->name.len, s->name.s, s->ip.s ? s->ip.s : "*", s->port, + s->workers, s->tls_cert_file.s ? " (TLS)" : ""); } + mi_procs[0].no = total_workers; + return 0; } diff --git a/modules/httpd/httpd_proc.c b/modules/httpd/httpd_proc.c index 0d4b43f207d..e864e4345c5 100644 --- a/modules/httpd/httpd_proc.c +++ b/modules/httpd/httpd_proc.c @@ -41,6 +41,7 @@ #include #endif +#include "../../globals.h" #include "../../pt.h" #include "../../sr_module.h" #include "../../str.h" @@ -67,6 +68,119 @@ extern int receive_buf_size; extern char *httpd_receive_buff; extern int httpd_receive_buff_pos; +int httpd_listen_fd = -1; + +static int httpd_build_sockaddr(str *sip, int sport, struct sockaddr_storage *ss, + int *family, int *dual_stack, char **ip_repr, char *reprbuf) +{ + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ss; + struct sockaddr_in *s4 = (struct sockaddr_in *)ss; + + memset(ss, 0, sizeof *ss); + *dual_stack = 0; + + if (sip->s && strcmp(sip->s, "*")) { + if (q_memchr(sip->s, ':', sip->len)) { + if (inet_pton(AF_INET6, sip->s, &s6->sin6_addr) <= 0) { + LM_ERR("failed to parse 'ip' modparam: %s\n", sip->s); + return -1; + } + s6->sin6_family = AF_INET6; + s6->sin6_port = htons(sport); + *family = AF_INET6; + sprintf(reprbuf, "[%s]", !strcmp(sip->s, "::0") ? "::" : sip->s); + *ip_repr = reprbuf; + return sizeof *s6; + } + + if (inet_pton(AF_INET, sip->s, &s4->sin_addr) <= 0) { + LM_ERR("failed to parse 'ip' modparam: %s\n", sip->s); + return -1; + } + s4->sin_family = AF_INET; + s4->sin_port = htons(sport); + *family = AF_INET; + *ip_repr = sip->s; + return sizeof *s4; + } + + s6->sin6_addr = in6addr_any; + s6->sin6_family = AF_INET6; + s6->sin6_port = htons(sport); + *family = AF_INET6; + *dual_stack = 1; + *ip_repr = "*"; + return sizeof *s6; +} + +static int httpd_open_listen_socket(struct httpd_server *s) +{ + struct sockaddr_storage ss; + int family, dual, salen, fd, on = 1, off = 0; + char *ip_repr, reprbuf[1 + IP_ADDR_MAX_STR_SIZE + 1]; + + salen = httpd_build_sockaddr(&s->ip, s->port, &ss, &family, &dual, + &ip_repr, reprbuf); + if (salen < 0) + return -1; + + fd = socket(family, SOCK_STREAM, 0); + if (fd < 0) { + LM_ERR("failed to create HTTPD socket: %s\n", strerror(errno)); + return -1; + } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0) + LM_WARN("failed to set SO_REUSEADDR: %s\n", strerror(errno)); + if (dual) + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof off); + + if (bind(fd, (struct sockaddr *)&ss, salen) < 0) { + LM_ERR("failed to bind HTTPD socket on %s:%d: %s\n", + ip_repr, s->port, strerror(errno)); + close(fd); + return -1; + } + if (listen(fd, 1024) < 0) { + LM_ERR("failed to listen on HTTPD socket: %s\n", strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +int httpd_pre_fork(void) +{ + int i; + struct httpd_server *s; + + for (i = 0; i < httpd_n_servers; i++) { + s = &httpd_servers[i]; + s->listen_fd = -1; + if (s->workers <= 1) + continue; + s->listen_fd = httpd_open_listen_socket(s); + if (s->listen_fd < 0) + return -1; + } + return 0; +} + +int httpd_post_fork(void) +{ + int i; + struct httpd_server *s; + + for (i = 0; i < httpd_n_servers; i++) { + s = &httpd_servers[i]; + if (s->listen_fd >= 0) { + close(s->listen_fd); + s->listen_fd = -1; + } + } + return 0; +} + static const str MI_HTTP_U_URL = str_init("" "Unable to parse URL!"); static const str MI_HTTP_U_METHOD = str_init("" @@ -733,6 +847,40 @@ int httpd_callback(int fd, void *dmn, int was_timeout) void httpd_proc(int rank) { struct httpd_cb *cb = httpd_cb_list; + struct httpd_server *s = NULL, *o; + int i, acc = 0; + + for (i = 0; i < httpd_n_servers; i++) { + o = &httpd_servers[i]; + if (rank < acc + o->workers) { + s = o; + break; + } + acc += o->workers; + } + if (!s) { + LM_ERR("cannot map HTTPD worker rank %d to a server\n", rank); + return; + } + + ip = s->ip; + port = s->port; + buffer.len = s->buf_size; + hd_conn_timeout_s = s->conn_timeout; + post_buf_size = s->post_buf_size; + receive_buf_size = s->receive_buf_size; + tls_cert_file = s->tls_cert_file; + tls_key_file = s->tls_key_file; + tls_ciphers = s->tls_ciphers; + httpd_listen_fd = s->listen_fd; + + for (i = 0; i < httpd_n_servers; i++) { + o = &httpd_servers[i]; + if (o != s && o->listen_fd >= 0) { + close(o->listen_fd); + o->listen_fd = -1; + } + } /*child's initial settings*/ if (init_mi_child()!=0) { @@ -740,6 +888,9 @@ void httpd_proc(int rank) return; } + if (buffer.len == 0) + buffer.len = (pkg_mem_size/4); + /* Allocating http response buffer */ buffer.s = (char*)malloc(sizeof(char)*buffer.len); if (buffer.s==NULL) { @@ -747,6 +898,12 @@ void httpd_proc(int rank) return; } + httpd_receive_buff = pkg_malloc(receive_buf_size); + if (httpd_receive_buff == NULL) { + LM_ERR("oom for receive buffer\n"); + return; + } + while(cb) { if (cb->init_proc_callback) cb->init_proc_callback(); @@ -760,7 +917,7 @@ void httpd_proc(int rank) char *key_pem, *cert_pem, *ip_repr, ip6buf[1+IP_ADDR_MAX_STR_SIZE+1]; void *saddr; struct sockaddr_in saddr4; - struct MHD_OptionItem mhd_opts[4]; + struct MHD_OptionItem mhd_opts[5]; const union MHD_DaemonInfo *dmni; int fd; @@ -797,10 +954,6 @@ void httpd_proc(int rank) } else mhd_flags = mhd_flags | MHD_NO_FLAG; - mhd_opts[mhd_opt_n].option = MHD_OPTION_END; - mhd_opts[mhd_opt_n].value = 0; - mhd_opts[mhd_opt_n].ptr_value = NULL; - #if (MHD_VERSION <= 0x00095000) mhd_flags = mhd_flags | MHD_USE_EPOLL_LINUX_ONLY; #else @@ -869,12 +1022,28 @@ void httpd_proc(int rank) httpd_server_info.sin.sin_port = port; #endif + if (httpd_listen_fd >= 0) { + mhd_flags &= ~(MHD_USE_IPv6 | MHD_USE_DUAL_STACK); + mhd_opts[mhd_opt_n].option = MHD_OPTION_LISTEN_SOCKET; + mhd_opts[mhd_opt_n].value = httpd_listen_fd; + mhd_opts[mhd_opt_n].ptr_value = NULL; + mhd_opt_n++; + } else { + mhd_opts[mhd_opt_n].option = MHD_OPTION_SOCK_ADDR; + mhd_opts[mhd_opt_n].value = 0; + mhd_opts[mhd_opt_n].ptr_value = saddr; + mhd_opt_n++; + } + + mhd_opts[mhd_opt_n].option = MHD_OPTION_END; + mhd_opts[mhd_opt_n].value = 0; + mhd_opts[mhd_opt_n].ptr_value = NULL; + LM_DBG("init_child [%d] - [%d] HTTP Server init [%s:%d]\n", rank, getpid(), ip_repr, port); set_proc_attrs("HTTPD %s:%d", ip_repr, port); dmn = MHD_start_daemon(mhd_flags, port, NULL, NULL, &(answer_to_connection), NULL, - MHD_OPTION_SOCK_ADDR, saddr, MHD_OPTION_ARRAY, mhd_opts, MHD_OPTION_END); diff --git a/modules/httpd/httpd_proc.h b/modules/httpd/httpd_proc.h index 7ed68a8e778..41fb3b419ed 100644 --- a/modules/httpd/httpd_proc.h +++ b/modules/httpd/httpd_proc.h @@ -26,6 +26,8 @@ #ifndef _MI_HTTP_HTTPD_PROC_H #define _MI_HTTP_HTTPD_PROC_H +#include "../../str.h" + #ifdef LIBMICROHTTPD #include extern struct MHD_Daemon *dmn; @@ -37,8 +39,28 @@ extern struct MHD_Daemon *dmn; #endif #endif +struct httpd_server { + str name; + str ip; + int port; + int buf_size; + int conn_timeout; + int post_buf_size; + int receive_buf_size; + str tls_cert_file; + str tls_key_file; + str tls_ciphers; + int workers; + int listen_fd; +}; + +extern struct httpd_server *httpd_servers; +extern int httpd_n_servers; + void httpd_proc(int rank); void httpd_proc_destroy(void); +int httpd_pre_fork(void); +int httpd_post_fork(void); #endif