Extending MySQL 8.0  /  ...  /  Using Your Own Protocol Trace Plugins

4.4.11.2 Using Your Own Protocol Trace Plugins

Note

To use your own protocol trace plugins, you must configure MySQL with the WITH_TEST_TRACE_PLUGIN CMake option disabled because only one protocol trace plugin can be loaded at a time and an error occurs for attempts to load a second one. If you have already built MySQL with the test protocol trace plugin enabled to see how it works, you must rebuild MySQL without it before you can use your own plugins.

This section discusses how to write a basic protocol trace plugin named simple_trace. This plugin provides a framework showing how to set up the client plugin descriptor and create the trace-related callback functions. In simple_trace, these functions are rudimentary and do little other than illustrate the arguments required. To see in detail how a trace plugin can make use of trace event information, check the source file for the test protocol trace plugin (test_trace_plugin.cc in the libmysql directory of a MySQL source distribution). However, note that the st_mysql_client_plugin_TRACE structure used there differs from the structures used with the usual client plugin declaration macros. In particular, the first two members are defined explicitly, not implicitly by declaration macros.

Several header files contain information relevant to protocol trace plugins:

  • client_plugin.h: Defines the API for client plugins. This includes the client plugin descriptor and function prototypes for client plugin C API calls (see C API Client Plugin Interface).

  • plugin_trace.h: Contains declarations for client-side plugins of type MYSQL_CLIENT_TRACE_PLUGIN. It also contains descriptions of the permitted protocol stages, transitions between stages, and the types of events permitted at each stage.

To write a protocol trace plugin, include the following header files in the plugin source file. Other MySQL or general header files might also be needed, depending on the plugin capabilities and requirements.

#include <mysql/plugin_trace.h>
#include <mysql.h>

plugin_trace.h includes client_plugin.h, so you need not include the latter file explicitly.

Declare the client-side plugin descriptor with the mysql_declare_client_plugin() and mysql_end_client_plugin macros (see Section 4.4.2.3, “Client Plugin Descriptors”). For the simple_trace plugin, the descriptor looks like this:

mysql_declare_client_plugin(TRACE)
  "simple_trace",                 /* plugin name */
  "Author Name",                  /* author */
  "Simple protocol trace plugin", /* description */
  {1,0,0},                        /* version = 1.0.0 */
  "GPL",                          /* license type */
  NULL,                           /* for internal use */
  plugin_init,                    /* initialization function */
  plugin_deinit,                  /* deinitialization function */
  plugin_options,                 /* option-handling function */
  trace_start,                    /* start-trace function */
  trace_stop,                     /* stop-trace function */
  trace_event                     /* event-handling function */
mysql_end_client_plugin;

The descriptor members from the plugin name through the option-handling function are common to all client plugin types. The members following the common members implement trace event handling.

Function members for which the plugin needs no processing can be declared as NULL in the descriptor, in which case you need not write any corresponding function. For illustration purposes and to show the argument syntax, the following discussion implements all functions listed in the descriptor, even though some of them do nothing,

The initialization, deinitialization, and options functions common to all client plugins are declared as follows. For a description of the arguments and return values, see Section 4.4.2.3, “Client Plugin Descriptors”.

static int
plugin_init(char *errbuf, size_t errbuf_len, int argc, va_list args)
{
  return 0;
}

static int
plugin_deinit()
{
  return 0;
}

static int
plugin_options(const char *option, const void *value)
{
  return 0;
}

The trace-specific members of the client plugin descriptor are callback functions. The following descriptions provide more detail on how they are used. Each has a first argument that is a pointer to the plugin instance in case your implementation needs to access it.

trace_start(): This function is called at the start of each traced connection (each connection that starts after the plugin is loaded). It is passed the connection handler and the protocol stage at which tracing starts. trace_start() allocates memory needed by the trace_event() function, if any, and returns a pointer to it. If no memory is needed, this function returns NULL.

static void*
trace_start(struct st_mysql_client_plugin_TRACE *self,
            MYSQL *conn,
            enum protocol_stage stage)
{
  struct st_trace_data *plugin_data= malloc(sizeof(struct st_trace_data));

  fprintf(stderr, "Initializing trace: stage %d\n", stage);
  if (plugin_data)
  {
    memset(plugin_data, 0, sizeof(struct st_trace_data));
    fprintf(stderr, "Trace initialized\n");
    return plugin_data;
  }
  fprintf(stderr, "Could not initialize trace\n");
  exit(1);
}

trace_stop(): This function is called when tracing of the connection ends. That usually happens when the connection is closed, but can happen earlier. For example, trace_event() can return a nonzero value at any time and that causes tracing of the connection to terminate. trace_stop() is then called even though the connection has not ended.

trace_stop() is passed the connection handler and a pointer to the memory allocated by trace_start() (NULL if none). If the pointer is non-NULL, trace_stop() should deallocate the memory. This function returns no value.

static void
trace_stop(struct st_mysql_client_plugin_TRACE *self,
           MYSQL *conn,
           void *plugin_data)
{
  fprintf(stderr, "Terminating trace\n");
  if (plugin_data)
    free(plugin_data);
}

trace_event(): This function is called for each event occurrence. It is passed a pointer to the memory allocated by trace_start() (NULL if none), the connection handler, the current protocol stage and event codes, and event data. This function returns 0 to continue tracing, nonzero if tracing should stop.

static int
trace_event(struct st_mysql_client_plugin_TRACE *self,
            void *plugin_data,
            MYSQL *conn,
            enum protocol_stage stage,
            enum trace_event event,
            struct st_trace_event_args args)
{
  fprintf(stderr, "Trace event received: stage %d, event %d\n", stage, event);
  if (event == TRACE_EVENT_DISCONNECTED)
    fprintf(stderr, "Connection closed\n");
  return 0;
}

The tracing framework shuts down tracing of the connection when the connection ends, so trace_event() should return nonzero only if you want to terminate tracing of the connection early. Suppose that you want to trace only connections for a certain MySQL account. After authentication, you can check the user name for the connection and stop tracing if it is not the user in whom you are interested.

For each call to trace_event(), the st_trace_event_args structure contains the event data. It has this definition:

struct st_trace_event_args
{
  const char           *plugin_name;
  int                   cmd;
  const unsigned char  *hdr;
  size_t                hdr_len;
  const unsigned char  *pkt;
  size_t                pkt_len;
};

For different event types, the st_trace_event_args structure contains the information described following. All lengths are in bytes. Unused members are set to 0/NULL.

AUTH_PLUGIN event:

plugin_name  The name of the plugin

SEND_COMMAND event:

cmd          The command code
hdr          Pointer to the command packet header
hdr_len      Length of the header
pkt          Pointer to the command arguments
pkt_len      Length of the arguments

Other SEND_xxx and xxx_RECEIVED events:

pkt          Pointer to the data sent or received
pkt_len      Length of the data

PACKET_SENT event:

pkt_len      Number of bytes sent

To compile and install a plugin library file, use the instructions in Section 4.4.3, “Compiling and Installing Plugin Libraries”. To make the library file available for use, install it in the plugin directory (the directory named by the plugin_dir system variable).

After the plugin library file is compiled and installed in the plugin directory, you can test it easily by setting the LIBMYSQL_PLUGINS environment variable to the plugin name, which affects any client program that uses that variable. mysql is one such program:

$> export LIBMYSQL_PLUGINS=simple_trace
shqll> mysql
Initializing trace: stage 0
Trace initialized
Trace event received: stage 0, event 1
Trace event received: stage 0, event 2
...
Welcome to the MySQL monitor.  Commands end with ; or \g.
Trace event received
Trace event received
...
mysql> SELECT 1;
Trace event received: stage 4, event 12
Trace event received: stage 4, event 16
...
Trace event received: stage 8, event 14
Trace event received: stage 8, event 15
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> quit
Trace event received: stage 4, event 12
Trace event received: stage 4, event 16
Trace event received: stage 4, event 3
Connection closed
Terminating trace
Bye

To stop the trace plugin from being loaded, do this:

$> LIBMYSQL_PLUGINS=

It is also possible to write client programs that directly load the plugin. You can tell the client where the plugin directory is located by calling mysql_options() to set the MYSQL_PLUGIN_DIR option:

char *plugin_dir = "path_to_plugin_dir";

/* ... process command-line options ... */

mysql_options(&mysql, MYSQL_PLUGIN_DIR, plugin_dir);

Typically, the program will also accept a --plugin-dir option that enables users to override the default value.

Should a client program require lower-level plugin management, the client library contains functions that take an st_mysql_client_plugin argument. See C API Client Plugin Interface.