pytests: add rehandshake test

parent 922cb93f
......@@ -287,6 +287,9 @@ pytests:run:
except:
- master
script:
- pushd tests/pytests/rehandshake
- make all
- popd
- PATH="$PREFIX/sbin:$PATH" ./ci/pytests/run.sh &> pytests.log.txt
after_script:
- tail -1 pytests.log.txt
......
......@@ -30,7 +30,7 @@ def create_file_from_template(template_path, dest, data):
fh.write(rendered_template)
Forward = namedtuple('Forward', ['proto', 'ip', 'port'])
Forward = namedtuple('Forward', ['proto', 'ip', 'port', 'hostname', 'ca_file'])
class Kresd(ContextDecorator):
......@@ -223,9 +223,11 @@ KRESD_LOG_IO_CLOSE = re.compile(r'^\[io\].*closed by peer.*')
@contextmanager
def make_kresd(workdir, certname=None, ip='127.0.0.1', ip6='::1', forward=None, hints=None):
port = make_port(ip, ip6)
tls_port = make_port(ip, ip6)
def make_kresd(
workdir, certname=None, ip='127.0.0.1', ip6='::1', forward=None, hints=None,
port=None, tls_port=None):
port = make_port(ip, ip6) if port is None else port
tls_port = make_port(ip, ip6) if tls_port is None else tls_port
with Kresd(workdir, port, tls_port, ip, ip6, certname, forward=forward, hints=hints) as kresd:
yield kresd
with open(kresd.logfile_path) as log: # display partial log for debugging
......
CC=gcc
CFLAGS_TLS=-DDEBUG -ggdb3 -O0 -lgnutls -luv
CFLAGS_TCP=-DDEBUG -ggdb3 -O0 -luv
all: tcproxy tlsproxy
tlsproxy: tls-proxy.o tlsproxy.o
$(CC) tls-proxy.o tlsproxy.o -o tlsproxy $(CFLAGS_TLS)
tls-proxy.o: tls-proxy.c tls-proxy.h array.h
$(CC) -c -o $@ $< $(CFLAGS_TLS)
tlsproxy.o: tlsproxy.c tls-proxy.h
$(CC) -c -o $@ $< $(CFLAGS_TLS)
tcproxy: tcp-proxy.o tcproxy.o
$(CC) tcp-proxy.o tcproxy.o -o tcproxy $(CFLAGS_TCP)
tcp-proxy.o: tcp-proxy.c tcp-proxy.h array.h
$(CC) -c -o $@ $< $(CFLAGS_TCP)
tcproxy.o: tcproxy.c tcp-proxy.h
$(CC) -c -o $@ $< $(CFLAGS_TCP)
clean:
rm -f tcp-proxy.o tcproxy.o tcproxy tls-proxy.o tlsproxy.o tlsproxy
.PHONY: all clean
/* Copyright (C) 2015-2017 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 <https://www.gnu.org/licenses/>.
*/
/**
*
* @file array.h
* @brief A set of simple macros to make working with dynamic arrays easier.
*
* @note The C has no generics, so it is implemented mostly using macros.
* Be aware of that, as direct usage of the macros in the evaluating macros
* may lead to different expectations:
*
* @code{.c}
* MIN(array_push(arr, val), other)
* @endcode
*
* May evaluate the code twice, leading to unexpected behaviour.
* This is a price to pay for the absence of proper generics.
*
* # Example usage:
*
* @code{.c}
* array_t(const char*) arr;
* array_init(arr);
*
* // Reserve memory in advance
* if (array_reserve(arr, 2) < 0) {
* return ENOMEM;
* }
*
* // Already reserved, cannot fail
* array_push(arr, "princess");
* array_push(arr, "leia");
*
* // Not reserved, may fail
* if (array_push(arr, "han") < 0) {
* return ENOMEM;
* }
*
* // It does not hide what it really is
* for (size_t i = 0; i < arr.len; ++i) {
* printf("%s\n", arr.at[i]);
* }
*
* // Random delete
* array_del(arr, 0);
* @endcode
* \addtogroup generics
* @{
*/
#pragma once
#include <stdlib.h>
/** Simplified Qt containers growth strategy. */
static inline size_t array_next_count(size_t want)
{
if (want < 2048) {
return (want < 20) ? want + 4 : want * 2;
} else {
return want + 2048;
}
}
/** @internal Incremental memory reservation */
static inline int array_std_reserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have)
{
if (*have >= want) {
return 0;
}
/* Simplified Qt containers growth strategy */
size_t next_size = array_next_count(want);
void *mem_new = realloc(*mem, next_size * elm_size);
if (mem_new != NULL) {
*mem = mem_new;
*have = next_size;
return 0;
}
return -1;
}
/** @internal Wrapper for stdlib free. */
static inline void array_std_free(void *baton, void *p)
{
free(p);
}
/** Declare an array structure. */
#define array_t(type) struct {type * at; size_t len; size_t cap; }
/** Zero-initialize the array. */
#define array_init(array) ((array).at = NULL, (array).len = (array).cap = 0)
/** Free and zero-initialize the array (plain malloc/free). */
#define array_clear(array) \
array_clear_mm(array, array_std_free, NULL)
/** Make the array empty and free pointed-to memory.
* Mempool usage: pass mm_free and a knot_mm_t* . */
#define array_clear_mm(array, free, baton) \
(free)((baton), (array).at), array_init(array)
/** Reserve capacity for at least n elements.
* @return 0 if success, <0 on failure */
#define array_reserve(array, n) \
array_reserve_mm(array, n, array_std_reserve, NULL)
/** Reserve capacity for at least n elements.
* Mempool usage: pass kr_memreserve and a knot_mm_t* .
* @return 0 if success, <0 on failure */
#define array_reserve_mm(array, n, reserve, baton) \
(reserve)((baton), (char **) &(array).at, sizeof((array).at[0]), (n), &(array).cap)
/**
* Push value at the end of the array, resize it if necessary.
* Mempool usage: pass kr_memreserve and a knot_mm_t* .
* @note May fail if the capacity is not reserved.
* @return element index on success, <0 on failure
*/
#define array_push_mm(array, val, reserve, baton) \
(int)((array).len < (array).cap ? ((array).at[(array).len] = val, (array).len++) \
: (array_reserve_mm(array, ((array).cap + 1), reserve, baton) < 0 ? -1 \
: ((array).at[(array).len] = val, (array).len++)))
/**
* Push value at the end of the array, resize it if necessary (plain malloc/free).
* @note May fail if the capacity is not reserved.
* @return element index on success, <0 on failure
*/
#define array_push(array, val) \
array_push_mm(array, val, array_std_reserve, NULL)
/**
* Pop value from the end of the array.
*/
#define array_pop(array) \
(array).len -= 1
/**
* Remove value at given index.
* @return 0 on success, <0 on failure
*/
#define array_del(array, i) \
(int)((i) < (array).len ? ((array).len -= 1,(array).at[i] = (array).at[(array).len], 0) : -1)
/**
* Return last element of the array.
* @warning Undefined if the array is empty.
*/
#define array_tail(array) \
(array).at[(array).len - 1]
/** @} */
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <uv.h>
#include "array.h"
struct buf {
char buf[16 * 1024];
size_t size;
};
enum peer_state {
STATE_NOT_CONNECTED,
STATE_LISTENING,
STATE_CONNECTED,
STATE_CONNECT_IN_PROGRESS,
STATE_CLOSING_IN_PROGRESS
};
struct proxy_ctx {
uv_loop_t *loop;
uv_tcp_t server;
uv_tcp_t client;
uv_tcp_t upstream;
struct sockaddr_storage server_addr;
struct sockaddr_storage upstream_addr;
int server_state;
int client_state;
int upstream_state;
array_t(struct buf *) buffer_pool;
array_t(struct buf *) upstream_pending;
};
static void read_from_upstream_cb(uv_stream_t *upstream, ssize_t nread, const uv_buf_t *buf);
static void read_from_client_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf);
static struct buf *borrow_io_buffer(struct proxy_ctx *proxy)
{
struct buf *buf = NULL;
if (proxy->buffer_pool.len > 0) {
buf = array_tail(proxy->buffer_pool);
array_pop(proxy->buffer_pool);
} else {
buf = calloc(1, sizeof (struct buf));
}
return buf;
}
static void release_io_buffer(struct proxy_ctx *proxy, struct buf *buf)
{
if (!buf) {
return;
}
if (proxy->buffer_pool.len < 1000) {
buf->size = 0;
array_push(proxy->buffer_pool, buf);
} else {
free(buf);
}
}
static void push_to_upstream_pending(struct proxy_ctx *proxy, const char *buf, size_t size)
{
while (size > 0) {
struct buf *b = borrow_io_buffer(proxy);
b->size = size <= sizeof(b->buf) ? size : sizeof(b->buf);
memcpy(b->buf, buf, b->size);
array_push(proxy->upstream_pending, b);
size -= b->size;
}
}
static struct buf *get_first_upstream_pending(struct proxy_ctx *proxy)
{
struct buf *buf = NULL;
if (proxy->upstream_pending.len > 0) {
buf = proxy->upstream_pending.at[0];
}
return buf;
}
static void remove_first_upstream_pending(struct proxy_ctx *proxy)
{
for (int i = 1; i < proxy->upstream_pending.len; ++i) {
proxy->upstream_pending.at[i - 1] = proxy->upstream_pending.at[i];
}
if (proxy->upstream_pending.len > 0) {
proxy->upstream_pending.len -= 1;
}
}
static void clear_upstream_pending(struct proxy_ctx *proxy)
{
for (int i = 1; i < proxy->upstream_pending.len; ++i) {
struct buf *b = proxy->upstream_pending.at[i];
release_io_buffer(proxy, b);
}
proxy->upstream_pending.len = 0;
}
static void clear_buffer_pool(struct proxy_ctx *proxy)
{
for (int i = 1; i < proxy->buffer_pool.len; ++i) {
struct buf *b = proxy->buffer_pool.at[i];
free(b);
}
proxy->buffer_pool.len = 0;
}
static void alloc_uv_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
static void on_client_close(uv_handle_t *handle)
{
struct proxy_ctx *proxy = (struct proxy_ctx *)handle->loop->data;
proxy->client_state = STATE_NOT_CONNECTED;
}
static void on_upstream_close(uv_handle_t *handle)
{
struct proxy_ctx *proxy = (struct proxy_ctx *)handle->loop->data;
proxy->upstream_state = STATE_NOT_CONNECTED;
}
static void write_to_client_cb(uv_write_t *req, int status)
{
struct proxy_ctx *proxy = (struct proxy_ctx *)req->handle->loop->data;
free(req);
if (status) {
fprintf(stderr, "error writing to client: %s\n", uv_strerror(status));
clear_upstream_pending(proxy);
proxy->client_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*)&proxy->client, on_client_close);
}
}
static void write_to_upstream_cb(uv_write_t *req, int status)
{
struct proxy_ctx *proxy = (struct proxy_ctx *)req->handle->loop->data;
free(req);
if (status) {
fprintf(stderr, "error writing to upstream: %s\n", uv_strerror(status));
clear_upstream_pending(proxy);
proxy->upstream_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*)&proxy->upstream, on_upstream_close);
return;
}
if (proxy->upstream_pending.len > 0) {
struct buf *buf = get_first_upstream_pending(proxy);
remove_first_upstream_pending(proxy);
release_io_buffer(proxy, buf);
if (proxy->upstream_state == STATE_CONNECTED &&
proxy->upstream_pending.len > 0) {
buf = get_first_upstream_pending(proxy);
/* TODO avoid allocation */
uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));
uv_buf_t wrbuf = uv_buf_init(buf->buf, buf->size);
uv_write(req, (uv_stream_t *)&proxy->upstream, &wrbuf, 1, write_to_upstream_cb);
}
}
}
static void on_client_connection(uv_stream_t *server, int status)
{
if (status < 0) {
fprintf(stderr, "incoming connection error: %s\n", uv_strerror(status));
return;
}
fprintf(stdout, "incoming connection\n");
struct proxy_ctx *proxy = (struct proxy_ctx *)server->loop->data;
if (proxy->client_state != STATE_NOT_CONNECTED) {
fprintf(stderr, "client already connected, ignoring\n");
return;
}
uv_tcp_init(proxy->loop, &proxy->client);
proxy->client_state = STATE_CONNECTED;
if (uv_accept(server, (uv_stream_t*)&proxy->client) == 0) {
uv_read_start((uv_stream_t*)&proxy->client, alloc_uv_buffer, read_from_client_cb);
} else {
proxy->client_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*)&proxy->client, on_client_close);
}
}
static void on_connect_to_upstream(uv_connect_t *req, int status)
{
struct proxy_ctx *proxy = (struct proxy_ctx *)req->handle->loop->data;
free(req);
if (status < 0) {
fprintf(stderr, "error connecting to upstream: %s\n", uv_strerror(status));
clear_upstream_pending(proxy);
proxy->upstream_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*)&proxy->upstream, on_upstream_close);
return;
}
proxy->upstream_state = STATE_CONNECTED;
uv_read_start((uv_stream_t*)&proxy->upstream, alloc_uv_buffer, read_from_upstream_cb);
if (proxy->upstream_pending.len > 0) {
struct buf *buf = get_first_upstream_pending(proxy);
/* TODO avoid allocation */
uv_write_t *wreq = (uv_write_t *) malloc(sizeof(uv_write_t));
uv_buf_t wrbuf = uv_buf_init(buf->buf, buf->size);
uv_write(wreq, (uv_stream_t *)&proxy->upstream, &wrbuf, 1, write_to_upstream_cb);
}
}
static void read_from_client_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
{
if (nread == 0) {
return;
}
struct proxy_ctx *proxy = (struct proxy_ctx *)client->loop->data;
if (nread < 0) {
if (nread != UV_EOF) {
fprintf(stderr, "error reading from client: %s\n", uv_err_name(nread));
}
if (proxy->client_state == STATE_CONNECTED) {
proxy->client_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*) client, on_client_close);
}
return;
}
if (proxy->upstream_state == STATE_CONNECTED) {
if (proxy->upstream_pending.len > 0) {
push_to_upstream_pending(proxy, buf->base, nread);
} else {
/* TODO avoid allocation */
uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));
uv_buf_t wrbuf = uv_buf_init(buf->base, nread);
uv_write(req, (uv_stream_t *)&proxy->upstream, &wrbuf, 1, write_to_upstream_cb);
}
} else if (proxy->upstream_state == STATE_NOT_CONNECTED) {
/* TODO avoid allocation */
uv_tcp_init(proxy->loop, &proxy->upstream);
uv_connect_t *conn = (uv_connect_t *) malloc(sizeof(uv_connect_t));
proxy->upstream_state = STATE_CONNECT_IN_PROGRESS;
uv_tcp_connect(conn, &proxy->upstream, (struct sockaddr *)&proxy->upstream_addr,
on_connect_to_upstream);
push_to_upstream_pending(proxy, buf->base, nread);
} else if (proxy->upstream_state == STATE_CONNECT_IN_PROGRESS) {
push_to_upstream_pending(proxy, buf->base, nread);
}
}
static void read_from_upstream_cb(uv_stream_t *upstream, ssize_t nread, const uv_buf_t *buf)
{
if (nread == 0) {
return;
}
struct proxy_ctx *proxy = (struct proxy_ctx *)upstream->loop->data;
if (nread < 0) {
if (nread != UV_EOF) {
fprintf(stderr, "error reading from upstream: %s\n", uv_err_name(nread));
}
clear_upstream_pending(proxy);
if (proxy->upstream_state == STATE_CONNECTED) {
proxy->upstream_state = STATE_CLOSING_IN_PROGRESS;
uv_close((uv_handle_t*)&proxy->upstream, on_upstream_close);
}
return;
}
if (proxy->client_state == STATE_CONNECTED) {
/* TODO Avoid allocation */
uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));
uv_buf_t wrbuf = uv_buf_init(buf->base, nread);
uv_write(req, (uv_stream_t *)&proxy->client, &wrbuf, 1, write_to_client_cb);
}
}
struct proxy_ctx *proxy_allocate()
{
return malloc(sizeof(struct proxy_ctx));
}
int proxy_init(struct proxy_ctx *proxy,
const char *server_addr, int server_port,
const char *upstream_addr, int upstream_port)
{
proxy->loop = uv_default_loop();
uv_tcp_init(proxy->loop, &proxy->server);
int res = uv_ip4_addr(server_addr, server_port, (struct sockaddr_in *)&proxy->server_addr);
if (res != 0) {
return res;
}
res = uv_ip4_addr(upstream_addr, upstream_port, (struct sockaddr_in *)&proxy->upstream_addr);
if (res != 0) {
return res;
}
array_init(proxy->buffer_pool);
array_init(proxy->upstream_pending);
proxy->server_state = STATE_NOT_CONNECTED;
proxy->client_state = STATE_NOT_CONNECTED;
proxy->upstream_state = STATE_NOT_CONNECTED;
proxy->loop->data = proxy;
return 0;
}
void proxy_free(struct proxy_ctx *proxy)
{
if (!proxy) {
return;
}
clear_upstream_pending(proxy);
clear_buffer_pool(proxy);
/* TODO correctly close all the uv_tcp_t */
free(proxy);
}
int proxy_start_listen(struct proxy_ctx *proxy)
{
uv_tcp_bind(&proxy->server, (const struct sockaddr*)&proxy->server_addr, 0);
int ret = uv_listen((uv_stream_t*)&proxy->server, 128, on_client_connection);
if (ret == 0) {
proxy->server_state = STATE_LISTENING;
}
return ret;
}
int proxy_run(struct proxy_ctx *proxy)
{
return uv_run(proxy->loop, UV_RUN_DEFAULT);
}
#pragma once
struct proxy_ctx;
struct proxy_ctx *proxy_allocate();
void proxy_free(struct proxy_ctx *proxy);
int proxy_init(struct proxy_ctx *proxy,
const char *server_addr, int server_port,
const char *upstream_addr, int upstream_port);
int proxy_start_listen(struct proxy_ctx *proxy);
int proxy_run(struct proxy_ctx *proxy);
#include <stdio.h>
#include "tcp-proxy.h"
int main()
{
struct proxy_ctx *proxy = proxy_allocate();
if (!proxy) {
fprintf(stderr, "can't allocate proxy structure\n");
return 1;
}
int res = proxy_init(proxy, "127.0.0.1", 54000, "127.0.0.1", 53001);
if (res) {
fprintf(stderr, "can't initialize proxy by given addresses\n");
return res;
}
res = proxy_start_listen(proxy);
if (res) {
fprintf(stderr, "error starting listen, error code: %i\n", res);
return res;
}
res = proxy_run(proxy);
proxy_free(proxy);
return res;
}
This diff is collapsed.
#pragma once
struct tls_proxy_ctx;
struct tls_proxy_ctx *tls_proxy_allocate();
void tls_proxy_free(struct tls_proxy_ctx *proxy);
int tls_proxy_init(struct tls_proxy_ctx *proxy,
const char *server_addr, int server_port,
const char *upstream_addr, int upstream_port,
const char *cert_file, const char *key_file);
int tls_proxy_start_listen(struct tls_proxy_ctx *proxy);
int tls_proxy_run(struct tls_proxy_ctx *proxy);
#include <stdio.h>
#include "tls-proxy.h"
#include <gnutls/gnutls.h>
int main()
{
struct tls_proxy_ctx *proxy = tls_proxy_allocate();
if (!proxy) {
fprintf(stderr, "can't allocate tls_proxy structure\n");
return 1;
}
int res = tls_proxy_init(proxy,
"127.0.0.1", 53921, /* Address to listen */
"127.0.0.1", 53910, /* Upstream address */
"../certs/tt.cert.pem",
"../certs/tt.key.pem");
if (res) {
fprintf(stderr, "can't initialize tls_proxy structure\n");
return res;
}
res = tls_proxy_start_listen(proxy);
if (res) {
fprintf(stderr, "error starting listen, error code: %i\n", res);
return res;
}
fprintf(stdout, "started...\n");
res = tls_proxy_run(proxy);
tls_proxy_free(proxy);
return res;
}
......@@ -32,7 +32,7 @@ hints['{{ name }}'] = '{{ ip }}'
policy.add(policy.all(
{% if kresd.forward.proto == 'tls' %}
policy.TLS_FORWARD({
{"{{ kresd.forward.ip }}@{{ kresd.forward.port }}", insecure=true}})
{"{{ kresd.forward.ip }}@{{ kresd.forward.port }}", hostname='{{ kresd.forward.hostname}}', ca_file='{{ kresd.forward.ca_file }}'}})
{% endif %}
))
{% endif %}
......
"""TLS rehandshake test
Test utilizes rehandshake/tls-proxy, which forwards queries to configured
resolver, but when it sends the response back to the query source, it
performs a rehandshake after every byte sent.
It is expected the answer will be received by the source kresd instance
and sent back to the client (this test).
Make sure to run `make all` in `rehandshake/` to compile the proxy.
"""
import os
import subprocess
import time
import pytest
from kresd import CERTS_DIR, Forward, make_kresd, PYTESTS_DIR
import utils
REHANDSHAKE_PROXY = os.path.join(PYTESTS_DIR, 'rehandshake', 'tlsproxy')
@pytest.mark.skipif(not os.path.exists(REHANDSHAKE_PROXY),
reason="tlsproxy not found (did you compile it?)")
def test_rehandshake(tmpdir):
def resolve_hint(sock, qname):
buff, msgid = utils.get_msgbuff(qname)
sock.sendall(buff)
answer = utils.receive_parse_answer(sock)
assert answer.id == msgid
assert answer.answer[0][0].address == '127.0.0.1'
hints = {
'0.foo.': '127.0.0.1',
'1.foo.': '127.0.0.1',
'2.foo.': '127.0.0.1',
'3.foo.': '127.0.0.1',
}
# run forward target instance
workdir = os.path.join(str(tmpdir), 'kresd_fwd_target')
os.makedirs(workdir)
with make_kresd(workdir, hints=hints, port=53910) as kresd_fwd_target:
sock = kresd_fwd_target.ip_tls_socket()