/* * Copyright (c) 2018 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2023-11-12 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #pragma once /** * @file include/maxscale/filter.hh - The public filter interface */ #include #include #include #include #include #include #include #include /** * MXS_FILTER is an opaque type representing a particular filter instance. * * MaxScale itself does not do anything with it, except for receiving it * from the @c createInstance function of a filter module and subsequently * passing it back to the API functions of the filter. */ typedef struct mxs_filter { } MXS_FILTER; /** * MXS_FILTER_SESSION is an opaque type representing the session related * data of a particular filter instance. * * MaxScale itself does not do anything with it, except for receiving it * from the @c newSession function of a filter module and subsequently * passing it back to the API functions of the filter. */ typedef struct mxs_filter_session { } MXS_FILTER_SESSION; /** * @verbatim * The "module object" structure for a filter module. All entry points * marked with `(optional)` are optional entry points which can be set to NULL * if no implementation is required. * * The entry points are: * createInstance Called by the service to create a new instance of the filter * newSession Called to create a new user session within the filter * closeSession Called when a session is closed * freeSession Called when a session is freed * setDownstream Sets the downstream component of the filter pipline * setUpstream Sets the upstream component of the filter pipline * routeQuery Called on each query that requires routing * clientReply Called for each reply packet (optional) * diagnostics Called for diagnostic output * getCapabilities Called to obtain the capabilities of the filter (optional) * destroyInstance Called for destroying a filter instance (optional) * * @endverbatim * * @see load_module */ typedef struct mxs_filter_object { /** * @brief Create a new instance of the filter * * This function is called when a new filter instance is created. The return * value of this function will be passed as the first parameter to the * other API functions. * * @param name Name of the filter instance * @param params Filter parameters * * @return New filter instance on NULL on error */ MXS_FILTER*(*createInstance)(const char* name, MXS_CONFIG_PARAMETER* params); /** * Called to create a new user session within the filter * * This function is called when a new filter session is created for a client. * The return value of this function will be passed as the second parameter * to the @c routeQuery, @c clientReply, @c closeSession, @c freeSession, * @c setDownstream and @c setUpstream functions. * * @param instance Filter instance * @param session Client MXS_SESSION object * * @return New filter session or NULL on error */ MXS_FILTER_SESSION*(*newSession)(MXS_FILTER * instance, MXS_SESSION* session); /** * @brief Called when a session is closed * * The filter should close all objects but not free any memory. * * @param instance Filter instance * @param fsession Filter session */ void (* closeSession)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession); /** * @brief Called when a session is freed * * The session should free all allocated memory in this function. * * @param instance Filter instance * @param fsession Filter session */ void (* freeSession)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession); /** * @brief Sets the downstream component of the filter pipeline * * @param instance Filter instance * @param fsession Filter session */ void (* setDownstream)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession, MXS_DOWNSTREAM* downstream); /** * @brief Sets the upstream component of the filter pipeline * * @param instance Filter instance * @param fsession Filter session */ void (* setUpstream)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession, MXS_UPSTREAM* downstream); /** * @brief Called on each query that requires routing * * TODO: Document how routeQuery should be used * * @param instance Filter instance * @param fsession Filter session * @param queue Request from the client * * @return If successful, the function returns 1. If an error occurs * and the session should be closed, the function returns 0. */ int32_t (* routeQuery)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession, GWBUF* queue); /** * @brief Called for each reply packet * * TODO: Document how clientReply should be used * * @param instance Filter instance * @param fsession Filter session * @param queue Response from the server * * @return If successful, the function returns 1. If an error occurs * and the session should be closed, the function returns 0. */ int32_t (* clientReply)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession, GWBUF* queue); /** * @brief Called for diagnostic output * * @param instance Filter instance * @param fsession Filter session, NULL if general information about the filter is queried * @param dcb DCB where the diagnostic information should be written */ void (* diagnostics)(MXS_FILTER* instance, MXS_FILTER_SESSION* fsession, DCB* dcb); /** * @brief Called for diagnostic output * * @param instance Filter instance * @param fsession Filter session, NULL if general information about the filter is queried * * @return JSON formatted information about the filter * * @see jansson.h */ json_t* (*diagnostics_json)(const MXS_FILTER * instance, const MXS_FILTER_SESSION* fsession); /** * @brief Called to obtain the capabilities of the filter * * @return Zero or more bitwise-or'd values from the mxs_routing_capability_t enum * * @see routing.h */ uint64_t (* getCapabilities)(MXS_FILTER* instance); /** * @brief Called for destroying a filter instance * * @param instance Filter instance */ void (* destroyInstance)(MXS_FILTER* instance); } MXS_FILTER_OBJECT; /** * The filter API version. If the MXS_FILTER_OBJECT structure or the filter API * is changed these values must be updated in line with the rules in the * file modinfo.h. */ #define MXS_FILTER_VERSION {4, 0, 0} /** * MXS_FILTER_DEF represents a filter definition from the configuration file. * Its exact definition is private to MaxScale. */ struct mxs_filter_def; typedef struct mxs_filter_def { } MXS_FILTER_DEF; /** * Get the name of a filter definition. This corresponds to * to a filter section in the configuration file. * * @param filter_def A filter definition. * * @return The filter name. */ const char* filter_def_get_name(const MXS_FILTER_DEF* filter_def); /** * Get module name of a filter definition. * * @param filter_def A filter definition. * * @return The module name. */ const char* filter_def_get_module_name(const MXS_FILTER_DEF* filter_def); /** * Get the filter instance of a particular filter definition. * * @return A filter instance. */ MXS_FILTER* filter_def_get_instance(const MXS_FILTER_DEF* filter_def); /** * Specifies capabilities specific for filters. Common capabilities * are defined by @c routing_capability_t. * * @see enum routing_capability * * @note The values of the capabilities here *must* be between 0x80000000 * and 0x01000000, that is, bits 24 to 31. */ typedef enum filter_capability { FCAP_TYPE_NONE = 0x0 // TODO: remove once filter capabilities are defined } filter_capability_t; namespace maxscale { /** * @class FilterSession filter.hh * * FilterSession is a base class for filter sessions. A concrete filter session * class should be derived from this class and override all relevant functions. * * Note that even though this class is intended to be derived from, no functions * are virtual. That is by design, as the class will be used in a context where * the concrete class is known. That is, there is no need for the virtual mechanism. */ class FilterSession : public MXS_FILTER_SESSION { public: /** * @class Downstream * * An instance of this class represents a component following a filter. */ class Downstream { public: Downstream() { m_data.instance = NULL; m_data.session = NULL; m_data.routeQuery = NULL; } Downstream(const MXS_DOWNSTREAM& down) : m_data(down) { } /** * Function for sending a packet from the client to the next component * in the routing chain towards the backend. * * @param pPacket A packet to be delivered towards the backend. * * @return Whatever the following component returns. */ int routeQuery(GWBUF* pPacket) { return m_data.routeQuery(m_data.instance, m_data.session, pPacket); } MXS_DOWNSTREAM m_data; }; class Upstream { public: /** * @class Upstream * * An instance of this class represents a component preceeding a filter. */ Upstream() { m_data.instance = NULL; m_data.session = NULL; m_data.clientReply = NULL; } Upstream(const MXS_UPSTREAM& up) : m_data(up) { } /** * Function for sending a packet from the backend to the next component * in the routing chain towards the client. * * @param pPacket A packet to be delivered towards the backend. * * @return Whatever the following component returns. */ int clientReply(GWBUF* pPacket) { return m_data.clientReply(m_data.instance, m_data.session, pPacket); } MXS_UPSTREAM m_data; }; /** * The FilterSession instance will be deleted when a client session * has terminated. Will be called only after @c close() has been called. */ ~FilterSession(); /** * Called when a client session has been closed. */ void close(); /** * Called for setting the component following this filter session. * * @param down The component following this filter. */ void setDownstream(const Downstream& down); /** * Called for setting the component preceeding this filter session. * * @param up The component preceeding this filter. */ void setUpstream(const Upstream& up); /** * Called when a packet being is routed to the backend. The filter should * forward the packet to the downstream component. * * @param pPacket A client packet. */ int routeQuery(GWBUF* pPacket); /** * Called when a packet is routed to the client. The filter should * forward the packet to the upstream component. * * @param pPacket A client packet. */ int clientReply(GWBUF* pPacket); /** * Called for obtaining diagnostics about the filter session. * * @param pDcb The dcb where the diagnostics should be written. */ void diagnostics(DCB* pDcb); /** * Called for obtaining diagnostics about the filter session. */ json_t* diagnostics_json() const; protected: FilterSession(MXS_SESSION* pSession); /** * To be called by a filter that short-circuits the request processing. * If this function is called (in routeQuery), the filter must return * without passing the request further. * * @param pResponse The response to be sent to the client. */ void set_response(GWBUF* pResponse) const { session_set_response(m_pSession, &m_up.m_data, pResponse); } protected: MXS_SESSION* m_pSession; /*< The MXS_SESSION this filter session is associated with. */ Downstream m_down; /*< The downstream component. */ Upstream m_up; /*< The upstream component. */ }; /** * @class Filter filter.hh * * An instantiation of the Filter template is used for creating a filter. * Filter is an example of the "Curiously recurring template pattern" * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern * that is used for compile time polymorphism. * * The typical way for using the template is as follows: * * @code * class MyFilterSession : public maxscale::FilterSession * { * // Override the relevant functions. * }; * * class MyFilter : public maxscale::Filter * { * public: * // This creates a new filter instance * static MyFilter* create(const char* zName, MXS_CONFIG_PARAMETER* ppParams); * * // This creates a new session for a filter instance * MyFilterSession* newSession(MXS_SESSION* pSession); * * // Diagnostic function that prints to a DCB * void diagnostics(DCB* pDcb) const; * * // Diagnostic function that returns a JSON object * json_t* diagnostics_json() const; * * // Get filter capabilities * uint64_t getCapabilities(); * }; * @endcode * * The concrete filter class must implement the methods @c create, @c newSession, * @c diagnostics and @c getCapabilities, with the prototypes as shown above. * * The plugin function @c GetModuleObject is then implemented as follows: * * @code * extern "C" MODULE* MXS_CREATE_MODULE() * { * return &MyFilter::s_object; * }; * @endcode */ template class Filter : public MXS_FILTER { public: static MXS_FILTER* createInstance(const char* zName, MXS_CONFIG_PARAMETER* ppParams) { FilterType* pFilter = NULL; MXS_EXCEPTION_GUARD(pFilter = FilterType::create(zName, ppParams)); return pFilter; } static MXS_FILTER_SESSION* newSession(MXS_FILTER* pInstance, MXS_SESSION* pSession) { FilterType* pFilter = static_cast(pInstance); FilterSessionType* pFilterSession = NULL; MXS_EXCEPTION_GUARD(pFilterSession = pFilter->newSession(pSession)); return pFilterSession; } static void closeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData) { FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(pFilterSession->close()); } static void freeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData) { FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(delete pFilterSession); } static void setDownstream(MXS_FILTER*, MXS_FILTER_SESSION* pData, MXS_DOWNSTREAM* pDownstream) { FilterSessionType* pFilterSession = static_cast(pData); typename FilterSessionType::Downstream down(*pDownstream); MXS_EXCEPTION_GUARD(pFilterSession->setDownstream(down)); } static void setUpstream(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, MXS_UPSTREAM* pUpstream) { FilterSessionType* pFilterSession = static_cast(pData); typename FilterSessionType::Upstream up(*pUpstream); MXS_EXCEPTION_GUARD(pFilterSession->setUpstream(up)); } static int routeQuery(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket) { FilterSessionType* pFilterSession = static_cast(pData); int rv = 0; MXS_EXCEPTION_GUARD(rv = pFilterSession->routeQuery(pPacket)); return rv; } static int clientReply(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket) { FilterSessionType* pFilterSession = static_cast(pData); int rv = 0; MXS_EXCEPTION_GUARD(rv = pFilterSession->clientReply(pPacket)); return rv; } static void diagnostics(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, DCB* pDcb) { if (pData) { FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(pFilterSession->diagnostics(pDcb)); } else { FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(pFilter->diagnostics(pDcb)); } } static json_t* diagnostics_json(const MXS_FILTER* pInstance, const MXS_FILTER_SESSION* pData) { json_t* rval = NULL; if (pData) { const FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(rval = pFilterSession->diagnostics_json()); } else { const FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(rval = pFilter->diagnostics_json()); } return rval; } static uint64_t getCapabilities(MXS_FILTER* pInstance) { uint64_t rv = 0; FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(rv = pFilter->getCapabilities()); return rv; } static void destroyInstance(MXS_FILTER* pInstance) { FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(delete pFilter); } static MXS_FILTER_OBJECT s_object; }; template MXS_FILTER_OBJECT Filter::s_object = { &Filter::createInstance, &Filter::newSession, &Filter::closeSession, &Filter::freeSession, &Filter::setDownstream, &Filter::setUpstream, &Filter::routeQuery, &Filter::clientReply, &Filter::diagnostics, &Filter::diagnostics_json, &Filter::getCapabilities, &Filter::destroyInstance, }; }