Updated PHP Windows builds for Brotli security fix

Начинаем разработку плагина mysqlnd

Важно помнить, что плагин mysqlnd сам по себе является модулем PHP.

Следующий пример показывает базовую структуру функции MINIT, использующуюся в типичном плагине mysqlnd:

/* my_php_mysqlnd_plugin.c */

 static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
  /* глобальные переменные, ini-настройки, ресурсы, классы */

  /* регистрируем плагин mysqlnd */
  mysqlnd_plugin_id = mysqlnd_plugin_register();

  conn_m = mysqlnd_get_conn_methods();
  memcpy(org_conn_m, conn_m,
    sizeof(struct st_mysqlnd_conn_methods));

  conn_m->query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
  conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
/* my_mysqlnd_plugin.c */

 enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
  /* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
  /* ... */
}

Анализ задачи: от C до пользовательского пространства

 class proxy extends mysqlnd_plugin_connection {
  public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());

Процесс:

  1. PHP: пользователь регистрирует callback-функцию плагина

  2. PHP: пользователь вызывает PHP MySQL API для соединения с MySQL

  3. C: ext/*mysql* вызывает метод mysqlnd

  4. C: mysqlnd обрывается в ext/mysqlnd_plugin

  5. C: ext/mysqlnd_plugin

    1. Вызывает пользовательскую callback-функцию

    2. Или оригинальный метод mysqlnd, если она не задана

Вам необходимо выполнить следующие действия:

  1. Создайте в С класс "mysqlnd_plugin_connection"

  2. Примите и зарегистрируйте прокси объект с помощью "mysqlnd_plugin_set_conn_proxy()"

  3. Вызовите прокси методы пространства пользователя из C (оптимизация - zend_interfaces.h)

Методы объекта пространства пользователя должны быть вызваны с помощью call_user_function() или на уровень ниже, с помощью zend_call_method().

Оптимизация: вызывайте методы из С с помощью zend_call_method

Следующий кусок кода демонстрирует прототип функции zend_call_method, взятый из zend_interfaces.h.

 ZEND_API zval* zend_call_method(
  zval **object_pp, zend_class_entry *obj_ce,
  zend_function **fn_proxy, char *function_name,
  int function_name_len, zval **retval_ptr_ptr,
  int param_count, zval* arg1, zval* arg2 TSRMLS_DC
);

API движка Zend поддерживает только два аргумента. Иногда требуется больше. Например:

 enum_func_status (*func_mysqlnd_conn__connect)(
  MYSQLND *conn, const char *host,
  const char * user, const char * passwd,
  unsigned int passwd_len, const char * db,
  unsigned int db_len, unsigned int port,
  const char * socket, unsigned int mysql_flags TSRMLS_DC
);

Для обхода этой проблемы вам необходимо сделать копию zend_call_method() и добавить необходимые параметры. Вы можете сделать это создав набор макросов MY_ZEND_CALL_METHOD_WRAPPER.

Обращение к пространству пользователя PHP

Этот кусок кода демонстрирует оптимизированный метод вызова функций пространства пользователя из С:

/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class, connect)
(
    MYSQLND *conn, const char *host /* ... */ TSRMLS_DC)
{
    enum_func_status ret = FAIL;
    zval *global_user_conn_proxy = fetch_userspace_proxy();
    if (global_user_conn_proxy)
    {
        /* Вызов прокси пространства пользователя */
        ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
    }
    else
    {
        /* или оригинальный метод mysqlnd = ничего не делать, быть прозрачным */
        ret = org_methods.connect(conn, host, user, passwd,
                                  passwd_len, db, db_len, port,
                                  socket, mysql_flags TSRMLS_CC);
    }
    return ret;
}

Обращение к пространству пользователя: простые аргументы

/* my_mysqlnd_plugin.c */

 MYSQLND_METHOD(my_conn_class,connect)(
  /* ... */, const char *host, /* ...*/) {
  /* ... */
  if (global_user_conn_proxy) {
    /* ... */
    zval* zv_host;
    MAKE_STD_ZVAL(zv_host);
    ZVAL_STRING(zv_host, host, 1);
    MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_host /*, ...*/);
    zval_ptr_dtor(&zv_host);
    /* ... */
  }
  /* ... */
}

Обращение к пространству пользователя: структуры как аргументы

/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class, connect)
(
    MYSQLND *conn, /* ...*/)
{
    /* ... */
    if (global_user_conn_proxy)
    {
        /* ... */
        zval *zv_conn;
        ZEND_REGISTER_RESOURCE(zv_conn, (void *)conn, le_mysqlnd_plugin_conn);
        MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_conn, zv_host /*, ...*/);
        zval_ptr_dtor(&zv_conn);
        /* ... */
    }
    /* ... */
}

первый аргумент многих методов mysqlnd — «объекты» С. Например, первый аргумент метода connect() — указатель на MYSQLND. Структура MYSQLND — объект соединения mysqlnd.

Указатель на объект соединения mysqlnd сравнивают со стандартным обработчиком файлового ввода-вывода. Так же как и он, объект соединения mysqlnd связывают с пространством пользователя через PHP-тип resource.

Из C в пространство пользователя и обратно

<?php

class proxy extends mysqlnd_plugin_connection
{
    public function connect($conn, $host, ...)
    {
        /* До внедрения */
        printf("Подключение к = '%s'\n", $host);
        debug_print_backtrace();
        return parent::connect($conn);
    }

    public function query($conn, $query)
    {
        /* После внедрения */
        $ret = parent::query($conn, $query);
        printf("Запрос = '%s'\n", $query);
        return $ret;
    }
}

mysqlnd_plugin_set_conn_proxy(new proxy());

Пользователи PHP должны иметь возможность вызывать родительские реализации переопределённых методов.

В результате наследования возможно «уточнить» только выбранные методы и вы можете выбрать, когда выполнять ваш код, до или после родительского.

Встроенный класс: mysqlnd_plugin_connection::connect()

/*  my_mysqlnd_plugin_classes.c */

PHP_METHOD("mysqlnd_plugin_connection", connect)
{
    /* ... упрощённый! ... */
    zval *mysqlnd_rsrc;
    MYSQLND *conn;
    char *host;
    int host_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",
                              &mysqlnd_rsrc, &host, &host_len) == FAILURE)
    {
        RETURN_NULL();
    }
    ZEND_FETCH_RESOURCE(conn, MYSQLND * conn, &mysqlnd_rsrc, -1,
                        "Mysqlnd Connection", le_mysqlnd_plugin_conn);
    if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
        RETVAL_TRUE;
    else
        RETVAL_FALSE;
}
+Добавить

Примечания пользователей

Пользователи ещё не добавляли примечания для страницы
To Top