diff --git a/include/maxscale/listener.hh b/include/maxscale/listener.hh index 94886a33d..e0e0b6b9b 100644 --- a/include/maxscale/listener.hh +++ b/include/maxscale/listener.hh @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -34,25 +35,152 @@ class SERVICE; class SERV_LISTENER { public: + + /** + * Creates a new listener that points to a service + * + * @param service Service where the listener points to + * @param name Name of the listener + * @param address The address where the listener listens + * @param port The port on which the listener listens + * @param protocol The protocol module to use + * @param authenticator The authenticator module to use + * @param auth_opts Options for the authenticator + * @param auth_instance The authenticator instance + * @param ssl The SSL configuration + */ SERV_LISTENER(SERVICE* service, const std::string& name, const std::string& address, uint16_t port, const std::string& protocol, const std::string& authenticator, const std::string& auth_opts, void* auth_instance, SSL_LISTENER* ssl); ~SERV_LISTENER(); -public: - std::string name; /**< Name of the listener */ - std::string protocol; /**< Protocol module to load */ - uint16_t port; /**< Port to listen on */ - std::string address; /**< Address to listen with */ - std::string authenticator; /**< Name of authenticator */ - std::string auth_options; /**< Authenticator options */ - void* auth_instance; /**< Authenticator instance */ - SSL_LISTENER* ssl; /**< Structure of SSL data or NULL */ - struct dcb* listener; /**< The DCB for the listener */ - struct users* users; /**< The user data for this listener */ - SERVICE* service; /**< The service which used by this listener */ - int active; /**< True if the port has not been deleted */ - SERV_LISTENER* next; /**< Next service protocol */ + /** + * Start listening on the configured port + * + * @return True if the listener was able to start listening + */ + bool listen(); + + /** + * Stop the listener + * + * @return True if the listener was successfully stopped + */ + bool stop(); + + /** + * Start a stopped listener + * + * @return True if the listener was successfully started + */ + bool start(); + + /** + * Closes a listener + * + * This closes the network socket the listener listens on to allow immediate reuse of it. The listener + * instance can remain if there are open sessions for it. + */ + void close(); + + /** + * Listener name + */ + const char* name() const; + + /** + * Network address the listener listens on + */ + const char* address() const; + + /** + * Network port the listener listens on + */ + uint16_t port() const; + + /** + * Service the listener points to + */ + SERVICE* service() const; + + /** + * The authenticator module name + */ + const char* authenticator() const; + + /** + * The protocol module name + */ + const char* protocol() const; + + /** + * The authenticator instance + */ + void* auth_instance() const; + + /** + * The state of the listener + */ + const char* state() const; + + /** + * Whether the listener is active + */ + bool is_active() const; + + /** + * Deactivate the listener + * + * TODO: Remove and deactivate via removal from global listener list + */ + void deactivate(); + + /** + * The SSL_LISTENER object + */ + SSL_LISTENER* ssl() const; + + /** + * Convert to JSON + * + * @return JSON representation of the object + */ + json_t* to_json() const; + + /** + * Load users for a listener + * + * @return The result from the authenticator module + */ + int load_users(); + + /** + * Print the users into a DCB + * + * Note: not const due to authenticator API + * + * @param dcb DCB to write into + */ + void print_users(DCB* dcb); + + // Functions that are temporarily public + bool create_listener_config(const char* filename); + struct users* users() const; + void set_users(struct users* u); + +private: + std::string m_name; /**< Name of the listener */ + std::string m_protocol; /**< Protocol module to load */ + uint16_t m_port; /**< Port to listen on */ + std::string m_address; /**< Address to listen with */ + std::string m_authenticator; /**< Name of authenticator */ + std::string m_auth_options; /**< Authenticator options */ + void* m_auth_instance; /**< Authenticator instance */ + SSL_LISTENER* m_ssl; /**< Structure of SSL data or NULL */ + struct dcb* m_listener; /**< The DCB for the listener */ + struct users* m_users; /**< The user data for this listener */ + SERVICE* m_service; /**< The service which used by this listener */ + std::atomic m_active; /**< True if the port has not been deleted */ }; using SListener = std::shared_ptr; @@ -69,15 +197,6 @@ using SListener = std::shared_ptr; */ bool listener_serialize(const SListener& listener); -/** - * @brief Convert listener to JSON - * - * @param listener Listener to convert - * - * @return Converted listener - */ -json_t* listener_to_json(const SListener& listener); - /** * Create a new listener * @@ -110,34 +229,6 @@ SListener listener_alloc(SERVICE* service, */ void listener_free(const SListener& listener); -/** - * Destroy a listener - * - * This deactivates the listener and closes the network port it listens on. Once destroyed, the listener - * can no longer be used. - * - * @param listener Listener to destroy - */ -void listener_destroy(const SListener& listener); - -/** - * Stop a listener - * - * @param listener Listener to stop - * - * @return True if listener was successfully stopped - */ -bool listener_stop(const SListener& listener); - -/** - * Start a stopped listener - * - * @param listener Listener to start - * - * @return True if listener was successfully started - */ -bool listener_start(const SListener& listener); - /** * Find a listener * @@ -183,29 +274,3 @@ bool SSL_LISTENER_init(SSL_LISTENER* ssl); * @param ssl SSL_LISTENER to free */ void SSL_LISTENER_free(SSL_LISTENER* ssl); - -/** - * @brief Check if listener is active - * - * @param listener Listener to check - * - * @return True if listener is active - */ -bool listener_is_active(const SListener& listener); - -/** - * @brief Modify listener active state - * - * @param listener Listener to modify - * @param active True to activate, false to disable - */ -void listener_set_active(const SListener& listener, bool active); - -/** - * Get listener state as a string - * - * @param listener Listener to inspect - * - * @return State of the listener as a string - */ -const char* listener_state_to_string(const SListener& listener); diff --git a/server/core/listener.cc b/server/core/listener.cc index 84ec00dc8..c21538a1c 100644 --- a/server/core/listener.cc +++ b/server/core/listener.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,8 @@ #include #include +#include "internal/modules.hh" + static std::list all_listeners; static std::mutex listener_lock; @@ -43,35 +46,34 @@ static RSA* tmp_rsa_callback(SSL* s, int is_export, int keylength); SERV_LISTENER::SERV_LISTENER(SERVICE* service, const std::string& name, const std::string& address, uint16_t port, const std::string& protocol, const std::string& authenticator, const std::string& auth_opts, void* auth_instance, SSL_LISTENER* ssl) - : name(name) - , protocol(protocol) - , port(port) - , address(address) - , authenticator(authenticator) - , auth_options(auth_opts) - , auth_instance(auth_instance) - , ssl(ssl) - , listener(nullptr) - , users(nullptr) - , service(service) - , active(1) - , next(nullptr) + : m_name(name) + , m_protocol(protocol) + , m_port(port) + , m_address(address) + , m_authenticator(authenticator) + , m_auth_options(auth_opts) + , m_auth_instance(auth_instance) + , m_ssl(ssl) + , m_listener(nullptr) + , m_users(nullptr) + , m_service(service) + , m_active(1) { } SERV_LISTENER::~SERV_LISTENER() { - if (users) + if (m_users) { - users_free(users); + users_free(m_users); } - if (listener) + if (m_listener) { - dcb_close(listener); + dcb_close(m_listener); } - SSL_LISTENER_free(ssl); + SSL_LISTENER_free(m_ssl); } SListener listener_alloc(SERVICE* service, @@ -120,41 +122,41 @@ void listener_free(const SListener& listener) all_listeners.remove(listener); } -void listener_destroy(const SListener& listener) +void SERV_LISTENER::close() { - listener_set_active(listener, false); - listener_stop(listener); + deactivate(); + stop(); // TODO: This is not pretty but it works, revise when listeners are refactored. This is // thread-safe as the listener is freed on the same thread that closes the socket. - close(listener->listener->fd); - listener->listener->fd = -1; + ::close(m_listener->fd); + m_listener->fd = -1; } -bool listener_stop(const SListener& listener) +bool SERV_LISTENER::stop() { bool rval = false; - mxb_assert(listener->listener); + mxb_assert(m_listener); - if (listener->listener->session->state == SESSION_STATE_LISTENER - && poll_remove_dcb(listener->listener) == 0) + if (m_listener->session->state == SESSION_STATE_LISTENER + && poll_remove_dcb(m_listener) == 0) { - listener->listener->session->state = SESSION_STATE_LISTENER_STOPPED; + m_listener->session->state = SESSION_STATE_LISTENER_STOPPED; rval = true; } return rval; } -bool listener_start(const SListener& listener) +bool SERV_LISTENER::start() { bool rval = true; - mxb_assert(listener->listener); + mxb_assert(m_listener); - if (listener->listener->session->state == SESSION_STATE_LISTENER_STOPPED - && poll_add_dcb(listener->listener) == 0) + if (m_listener->session->state == SESSION_STATE_LISTENER_STOPPED + && poll_add_dcb(m_listener) == 0) { - listener->listener->session->state = SESSION_STATE_LISTENER; + m_listener->session->state = SESSION_STATE_LISTENER; rval = true; } @@ -168,7 +170,7 @@ SListener listener_find(const std::string& name) for (const auto& a : all_listeners) { - if (listener_is_active(a) && a->name == name) + if (a->is_active() && a->name() == name) { rval = a; break; @@ -185,7 +187,7 @@ std::vector listener_find_by_service(const SERVICE* service) for (const auto& a : all_listeners) { - if (listener_is_active(a) && a->service == service) + if (a->is_active() && a->service() == service) { rval.push_back(a); } @@ -490,7 +492,7 @@ static RSA* tmp_rsa_callback(SSL* s, int is_export, int keylength) * @param filename Filename where configuration is written * @return True on success, false on error */ -static bool create_listener_config(const SListener& listener, const char* filename) +bool SERV_LISTENER::create_listener_config(const char* filename) { int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); @@ -498,32 +500,32 @@ static bool create_listener_config(const SListener& listener, const char* filena { MXS_ERROR("Failed to open file '%s' when serializing listener '%s': %d, %s", filename, - listener->name.c_str(), + m_name.c_str(), errno, mxs_strerror(errno)); return false; } // TODO: Check for return values on all of the dprintf calls - dprintf(file, "[%s]\n", listener->name.c_str()); + dprintf(file, "[%s]\n", m_name.c_str()); dprintf(file, "type=listener\n"); - dprintf(file, "protocol=%s\n", listener->protocol.c_str()); - dprintf(file, "service=%s\n", listener->service->name); - dprintf(file, "address=%s\n", listener->address.c_str()); - dprintf(file, "port=%u\n", listener->port); - dprintf(file, "authenticator=%s\n", listener->authenticator.c_str()); + dprintf(file, "protocol=%s\n", m_protocol.c_str()); + dprintf(file, "service=%s\n", m_service->name); + dprintf(file, "address=%s\n", m_address.c_str()); + dprintf(file, "port=%u\n", m_port); + dprintf(file, "authenticator=%s\n", m_authenticator.c_str()); - if (!listener->auth_options.empty()) + if (!m_auth_options.empty()) { - dprintf(file, "authenticator_options=%s\n", listener->auth_options.c_str()); + dprintf(file, "authenticator_options=%s\n", m_auth_options.c_str()); } - if (listener->ssl) + if (m_ssl) { - write_ssl_config(file, listener->ssl); + write_ssl_config(file, m_ssl); } - close(file); + ::close(file); return true; } @@ -536,7 +538,7 @@ bool listener_serialize(const SListener& listener) sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(), - listener->name.c_str()); + listener->name()); if (unlink(filename) == -1 && errno != ENOENT) { @@ -545,7 +547,7 @@ bool listener_serialize(const SListener& listener) errno, mxs_strerror(errno)); } - else if (create_listener_config(listener, filename)) + else if (listener->create_listener_config(filename)) { char final_filename[PATH_MAX]; strcpy(final_filename, filename); @@ -570,35 +572,35 @@ bool listener_serialize(const SListener& listener) return rval; } -json_t* listener_to_json(const SListener& listener) +json_t* SERV_LISTENER::to_json() const { json_t* param = json_object(); - json_object_set_new(param, "address", json_string(listener->address.c_str())); - json_object_set_new(param, "port", json_integer(listener->port)); - json_object_set_new(param, "protocol", json_string(listener->protocol.c_str())); - json_object_set_new(param, "authenticator", json_string(listener->authenticator.c_str())); - json_object_set_new(param, "auth_options", json_string(listener->auth_options.c_str())); + json_object_set_new(param, "address", json_string(m_address.c_str())); + json_object_set_new(param, "port", json_integer(m_port)); + json_object_set_new(param, "protocol", json_string(m_protocol.c_str())); + json_object_set_new(param, "authenticator", json_string(m_authenticator.c_str())); + json_object_set_new(param, "auth_options", json_string(m_auth_options.c_str())); - if (listener->ssl) + if (m_ssl) { json_t* ssl = json_object(); - const char* ssl_method = ssl_method_type_to_string(listener->ssl->ssl_method_type); + const char* ssl_method = ssl_method_type_to_string(m_ssl->ssl_method_type); json_object_set_new(ssl, "ssl_version", json_string(ssl_method)); - json_object_set_new(ssl, "ssl_cert", json_string(listener->ssl->ssl_cert)); - json_object_set_new(ssl, "ssl_ca_cert", json_string(listener->ssl->ssl_ca_cert)); - json_object_set_new(ssl, "ssl_key", json_string(listener->ssl->ssl_key)); + json_object_set_new(ssl, "ssl_cert", json_string(m_ssl->ssl_cert)); + json_object_set_new(ssl, "ssl_ca_cert", json_string(m_ssl->ssl_ca_cert)); + json_object_set_new(ssl, "ssl_key", json_string(m_ssl->ssl_key)); json_object_set_new(param, "ssl", ssl); } json_t* attr = json_object(); - json_object_set_new(attr, CN_STATE, json_string(listener_state_to_string(listener))); + json_object_set_new(attr, CN_STATE, json_string(state())); json_object_set_new(attr, CN_PARAMETERS, param); - if (listener->listener->authfunc.diagnostic_json) + if (m_listener->authfunc.diagnostic_json) { - json_t* diag = listener->listener->authfunc.diagnostic_json(listener.get()); + json_t* diag = m_listener->authfunc.diagnostic_json(this); if (diag) { @@ -607,30 +609,68 @@ json_t* listener_to_json(const SListener& listener) } json_t* rval = json_object(); - json_object_set_new(rval, CN_ID, json_string(listener->name.c_str())); + json_object_set_new(rval, CN_ID, json_string(m_name.c_str())); json_object_set_new(rval, CN_TYPE, json_string(CN_LISTENERS)); json_object_set_new(rval, CN_ATTRIBUTES, attr); return rval; } -void listener_set_active(const SListener& listener, bool active) +void SERV_LISTENER::deactivate() { - atomic_store_int32(&listener->active, active ? 1 : 0); + m_active = false; } -bool listener_is_active(const SListener& listener) +bool SERV_LISTENER::is_active() const { - return atomic_load_int32(&listener->active); + return m_active; } -const char* listener_state_to_string(const SListener& listener) +const char* SERV_LISTENER::name() const { - mxb_assert(listener); + return m_name.c_str(); +} - if (listener->listener && listener->listener->session) +const char* SERV_LISTENER::address() const +{ + return m_address.c_str(); +} + +uint16_t SERV_LISTENER::port() const +{ + return m_port; +} + +SERVICE* SERV_LISTENER::service() const +{ + return m_service; +} + +const char* SERV_LISTENER::authenticator() const +{ + return m_authenticator.c_str(); +} + +const char* SERV_LISTENER::protocol() const +{ + return m_protocol.c_str(); +} + +void* SERV_LISTENER::auth_instance() const +{ + return m_auth_instance; +} + +SSL_LISTENER* SERV_LISTENER::ssl() const +{ + return m_ssl; +} + +const char* SERV_LISTENER::state() const +{ + if (m_listener && m_listener->session) { - switch (listener->listener->session->state) + switch (m_listener->session->state) { case SESSION_STATE_LISTENER_STOPPED: return "Stopped"; @@ -648,3 +688,131 @@ const char* listener_state_to_string(const SListener& listener) return "Failed"; } } + +void SERV_LISTENER::print_users(DCB* dcb) +{ + if (m_listener && m_listener->authfunc.diagnostic) + { + dcb_printf(dcb, "User names (%s): ", name()); + + m_listener->authfunc.diagnostic(dcb, this); + + dcb_printf(dcb, "\n"); + } +} + +int SERV_LISTENER::load_users() +{ + int rval = MXS_AUTH_LOADUSERS_OK; + + if (m_listener && m_listener->authfunc.loadusers) + { + rval = m_listener->authfunc.loadusers(this); + } + + return rval; +} + +struct users* SERV_LISTENER::users() const +{ + return m_users; +} + + +void SERV_LISTENER::set_users(struct users* u) +{ + m_users = u; +} + +bool SERV_LISTENER::listen() +{ + m_listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER, this, m_service); + + if (!m_listener) + { + MXS_ERROR("Failed to create listener for service %s.", m_service->name); + return false; + } + + MXS_PROTOCOL* funcs = (MXS_PROTOCOL*)load_module(protocol(), MODULE_PROTOCOL); + + if (!funcs) + { + MXS_ERROR("Unable to load protocol module %s. Listener for service %s not started.", + protocol(), m_service->name); + return false; + } + + memcpy(&(m_listener->func), funcs, sizeof(MXS_PROTOCOL)); + + if (m_authenticator.empty()) + { + m_authenticator = m_listener->func.auth_default(); + } + + MXS_AUTHENTICATOR* authfuncs = (MXS_AUTHENTICATOR*)load_module(authenticator(), MODULE_AUTHENTICATOR); + + if (!authfuncs) + { + MXS_ERROR("Failed to load authenticator module '%s' for listener '%s'", authenticator(), name()); + return false; + } + + // Add protocol and authenticator capabilities from the listener + const MXS_MODULE* proto_mod = get_module(protocol(), MODULE_PROTOCOL); + const MXS_MODULE* auth_mod = get_module(authenticator(), MODULE_AUTHENTICATOR); + mxb_assert(proto_mod && auth_mod); + + // Note: This isn't good: we modify the service from a listener and the service itself should do this. + m_service->capabilities |= proto_mod->module_capabilities | auth_mod->module_capabilities; + + memcpy(&m_listener->authfunc, authfuncs, sizeof(MXS_AUTHENTICATOR)); + + /** + * Normally, we'd allocate the DCB specific authentication data. As the + * listeners aren't normal DCBs, we can skip that. + */ + std::stringstream ss; + ss << m_address << "|" << m_port; + auto config_bind = ss.str(); + + /** Load the authentication users before before starting the listener */ + if (m_listener->authfunc.loadusers) + { + switch (m_listener->authfunc.loadusers(this)) + { + case MXS_AUTH_LOADUSERS_FATAL: + MXS_ERROR("[%s] Fatal error when loading users for listener '%s', " + "service is not started.", m_service->name, name()); + return 0; + + case MXS_AUTH_LOADUSERS_ERROR: + MXS_WARNING("[%s] Failed to load users for listener '%s', authentication" + " might not work.", m_service->name, name()); + break; + + default: + break; + } + } + + if (m_listener->func.listen(m_listener, &config_bind[0])) + { + m_listener->session = session_alloc(m_service, m_listener); + + if (m_listener->session != NULL) + { + m_listener->session->state = SESSION_STATE_LISTENER; + } + else + { + MXS_ERROR("[%s] Failed to create listener session.", m_service->name); + } + } + else + { + MXS_ERROR("[%s] Failed to listen on %s", m_service->name, config_bind.c_str()); + } + + return true; +}