From e47e90488ae6ebc730b0722a3f2bcd0a359247a3 Mon Sep 17 00:00:00 2001 From: Daniel Salzman <daniel.salzman@nic.cz> Date: Fri, 18 Mar 2016 08:54:13 +0100 Subject: [PATCH] libknot: add server control interface --- Knot.files | 3 + src/Makefile.am | 2 + src/libknot/control/control.c | 565 ++++++++++++++++++++++++++++++++++ src/libknot/control/control.h | 157 ++++++++++ src/libknot/libknot.h | 3 +- tests/.gitignore | 1 + tests/Makefile.am | 1 + tests/control.c | 221 +++++++++++++ 8 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 src/libknot/control/control.c create mode 100644 src/libknot/control/control.h create mode 100644 tests/control.c diff --git a/Knot.files b/Knot.files index dd77c651d..fe098c00f 100644 --- a/Knot.files +++ b/Knot.files @@ -347,6 +347,8 @@ src/libknot/binary.h src/libknot/codes.c src/libknot/codes.h src/libknot/consts.h +src/libknot/control/control.c +src/libknot/control/control.h src/libknot/db/db.h src/libknot/db/db_lmdb.c src/libknot/db/db_lmdb.h @@ -497,6 +499,7 @@ tests/conf.c tests/conf_tools.c tests/confdb.c tests/confio.c +tests/control.c tests/db.c tests/descriptor.c tests/dname.c diff --git a/src/Makefile.am b/src/Makefile.am index 2f1ba223a..cee2e0eb5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,6 +98,7 @@ nobase_libknot_la_HEADERS = \ libknot/binary.h \ libknot/codes.h \ libknot/consts.h \ + libknot/control/control.h \ libknot/descriptor.h \ libknot/dname.h \ libknot/dnssec/rrset-sign.h \ @@ -141,6 +142,7 @@ nobase_libknot_la_HEADERS = \ libknot_la_SOURCES = \ libknot/binary.c \ libknot/codes.c \ + libknot/control/control.c \ libknot/descriptor.c \ libknot/dname.c \ libknot/dnssec/rrset-sign.c \ diff --git a/src/libknot/control/control.c b/src/libknot/control/control.c new file mode 100644 index 000000000..677c1e1ae --- /dev/null +++ b/src/libknot/control/control.c @@ -0,0 +1,565 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libknot/control/control.h" +#include "libknot/attribute.h" +#include "libknot/error.h" +#include "contrib/mempattern.h" +#include "contrib/net.h" +#include "contrib/sockaddr.h" +#include "contrib/ucw/mempool.h" +#include "contrib/wire_ctx.h" + +/*! Size of the input and output buffers. */ +#ifndef CTL_BUFF_SIZE +#define CTL_BUFF_SIZE (256 * 1024) +#endif + +/*! Default socket operations timeout in milliseconds. */ +#define DEFAULT_TIMEOUT (5 * 1000) + +/*! The first data item code. */ +#define DATA_CODE_OFFSET 16 + +/*! Control context structure. */ +struct knot_ctl { + /*! Memory pool context. */ + knot_mm_t mm; + /*! Network operations timeout. */ + int timeout; + /*! Server listening socket. */ + int listen_sock; + /*! Remote server/client socket. */ + int sock; + + /*! The latter read data. */ + knot_ctl_data_t data; + + /*! Write wire context. */ + wire_ctx_t wire_out; + /*! Read wire context. */ + wire_ctx_t wire_in; + + /*! Write buffer. */ + uint8_t buff_out[CTL_BUFF_SIZE]; + /*! Read buffer. */ + uint8_t buff_in[CTL_BUFF_SIZE]; +}; + +static int type_to_code(knot_ctl_type_t type) +{ + switch (type) { + case KNOT_CTL_TYPE_END: return 0; + case KNOT_CTL_TYPE_DATA: return 1; + case KNOT_CTL_TYPE_EXTRA: return 2; + case KNOT_CTL_TYPE_BLOCK: return 3; + default: return -1; + } +} + +static int code_to_type(uint8_t code) +{ + switch (code) { + case 0: return KNOT_CTL_TYPE_END; + case 1: return KNOT_CTL_TYPE_DATA; + case 2: return KNOT_CTL_TYPE_EXTRA; + case 3: return KNOT_CTL_TYPE_BLOCK; + default: return -1; + } +} + +static bool is_data_type(knot_ctl_type_t type) +{ + switch (type) { + case KNOT_CTL_TYPE_DATA: + case KNOT_CTL_TYPE_EXTRA: + return true; + default: + return false; + } +} + +static int idx_to_code(knot_ctl_idx_t idx) +{ + if (idx >= KNOT_CTL_IDX__COUNT) { + return -1; + } + + return DATA_CODE_OFFSET + idx; +} + +static int code_to_idx(uint8_t code) +{ + if (code < DATA_CODE_OFFSET || + code >= DATA_CODE_OFFSET + KNOT_CTL_IDX__COUNT) { + return -1; + } + + return code - DATA_CODE_OFFSET; +} + +static void reset_buffers(knot_ctl_t *ctx) +{ + ctx->wire_out = wire_ctx_init(ctx->buff_out, CTL_BUFF_SIZE); + ctx->wire_in = wire_ctx_init(ctx->buff_in, 0); +} + +static void clean_data(knot_ctl_t *ctx) +{ + for (knot_ctl_idx_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) { + if (ctx->data[i] != NULL) { + mm_free(&ctx->mm, (void *)ctx->data[i]); + ctx->data[i] = NULL; + } + } +} + +static void close_sock(int *sock) +{ + if (*sock < 0) { + return; + } + + close(*sock); + *sock = -1; +} + +_public_ +knot_ctl_t* knot_ctl_alloc(void) +{ + knot_ctl_t *ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } + memset(ctx, 0, sizeof(*ctx)); + + mm_ctx_mempool(&ctx->mm, MM_DEFAULT_BLKSIZE); + ctx->timeout = DEFAULT_TIMEOUT; + ctx->listen_sock = -1; + ctx->sock = -1; + + reset_buffers(ctx); + + return ctx; +} + +_public_ +void knot_ctl_free(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return; + } + + close_sock(&ctx->listen_sock); + close_sock(&ctx->sock); + + clean_data(ctx); + + mp_delete(ctx->mm.ctx); + + memset(ctx, 0, sizeof(*ctx)); + free(ctx); +} + +_public_ +void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms) +{ + if (ctx == NULL) { + return; + } + + ctx->timeout = (timeout_ms > 0) ? timeout_ms : -1; +} + +_public_ +int knot_ctl_bind(knot_ctl_t *ctx, const char *path) +{ + if (ctx == NULL || path == NULL) { + return KNOT_EINVAL; + } + + // Prepare socket address. + struct sockaddr_storage addr; + int ret = sockaddr_set(&addr, AF_UNIX, path, 0); + if (ret != KNOT_EOK) { + return ret; + } + + // Bind the socket. + ctx->listen_sock = net_bound_socket(SOCK_STREAM, &addr, 0); + if (ctx->listen_sock < 0) { + return ctx->listen_sock; + } + + // Start listening. + if (listen(ctx->listen_sock, 1) != 0) { + close_sock(&ctx->listen_sock); + return knot_map_errno(); + } + + return KNOT_EOK; +} + +_public_ +void knot_ctl_unbind(knot_ctl_t *ctx) +{ + if (ctx == NULL || ctx->listen_sock < 0) { + return; + } + + // Remove the control socket file. + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(ctx->listen_sock, (struct sockaddr *)&addr, &addr_len) == 0) { + char addr_str[SOCKADDR_STRLEN] = { 0 }; + if (sockaddr_tostr(addr_str, sizeof(addr_str), &addr) > 0) { + (void)unlink(addr_str); + } + } + + // Close the listening socket. + close_sock(&ctx->listen_sock); +} + +_public_ +int knot_ctl_accept(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + knot_ctl_close(ctx); + + // Control interface. + struct pollfd pfd = { .fd = ctx->listen_sock, .events = POLLIN }; + int ret = poll(&pfd, 1, -1); + if (ret <= 0) { + return knot_map_errno(); + } + + int client = net_accept(ctx->listen_sock, NULL); + if (client < 0) { + return client; + } + + ctx->sock = client; + + reset_buffers(ctx); + + return KNOT_EOK; +} + +_public_ +int knot_ctl_connect(knot_ctl_t *ctx, const char *path) +{ + if (ctx == NULL || path == NULL) { + return KNOT_EINVAL; + } + + // Prepare socket address. + struct sockaddr_storage addr; + int ret = sockaddr_set(&addr, AF_UNIX, path, 0); + if (ret != KNOT_EOK) { + return ret; + } + + // Connect to socket. + ctx->sock = net_connected_socket(SOCK_STREAM, &addr, NULL); + if (ctx->sock < 0) { + return ctx->sock; + } + + reset_buffers(ctx); + + return KNOT_EOK; +} + +_public_ +void knot_ctl_close(knot_ctl_t *ctx) +{ + if (ctx == NULL) { + return; + } + + close_sock(&ctx->sock); +} + +static int ensure_output(knot_ctl_t *ctx, uint16_t len) +{ + wire_ctx_t *w = &ctx->wire_out; + + // Check for enough available room in the output buffer. + size_t available = wire_ctx_available(w); + if (available >= len) { + return KNOT_EOK; + } + + // Flush the buffer. + int ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w), + ctx->timeout); + if (ret < 0) { + return ret; + } + + *w = wire_ctx_init(w->wire, CTL_BUFF_SIZE); + + return KNOT_EOK; +} + +static int send_item(knot_ctl_t *ctx, uint8_t code, const char *data, bool flush) +{ + wire_ctx_t *w = &ctx->wire_out; + + // Write the control block code. + int ret = ensure_output(ctx, sizeof(uint8_t)); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write_u8(w, code); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Control block data is optional. + if (data != NULL) { + // Get the data length. + size_t data_len = strlen(data); + if (data_len > UINT16_MAX) { + return KNOT_ERANGE; + } + + // Write the data length. + ret = ensure_output(ctx, sizeof(uint16_t)); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write_u16(w, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Write the data. + ret = ensure_output(ctx, data_len); + if (ret != KNOT_EOK) { + return ret; + } + wire_ctx_write(w, (uint8_t *)data, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + } + + // Send finalized buffer. + if (flush && wire_ctx_offset(w) > 0) { + ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w), + ctx->timeout); + if (ret < 0) { + return ret; + } + + *w = wire_ctx_init(w->wire, CTL_BUFF_SIZE); + } + + return KNOT_EOK; +} + +_public_ +int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data) +{ + if (ctx == NULL) { + return KNOT_EINVAL; + } + + // Get the type code. + int code = type_to_code(type); + if (code == -1) { + return KNOT_EINVAL; + } + + // Send unit type. + int ret = send_item(ctx, code, NULL, !is_data_type(type)); + if (ret != KNOT_EOK) { + return ret; + } + + // Send unit data. + if (is_data_type(type) && data != NULL) { + // Send all non-empty data items. + for (knot_ctl_idx_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) { + const char *value = (*data)[i]; + if (value == NULL) { + continue; + } + + ret = send_item(ctx, idx_to_code(i), value, false); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + return KNOT_EOK; +} + +static int ensure_input(knot_ctl_t *ctx, uint16_t len) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Check for enough available room in the input buffer. + size_t available = wire_ctx_available(w); + if (available >= len) { + return KNOT_EOK; + } + + // Move unprocessed data to the beginning of the buffer. + memmove(w->wire, w->wire + wire_ctx_offset(w), available); + + // Receive enough data. + while (available < len) { + int ret = net_stream_recv(ctx->sock, w->wire + available, + CTL_BUFF_SIZE - available, + ctx->timeout); + if (ret < 0) { + return ret; + } + assert(ret > 0); + available += ret; + } + + ctx->wire_in = wire_ctx_init(w->wire, available); + + return KNOT_EOK; +} + +static int receive_item_code(knot_ctl_t *ctx, uint8_t *code) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Read the type. + int ret = ensure_input(ctx, sizeof(uint8_t)); + if (ret != KNOT_EOK) { + return ret; + } + *code = wire_ctx_read_u8(w); + if (w->error != KNOT_EOK) { + return w->error; + } + + return KNOT_EOK; +} + +static int receive_item_value(knot_ctl_t *ctx, char **value) +{ + wire_ctx_t *w = &ctx->wire_in; + + // Read value length. + int ret = ensure_input(ctx, sizeof(uint16_t)); + if (ret != KNOT_EOK) { + return ret; + } + uint16_t data_len = wire_ctx_read_u16(w); + if (w->error != KNOT_EOK) { + return w->error; + } + + // Read the value. + ret = ensure_input(ctx, data_len); + if (ret != KNOT_EOK) { + return ret; + } + *value = mm_alloc(&ctx->mm, data_len + 1); + if (*value == NULL) { + return KNOT_ENOMEM; + } + wire_ctx_read(w, (uint8_t *)*value, data_len); + if (w->error != KNOT_EOK) { + return w->error; + } + (*value)[data_len] = '\0'; + + return KNOT_EOK; +} + +_public_ +int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data) +{ + if (ctx == NULL || type == NULL) { + return KNOT_EINVAL; + } + + wire_ctx_t *w = &ctx->wire_in; + + // Reset output variables. + *type = KNOT_CTL_TYPE_END; + clean_data(ctx); + + // Read data units until end of message. + bool have_type = false; + while (true) { + uint8_t code; + int ret = receive_item_code(ctx, &code); + if (ret != KNOT_EOK) { + return ret; + } + + // Process unit type. + int current_type = code_to_type(code); + if (current_type != -1) { + if (have_type) { + // Revert parsed type. + wire_ctx_skip(w, -sizeof(uint8_t)); + assert(w->error == KNOT_EOK); + break; + } + + // Set the unit type. + *type = current_type; + + if (is_data_type(current_type)) { + have_type = true; + continue; + } else { + break; + } + } + + // Check for data item code. + int idx = code_to_idx(code); + if (idx == -1) { + return KNOT_EINVAL; + } + + // Store the item data value. + ret = receive_item_value(ctx, (char **)&ctx->data[idx]); + if (ret != KNOT_EOK) { + return ret; + } + } + + // Set the output data. + if (data != NULL) { + memcpy(*data, ctx->data, sizeof(*data)); + } + + return KNOT_EOK; +} diff --git a/src/libknot/control/control.h b/src/libknot/control/control.h new file mode 100644 index 000000000..ad91cc05d --- /dev/null +++ b/src/libknot/control/control.h @@ -0,0 +1,157 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + */ +/*! + * \file + * + * \brief A server control interface. + * + * \addtogroup libknot + * @{ + */ + +#pragma once + +/*! Control data item indexes. */ +typedef enum { + KNOT_CTL_IDX_CMD = 0, /*!< Control command name. */ + KNOT_CTL_IDX_FLAGS, /*!< Control command flags. */ + KNOT_CTL_IDX_ERROR, /*!< Error message. */ + KNOT_CTL_IDX_SECTION, /*!< Configuration section name. */ + KNOT_CTL_IDX_ITEM, /*!< Configuration item name. */ + KNOT_CTL_IDX_ID, /*!< Congiguration item identifier. */ + KNOT_CTL_IDX_ZONE, /*!< Zone name. */ + KNOT_CTL_IDX_OWNER, /*!< Zone record owner */ + KNOT_CTL_IDX_TTL, /*!< Zone record TTL. */ + KNOT_CTL_IDX_TYPE, /*!< Zone record type name. */ + KNOT_CTL_IDX_DATA, /*!< Configuration item/zone record data. */ + KNOT_CTL_IDX__COUNT, /*!< The number of data items. */ +} knot_ctl_idx_t; + +/*! Control unit types. */ +typedef enum { + KNOT_CTL_TYPE_END, /*!< End of message, cache flushed. */ + KNOT_CTL_TYPE_DATA, /*!< Data unit, cached. */ + KNOT_CTL_TYPE_EXTRA, /*!< Extra value data unit, cached. */ + KNOT_CTL_TYPE_BLOCK, /*!< End of data block, cache flushed. */ +} knot_ctl_type_t; + +/*! Control input/output string data. */ +typedef const char* knot_ctl_data_t[KNOT_CTL_IDX__COUNT]; + +/*! A control context. */ +struct knot_ctl; +typedef struct knot_ctl knot_ctl_t; + +/*! + * Allocates a control context. + * + * \return Control context. + */ +knot_ctl_t* knot_ctl_alloc(void); + +/*! + * Deallocates a control context. + * + * \param[in] ctx Control context. + */ +void knot_ctl_free(knot_ctl_t *ctx); + +/*! + * Sets the timeout for socket operations. + * + * Default value is 5 seconds. + * + * \param[in] ctx Control context. + * \param[in] timeout Timeout in milliseconds (0 for infinity). + */ +void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms); + +/*! + * Binds a specified UNIX socket path. + * + * \note Server operation. + * + * \param[in] ctx Control context. + * \param[in] path Control UNIX socket path. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_bind(knot_ctl_t *ctx, const char *path); + +/*! + * Unbinds a control socket. + * + * \note Server operation. + * + * \param[in] ctx Control context. + */ +void knot_ctl_unbind(knot_ctl_t *ctx); + +/*! + * Connects to a specified UNIX socket path. + * + * \note Client operation. + * + * \param[in] ctx Control context. + * \param[in] path Control UNIX socket path. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_connect(knot_ctl_t *ctx, const char *path); + +/*! + * Waits for an incomming connection. + * + * \note Server operation. + * + * \param[in] ctx Control context. + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_accept(knot_ctl_t *ctx); + +/*! + * Closes the remote connections. + * + * \note Applies to both server and client. + * + * \param[in] ctx Control context. + */ +void knot_ctl_close(knot_ctl_t *ctx); + +/*! + * Sends one control unit. + * + * \param[in] ctx Control context. + * \param[in] type Unit type to send. + * \param[in] data Data unit to send (optional, ignored if non-data type). + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data); + +/*! + * Receives one control unit. + * + * \param[in] ctx Control context. + * \param[out] type Received unit type. + * \param[out] data Received data unit (optional). + * + * \return Error code, KNOT_EOK if successful. + */ +int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data); + +/*! @} */ diff --git a/src/libknot/libknot.h b/src/libknot/libknot.h index b925b7764..712109879 100644 --- a/src/libknot/libknot.h +++ b/src/libknot/libknot.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> 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 @@ -40,6 +40,7 @@ #include "libknot/rrset.h" #include "libknot/tsig-op.h" #include "libknot/tsig.h" +#include "libknot/control/control.h" #include "libknot/db/db.h" #include "libknot/db/db_lmdb.h" #include "libknot/db/db_trie.h" diff --git a/tests/.gitignore b/tests/.gitignore index a45978541..986a1266d 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -11,6 +11,7 @@ conf conf_tools confdb confio +control db descriptor dname diff --git a/tests/Makefile.am b/tests/Makefile.am index 69be33925..86db0ff2c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -19,6 +19,7 @@ check_PROGRAMS = \ conf_tools \ confdb \ confio \ + control \ db \ descriptor \ dname \ diff --git a/tests/control.c b/tests/control.c new file mode 100644 index 000000000..e4052f954 --- /dev/null +++ b/tests/control.c @@ -0,0 +1,221 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> +#include <tap/basic.h> +#include <tap/files.h> + +#define CTL_BUFF_SIZE 18 +#include "libknot/control/control.c" + +#define fake_ok(condition, msg, ...) \ + if (!(condition)) { \ + if (msg != NULL) { \ + printf("error: " msg "\n", ##__VA_ARGS__); \ + } \ + exit(-1); \ + } + +static void ctl_client(const char *socket, size_t argc, knot_ctl_data_t *argv) +{ + knot_ctl_t *ctl = knot_ctl_alloc(); + fake_ok(ctl != NULL, "Allocate control"); + + int ret; + for (int i = 0; i < 20; i++) { + ret = knot_ctl_connect(ctl, socket); + if (ret == KNOT_EOK) { + break; + } + usleep(100000); + } + fake_ok(ret == KNOT_EOK, "Connect to socket"); + + diag("BEGIN: Client -> Server"); + + if (argc > 0) { + for (size_t i = 0; i < argc; i++) { + if (argv[i][KNOT_CTL_IDX_CMD] != NULL && + argv[i][KNOT_CTL_IDX_CMD][0] == '\0') { + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL); + fake_ok(ret == KNOT_EOK, "Client send data block end type"); + } else { + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]); + fake_ok(ret == KNOT_EOK, "Client send data %zu", i); + } + } + } + + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL); + fake_ok(ret == KNOT_EOK, "Client send final data"); + + diag("END: Client -> Server"); + diag("BEGIN: Client <- Server"); + + size_t count = 0; + knot_ctl_data_t data; + knot_ctl_type_t type; + while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) { + if (type == KNOT_CTL_TYPE_END) { + break; + } + if (argv[count][KNOT_CTL_IDX_CMD] != NULL && + argv[count][KNOT_CTL_IDX_CMD][0] == '\0') { + fake_ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type"); + } else { + fake_ok(type == KNOT_CTL_TYPE_DATA, "Check data type"); + for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) { + fake_ok((data[i] == NULL && argv[count][i] == NULL) || + (data[i] != NULL && argv[count][i] != NULL), + "Client compare input item occupation %zu", i); + if (data[i] == NULL) { + continue; + } + + fake_ok(strcmp(data[i], argv[count][i]) == 0, + "Client compare input item '%s", argv[count][i]); + } + } + count++; + } + fake_ok(ret == KNOT_EOK, "Receive OK check"); + fake_ok(type == KNOT_CTL_TYPE_END, "Receive EOF type"); + fake_ok(count == argc, "Client compare input count '%zu'", argc); + + diag("END: Client <- Server"); + + knot_ctl_close(ctl); + knot_ctl_free(ctl); +} + +static void ctl_server(const char *socket, size_t argc, knot_ctl_data_t *argv) +{ + knot_ctl_t *ctl = knot_ctl_alloc(); + ok(ctl != NULL, "Allocate control"); + + int ret = knot_ctl_bind(ctl, socket); + ok(ret == KNOT_EOK, "Bind control socket"); + + ret = knot_ctl_accept(ctl); + ok(ret == KNOT_EOK, "Accept a connection"); + + diag("BEGIN: Server <- Client"); + + size_t count = 0; + knot_ctl_data_t data; + knot_ctl_type_t type; + while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) { + if (type == KNOT_CTL_TYPE_END) { + break; + } + if (argv[count][KNOT_CTL_IDX_CMD] != NULL && + argv[count][KNOT_CTL_IDX_CMD][0] == '\0') { + ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type"); + } else { + ok(type == KNOT_CTL_TYPE_DATA, "Check data type"); + for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) { + ok((data[i] == NULL && argv[count][i] == NULL) || + (data[i] != NULL && argv[count][i] != NULL), + "Server compare input item occupation %zu", i); + if (data[i] == NULL) { + continue; + } + + ok(strcmp(data[i], argv[count][i]) == 0, + "Server compare input item '%s", argv[count][i]); + } + } + count++; + } + ok(ret == KNOT_EOK, "Receive OK check"); + ok(type == KNOT_CTL_TYPE_END, "Receive EOF type"); + ok(count == argc, "Server compare input count '%zu'", argc); + + diag("END: Server <- Client"); + diag("BEGIN: Server -> Client"); + + if (argc > 0) { + for (size_t i = 0; i < argc; i++) { + if (argv[i][KNOT_CTL_IDX_CMD] != NULL && + argv[i][KNOT_CTL_IDX_CMD][0] == '\0') { + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL); + ok(ret == KNOT_EOK, "Client send data block end type"); + } else { + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]); + ok(ret == KNOT_EOK, "Server send data %zu", i); + } + } + } + + ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL); + ok(ret == KNOT_EOK, "Server send final data"); + + diag("END: Server -> Client"); + + knot_ctl_close(ctl); + knot_ctl_unbind(ctl); + knot_ctl_free(ctl); +} + +static void test_client_server_client(void) +{ + char *socket = test_mktemp(); + ok(socket != NULL, "Make a temporary socket file '%s'", socket); + + size_t data_len = 5; + knot_ctl_data_t data[] = { + { "command", "error", "section", "item", "identifier", + "zone", "owner", "ttl", "type", "data" }, + { [KNOT_CTL_IDX_DATA] = "\x01\x02" }, + { [KNOT_CTL_IDX_CMD] = "\0" }, // This means block end in this test! + { NULL }, + { [KNOT_CTL_IDX_ERROR] = "Ultra long message" } + }; + + // Fork a client process. + pid_t child_pid = fork(); + if (child_pid == -1) { + ok(child_pid >= 0, "Process fork"); + return; + } + if (child_pid == 0) { + ctl_client(socket, data_len, data); + free(socket); + return; + } else { + ctl_server(socket, data_len, data); + } + + int status; + wait(&status); + ok(status >= 0, "Wait for client"); + + test_rm_rf(socket); + free(socket); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + diag("Client -> Server -> Client"); + test_client_server_client(); + + return 0; +} -- GitLab