Merge branch 'develop' into schemarouter_refresh

This commit is contained in:
Markus Makela
2015-06-27 06:33:58 +03:00
56 changed files with 2998 additions and 322 deletions

View File

@ -31,19 +31,31 @@ include(cmake/CheckPlatform.cmake)
check_deps()
check_dirs()
find_package(OpenSSL)
find_package(Valgrind)
find_package(MySQLClient)
find_package(MySQL)
find_package(Pandoc)
find_package(TCMalloc)
find_package(Jemalloc)
find_package(CURL)
# You can find the variables set by this in the FindCURL.cmake file
# which is a default module in CMake.
find_package(CURL)
if(NOT CURL_FOUND)
message(FATAL_ERROR "Failed to locate dependency: libcurl")
endif()
if(NOT OPENSSL_FOUND)
message(FATAL_ERROR "Failed to locate dependency: OpenSSL")
else()
if(OPENSSL_VERSION VERSION_LESS 1 AND NOT FORCE_OPENSSL100)
add_definitions("-DOPENSSL_0_9")
else()
add_definitions("-DOPENSSL_1_0")
endif()
endif()
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/${MAXSCALE_LIBDIR})
# Make sure the release notes for this release are present if it is a stable one
@ -159,8 +171,10 @@ install(FILES ${ERRMSG} DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale
install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION ${MAXSCALE_SHAREDIR})
install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION ${MAXSCALE_SHAREDIR})
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${MAXSCALE_SHAREDIR})
install(FILES etc/lsyncd_example.conf DESTINATION ${MAXSCALE_SHAREDIR})
install(FILES Documentation/maxscale.1 DESTINATION ${CMAKE_INSTALL_DATADIR}/man/man1)
# Install startup scripts and ldconfig files
if(WITH_SCRIPTS)
configure_file(${CMAKE_SOURCE_DIR}/maxscale.conf.in ${CMAKE_BINARY_DIR}/maxscale.conf @ONLY)
@ -173,6 +187,7 @@ if(WITH_SCRIPTS)
if(PACKAGE)
message(STATUS "maxscale.conf will unpack to: /etc/ld.so.conf.d")
message(STATUS "startup scripts will unpack to to: /etc/init.d")
message(STATUS "systemd service files will unpack to to: /usr/lib/systemd/system")
install(FILES ${CMAKE_BINARY_DIR}/maxscale DESTINATION ${MAXSCALE_SHAREDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION ${MAXSCALE_SHAREDIR}
@ -184,8 +199,11 @@ if(WITH_SCRIPTS)
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES ${CMAKE_BINARY_DIR}/maxscale.conf DESTINATION /etc/ld.so.conf.d
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES ${CMAKE_BINARY_DIR}/maxscale.service DESTINATION /usr/lib/systemd/system
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
message(STATUS "Installing maxscale.conf to: /etc/ld.so.conf.d")
message(STATUS "Installing startup scripts to: /etc/init.d")
message(STATUS "Installing systemd service files to: /usr/lib/systemd/system")
endif()
endif()

View File

@ -5,7 +5,7 @@
## About MaxScale
- [About MaxScale](About/About-MaxScale.md)
- [Release Notes 1.1](Release-Notes/MaxScale-1.1-Release-Notes.md)
- [MaxScale 1.2.0 Release Notes](Release-Notes/MaxScale-1.2.0-Release-Notes.md)
- [Changelog](Changelog.md)
- [Limitations](About/Limitations.md)
- [COPYRIGHT](About/COPYRIGHT.md)
@ -25,8 +25,10 @@
- [MaxAdmin](Reference/MaxAdmin.md)
- [MaxScale HA with Corosync-Pacemaker](Reference/MaxScale-HA-with-Corosync-Pacemaker.md)
- [MaxScale HA with Lsyncd](Reference/MaxScale-HA-with-lsyncd.md)
- [How Errors are Handled in MaxScale](Reference/How-errors-are-handled-in-MaxScale.md)
- [Debug and Diagnostic Support](Reference/Debug-And-Diagnostic-Support.md)
- [Routing Hints](Reference/Hint-Syntax.md)
## Tutorials
@ -69,10 +71,6 @@ Here are detailed documents about the filters MaxScale offers. They contain conf
- [RabbitMQ Consumer Client](filters/RabbitMQ-Consumer-Client.md)
## Routers
- [Simple Schema Sharding Router](routers/schemarouter/SchemaRouter.md)
## Design Documents
- [Core Objects Design (in development)](http://mariadb-corporation.github.io/MaxScale/Design-Documents/core-objects-html-docs)
@ -88,4 +86,7 @@ Here are detailed documents about the filters MaxScale offers. They contain conf
- [MaxScale 1.0 Release Notes](Release-Notes/MaxScale-1.0-Release-Notes.md)
- [MaxScale 1.0.1 Release Notes](Release-Notes/MaxScale-1.0.1-Release-Notes.md)
- [MaxScale 1.0.3 Release Notes](Release-Notes/MaxScale-1.0.3-Release-Notes.md)
- [MaxScale 1.1.0 Release Notes](Release-Notes/MaxScale-1.1-Release-Notes.md)
- [MaxScale 1.1.1 Release Notes](Release-Notes/MaxScale-1.1.1-Release-Notes.md)
- [MaxScale 1.2.0 Release Notes](Release-Notes/MaxScale-1.2.0-Release-Notes.md)

View File

@ -119,7 +119,7 @@ datadir=/home/user/maxscale_data/
#### `libdir`
Set the directory where MaxScale looks for modules. The library director is the only directory that MaxScale uses when it searches for modules. If you have custom modules for MaxScale, make sure you have them in this folder.
Set the directory where MaxScale looks for modules. The library directory is the only directory that MaxScale uses when it searches for modules. If you have custom modules for MaxScale, make sure you have them in this folder.
```
libdir=/home/user/lib64/
@ -326,6 +326,62 @@ Example:
connection_timeout=300
```
### Service and SSL
This section describes configuration parameters for services that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter with either `enabled` or `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this service can be encrypted with SSL.
#### `ssl`
This enables SSL connections to the service. If this parameter is set to either `required` or `enabled` and the three certificate files can be found (these are explained afterwards), then client connections will be encrypted with SSL. If the parameter is `enabled` then both SSL and non-SSL connections can connect to this service. If the parameter is set to `required` then only SSL connections can be used for this service and non-SSL connections will get an error when they try to connect to the service.
#### `ssl_key`
The SSL private key the service should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_cert`
The SSL certificate the service should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_ca_cert`
This is the Certificate Authority file. It will be used to verify that both the client and the server certificates are valid. This is a required parameter for SSL enabled services.
### `ssl_version`
This parameter controls the level of encryption used. Accepted values are:
* SSLv3
* TLSv10
* TLSv11
* TLSv12
* MAX
### `ssl_cert_verification_depth`
The maximum length of the certificate authority chain that will be accepted. Accepted values are positive integers.
```
# Example
ssl_cert_verification_depth=10
```
Example SSL enabled service configuration:
```
[ReadWriteSplitService]
type=service
router=readwritesplit
servers=server1,server2,server3
user=myuser
passwd=mypasswd
ssl=required
ssl_cert=/home/markus/certs/server-cert.pem
ssl_key=/home/markus/certs/server-key.pem
ssl_ca_cert=/home/markus/certs/ca.pem
ssl_version=TLSv12
```
This configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided.
### Server
Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters.
@ -1072,7 +1128,7 @@ MariaDB [mysql]> grant REPLICATION CLIENT on *.* to 'maxscalemon'@'maxscalehost'
Query OK, 0 rows affected (0.00 sec)
```
MySQL monitor fetches the `@@server_id` variable and other informations from `SHOW SLAVE STATUS` in order to compute the replication topology tree that may include intermediate master servers, called relay servers.
MySQL monitor fetches the `@@server_id` variable and other information from `SHOW SLAVE STATUS` in order to compute the replication topology tree that may include intermediate master servers, called relay servers.
The *Master* server used by router modules is the so called "root master": a server that has the `SERVER_MASTER` status bit set and it's at the lowest level of the replication depth.
@ -1336,20 +1392,24 @@ In addition parameters may be added to define patterns to match against to eithe
## Encrypting Passwords
Passwords stored in the maxscale.cnf file may optionally be encrypted for added security. This is done by creation of an encryption key on installation of MaxScale. Encryption keys may be created manually by executing the maxkeys utility with the argument of the filename to store the key. The default location MaxScale stores the keys is `/var/cache/maxscale`.
Passwords stored in the maxscale.cnf file may optionally be encrypted for added security. This is done by creation of an encryption key on installation of MaxScale. Encryption keys may be created manually by executing the maxkeys utility with the argument of the filename to store the key. The default location MaxScale stores the keys is `/var/lib/maxscale`.
```
maxkeys /var/cache/maxscale/.secrets
# Usage: maxkeys [PATH]
maxkeys /var/lib/maxscale/
```
Changing the encryption key for MaxScale will invalidate any currently encrypted keys stored in the maxscale.cnf file.
### Creating Encrypted Passwords
Encrypted passwords are created by executing the maxpasswd command with the password you require to encrypt as an argument.
Encrypted passwords are created by executing the maxpasswd command with the location of the .secrets file and the password you require to encrypt as an argument.
maxpasswd MaxScalePw001
61DD955512C39A4A8BC4BB1E5F116705
```
# Usage: maxpasswd PATH PASSWORD
maxpasswd /var/lib/maxscale/ MaxScalePw001
61DD955512C39A4A8BC4BB1E5F116705
```
The output of the maxpasswd command is a hexadecimal string, this should be inserted into the maxscale.cnf file in place of the ordinary, plain text, password. MaxScale will determine this as an encrypted password and automatically decrypt it before sending it the database server.

View File

@ -1,42 +1,97 @@
# Hint Syntax
Use either ’-- ’ (notice the whitespace) or ’#’ after the semicolon or ’/* .. */’ before
the semicolon.
## Enabling routing hints
The MySQL manual doesn’t specify if comment blocks, i.e. ’/* .. */’, should contain a w
hitespace character before or after the tags.
To enable routing hints for a service, the hintfilter module needs to be configured and the filter needs to be applied to the service.
All hints must start with the ’maxscale tag’:
-- maxscale <hint>
Here is an example service which has the hint filter configured and applied.
The hints right now have two types, ones that route to a server and others that contain
```
[Read Service]
type=service
router=readconnroute
router_options=master
servers=server1
user=maxuser
passwd=maxpwd
filter=Hint
[Hint]
type=filter
module=hintfilter
```
## Comments and comment types
The client connection will need to have comments enabled. For example the `mysql` command line client has comments disabled by default.
For comment types, use either `-- ` (notice the whitespace) or `#` after the semicolon or `/* .. */` before the semicolon. All comment types work with routing hints.
The MySQL manual doesn`t specify if comment blocks, i.e. `/* .. */`, should contain a w
hitespace character before or after the tags, so adding whitespace at both the start and the end is advised.
## Hint body
All hints must start with the `maxscale` tag.
```
-- maxscale <hint body>
```
The hints have two types, ones that route to a server and others that contain
name-value pairs.
Routing queries to a server:
###Routing destination hints
These hints will instruct the router to route a query to a certain type of a server.
```
-- maxscale route to [master | slave | server <server name>]
```
The name of the server is the same as in maxscale.cnf
A `master` value in a routing hint will route the query to a master server. This can be used to direct read queries to a master server for a up-to-date result with no replication lag. A `slave` value will route the query to a slave server. A `server` value will route the query to a named server. The value of <server name> needs to be the same as the server section name in maxscale.cnf.
Creating a name-value pair:
### Name-value hints
These control the behavior and affect the routing decisions made by the router.
```
-- maxscale <param>=<value>
```
Currently the only accepted parameter is
’max_slave_replication_lag’
Currently the only accepted parameter is `max_slave_replication_lag`. This will route the query to a server with lower replication lag then what is defined in the hint value.
## Hint stack
Hints can be either single-use hints, which makes them affect only one query, or named
hints, which can be pushed on and off a stack of active hints.
Defining named hints:
```
-- maxscale <hint name> prepare <hint content>
```
Pushing a hint onto the stack:
```
-- maxscale <hint name> begin
```
Popping the topmost hint off the stack:
```
-- maxscale end
```
You can define and activate a hint in a single command using the following:
```
-- maxscale <hint name> begin <hint content>
```
You can also push anonymous hints onto the stack which are only used as long as they are on the stack:
```
-- maxscale begin <hint content>
```

View File

@ -0,0 +1,184 @@
# MaxScale HA with Lsyncd
***This guide was written for lsyncd 2.1.5.***
This document guides you in setting up multiple MaxScale instances and synchronizing the configuration files with lsyncd. Lsyncd is a rsync wrapper which can synchronize files across the network. The lsyncd daemon uses a configuration file to control the files to synchronize and the remote targets where these files are synchronized to.
Copying the configuration file and running the lsyncd daemon on all the hosts keeps all the configuration files in sync. Modifications in the configuration file on one of the hosts will be copied on the other hosts. This allows adinistrators to easily provide a highly available, disaster resistant MaxScale installation with up-to-date configuration files on all the hosts.
### Requirements
You will need:
* Access to the remote hosts.
* MaxScale installed on all systems
* Configured maxscale.cnf file in /etc
* SSH daemon and clients installed on all hosts
The installation and configuration of MaxScale is covered in other documents.
## Creating SSH keys
For lsyncd to work, we will need to either use an existing set of SSH keys or to create a new set of keys. The creation and copying of keys needs to be repeated on all of the hosts.
If you already have a SSH key generated, you can skip this next step and go to the Copying Keys part.
### Generating keys
To generate a new set of SSH keys, we will use `ssh-keygen`.
```
[root@localhost ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
f4:99:0a:cc:d4:ac:ea:ed:ff:0d:bb:e5:87:3e:38:df root@localhost.localdomain
The key's randomart image is:
+--[ RSA 2048]----+
| |
| o |
| . + |
| + o . o |
| = S + |
| . . . |
| . . .... |
| . . o*o.. |
| ..o...+==oE |
+-----------------+
```
The keys will be generated in the .ssh folder and will automatically be used by ssh.
### Copying keys
To copy the SSH keys to the remote host we will use `ssh-copy-id`.
Use the username and host of the remote server you wish to synchronize MaxScale's configuration files to. For example, if the server's address is 192.168.122.100 and the user we use for synchronization us `user` we can use the following command.
```
ssh-copy-id user@192.168.122.100
```
Repeat the last command with the usernames and addresses of all the remote hosts you want to synchronize the configuration files to.
## Installing lsyncd
You will need to install lsyncd on all of the hosts for changes in the configuration file on one of the nodes to be synchronized to the other nodes.
You can install lsyncd with either a package manager or by building from source code. This guide demonstrates installation using a package manager and those looking to build lsyncd from source should refer to its documentation: https://github.com/axkibe/lsyncd/wiki/Manual-to-Lsyncd-2.1.x
Installing with Yum:
```
yum install lsyncd
```
Installing with Apt:
```
apt-get install lsyncd
```
## Creating the Lsyncd configuration file
Lsyncd uses a configuration file to determine where to read files from and where to synchronize them if changes in them occur. Lsyncd is written in Lua and the configuration file is also valid Lua code.
Here is an example configuration file with descriptions on the meaning of the values in it.
```
-- Lsyncd will log to these two files.
settings{
logfile = "/var/log/maxscale/maxscale-ha.log",
statusFile = "/var/log/maxscale/maxscale-ha-status.log"
}
-- Copy and paste the sync section and change the host value to add new remote targets.
sync{
default.rsyncssh,
-- This is where the maxscale.cnf file is copied from.
source="/etc",
-- This is the user and host where the maxscale.cnf is copied to.
-- Change this to the user and destination host where you want maxscale.cnf to be synchronized to.
host="user@192.168.122.100",
-- This is where the maxscale.cnf is copied to on the remote host.
targetdir="/etc",
-- This is an optional section which defines a custom SSH port. Uncomment to enable.
-- ssh={port=2222},
-- These are values passed to rsync. Only change these if you know what you are doing.
rsync={
compress=true,
_extra = {[[--filter=+ *maxscale.cnf]],
[[--filter=- **]]
}
}
}
```
The most important part is the `sync` section which defines a target for synchronization. The `default.rsyncssh` tells lsyncd to synchronize files using SSH.
The `source` parameter tells lsyncd where to read the files from. This should be the location of the maxscale.cnf file. The `host` parameter defines the host where the files should be synchronized to and the user account lsyncd should use when synchronizing the files. The `targetdir` parameter defines the local directory on the remote target where the files should be synchronized to. This value should be the location on the remote host where the maxscale.cnf file is searched from. By default, this is the `/etc` folder.
The optional `ssh` parameter and its sub-parameter `port`define a custom port for the SSH connection. Most users do not need this parameterer. The `rsycn` parameter contains an arra of options that are passed to the rsycn executable. These should not be changed unless you specifically know what you are doing. For more information on the options passed to rsync read the rsync(1) manpage.
You can add multiple remote targets by defining multiple `sync` sections. Here is an example with two sync sections defining different hosts that have MaxScale installed and whose configuration files should be kep in sync.
```
settings{
logfile = "/var/log/maxscale/maxscale-ha.log",
statusFile = "/var/log/maxscale/maxscale-ha-status.log"
}
sync{
default.rsyncssh,
source="/etc",
host="maxuser@192.168.0.50",
targetdir="/etc",
rsync={
compress=true,
_extra = {[[--filter=+ *maxscale.cnf]],
[[--filter=- **]]
}
}
}
sync{
default.rsyncssh,
source="/etc",
host="syncuser@192.168.122.105",
targetdir="/etc",
rsync={
compress=true,
_extra = {[[--filter=+ *maxscale.cnf]],
[[--filter=- **]]
}
}
}
```
## Starting Lsyncd
Starting lsyncd can be done from the command line or through a init script. To start syncd from the command like, execute the `lsyncd` command and pass the configuration file as the only parameter.
By default lsyncd will search for the configuration file in `/etc/lsyncd.conf`. By placing the configuration file we created in the `/etc` folder, we can start lsyncd with the following command.
```
service lsyncd start
```
Here is an example which start lsyncd and reads the configuration options from the `lsyncd.cnf` file.
```
lsyncd lsyncd.cnf
```
For more information on the lsyncd executable and its options, please see the --help output of lsyncd or the lsyncd(1) manpage.

View File

@ -0,0 +1,15 @@
# MaxScale and SSL
MaxScale supports client side SSL connections. Enabling is done on a per service basis and each service has its own set of certificates.
## SSL Options
Here are the options which relate to SSL and certificates.
Parameter|Values |Description
---------|-----------|--------
ssl | disabled, enabled, required |`disable` disables SSL, `enabled` enables SSL for client connections but still allows non-SSL connections and `required` requires SSL from all client connections. With the `required` option, client connections that do not use SSL will be rejected.
ssl_cert | path to file |Path to server certificate
ssl_key | path to file |Path to server private key
ssl_ca_cert | path to file |Path to Certificate Authority file
ssl_version|SSLV3,TLSV10,TLSV11,TLSV12,MAX| The SSL method level, defaults to highest available encryption level which is TLSv1.2
ssl_cert_verify_depth|integer|Certificate authority certificate verification depth, default is 100.

View File

@ -24,3 +24,8 @@ A quick list of changes in installation directories and file names:
* Data directory is `/var/lib/maxscale`. This is the default location for MaxScale-specific data.
* PID file can be found at `/var/run/maxscale`
### Client side SSL encryption
MaxScale now supports SSL/TLS encrypted connections to MaxScale.
### Monitor scripts
State changes in backend servers can now trigger the execution of a custom script. With this you can easily customize MaxScale's behavior.

View File

@ -108,7 +108,7 @@ master_down|A Master server has gone down
master_up|A Master server has come up
slave_down|A Slave server has gone down
slave_up|A Slave server has come up
server_down|A server with no assigned role has done down
server_down|A server with no assigned role has gone down
server_up|A server with no assigned role has come up
synced_down|A synced Galera node has come up
synced_up|A synced Galera node has gone down

View File

@ -99,7 +99,7 @@ master_down|A Master server has gone down
master_up|A Master server has come up
slave_down|A Slave server has gone down
slave_up|A Slave server has come up
server_down|A server with no assigned role has done down
server_down|A server with no assigned role has gone down
server_up|A server with no assigned role has come up
lost_master|A server lost Master status
lost_slave|A server lost Slave status

View File

@ -108,10 +108,40 @@ master_down|A Master server has gone down
master_up|A Master server has come up
slave_down|A Slave server has gone down
slave_up|A Slave server has come up
server_down|A server with no assigned role has done down
server_down|A server with no assigned role has gone down
server_up|A server with no assigned role has come up
lost_master|A server lost Master status
lost_slave|A server lost Slave status
new_master|A new Master was detected
new_slave|A new Slave was detected
## Example 1 - Monitor script
Here is an example shell script which sends an email to an admin when a server goes down.
```
#!/usr/bin/env bash
#This script assumes that the local mail server is configured properly
#The second argument is the event type
event=${$2/.*=/}
server=${$3/.*=/}
message="A server has gone down at `date`."
echo $message|mail -s "The event was $event for server $server." admin@my.org
```
Here is a monitor configuration that only triggers the script when a master or a slave server goes down.
```
[Database Monitor]
type=monitor
module=mysqlmon
servers=server1,server2
script=mail_to_admin.sh
events=master_down,slave_down
```
When a master or a slave server goes down, the script is executed, a mail is sent and the administrator will be immediately notified of any possible problems.
This is just a simple example showing what you can do with MaxScale and monitor scripts.

View File

@ -89,7 +89,7 @@ master_down|A Master server has gone down
master_up|A Master server has come up
slave_down|A Slave server has gone down
slave_up|A Slave server has come up
server_down|A server with no assigned role has done down
server_down|A server with no assigned role has gone down
server_up|A server with no assigned role has come up
ndb_down|A MySQL Cluster node has gone down
ndb_up|A MySQL Cluster node has come up

View File

@ -4,7 +4,9 @@ This document provides a short overview of the **readwritesplit** router module
## Overview
The **readwritesplit** router is designed to be used with a Master-Slave replication cluster. It automatically detects changes in the master server and will use the current master server of the cluster. With a Galera cluster, one can achieve a resilient setup and easy master failover by using one of the Galera nodes as a Write-Master node, where are write queries are routed, and spreading the read load over all the nodes.
The **readwritesplit** router is designed to increase the read-only processing capability of a cluster while maintaining consistency. This is achieved by splitting the query load into read and write queries. Read queries, which do not modify data, are spread across multiple nodes while all write queries will be sent to a single node.
The router is designed to be used with a traditional Master-Slave replication cluster. It automatically detects changes in the master server and will use the current master server of the cluster. With a Galera cluster, one can achieve a resilient setup and easy master failover by using one of the Galera nodes as a Write-Master node, where all write queries are routed, and spreading the read load over all the nodes.
## Configuration

View File

@ -9,13 +9,13 @@ macro(set_maxscale_version)
# MaxScale version number
set(MAXSCALE_VERSION_MAJOR "1")
set(MAXSCALE_VERSION_MINOR "1")
set(MAXSCALE_VERSION_PATCH "1")
set(MAXSCALE_VERSION_MINOR "2")
set(MAXSCALE_VERSION_PATCH "0")
set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}")
set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}")
# This should be incremented each time a package is rebuilt
set(MAXSCALE_BUILD_NUMBER 2)
set(MAXSCALE_BUILD_NUMBER 1)
endmacro()
macro(set_variables)

30
etc/lsyncd_example.conf Normal file
View File

@ -0,0 +1,30 @@
-- Lsyncd will log to these two files.
settings{
logfile = "/var/log/maxscale/maxscale-ha.log",
statusFile = "/var/log/maxscale/maxscale-ha-status.log"
}
-- Copy and paste the sync section and change the host value to add new remote targets.
sync{
default.rsyncssh,
-- This is where the maxscale.cnf file is copied from.
source="/etc",
-- This is the user and host where the maxscale.cnf is copied to.
host="user@127.0.0.1",
-- This is where the maxscale.cnf is copied to on the remote host.
targetdir="/etc",
-- This is an optional section which defines a custom SSH port. Uncomment to enable.
-- ssh={port=2222},
-- These are values passed to rsync. Only change these if you know what you are doing.
rsync={
compress=true,
_extra = {[[--filter=+ *maxscale.cnf]],
[[--filter=- **]]}
}
}

View File

@ -4,6 +4,7 @@ After=network.target
[Service]
Type=forking
Restart=on-failure
PIDFile=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid
ExecStart=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale

View File

@ -31,14 +31,22 @@ chmod 0755 @MAXSCALE_VARDIR@/cache/maxscale
chmod 0755 @MAXSCALE_VARDIR@/run/maxscale
# Copy init.d script and ldconfig file
cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale /etc/init.d/
cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.conf /etc/ld.so.conf.d/
if [ -d "/usr/lib/systemd/system" ]
if [ -f "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale" ]
then
cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale /etc/init.d/
fi
if [ -f "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.conf" ]
then
cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.conf /etc/init.d/
fi
if [ -d "/usr/lib/systemd/system" -a -f @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.service ]
then
cp @CMAKE_INSTALL_PREFIX@/@MAXSCALE_SHAREDIR@/maxscale.service /usr/lib/systemd/system
fi
/sbin/ldconfig
mandb
cat <<EOF >& 2
********** Notice: MaxScale 1.2 Changes **************

View File

@ -44,8 +44,8 @@ _RETVAL_STATUS_NOT_RUNNING=3
# stop/start/status related vars
#################################
NAME=maxscale
DAEMON=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale
DAEMON=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale
DAEMON_OPTS= --user=maxscale
# Source function library.
. /lib/lsb/init-functions
@ -57,11 +57,11 @@ RETVAL=0
start() {
log_daemon_msg "Starting MaxScale"
start_daemon -p $MAXSCALE_PIDFILE $DAEMON 2> /dev/null > /dev/null
start_daemon -p "$MAXSCALE_PIDFILE" "$DAEMON" "$DAEMON_OPTS" 2> /dev/null > /dev/null
sleep 2
status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME
status_of_proc -p "$MAXSCALE_PIDFILE" "$DAEMON" $NAME
log_end_msg $?
}
@ -77,13 +77,13 @@ stop() {
reload() {
log_daemon_msg "Reloading MaxScale"
kill -HUP $(cat $MAXSCALE_PIDFILE)
kill -HUP $(cat "$MAXSCALE_PIDFILE")
log_end_msg $?
}
maxscale_wait_stop() {
PIDTMP=$(pidofproc -p $MAXSCALE_PIDFILE @CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale)
PIDTMP=$(pidofproc -p "$MAXSCALE_PIDFILE" "$DAEMON")
kill -TERM "${PIDTMP:-}" 2> /dev/null;
if [ -n "${PIDTMP:-}" ] && kill -0 "${PIDTMP:-}" 2> /dev/null; then
local i=0
@ -115,7 +115,7 @@ case "$1" in
# return 3 on any error
log_daemon_msg "Checking MaxScale"
status_of_proc -p $MAXSCALE_PIDFILE $DAEMON $NAME
status_of_proc -p "$MAXSCALE_PIDFILE" "$DAEMON" $NAME
RETVAL=$?
if [ $RETVAL -ne 0 ]; then

View File

@ -1761,7 +1761,6 @@ static bool fnames_conf_init(
case 's':
/** record list of log file ids for later use */
if(do_syslog)
shmem_id_str = optarg;
break;
case 'h':
@ -1793,12 +1792,14 @@ static bool fnames_conf_init(
strdup(get_logpath_default()) : fn->fn_logpath;
/** Set identity string for syslog if it is not set in config.*/
if(do_syslog)
{
syslog_ident_str =
(syslog_ident_str == NULL ?
(argv == NULL ? strdup(program_invocation_short_name) :
strdup(*argv)) :
syslog_ident_str);
}
/* ss_dfprintf(stderr, "\n\n\tCommand line : ");
for (i=0; i<argc; i++) {
ss_dfprintf(stderr, "%s ", argv[i]);

View File

@ -345,6 +345,8 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
char *weightby;
char *version_string;
char *subservices;
char *ssl,*ssl_cert,*ssl_key,*ssl_ca_cert,*ssl_version;
char* ssl_cert_verify_depth;
bool is_rwsplit = false;
bool is_schemarouter = false;
char *allow_localhost_match_wildcard_host;
@ -353,6 +355,12 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
user = config_get_value(obj->parameters, "user");
auth = config_get_value(obj->parameters, "passwd");
subservices = config_get_value(obj->parameters, "subservices");
ssl = config_get_value(obj->parameters, "ssl");
ssl_cert = config_get_value(obj->parameters, "ssl_cert");
ssl_key = config_get_value(obj->parameters, "ssl_key");
ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert");
ssl_version = config_get_value(obj->parameters, "ssl_version");
ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth");
enable_root_user = config_get_value(
obj->parameters,
"enable_root_user");
@ -444,6 +452,83 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
config_get_value(obj->parameters,
"max_slave_replication_lag");
if(ssl)
{
if(ssl_cert == NULL)
{
error_count++;
skygw_log_write(LE,"Error: Server certificate missing for service '%s'."
"Please provide the path to the server certificate by adding the ssl_cert=<path> parameter",
obj->object);
}
if(ssl_ca_cert == NULL)
{
error_count++;
skygw_log_write(LE,"Error: CA Certificate missing for service '%s'."
"Please provide the path to the certificate authority certificate by adding the ssl_ca_cert=<path> parameter",
obj->object);
}
if(ssl_key == NULL)
{
error_count++;
skygw_log_write(LE,"Error: Server private key missing for service '%s'. "
"Please provide the path to the server certificate key by adding the ssl_key=<path> parameter"
,obj->object);
}
if(access(ssl_ca_cert,F_OK) != 0)
{
skygw_log_write(LE,"Error: Certificate authority file for service '%s' not found: %s",
obj->object,
ssl_ca_cert);
error_count++;
}
if(access(ssl_cert,F_OK) != 0)
{
skygw_log_write(LE,"Error: Server certificate file for service '%s' not found: %s",
obj->object,
ssl_cert);
error_count++;
}
if(access(ssl_key,F_OK) != 0)
{
skygw_log_write(LE,"Error: Server private key file for service '%s' not found: %s",
obj->object,
ssl_key);
error_count++;
}
if(error_count == 0)
{
if(serviceSetSSL(obj->element,ssl) != 0)
{
skygw_log_write(LE,"Error: Unknown parameter for service '%s': %s",obj->object,ssl);
error_count++;
}
else
{
serviceSetCertificates(obj->element,ssl_cert,ssl_key,ssl_ca_cert);
if(ssl_version)
{
if(serviceSetSSLVersion(obj->element,ssl_version) != 0)
{
skygw_log_write(LE,"Error: Unknown parameter value for 'ssl_version' for service '%s': %s",obj->object,ssl_version);
error_count++;
}
}
if(ssl_cert_verify_depth)
{
if(serviceSetSSLVerifyDepth(obj->element,atoi(ssl_cert_verify_depth)) != 0)
{
skygw_log_write(LE,"Error: Invalid parameter value for 'ssl_cert_verify_depth' for service '%s': %s",obj->object,ssl_cert_verify_depth);
error_count++;
}
}
}
}
}
if (enable_root_user)
serviceEnableRootUser(
obj->element,
@ -1339,7 +1424,7 @@ int i;
}
else if (strcmp(name, "ms_timestamp") == 0)
{
skygw_set_highp(config_truth_value(value));
skygw_set_highp(config_truth_value((char*)value));
}
else
{
@ -1347,7 +1432,7 @@ int i;
{
if (strcasecmp(name, lognames[i].logname) == 0)
{
if (config_truth_value(value))
if (config_truth_value((char*)value))
skygw_log_enable(lognames[i].logfile);
else
skygw_log_disable(lognames[i].logfile);
@ -1925,6 +2010,12 @@ static char *service_params[] =
"version_string",
"filters",
"weightby",
"ssl_cert",
"ssl_ca_cert",
"ssl",
"ssl_key",
"ssl_version",
"ssl_cert_verify_depth",
NULL
};

View File

@ -49,6 +49,7 @@
* backend
* 07/05/2014 Mark Riddoch Addition of callback mechanism
* 20/06/2014 Mark Riddoch Addition of dcb_clone
* 29/05/2015 Markus Makela Addition of dcb_write_SSL
*
* @endverbatim
*/
@ -433,7 +434,8 @@ DCB_CALLBACK *cb;
free(cb);
}
spinlock_release(&dcb->cb_lock);
if(dcb->ssl)
SSL_free(dcb->ssl);
bitmask_free(&dcb->memdata.bitmask);
free(dcb);
}
@ -881,6 +883,360 @@ return_n:
return n;
}
/**
* General purpose read routine to read data from a socket in the
* Descriptor Control Block and append it to a linked list of buffers.
* This function will read at most nbytes of data.
*
* The list may be empty, in which case *head == NULL. This
*
* @param dcb The DCB to read from
* @param head Pointer to linked list to append data to
* @param nbytes Maximum number of bytes read
* @return -1 on error, otherwise the number of read bytes on the last
* iteration of while loop. 0 is returned if no data available.
*/
int dcb_read_n(
DCB *dcb,
GWBUF **head,
int nbytes)
{
GWBUF *buffer = NULL;
int b;
int rc;
int n;
int nread = 0;
CHK_DCB(dcb);
if (dcb->fd <= 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Read failed, dcb is %s.",
dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not readable")));
n = 0;
goto return_n;
}
int bufsize;
rc = ioctl(dcb->fd, FIONREAD, &b);
if (rc == -1)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : ioctl FIONREAD for dcb %p in "
"state %s fd %d failed due error %d, %s.",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
errno,
strerror(errno))));
n = -1;
goto return_n;
}
if (b == 0)
{
/** Handle closed client socket */
if (nread == 0 && dcb_isclient(dcb))
{
char c;
int l_errno = 0;
int r = -1;
/* try to read 1 byte, without consuming the socket buffer */
r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK);
l_errno = errno;
if (r <= 0 &&
l_errno != EAGAIN &&
l_errno != EWOULDBLOCK &&
l_errno != 0)
{
n = -1;
goto return_n;
}
}
n = 0;
goto return_n;
}
dcb->last_read = hkheartbeat;
bufsize = MIN(b, nbytes);
if ((buffer = gwbuf_alloc(bufsize)) == NULL)
{
/*<
* This is a fatal error which should cause shutdown.
* Todo shutdown if memory allocation fails.
*/
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to allocate read buffer "
"for dcb %p fd %d, due %d, %s.",
dcb,
dcb->fd,
errno,
strerror(errno))));
n = -1;
goto return_n;
}
GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize);
dcb->stats.n_reads++);
if (n <= 0)
{
if (errno != 0 && errno != EAGAIN && errno != EWOULDBLOCK)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Read failed, dcb %p in state "
"%s fd %d, due %d, %s.",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
errno,
strerror(errno))));
}
gwbuf_free(buffer);
goto return_n;
}
nread += n;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_read] Read %d bytes from dcb %p in state %s "
"fd %d.",
pthread_self(),
n,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
/*< Append read data to the gwbuf */
*head = gwbuf_append(*head, buffer);
return_n:
return n;
}
/**
* General purpose read routine to read data from a socket through the SSL
* structure lined with this DCB and append it to a linked list of buffers.
* The list may be empty, in which case *head == NULL. The SSL structure should
* be initialized and the SSL handshake should be done.
*
* @param dcb The DCB to read from
* @param head Pointer to linked list to append data to
* @return -1 on error, otherwise the number of read bytes on the last
* iteration of while loop. 0 is returned if no data available.
*/
int dcb_read_SSL(
DCB *dcb,
GWBUF **head)
{
GWBUF *buffer = NULL;
int b,pending;
int rc;
int n;
int nread = 0;
int ssl_errno = 0;
CHK_DCB(dcb);
if (dcb->fd <= 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Read failed, dcb is %s.",
dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not readable")));
n = 0;
goto return_n;
}
while (true)
{
int bufsize;
ssl_errno = 0;
rc = ioctl(dcb->fd, FIONREAD, &b);
pending = SSL_pending(dcb->ssl);
if (rc == -1)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : ioctl FIONREAD for dcb %p in "
"state %s fd %d failed due error %d, %s.",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
errno,
strerror(errno))));
n = -1;
goto return_n;
}
if (b == 0 && pending == 0 && nread == 0)
{
/** Handle closed client socket */
if (dcb_isclient(dcb))
{
char c = 0;
int r = -1;
/* try to read 1 byte, without consuming the socket buffer */
r = SSL_peek(dcb->ssl, &c, sizeof(char));
if (r <= 0)
{
ssl_errno = SSL_get_error(dcb->ssl,r);
if(ssl_errno != SSL_ERROR_WANT_READ &&
ssl_errno != SSL_ERROR_WANT_WRITE &&
ssl_errno != SSL_ERROR_NONE)
n = -1;
else
n = 0;
goto return_n;
}
}
n = 0;
goto return_n;
}
else if (b == 0 && pending == 0)
{
n = 0;
goto return_n;
}
#ifdef SS_DEBUG
else
{
skygw_log_write_flush(LD,"Total: %d Socket: %d Pending: %d",
nread,b,pending);
}
#endif
dcb->last_read = hkheartbeat;
bufsize = MIN(b, MAX_BUFFER_SIZE);
if ((buffer = gwbuf_alloc(bufsize)) == NULL)
{
/*<
* This is a fatal error which should cause shutdown.
* Todo shutdown if memory allocation fails.
*/
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to allocate read buffer "
"for dcb %p fd %d, due %d, %s.",
dcb,
dcb->fd,
errno,
strerror(errno))));
n = -1;
goto return_n;
}
n = SSL_read(dcb->ssl, GWBUF_DATA(buffer), bufsize);
dcb->stats.n_reads++;
if (n < 0)
{
char errbuf[200];
ssl_errno = SSL_get_error(dcb->ssl,n);
#ifdef SS_DEBUG
if(ssl_errno == SSL_ERROR_SSL ||
ssl_errno == SSL_ERROR_SYSCALL)
{
int eno;
while((eno = ERR_get_error()) != 0)
{
ERR_error_string(eno,errbuf);
skygw_log_write(LE,
"%s",
errbuf);
}
}
#endif
if(ssl_errno == SSL_ERROR_WANT_READ ||
ssl_errno == SSL_ERROR_WANT_WRITE ||
ssl_errno == SSL_ERROR_NONE)
{
n = 0;
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Read failed, dcb %p in state "
"%s fd %d, SSL error %d: %s.",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
ssl_errno,
strerror(errno))));
if(ssl_errno == SSL_ERROR_SSL ||
ssl_errno == SSL_ERROR_SYSCALL)
{
while((ssl_errno = ERR_get_error()) != 0)
{
ERR_error_string(ssl_errno,errbuf);
skygw_log_write(LE,
"%s",
errbuf);
}
}
}
n = -1;
gwbuf_free(buffer);
goto return_n;
}
else if(n == 0)
{
gwbuf_free(buffer);
goto return_n;
}
gwbuf_rtrim(buffer,bufsize - n);
if(buffer == NULL)
{
goto return_n;
}
#ifdef SS_DEBUG
skygw_log_write(LD,"%lu SSL: Truncated buffer from %d to %d bytes. "
"Read %d bytes, %d bytes waiting.\n",pthread_self(),
bufsize,GWBUF_LENGTH(buffer),n,b);
if(GWBUF_LENGTH(buffer) != n){
skygw_log_sync_all();
}
ss_info_dassert((buffer->start <= buffer->end),"Buffer start has passed end.");
ss_info_dassert(GWBUF_LENGTH(buffer) == n,"Buffer size not equal to read bytes.");
#endif
nread += n;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_read_SSL] Read %d bytes from dcb %p in state %s "
"fd %d.",
pthread_self(),
n,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
/*< Append read data to the gwbuf */
*head = gwbuf_append(*head, buffer);
} /*< while (true) */
return_n:
return nread;
}
/**
* General purpose routine to write to a DCB
*
@ -1117,6 +1473,261 @@ int below_water;
return 1;
}
/**
* General purpose routine to write to an SSL enabled DCB
*
* @param dcb The DCB of the client
* @param ssl The SSL structure for this DCB
* @param queue Queue of buffers to write
* @return 0 on failure, 1 on success
*/
int
dcb_write_SSL(DCB *dcb, GWBUF *queue)
{
int w;
int saved_errno = 0;
int below_water;
below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0;
ss_dassert(queue != NULL);
if (dcb->fd <= 0)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Write failed, dcb is %s.",
dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not writable")));
return 0;
}
/**
* SESSION_STATE_STOPPING means that one of the backends is closing
* the router session. Some backends may have not completed
* authentication yet and thus they have no information about router
* being closed. Session state is changed to SESSION_STATE_STOPPING
* before router's closeSession is called and that tells that DCB may
* still be writable.
*/
if (queue == NULL ||
(dcb->state != DCB_STATE_ALLOC &&
dcb->state != DCB_STATE_POLLING &&
dcb->state != DCB_STATE_LISTENING &&
dcb->state != DCB_STATE_NOPOLLING &&
(dcb->session == NULL ||
dcb->session->state != SESSION_STATE_STOPPING)))
{
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Write aborted to dcb %p because "
"it is in state %s",
pthread_self(),
dcb->stats.n_buffered,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
//ss_dassert(false);
return 0;
}
spinlock_acquire(&dcb->writeqlock);
if (dcb->writeq != NULL)
{
/*
* We have some queued data, so add our data to
* the write queue and return.
* The assumption is that there will be an EPOLLOUT
* event to drain what is already queued. We are protected
* by the spinlock, which will also be acquired by the
* the routine that drains the queue data, so we should
* not have a race condition on the event.
*/
if (queue)
{
int qlen;
qlen = gwbuf_length(queue);
atomic_add(&dcb->writeqlen, qlen);
dcb->writeq = gwbuf_append(dcb->writeq, queue);
dcb->stats.n_buffered++;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Append to writequeue. %d writes "
"buffered for dcb %p in state %s fd %d",
pthread_self(),
dcb->stats.n_buffered,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
}
}
else
{
/*
* Loop over the buffer chain that has been passed to us
* from the reading side.
* Send as much of the data in that chain as possible and
* add any balance to the write queue.
*/
while (queue != NULL)
{
int qlen;
#if defined(FAKE_CODE)
if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER &&
dcb->session != NULL)
{
if (dcb_isclient(dcb) && fail_next_client_fd) {
dcb_fake_write_errno[dcb->fd] = 32;
dcb_fake_write_ev[dcb->fd] = 29;
fail_next_client_fd = false;
} else if (!dcb_isclient(dcb) &&
fail_next_backend_fd)
{
dcb_fake_write_errno[dcb->fd] = 32;
dcb_fake_write_ev[dcb->fd] = 29;
fail_next_backend_fd = false;
}
}
#endif /* FAKE_CODE */
qlen = GWBUF_LENGTH(queue);
do
{
w = gw_write_SSL(dcb->ssl, GWBUF_DATA(queue), qlen);
dcb->stats.n_writes++;
if (w <= 0)
{
int ssl_errno = SSL_get_error(dcb->ssl,w);
if (LOG_IS_ENABLED(LOGFILE_DEBUG))
{
switch(ssl_errno)
{
case SSL_ERROR_WANT_READ:
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Write to dcb "
"%p in state %s fd %d failed "
"due error SSL_ERROR_WANT_READ",
pthread_self(),
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
break;
case SSL_ERROR_WANT_WRITE:
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Write to dcb "
"%p in state %s fd %d failed "
"due error SSL_ERROR_WANT_WRITE",
pthread_self(),
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
break;
default:
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Write to dcb "
"%p in state %s fd %d failed "
"due error %d",
pthread_self(),
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,ssl_errno)));
break;
}
}
if (LOG_IS_ENABLED(LOGFILE_ERROR) && ssl_errno != SSL_ERROR_WANT_WRITE)
{
if (ssl_errno == -1)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Write to dcb %p in "
"state %s fd %d failed due to "
"SSL error %d",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
ssl_errno)));
if(ssl_errno == SSL_ERROR_SSL || ssl_errno == SSL_ERROR_SYSCALL)
{
if(ssl_errno == SSL_ERROR_SYSCALL)
{
skygw_log_write(LE,"%d:%s",errno,strerror(errno));
}
do
{
char errbuf[140];
ERR_error_string(ssl_errno,errbuf);
skygw_log_write(LE,"%d:%s",ssl_errno,errbuf);
}while((ssl_errno = ERR_get_error()) != 0);
}
}
else if(w == 0)
{
do
{
char errbuf[140];
ERR_error_string(ssl_errno,errbuf);
skygw_log_write(LE,"%d:%s",ssl_errno,errbuf);
}while((ssl_errno = ERR_get_error()) != 0);
}
}
if(ssl_errno != SSL_ERROR_WANT_WRITE)
break;
}
}while(w <= 0);
if(w <= 0)
{
break;
}
else
{
/** Remove written bytes from the queue */
queue = gwbuf_consume(queue, w);
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [dcb_write] Wrote %d Bytes to dcb %p in "
"state %s fd %d",
pthread_self(),
w,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd)));
}
} /*< while (queue != NULL) */
/*<
* What wasn't successfully written is stored to write queue
* for suspended write.
*/
dcb->writeq = queue;
if (queue)
{
int qlen;
qlen = gwbuf_length(queue);
atomic_add(&dcb->writeqlen, qlen);
dcb->stats.n_buffered++;
}
} /* if (dcb->writeq) */
spinlock_release(&dcb->writeqlock);
if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water)
{
atomic_add(&dcb->stats.n_high_water, 1);
dcb_call_callback(dcb, DCB_REASON_HIGH_WATER);
}
return 1;
}
/**
* Drain the write queue of a DCB. This is called as part of the EPOLLOUT handling
* of a socket and will try to send any buffered data from the write queue
@ -1209,6 +1820,105 @@ int above_water;
return n;
}
/**
* Drain the write queue of a DCB. This is called as part of the EPOLLOUT handling
* of a socket and will try to send any buffered data from the write queue
* up until the point the write would block. This function uses SSL encryption
* and the SSL handshake should have been completed prior to calling this function.
*
* @param dcb DCB to drain the write queue of
* @return The number of bytes written
*/
int
dcb_drain_writeq_SSL(DCB *dcb)
{
int n = 0;
int w;
int saved_errno = 0;
int above_water;
above_water = (dcb->low_water && dcb->writeqlen > dcb->low_water) ? 1 : 0;
spinlock_acquire(&dcb->writeqlock);
if (dcb->writeq)
{
int len;
/*
* Loop over the buffer chain in the pending writeq
* Send as much of the data in that chain as possible and
* leave any balance on the write queue.
*/
while (dcb->writeq != NULL)
{
len = GWBUF_LENGTH(dcb->writeq);
w = gw_write_SSL(dcb->ssl, GWBUF_DATA(dcb->writeq), len);
if (w < 0)
{
int ssl_errno = SSL_get_error(dcb->ssl,w);
if(ssl_errno == SSL_ERROR_WANT_WRITE || ssl_errno == SSL_ERROR_WANT_READ)
{
break;
}
skygw_log_write_flush(LOGFILE_ERROR,
"Error : Write to dcb failed due to "
"SSL error %d:",
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
ssl_errno);
switch(ssl_errno)
{
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
while((ssl_errno = ERR_get_error()) != 0)
{
char errbuf[140];
ERR_error_string(ssl_errno,errbuf);
skygw_log_write(LE,"%s",errbuf);
}
if(errno != 0)
skygw_log_write(LE,"%d:%s",errno,strerror(errno));
break;
case SSL_ERROR_ZERO_RETURN:
skygw_log_write(LE,"Socket is closed.");
break;
default:
skygw_log_write(LE,"Unexpected error.");
break;
}
break;
}
/*
* Pull the number of bytes we have written from
* queue with have.
*/
dcb->writeq = gwbuf_consume(dcb->writeq, w);
n += w;
}
}
spinlock_release(&dcb->writeqlock);
atomic_add(&dcb->writeqlen, -n);
/* The write queue has drained, potentially need to call a callback function */
if (dcb->writeq == NULL)
dcb_call_callback(dcb, DCB_REASON_DRAINED);
if (above_water && dcb->writeqlen < dcb->low_water)
{
atomic_add(&dcb->stats.n_low_water, 1);
dcb_call_callback(dcb, DCB_REASON_LOW_WATER);
}
return n;
}
/**
* Removes dcb from poll set, and adds it to zombies list. As a consequense,
* dcb first moves to DCB_STATE_NOPOLLING, and then to DCB_STATE_ZOMBIE state.
@ -1717,6 +2427,9 @@ static bool dcb_set_state_nomutex(
case DCB_STATE_NOPOLLING:
switch (new_state) {
/** Stopped services which are restarting will go from
* DCB_STATE_NOPOLLING to DCB_STATE_LISTENING.*/
case DCB_STATE_LISTENING:
case DCB_STATE_ZOMBIE: /*< fall through */
dcb->state = new_state;
case DCB_STATE_POLLING: /*< ok to try but state can't change */
@ -1793,6 +2506,30 @@ static bool dcb_set_state_nomutex(
return succp;
}
/**
* Write data to a socket through an SSL structure. The SSL structure is linked to a DCB's socket
* and all communication is encrypted and done via the SSL structure.
*
* @param ssl The SSL structure to use for writing
* @param buf Buffer to write
* @param nbytes Number of bytes to write
* @return Number of written bytes
*/
int
gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes)
{
int w = 0;
int fd = SSL_get_fd(ssl);
if (fd > 0)
{
w = SSL_write(ssl, buf, nbytes);
}
return w;
}
/**
* Write data to a DCB
*
@ -2226,3 +2963,201 @@ DCB *ptr;
spinlock_release(&dcbspin);
return rval;
}
/**
* Create the SSL structure for this DCB.
* This function creates the SSL structure for the given SSL context. This context
* should be the service's context
* @param dcb
* @param context
* @return
*/
int dcb_create_SSL(DCB* dcb)
{
if(serviceInitSSL(dcb->service) != 0)
{
return -1;
}
if((dcb->ssl = SSL_new(dcb->service->ctx)) == NULL)
{
skygw_log_write(LE,"Error: Failed to initialize SSL for connection.");
return -1;
}
if(SSL_set_fd(dcb->ssl,dcb->fd) == 0)
{
skygw_log_write(LE,"Error: Failed to set file descriptor for SSL connection.");
return -1;
}
return 0;
}
/**
* Accept a SSL connection and do the SSL authentication handshake.
* This function accepts a client connection to a DCB. It assumes that the SSL
* structure has the underlying method of communication set and this method is ready
* for usage. It then proceeds with the SSL handshake and stops only if an error
* occurs or the client has not yet written enough data to complete the handshake.
* @param dcb DCB which should accept the SSL connection
* @return 1 if the handshake was successfully completed, 0 if the handshake is
* still ongoing and another call to dcb_SSL_accept should be made or -1 if an
* error occurred during the handshake and the connection should be terminated.
*/
int dcb_accept_SSL(DCB* dcb)
{
int rval = 0,ssl_rval,errnum = 0,fd,b = 0,pending;
char errbuf[140];
fd = dcb->fd;
do
{
ssl_rval = SSL_accept(dcb->ssl);
LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept %d, error %d",
ssl_rval,errnum)));
switch(ssl_rval)
{
case 0:
errnum = SSL_get_error(dcb->ssl,ssl_rval);
skygw_log_write(LE,"Error: SSL authentication failed (SSL error %d):",
dcb,
dcb->remote,
errnum);
if(errnum == SSL_ERROR_SSL ||
errnum == SSL_ERROR_SYSCALL)
{
while((errnum = ERR_get_error()) != 0)
{
ERR_error_string(errnum,errbuf);
skygw_log_write(LE,"%s",errbuf);
}
}
rval = -1;
break;
case 1:
rval = 1;
LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept done for %s",
dcb->remote)));
return rval;
case -1:
errnum = SSL_get_error(dcb->ssl,ssl_rval);
if(errnum == SSL_ERROR_WANT_READ || errnum == SSL_ERROR_WANT_WRITE)
{
/** Not all of the data has been read. Go back to the poll
queue and wait for more.*/
rval = 0;
LOGIF(LD,(skygw_log_write_flush(LD,"[dcb_accept_SSL] SSL_accept ongoing for %s",
dcb->remote)));
return rval;
}
else
{
rval = -1;
skygw_log_write(LE,
"Error: Fatal error in SSL_accept for %s: (SSL version: %s SSL error code: %d)",
dcb->remote,
SSL_get_version(dcb->ssl),
errnum);
if(errnum == SSL_ERROR_SSL ||
errnum == SSL_ERROR_SYSCALL)
{
while((errnum = ERR_get_error()) != 0)
{
ERR_error_string(errnum,errbuf);
skygw_log_write(LE,
"%s",
errbuf);
}
}
}
break;
default:
skygw_log_write_flush(LE,
"Error: Fatal library error in SSL_accept, returned value was %d.",
ssl_rval);
rval = -1;
break;
}
ioctl(fd,FIONREAD,&b);
pending = SSL_pending(dcb->ssl);
#ifdef SS_DEBUG
skygw_log_write_flush(LD,"[dcb_accept_SSL] fd %d: %d bytes, %d pending",fd,b,pending);
#endif
}while((b > 0 || pending > 0) && rval != -1);
return rval;
}
/**
* Initiate an SSL client connection to a server
*
* This functions starts an SSL client connection to a server which is expecting
* an SSL handshake. The DCB should already have a TCP connection to the server and
* this connection should be in a state that expects an SSL handshake.
* @param dcb DCB to connect
* @return 1 on success, -1 on error and 0 if the SSL handshake is still ongoing
*/
int dcb_connect_SSL(DCB* dcb)
{
int rval,errnum;
char errbuf[140];
rval = SSL_connect(dcb->ssl);
switch(rval)
{
case 0:
errnum = SSL_get_error(dcb->ssl,rval);
LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect shutdown for %s@%s",
dcb->user,
dcb->remote)));
return -1;
break;
case 1:
rval = 1;
LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect done for %s@%s",
dcb->user,
dcb->remote)));
return rval;
case -1:
errnum = SSL_get_error(dcb->ssl,rval);
if(errnum == SSL_ERROR_WANT_READ || errnum == SSL_ERROR_WANT_WRITE)
{
/** Not all of the data has been read. Go back to the poll
queue and wait for more.*/
rval = 0;
LOGIF(LD,(skygw_log_write_flush(LD,"SSL_connect ongoing for %s@%s",
dcb->user,
dcb->remote)));
}
else
{
rval = -1;
ERR_error_string(errnum,errbuf);
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_accept for %s@%s: (SSL error code: %d) %s",
dcb->user,
dcb->remote,
errnum,
errbuf);
}
break;
default:
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_connect, returned value was %d.",
rval);
break;
}
return rval;
}

View File

@ -40,7 +40,16 @@
* @endverbatim
*/
#define _XOPEN_SOURCE 700
#define OPENSSL_THREAD_DEFINES
#include <my_config.h>
#include <openssl/opensslconf.h>
#if defined(OPENSSL_THREADS)
#define HAVE_OPENSSL_THREADS 1
#else
#define HAVE_OPENSSL_THREADS 0
#endif
#include <ftw.h>
#include <string.h>
#include <strings.h>
@ -197,6 +206,83 @@ static bool resolve_maxscale_conf_fname(
static char* check_dir_access(char* dirname,bool,bool);
static int set_user();
/** SSL multi-threading functions and structures */
static SPINLOCK* ssl_locks;
static void ssl_locking_function(int mode,int n,const char* file, int line)
{
if(mode & CRYPTO_LOCK)
spinlock_acquire(&ssl_locks[n]);
else
spinlock_release(&ssl_locks[n]);
}
/**
* OpenSSL requires this struct to be defined in order to use dynamic locks
*/
struct CRYPTO_dynlock_value
{
SPINLOCK lock;
};
/**
* Create a dynamic OpenSSL lock. The dynamic lock is just a wrapper structure
* around a SPINLOCK structure.
* @param file File name
* @param line Line number
* @return Pointer to new lock or NULL of an error occurred
*/
static struct CRYPTO_dynlock_value *ssl_create_dynlock(const char* file, int line)
{
struct CRYPTO_dynlock_value* lock = malloc(sizeof(struct CRYPTO_dynlock_value));
if(lock)
{
spinlock_init(&lock->lock);
}
return lock;
}
/**
* Lock a dynamic lock for OpenSSL.
* @param mode
* @param n pointer to lock
* @param file File name
* @param line Line number
*/
static void ssl_lock_dynlock(int mode,struct CRYPTO_dynlock_value * n,const char* file, int line)
{
if(mode & CRYPTO_LOCK)
{
spinlock_acquire(&n->lock);
}
else
{
spinlock_release(&n->lock);
}
}
/**
* Free a dynamic OpenSSL lock.
* @param n Lock to free
* @param file File name
* @param line Line number
*/
static void ssl_free_dynlock(struct CRYPTO_dynlock_value * n,const char* file, int line)
{
free(n);
}
#ifdef OPENSSL_1_0
/**
* The thread ID callback function for OpenSSL dynamic locks.
* @param id Id to modify
*/
static void maxscale_ssl_id(CRYPTO_THREADID* id)
{
CRYPTO_THREADID_set_numeric(id,pthread_self());
}
#endif
/**
* Handler for SIGHUP signal. Reload the configuration for the
* gateway.
@ -1379,6 +1465,18 @@ int main(int argc, char **argv)
goto return_main;
}
/** OpenSSL initialization */
if(!HAVE_OPENSSL_THREADS)
{
char* logerr = "OpenSSL library does not support multi-threading";
print_log_n_stderr(true, true, logerr, logerr, eno);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
}
SSL_library_init();
SSL_load_error_strings();
OPENSSL_add_all_algorithms_noconf();
/* register exit function for embedded MySQL library */
l = atexit(libmysqld_done);
@ -1711,6 +1809,26 @@ int main(int argc, char **argv)
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
"MaxScale started with %d server threads.",
config_threadcount())));
int numlocks = CRYPTO_num_locks();
if((ssl_locks = malloc(sizeof(SPINLOCK)*(numlocks + 1))) == NULL)
{
char* logerr = "Memory allocation failed";
print_log_n_stderr(true, true, logerr, logerr, eno);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
}
for(i = 0;i<numlocks + 1;i++)
spinlock_init(&ssl_locks[i]);
CRYPTO_set_locking_callback(ssl_locking_function);
CRYPTO_set_dynlock_create_callback(ssl_create_dynlock);
CRYPTO_set_dynlock_destroy_callback(ssl_free_dynlock);
CRYPTO_set_dynlock_lock_callback(ssl_lock_dynlock);
#ifdef OPENSSL_1_0
CRYPTO_THREADID_set_callback(maxscale_ssl_id);
#else
CRYPTO_set_id_callback(pthread_self);
#endif
MaxScaleStarted = time(0);
/*<
@ -2010,3 +2128,4 @@ static int set_user(char* user)
return rval;
}

View File

@ -32,19 +32,24 @@
#include <skygw_utils.h>
#include <log_manager.h>
#include <gwdirs.h>
int main(int argc, char **argv)
{
int arg_count = 6;
int arg_count = 4;
char *home;
char *keyfile;
char** arg_vector;
int rval = 0;
if (argc != 2)
if (argc < 2)
{
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
keyfile = "/var/lib/maxscale/";
fprintf(stderr, "Generating .secrets file in /var/lib/maxscale/ ...\n");
}
else
{
keyfile = argv[1];
}
arg_vector = malloc(sizeof(char*)*(arg_count + 1));
if(arg_vector == NULL)
@ -53,27 +58,16 @@ int main(int argc, char **argv)
return 1;
}
if(access("/var/log/maxscale/maxkeys/",F_OK) != 0)
{
if(mkdir("/var/log/maxscale/maxkeys/",0777) == -1)
{
if(errno != EEXIST)
{
fprintf(stderr,"Error: %d - %s",errno,strerror(errno));
return 1;
}
}
}
arg_vector[0] = strdup("logmanager");
arg_vector[1] = strdup("-j");
arg_vector[2] = strdup("/var/log/maxscale/maxkeys");
arg_vector[3] = NULL;
arg_vector[0] = "logmanager";
arg_vector[1] = "-j";
arg_vector[2] = "/var/log/maxscale/maxkeys";
arg_vector[3] = "-o";
arg_vector[4] = NULL;
skygw_logmanager_init(arg_count,arg_vector);
free(arg_vector[2]);
free(arg_vector);
if (secrets_writeKeys(argv[1]))
if (secrets_writeKeys(keyfile))
{
fprintf(stderr, "Failed to encode the password\n");
rval = 1;

View File

@ -46,9 +46,9 @@ main(int argc, char **argv)
char** arg_vector;
int rval = 0;
if (argc != 2)
if (argc != 3)
{
fprintf(stderr, "Usage: %s <password>\n", argv[0]);
fprintf(stderr, "Usage: %s <file> <password>\n", argv[0]);
return 1;
}
@ -79,9 +79,9 @@ main(int argc, char **argv)
return 1;
}
strncpy(pw,argv[1],80);
strncpy(pw,argv[2],80);
if ((enc = encryptPassword(pw)) != NULL){
if ((enc = encryptPassword(argv[1],pw)) != NULL){
printf("%s\n", enc);
}else{
fprintf(stderr, "Failed to encode the password\n");

View File

@ -330,7 +330,8 @@ poll_remove_dcb(DCB *dcb)
CHK_DCB(dcb);
/*< It is possible that dcb has already been removed from the set */
if (dcb->state != DCB_STATE_POLLING)
if (dcb->state != DCB_STATE_POLLING &&
dcb->state != DCB_STATE_LISTENING)
{
if (dcb->state == DCB_STATE_NOPOLLING ||
dcb->state == DCB_STATE_ZOMBIE)

View File

@ -53,15 +53,14 @@ int i;
}
/**
* secrets_readKeys
*
* This routine reads data from a binary file and extracts the AES encryption key
* and the AES Init Vector
*
* This routine reads data from a binary file named ".secrets" and extracts the AES encryption key
* and the AES Init Vector.
* If the path parameter is not null the custom path is interpreted as a folder
* containing the .secrets file. Otherwise the default location is used.
* @return The keys structure or NULL on error
*/
static MAXKEYS *
secrets_readKeys()
secrets_readKeys(char* path)
{
char secret_file[PATH_MAX+1];
char *home;
@ -70,9 +69,10 @@ struct stat secret_stats;
int fd;
int len;
static int reported = 0;
if(path != NULL)
snprintf(secret_file, PATH_MAX, "%s/.secrets", path);
else
snprintf(secret_file, PATH_MAX, "%s/.secrets", get_datadir());
/* Try to access secrets file */
if (access(secret_file, R_OK) == -1)
{
@ -221,11 +221,20 @@ static int reported = 0;
* @param secret_file The file with secret keys
* @return 0 on success and 1 on failure
*/
int secrets_writeKeys(char *secret_file)
int secrets_writeKeys(char *path)
{
int fd,randfd;
unsigned int randval;
MAXKEYS key;
char secret_file[PATH_MAX + 10];
if(strlen(path) > PATH_MAX)
{
skygw_log_write(LOGFILE_ERROR,"Error: Pathname too long.");
return 1;
}
sprintf(secret_file,"%s/.secrets",path);
/* Open for writing | Create | Truncate the file for writing */
if ((fd = open(secret_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR)) < 0)
@ -328,7 +337,7 @@ char *ptr;
unsigned char encrypted[80];
int enlen;
keys = secrets_readKeys();
keys = secrets_readKeys(NULL);
if (!keys)
return strdup(crypt);
/*
@ -365,12 +374,12 @@ int enlen;
* Encrypt a password that can be stored in the MaxScale configuration file.
*
* Note the return is always a malloc'd string that the caller must free
*
* @param path Path the the .secrets file
* @param password The password to encrypt
* @return The encrypted password
*/
char *
encryptPassword(char *password)
encryptPassword(char* path, char *password)
{
MAXKEYS *keys;
AES_KEY aeskey;
@ -379,7 +388,7 @@ char *hex_output;
unsigned char padded_passwd[80];
unsigned char encrypted[80];
if ((keys = secrets_readKeys()) == NULL)
if ((keys = secrets_readKeys(path)) == NULL)
return NULL;
memset(padded_passwd, 0, 80);

View File

@ -69,6 +69,9 @@ extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
static RSA *rsa_512 = NULL;
static RSA *rsa_1024 = NULL;
/** To be used with configuration type checks */
typedef struct typelib_st {
int tl_nelems;
@ -136,7 +139,14 @@ SERVICE *service;
service->routerModule = strdup(router);
service->users_from_all = false;
service->resources = NULL;
service->ssl_mode = SSL_DISABLED;
service->ssl_init_done = false;
service->ssl_ca_cert = NULL;
service->ssl_cert = NULL;
service->ssl_key = NULL;
service->ssl_cert_verify_depth = DEFAULT_SSL_CERT_VERIFY_DEPTH;
/** Support the highest possible SSL/TLS methods available as the default */
service->ssl_method_type = SERVICE_SSL_TLS_MAX;
if (service->name == NULL || service->routerModule == NULL)
{
if (service->name)
@ -258,7 +268,7 @@ GWPROTOCOL *funcs;
/* Save authentication data to file cache */
char *ptr, path[PATH_MAX + 1];
int mkdir_rval = 0;
strcpy(path, get_cachedir());
strncpy(path, get_cachedir(),PATH_MAX);
strncat(path, "/", 4096);
strncat(path, service->name, PATH_MAX);
if (access(path, R_OK) == -1)
@ -412,6 +422,17 @@ serviceStart(SERVICE *service)
SERV_PROTOCOL *port;
int listeners = 0;
if(service->ssl_mode != SSL_DISABLED)
{
if(serviceInitSSL(service) != 0)
{
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"%s: SSL initialization failed. Service not started.",
service->name)));
service->state = SERVICE_STATE_FAILED;
return 0;
}
}
if ((service->router_instance = service->router->createInstance(service,
service->routerOptions)) == NULL)
{
@ -513,10 +534,15 @@ int listeners = 0;
port = service->ports;
while (port)
{
poll_remove_dcb(port->listener);
if(port->listener &&
port->listener->session->state == SESSION_STATE_LISTENER)
{
if(poll_remove_dcb(port->listener) == 0)
{
port->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
listeners++;
}
}
port = port->next;
}
service->state = SERVICE_STATE_STOPPED;
@ -541,13 +567,18 @@ int listeners = 0;
port = service->ports;
while (port)
{
if (poll_add_dcb(port->listener) == 0) {
if(port->listener &&
port->listener->session->state == SESSION_STATE_LISTENER_STOPPED)
{
if(poll_add_dcb(port->listener) == 0)
{
port->listener->session->state = SESSION_STATE_LISTENER;
listeners++;
}
}
port = port->next;
}
service->state = SERVICE_STATE_STARTED;
return listeners;
}
@ -855,6 +886,99 @@ serviceOptimizeWildcard(SERVICE *service, int action)
return 1;
}
/**
* Set the locations of the server's SSL certificate, server's private key and the CA
* certificate which both the client and the server should trust.
* @param service Service to configure
* @param cert SSL certificate
* @param key SSL private key
* @param ca_cert SSL CA certificate
*/
void
serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert)
{
if(service->ssl_cert)
free(service->ssl_cert);
service->ssl_cert = strdup(cert);
if(service->ssl_key)
free(service->ssl_key);
service->ssl_key = strdup(key);
if(service->ssl_ca_cert)
free(service->ssl_ca_cert);
service->ssl_ca_cert = strdup(ca_cert);
}
/**
* Set the maximum SSL/TLS version the service will support
* @param service Service to configure
* @param version SSL/TLS version string
* @return 0 on success, -1 on invalid version string
*/
int
serviceSetSSLVersion(SERVICE *service, char* version)
{
if(strcasecmp(version,"SSLV3") == 0)
service->ssl_method_type = SERVICE_SSLV3;
else if(strcasecmp(version,"TLSV10") == 0)
service->ssl_method_type = SERVICE_TLS10;
#ifdef OPENSSL_1_0
else if(strcasecmp(version,"TLSV11") == 0)
service->ssl_method_type = SERVICE_TLS11;
else if(strcasecmp(version,"TLSV12") == 0)
service->ssl_method_type = SERVICE_TLS12;
#endif
else if(strcasecmp(version,"MAX") == 0)
service->ssl_method_type = SERVICE_SSL_TLS_MAX;
else return -1;
return 0;
}
/**
* Set the service's SSL certificate verification depth. Depth of 0 means the peer
* certificate, 1 is the CA and 2 is a higher CA and so on.
* @param service Service to configure
* @param depth Certificate verification depth
* @return 0 on success, -1 on incorrect depth value
*/
int serviceSetSSLVerifyDepth(SERVICE* service, int depth)
{
if(depth < 0)
return -1;
service->ssl_cert_verify_depth = depth;
return 0;
}
/**
* Enable or disable the service SSL capability of a service.
* The SSL mode string passed as a parameter should be one of required, enabled
* or disabled. Required requires all connections to use SSL encryption, enabled
* allows both SSL and non-SSL connections and disabled does not use SSL encryption.
* If the service SSL mode is set to enabled, then the client will decide whether
* SSL encryption is used.
* @param service Service to configure
* @param action Mode string. One of required, enabled or disabled.
* @return 0 on success, -1 on error
*/
int
serviceSetSSL(SERVICE *service, char* action)
{
int rval = 0;
if(strcasecmp(action,"required") == 0)
service->ssl_mode = SSL_REQUIRED;
else if(strcasecmp(action,"enabled") == 0)
service->ssl_mode = SSL_ENABLED;
else if(strcasecmp(action,"disabled") == 0)
service->ssl_mode = SSL_DISABLED;
else
rval = -1;
return rval;
}
/**
* Whether to strip escape characters from the name of the database the client
* is connecting to.
@ -1018,6 +1142,8 @@ int i;
printf("\tUsers data: %p\n", (void *)service->users);
printf("\tTotal connections: %d\n", service->stats.n_sessions);
printf("\tCurrently connected: %d\n", service->stats.n_current);
printf("\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
(service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
}
/**
@ -1127,6 +1253,8 @@ int i;
service->stats.n_sessions);
dcb_printf(dcb, "\tCurrently connected: %d\n",
service->stats.n_current);
dcb_printf(dcb,"\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
(service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
}
/**
@ -1255,7 +1383,14 @@ void *router_obj;
}
}
/**
* Refresh the database users for the service
* This function replaces the MySQL users used by the service with the latest
* version found on the backend servers. There is a limit on how often the users
* can be reloaded and if this limit is exceeded, the reload will fail.
* @param service Service to reload
* @return 0 on success and 1 on error
*/
int service_refresh_users(SERVICE *service) {
int ret = 1;
/* check for another running getUsers request */
@ -1775,3 +1910,140 @@ int *data;
return set;
}
/**
* The RSA ket generation callback function for OpenSSL.
* @param s SSL structure
* @param is_export Not used
* @param keylength Length of the key
* @return Pointer to RSA structure
*/
RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength)
{
RSA *rsa_tmp=NULL;
switch (keylength) {
case 512:
if (rsa_512)
rsa_tmp = rsa_512;
else { /* generate on the fly, should not happen in this example */
rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL);
rsa_512 = rsa_tmp; /* Remember for later reuse */
}
break;
case 1024:
if (rsa_1024)
rsa_tmp=rsa_1024;
break;
default:
/* Generating a key on the fly is very costly, so use what is there */
if (rsa_1024)
rsa_tmp=rsa_1024;
else
rsa_tmp=rsa_512; /* Use at least a shorter key */
}
return(rsa_tmp);
}
/**
* Initialize the servce's SSL context. This sets up the generated RSA
* encryption keys, chooses the server encryption level and configures the server
* certificate, private key and certificate authority file.
* @param service
* @return
*/
int serviceInitSSL(SERVICE* service)
{
DH* dh;
RSA* rsa;
if(!service->ssl_init_done)
{
switch(service->ssl_method_type)
{
case SERVICE_SSLV3:
service->method = (SSL_METHOD*)SSLv3_server_method();
break;
case SERVICE_TLS10:
service->method = (SSL_METHOD*)TLSv1_server_method();
break;
#ifdef OPENSSL_1_0
case SERVICE_TLS11:
service->method = (SSL_METHOD*)TLSv1_1_server_method();
break;
case SERVICE_TLS12:
service->method = (SSL_METHOD*)TLSv1_2_server_method();
break;
#endif
/** Rest of these use the maximum available SSL/TLS methods */
case SERVICE_SSL_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_TLS_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_SSL_TLS_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
default:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
}
service->ctx = SSL_CTX_new(service->method);
/** Enable all OpenSSL bug fixes */
SSL_CTX_set_options(service->ctx,SSL_OP_ALL);
/** Generate the 512-bit and 1024-bit RSA keys */
if(rsa_512 == NULL)
{
rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL);
if (rsa_512 == NULL)
skygw_log_write(LE,"Error: 512-bit RSA key generation failed.");
}
if(rsa_1024 == NULL)
{
rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL);
if (rsa_1024 == NULL)
skygw_log_write(LE,"Error: 1024-bit RSA key generation failed.");
}
if(rsa_512 != NULL && rsa_1024 != NULL)
SSL_CTX_set_tmp_rsa_callback(service->ctx,tmp_rsa_callback);
/** Load the server sertificate */
if (SSL_CTX_use_certificate_file(service->ctx, service->ssl_cert, SSL_FILETYPE_PEM) <= 0) {
skygw_log_write(LE,"Error: Failed to set server SSL certificate.");
return -1;
}
/* Load the private-key corresponding to the server certificate */
if (SSL_CTX_use_PrivateKey_file(service->ctx, service->ssl_key, SSL_FILETYPE_PEM) <= 0) {
skygw_log_write(LE,"Error: Failed to set server SSL key.");
return -1;
}
/* Check if the server certificate and private-key matches */
if (!SSL_CTX_check_private_key(service->ctx)) {
skygw_log_write(LE,"Error: Server SSL certificate and key do not match.");
return -1;
}
/* Load the RSA CA certificate into the SSL_CTX structure */
if (!SSL_CTX_load_verify_locations(service->ctx, service->ssl_ca_cert, NULL)) {
skygw_log_write(LE,"Error: Failed to set Certificate Authority file.");
return -1;
}
/* Set to require peer (client) certificate verification */
SSL_CTX_set_verify(service->ctx,SSL_VERIFY_PEER,NULL);
/* Set the verification depth */
SSL_CTX_set_verify_depth(service->ctx,service->ssl_cert_verify_depth);
service->ssl_init_done = true;
}
return 0;
}

View File

@ -23,6 +23,9 @@
#include <gwbitmask.h>
#include <skygw_utils.h>
#include <netinet/in.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define ERRHANDLE
@ -265,6 +268,7 @@ typedef struct dcb {
unsigned int high_water; /**< High water mark */
unsigned int low_water; /**< Low water mark */
struct server *server; /**< The associated backend server */
SSL* ssl; /*< SSL struct for connection */
#if defined(SS_DEBUG)
int dcb_port; /**< port of target server */
skygw_chk_t dcb_chk_tail;
@ -312,6 +316,7 @@ void dcb_free(DCB *);
DCB *dcb_connect(struct server *, struct session *, const char *);
DCB *dcb_clone(DCB *);
int dcb_read(DCB *, GWBUF **);
int dcb_read_n(DCB*,GWBUF **,int);
int dcb_drain_writeq(DCB *);
void dcb_close(DCB *);
DCB *dcb_process_zombies(int); /* Process Zombies except the one behind the pointer */
@ -337,7 +342,13 @@ bool dcb_set_state(DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state);
void dcb_call_foreach (struct server* server, DCB_REASON reason);
size_t dcb_get_session_id(DCB* dcb);
bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs);
int dcb_create_SSL(DCB* dcb);
int dcb_accept_SSL(DCB* dcb);
int dcb_connect_SSL(DCB* dcb);
int gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes);
int dcb_write_SSL(DCB *dcb,GWBUF *queue);
int dcb_read_SSL(DCB *dcb,GWBUF **head);
int dcb_drain_writeq_SSL(DCB *dcb);
/**

View File

@ -26,7 +26,9 @@
/** Default file locations, configured by CMake */
static const char* default_cnf_fname = "maxscale.cnf";
static const char* default_configdir = "/etc/";
static const char* default_piddir = "@MAXSCALE_VARDIR@/run/maxscale/";
static const char* default_piddir = "@MAXSCALE_VARDIR@/run/maxscale/"; /*< This should be changed to just /run eventually,
* the /var/run folder is an old standard and the newe FSH 3.0
* uses /run for PID files.*/
static const char* default_logdir = "@MAXSCALE_VARDIR@/log/maxscale/";
static const char* default_datadir = "@MAXSCALE_VARDIR@/lib/maxscale/";
static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@";

View File

@ -53,5 +53,5 @@ typedef struct maxkeys {
extern int secrets_writeKeys(char *filename);
extern char *decryptPassword(char *);
extern char *encryptPassword(char *);
extern char *encryptPassword(char*,char *);
#endif

View File

@ -26,7 +26,10 @@
#include <hashtable.h>
#include <resultset.h>
#include <maxconfig.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
/**
* @file service.h
*
@ -105,6 +108,26 @@ typedef struct server_ref_t{
SERVER* server;
}SERVER_REF;
typedef enum {
SSL_DISABLED,
SSL_ENABLED,
SSL_REQUIRED
} ssl_mode_t;
enum{
SERVICE_SSLV3,
SERVICE_TLS10,
#ifdef OPENSSL_1_0
SERVICE_TLS11,
SERVICE_TLS12,
#endif
SERVICE_SSL_MAX,
SERVICE_TLS_MAX,
SERVICE_SSL_TLS_MAX
};
#define DEFAULT_SSL_CERT_VERIFY_DEPTH 100 /*< The default certificate verification depth */
/**
* Defines a service within the gateway.
*
@ -149,8 +172,19 @@ typedef struct service {
FILTER_DEF **filters; /**< Ordered list of filters */
int n_filters; /**< Number of filters */
int conn_timeout; /*< Session timeout in seconds */
ssl_mode_t ssl_mode; /*< one of DISABLED, ENABLED or REQUIRED */
char *weightby;
struct service *next; /**< The next service in the linked list */
SSL_CTX *ctx;
SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods
* see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */
int ssl_cert_verify_depth; /*< SSL certificate verification depth */
int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */
char* ssl_cert; /*< SSL certificate */
char* ssl_key; /*< SSL private key */
char* ssl_ca_cert; /*< SSL CA certificate */
bool ssl_init_done; /*< If SSL has already been initialized for this service */
} SERVICE;
typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOST} count_spec_t;
@ -178,6 +212,11 @@ extern int serviceRestart(SERVICE *);
extern int serviceSetUser(SERVICE *, char *, char *);
extern int serviceGetUser(SERVICE *, char **, char **);
extern void serviceSetFilters(SERVICE *, char *);
extern int serviceSetSSL(SERVICE *service, char* action);
extern int serviceInitSSL(SERVICE* service);
extern int serviceSetSSLVersion(SERVICE *service, char* version);
extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth);
extern void serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert);
extern int serviceEnableRootUser(SERVICE *, int );
extern int serviceSetTimeout(SERVICE *, int );
extern void serviceWeightBy(SERVICE *, char *);

View File

@ -26,6 +26,7 @@
*
* Date Who Description
* 02/04/14 Mark Riddoch Initial implementation
* 11/05/15 Massimilaino Pinto Added mariadb10_compat to master and slave structs
*
* @endverbatim
*/
@ -34,6 +35,7 @@
#include <pthread.h>
#include <memlog.h>
#include <zlib.h>
#define BINLOG_FNAMELEN 16
#define BLR_PROTOCOL "MySQLBackend"
@ -175,6 +177,7 @@ typedef struct router_slave {
uint32_t lastEventTimestamp;/*< Last event timestamp sent */
SPINLOCK catch_lock; /*< Event catchup lock */
unsigned int cstate; /*< Catch up state */
bool mariadb10_compat;/*< MariaDB 10.0 compatibility */
SPINLOCK rses_lock; /*< Protects rses_deleted */
pthread_t pthread;
struct router_instance
@ -233,6 +236,7 @@ typedef struct {
GWBUF *selectvercom; /*< select @@version_comment */
GWBUF *selecthostname;/*< select @@hostname */
GWBUF *map; /*< select @@max_allowed_packet */
GWBUF *mariadb10; /*< set @mariadb_slave_capability */
uint8_t *fde_event; /*< Format Description Event */
int fde_len; /*< Length of fde_event */
} MASTER_RESPONSES;
@ -251,7 +255,8 @@ typedef struct router_instance {
char *user; /*< User name to use with master */
char *password; /*< Password to use with master */
char *fileroot; /*< Root of binlog filename */
bool master_chksum;/*< Does the master provide checksums */
bool master_chksum; /*< Does the master provide checksums */
bool mariadb10_compat; /*< MariaDB 10.0 compatibility */
char *master_uuid; /*< UUID of the master */
DCB *master; /*< DCB for master connection */
DCB *client; /*< DCB for dummy client */
@ -287,8 +292,7 @@ typedef struct router_instance {
int retry_backoff;
time_t connect_time;
int handling_threads;
struct router_instance
*next;
struct router_instance *next;
} ROUTER_INSTANCE;
/**
@ -314,15 +318,16 @@ typedef struct router_instance {
#define BLRM_MAP 0x0011
#define BLRM_REGISTER 0x0012
#define BLRM_BINLOGDUMP 0x0013
#define BLRM_MARIADB10 0x0014
#define BLRM_MAXSTATE 0x0013
#define BLRM_MAXSTATE 0x0014
static char *blrm_states[] = { "Unconnected", "Connecting", "Authenticated", "Timestamp retrieval",
"Server ID retrieval", "HeartBeat Period setup", "binlog checksum config",
"binlog checksum rerieval", "GTID Mode retrieval", "Master UUID retrieval",
"Set Slave UUID", "Set Names latin1", "Set Names utf8", "select 1",
"select version()", "select @@version_comment", "select @@hostname",
"select @@mx_allowed_packet", "Register slave", "Binlog Dump" };
"select @@mx_allowed_packet", "Register slave", "Binlog Dump", "Set MariaDB slave capability" };
#define BLRS_CREATED 0x0000
#define BLRS_UNREGISTERED 0x0001
@ -396,6 +401,7 @@ static char *blrs_states[] = { "Created", "Unregistered", "Registered",
#define PREVIOUS_GTIDS_EVENT 0x23
#define MAX_EVENT_TYPE 0x23
#define MAX_EVENT_TYPE_MARIADB10 0xa3
/**
* Binlog event flags

View File

@ -54,7 +54,9 @@
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <service.h>
#include <router.h>
#include <poll.h>
@ -89,6 +91,10 @@
#define COM_QUIT_PACKET_SIZE (4+1)
struct dcb;
#define MYSQL_FAILED_AUTH 1
#define MYSQL_FAILED_AUTH_DB 2
#define MYSQL_FAILED_AUTH_SSL 3
typedef enum {
MYSQL_ALLOC,
MYSQL_PENDING_CONNECT,
@ -97,6 +103,11 @@ typedef enum {
MYSQL_AUTH_RECV,
MYSQL_AUTH_FAILED,
MYSQL_HANDSHAKE_FAILED,
MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */
MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */
MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */
MYSQL_AUTH_SSL_HANDSHAKE_ONGOING, /*< SSL_accept has been called but the
* SSL handshake hasn't been completed */
MYSQL_IDLE
} mysql_auth_state_t;
@ -290,6 +301,7 @@ typedef struct {
unsigned long tid; /*< MySQL Thread ID, in
* handshake */
unsigned int charset; /*< MySQL character set at connect time */
bool use_ssl;
#if defined(SS_DEBUG)
skygw_chk_t protocol_chk_tail;
#endif
@ -309,7 +321,7 @@ typedef struct {
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
#define MYSQL_GET_NATTR(payload) ((int)payload[4])
#endif /** _MYSQL_PROTOCOL_H */
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd);
void mysql_protocol_done (DCB* dcb);
@ -405,4 +417,4 @@ void init_response_status (
int* npackets,
ssize_t* nbytes);
#endif /** _MYSQL_PROTOCOL_H */

View File

@ -155,7 +155,10 @@ startMonitor(void *arg,void* opt)
else if(!strcmp(params->name,"script"))
{
if(handle->script)
{
free(handle->script);
handle->script = NULL;
}
if(access(params->value,X_OK) == 0)
{

View File

@ -126,6 +126,7 @@ startMonitor(void *arg,void* opt)
handle->shutdown = 0;
handle->id = MONITOR_DEFAULT_ID;
handle->master = NULL;
handle->script = NULL;
memset(handle->events,false,sizeof(handle->events));
spinlock_init(&handle->lock);
}

View File

@ -230,7 +230,7 @@ void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len)
MONITOR_SERVERS* ptr = start;
bool first = true;
int slen = strlen(str);
char arr[256];
while(ptr && slen < len)
{
if(!first)
@ -238,7 +238,8 @@ void mon_append_node_names(MONITOR_SERVERS* start,char* str, int len)
strncat(str,",",len);
}
first = false;
strncat(str,ptr->server->unique_name,len);
sprintf(arr,"%s:%d",ptr->server->name,ptr->server->port);
strcat(str,arr);
ptr = ptr->next;
slen = strlen(str);
}
@ -299,10 +300,11 @@ void monitor_launch_script(MONITOR* mon,MONITOR_SERVERS* ptr, char* script)
EXTERNCMD* cmd;
snprintf(argstr,PATH_MAX + MON_ARG_MAX,
"%s --event=%s --initiator=%s --nodelist=",
"%s --event=%s --initiator=%s:%d --nodelist=",
script,
mon_get_event_name(ptr),
ptr->server->unique_name);
ptr->server->name,
ptr->server->port);
mon_append_node_names(mon->databases,argstr,PATH_MAX + MON_ARG_MAX + 1);
if((cmd = externcmd_allocate(argstr)) == NULL)

View File

@ -17,6 +17,7 @@ install(TARGETS HTTPD DESTINATION ${MAXSCALE_LIBDIR})
if(BUILD_TESTS)
add_library(testprotocol SHARED testprotocol.c)
install(TARGETS testprotocol DESTINATION ${MAXSCALE_LIBDIR})
add_subdirectory(test)
endif()
add_library(maxscaled SHARED maxscaled.c)

View File

@ -72,7 +72,7 @@ static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process);
extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
static bool sescmd_response_complete(DCB* dcb);
@ -1159,13 +1159,14 @@ gw_backend_close(DCB *dcb)
* 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 != NULL)
{
spinlock_acquire(&session->ses_lock);
/**
* If session->state is STOPPING, start closing client session.
* Otherwise only this backend connection is closed.
*/
if (session != NULL &&
session->state == SESSION_STATE_STOPPING &&
if (session->state == SESSION_STATE_STOPPING &&
session->client != NULL)
{
if (session->client->state == DCB_STATE_POLLING)
@ -1184,6 +1185,7 @@ gw_backend_close(DCB *dcb)
{
spinlock_release(&session->ses_lock);
}
}
return 1;
}
@ -1433,7 +1435,7 @@ static int gw_change_user(
message = create_auth_fail_str(username,
backend->session->client->remote,
password_set,
"");
"",auth_ret);
if (message == NULL)
{
LOGIF(LE, (skygw_log_write_flush(

View File

@ -37,7 +37,7 @@
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
* 13/10/2014 Massimiliano Pinto Added: dbname authentication check
* 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct
*
* 29/05/2015 Markus Makela Added SSL support
*/
#include <skygw_utils.h>
#include <log_manager.h>
@ -70,14 +70,18 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue);
static int gw_error_client_event(DCB *dcb);
static int gw_client_close(DCB *dcb);
static int gw_client_hangup_event(DCB *dcb);
int gw_read_client_event_SSL(DCB* dcb);
int gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue);
int gw_write_client_event_SSL(DCB *dcb);
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
int MySQLSendHandshake(DCB* dcb);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue);
static int route_by_statement(SESSION *, GWBUF **);
extern char* get_username_from_auth(char* ptr, uint8_t* data);
extern int check_db_name_after_auth(DCB *, char *, int);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
int do_ssl_accept(MySQLProtocol* protocol);
/*
* The "module object" for the mysqld client protocol module.
@ -320,7 +324,16 @@ MySQLSendHandshake(DCB* dcb)
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS;
if(dcb->service->ssl_mode != SSL_DISABLED)
{
mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8;
}
else
{
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL;
}
memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one));
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one);
@ -376,21 +389,24 @@ MySQLSendHandshake(DCB* dcb)
/**
* gw_mysql_do_authentication
*
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF **queue.
*
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
* the dcb->data and later to dcb->session->data.
* the dcb->data and later to dcb->session->data. client_capabilities are copied
* into the dcb->protocol.
*
* client_capabilitiesa are copied into the dcb->protocol
* If SSL is enabled for the service, the SSL handshake will be done before the
* MySQL authentication.
*
* @param dcb Descriptor Control Block of the client
* @param queue The GWBUF with data from client
* @param queue Pointer to the location of the GWBUF with data from client
* @return 0 If succeed, otherwise non-zero value
*
* @note in case of failure, dcb->data is freed before returning. If succeed,
* dcb->data is freed in session.c:session_free.
*/
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) {
GWBUF* queue = *buf;
MySQLProtocol *protocol = NULL;
/* int compress = -1; */
int connect_with_db = -1;
@ -403,7 +419,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
uint8_t *stage1_hash = NULL;
int auth_ret = -1;
MYSQL_session *client_data = NULL;
int ssl = 0;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
@ -438,7 +454,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
/* Detect now if there are enough bytes to continue */
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
{
return 1;
return MYSQL_FAILED_AUTH;
}
memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4);
@ -452,11 +468,66 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
&protocol->client_capabilities);
*/
/** Skip this if the SSL handshake is already done.
* If not, start the SSL handshake. */
if(protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE)
{
ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL;
/** Client didn't requested SSL when SSL mode was required*/
if(!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED)
{
LOGIF(LT,(skygw_log_write(LT,"User %s@%s connected to service '%s' without SSL when SSL was required.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name)));
return MYSQL_FAILED_AUTH_SSL;
}
if(LOG_IS_ENABLED(LT) && ssl)
{
skygw_log_write(LT,"User %s@%s connected to service '%s' with SSL.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name);
}
/** Do the SSL Handshake */
if(ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED)
{
protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ;
if(do_ssl_accept(protocol) < 0)
{
return MYSQL_FAILED_AUTH;
}
else
{
return 0;
}
}
else if(dcb->service->ssl_mode == SSL_ENABLED)
{
/** This is a non-SSL connection to a SSL enabled service.
* We have only read enough of the packet to know that the client
* is not requesting SSL and the rest of the auth packet is still
* waiting in the socket. We need to read the data from the socket
* to find out the username of the connecting client. */
int bytes = dcb_read(dcb,&queue);
queue = gwbuf_make_contiguous(queue);
client_auth_packet = GWBUF_DATA(queue);
client_auth_packet_size = gwbuf_length(queue);
*buf = queue;
LOGIF(LD,(skygw_log_write(LD,"%lu Read %d bytes from fd %d",pthread_self(),bytes,dcb->fd)));
}
}
username = get_username_from_auth(username, client_auth_packet);
if (username == NULL)
{
return 1;
return MYSQL_FAILED_AUTH;
}
/* get charset */
@ -558,6 +629,24 @@ gw_MySQLWrite_client(DCB *dcb, GWBUF *queue)
return dcb_write(dcb, queue);
}
/**
* Write function for client DCB: writes data from MaxScale to Client using SSL
* encryption. The SSH handshake must have already been done.
*
* @param dcb The DCB of the client
* @param queue Queue of buffers to write
*/
int
gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue)
{
MySQLProtocol *protocol = NULL;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
return dcb_write_SSL(dcb, queue);
}
/**
* Client read event triggered by EPOLLIN
*
@ -581,8 +670,65 @@ int gw_read_client_event(
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
rc = dcb_read(dcb, &read_buffer);
#ifdef SS_DEBUG
skygw_log_write(LD,"[gw_read_client_event] Protocol state: %s",
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
#endif
/** SSL authentication is still going on, we need to call do_ssl_accept
* until it return 1 for success or -1 for error */
if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ)
{
switch(do_ssl_accept(protocol))
{
case 0:
return 0;
break;
case 1:
{
int b = 0;
ioctl(dcb->fd,FIONREAD,&b);
if(b == 0)
{
skygw_log_write(LD,
"[gw_read_client_event] No data in socket after SSL auth");
return 0;
}
break;
}
case -1:
return 1;
break;
default:
return 1;
break;
}
}
if(protocol->use_ssl)
{
/** SSL handshake is done, communication is now encrypted with SSL */
rc = dcb_read_SSL(dcb, &read_buffer);
}
else if(dcb->service->ssl_mode != SSL_DISABLED &&
protocol->protocol_auth_state == MYSQL_AUTH_SENT)
{
/** The service allows both SSL and non-SSL connections.
* read only enough of the auth packet to know if the client is
* requesting SSL. If the client is not requesting SSL the rest of
the auth packet will be read later. */
rc = dcb_read_n(dcb, &read_buffer,(4 + 4 + 4 + 1 + 23));
}
else
{
/** Normal non-SSL connection */
rc = dcb_read(dcb, &read_buffer);
}
if (rc < 0)
{
@ -691,8 +837,8 @@ int gw_read_client_event(
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
nbytes_read = gwbuf_length(dcb->dcb_readqueue);
data = (uint8_t *)GWBUF_DATA(dcb->dcb_readqueue);
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data))
int plen = MYSQL_GET_PACKET_LEN(data);
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data) + 4)
{
rc = 0;
goto return_rc;
@ -730,7 +876,18 @@ int gw_read_client_event(
{
int auth_val;
auth_val = gw_mysql_do_authentication(dcb, read_buffer);
auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED)
{
/** SSL was requested and the handshake is either done or
* still ongoing. After the handshake is done, the client
* will send another auth packet. */
break;
}
if (auth_val == 0)
{
@ -797,7 +954,7 @@ int gw_read_client_event(
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);
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str);
}
if (fail_str)
@ -825,6 +982,113 @@ int gw_read_client_event(
}
break;
case MYSQL_AUTH_SSL_HANDSHAKE_DONE:
{
int auth_val;
auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if (auth_val == 0)
{
SESSION *session;
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
/**
* Create session, and a router session for it.
* If successful, there will be backend connection(s)
* after this point.
*/
session = session_alloc(dcb->service, dcb);
if (session != NULL)
{
CHK_SESSION(session);
ss_dassert(session->state != SESSION_STATE_ALLOC);
protocol->protocol_auth_state = MYSQL_IDLE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # 2
*/
mysql_send_ok(dcb, 3, 0, NULL);
}
else
{
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd)));
/** Send ERR 1045 to client */
mysql_send_auth_error(
dcb,
3,
0,
"failed to create new session");
dcb_close(dcb);
}
}
else
{
char* fail_str = NULL;
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
if (auth_val == 2) {
/** Send error 1049 to client */
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'",
(char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str);
}else if(auth_val == 3){
/** 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);
modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str);
}else {
/** 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);
modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str);
}
if (fail_str)
free(fail_str);
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [gw_read_client_event] after "
"gw_mysql_do_authentication, fd %d, "
"state = MYSQL_AUTH_FAILED.",
protocol->owner_dcb->fd,
pthread_self())));
/**
* Release MYSQL_session since it is not used anymore.
*/
if (!DCB_IS_CLONE(dcb))
{
free(dcb->data);
}
dcb->data = NULL;
dcb_close(dcb);
}
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
}
break;
case MYSQL_IDLE:
{
uint8_t* payload = NULL;
@ -944,6 +1208,7 @@ return_rc:
return rc;
}
///////////////////////////////////////////////
// client write event to Client triggered by EPOLLOUT
//////////////////////////////////////////////
@ -1001,6 +1266,53 @@ return_1:
return 1;
}
/**
* EPOLLOUT event arrived and as a consequence, client input buffer (writeq) is
* flushed. The data is encrypted and SSL is used. The SSL handshake must have
* been successfully completed prior to this function being called.
* @param client dcb
* @return constantly 1
*/
int gw_write_client_event_SSL(DCB *dcb)
{
MySQLProtocol *protocol = NULL;
CHK_DCB(dcb);
ss_dassert(dcb->state != DCB_STATE_DISCONNECTED);
if (dcb == NULL) {
goto return_1;
}
if (dcb->state == DCB_STATE_DISCONNECTED) {
goto return_1;
}
if (dcb->protocol == NULL) {
goto return_1;
}
protocol = (MySQLProtocol *)dcb->protocol;
CHK_PROTOCOL(protocol);
if (protocol->protocol_auth_state == MYSQL_IDLE)
{
dcb_drain_writeq_SSL(dcb);
goto return_1;
}
return_1:
#if defined(SS_DEBUG)
if (dcb->state == DCB_STATE_POLLING ||
dcb->state == DCB_STATE_NOPOLLING ||
dcb->state == DCB_STATE_ZOMBIE)
{
CHK_PROTOCOL(protocol);
}
#endif
return 1;
}
/**
* set listener for mysql protocol, retur 1 on success and 0 in failure
*/
@ -1609,61 +1921,79 @@ return_rc:
return rc;
}
/**
* Create a character array including the query string.
* GWBUF given as input includes either one complete or partial query.
* Length of buffer is at most the query length+4 (length of packet header).
* Do the SSL authentication handshake.
* This creates the DCB SSL structure if one has not been created and starts the
* SSL handshake handling.
* @param protocol Protocol to connect with SSL
* @return 1 on success, 0 when the handshake is ongoing or -1 on error
*/
#if defined(NOT_USED)
static char* gw_get_or_create_querystr (
void* data,
bool* new_allocation)
int do_ssl_accept(MySQLProtocol* protocol)
{
GWBUF* buf = (GWBUF *)data;
size_t buflen; /*< first gw buffer data length */
size_t packetlen; /*< length of mysql packet */
size_t querylen; /*< total buffer length-<length of type indicator> */
size_t nbytes_copied;
char* startpos; /*< first byte of query in gw buffer */
char* str; /*< resulting query string */
CHK_GWBUF(buf);
packetlen = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf));
str = (char *)malloc(packetlen); /*< leave space for terminating null */
if (str == NULL)
int rval,errnum;
char errbuf[2014];
DCB* dcb = protocol->owner_dcb;
if(dcb->ssl == NULL)
{
goto return_str;
}
*new_allocation = true;
/**
* First buffer includes 4 bytes header and a type indicator byte.
*/
buflen = GWBUF_LENGTH(buf);
querylen = packetlen-1;
ss_dassert(buflen<=querylen+5); /*< 5 == header+type indicator */
startpos = (char *)GWBUF_DATA(buf)+5;
nbytes_copied = MIN(querylen, buflen-5);
memcpy(str, startpos, nbytes_copied);
memset(&str[querylen-1], 0, 1);
buf = gwbuf_consume(buf, querylen-1);
/**
* In case of multi-packet statement whole buffer consists of query
* string.
*/
while (buf != NULL)
if(dcb_create_SSL(dcb) != 0)
{
buflen = GWBUF_LENGTH(buf);
memcpy(str+nbytes_copied, GWBUF_DATA(buf), buflen);
nbytes_copied += buflen;
buf = gwbuf_consume(buf, buflen);
return -1;
}
}
ss_dassert(str[querylen-1] == 0);
return_str:
return str;
}
rval = dcb_accept_SSL(dcb);
switch(rval)
{
case 0:
/** Not all of the data has been read. Go back to the poll
queue and wait for more.*/
rval = 0;
skygw_log_write_flush(LT,"SSL_accept ongoing for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
return 0;
break;
case 1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE;
protocol->use_ssl = true;
spinlock_release(&protocol->protocol_lock);
spinlock_acquire(&dcb->authlock);
dcb->func.write = gw_MySQLWrite_client_SSL;
dcb->func.write_ready = gw_write_client_event_SSL;
spinlock_release(&dcb->authlock);
rval = 1;
skygw_log_write_flush(LT,"SSL_accept done for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
break;
case -1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED;
spinlock_release(&protocol->protocol_lock);
rval = -1;
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_accept for %s",
protocol->owner_dcb->remote);
break;
default:
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_accept, returned value was %d.",
rval);
break;
}
#ifdef SS_DEBUG
skygw_log_write(LD,"[do_ssl_accept] Protocol state: %s",
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
#endif
return rval;
}

View File

@ -909,6 +909,10 @@ gw_mysql_protocol_state2string (int state) {
return "MySQL Authentication failed";
case MYSQL_IDLE:
return "MySQL authentication is succesfully done.";
case MYSQL_AUTH_SSL_REQ: return "MYSQL_AUTH_SSL_REQ";
case MYSQL_AUTH_SSL_HANDSHAKE_DONE: return "MYSQL_AUTH_SSL_HANDSHAKE_DONE";
case MYSQL_AUTH_SSL_HANDSHAKE_FAILED: return "MYSQL_AUTH_SSL_HANDSHAKE_FAILED";
case MYSQL_AUTH_SSL_HANDSHAKE_ONGOING: return "MYSQL_AUTH_SSL_HANDSHAKE_ONGOING";
default:
return "MySQL (unknown protocol state)";
}
@ -2217,7 +2221,8 @@ char *create_auth_fail_str(
char *username,
char *hostaddr,
char *sha1,
char *db)
char *db,
int errcode)
{
char* errstr;
const char* ferrstr;
@ -2232,6 +2237,10 @@ char *create_auth_fail_str(
{
ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'";
}
else if(errcode == MYSQL_FAILED_AUTH_SSL)
{
ferrstr = "Access without SSL denied";
}
else
{
ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
@ -2251,6 +2260,10 @@ char *create_auth_fail_str(
{
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db);
}
else if(errcode == MYSQL_FAILED_AUTH_SSL)
{
sprintf(errstr, ferrstr);
}
else
{
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));

View File

@ -0,0 +1,11 @@
configure_file(test_ssl.sh ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh @ONLY)
configure_file(no_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_ca.cnf @ONLY)
configure_file(no_server_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_cert.cnf @ONLY)
configure_file(no_server_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_key.cnf @ONLY)
configure_file(bad_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ca.cnf @ONLY)
configure_file(bad_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_cert.cnf @ONLY)
configure_file(bad_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_key.cnf @ONLY)
configure_file(bad_ssl.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl.cnf @ONLY)
configure_file(bad_ssl_version.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl_version.cnf @ONLY)
configure_file(ok.cnf ${CMAKE_CURRENT_BINARY_DIR}/ok.cnf @ONLY)
add_test(NAME SSLTest COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh)

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=This is not a value
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=This is not a value
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=This is not a value
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=testing
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,29 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
ssl_version=Don't use SSL, it's not needed!
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
#ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
#ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
#ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
function create_certs()
{
echo "CA cert" > @CMAKE_CURRENT_BINARY_DIR@/ca.pem
echo "Server Certificate" > @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem
echo "Server Key" > @CMAKE_CURRENT_BINARY_DIR@/server-key.pem
}
function start_maxscale ()
{
local result=$(@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale -d -f $1 &> $1.log;echo $?)
if [[ $result == "0" ]]
then
echo "Error: $1 exited with status $result!"
exit 1
fi
}
# All test cases expect that MaxScale will not start with a bad configuration or missing certificates
# No CA defined
printf "Testing No CA defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_ca.cnf
echo " OK"
# No cert defined
printf "Testing No cert defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_cert.cnf
echo " OK"
# No key defined
printf "Testing No key defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_key.cnf
echo " OK"
# Bad SSL value defined
printf "Testing Bad SSL defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl.cnf
echo " OK"
# Bad SSL version defined
printf "Testing Bad SSL version defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl_version.cnf
echo " OK"
# Bad CA value defined
printf "Testing Bad CA defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ca.cnf
echo " OK"
# Bad server certificate defined
printf "Testing Bad cert defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_cert.cnf
echo " OK"
# Bad server key defined
printf "Testing Bad key defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_key.cnf
echo " OK"
# No CA file
printf "Testing No CA file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/ca.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
# No server certificate file
printf "Testing No cert file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
# No server key file
printf "Testing No key file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/server-key.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
exit 0

View File

@ -35,6 +35,8 @@
* 02/04/2014 Mark Riddoch Initial implementation
* 17/02/2015 Massimiliano Pinto Addition of slave port and username in diagnostics
* 18/02/2015 Massimiliano Pinto Addition of dcb_close in closeSession
* 07/05/2015 Massimiliano Pinto Addition of MariaDB 10 compatibility support
*
* @endverbatim
*/
@ -195,6 +197,7 @@ unsigned char *defuuid;
inst->retry_backoff = 1;
inst->binlogdir = NULL;
inst->heartbeat = 300; // Default is every 5 minutes
inst->mariadb10_compat = false;
inst->user = strdup(service->credentials.name);
inst->password = strdup(service->credentials.authdata);
@ -282,6 +285,10 @@ unsigned char *defuuid;
{
inst->masterid = atoi(value);
}
else if (strcmp(options[i], "mariadb10-compatibility") == 0)
{
inst->mariadb10_compat = config_truth_value(value);
}
else if (strcmp(options[i], "filestem") == 0)
{
inst->fileroot = strdup(value);
@ -388,6 +395,7 @@ unsigned char *defuuid;
inst->saved_master.selectvercom = blr_cache_read_response(inst, "selectvercom");
inst->saved_master.selecthostname = blr_cache_read_response(inst, "selecthostname");
inst->saved_master.map = blr_cache_read_response(inst, "map");
inst->saved_master.mariadb10 = blr_cache_read_response(inst, "mariadb10");
/*
* Initialise the binlog file and position
@ -490,6 +498,7 @@ ROUTER_SLAVE *slave;
strcpy(slave->binlogfile, "unassigned");
slave->connect_time = time(0);
slave->lastEventTimestamp = 0;
slave->mariadb10_compat = false;
/**
* Add this session to the list of active sessions.

View File

@ -25,6 +25,7 @@
*
* Date Who Description
* 14/04/2014 Mark Riddoch Initial implementation
* 07/05/2015 Massimiliano Pinto Added MAX_EVENT_TYPE_MARIADB10
*
* @endverbatim
*/
@ -210,9 +211,8 @@ int fd;
close(router->binlog_fd);
spinlock_acquire(&router->binlog_lock);
strncpy(router->binlog_name, file,BINLOG_FNAMELEN);
blr_file_add_magic(router, fd);
spinlock_release(&router->binlog_lock);
router->binlog_fd = fd;
spinlock_release(&router->binlog_lock);
return 1;
}
@ -255,11 +255,12 @@ int fd;
"%s: binlog file %s has an invalid length %d.",
router->service->name, path, router->binlog_position)));
close(fd);
spinlock_release(&router->binlog_lock);
return;
}
}
spinlock_release(&router->binlog_lock);
router->binlog_fd = fd;
spinlock_release(&router->binlog_lock);
}
/**
@ -439,15 +440,26 @@ struct stat statb;
hdr->next_pos = EXTRACT32(&hdbuf[13]);
hdr->flags = EXTRACT16(&hdbuf[17]);
if (hdr->event_type > MAX_EVENT_TYPE)
{
if (router->mariadb10_compat) {
if (hdr->event_type > MAX_EVENT_TYPE_MARIADB10) {
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"Invalid MariaDB 10 event type 0x%x. "
"Binlog file is %s, position %d",
hdr->event_type,
file->binlogname, pos)));
return NULL;
}
} else {
if (hdr->event_type > MAX_EVENT_TYPE) {
LOGIF(LE, (skygw_log_write(LOGFILE_ERROR,
"Invalid event type 0x%x. "
"Binlog file is %s, position %d",
hdr->event_type,
file->binlogname, pos)));
return NULL;
}
}
if (hdr->next_pos < pos && hdr->event_type != ROTATE_EVENT)
{

View File

@ -33,6 +33,7 @@
*
* Date Who Description
* 02/04/2014 Mark Riddoch Initial implementation
* 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility
*
* @endverbatim
*/
@ -448,11 +449,27 @@ char query[128];
GWBUF_CONSUME_ALL(router->saved_master.chksum2);
router->saved_master.chksum2 = buf;
blr_cache_response(router, "chksum2", buf);
if (router->mariadb10_compat) {
buf = blr_make_query("SET @mariadb_slave_capability=4");
router->master_state = BLRM_MARIADB10;
} else {
buf = blr_make_query("SELECT @@GLOBAL.GTID_MODE");
router->master_state = BLRM_GTIDMODE;
}
router->master->func.write(router->master, buf);
break;
}
case BLRM_MARIADB10:
// Response to the SET @mariadb_slave_capability=4, should be stored
if (router->saved_master.mariadb10)
GWBUF_CONSUME_ALL(router->saved_master.mariadb10);
router->saved_master.mariadb10 = buf;
blr_cache_response(router, "mariadb10", buf);
buf = blr_make_query("SHOW VARIABLES LIKE 'SERVER_UUID'");
router->master_state = BLRM_MUUID;
router->master->func.write(router->master, buf);
break;
case BLRM_GTIDMODE:
// Response to the GTID_MODE, should be stored
if (router->saved_master.gtid_mode)

View File

@ -36,9 +36,12 @@
* 18/02/2015 Massimiliano Pinto Addition of DISCONNECT ALL and DISCONNECT SERVER server_id
* 18/03/2015 Markus Makela Better detection of CRC32 | NONE checksum
* 19/03/2015 Massimiliano Pinto Addition of basic MariaDB 10 compatibility support
* 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility
* 11/05/2015 Massimiliano Pinto Only MariaDB 10 Slaves can register to binlog router with a MariaDB 10 Master
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -123,7 +126,28 @@ blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
return blr_slave_query(router, slave, queue);
break;
case COM_REGISTER_SLAVE:
/*
* If Master is MariaDB10 don't allow registration from
* MariaDB/Mysql 5 Slaves
*/
if (router->mariadb10_compat && !slave->mariadb10_compat) {
slave->state = BLRS_ERRORED;
blr_send_custom_error(slave->dcb, 1, 0,
"MariaDB 10 Slave is required for Slave registration");
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR,
"%s: Slave %s: a MariaDB 10 Slave is required for Slave registration",
router->service->name,
slave->dcb->remote)));
dcb_close(slave->dcb);
return 1;
} else {
/* Master and Slave version OK: continue with slave registration */
return blr_slave_register(router, slave, queue);
}
break;
case COM_BINLOG_DUMP:
return blr_slave_binlog_dump(router, slave, queue);
@ -368,9 +392,16 @@ int query_len;
}
else if (strcasecmp(word, "@mariadb_slave_capability") == 0)
{
/* mariadb10 compatibility is set for the slave */
slave->mariadb10_compat=true;
free(query_text);
if (router->mariadb10_compat) {
return blr_slave_replay(router, slave, router->saved_master.mariadb10);
} else {
return blr_slave_send_ok(router, slave);
}
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
word = strtok_r(NULL, sep, &brkb);
@ -442,7 +473,7 @@ int query_len;
query_text = strndup(qtext, query_len);
LOGIF(LE, (skygw_log_write(
LOGFILE_ERROR, "Unexpected query from slave server %s", query_text)));
LOGFILE_ERROR, "Unexpected query from slave %s: %s", slave->dcb->remote, query_text)));
free(query_text);
blr_slave_send_error(router, slave, "Unexpected SQL query received from slave.");
return 1;
@ -1693,6 +1724,9 @@ uint32_t chksum;
binlognamelen = strlen(slave->binlogfile);
len = 19 + 8 + 4 + binlognamelen;
/* no slave crc, remove 4 bytes */
if (slave->nocrc)
len -= 4;
// Build a fake rotate event
resp = gwbuf_alloc(len + 5);
@ -1711,6 +1745,7 @@ uint32_t chksum;
memcpy(ptr, slave->binlogfile, binlognamelen);
ptr += binlognamelen;
if (!slave->nocrc) {
/*
* Now add the CRC to the fake binlog rotate event.
*
@ -1722,6 +1757,7 @@ uint32_t chksum;
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4);
encode_value(ptr, chksum, 32);
}
slave->dcb->func.write(slave->dcb, resp);
return 1;

View File

@ -1,9 +1,9 @@
#ifndef MAXSCALE_TEST_H
#define MAXSCALE_TEST_H
#define TEST_DIR "${CMAKE_BINARY_DIR}"
#define TEST_LOG_DIR "${CMAKE_BINARY_DIR}/log"
#define TEST_BIN_DIR "${CMAKE_BINARY_DIR}/bin"
#define TEST_MOD_DIR "${CMAKE_BINARY_DIR}/modules"
#define TEST_LIB_DIR "${CMAKE_BINARY_DIR}/lib"
#define TEST_ETC_DIR "${CMAKE_BINARY_DIR}/etc"
#define TEST_DIR "@CMAKE_BINARY_DIR@"
#define TEST_LOG_DIR "@CMAKE_BINARY_DIR@/log"
#define TEST_BIN_DIR "@CMAKE_BINARY_DIR@/bin"
#define TEST_MOD_DIR "@CMAKE_BINARY_DIR@/modules"
#define TEST_LIB_DIR "@CMAKE_BINARY_DIR@/lib"
#define TEST_ETC_DIR "@CMAKE_BINARY_DIR@/etc"
#endif

View File

@ -396,6 +396,7 @@ mlist_cursor_t* mlist_cursor_init(
if (c == NULL) {
goto return_cursor;
simple_mutex_unlock(&list->mlist_mutex);
}
c->mlcursor_chk_top = CHK_NUM_MLIST_CURSOR;
c->mlcursor_chk_tail = CHK_NUM_MLIST_CURSOR;
@ -581,6 +582,7 @@ bool mlist_cursor_move_to_first(
simple_mutex_lock(&list->mlist_mutex, true);
if (mc->mlcursor_list->mlist_deleted) {
simple_mutex_unlock(&list->mlist_mutex);
return false;
}
/** Set position point to first node */