Merge branch 'develop' into mon_script_test

This commit is contained in:
Markus Makela
2015-04-06 16:15:37 +03:00
21 changed files with 620 additions and 129 deletions

View File

@ -52,37 +52,25 @@ configure_file(${CMAKE_SOURCE_DIR}/server/test/maxscale_test.h.in ${CMAKE_BINARY
configure_file(${CMAKE_SOURCE_DIR}/etc/postinst.in ${CMAKE_BINARY_DIR}/postinst)
configure_file(${CMAKE_SOURCE_DIR}/etc/postrm.in ${CMAKE_BINARY_DIR}/postrm)
set(CMAKE_C_FLAGS "-Wall -Wno-unused-variable -Wno-unused-function -fPIC")
set(CMAKE_CXX_FLAGS "-Wall -Wno-unused-variable -Wno-unused-function -fPIC")
set(DEBUG_FLAGS "-ggdb -pthread -pipe -Wformat -fstack-protector --param=ssp-buffer-size=4")
set(FLAGS "-Wall -Wno-unused-variable -Wno-unused-function -fPIC" CACHE STRING "Compilation flags")
set(DEBUG_FLAGS "-ggdb -pthread -pipe -Wformat -fstack-protector --param=ssp-buffer-size=4" CACHE STRING "Debug compilation flags")
if(CMAKE_VERSION VERSION_GREATER 2.6)
if((CMAKE_C_COMPILER_ID STREQUAL "GNU") AND (NOT (CMAKE_C_COMPILER_VERSION VERSION_LESS 4.2)))
message(STATUS "C Compiler supports: -Werror=format-security")
set(DEBUG_FLAGS "${DEBUG_FLAGS} -Werror=format-security")
set(DEBUG_FLAGS "${DEBUG_FLAGS} -Werror=format-security" CACHE STRING "Debug compilation flags")
endif()
if((CMAKE_C_COMPILER_ID STREQUAL "GNU") AND (NOT (CMAKE_C_COMPILER_VERSION VERSION_LESS 4.6)))
message(STATUS "C Compiler supports: -Wno-unused-but-set-variable")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-but-set-variable ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-variable ")
set(FLAGS "${FLAGS} -Wno-unused-but-set-variable " CACHE STRING "Compilation flags")
endif()
endif()
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBUG_FLAGS} -DSS_DEBUG")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBUG_FLAGS} -DSS_DEBUG")
message(STATUS "Generating debugging symbols and enabling debugging code")
elseif(CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEBUG_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEBUG_FLAGS}")
message(STATUS "Generating debugging symbols")
endif()
if(DEFINED OLEVEL )
IF(DEFINED OLEVEL )
if((OLEVEL GREATER -1) AND (OLEVEL LESS 4) )
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O${OLEVEL}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O${OLEVEL}")
set(FLAGS "${FLAGS} -O${OLEVEL}" CACHE STRING "Compilation flags")
message(STATUS "Optimization level at: ${OLEVEL}")
else()
message(WARNING "Optimization level was set to a bad value, ignoring it. (Valid values are 0-3)")
@ -90,16 +78,25 @@ if(DEFINED OLEVEL )
endif()
if(GCOV)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
set(FLAGS "${FLAGS} -fprofile-arcs -ftest-coverage" CACHE STRING "Compilation flags")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
endif()
if(FAKE_CODE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFAKE_CODE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFAKE_CODE")
set(FLAGS "${FLAGS} -DFAKE_CODE" CACHE STRING "Compilation flags")
endif()
set(CMAKE_C_FLAGS "${FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${DEBUG_FLAGS} -DSS_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-ggdb")
set(CMAKE_CXX_FLAGS "${FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "${DEBUG_FLAGS} -DSS_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-ggdb")
subdirs(MYSQL_DIR_ALL ${MYSQL_DIR})
foreach(DIR ${MYSQL_DIR_ALL})
include_directories(${DIR})

View File

@ -9,11 +9,12 @@ These are the changes introduced in the next MaxScale version. This is not the o
* Firewall filter
* Multi-Master monitor
* RabbitMQ logging filter
* Schema Sharding router
* Added option to use high precision timestamps in logging.
* Readwritesplit router now returns the master server's response.
* New readwritesplit router option added. It is now possible to control the amount of memory readwritesplit sessions will consume by limiting the amount of session modifying statements they can execute.
* Minimum required CMake version is now 2.8.12 for package building.
* Session idle timeout added for services.
* Session idle timeout added for services. More details can be found in the configuration guide.
* Monitor API is updated to 2.0.0. Monitors with earlier versions of the API no longer work with this version of MaxScale.
* MaxScale now requires libcurl and libcurl development headers.
* Nagios plugins added.

View File

@ -10,7 +10,7 @@ This document details the changes in version 1.1 since the release of the 1.0.5
Replicate Binlog from the master to slave through MaxScale as simplified relay server for reduced network load and disaster recovery
### Database Firewall Filter
Block queries based on columns in the query, where condition, query type(select, insert, delete, update), presence of wildcard, regular expression match and time of the query
Block queries based on columns in the query, where condition, query type(select, insert, delete, update), presence of wildcard in column selection, regular expression match and time of the query
### Schema Sharding Router
Route to databases sharded by schema without application level knowledge of shard configuration
@ -22,7 +22,7 @@ Pass hints in the SQL statement to influence the routing decision based on repli
Routing to a named server if incoming query matches a regular expression
### Canonical Query logging
Convert incoming queries to canonical form and push the query and response into RabbitMQ Broker- for a RabbitMQ Client to later retrieve from
Convert incoming queries to canonical form and push the query and response into RabbitMQ Broker for a RabbitMQ Client to later retrieve from
### Nagios Plugin
Plugin scripts for monitoring MaxScale status and performance from a Nagios Server
@ -33,6 +33,9 @@ Receive notification of security update and patches tailored to your MaxScale co
### MySQL NDB cluster support
Connection based routing to MySQL NDB clusters
### Updated installation path
MaxScale is now installed into `/usr/local/mariadb-maxscale`
## Bug Fixes
A number of bug fixes have been applied between the 1.0.5 GA and this RC release. The table below lists the bugs that have been resolved. The details for each of these may be found in https://mariadb.atlassian.net/projects/MXS or in the former http://bugs.mariadb.com Bug database
@ -42,10 +45,62 @@ A number of bug fixes have been applied between the 1.0.5 GA and this RC release
<td>ID</td>
<td>Summary</td>
</tr>
<tr>
<td>MXS-80</td>
<td>"show sessions" can crash MaxScale</td>
</tr>
<tr>
<td>MXS-79</td>
<td>schemarouter hangs if client connects with empty database</td>
</tr>
<tr>
<td>MXS-78</td>
<td>"USE" statement gives unpredictable/unexpected results</td>
</tr>
<tr>
<td>MXS-76</td>
<td>core/dbusers.c needs better error messages</td>
</tr>
<tr>
<td>MXS-74</td>
<td>Crash when no arguments given to on_queries clause</td>
</tr>
<tr>
<td>MXS-72</td>
<td>dbfwfilter on_queries clause appears to be ignored</td>
</tr>
<tr>
<td>MXS-71</td>
<td>dbfwfilter at_times clause seems to erroneously block user</td>
</tr>
<tr>
<td>MXS-68</td>
<td>Wrong rule name in dbfwfilter leads to MaxScale crash</td>
</tr>
<tr>
<td>MXS-65</td>
<td>Omitting <any|all|strict_all> in users directive causes crash in libdbfwfilter.so(link_rules)</td>
</tr>
<tr>
<td>MXS-63</td>
<td>Maxkeys and Maxpasswd log to /tpm</td>
</tr>
<tr>
<td>MXS-57</td>
<td>MaxScale should write a message to the error log when config is not found</td>
</tr>
<tr>
<td>MXS-54</td>
<td>Write failed auth attempt to trace log</td>
</tr>
<tr>
<td>MXS-50</td>
<td>Removing 1.0.5 RPM gives error about /etc/ld.so.conf.d/maxscale.conf</td>
</tr>
<tr>
<td>MXS-47</td>
<td>Session freeze when small tail packet</td>
</tr>
<tr>
<tr>
<td>MXS-5</td>
<td>Possible memory leak in readwritesplit router</td>
@ -189,6 +244,15 @@ There are a number bugs and known limitations within this version of MaxScale, t
* Service init script is missing after upgrade from 1.0 in RPM-based system. Can be fixed by reinstalling the package ('yum reinstall maxscale' or 'rpm -i --force /maxscale-1.1.rpm')
* Binlog Router Plugin is compatible with MySQL 5.6 and MariaDB 5.5
Binlog Router Plugin compatibility with MariaDB 10 is alpha
* LONGBLOG are currently not supported.
* Galera Cluster variables, such as @@wsrep_node_name, are not resolved by the embedded MariaDB parser.
* The Database Firewall filter does not support multi-statements. Using them will result in an error being sent to the client.
## Packaging
Both RPM and Debian packages are available for MaxScale in addition to the tar based releases previously distributed we now provide

View File

@ -1,7 +1,7 @@
#Database Firewall filter
## Overview
The database firewall filter is used to block queries that match a set of rules. It can be used to prevent harmful queries from reaching the backend database instances or to limit access to the database based on a more flexible set of rules compared to the traditional GRANT-based privilege system.
The database firewall filter is used to block queries that match a set of rules. It can be used to prevent harmful queries from reaching the backend database instances or to limit access to the database based on a more flexible set of rules compared to the traditional GRANT-based privilege system. Currently the filter does not support multi-statements.
## Configuration
@ -16,7 +16,7 @@ rules=/home/user/rules.txt
### Filter Options
The database firewall filter does not support any filter options.
The database firewall filter supports a single option, `ignorecase`. This will set the regular expression matching to case-insensitive mode.
### Filter Parameters
@ -32,7 +32,7 @@ rule NAME deny [wildcard | columns VALUE ... |
no_where_clause] [at_times VALUE...] [on_queries [select|update|insert|delete]]
```
Rules always define a blocking action so the basic mode for the database firewall filter is to allow all queries that do not match a given set of rules. Rules are identified by their name and have a mandatory part and optional parts.
Rules always define a blocking action so the basic mode for the database firewall filter is to allow all queries that do not match a given set of rules. Rules are identified by their name and have a mandatory part and optional parts. You can add comments to the rule files by adding the `#` character at the beginning of the line.
The first step of defining a rule is to start with the keyword `rule` which identifies this line of text as a rule. The second token is identified as the name of the rule. After that the mandatory token `deny` is required to mark the start of the actual rule definition.
@ -40,23 +40,23 @@ The first step of defining a rule is to start with the keyword `rule` which iden
The database firewall filter's rules expect a single mandatory parameter for a rule. You can define multiple rules to cover situations where you would like to apply multiple mandatory rules to a query.
#### wildcard
#### `wildcard`
This rule blocks all queries that use the wildcard character *.
#### columns
#### `columns`
This rule expects a list of values after the `columns` keyword. These values are interpreted as column names and if a query targets any of these, it is blocked.
#### regex
#### `regex`
This rule blocks all queries matching a regex enclosed in single or double quotes.
#### limit_queries
#### `limit_queries`
The limit_queries rule expects three parameters. The first parameter is the number of allowed queries during the time period. The second is the time period in seconds and the third is the amount of time for which the rule is considered active and blocking.
#### no_where_clause
#### `no_where_clause`
This rule inspects the query and blocks it if it has no WHERE clause. For example, this would disallow a `DELETE FROM ...` query without a `WHERE` clause. This does not prevent wrongful usage of the `WHERE` clause e.g. `DELETE FROM ... WHERE 1=1`.
@ -64,11 +64,11 @@ This rule inspects the query and blocks it if it has no WHERE clause. For exampl
Each mandatory rule accepts one or more optional parameters. These are to be defined after the mandatory part of the rule.
#### at_times
#### `at_times`
This rule expects a list of time ranges that define the times when the rule in question is active. The time formats are expected to be ISO-8601 compliant and to be separated by a single dash (the - character). For example, to define the active period of a rule to be 5pm to 7pm, you would include `at times 17:00:00-19:00:00` in the rule definition.
#### on_queries
#### `on_queries`
This limits the rule to be active only on certain types of queries.

View File

@ -14,18 +14,24 @@ In almost all the cases these can be avoided by proper server configuration and
Here is an example configuration of the schemarouter router:
[Shard Router]
```
Shard Router]
type=service
router=schemarouter
servers=server1,server2
user=myuser
passwd=mypwd
auth_all_servers=1
```
The module generates the list of databases based on the servers parameter using the connecting client's credentials. The user and passwd parameters define the credentials that are used to fetch the authentication data from the database servers. The credentials used only require the same grants as mentioned in the configuration documentation.
The list of databases is built by sending a SHOW DATABASES query to all the servers. This requires the user to have at least USAGE and SELECT grants on the databases that need be sharded.
If you are connecting directly to a database or have different users on some of the servers, you need to get the authentication data from all the servers. You can control this with the `auth_all_servers` parameter. With this parameter, MaxScale forms a union of all the users and their grants from all the servers. By default, the schemarouter will fetch the authentication data from all servers.
For example, if two servers have the database 'shard' and the following rights are granted only on one server, all queries targeting the database 'shard' would be routed to the server where the grants were given.
```
# Execute this on both servers
CREATE USER 'john'@'%' IDENTIFIED BY 'password';
@ -33,13 +39,14 @@ CREATE USER 'john'@'%' IDENTIFIED BY 'password';
# Execute this only on the server where you want the queries to go
GRANT SELECT,USAGE ON shard.* TO 'john'@'%';
```
This would in effect allow the user 'john' to only see the database 'shard' on this server. Take notice that these grants are matched against MaxScale's hostname instead of the client's hostname. Only user authentication uses the client's hostname and all other grants use MaxScale's hostname.
## Limitations
The schemarouter router currently has some limitations due to the nature of the sharding implementation and the way the session variables are detected and routed. Here is a list of the current limitations.
- Cross-database queries (e.g. SELECT column FROM database1.table UNION select column FROM database2.table) are not supported and are routed either to the first explicit database in the query, the current database in use or to the first available database, if none of the previous conditions are met.
- Cross-database queries (e.g. `SELECT column FROM database1.table UNION select column FROM database2.table`) are not supported and are routed either to the first explicit database in the query, the current database in use or to the first available database, if none of the previous conditions are met.
- Queries without explicit databases that are not session commands in them are either routed to the current or the first available database. This means that, for example when creating a new database, queries should be done directly on the node or the router should be equipped with the hint filter and a routing hint should be used.

View File

@ -66,6 +66,9 @@ macro(set_variables)
# Build packages
set(PACKAGE FALSE CACHE BOOL "Enable package building (this disables local installation of system files)")
# Build extra tools
set(BUILD_TOOLS FALSE CACHE BOOL "Build extra utility tools")
endmacro()
macro(check_deps)

View File

@ -1664,6 +1664,9 @@ skygw_query_op_t query_classifier_get_operation(GWBUF* querybuf)
case SQLCOM_DROP_INDEX:
operation = QUERY_OP_DROP_INDEX;
break;
case SQLCOM_CHANGE_DB:
operation = QUERY_OP_CHANGE_DB;
break;
default:
operation = QUERY_OP_UNDEFINED;

View File

@ -74,7 +74,8 @@ typedef enum {
QUERY_OP_CREATE_TABLE = (1 << 7),
QUERY_OP_CREATE_INDEX = (1 << 8),
QUERY_OP_DROP_TABLE = (1 << 9),
QUERY_OP_DROP_INDEX = (1 << 10)
QUERY_OP_DROP_INDEX = (1 << 10),
QUERY_OP_CHANGE_DB = (1 << 11)
}skygw_query_op_t;
typedef struct parsing_info_st {

View File

@ -1,4 +1,4 @@
if(BUILD_TESTS)
if(BUILD_TESTS OR BUILD_TOOLS)
file(GLOB FULLCORE_SRC *.c)
add_library(fullcore STATIC ${FULLCORE_SRC})
target_link_libraries(fullcore ${CURL_LIBRARIES} log_manager utils pthread ${EMBEDDED_LIB} ssl aio rt crypt dl crypto inih z m stdc++)

View File

@ -89,6 +89,7 @@
#define LOAD_MYSQL_DATABASE_NAMES "SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \'\\'\',\'\')=CURRENT_USER()) AS tbl2)"
#define ERROR_NO_SHOW_DATABASES "%s: Unable to load database grant information, MaxScale authentication will proceed without including database permissions. To correct this GRANT SHOW DATABASES ON *.* privilege to the user %s."
/** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
@ -380,10 +381,7 @@ addDatabases(SERVICE *service, MYSQL *con)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%s: Unable to load database grant information, MaxScale "
"authentication will proceed without including database "
"permissions. To correct this GRANT select permission "
"on mysql.db to the user %s.",
ERROR_NO_SHOW_DATABASES,
service->name, service_user)));
}
@ -486,10 +484,7 @@ getDatabases(SERVICE *service, MYSQL *con)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%s: Unable to load database grant information, MaxScale "
"authentication will proceed without including database "
"permissions. To correct this GRANT select permission "
"on mysql.db to the user %s.",
ERROR_NO_SHOW_DATABASES,
service->name, service_user)));
}
@ -762,10 +757,7 @@ getAllUsers(SERVICE *service, USERS *users)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%s: Unable to load database grant information, MaxScale "
"authentication will proceed without including database "
"permissions. To correct this GRANT select permission "
"on msql.db to the user %s.",
ERROR_NO_SHOW_DATABASES,
service->name, service_user)));
/* check for root user select */
@ -1255,10 +1247,7 @@ getUsers(SERVICE *service, USERS *users)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%s: Unable to load database grant information, MaxScale "
"authentication will proceed without including database "
"permissions. To correct this GRANT select permission "
"on msql.db to the user %s.",
ERROR_NO_SHOW_DATABASES,
service->name, service_user)));
/* check for root user select */

View File

@ -830,11 +830,17 @@ int modutil_count_statements(GWBUF* buffer)
char* end = ((char*)(buffer)->end);
int num = 1;
while((ptr = strnchr_esc(ptr,';', end - ptr)))
while(ptr < end && (ptr = strnchr_esc(ptr,';', end - ptr)))
{
num++;
while(*ptr == ';')
ptr++;
}
if(*(end - 1) == ';')
{
num--;
}
return num;
}

View File

@ -608,21 +608,30 @@ SESSION *ptr;
ptr = allSessions;
while (ptr)
{
double idle = (hkheartbeat - ptr->client->last_read);
idle = idle > 0 ? idle/10.0:0;
dcb_printf(dcb, "Session %d (%p)\n",ptr->ses_id, ptr);
dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state));
dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service);
dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client);
if (ptr->client && ptr->client->remote)
{
dcb_printf(dcb, "\tClient Address: %s%s%s\n",
ptr->client->user?ptr->client->user:"",
ptr->client->user?"@":"",
ptr->client->remote);
}
dcb_printf(dcb, "\tConnected: %s",
asctime_r(localtime_r(&ptr->stats.connect, &result), timebuf));
if(ptr->client->state == DCB_STATE_POLLING)
if(ptr->client && ptr->client->state == DCB_STATE_POLLING)
{
double idle = (hkheartbeat - ptr->client->last_read);
idle = idle > 0 ? idle/10.0:0;
dcb_printf(dcb, "\tIdle: %.0f seconds\n",idle);
}
ptr = ptr->next;
}
spinlock_release(&session_spin);

View File

@ -56,7 +56,7 @@ DCB *dcb;
int result;
int argc = 3;
init_test_env();
init_test_env(NULL);
/* char* argv[] = */
/* { */
/* "log_manager", */

View File

@ -6,7 +6,7 @@
#include <maxscale_test.h>
#include <log_manager.h>
void init_test_env()
void init_test_env(char *path)
{
int argc = 3;
@ -14,7 +14,7 @@ void init_test_env()
{
"log_manager",
"-j",
TEST_LOG_DIR,
path? path:TEST_LOG_DIR,
NULL
};

View File

@ -40,6 +40,13 @@ if(BUILD_SLAVELAG)
install(TARGETS slavelag DESTINATION modules)
endif()
if(BUILD_TOOLS)
add_executable(ruleparser dbfwfilter.c)
target_compile_definitions(ruleparser PUBLIC "BUILD_RULE_PARSER")
target_link_libraries(ruleparser ${EMBEDDED_LIB} log_manager utils query_classifier fullcore)
install(TARGETS ruleparser DESTINATION tools)
endif()
add_subdirectory(hint)
if(BUILD_TESTS)

View File

@ -221,6 +221,7 @@ typedef struct {
bool def_op;/*< Default operation mode, defaults to deny */
SPINLOCK* lock;/*< Instance spinlock */
long idgen; /*< UID generator */
int regflags;
} FW_INSTANCE;
/**
@ -669,6 +670,7 @@ bool link_rules(char* orig, FW_INSTANCE* instance)
rval = false;
goto parse_err;
}
if(strcmp(tok,"match") == 0){
tok = strtok_r(NULL," ",&saveptr);
if(tok == NULL)
@ -690,8 +692,31 @@ bool link_rules(char* orig, FW_INSTANCE* instance)
goto parse_err;
}
}
else
{
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule syntax incorrect, bad token: %s",tok);
rval = false;
goto parse_err;
}
tok = strtok_r(NULL," ",&saveptr);
if(tok != NULL)
{
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule syntax incorrect, extra token found after 'match' keyword: %s",orig);
rval = false;
goto parse_err;
}
tok = strtok_r(ruleptr," ",&saveptr);
if(tok == NULL)
{
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule syntax incorrect, no rules given: %s",orig);
rval = false;
goto parse_err;
}
tok = strtok_r(NULL," ",&saveptr);
if(tok == NULL)
@ -716,6 +741,8 @@ bool link_rules(char* orig, FW_INSTANCE* instance)
else
{
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule syntax incorrect, could not find rule '%s'.",tok);
rval = false;
goto parse_err;
}
tok = strtok_r(NULL," ",&saveptr);
}
@ -837,7 +864,7 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
if(tok == NULL)
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, no rule rule: %s",rule);
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, no rule: %s",rule);
rval = false;
goto retblock;
}
@ -900,6 +927,12 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
tok = strtok_r(NULL, " ,",&saveptr);
if(tok == NULL)
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, no allow or deny: %s",rule);
rval = false;
goto retblock;
}
if((allow = (strcmp(tok,"allow") == 0)) ||
(deny = (strcmp(tok,"deny") == 0)))
@ -946,7 +979,9 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
if(!check_time(tok))
{
not_valid = true;
break;
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, malformed time definition: %s",tok);
rval = false;
goto retblock;
}
TIMERANGE *tmp = parse_time(tok,instance);
@ -984,6 +1019,20 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
char delim = '\'';
int n_char = 0;
if(tok == NULL)
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, No regex string.");
rval = false;
goto retblock;
}
if(*tok != '\'' && *tok != '\"')
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: Rule parsing failed, regex string not quoted.");
rval = false;
goto retblock;
}
while(*tok == '\'' || *tok == '"')
{
delim = *tok;
@ -1036,7 +1085,7 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
memcpy(str, start, (tok-start));
if(regcomp(re, str,REG_NOSUB)){
if(regcomp(re, str,REG_NOSUB|instance->regflags)){
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Invalid regular expression '%s'.", str);
rval = false;
free(re);
@ -1054,6 +1103,7 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
{
QUERYSPEED* qs = (QUERYSPEED*)calloc(1,sizeof(QUERYSPEED));
char *errptr = NULL;
spinlock_acquire(instance->lock);
qs->id = ++instance->idgen;
@ -1066,7 +1116,34 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Missing parameter in limit_queries: '%s'.", rule);
goto retblock;
}
qs->limit = atoi(tok);
qs->limit = strtol(tok,&errptr,0);
if(errptr && *errptr != '\0')
{
free(qs);
rval = false;
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule parsing failed, not a number: '%s'.", tok);
goto retblock;
}
errptr = NULL;
tok = strtok_r(NULL," ",&saveptr);
if(tok == NULL){
free(qs);
rval = false;
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Missing parameter in limit_queries: '%s'.", rule);
goto retblock;
}
qs->period = strtod(tok,&errptr);
if(errptr && *errptr != '\0')
{
free(qs);
rval = false;
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule parsing failed, not a number: '%s'.", tok);
goto retblock;
}
errptr = NULL;
tok = strtok_r(NULL," ",&saveptr);
if(tok == NULL){
@ -1075,15 +1152,16 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Missing parameter in limit_queries: '%s'.", rule);
goto retblock;
}
qs->period = atof(tok);
tok = strtok_r(NULL," ",&saveptr);
if(tok == NULL){
qs->cooldown = strtod(tok,&errptr);
if(errptr && *errptr != '\0')
{
free(qs);
rval = false;
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Missing parameter in limit_queries: '%s'.", rule);
skygw_log_write(LOGFILE_ERROR, "dbfwfilter: Rule parsing failed, not a number: '%s'.", tok);
goto retblock;
}
qs->cooldown = atof(tok);
ruledef->type = RT_THROTTLE;
ruledef->data = (void*)qs;
}
@ -1113,6 +1191,14 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
goto retblock;
}
}
else
{
skygw_log_write(LOGFILE_ERROR,
"dbfwfilter: Unknown rule type: %s"
,tok);
rval = false;
goto retblock;
}
tok = strtok_r(NULL," ,",&saveptr);
}
@ -1125,6 +1211,22 @@ bool parse_rule(char* rule, FW_INSTANCE* instance)
}
bool is_comment(char* str)
{
char *ptr = str;
while(*ptr != '\0')
{
if(isspace(*ptr))
ptr++;
else if(*ptr == '#')
return true;
else
return false;
}
return true;
}
/**
* Create an instance of the filter for a particular service
* within MaxScale.
@ -1163,17 +1265,27 @@ createInstance(char **options, FILTER_PARAMETER **params)
my_instance->htable = ht;
my_instance->def_op = true;
my_instance->userstrings = NULL;
my_instance->regflags = 0;
for(i = 0;params[i];i++){
if(strcmp(params[i]->name, "rules") == 0){
if(filename)
free(filename);
filename = strdup(params[i]->value);
}
}
if(options)
{
for(i = 0;options[i];i++)
{
if(strcmp(options[i],"ignorecase") == 0)
{
my_instance->regflags |= REG_ICASE;
}
}
}
if(filename == NULL)
{
@ -1192,7 +1304,8 @@ createInstance(char **options, FILTER_PARAMETER **params)
return NULL;
}
free(filename);
bool file_empty = true;
while(!feof(file))
{
@ -1215,6 +1328,13 @@ createInstance(char **options, FILTER_PARAMETER **params)
*nl = '\0';
}
if(strnlen(buffer,2048) < 1 || is_comment(buffer))
{
continue;
}
file_empty = false;
if(!parse_rule(buffer,my_instance))
{
fclose(file);
@ -1223,12 +1343,27 @@ createInstance(char **options, FILTER_PARAMETER **params)
}
}
if(file_empty)
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: File is empty: %s");
free(filename);
err = true;
goto retblock;
}
fclose(file);
free(filename);
/**Apply the rules to users*/
ptr = my_instance->userstrings;
if(ptr == NULL)
{
skygw_log_write(LOGFILE_ERROR,"dbfwfilter: No 'users' line found.");
err = true;
goto retblock;
}
while(ptr){
if(!link_rules(ptr->value,my_instance))
@ -1849,6 +1984,15 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
ipaddr = strdup(dcb->remote);
sprintf(uname_addr,"%s@%s",dcb->user,ipaddr);
if(modutil_is_SQL(queue) && modutil_count_statements(queue) > 1)
{
if(my_session->errmsg)
free(my_session->errmsg);
my_session->errmsg = strdup("This filter does not support multi-statements.");
accept = false;
goto queryresolved;
}
if((user = (USER*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL){
while(user == NULL && next_ip_class(ipaddr)){
@ -1956,3 +2100,90 @@ diagnostic(FILTER *instance, void *fsession, DCB *dcb)
spinlock_release(my_instance->lock);
}
}
#ifdef BUILD_RULE_PARSER
#include <test_utils.h>
int main(int argc, char** argv)
{
char ch;
bool have_icase = false;
char *home;
char cwd[PATH_MAX];
char* opts[2] = {NULL,NULL};
FILTER_PARAMETER ruleparam;
FILTER_PARAMETER* paramlist[2];
opterr = 0;
while((ch = getopt(argc,argv,"h?")) != -1)
{
switch(ch)
{
case '?':
case 'h':
printf("Usage: %s [OPTION]... RULEFILE\n"
"Options:\n"
"\t-?\tPrint this information\n",
argv[0]);
return 0;
default:
printf("Unknown option '%c'.\n",ch);
return 1;
}
}
if(argc < 2)
{
printf("Usage: %s [OPTION]... RULEFILE\n"
"-?\tPrint this information\n",
argv[0]);
return 1;
}
if((home = getenv("MAXSCALE_HOME")) == NULL)
{
home = malloc(sizeof(char)*(PATH_MAX+1));
if(getcwd(home,PATH_MAX) == NULL)
{
free(home);
home = NULL;
}
}
printf("Log files written to: %s\n",home?home:"/tpm");
int argc_ = 11;
char* argv_[] =
{
"log_manager",
"-j",home?home:"/tmp",
"-a","ruleparser_debug",
"-c","ruleparser_trace",
"-e","ruleparser_message",
"-g","ruleparser_error",
NULL
};
skygw_logmanager_init(argc_,argv_);
init_test_env(home);
ruleparam.name = strdup("rules");
ruleparam.value = strdup(argv[1]);
paramlist[0] = &ruleparam;
paramlist[1] = NULL;
if(createInstance(opts,paramlist))
{
printf("Rule parsing was successful.\n");
}
else
{
printf("Failed to parse rule. Read the error log for the reason of the failure.\n");
}
skygw_log_sync_all();
return 0;
}
#endif

View File

@ -1,4 +1,4 @@
rule union_regex deny regex '.*union.*'
rule dont_delete_everything deny no_where_clause on_operations delete|update
rule dont_delete_everything deny no_where_clause on_queries delete|update
rule no_wildcard deny wildcard
users %@% match any rules union_regex dont_delete_everything no_wildcard

View File

@ -158,7 +158,7 @@ typedef struct router_slave {
#endif
DCB *dcb; /*< The slave server DCB */
int state; /*< The state of this slave */
int binlog_pos; /*< Binlog position for this slave */
uint32_t binlog_pos; /*< Binlog position for this slave */
char binlogfile[BINLOG_FNAMELEN+1];
/*< Current binlog file for this slave */
char *uuid; /*< Slave UUID */

View File

@ -374,8 +374,13 @@ int query_len;
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
word = strtok_r(NULL, sep, &brkb);
if (word && (strcasecmp(word, "@@global.biglog_checksum'") == 0))
if (word && (strcasecmp(word, "'none'") == 0))
slave->nocrc = 1;
else if (word && (strcasecmp(word, "@@global.binlog_checksum") == 0))
slave->nocrc = !router->master_chksum;
else
slave->nocrc = 0;
free(query_text);
return blr_slave_replay(router, slave, router->saved_master.chksum1);
@ -1196,7 +1201,7 @@ uint32_t chksum;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%s: COM_BINLOG_DUMP: binlog name '%s', length %d, "
"from position %d.", router->service->name,
"from position %lu.", router->service->name,
slave->binlogfile, binlognamelen,
slave->binlog_pos)));
@ -1524,7 +1529,7 @@ if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write(
if (slave->stats.n_caughtup == 1)
{
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
"%s: Slave %s is up to date %s, %u.",
"%s: Slave %s is up to date %s, %lu.",
router->service->name,
slave->dcb->remote,
slave->binlogfile, slave->binlog_pos)));
@ -1532,7 +1537,7 @@ if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write(
else if ((slave->stats.n_caughtup % 50) == 0)
{
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
"%s: Slave %s is up to date %s, %u.",
"%s: Slave %s is up to date %s, %lu.",
router->service->name,
slave->dcb->remote,
slave->binlogfile, slave->binlog_pos)));
@ -1556,7 +1561,7 @@ if (hkheartbeat - beat1 > 1) LOGIF(LE, (skygw_log_write(
* we ignore these issues during the rotate processing.
*/
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"Slave reached end of file for binlog file %s at %u "
"Slave reached end of file for binlog file %s at %lu "
"which is not the file currently being downloaded. "
"Master binlog is %s, %lu. This may be caused by a "
"previous failure of the master.",
@ -1956,7 +1961,7 @@ blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int se
{
/* server_id found */
server_found = 1;
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s: Slave %s, server id %d, disconnected by %s@%s",
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote,
server_id,
@ -2048,7 +2053,7 @@ blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
return 1;
}
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s: Slave %s, server id %d, disconnected by %s@%s",
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote, sptr->serverid, slave->dcb->user, slave->dcb->remote)));

View File

@ -693,6 +693,14 @@ createInstance(SERVICE *service, char **options)
server = service->dbref;
nservers = 0;
conf = service->svc_config_param;
if((config_get_param(conf,"auth_all_servers")) == NULL)
{
skygw_log_write(LOGFILE_MESSAGE,"Schemarouter: Authentication data is fetched from all servers. To disable this "
"add 'auth_all_servers=0' to the service.");
service->users_from_all = true;
}
while (server != NULL)
{
nservers++;
@ -803,6 +811,7 @@ static void* newSession(
MySQLProtocol* protocol = session->client->protocol;
MYSQL_session* data = session->data;
bool using_db = false;
bool have_db = false;
memset(db,0,MYSQL_DATABASE_MAXLEN+1);
@ -810,7 +819,8 @@ static void* newSession(
/* To enable connecting directly to a sharded database we first need
* to disable it for the client DCB's protocol so that we can connect to them*/
if(protocol->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB)
if(protocol->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB &&
(have_db = strnlen(data->db,MYSQL_DATABASE_MAXLEN) > 0))
{
protocol->client_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
@ -821,6 +831,11 @@ static void* newSession(
"postponing until databases have been mapped.",db);
}
if(!have_db)
{
LOGIF(LT,(skygw_log_write(LT,"schemarouter: Client'%s' connecting with empty database.",data->user)));
}
spinlock_release(&protocol->protocol_lock);
client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES));
@ -1004,7 +1019,7 @@ static void closeSession(
backend_ref_t* backend_ref;
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
"%lu [RWSplit:closeSession]",
"%lu [schemarouter:closeSession]",
pthread_self())));
/**
@ -1855,7 +1870,10 @@ static int routeQuery(
* all of them.
*/
if (packet_type == MYSQL_COM_INIT_DB)
skygw_query_op_t op = query_classifier_get_operation(querybuf);
if (packet_type == MYSQL_COM_INIT_DB ||
op == QUERY_OP_CHANGE_DB)
{
if (!(change_successful = change_current_db(inst, router_cli_ses, querybuf)))
{
@ -1882,7 +1900,8 @@ static int routeQuery(
router_cli_ses->rses_transaction_active,
querybuf->hint);
if (packet_type == MYSQL_COM_INIT_DB)
if (packet_type == MYSQL_COM_INIT_DB ||
op == QUERY_OP_CHANGE_DB)
{
route_target = TARGET_UNDEFINED;
tname = hashtable_fetch(router_cli_ses->dbhash,router_cli_ses->rses_mysql_session->db);
@ -2329,15 +2348,20 @@ static void clientReply (
if((target = hashtable_fetch(router_cli_ses->dbhash,
router_cli_ses->connect_db)) == NULL)
{
/** Unknown database, hang up on the client*/
skygw_log_write_flush(LOGFILE_TRACE,"schemarouter: Connecting to a non-existent database '%s'",
router_cli_ses->connect_db);
router_cli_ses->rses_closed = true;
char errmsg[128 + MYSQL_DATABASE_MAXLEN+1];
sprintf(errmsg,"Unknown database '%s'",router_cli_ses->connect_db);
GWBUF* errbuff = modutil_create_mysql_err_msg(1,0,1049,"42000",errmsg);
router_cli_ses->rses_client_dcb->func.write(router_cli_ses->rses_client_dcb,errbuff);
if(router_cli_ses->queue)
{
while((router_cli_ses->queue = gwbuf_consume(
router_cli_ses->queue,gwbuf_length(router_cli_ses->queue))));
}
rses_end_locked_router_action(router_cli_ses);
router_cli_ses->rses_client_dcb->func.hangup(router_cli_ses->rses_client_dcb);
return;
}
@ -4106,10 +4130,53 @@ static bool change_current_db(
plen = gw_mysql_get_byte3(packet) - 1;
/** Copy database name from MySQL packet to session */
if(query_classifier_get_operation(buf) == QUERY_OP_CHANGE_DB)
{
char* query = modutil_get_SQL(buf);
char *saved,*tok;
tok = strtok_r(query," ;",&saved);
if(tok == NULL || strcasecmp(tok,"use") != 0)
{
skygw_log_write(LOGFILE_ERROR,"Schemarouter: Malformed chage database packet.");
free(query);
message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str,
message_len,
"Unknown database '%s'",
(char*)rses->rses_mysql_session->db);
rses->rses_mysql_session->db[0] = '\0';
succp = false;
goto reply_error;
}
tok = strtok_r(NULL," ;",&saved);
if(tok == NULL)
{
skygw_log_write(LOGFILE_ERROR,"Schemarouter: Malformed chage database packet.");
free(query);
message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str,
message_len,
"Unknown database '%s'",
(char*)rses->rses_mysql_session->db);
rses->rses_mysql_session->db[0] = '\0';
succp = false;
goto reply_error;
}
strncpy(rses->rses_mysql_session->db,tok,MYSQL_DATABASE_MAXLEN);
free(query);
query = NULL;
}
else
{
memcpy(rses->rses_mysql_session->db,packet + 5,plen);
memset(rses->rses_mysql_session->db + plen,0,1);
}
skygw_log_write(LOGFILE_TRACE,"schemarouter: INIT_DB with database '%s'",
rses->rses_mysql_session->db);
/**

View File

@ -122,7 +122,8 @@ int main(int argc, char** argv)
sprintf(query,"CREATE DATABASE %s",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to create table in %d: %s.\n",
fprintf(stderr, "Failed to create database '%s' in %d: %s.\n",
databases[i],
ports[i],
mysql_error(server));
rval = 1;
@ -132,7 +133,8 @@ int main(int argc, char** argv)
sprintf(query,"DROP TABLE IF EXISTS %s.t1",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to drop table in %d: %s.\n",
fprintf(stderr, "Failed to drop table '%s.t1' in %d: %s.\n",
databases[i],
ports[i],
mysql_error(server));
}
@ -141,14 +143,26 @@ int main(int argc, char** argv)
sprintf(query,"CREATE TABLE %s.t1 (id int)",databases[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to create table in %d: %s.\n",
fprintf(stderr, "Failed to create table '%s.t1' in %d: %s.\n",
databases[i],
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"INSERT INTO %s.t1 values (%s)",databases[i],srv_id[i]);
if(mysql_select_db(server,databases[i]))
{
fprintf(stderr, "Failed to use database %s in %d: %s.\n",
databases[i],
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"INSERT INTO t1 values (%s)",srv_id[i]);
if(mysql_real_query(server,query,strlen(query)))
{
fprintf(stderr, "Failed to insert values in %d: %s.\n",
@ -161,10 +175,14 @@ int main(int argc, char** argv)
mysql_close(server);
}
/** Test 1 - With default database */
printf("Testing with default database.\n");
for(i = 0;i<4;i++)
{
printf("Testing server on port %d through MaxScale.\n",ports[i]);
printf("Testing database %s through MaxScale.\n",databases[i]);
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
@ -204,6 +222,89 @@ int main(int argc, char** argv)
mysql_free_result(result);
mysql_close(server);
}
printf("Testing without default database and USE ... query.\n");
for(i = 0;i<4;i++)
{
printf("Testing server on port %d through MaxScale.\n",ports[i]);
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
if(mysql_real_connect(server,host,username,password,NULL,port,NULL,0) == NULL){
fprintf(stderr, "Failed to connect to port %d using database %s: %s\n",
port,
databases[i],
mysql_error(server));
rval = 1;
goto report;
}
sprintf(query,"USE %s",databases[i]);
if(mysql_select_db(server,databases[i]))
{
fprintf(stderr, "Failed to use database %s in %d: %s.\n",
databases[i],
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
if(mysql_real_query(server,"SELECT id FROM t1",strlen("SELECT id FROM t1")))
{
fprintf(stderr, "Failed to execute query in %d: %s.\n",
ports[i],
mysql_error(server));
rval = 1;
goto report;
}
result = mysql_store_result(server);
while((row = mysql_fetch_row(result)))
{
if(strcmp(row[0],srv_id[i]))
{
fprintf(stderr, "Test failed in %d: Was expecting %s but got %s instead.\n",
ports[i],srv_id[i],row[0]);
rval = 1;
}
}
mysql_free_result(result);
mysql_close(server);
}
/** Cleanup and START SLAVE */
for(i = 0;i<4;i++)
{
if((server = mysql_init(NULL)) == NULL){
fprintf(stderr,"Error : Initialization of MySQL client failed.\n");
rval = 1;
goto report;
}
if(mysql_real_connect(server,host,username,password,NULL,port,NULL,0) == NULL){
fprintf(stderr, "Failed to connect to port %d using database %s: %s\n",
port,
databases[i],
mysql_error(server));
rval = 1;
goto report;
}
if(i > 0 && mysql_real_query(server,"START SLAVE",strlen("START SLAVE")))
{
fprintf(stderr, "Failed to start slave in %d: %s.\n",