Merge branch 'develop' into MXS-1075
This commit is contained in:
@ -20,13 +20,6 @@ different backend authentication module is not supported.
|
||||
|
||||
### Limitations in the MySQL authenticator (MySQLAuth)
|
||||
|
||||
* MariaDB MaxScale supports authentication that uses wildcard matching in
|
||||
hostnames in the `mysql.user` table of the backend database. For IP address
|
||||
entries either `%` or `_`-wildcards are accepted, they should not be mixed in
|
||||
the same entry. For text addresses both wildcards can be mixed.
|
||||
|
||||
* Wildcards in text-form hostnames are not supported.
|
||||
|
||||
* MySQL old style passwords are not supported. MySQL versions 4.1 and newer use a
|
||||
new authentication protocol which does not support pre-4.1 style passwords.
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ NOTE This feature was available already in _2.1.0_.
|
||||
|
||||
[Here is a list of bugs fixed since the release of MaxScale 2.1.0.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.1.1%20AND%20fixVersion%20NOT%20IN%20(2.1.0))
|
||||
|
||||
* [MXS-1178](https://jira.mariadb.org/browse/MXS-1178) master_accept_reads doesn't work with detect_replication_lag
|
||||
* [MXS-1165](https://jira.mariadb.org/browse/MXS-1165) MaxInfo eat too much memory when getting list of session and client.
|
||||
* [MXS-1143](https://jira.mariadb.org/browse/MXS-1143) Add support for new MariaDB 10.2 flags
|
||||
* [MXS-1130](https://jira.mariadb.org/browse/MXS-1130) Unexpected length encoding 'ff' encountered
|
||||
|
||||
52
Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md
Normal file
52
Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md
Normal file
@ -0,0 +1,52 @@
|
||||
# MariaDB MaxScale 2.1.2 Release Notes
|
||||
|
||||
Release 2.1.2 is a Beta release.
|
||||
|
||||
This document describes the changes in release 2.1.2, when compared to
|
||||
release [2.1.1](MaxScale-2.1.1-Release-Notes.md).
|
||||
|
||||
If you are upgrading from release 2.0, please also read the following
|
||||
release notes:
|
||||
[2.1.1](./MaxScale-2.1.1-Release-Notes.md)
|
||||
[2.1.0](./MaxScale-2.1.0-Release-Notes.md)
|
||||
|
||||
For any problems you encounter, please consider submitting a bug
|
||||
report at [Jira](https://jira.mariadb.org).
|
||||
|
||||
## Changed Features
|
||||
|
||||
### Improved Wildcard Matching
|
||||
|
||||
The MySQLAuth module now supports all types of wildcards for both IP addresses
|
||||
as well as hostnames.
|
||||
|
||||
## New Features
|
||||
|
||||
### IPv6 Support
|
||||
|
||||
MaxScale now supports IPv6 connections on both the client and backend side as
|
||||
well as being able to listen on IPv6 addresses.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
[Here is a list of bugs fixed since the release of MaxScale 2.1.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.1.2%20AND%20fixVersion%20NOT%20IN%20(2.1.1))
|
||||
|
||||
## Known Issues and Limitations
|
||||
|
||||
There are some limitations and known issues within this version of MaxScale.
|
||||
For more information, please refer to the [Limitations](../About/Limitations.md) document.
|
||||
|
||||
## Packaging
|
||||
|
||||
RPM and Debian packages are provided for the Linux distributions supported
|
||||
by MariaDB Enterprise.
|
||||
|
||||
Packages can be downloaded [here](https://mariadb.com/resources/downloads).
|
||||
|
||||
## Source Code
|
||||
|
||||
The source code of MaxScale is tagged at GitHub with a tag, which is identical
|
||||
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
|
||||
is X.Y.Z. Further, *master* always refers to the latest released non-beta version.
|
||||
|
||||
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).
|
||||
@ -195,7 +195,7 @@ typedef struct dcb
|
||||
int flags; /**< DCB flags */
|
||||
char *remote; /**< Address of remote end */
|
||||
char *user; /**< User name for connection */
|
||||
struct sockaddr_in ipv4; /**< remote end IPv4 address */
|
||||
struct sockaddr_storage ip; /**< remote IPv4/IPv6 address */
|
||||
char *protoname; /**< Name of the protocol */
|
||||
void *protocol; /**< The protocol specific state */
|
||||
size_t protocol_packet_length; /**< How long the protocol specific packet is */
|
||||
@ -236,7 +236,7 @@ typedef struct dcb
|
||||
} DCB;
|
||||
|
||||
#define DCB_INIT {.dcb_chk_top = CHK_NUM_DCB, \
|
||||
.evq = DCBEVENTQ_INIT, .ipv4 = {0}, .func = {0}, .authfunc = {0}, \
|
||||
.evq = DCBEVENTQ_INIT, .ip = {0}, .func = {0}, .authfunc = {0}, \
|
||||
.stats = {0}, .memdata = DCBMM_INIT, \
|
||||
.fd = DCBFD_CLOSED, .stats = DCBSTATS_INIT, .ssl_state = SSL_HANDSHAKE_UNKNOWN, \
|
||||
.state = DCB_STATE_ALLOC, .dcb_chk_tail = CHK_NUM_DCB, \
|
||||
@ -339,6 +339,14 @@ void dcb_process_idle_sessions(int thr);
|
||||
*/
|
||||
bool dcb_foreach(bool (*func)(DCB *, void *), void *data);
|
||||
|
||||
/**
|
||||
* @brief Return the port number this DCB is connected to
|
||||
*
|
||||
* @param dcb DCB to inspect
|
||||
* @return Port number the DCB is connected to or -1 if information is not available
|
||||
*/
|
||||
int dcb_get_port(const DCB *dcb);
|
||||
|
||||
/**
|
||||
* DCB flags values
|
||||
*/
|
||||
|
||||
@ -407,7 +407,7 @@ bool protocol_get_response_status (MySQLProtocol* p, int* npackets, ssize_t* nby
|
||||
void protocol_set_response_status (MySQLProtocol* p, int npackets, ssize_t nbytes);
|
||||
void protocol_archive_srv_command(MySQLProtocol* p);
|
||||
|
||||
char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db, int);
|
||||
char* create_auth_fail_str(char *username, char *hostaddr, bool password, char *db, int);
|
||||
|
||||
void init_response_status (
|
||||
GWBUF* buf,
|
||||
|
||||
@ -324,6 +324,17 @@ static inline bool session_set_autocommit(MXS_SESSION* ses, bool autocommit)
|
||||
*/
|
||||
MXS_SESSION* session_get_by_id(int id);
|
||||
|
||||
/**
|
||||
* @brief Close a session
|
||||
*
|
||||
* Calling this function will start the session shutdown process. The shutdown
|
||||
* closes all related backend DCBs by calling the closeSession entry point
|
||||
* of the router session.
|
||||
*
|
||||
* @param session The session to close
|
||||
*/
|
||||
void session_close(MXS_SESSION *session);
|
||||
|
||||
/**
|
||||
* @brief Release a session reference
|
||||
*
|
||||
|
||||
@ -35,13 +35,58 @@ MXS_BEGIN_DECLS
|
||||
*/
|
||||
#define MXS_PTR(a, b) (((uint8_t*)(a)) + (b))
|
||||
|
||||
/** The type of the socket */
|
||||
enum mxs_socket_type
|
||||
{
|
||||
MXS_SOCKET_LISTENER, /**< */
|
||||
MXS_SOCKET_NETWORK,
|
||||
};
|
||||
|
||||
bool utils_init(); /*< Call this first before using any other function */
|
||||
void utils_end();
|
||||
|
||||
int setnonblocking(int fd);
|
||||
int parse_bindconfig(const char *, struct sockaddr_in *);
|
||||
int setipaddress(struct in_addr *, char *);
|
||||
/**
|
||||
* @brief Create a network socket and a socket configuration
|
||||
*
|
||||
* This helper function can be used to open both listener socket and network
|
||||
* connection sockets. For listener sockets, the @c host and @c port parameters
|
||||
* tell where the socket will bind to. For network sockets, the parameters tell
|
||||
* where the connection is created.
|
||||
*
|
||||
* After calling this function, the only thing that needs to be done is to
|
||||
* give @c addr and the return value of this function as the parameters to
|
||||
* either bind() (for listeners) or connect() (for outbound network connections).
|
||||
*
|
||||
* @param type Type of the socket, either MXS_SOCKET_LISTENER for a listener
|
||||
* socket or MXS_SOCKET_NETWORK for a network connection socket
|
||||
* @param addr Pointer to a struct sockaddr_storage where the socket
|
||||
* configuration is stored
|
||||
* @param host The target host for which the socket is created
|
||||
* @param port The target port on the host
|
||||
*
|
||||
* @return The opened socket or -1 on failure
|
||||
*/
|
||||
int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr,
|
||||
const char *host, uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Create a UNIX domain socket
|
||||
*
|
||||
* This opens and prepares a UNIX domain socket for use. The @c addr parameter
|
||||
* can be given to the bind() function to bind the socket.
|
||||
*
|
||||
* @param type Type of the socket, either MXS_SOCKET_LISTENER for a listener
|
||||
* socket or MXS_SOCKET_NETWORK for a network connection socket
|
||||
* @param addr Pointer to a struct sockaddr_un where the socket configuration
|
||||
* is stored
|
||||
* @param path Path to the socket
|
||||
*
|
||||
* @return The opened socket or -1 on failure
|
||||
*/
|
||||
int open_unix_socket(enum mxs_socket_type type, struct sockaddr_un *addr,
|
||||
const char *path);
|
||||
|
||||
int setnonblocking(int fd);
|
||||
char *gw_strend(register const char *s);
|
||||
static char gw_randomchar();
|
||||
int gw_generate_random_str(char *output, int len);
|
||||
|
||||
@ -83,7 +83,6 @@ typedef struct duplicate_context
|
||||
static bool duplicate_context_init(DUPLICATE_CONTEXT* context);
|
||||
static void duplicate_context_finish(DUPLICATE_CONTEXT* context);
|
||||
|
||||
extern int setipaddress(struct in_addr *, char *);
|
||||
static bool process_config_context(CONFIG_CONTEXT *);
|
||||
static bool process_config_update(CONFIG_CONTEXT *);
|
||||
static char *config_get_value(MXS_CONFIG_PARAMETER *, const char *);
|
||||
@ -3401,16 +3400,14 @@ bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key,
|
||||
break;
|
||||
|
||||
case MXS_MODULE_PARAM_SERVICE:
|
||||
if ((context && config_contains_type(context, value, "service")) ||
|
||||
service_find(value))
|
||||
if (context && config_contains_type(context, value, "service"))
|
||||
{
|
||||
valid = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case MXS_MODULE_PARAM_SERVER:
|
||||
if ((context && config_contains_type(context, value, "server")) ||
|
||||
server_find_by_unique_name(value))
|
||||
if (context && config_contains_type(context, value, "server"))
|
||||
{
|
||||
valid = true;
|
||||
}
|
||||
|
||||
@ -152,8 +152,8 @@ static int gw_write(DCB *dcb, GWBUF *writeq, bool *stop_writing);
|
||||
static int gw_write_SSL(DCB *dcb, GWBUF *writeq, bool *stop_writing);
|
||||
static int dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret);
|
||||
static int dcb_accept_one_connection(DCB *listener, struct sockaddr *client_conn);
|
||||
static int dcb_listen_create_socket_inet(const char *config_bind);
|
||||
static int dcb_listen_create_socket_unix(const char *config_bind);
|
||||
static int dcb_listen_create_socket_inet(const char *host, uint16_t port);
|
||||
static int dcb_listen_create_socket_unix(const char *path);
|
||||
static int dcb_set_socket_option(int sockfd, int level, int optname, void *optval, socklen_t optlen);
|
||||
static void dcb_add_to_all_list(DCB *dcb);
|
||||
static DCB *dcb_find_free();
|
||||
@ -2711,25 +2711,30 @@ dcb_accept(DCB *listener)
|
||||
if (client_conn.ss_family == AF_UNIX)
|
||||
{
|
||||
// client address
|
||||
// Should this be `localhost` like it is in the MariaDB server?
|
||||
client_dcb->remote = MXS_STRDUP_A("localhost_from_socket");
|
||||
// set localhost IP for user authentication
|
||||
(client_dcb->ipv4).sin_addr.s_addr = 0x0100007F;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* client IPv4 in raw data*/
|
||||
memcpy(&client_dcb->ipv4,
|
||||
(struct sockaddr_in *)&client_conn,
|
||||
sizeof(struct sockaddr_in));
|
||||
/* client IPv4 in string representation */
|
||||
client_dcb->remote = (char *)MXS_CALLOC(INET_ADDRSTRLEN + 1, sizeof(char));
|
||||
/* client IP in raw data*/
|
||||
memcpy(&client_dcb->ip, &client_conn, sizeof(client_conn));
|
||||
/* client IP in string representation */
|
||||
client_dcb->remote = (char *)MXS_CALLOC(INET6_ADDRSTRLEN + 1, sizeof(char));
|
||||
|
||||
if (client_dcb->remote != NULL)
|
||||
if (client_dcb->remote)
|
||||
{
|
||||
inet_ntop(AF_INET,
|
||||
&(client_dcb->ipv4).sin_addr,
|
||||
client_dcb->remote,
|
||||
INET_ADDRSTRLEN);
|
||||
void *ptr;
|
||||
if (client_dcb->ip.ss_family == AF_INET)
|
||||
{
|
||||
ptr = &((struct sockaddr_in*)&client_dcb->ip)->sin_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = &((struct sockaddr_in6*)&client_dcb->ip)->sin6_addr;
|
||||
}
|
||||
|
||||
inet_ntop(client_dcb->ip.ss_family, ptr,
|
||||
client_dcb->remote, INET6_ADDRSTRLEN);
|
||||
}
|
||||
}
|
||||
memcpy(&client_dcb->func, protocol_funcs, sizeof(MXS_PROTOCOL));
|
||||
@ -2884,22 +2889,38 @@ dcb_accept_one_connection(DCB *listener, struct sockaddr *client_conn)
|
||||
* @param protocol_name Name of protocol that is listening
|
||||
* @return 0 if new listener created successfully, otherwise -1
|
||||
*/
|
||||
int
|
||||
dcb_listen(DCB *listener, const char *config, const char *protocol_name)
|
||||
int dcb_listen(DCB *listener, const char *config, const char *protocol_name)
|
||||
{
|
||||
int listener_socket;
|
||||
char host[strlen(config) + 1];
|
||||
strcpy(host, config);
|
||||
char *port_str = strrchr(host, '|');
|
||||
uint16_t port = 0;
|
||||
|
||||
listener->fd = -1;
|
||||
if (strchr(config, '/'))
|
||||
if (port_str)
|
||||
{
|
||||
listener_socket = dcb_listen_create_socket_unix(config);
|
||||
*port_str++ = 0;
|
||||
port = atoi(port_str);
|
||||
}
|
||||
|
||||
int listener_socket = -1;
|
||||
|
||||
if (strchr(host, '/'))
|
||||
{
|
||||
listener_socket = dcb_listen_create_socket_unix(host);
|
||||
}
|
||||
else if (port > 0)
|
||||
{
|
||||
listener_socket = dcb_listen_create_socket_inet(host, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
listener_socket = dcb_listen_create_socket_inet(config);
|
||||
// We don't have a socket path or a network port
|
||||
ss_dassert(false);
|
||||
}
|
||||
|
||||
if (listener_socket < 0)
|
||||
{
|
||||
ss_dassert(listener_socket == -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -2912,17 +2933,13 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name)
|
||||
*/
|
||||
if (listen(listener_socket, INT_MAX) != 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to start listening on '%s' with protocol '%s': %d, %s",
|
||||
config,
|
||||
protocol_name,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
config, protocol_name, errno, mxs_strerror(errno));
|
||||
close(listener_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MXS_NOTICE("Listening connections at %s with protocol %s", config, protocol_name);
|
||||
MXS_NOTICE("Listening for connections at %s with protocol %s", config, protocol_name);
|
||||
|
||||
// assign listener_socket to dcb
|
||||
listener->fd = listener_socket;
|
||||
@ -2938,151 +2955,41 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a listening socket, TCP
|
||||
* @brief Create a network listener socket
|
||||
*
|
||||
* Parse the configuration provided and if valid create a socket.
|
||||
* Set options, set non-blocking and bind to the socket.
|
||||
*
|
||||
* @param config_bind The configuration information
|
||||
* @return socket if successful, -1 otherwise
|
||||
* @param host The network address to listen on
|
||||
* @param port The port to listen on
|
||||
* @return The opened socket or -1 on error
|
||||
*/
|
||||
static int
|
||||
dcb_listen_create_socket_inet(const char *config_bind)
|
||||
static int dcb_listen_create_socket_inet(const char *host, uint16_t port)
|
||||
{
|
||||
int listener_socket;
|
||||
struct sockaddr_in server_address;
|
||||
int one = 1;
|
||||
|
||||
memset(&server_address, 0, sizeof(server_address));
|
||||
if (!parse_bindconfig(config_bind, &server_address))
|
||||
{
|
||||
MXS_ERROR("Error in parse_bindconfig for [%s]", config_bind);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Create the TCP socket */
|
||||
if ((listener_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Can't create socket: %i, %s",
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// socket options
|
||||
if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) != 0 ||
|
||||
dcb_set_socket_option(listener_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// set NONBLOCKING mode
|
||||
if (setnonblocking(listener_socket) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to set socket to non-blocking mode.");
|
||||
close(listener_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind(listener_socket, (struct sockaddr *) &server_address, sizeof(server_address)) < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to bind on '%s': %i, %s",
|
||||
config_bind,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(listener_socket);
|
||||
return -1;
|
||||
}
|
||||
return listener_socket;
|
||||
struct sockaddr_storage server_address = {};
|
||||
return open_network_socket(MXS_SOCKET_LISTENER, &server_address, host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a listening socket, Unix
|
||||
* @brief Create a Unix domain socket
|
||||
*
|
||||
* Parse the configuration provided and if valid create a socket.
|
||||
* Set options, set non-blocking and bind to the socket.
|
||||
*
|
||||
* @param config_bind The configuration information
|
||||
* @return socket if successful, -1 otherwise
|
||||
* @param path The socket path
|
||||
* @return The opened socket or -1 on error
|
||||
*/
|
||||
static int
|
||||
dcb_listen_create_socket_unix(const char *config_bind)
|
||||
static int dcb_listen_create_socket_unix(const char *path)
|
||||
{
|
||||
int listener_socket;
|
||||
struct sockaddr_un local_addr;
|
||||
int one = 1;
|
||||
|
||||
char *tmp = strrchr(config_bind, ':');
|
||||
if (tmp)
|
||||
if (unlink(path) == -1 && errno != ENOENT)
|
||||
{
|
||||
*tmp = '\0';
|
||||
}
|
||||
|
||||
if (strlen(config_bind) > sizeof(local_addr.sun_path) - 1)
|
||||
{
|
||||
MXS_ERROR("The path %s specified for the UNIX domain socket is too long. "
|
||||
"The maximum length is %lu.", config_bind, sizeof(local_addr.sun_path) - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// UNIX socket create
|
||||
if ((listener_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Can't create UNIX socket: %i, %s",
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// socket options
|
||||
if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// set NONBLOCKING mode
|
||||
if (setnonblocking(listener_socket) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to set socket to non-blocking mode.");
|
||||
close(listener_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&local_addr, 0, sizeof(local_addr));
|
||||
local_addr.sun_family = AF_UNIX;
|
||||
strcpy(local_addr.sun_path, config_bind);
|
||||
|
||||
if ((-1 == unlink(config_bind)) && (errno != ENOENT))
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to unlink Unix Socket %s: %d %s",
|
||||
config_bind, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
path, errno, mxs_strerror(errno));
|
||||
}
|
||||
|
||||
/* Bind the socket to the Unix domain socket */
|
||||
if (bind(listener_socket, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
|
||||
struct sockaddr_un local_addr;
|
||||
int listener_socket = open_unix_socket(MXS_SOCKET_LISTENER, &local_addr, path);
|
||||
|
||||
if (listener_socket >= 0 && chmod(path, 0777) < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %i, %s",
|
||||
config_bind,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
close(listener_socket);
|
||||
return -1;
|
||||
MXS_ERROR("Failed to change permissions on UNIX Domain socket '%s': %d, %s",
|
||||
path, errno, mxs_strerror(errno));
|
||||
}
|
||||
|
||||
/* set permission for all users */
|
||||
if (chmod(config_bind, 0777) < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to change permissions on UNIX Domain socket '%s': %i, %s",
|
||||
config_bind,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
return listener_socket;
|
||||
}
|
||||
|
||||
@ -3303,3 +3210,25 @@ bool dcb_foreach(bool(*func)(DCB *, void *), void *data)
|
||||
|
||||
return more;
|
||||
}
|
||||
|
||||
int dcb_get_port(const DCB *dcb)
|
||||
{
|
||||
int rval = -1;
|
||||
|
||||
if (dcb->ip.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ip = (struct sockaddr_in*)&dcb->ip;
|
||||
rval = ntohs(ip->sin_port);
|
||||
}
|
||||
else if (dcb->ip.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ip = (struct sockaddr_in6*)&dcb->ip;
|
||||
rval = ntohs(ip->sin6_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss_dassert(dcb->ip.ss_family == AF_UNIX);
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -998,10 +998,13 @@ bool mon_status_changed(MXS_MONITOR_SERVERS* mon_srv)
|
||||
unsigned int new_status = mon_srv->server->status & all_server_bits;
|
||||
|
||||
/**
|
||||
* The state has changed if the relevant state bits are not the same and
|
||||
* the server is not going into maintenance or coming out of it
|
||||
* The state has changed if the relevant state bits are not the same,
|
||||
* the server is either running, stopping or starting and the server is
|
||||
* not going into maintenance or coming out of it
|
||||
*/
|
||||
if (old_status != new_status && ((old_status | new_status) & SERVER_MAINT) == 0)
|
||||
if (old_status != new_status &&
|
||||
((old_status | new_status) & SERVER_MAINT) == 0 &&
|
||||
((old_status | new_status) & SERVER_RUNNING) == SERVER_RUNNING)
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
|
||||
@ -307,11 +307,11 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port)
|
||||
|
||||
if (port->address)
|
||||
{
|
||||
sprintf(config_bind, "%s:%d", port->address, port->port);
|
||||
sprintf(config_bind, "%s|%d", port->address, port->port);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(config_bind, "0.0.0.0:%d", port->port);
|
||||
sprintf(config_bind, "0.0.0.0|%d", port->port);
|
||||
}
|
||||
|
||||
/** Load the authentication users before before starting the listener */
|
||||
|
||||
@ -303,6 +303,23 @@ session_simple_free(MXS_SESSION *session, DCB *dcb)
|
||||
session_final_free(session);
|
||||
}
|
||||
|
||||
void session_close(MXS_SESSION *session)
|
||||
{
|
||||
if (!session->ses_is_child && session->router_session)
|
||||
{
|
||||
if (session->state != SESSION_STATE_STOPPING)
|
||||
{
|
||||
session->state = SESSION_STATE_STOPPING;
|
||||
}
|
||||
|
||||
MXS_ROUTER_OBJECT* router = session->service->router;
|
||||
void* router_instance = session->service->router_instance;
|
||||
|
||||
/** Close router session and all its connections */
|
||||
router->closeSession(router_instance, session->router_session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocate the specified session
|
||||
*
|
||||
|
||||
@ -37,12 +37,14 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/dcb.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/limits.h>
|
||||
#include <maxscale/pcre2.h>
|
||||
#include <maxscale/poll.h>
|
||||
#include <maxscale/random_jkiss.h>
|
||||
@ -889,150 +891,151 @@ void utils_end()
|
||||
|
||||
SPINLOCK tmplock = SPINLOCK_INIT;
|
||||
|
||||
/*
|
||||
* Set IP address in socket structure in_addr
|
||||
*
|
||||
* @param a Pointer to a struct in_addr into which the address is written
|
||||
* @param p The hostname to lookup
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
int
|
||||
setipaddress(struct in_addr *a, char *p)
|
||||
static bool configure_network_socket(int so)
|
||||
{
|
||||
#ifdef __USE_POSIX
|
||||
struct addrinfo *ai = NULL, hint;
|
||||
int rc;
|
||||
struct sockaddr_in *res_addr;
|
||||
memset(&hint, 0, sizeof (hint));
|
||||
int sndbufsize = MXS_BACKEND_SO_SNDBUF;
|
||||
int rcvbufsize = MXS_BACKEND_SO_RCVBUF;
|
||||
int one = 1;
|
||||
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/*
|
||||
* This is for the listening socket, matching INADDR_ANY only for now.
|
||||
* For future specific addresses bind, a dedicated routine woulbd be better
|
||||
*/
|
||||
|
||||
if (strcmp(p, "0.0.0.0") == 0)
|
||||
if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &sndbufsize, sizeof(sndbufsize)) != 0 ||
|
||||
setsockopt(so, SOL_SOCKET, SO_RCVBUF, &rcvbufsize, sizeof(rcvbufsize)) != 0 ||
|
||||
setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0)
|
||||
{
|
||||
hint.ai_flags = AI_PASSIVE;
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to obtain address for host %s, %s",
|
||||
p,
|
||||
gai_strerror(rc));
|
||||
|
||||
return 0;
|
||||
MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return setnonblocking(so) == 0;
|
||||
}
|
||||
|
||||
static bool configure_listener_socket(int so)
|
||||
{
|
||||
int one = 1;
|
||||
|
||||
if (setsockopt(so, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0 ||
|
||||
setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return setnonblocking(so) == 0;
|
||||
}
|
||||
|
||||
static void set_port(struct sockaddr_storage *addr, uint16_t port)
|
||||
{
|
||||
if (addr->ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in *ip = (struct sockaddr_in*)addr;
|
||||
ip->sin_port = htons(port);
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr;
|
||||
ip->sin6_port = htons(port);
|
||||
}
|
||||
else
|
||||
{
|
||||
hint.ai_flags = AI_CANONNAME;
|
||||
hint.ai_family = AF_INET;
|
||||
|
||||
if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to obtain address for host %s, %s",
|
||||
p,
|
||||
gai_strerror(rc));
|
||||
|
||||
return 0;
|
||||
MXS_ERROR("Unknown address family: %d", (int)addr->ss_family);
|
||||
ss_dassert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* take the first one */
|
||||
if (ai != NULL)
|
||||
int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr, const char *host, uint16_t port)
|
||||
{
|
||||
res_addr = (struct sockaddr_in *)(ai->ai_addr);
|
||||
memcpy(a, &res_addr->sin_addr, sizeof(struct in_addr));
|
||||
ss_dassert(type == MXS_SOCKET_NETWORK || type == MXS_SOCKET_LISTENER);
|
||||
#ifdef __USE_POSIX
|
||||
struct addrinfo *ai = NULL, hint = {};
|
||||
int so, rc;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
hint.ai_family = AF_UNSPEC;
|
||||
hint.ai_flags = AI_ALL;
|
||||
|
||||
if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0)
|
||||
{
|
||||
MXS_ERROR("Failed to obtain address for host %s: %s", host, gai_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Take the first one */
|
||||
if (ai)
|
||||
{
|
||||
if ((so = socket(ai->ai_family, SOCK_STREAM, 0)) == -1)
|
||||
{
|
||||
MXS_ERROR("Socket creation failed: %d, %s.", errno, mxs_strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(addr, ai->ai_addr, ai->ai_addrlen);
|
||||
set_port(addr, port);
|
||||
|
||||
if ((type == MXS_SOCKET_NETWORK && !configure_network_socket(so)) ||
|
||||
(type == MXS_SOCKET_LISTENER && !configure_listener_socket(so)))
|
||||
{
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
else if (type == MXS_SOCKET_LISTENER && bind(so, (struct sockaddr*)addr, sizeof(*addr)) < 0)
|
||||
{
|
||||
MXS_ERROR("Failed to bind on '%s:%u': %d, %s",
|
||||
host, port, errno, mxs_strerror(errno));
|
||||
close(so);
|
||||
so = -1;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(ai);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
struct hostent *h;
|
||||
|
||||
spinlock_acquire(&tmplock);
|
||||
h = gethostbyname(p);
|
||||
spinlock_release(&tmplock);
|
||||
|
||||
if (h == NULL)
|
||||
{
|
||||
if ((a->s_addr = inet_addr(p)) == -1)
|
||||
{
|
||||
MXS_ERROR("gethostbyname failed for [%s]", p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* take the first one */
|
||||
memcpy(a, h->h_addr, h->h_length);
|
||||
|
||||
return 1;
|
||||
}
|
||||
#error Only the POSIX networking interface is supported
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
return so;
|
||||
}
|
||||
|
||||
static bool configure_unix_socket(int so)
|
||||
{
|
||||
int one = 1;
|
||||
|
||||
/**
|
||||
* Parse the bind config data. This is passed in a string as address:port.
|
||||
*
|
||||
* The address may be either a . separated IP address or a hostname to
|
||||
* lookup. The address 0.0.0.0 is the wildcard address for SOCKADR_ANY.
|
||||
* The ':' and port are required.
|
||||
*
|
||||
* @param config The bind address and port separated by a ':'
|
||||
* @param addr The sockaddr_in in which the data is written
|
||||
* @return 0 on failure
|
||||
*/
|
||||
int
|
||||
parse_bindconfig(const char *config, struct sockaddr_in *addr)
|
||||
if (setsockopt(so, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0)
|
||||
{
|
||||
char buf[strlen(config) + 1];
|
||||
strcpy(buf, config);
|
||||
|
||||
char *port = strrchr(buf, ':');
|
||||
short pnum;
|
||||
if (port)
|
||||
{
|
||||
*port = 0;
|
||||
port++;
|
||||
pnum = atoi(port);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strcmp(buf, "0.0.0.0"))
|
||||
{
|
||||
addr->sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
return setnonblocking(so) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inet_aton(buf, &addr->sin_addr))
|
||||
{
|
||||
struct hostent *hp = gethostbyname(buf);
|
||||
|
||||
if (hp)
|
||||
int open_unix_socket(enum mxs_socket_type type, struct sockaddr_un *addr, const char *path)
|
||||
{
|
||||
bcopy(hp->h_addr, &(addr->sin_addr.s_addr), hp->h_length);
|
||||
}
|
||||
else
|
||||
int fd = -1;
|
||||
|
||||
if (strlen(path) > sizeof(addr->sun_path) - 1)
|
||||
{
|
||||
MXS_ERROR("Failed to lookup host '%s'.", buf);
|
||||
return 0;
|
||||
MXS_ERROR("The path %s specified for the UNIX domain socket is too long. "
|
||||
"The maximum length is %lu.", path, sizeof(addr->sun_path) - 1);
|
||||
}
|
||||
else if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
MXS_ERROR("Can't create UNIX socket: %d, %s", errno, mxs_strerror(errno));
|
||||
}
|
||||
else if (configure_unix_socket(fd))
|
||||
{
|
||||
addr->sun_family = AF_UNIX;
|
||||
strcpy(addr->sun_path, path);
|
||||
|
||||
/* Bind the socket to the Unix domain socket */
|
||||
if (type == MXS_SOCKET_LISTENER && bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0)
|
||||
{
|
||||
MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %d, %s",
|
||||
path, errno, mxs_strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
addr->sin_family = AF_INET;
|
||||
addr->sin_port = htons(pnum);
|
||||
return 1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -53,7 +53,7 @@ static int get_users(SERV_LISTENER *listener);
|
||||
static MYSQL *gw_mysql_init(void);
|
||||
static int gw_mysql_set_timeouts(MYSQL* handle);
|
||||
static char *mysql_format_user_entry(void *data);
|
||||
static bool get_hostname(const char *ip_address, char *client_hostname);
|
||||
static bool get_hostname(DCB *dcb, char *client_hostname, size_t size);
|
||||
|
||||
static char* get_new_users_query(const char *server_version, bool include_root)
|
||||
{
|
||||
@ -208,7 +208,8 @@ int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session,
|
||||
* as a last resort so we avoid the high cost of the DNS lookup.
|
||||
*/
|
||||
char client_hostname[MYSQL_HOST_MAXLEN];
|
||||
get_hostname(dcb->remote, client_hostname);
|
||||
get_hostname(dcb, client_hostname, sizeof(client_hostname) - 1);
|
||||
|
||||
sprintf(sql, mysqlauth_validate_user_query, session->user, client_hostname,
|
||||
client_hostname, session->db, session->db);
|
||||
|
||||
@ -237,12 +238,6 @@ int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session,
|
||||
rval = MXS_AUTH_FAILED_DB;
|
||||
}
|
||||
}
|
||||
else if (session->auth_token_len)
|
||||
{
|
||||
/** If authentication fails, this will trigger the right
|
||||
* error message with `Using password : YES` */
|
||||
session->client_sha1[0] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
@ -269,6 +264,69 @@ static bool delete_mysql_users(sqlite3 *handle)
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the hostname is of form a.b.c.d/e.f.g.h where e-h is 255 or 0, replace
|
||||
* the zeros in the first part with '%' and remove the second part. This does
|
||||
* not yet support netmasks completely, but should be sufficient for most
|
||||
* situations. In case of error, the hostname may end in an invalid state, which
|
||||
* will cause an error later on.
|
||||
*
|
||||
* @param host The hostname, which is modified in-place. If merging is unsuccessful,
|
||||
* it may end up garbled.
|
||||
*/
|
||||
static void merge_netmask(char *host)
|
||||
{
|
||||
char *delimiter_loc = strchr(host, '/');
|
||||
if (delimiter_loc == NULL)
|
||||
{
|
||||
return; // Nothing to do
|
||||
}
|
||||
/* If anything goes wrong, we put the '/' back in to ensure the hostname
|
||||
* cannot be used.
|
||||
*/
|
||||
*delimiter_loc = '\0';
|
||||
|
||||
char *ip_token_loc = host;
|
||||
char *mask_token_loc = delimiter_loc + 1; // This is at minimum a \0
|
||||
|
||||
while (ip_token_loc && mask_token_loc)
|
||||
{
|
||||
if (strncmp(mask_token_loc, "255", 3) == 0)
|
||||
{
|
||||
// Skip
|
||||
}
|
||||
else if (*mask_token_loc == '0' && *ip_token_loc == '0')
|
||||
{
|
||||
*ip_token_loc = '%';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Any other combination is considered invalid. This may leave the
|
||||
* hostname in a partially modified state.
|
||||
* TODO: handle more cases
|
||||
*/
|
||||
*delimiter_loc = '/';
|
||||
MXS_ERROR("Unrecognized IP-bytes in host/mask-combination. "
|
||||
"Merge incomplete: %s", host);
|
||||
return;
|
||||
}
|
||||
|
||||
ip_token_loc = strchr(ip_token_loc, '.');
|
||||
mask_token_loc = strchr(mask_token_loc, '.');
|
||||
if (ip_token_loc && mask_token_loc)
|
||||
{
|
||||
ip_token_loc++;
|
||||
mask_token_loc++;
|
||||
}
|
||||
}
|
||||
if (ip_token_loc || mask_token_loc)
|
||||
{
|
||||
*delimiter_loc = '/';
|
||||
MXS_ERROR("Unequal number of IP-bytes in host/mask-combination. "
|
||||
"Merge incomplete: %s", host);
|
||||
}
|
||||
}
|
||||
|
||||
void add_mysql_user(sqlite3 *handle, const char *user, const char *host,
|
||||
const char *db, bool anydb, const char *pw)
|
||||
{
|
||||
@ -611,30 +669,27 @@ bool check_service_permissions(SERVICE* service)
|
||||
*
|
||||
* @return True if the hostname query was successful
|
||||
*/
|
||||
static bool get_hostname(const char *ip_address, char *client_hostname)
|
||||
static bool get_hostname(DCB *dcb, char *client_hostname, size_t size)
|
||||
{
|
||||
/* Looks like the parameters are valid. First, convert the client IP string
|
||||
* to binary form. This is somewhat silly, since just a while ago we had the
|
||||
* binary address but had to zero it. dbusers.c should be refactored to fix this.
|
||||
*/
|
||||
struct sockaddr_in bin_address;
|
||||
bin_address.sin_family = AF_INET;
|
||||
if (inet_pton(bin_address.sin_family, ip_address, &(bin_address.sin_addr)) != 1)
|
||||
struct addrinfo *ai = NULL, hint = {};
|
||||
hint.ai_flags = AI_ALL;
|
||||
int rc;
|
||||
|
||||
if ((rc = getaddrinfo(dcb->remote, NULL, &hint, &ai)) != 0)
|
||||
{
|
||||
MXS_ERROR("Could not convert to binary ip-address: '%s'.", ip_address);
|
||||
MXS_ERROR("Failed to obtain address for host %s, %s",
|
||||
dcb->remote, gai_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to lookup the domain name of the given IP-address. This is a slow
|
||||
* i/o-operation, which will stall the entire thread. TODO: cache results
|
||||
* if this feature is used often.
|
||||
*/
|
||||
MXS_DEBUG("Resolving '%s'", ip_address);
|
||||
int lookup_result = getnameinfo((struct sockaddr*)&bin_address,
|
||||
sizeof(struct sockaddr_in),
|
||||
client_hostname, sizeof(client_hostname),
|
||||
* if this feature is used often. */
|
||||
int lookup_result = getnameinfo(ai->ai_addr, ai->ai_addrlen,
|
||||
client_hostname, size,
|
||||
NULL, 0, // No need for the port
|
||||
NI_NAMEREQD); // Text address only
|
||||
freeaddrinfo(ai);
|
||||
|
||||
if (lookup_result != 0)
|
||||
{
|
||||
@ -646,7 +701,7 @@ static bool get_hostname(const char *ip_address, char *client_hostname)
|
||||
MXS_DEBUG("IP-lookup success, hostname is: '%s'", client_hostname);
|
||||
}
|
||||
|
||||
return false;
|
||||
return lookup_result == 0;
|
||||
}
|
||||
|
||||
void start_sqlite_transaction(sqlite3 *handle)
|
||||
@ -695,8 +750,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV
|
||||
{
|
||||
start_sqlite_transaction(instance->handle);
|
||||
|
||||
/** Delete the old users */
|
||||
delete_mysql_users(instance->handle);
|
||||
MYSQL_ROW row;
|
||||
|
||||
while ((row = mysql_fetch_row(result)))
|
||||
@ -706,6 +759,11 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV
|
||||
strip_escape_chars(row[2]);
|
||||
}
|
||||
|
||||
if (strchr(row[1], '/'))
|
||||
{
|
||||
merge_netmask(row[1]);
|
||||
}
|
||||
|
||||
add_mysql_user(instance->handle, row[0], row[1], row[2],
|
||||
row[3] && strcmp(row[3], "Y") == 0, row[4]);
|
||||
users++;
|
||||
@ -786,6 +844,10 @@ static int get_users(SERV_LISTENER *listener)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Delete the old users */
|
||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
||||
delete_mysql_users(instance->handle);
|
||||
|
||||
SERVER_REF *server = service->dbref;
|
||||
int total_users = -1;
|
||||
|
||||
|
||||
@ -106,18 +106,19 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
||||
return &info;
|
||||
}
|
||||
|
||||
static void get_database_path(SERV_LISTENER *port, char *dest)
|
||||
static void get_database_path(SERV_LISTENER *port, char *dest, size_t size)
|
||||
{
|
||||
MYSQL_AUTH *instance = port->auth_instance;
|
||||
SERVICE *service = port->service;
|
||||
ss_dassert(size - sizeof(DBUSERS_FILE) - 1 >= 0);
|
||||
|
||||
if (instance->cache_dir)
|
||||
{
|
||||
snprintf(dest, sizeof(dest) - sizeof(DBUSERS_FILE) - 1, "%s/", instance->cache_dir);
|
||||
snprintf(dest, size, "%s/", instance->cache_dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(dest, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR);
|
||||
snprintf(dest, size, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR);
|
||||
}
|
||||
|
||||
if (mxs_mkdir_all(dest, S_IRWXU))
|
||||
@ -254,6 +255,30 @@ static void mysql_auth_destroy(void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_localhost_address(struct sockaddr_storage *addr)
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
if (addr->ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in *ip = (struct sockaddr_in*)addr;
|
||||
if (ip->sin_addr.s_addr == INADDR_LOOPBACK)
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr;
|
||||
if (memcmp(&ip->sin6_addr, &in6addr_loopback, sizeof(ip->sin6_addr)) == 0)
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authenticates a MySQL user who is a client to MaxScale.
|
||||
*
|
||||
@ -325,13 +350,13 @@ mysql_auth_authenticate(DCB *dcb)
|
||||
else if (dcb->service->log_auth_warnings)
|
||||
{
|
||||
MXS_WARNING("%s: login attempt for user '%s'@%s:%d, authentication failed.",
|
||||
dcb->service->name, client_data->user, dcb->remote, ntohs(dcb->ipv4.sin_port));
|
||||
if (dcb->ipv4.sin_addr.s_addr == 0x0100007F &&
|
||||
dcb->service->name, client_data->user, dcb->remote, dcb_get_port(dcb));
|
||||
|
||||
if (is_localhost_address(&dcb->ip) &&
|
||||
!dcb->service->localhost_match_wildcard_host)
|
||||
{
|
||||
MXS_NOTICE("If you have a wildcard grant that covers"
|
||||
" this address, try adding "
|
||||
"'localhost_match_wildcard_host=true' for "
|
||||
MXS_NOTICE("If you have a wildcard grant that covers this address, "
|
||||
"try adding 'localhost_match_wildcard_host=true' for "
|
||||
"service '%s'. ", dcb->service->name);
|
||||
}
|
||||
}
|
||||
@ -374,7 +399,7 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf)
|
||||
if (auth_ses->handle == NULL)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
get_database_path(dcb->listener, path);
|
||||
get_database_path(dcb->listener, path, sizeof(path));
|
||||
|
||||
if (!open_client_database(path, &auth_ses->handle))
|
||||
{
|
||||
@ -591,7 +616,7 @@ static int mysql_auth_load_users(SERV_LISTENER *port)
|
||||
if (instance->handle == NULL)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
get_database_path(port, path);
|
||||
get_database_path(port, path, sizeof(path));
|
||||
if (!open_instance_database(path, &instance->handle))
|
||||
{
|
||||
return MXS_AUTH_LOADUSERS_FATAL;
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/filter.h>
|
||||
@ -52,7 +53,7 @@ struct RegexHintSess_t;
|
||||
|
||||
typedef struct source_host
|
||||
{
|
||||
const char *address;
|
||||
char *address;
|
||||
struct sockaddr_in ipv4;
|
||||
int netmask;
|
||||
} REGEXHINT_SOURCE_HOST;
|
||||
@ -75,7 +76,7 @@ private:
|
||||
volatile unsigned int m_total_diverted;
|
||||
volatile unsigned int m_total_undiverted;
|
||||
|
||||
int check_source_host(const char *remote, const struct sockaddr_in *ipv4);
|
||||
int check_source_host(const char *remote, const struct sockaddr_storage *ip);
|
||||
|
||||
public:
|
||||
RegexHintInst(string match, string server, string user, REGEXHINT_SOURCE_HOST* source,
|
||||
@ -135,6 +136,10 @@ RegexHintInst::RegexHintInst(string match, string server, string user,
|
||||
RegexHintInst::~RegexHintInst()
|
||||
{
|
||||
pcre2_code_free(m_regex);
|
||||
if (m_source)
|
||||
{
|
||||
MXS_FREE(m_source->address);
|
||||
}
|
||||
MXS_FREE(m_source);
|
||||
}
|
||||
|
||||
@ -158,7 +163,7 @@ RegexHintSess_t* RegexHintInst::newSession(MXS_SESSION *session)
|
||||
(remote = session_get_remote(session)) != NULL)
|
||||
{
|
||||
my_session->active =
|
||||
this->check_source_host(remote, &session->client_dcb->ipv4);
|
||||
this->check_source_host(remote, &session->client_dcb->ip);
|
||||
}
|
||||
|
||||
/* Check client user against 'user' option */
|
||||
@ -253,12 +258,12 @@ void RegexHintInst::diagnostic(RegexHintSess_t* my_session, DCB *dcb)
|
||||
* @param ipv4 The client IPv4 struct
|
||||
* @return 1 for match, 0 otherwise
|
||||
*/
|
||||
int RegexHintInst::check_source_host(const char *remote, const struct sockaddr_in *ipv4)
|
||||
int RegexHintInst::check_source_host(const char *remote, const struct sockaddr_storage *ip)
|
||||
{
|
||||
int ret = 0;
|
||||
struct sockaddr_in check_ipv4;
|
||||
|
||||
memcpy(&check_ipv4, ipv4, sizeof(check_ipv4));
|
||||
memcpy(&check_ipv4, ip, sizeof(check_ipv4));
|
||||
|
||||
switch (m_source->netmask)
|
||||
{
|
||||
@ -552,28 +557,26 @@ static bool validate_ip_address(const char *host)
|
||||
*/
|
||||
static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host)
|
||||
{
|
||||
ss_dassert(input_host);
|
||||
int netmask = 32;
|
||||
int bytes = 0;
|
||||
struct sockaddr_in serv_addr;
|
||||
REGEXHINT_SOURCE_HOST *source_host =
|
||||
(REGEXHINT_SOURCE_HOST*)MXS_CALLOC(1, sizeof(REGEXHINT_SOURCE_HOST));
|
||||
|
||||
if (!input_host || !source_host)
|
||||
if (!source_host)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!validate_ip_address(input_host))
|
||||
{
|
||||
MXS_WARNING("The given 'source' parameter source=%s"
|
||||
" is not a valid IP address: it will not be used.",
|
||||
input_host);
|
||||
|
||||
source_host->address = NULL;
|
||||
return source_host;
|
||||
MXS_WARNING("The given 'source' parameter '%s'"
|
||||
" is not a valid IPv4 address.", input_host);
|
||||
MXS_FREE(source_host);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
source_host->address = input_host;
|
||||
source_host->address = MXS_STRDUP_A(input_host);
|
||||
|
||||
/* If no wildcards don't check it, set netmask to 32 and return */
|
||||
if (!strchr(input_host, '%'))
|
||||
@ -610,9 +613,15 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host)
|
||||
*out = '\0';
|
||||
source_host->netmask = netmask;
|
||||
|
||||
struct addrinfo *ai = NULL, hint = {};
|
||||
hint.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
|
||||
int rc = getaddrinfo(input_host, NULL, &hint, &ai);
|
||||
|
||||
/* fill IPv4 data struct */
|
||||
if (setipaddress(&source_host->ipv4.sin_addr, format_host) && strlen(format_host))
|
||||
if (rc == 0)
|
||||
{
|
||||
ss_dassert(ai->ai_family == AF_INET);
|
||||
memcpy(&source_host->ipv4, ai->ai_addr, ai->ai_addrlen);
|
||||
|
||||
/* if netmask < 32 there are % wildcards */
|
||||
if (source_host->netmask < 32)
|
||||
@ -621,16 +630,16 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host)
|
||||
source_host->ipv4.sin_addr.s_addr &= 0x00FFFFFF;
|
||||
}
|
||||
|
||||
MXS_INFO("Input %s is valid with netmask %d\n",
|
||||
source_host->address,
|
||||
source_host->netmask);
|
||||
MXS_INFO("Input %s is valid with netmask %d", source_host->address, source_host->netmask);
|
||||
freeaddrinfo(ai);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Found invalid IP address for parameter 'source=%s',"
|
||||
" it will not be used.",
|
||||
input_host);
|
||||
source_host->address = NULL;
|
||||
MXS_WARNING("Found invalid IP address for parameter 'source=%s': %s",
|
||||
input_host, gai_strerror(rc));
|
||||
MXS_FREE(source_host->address);
|
||||
MXS_FREE(source_host);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (REGEXHINT_SOURCE_HOST *)source_host;
|
||||
|
||||
@ -536,22 +536,9 @@ closeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session)
|
||||
if ((bsession = my_session->branch_session) != NULL)
|
||||
{
|
||||
CHK_SESSION(bsession);
|
||||
|
||||
if (bsession->state != SESSION_STATE_STOPPING)
|
||||
{
|
||||
bsession->state = SESSION_STATE_STOPPING;
|
||||
bsession->ses_is_child = false;
|
||||
session_close(bsession);
|
||||
}
|
||||
router = bsession->service->router;
|
||||
router_instance = bsession->service->router_instance;
|
||||
rsession = bsession->router_session;
|
||||
|
||||
/** Close router session and all its connections */
|
||||
router->closeSession(router_instance, rsession);
|
||||
}
|
||||
/* No need to free the session, this is done as
|
||||
* a side effect of closing the client DCB of the
|
||||
* session.
|
||||
*/
|
||||
|
||||
if (my_session->waiting[PARENT])
|
||||
{
|
||||
|
||||
@ -1777,8 +1777,11 @@ static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *mon, int num_serve
|
||||
master->server->unique_name);
|
||||
ss_dassert(info);
|
||||
|
||||
if (SERVER_IS_RUNNING(master->server))
|
||||
{
|
||||
/** Only set the Master status if read_only is disabled */
|
||||
monitor_set_pending_status(master, info->read_only ? SERVER_SLAVE : SERVER_MASTER);
|
||||
}
|
||||
|
||||
handle->master = master;
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/utils.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/modinfo.h>
|
||||
@ -271,88 +270,20 @@ return_fd:
|
||||
* backend server. In failure, fd == -1 and socket is closed.
|
||||
*
|
||||
*/
|
||||
static int
|
||||
gw_do_connect_to_backend(char *host, int port, int *fd)
|
||||
static int gw_do_connect_to_backend(char *host, int port, int *fd)
|
||||
{
|
||||
struct sockaddr_in serv_addr;
|
||||
int rv;
|
||||
int so = 0;
|
||||
int bufsize;
|
||||
struct sockaddr_storage serv_addr = {};
|
||||
int rv = -1;
|
||||
|
||||
memset(&serv_addr, 0, sizeof serv_addr);
|
||||
serv_addr.sin_family = (int)AF_INET;
|
||||
so = socket((int)AF_INET, (int)SOCK_STREAM, 0);
|
||||
|
||||
if (so < 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Establishing connection to backend server "
|
||||
"%s:%d failed.\n\t\t Socket creation failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
rv = -1;
|
||||
goto return_rv;
|
||||
}
|
||||
/* prepare for connect */
|
||||
setipaddress(&serv_addr.sin_addr, host);
|
||||
serv_addr.sin_port = htons(port);
|
||||
bufsize = MXS_BACKEND_SO_SNDBUF;
|
||||
int so = open_network_socket(MXS_SOCKET_NETWORK, &serv_addr, host, port);
|
||||
|
||||
if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0)
|
||||
if (so == -1)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to set socket options "
|
||||
"%s:%d failed.\n\t\t Socket configuration failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
rv = -1;
|
||||
/** Close socket */
|
||||
close_socket(so);
|
||||
goto return_rv;
|
||||
}
|
||||
bufsize = MXS_BACKEND_SO_RCVBUF;
|
||||
|
||||
if (setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to set socket options "
|
||||
"%s:%d failed.\n\t\t Socket configuration failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
rv = -1;
|
||||
/** Close socket */
|
||||
close_socket(so);
|
||||
goto return_rv;
|
||||
MXS_ERROR("Establishing connection to backend server %s:%d failed.", host, port);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
if (setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to set socket options "
|
||||
"%s:%d failed.\n\t\t Socket configuration failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
rv = -1;
|
||||
/** Close socket */
|
||||
close_socket(so);
|
||||
goto return_rv;
|
||||
}
|
||||
|
||||
/* set socket to as non-blocking here */
|
||||
setnonblocking(so);
|
||||
rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
|
||||
|
||||
if (rv != 0)
|
||||
@ -363,24 +294,17 @@ gw_do_connect_to_backend(char *host, int port, int *fd)
|
||||
}
|
||||
else
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to connect backend server %s:%d, "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
/** Close socket */
|
||||
close_socket(so);
|
||||
goto return_rv;
|
||||
MXS_ERROR("Failed to connect backend server %s:%d due to: %d, %s.",
|
||||
host, port, errno, mxs_strerror(errno));
|
||||
close(so);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
*fd = so;
|
||||
MXS_DEBUG("%lu [gw_do_connect_to_backend] Connected to backend server "
|
||||
"%s:%d, fd %d.",
|
||||
pthread_self(), host, port, so);
|
||||
"%s:%d, fd %d.", pthread_self(), host, port, so);
|
||||
|
||||
return_rv:
|
||||
return rv;
|
||||
|
||||
}
|
||||
@ -1188,46 +1112,31 @@ static int gw_backend_hangup(DCB *dcb)
|
||||
*/
|
||||
static int gw_backend_close(DCB *dcb)
|
||||
{
|
||||
MXS_SESSION* session;
|
||||
GWBUF* quitbuf;
|
||||
|
||||
CHK_DCB(dcb);
|
||||
session = dcb->session;
|
||||
|
||||
MXS_DEBUG("%lu [gw_backend_close]", pthread_self());
|
||||
|
||||
quitbuf = mysql_create_com_quit(NULL, 0);
|
||||
gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL);
|
||||
ss_dassert(dcb->session);
|
||||
|
||||
/** Send COM_QUIT to the backend being closed */
|
||||
GWBUF* quitbuf = mysql_create_com_quit(NULL, 0);
|
||||
gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL);
|
||||
mysql_send_com_quit(dcb, 0, quitbuf);
|
||||
|
||||
/** Free protocol data */
|
||||
mysql_protocol_done(dcb);
|
||||
|
||||
if (session)
|
||||
{
|
||||
MXS_SESSION* session = dcb->session;
|
||||
CHK_SESSION(session);
|
||||
/**
|
||||
* The lock is needed only to protect the read of session->state and
|
||||
* session->client_dcb values. Client's state may change by other thread
|
||||
* but client's close and adding client's DCB to zombies list is executed
|
||||
* only if client's DCB's state does _not_ change in parallel.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If session->state is STOPPING, start closing client session.
|
||||
* If session state is SESSION_STATE_STOPPING, start closing client session.
|
||||
* Otherwise only this backend connection is closed.
|
||||
*/
|
||||
if (session->state == SESSION_STATE_STOPPING &&
|
||||
session->client_dcb != NULL)
|
||||
if (session->client_dcb &&
|
||||
session->state == SESSION_STATE_STOPPING &&
|
||||
session->client_dcb->state == DCB_STATE_POLLING)
|
||||
{
|
||||
if (session->client_dcb->state == DCB_STATE_POLLING)
|
||||
{
|
||||
/** Close client DCB */
|
||||
dcb_close(session->client_dcb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1446,7 +1355,7 @@ static int gw_change_user(DCB *backend,
|
||||
message = create_auth_fail_str(username,
|
||||
backend->session->client_dcb->remote,
|
||||
password_set,
|
||||
"",
|
||||
false,
|
||||
auth_ret);
|
||||
if (message == NULL)
|
||||
{
|
||||
@ -1683,21 +1592,6 @@ static bool sescmd_response_complete(DCB* dcb)
|
||||
return succp;
|
||||
}
|
||||
|
||||
static void inline
|
||||
close_socket(int sock)
|
||||
{
|
||||
/*< Close newly created socket. */
|
||||
if (close(sock) != 0)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
MXS_ERROR("Failed to close socket %d due %d, %s.",
|
||||
sock,
|
||||
errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create COM_CHANGE_USER packet and store it to GWBUF
|
||||
*
|
||||
|
||||
@ -83,7 +83,6 @@ static void mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_
|
||||
static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read);
|
||||
static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities);
|
||||
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db, int);
|
||||
static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read);
|
||||
static void gw_process_one_new_client(DCB *client_dcb);
|
||||
|
||||
@ -1047,86 +1046,67 @@ mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_number)
|
||||
{
|
||||
int message_len;
|
||||
char *fail_str = NULL;
|
||||
MYSQL_session *session = (MYSQL_session*)dcb->data;
|
||||
|
||||
switch (auth_val)
|
||||
{
|
||||
case MXS_AUTH_NO_SESSION:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] session "
|
||||
"creation failed. fd %d, "
|
||||
"state = MYSQL_AUTH_NO_SESSION.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
MXS_DEBUG("%lu [gw_read_client_event] session creation failed. fd %d, "
|
||||
"state = MYSQL_AUTH_NO_SESSION.", pthread_self(), dcb->fd);
|
||||
|
||||
/** Send ERR 1045 to client */
|
||||
mysql_send_auth_error(dcb,
|
||||
packet_number,
|
||||
0,
|
||||
"failed to create new session");
|
||||
mysql_send_auth_error(dcb, packet_number, 0, "failed to create new session");
|
||||
break;
|
||||
|
||||
case MXS_AUTH_FAILED_DB:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] database "
|
||||
"specified was not valid. fd %d, "
|
||||
"state = MYSQL_FAILED_AUTH_DB.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
MXS_DEBUG("%lu [gw_read_client_event] database specified was not valid. fd %d, "
|
||||
"state = MYSQL_FAILED_AUTH_DB.", pthread_self(), dcb->fd);
|
||||
/** Send error 1049 to client */
|
||||
message_len = 25 + MYSQL_DATABASE_MAXLEN;
|
||||
|
||||
fail_str = MXS_CALLOC(1, message_len + 1);
|
||||
MXS_ABORT_IF_NULL(fail_str);
|
||||
snprintf(fail_str, message_len, "Unknown database '%s'",
|
||||
(char*)((MYSQL_session *)dcb->data)->db);
|
||||
snprintf(fail_str, message_len, "Unknown database '%s'", session->db);
|
||||
|
||||
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str);
|
||||
break;
|
||||
|
||||
case MXS_AUTH_FAILED_SSL:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] client is "
|
||||
"not SSL capable for SSL listener. fd %d, "
|
||||
"state = MYSQL_FAILED_AUTH_SSL.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
"state = MYSQL_FAILED_AUTH_SSL.", pthread_self(), dcb->fd);
|
||||
|
||||
/** Send ERR 1045 to client */
|
||||
mysql_send_auth_error(dcb,
|
||||
packet_number,
|
||||
0,
|
||||
"Access without SSL denied");
|
||||
mysql_send_auth_error(dcb, packet_number, 0, "Access without SSL denied");
|
||||
break;
|
||||
|
||||
case MXS_AUTH_SSL_INCOMPLETE:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] unable to "
|
||||
"complete SSL authentication. fd %d, "
|
||||
"state = MYSQL_AUTH_SSL_INCOMPLETE.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
"state = MYSQL_AUTH_SSL_INCOMPLETE.", pthread_self(), dcb->fd);
|
||||
|
||||
/** Send ERR 1045 to client */
|
||||
mysql_send_auth_error(dcb,
|
||||
packet_number,
|
||||
0,
|
||||
mysql_send_auth_error(dcb, packet_number, 0,
|
||||
"failed to complete SSL authentication");
|
||||
break;
|
||||
|
||||
case MXS_AUTH_FAILED:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
|
||||
"state = MYSQL_FAILED_AUTH.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
"state = MYSQL_FAILED_AUTH.", pthread_self(), dcb->fd);
|
||||
/** Send error 1045 to client */
|
||||
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
|
||||
dcb->remote,
|
||||
(char*)((MYSQL_session *)dcb->data)->client_sha1,
|
||||
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
|
||||
fail_str = create_auth_fail_str(session->user, dcb->remote,
|
||||
session->auth_token_len > 0,
|
||||
session->db, auth_val);
|
||||
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
||||
break;
|
||||
|
||||
default:
|
||||
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
|
||||
"state unrecognized.",
|
||||
pthread_self(),
|
||||
dcb->fd);
|
||||
"state unrecognized.", pthread_self(), dcb->fd);
|
||||
/** Send error 1045 to client */
|
||||
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
|
||||
dcb->remote,
|
||||
(char*)((MYSQL_session *)dcb->data)->client_sha1,
|
||||
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
|
||||
fail_str = create_auth_fail_str(session->user, dcb->remote,
|
||||
session->auth_token_len > 0,
|
||||
session->db, auth_val);
|
||||
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
||||
}
|
||||
MXS_FREE(fail_str);
|
||||
@ -1347,52 +1327,12 @@ retblock:
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
gw_client_close(DCB *dcb)
|
||||
static int gw_client_close(DCB *dcb)
|
||||
{
|
||||
MXS_SESSION* session;
|
||||
MXS_ROUTER_OBJECT* router;
|
||||
void* router_instance;
|
||||
#if defined(SS_DEBUG)
|
||||
MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol;
|
||||
|
||||
if (dcb->state == DCB_STATE_POLLING ||
|
||||
dcb->state == DCB_STATE_NOPOLLING ||
|
||||
dcb->state == DCB_STATE_ZOMBIE)
|
||||
{
|
||||
if (!DCB_IS_CLONE(dcb))
|
||||
{
|
||||
CHK_PROTOCOL(protocol);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
MXS_DEBUG("%lu [gw_client_close]", pthread_self());
|
||||
CHK_DCB(dcb);
|
||||
ss_dassert(dcb->protocol);
|
||||
mysql_protocol_done(dcb);
|
||||
session = dcb->session;
|
||||
/**
|
||||
* session may be NULL if session_alloc failed.
|
||||
* In that case, router session wasn't created.
|
||||
*/
|
||||
if (session != NULL && SESSION_STATE_DUMMY != session->state)
|
||||
{
|
||||
CHK_SESSION(session);
|
||||
|
||||
if (session->state != SESSION_STATE_STOPPING)
|
||||
{
|
||||
session->state = SESSION_STATE_STOPPING;
|
||||
}
|
||||
router_instance = session->service->router_instance;
|
||||
router = session->service->router;
|
||||
/**
|
||||
* If router session is being created concurrently router
|
||||
* session might be NULL and it shouldn't be closed.
|
||||
*/
|
||||
if (session->router_session != NULL)
|
||||
{
|
||||
/** Close router session and all its connections */
|
||||
router->closeSession(router_instance, session->router_session);
|
||||
}
|
||||
}
|
||||
session_close(dcb->session);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -925,16 +925,17 @@ char* create_auth_failed_msg(GWBUF*readbuf,
|
||||
/**
|
||||
* Create a message error string to send via MySQL ERR packet.
|
||||
*
|
||||
* @param username the MySQL user
|
||||
* @param hostaddr the client IP
|
||||
* @param sha1 authentication scramble data
|
||||
* @param db the MySQL db to connect to
|
||||
* @param username The MySQL user
|
||||
* @param hostaddr The client IP
|
||||
* @param password If client provided a password
|
||||
* @param db The default database the client requested
|
||||
* @param errcode Authentication error code
|
||||
*
|
||||
* @return Pointer to the allocated string or NULL on failure
|
||||
*/
|
||||
char *create_auth_fail_str(char *username,
|
||||
char *hostaddr,
|
||||
char *sha1,
|
||||
bool password,
|
||||
char *db,
|
||||
int errcode)
|
||||
{
|
||||
@ -974,7 +975,7 @@ char *create_auth_fail_str(char *username,
|
||||
|
||||
if (db_len > 0)
|
||||
{
|
||||
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db);
|
||||
sprintf(errstr, ferrstr, username, hostaddr, password ? "YES": "NO", db);
|
||||
}
|
||||
else if (errcode == MXS_AUTH_FAILED_SSL)
|
||||
{
|
||||
@ -982,7 +983,7 @@ char *create_auth_fail_str(char *username,
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
|
||||
sprintf(errstr, ferrstr, username, hostaddr, password ? "YES" : "NO");
|
||||
}
|
||||
|
||||
retblock:
|
||||
|
||||
@ -904,7 +904,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb)
|
||||
|
||||
dcb_printf(dcb, "\t\tClient UUID: %s\n", session->uuid);
|
||||
dcb_printf(dcb, "\t\tClient_host_port: %s:%d\n",
|
||||
session->dcb->remote, ntohs((session->dcb->ipv4).sin_port));
|
||||
session->dcb->remote, dcb_get_port(session->dcb));
|
||||
dcb_printf(dcb, "\t\tUsername: %s\n", session->dcb->user);
|
||||
dcb_printf(dcb, "\t\tClient DCB: %p\n", session->dcb);
|
||||
dcb_printf(dcb, "\t\tClient protocol: %s\n",
|
||||
|
||||
@ -1173,7 +1173,7 @@ closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
|
||||
MXS_NOTICE("%s: Slave %s:%d, server id %d, disconnected after %ld seconds. "
|
||||
"%d SQL commands, %d events sent (%lu bytes), binlog '%s', "
|
||||
"last position %lu",
|
||||
router->service->name, slave->dcb->remote, ntohs((slave->dcb->ipv4).sin_port),
|
||||
router->service->name, slave->dcb->remote, dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
time(0) - slave->connect_time,
|
||||
slave->stats.n_queries,
|
||||
@ -1581,7 +1581,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb)
|
||||
}
|
||||
dcb_printf(dcb,
|
||||
"\t\tSlave_host_port: %s:%d\n",
|
||||
session->dcb->remote, ntohs((session->dcb->ipv4).sin_port));
|
||||
session->dcb->remote, dcb_get_port(session->dcb));
|
||||
dcb_printf(dcb,
|
||||
"\t\tUsername: %s\n",
|
||||
session->dcb->user);
|
||||
|
||||
@ -736,14 +736,15 @@ typedef struct binlog_pos_fix
|
||||
#define BLRM_SERVER_VARS 0x0016
|
||||
#define BLRM_BINLOG_VARS 0x0017
|
||||
#define BLRM_LOWER_CASE_TABLES 0x0018
|
||||
#define BLRM_REGISTER 0x0019
|
||||
#define BLRM_CHECK_SEMISYNC 0x001A
|
||||
#define BLRM_REQUEST_SEMISYNC 0x001B
|
||||
#define BLRM_REQUEST_BINLOGDUMP 0x001C
|
||||
#define BLRM_BINLOGDUMP 0x001D
|
||||
#define BLRM_SLAVE_STOPPED 0x001E
|
||||
#define BLRM_REGISTER_READY 0x0019
|
||||
#define BLRM_REGISTER 0x001A
|
||||
#define BLRM_CHECK_SEMISYNC 0x001B
|
||||
#define BLRM_REQUEST_SEMISYNC 0x001C
|
||||
#define BLRM_REQUEST_BINLOGDUMP 0x001D
|
||||
#define BLRM_BINLOGDUMP 0x001E
|
||||
#define BLRM_SLAVE_STOPPED 0x001F
|
||||
|
||||
#define BLRM_MAXSTATE 0x001E
|
||||
#define BLRM_MAXSTATE 0x001F
|
||||
|
||||
static char *blrm_states[] =
|
||||
{
|
||||
@ -772,6 +773,7 @@ static char *blrm_states[] =
|
||||
"Query server variables",
|
||||
"Query binlog variables",
|
||||
"Query @@lower_case_table_names",
|
||||
"Ready to Register",
|
||||
"Register slave",
|
||||
"Semi-Sync Support retrivial",
|
||||
"Request Semi-Sync Replication",
|
||||
|
||||
@ -714,12 +714,27 @@ blr_master_response(ROUTER_INSTANCE *router, GWBUF *buf)
|
||||
router->saved_master.map = buf;
|
||||
blr_cache_response(router, "map", buf);
|
||||
|
||||
if (router->maxwell_compat)
|
||||
{
|
||||
// Query for Server Variables
|
||||
buf = blr_make_query(router->master, MYSQL_CONNECTOR_SERVER_VARS_QUERY);
|
||||
router->master_state = BLRM_SERVER_VARS;
|
||||
router->master->func.write(router->master, buf);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Continue: ready for the registration, nothing to write/read
|
||||
router->master_state = BLRM_REGISTER_READY;
|
||||
}
|
||||
case BLRM_SERVER_VARS:
|
||||
/**
|
||||
* This branch could be reached as fallthrough from BLRM_MAP
|
||||
* with new state BLRM_REGISTER_READY
|
||||
* Go ahead if maxwell_compat is not set
|
||||
*/
|
||||
if (router->maxwell_compat)
|
||||
{
|
||||
if (router->saved_master.server_vars)
|
||||
{
|
||||
GWBUF_CONSUME_ALL(router->saved_master.server_vars);
|
||||
@ -727,11 +742,21 @@ blr_master_response(ROUTER_INSTANCE *router, GWBUF *buf)
|
||||
router->saved_master.server_vars = buf;
|
||||
blr_cache_response(router, "server_vars", buf);
|
||||
|
||||
buf = blr_make_query(router->master, "SELECT IF(@@global.log_bin, 'ON', 'OFF'), @@global.binlog_format, @@global.binlog_row_image");
|
||||
buf = blr_make_query(router->master,
|
||||
"SELECT IF(@@global.log_bin, 'ON', 'OFF'), "
|
||||
"@@global.binlog_format, @@global.binlog_row_image");
|
||||
router->master_state = BLRM_BINLOG_VARS;
|
||||
router->master->func.write(router->master, buf);
|
||||
break;
|
||||
}
|
||||
case BLRM_BINLOG_VARS:
|
||||
/**
|
||||
* This branch could be reached as fallthrough from BLRM_MAP
|
||||
* with new state BLRM_REGISTER_READY.
|
||||
* Go ahead if maxwell_compat is not set
|
||||
*/
|
||||
if (router->maxwell_compat)
|
||||
{
|
||||
if (router->saved_master.binlog_vars)
|
||||
{
|
||||
GWBUF_CONSUME_ALL(router->saved_master.binlog_vars);
|
||||
@ -743,14 +768,26 @@ blr_master_response(ROUTER_INSTANCE *router, GWBUF *buf)
|
||||
router->master_state = BLRM_LOWER_CASE_TABLES;
|
||||
router->master->func.write(router->master, buf);
|
||||
break;
|
||||
}
|
||||
case BLRM_LOWER_CASE_TABLES:
|
||||
/**
|
||||
* This branch could be reached as fallthrough from BLRM_MAP
|
||||
* with new state BLRM_REGISTER_READY.
|
||||
* Go ahead if maxwell_compat is not set
|
||||
*/
|
||||
if (router->maxwell_compat)
|
||||
{
|
||||
if (router->saved_master.lower_case_tables)
|
||||
{
|
||||
GWBUF_CONSUME_ALL(router->saved_master.lower_case_tables);
|
||||
}
|
||||
router->saved_master.lower_case_tables = buf;
|
||||
blr_cache_response(router, "lower_case_tables", buf);
|
||||
|
||||
router->master_state = BLRM_REGISTER_READY;
|
||||
// Continue: ready for the registration, nothing to write/read
|
||||
}
|
||||
case BLRM_REGISTER_READY:
|
||||
// Prepare registration
|
||||
buf = blr_make_registration(router);
|
||||
router->master_state = BLRM_REGISTER;
|
||||
router->master->func.write(router->master, buf);
|
||||
@ -2448,7 +2485,7 @@ bool blr_send_event(blr_thread_role_t role,
|
||||
"the event has already been sent by thread %lu in the role of %s. "
|
||||
"%u bytes buffered for writing in DCB %p. %lu events received from master.",
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
binlog_name,
|
||||
binlog_pos,
|
||||
@ -2511,7 +2548,7 @@ bool blr_send_event(blr_thread_role_t role,
|
||||
{
|
||||
MXS_ERROR("Failed to send an event of %u bytes to slave at %s:%d.",
|
||||
hdr->event_size, slave->dcb->remote,
|
||||
ntohs(slave->dcb->ipv4.sin_port));
|
||||
dcb_get_port(slave->dcb));
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -2262,7 +2262,7 @@ blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue
|
||||
"Latest safe position %lu, end of binlog file %lu",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
(unsigned long)slave->binlog_pos,
|
||||
@ -2382,7 +2382,7 @@ blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue
|
||||
|
||||
MXS_NOTICE("%s: Slave %s:%d, server id %d requested binlog file %s from position %lu",
|
||||
router->service->name, slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile, (unsigned long)slave->binlog_pos);
|
||||
|
||||
@ -2546,7 +2546,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
}
|
||||
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
|
||||
"failed to open binlog file",
|
||||
slave->dcb->remote, ntohs((slave->dcb->ipv4).sin_port), slave->serverid,
|
||||
slave->dcb->remote, dcb_get_port(slave->dcb), slave->serverid,
|
||||
slave->binlogfile);
|
||||
|
||||
slave->cstate &= ~CS_BUSY;
|
||||
@ -2675,7 +2675,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
|
||||
"failed to open binlog file in rotate event",
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile);
|
||||
|
||||
@ -2719,7 +2719,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
MXS_WARNING("Slave %s:%i, server-id %d, binlog '%s, position %u: "
|
||||
"Slave-thread could not send event to slave, closing connection.",
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
binlog_name,
|
||||
binlog_pos);
|
||||
@ -2754,7 +2754,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
read_errmsg);
|
||||
@ -2765,7 +2765,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
read_errmsg);
|
||||
@ -2787,7 +2787,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
read_errmsg);
|
||||
@ -2822,7 +2822,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
"current committed transaction event being sent: %lu, %s",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
slave->stats.n_events - events_before,
|
||||
@ -2897,7 +2897,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
|
||||
"previous failure of the master.",
|
||||
router->service->name,
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile, (unsigned long)slave->binlog_pos,
|
||||
router->binlog_name, router->binlog_position);
|
||||
@ -3142,7 +3142,7 @@ blr_slave_read_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
|
||||
{
|
||||
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', blr_read_binlog failure: %s",
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
err_msg);
|
||||
@ -6104,7 +6104,7 @@ blr_slave_read_ste(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, uint32_t fde_en
|
||||
{
|
||||
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', blr_read_binlog failure: %s",
|
||||
slave->dcb->remote,
|
||||
ntohs((slave->dcb->ipv4).sin_port),
|
||||
dcb_get_port(slave->dcb),
|
||||
slave->serverid,
|
||||
slave->binlogfile,
|
||||
err_msg);
|
||||
|
||||
@ -102,7 +102,6 @@ static uint64_t getCapabilities(MXS_ROUTER* instance);
|
||||
static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses);
|
||||
static void rses_end_locked_router_action(ROUTER_CLIENT_SES* rses);
|
||||
static SERVER_REF *get_root_master(SERVER_REF *servers);
|
||||
static int handle_state_switch(DCB* dcb, DCB_REASON reason, void * routersession);
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
@ -810,49 +809,3 @@ static SERVER_REF *get_root_master(SERVER_REF *servers)
|
||||
}
|
||||
return master_host;
|
||||
}
|
||||
|
||||
static int handle_state_switch(DCB* dcb, DCB_REASON reason, void * routersession)
|
||||
{
|
||||
ss_dassert(dcb != NULL);
|
||||
MXS_SESSION* session = dcb->session;
|
||||
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*) routersession;
|
||||
SERVICE* service = session->service;
|
||||
MXS_ROUTER* router = (MXS_ROUTER *) service->router;
|
||||
|
||||
if (NULL == dcb->session->router_session && DCB_REASON_ERROR != reason)
|
||||
{
|
||||
/*
|
||||
* We cannot handle a DCB that does not have a router session,
|
||||
* except in the case where error processing is invoked.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
switch (reason)
|
||||
{
|
||||
case DCB_REASON_CLOSE:
|
||||
dcb->func.close(dcb);
|
||||
break;
|
||||
case DCB_REASON_DRAINED:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_HIGH_WATER:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_LOW_WATER:
|
||||
/** Do we need to do anything? */
|
||||
break;
|
||||
case DCB_REASON_ERROR:
|
||||
dcb->func.error(dcb);
|
||||
break;
|
||||
case DCB_REASON_HUP:
|
||||
dcb->func.hangup(dcb);
|
||||
break;
|
||||
case DCB_REASON_NOT_RESPONDING:
|
||||
dcb->func.hangup(dcb);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user