mariadb/vio/viossl.c
unknown a5b881594d MWL#192: Non-blocking client API for libmysqlclient.
All client functions that can block on I/O have alternate _start() and
_cont() versions that do not block but return control back to the
application, which can then issue I/O wait in its own fashion and later
call back into the library to continue the operation.

Works behind the scenes by spawning a co-routine/fiber to run the
blocking operation and suspend it while waiting for I/O. This
co-routine/fiber use is invisible to applications.

For i368/x86_64 on GCC, uses very fast assembler co-routine support. On
Windows uses native Win32 Fibers. Falls back to POSIX ucontext on other
platforms. Assembler routines for more platforms are relatively easy to
add by extending mysys/my_context.c, eg. similar to the Lua lcoco
library.

For testing, mysqltest and mysql_client_test are extended with the
option --non-blocking-api. This causes the programs to use the
non-blocking API for database access. mysql-test-run.pl has a similar
option --non-blocking-api that uses this, as well as additional
testcases.

An example program tests/async_queries.c is included that uses the new
non-blocking API with libevent to show how, in a single-threaded
program, to issue many queries in parallel against a database.


client/async_example.c:
  Fix const warning
  ******
  Fix bug with wrong timeout value for poll().
include/Makefile.am:
  Fix missing include for `make dist`
include/mysql.h:
  Add prototypes for all non-blocking API calls.
include/mysql.h.pp:
  Add prototypes for all non-blocking API calls.
mysys/my_context.c:
  Fix type warning for makecontext() function pointer argument.
sql-common/mysql_async.c:
  Fix crashes in the non-blocking API for functions that can take MYSQL argument
  that is NULL.
tests/Makefile.am:
  Add header file to `make dist`
tests/mysql_client_test.c:
  Replace blocking calls with wrappers around the non-blocking calls, used in
  mysql_client_test to test the new non-blocking API.
tests/nonblock-wrappers.h:
  Replace blocking calls with wrappers around the non-blocking calls, used in
  mysql_client_test to test the new non-blocking API.
2011-09-20 12:49:25 +02:00

300 lines
7.9 KiB
C

/* Copyright (C) 2000 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
Note that we can't have assertion on file descriptors; The reason for
this is that during mysql shutdown, another thread can close a file
we are working on. In this case we should just return read errors from
the file descriptior.
*/
#include "vio_priv.h"
#include "my_context.h"
#ifdef HAVE_OPENSSL
#ifdef __NETWARE__
/* yaSSL already uses BSD sockets */
#ifndef HAVE_YASSL
/*
The default OpenSSL implementation on NetWare uses WinSock.
This code allows us to use the BSD sockets.
*/
static int SSL_set_fd_bsd(SSL *s, int fd)
{
int result= -1;
BIO_METHOD *BIO_s_bsdsocket();
BIO *bio;
if ((bio= BIO_new(BIO_s_bsdsocket())))
{
result= BIO_set_fd(bio, fd, BIO_NOCLOSE);
SSL_set_bio(s, bio, bio);
}
return result;
}
#define SSL_set_fd(A, B) SSL_set_fd_bsd((A), (B))
#endif /* HAVE_YASSL */
#endif /* __NETWARE__ */
static void
report_errors(SSL* ssl)
{
unsigned long l;
const char *file;
const char *data;
int line, flags;
#ifndef DBUG_OFF
char buf[512];
#endif
DBUG_ENTER("report_errors");
while ((l= ERR_get_error_line_data(&file,&line,&data,&flags)))
{
DBUG_PRINT("error", ("OpenSSL: %s:%s:%d:%s\n", ERR_error_string(l,buf),
file,line,(flags&ERR_TXT_STRING)?data:"")) ;
}
if (ssl)
{
#ifndef DBUG_OFF
int error= SSL_get_error(ssl, l);
DBUG_PRINT("error", ("error: %s (%d)",
ERR_error_string(error, buf), error));
#endif
}
DBUG_PRINT("info", ("socket_errno: %d", socket_errno));
DBUG_VOID_RETURN;
}
size_t vio_ssl_read(Vio *vio, uchar* buf, size_t size)
{
size_t r;
extern int my_ssl_read_async(struct mysql_async_context *b, SSL *ssl,
void *buf, int size);
DBUG_ENTER("vio_ssl_read");
DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u ssl: 0x%lx",
vio->sd, (long) buf, (uint) size, (long) vio->ssl_arg));
if (vio->async_context && vio->async_context->active)
r= my_ssl_read_async(vio->async_context, (SSL *)vio->ssl_arg, buf, size);
else
r= SSL_read((SSL*) vio->ssl_arg, buf, size);
#ifndef DBUG_OFF
if (r == (size_t) -1)
report_errors((SSL*) vio->ssl_arg);
#endif
DBUG_PRINT("exit", ("%u", (uint) r));
DBUG_RETURN(r);
}
size_t vio_ssl_write(Vio *vio, const uchar* buf, size_t size)
{
size_t r;
extern int my_ssl_write_async(struct mysql_async_context *b, SSL *ssl,
const void *buf, int size);
DBUG_ENTER("vio_ssl_write");
DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd,
(long) buf, (uint) size));
if (vio->async_context && vio->async_context->active)
r= my_ssl_write_async(vio->async_context, (SSL *)vio->ssl_arg, buf, size);
else
r= SSL_write((SSL*) vio->ssl_arg, buf, size);
#ifndef DBUG_OFF
if (r == (size_t) -1)
report_errors((SSL*) vio->ssl_arg);
#endif
DBUG_PRINT("exit", ("%u", (uint) r));
DBUG_RETURN(r);
}
int vio_ssl_close(Vio *vio)
{
int r= 0;
SSL *ssl= (SSL*)vio->ssl_arg;
DBUG_ENTER("vio_ssl_close");
if (ssl)
{
/*
THE SSL standard says that SSL sockets must send and receive a close_notify
alert on socket shutdown to avoid truncation attacks. However, this can
cause problems since we often hold a lock during shutdown and this IO can
take an unbounded amount of time to complete. Since our packets are self
describing with length, we aren't vunerable to these attacks. Therefore,
we just shutdown by closing the socket (quiet shutdown).
*/
SSL_set_quiet_shutdown(ssl, 1);
switch ((r= SSL_shutdown(ssl))) {
case 1:
/* Shutdown successful */
break;
case 0:
/*
Shutdown not yet finished - since the socket is going to
be closed there is no need to call SSL_shutdown() a second
time to wait for the other side to respond
*/
break;
default: /* Shutdown failed */
DBUG_PRINT("vio_error", ("SSL_shutdown() failed, error: %d",
SSL_get_error(ssl, r)));
break;
}
}
DBUG_RETURN(vio_close(vio));
}
void vio_ssl_delete(Vio *vio)
{
if (!vio)
return; /* It must be safe to delete null pointer */
if (vio->type == VIO_TYPE_SSL)
vio_ssl_close(vio); /* Still open, close connection first */
if (vio->ssl_arg)
{
SSL_free((SSL*) vio->ssl_arg);
vio->ssl_arg= 0;
}
vio_delete(vio);
}
static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
int (*connect_accept_func)(SSL*), char *error_string)
{
SSL *ssl;
my_bool unused;
my_bool was_blocking;
DBUG_ENTER("ssl_do");
DBUG_PRINT("enter", ("ptr: 0x%lx, sd: %d ctx: 0x%lx",
(long) ptr, vio->sd, (long) ptr->ssl_context));
error_string[0]= 0;
/* Set socket to blocking if not already set */
vio_blocking(vio, 1, &was_blocking);
if (!(ssl= SSL_new(ptr->ssl_context)))
{
DBUG_PRINT("error", ("SSL_new failure"));
report_errors(ssl);
strmov(error_string, "SSL_new failed");
vio_blocking(vio, was_blocking, &unused);
DBUG_RETURN(1);
}
DBUG_PRINT("info", ("ssl: 0x%lx timeout: %ld", (long) ssl, timeout));
SSL_clear(ssl);
SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout);
SSL_set_fd(ssl, vio->sd);
if (connect_accept_func(ssl) < 1)
{
DBUG_PRINT("error", ("SSL_connect/accept failure"));
report_errors(ssl);
ERR_error_string(SSL_get_error(ssl, 0), error_string);
SSL_free(ssl);
vio_blocking(vio, was_blocking, &unused);
DBUG_RETURN(1);
}
/*
Connection succeeded. Install new function handlers,
change type, set sd to the fd used when connecting
and set pointer to the SSL structure
*/
vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), 0, 0);
vio->ssl_arg= (void*)ssl;
#ifndef DBUG_OFF
{
/* Print some info about the peer */
X509 *cert;
char buf[512];
DBUG_PRINT("info",("SSL connection succeeded"));
DBUG_PRINT("info",("Using cipher: '%s'" , SSL_get_cipher_name(ssl)));
if ((cert= SSL_get_peer_certificate (ssl)))
{
DBUG_PRINT("info",("Peer certificate:"));
X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
DBUG_PRINT("info",("\t subject: '%s'", buf));
X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
DBUG_PRINT("info",("\t issuer: '%s'", buf));
X509_free(cert);
}
else
DBUG_PRINT("info",("Peer does not have certificate."));
if (SSL_get_shared_ciphers(ssl, buf, sizeof(buf)))
{
DBUG_PRINT("info",("shared_ciphers: '%s'", buf));
}
else
DBUG_PRINT("info",("no shared ciphers!"));
}
#endif
DBUG_RETURN(0);
}
int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
char *error_string)
{
DBUG_ENTER("sslaccept");
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_accept, error_string));
}
int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
char *error_string)
{
DBUG_ENTER("sslconnect");
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_connect, error_string));
}
int vio_ssl_blocking(Vio *vio __attribute__((unused)),
my_bool set_blocking_mode,
my_bool *old_mode)
{
/* Mode is always blocking */
*old_mode= 1;
/* Return error if we try to change to non_blocking mode */
return (set_blocking_mode ? 0 : 1);
}
#endif /* HAVE_OPENSSL */