--- a/src/server/daemon/httplistener.c Sun Nov 20 12:43:44 2022 +0100 +++ b/src/server/daemon/httplistener.c Sat Nov 26 17:07:08 2022 +0100 @@ -67,7 +67,33 @@ #define LISTENER_MAX_PROTOCOL_TOKENS 1024 -CxMap *listener_map = NULL; + +#define LISTENER_PROTO_IPV4 "ipv4" +#define LISTENER_PROTO_IPV6 "ipv6" + + +/* + * key: string format: <protocol>:<port> + * value: WSSocket* + * + * protocol: ipv4 | ipv6 + * port: short + */ +static CxMap *listener_socket_map; + +static pthread_mutex_t listener_mutex; + + +int http_listener_global_init(void) { + listener_socket_map = cxHashMapCreate(cxDefaultAllocator, 4); + if(!listener_socket_map) { + return 1; + } + + listener_mutex = PTHREAD_MUTEX_INITIALIZER; + + return 0; +} int start_all_listener() { ServerConfiguration *conf = cfgmgr_get_server_config(); @@ -80,91 +106,244 @@ return 0; } -HttpListener* http_listener_create(ListenerConfig *conf) { - if(listener_map == NULL) { - listener_map = cxHashMapCreate(cxDefaultAllocator, 16); +static HttpSSL* create_http_ssl(ListenerConfig *conf) { + SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ctx) { + return NULL; } - - HttpListener *fl = cxMapGet(listener_map, cx_hash_key(conf->name.ptr, conf->name.length)); - if(fl == NULL) { - return http_listener_new(conf); - } + SSL_CTX_set_options( + ctx, + SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv3); - HttpListener* newls = calloc(1, sizeof(HttpListener)); - if(newls == NULL) { - // TODO: error + int error = 0; + if(conf->disable_proto.ptr) { + cxstring *plist = NULL; + ssize_t n = cx_strsplit_a(cxDefaultAllocator, conf->disable_proto, cx_str(","), LISTENER_MAX_PROTOCOL_TOKENS, &plist); + if(plist) { + for(int i=0;i<n;i++) { + cxstring proto = plist[i]; + log_ereport( + LOG_VERBOSE, + "Listener %s: Disable protocol %s", + conf->name.ptr, + proto.ptr); + if(!cx_strcasecmp(cx_strtrim(proto), cx_str("SSLv2"))) { + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); + } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("SSLv3"))) { + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); + } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1"))) { + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.1"))) { +#ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); +#else + log_ereport( + LOG_WARN, + "Listener: %s: TLSv1.1 not supported", + conf->name.ptr); +#endif + } else if(cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.2"))) { +#ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2); +#else + log_ereport( + LOG_WARN, + "Listener: %s: TLSv1.2 not supported", + conf->name.ptr); +#endif + } else if(cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.3"))) { +#ifdef SSL_OP_NO_TLSv1_3 + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3); +#else + log_ereport( + LOG_WARN, + "Listener: %s: TLSv1.3 not supported", + conf->name.ptr); +#endif + } else { + error = 1; + log_ereport( + LOG_MISCONFIG, + "Listener: %s: Unknown protocol %s", + conf->name.ptr, + proto.ptr); + } + } + free(plist); + } } - newls->name = conf->name; - newls->cfg = conf->cfg; - newls->nacceptors = conf->nacceptors; - newls->default_vs.vs_name = conf->vs.ptr; - newls->port = fl->port; - newls->server_socket = fl->server_socket; - newls->server_socket6 = fl->server_socket6; - newls->running = 1; - newls->threadpool = NULL; - newls->ref = 2; // 1 reference is fl->next - - newls->session_handler = fl->session_handler; // TODO - - // the listener threadpool might be changed - if(conf->threadpool.ptr != NULL) { - newls->threadpool = get_threadpool(cx_strcast(conf->threadpool)); - } - if(newls->threadpool == NULL) { - newls->threadpool = get_default_threadpool(); + if(error) { + SSL_CTX_free(ctx); + return NULL; } - // create acceptor threads - newls->acceptors = calloc(newls->nacceptors, sizeof(void*)); - for (int i=0;i<newls->nacceptors;i++) { - newls->acceptors[i] = acceptor_new(newls); + int ret; + char errbuf[512]; + + error = 0; + if(!conf->chainfile.ptr) { + ret = SSL_CTX_use_certificate_file(ctx, conf->certfile.ptr, SSL_FILETYPE_PEM); + if(!ret) { + ERR_error_string(ERR_get_error(), errbuf); + log_ereport(LOG_MISCONFIG, "Cannot load ssl chain file: %s", errbuf); + error = 1; + } + } else { + ret = SSL_CTX_use_certificate_chain_file(ctx, conf->chainfile.ptr); + if(!ret) { + ERR_error_string(ERR_get_error(), errbuf); + log_ereport(LOG_MISCONFIG, "Cannot load ssl cert file: %s", errbuf); + error = 1; + } + } + + ret = SSL_CTX_use_PrivateKey_file(ctx, conf->privkeyfile.ptr, SSL_FILETYPE_PEM); + if(!ret) { + ERR_error_string(ERR_get_error(), errbuf); + log_ereport(LOG_MISCONFIG, "Cannot load ssl key file: %s", errbuf); + error = 1; + } + + if(error) { + SSL_CTX_free(ctx); + return NULL; } - newls->acceptors6 = calloc(newls->nacceptors, sizeof(void*)); - for (int i=0;i<newls->nacceptors;i++) { - newls->acceptors6[i] = acceptor_new(newls); - newls->acceptors6[i]->ipv6 = TRUE; - } + HttpSSL *ssl = pool_malloc(conf->cfg->pool, sizeof(HttpSSL)); + ZERO(ssl, sizeof(HttpSSL)); + ssl->sslctx = ctx; + + return NULL; +} + +static WSSocket* create_socket(ListenerConfig *conf, const char *protocol) { + WSBool ipv4 = !strcmp(protocol, "ipv4") ? TRUE : FALSE; - // fl hold one reference of newls - fl->next = newls; - - - cxMapPut(listener_map, cx_hash_key(newls->name.ptr, newls->name.length), newls); - - for (int i=0;i<newls->nacceptors;i++) { - //acceptor_start(newls->acceptors[i]); - acceptor_start(newls->acceptors6[i]); + int s = -1; + if(ipv4) { + // ipv4 socket + s = socket(AF_INET, SOCK_STREAM, 0); + } else { + // ipv6 socket + s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + } + if(s < 0) { + log_ereport( + LOG_FAILURE, + "cannot create socket: protocol: %s port: %d error: %s", + protocol, + conf->port, + strerror(errno)); + return NULL; } - // check if a restart is required to apply all changes + // socket options + int o = 1; + setsockopt( + s, + SOL_SOCKET, SO_REUSEADDR, + &o, + sizeof(int)); + +#ifdef LINUX + if(!ipv4) { + o = 1; + setsockopt( + listener->server_socket6, + IPPROTO_IPV6, + IPV6_V6ONLY, + &o, + sizeof(int)); + } +#endif - if(newls->port != conf->port) { - // TODO: log + // bind server socket to address + struct sockaddr_in servaddr4; + struct sockaddr_in6 servaddr6; + struct sockaddr *servaddr; + size_t servaddr_size; + if(ipv4) { + // ipv4 + memset(&servaddr4, 0, sizeof(servaddr4)); + servaddr4.sin_family = AF_INET; + servaddr4.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr4.sin_port = htons(conf->port); + servaddr = (struct sockaddr *)&servaddr4; + servaddr_size = sizeof(servaddr4); + } else { + // ipv6 + memset(&servaddr6, 0, sizeof(servaddr6)); + servaddr6.sin6_family = AF_INET6; + servaddr6.sin6_addr = in6addr_any; + servaddr6.sin6_port = htons(conf->port); + servaddr = (struct sockaddr *)&servaddr6; + servaddr_size = sizeof(servaddr6); } - return newls; + if(bind(s, servaddr, servaddr_size)) { + log_ereport( + LOG_FAILURE, + "cannot bind socket: protocol: %s port: %d error: %s", + protocol, + conf->port, + strerror(errno)); + close(s); + return NULL; + } + + // everything is ok, create WSSocket object + WSSocket *wssocket = malloc(sizeof(WSSocket)); + if(!wssocket) { + close(s); + return NULL; + } + ZERO(wssocket, sizeof(WSSocket)); + wssocket->socket = s; + + return wssocket; } -HttpListener* http_listener_new(ListenerConfig *conf) { - // TODO: remove - if(listener_map == NULL) { - listener_map = cxHashMapCreate(cxDefaultAllocator, 16); +static WSSocket* get_socket(ListenerConfig *conf, const char *protocol) { + char key_data[32]; + size_t key_len = snprintf(key_data, 32, "%s:%d", protocol, conf->port); + CxHashKey key = cx_hash_key(key_data, key_len); + + WSSocket *sock = cxMapGet(listener_socket_map, key); + if(!sock) { + sock = create_socket(conf, protocol); + if(sock) { + cxMapPut(listener_socket_map, key, sock); + } } + return sock; +} - HttpListener *fl = cxMapGet(listener_map, cx_hash_key(conf->name.ptr, conf->name.length)); - if(fl != NULL) { - return fl; +/* + * returns HttpSSL of socket1 or socket2 if ssl is available + */ +static HttpSSL* socket_get_ssl(WSSocket *socket1, WSSocket *socket2) { + if(socket1 && socket1->ssl) { + return socket1->ssl; + } + if(socket2 && socket2->ssl) { + return socket2->ssl; } - // end remove + return NULL; +} - HttpListener *listener = malloc(sizeof(HttpListener)); +static HttpListener* listener_create(ListenerConfig *conf) { + pool_handle_t *pool = conf->cfg->pool; + HttpListener *listener = pool_malloc(pool, sizeof(HttpListener)); + if(!listener) { + return NULL; + } + ZERO(listener, sizeof(HttpListener)); + listener->running = 0; listener->cfg = conf->cfg; - listener->name = conf->name; - listener->default_vs.vs_name = conf->vs.ptr; + listener->name = cx_strdup_a(pool_allocator(pool), cx_strcast(conf->name)); + listener->default_vs.vs_name = pool_strdup(pool, conf->vs.ptr); listener->threadpool = NULL; if(conf->threadpool.ptr != NULL) { listener->threadpool = get_threadpool(cx_strcast(conf->threadpool)); @@ -173,9 +352,9 @@ listener->threadpool = get_default_threadpool(); } if(conf->blockingio) { - listener->session_handler = create_basic_session_handler(); + listener->session_handler = create_basic_session_handler(pool); } else { - listener->session_handler = create_event_session_handler(); + listener->session_handler = create_event_session_handler(pool); } listener->nacceptors = conf->nacceptors; listener->port = conf->port; @@ -183,170 +362,40 @@ listener->next = NULL; listener->ssl = NULL; - int error = 0; + // create sockets + listener->server_socket = get_socket(conf, LISTENER_PROTO_IPV4); + listener->server_socket6 = get_socket(conf, LISTENER_PROTO_IPV6); + if(!listener->server_socket && !listener->server_socket6) { + log_ereport(LOG_FAILURE, "Listener %s: no server socket", conf->name.ptr); + return NULL; + } + if(listener->server_socket) { + wssocket_ref(listener->server_socket); + } + if(listener->server_socket6) { + wssocket_ref(listener->server_socket6); + } + + // create SSL context if(conf->ssl) { - listener->ssl = malloc(sizeof(HttpSSL)); - - SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method()); - SSL_CTX_set_options( - ctx, - SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv3); - if(conf->disable_proto.ptr) { - cxstring *plist = NULL; - ssize_t n = cx_strsplit_a(cxDefaultAllocator, conf->disable_proto, cx_str(","), LISTENER_MAX_PROTOCOL_TOKENS, &plist); - if(plist) { - for(int i=0;i<n;i++) { - cxstring proto = plist[i]; - log_ereport( - LOG_VERBOSE, - "Listener %s: Disable protocol %s", - listener->name.ptr, - proto.ptr); - if(!cx_strcasecmp(cx_strtrim(proto), cx_str("SSLv2"))) { - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); - } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("SSLv3"))) { - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); - } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1"))) { - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); - } else if(!cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.1"))) { -#ifdef SSL_OP_NO_TLSv1_1 - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); -#else - log_ereport( - LOG_WARN, - "Listener: %s: TLSv1.1 already not supported", - listener->name.ptr); -#endif - } else if(cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.2"))) { -#ifdef SSL_OP_NO_TLSv1_2 - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2); -#else - log_ereport( - LOG_WARN, - "Listener: %s: TLSv1.2 already not supported", - listener->name.ptr); -#endif - } else if(cx_strcasecmp(cx_strtrim(proto), cx_str("TLSv1.3"))) { -#ifdef SSL_OP_NO_TLSv1_3 - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3); -#else - log_ereport( - LOG_WARN, - "Listener: %s: TLSv1.3 already not supported", - listener->name.ptr); -#endif - } else { - error = 1; - log_ereport( - LOG_MISCONFIG, - "Listener: %s: Unknown protocol %s", - listener->name.ptr, - proto.ptr); - } - } - free(plist); - } - } - - if(error) { - return NULL; - } - // TODO: cleanup on error - - int ret; - char errbuf[512]; - - if(!conf->chainfile.ptr) { - ret = SSL_CTX_use_certificate_file(ctx, conf->certfile.ptr, SSL_FILETYPE_PEM); - if(!ret) { - ERR_error_string(ERR_get_error(), errbuf); - log_ereport(LOG_MISCONFIG, "Cannot load ssl chain file: %s", errbuf); - return NULL; - } - } else { - ret = SSL_CTX_use_certificate_chain_file(ctx, conf->chainfile.ptr); - if(!ret) { - ERR_error_string(ERR_get_error(), errbuf); - log_ereport(LOG_MISCONFIG, "Cannot load ssl cert file: %s", errbuf); + HttpSSL *ssl = socket_get_ssl(listener->server_socket, listener->server_socket6); + if(!ssl) { + ssl = create_http_ssl(conf); + if(!ssl) { + log_ereport(LOG_FAILURE, "Listener %s: cannot create SSL context", conf->name.ptr); return NULL; } } - - ret = SSL_CTX_use_PrivateKey_file(ctx, conf->privkeyfile.ptr, SSL_FILETYPE_PEM); - if(!ret) { - ERR_error_string(ERR_get_error(), errbuf); - log_ereport(LOG_MISCONFIG, "Cannot load ssl key file: %s", errbuf); - return NULL; + if(listener->server_socket) { + listener->server_socket->ssl = ssl; } - - // TODO: chain - listener->ssl->sslctx = ctx; - } - - - cxMapPut(listener_map, cx_hash_key(listener->name.ptr, listener->name.length), listener); - - struct sockaddr_in servaddr; /* server address */ - struct sockaddr_in6 servaddr6; - - /* init address structure */ - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = htonl(INADDR_ANY); - servaddr.sin_port = htons(conf->port); - - memset(&servaddr6, 0, sizeof(servaddr6)); - servaddr6.sin6_family = AF_INET6; - servaddr6.sin6_addr = in6addr_any; - servaddr6.sin6_port = htons(conf->port); - - /* create socket */ - if((listener->server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - perror("Error: http_listener_new: socket"); - return NULL; - } - - if((listener->server_socket6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { - perror("Error: http_listener_new: socket v6"); - return NULL; - } - - int o = 1; - setsockopt( - listener->server_socket, - SOL_SOCKET, SO_REUSEADDR, - &o, - sizeof(int)); - o = 1; - setsockopt( - listener->server_socket6, - SOL_SOCKET, SO_REUSEADDR, - &o, - sizeof(int)); - -#ifdef LINUX - o = 1; - setsockopt( - listener->server_socket6, - IPPROTO_IPV6, - IPV6_V6ONLY, - &o, - sizeof(int)); -#endif - - /* bind server socket to address */ - if(bind(listener->server_socket, (struct sockaddr*)&servaddr, sizeof(servaddr))){ - log_ereport(LOG_FAILURE, "http_listener_new: bind failed. Port: %d", conf->port); - return NULL; - } - - if(bind(listener->server_socket6, (struct sockaddr*)&servaddr6, sizeof(servaddr6))){ - log_ereport(LOG_FAILURE, "http_listener_new: bind v6 failed. Port: %d: %s", conf->port, strerror(errno)); - return NULL; + if(listener->server_socket6) { + listener->server_socket6->ssl = ssl; + } } - /* create acceptors */ + // create acceptors listener->acceptors = calloc(listener->nacceptors, sizeof(void*)); listener->acceptors6 = calloc(listener->nacceptors, sizeof(void*)); for (int i=0;i<listener->nacceptors;i++) { @@ -358,26 +407,45 @@ return listener; } +HttpListener* http_listener_create(ListenerConfig *conf) { + pthread_mutex_lock(&listener_mutex); + HttpListener *listener = listener_create(conf); + pthread_mutex_unlock(&listener_mutex); + return listener; +} + int http_listener_start(HttpListener *listener) { if(listener->running) { return 0; } log_ereport(LOG_INFORM, "start listener on port %d", listener->port); + + WSBool ipv4 = listener->server_socket ? TRUE : FALSE; + WSBool ipv6 = listener->server_socket6 ? TRUE: FALSE; - if (listen(listener->server_socket, 256) == -1) { + if (ipv4 && !listener->server_socket->listening && listen(listener->server_socket->socket, 256) == -1) { log_ereport(LOG_FAILURE, "http_listener_start: listen failed: %s", strerror(errno)); return -1; + } else { + listener->server_socket->listening = TRUE; } - if (listen(listener->server_socket6, 256) == -1) { + if (ipv6 && !listener->server_socket6->listening && listen(listener->server_socket6->socket, 256) == -1) { log_ereport(LOG_FAILURE, "http_listener_start: listen v6 failed: %s", strerror(errno)); return -1; + } else { + listener->server_socket6->listening = TRUE; } - /* start acceptor threads */ + // start acceptor threads for (int i=0;i<listener->nacceptors;i++) { - acceptor_start(listener->acceptors[i]); - acceptor_start(listener->acceptors6[i]); + if(ipv4) { + acceptor_start(listener->acceptors[i]); + } + if(ipv6) { + acceptor_start(listener->acceptors6[i]); + } } + listener->running = TRUE; return 0; } @@ -389,6 +457,7 @@ void http_listener_unref(HttpListener *listener) { uint32_t ref = ws_atomic_dec32(&listener->ref); if(ref == 0) { + log_ereport(LOG_VERBOSE, "HttpListener %s: destroy", listener->name.ptr); free(listener->acceptors); // TODO: unref cfg // TODO: unref session handler @@ -412,7 +481,7 @@ (void*(*)(void*))acceptor_thread, a) != 0) { - perror("Error: acceptor_start: pthread_create"); + log_ereport(LOG_FAILURE, "Listener %s: acceptor_start: %s", a->listener->name.ptr, strerror(errno)); } } @@ -431,12 +500,12 @@ socklen_t ca_length; ConnectionAddrType addr_type; if(acceptor->ipv6) { - server_socket = listener->server_socket6; + server_socket = listener->server_socket6->socket; ca_ptr = (struct sockaddr*)&ca.address_v6; ca_length = sizeof(ca.address_v6); addr_type = CONN_ADDR_IPV6; } else { - server_socket = listener->server_socket; + server_socket = listener->server_socket->socket; ca_ptr = (struct sockaddr*)&ca.address_v4; ca_length = sizeof(ca.address_v4); addr_type = CONN_ADDR_IPV4; @@ -444,11 +513,11 @@ for (;;) { - /* accept connections */ + // accept connections int clientfd; socklen_t length = ca_length; - /* accept a connection */ + // accept a connection clientfd = accept( server_socket, ca_ptr, @@ -512,6 +581,7 @@ if(acceptor_exit) { // this acceptor is outdated + log_ereport(LOG_VERBOSE, "acceptor thread %p: exit", (void*)acceptor->tid); break; } } @@ -521,3 +591,13 @@ return NULL; } + +void wssocket_ref(WSSocket *ws) { + ws_atomic_inc32(&ws->ref); +} + +void wssocket_unref(WSSocket *ws) { + // does nothing yet, because maybe it is not a good idea to destroy + // a socket + ws_atomic_dec32(&ws->ref); +}