Commit 942cccad authored by Dustin Lundquist's avatar Dustin Lundquist

Merge pull request #67 from dlundquist/nonblocking-connect

Non-blocking connect
parents cef0d4c4 c5df50e4
......@@ -4,7 +4,7 @@ compiler:
- gcc
install:
- sudo apt-get update
- DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libev-dev libpcre3-dev cdbs dh-autoreconf valgrind apache2-utils rpm
- DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libev-dev libpcre3-dev libudns-dev cdbs dh-autoreconf valgrind apache2-utils rpm
- mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
- ./autogen.sh
script:
......
......@@ -30,7 +30,7 @@ For Debian or Fedora based Linux distributions see building packages below.
**Prerequisites**
+ Autotools (autoconf, automake and libtool)
+ libev4 and libpcre development headers
+ libev4, libpcre and libudns development headers
+ Perl and cURL for test suite
**Install**
......@@ -44,7 +44,7 @@ This is the preferred installation method on recent Debian based distributions:
1. Install required packages
sudo apt-get install dpkg-dev cdbs debhelper dh-autoreconf libev-dev libpcre3-dev pkg-config
sudo apt-get install dpkg-dev cdbs debhelper dh-autoreconf libev-dev libpcre3-dev libudns-dev pkg-config
2. Build a Debian package
......
* Non-blocking connect() to backend server
* Non-blocking DNS resolution
** Allow nameservers to be specified in configuration file
* Allow nameservers to be specified in configuration file
* Allow source address for connection to backend to be specified
* Reimplement configuration reloading
* Improved table backend lookup, currently this is a linear search
......
......@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.60])
AC_INIT([sniproxy], [0.1])
AC_INIT([sniproxy], [0.3])
AC_CONFIG_SRCDIR([src/sniproxy.c])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([subdir-objects])
......@@ -35,6 +35,13 @@ PKG_CHECK_MODULES([LIBPCRE], [libpcre],,
fi
])
PKG_CHECK_MODULES([LIBUDNS], [libudns],,
[AC_LIB_HAVE_LINKFLAGS(udns,, [#include <udns.h>], [dns_init(0, 0);])
if test x$ac_cv_libudns = xyes; then
AC_SUBST([LIBUDNS_LIBS], [$LIBUDNS])
fi
])
PKG_CHECK_MODULES([LIBRT], [librt],,
[AC_LIB_HAVE_LINKFLAGS(rt,, [#include <time.h>], [clock_gettime(CLOCK_MONOTONIC, NULL);])
if test x$ac_cv_librt = xyes; then
......
sniproxy (0.3-1) unstable; urgency=medium
* Nonblocking connect and DNS resolution
-- Dustin Lundquist <dustin@null-ptr.net> Tue, 08 Apr 2014 17:03:37 -0700
sniproxy (0.2) unstable; urgency=low
* Moving pidfile
......
......@@ -2,7 +2,7 @@ Source: sniproxy
Section: unknown
Priority: extra
Maintainer: Andreas Loibl <andreas@andreas-loibl.de>
Build-Depends: cdbs, debhelper (>= 8.0.0), dh-autoreconf, autotools-dev, pkg-config, libev-dev (>= 4.0), libpcre3-dev
Build-Depends: cdbs, debhelper (>= 8.0.0), dh-autoreconf, autotools-dev, pkg-config, libev-dev (>= 4.0), libpcre3-dev, libudns-dev
Standards-Version: 3.9.3
Vcs-Git: https://github.com/dlundquist/HTTPS-SNI-Proxy.git
Vcs-Browser: https://github.com/dlundquist/HTTPS-SNI-Proxy
......
Name: sniproxy
Version: 0.1
Version: 0.3
Release: 1%{?dist}
Summary: Transparent TLS proxy
......@@ -9,7 +9,7 @@ URL: https://github.com/dlundquist/sniproxy
Source0: %{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires: autoconf, automake, curl, libev-devel, pcre-devel, perl
BuildRequires: autoconf, automake, curl, libev-devel, pcre-devel, perl, udns-devel
%description
Proxies incoming HTTP and TLS connections based on the hostname contained in
......
AM_CPPFLAGS = $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBRT_LIBS)
AM_CPPFLAGS = $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBRT_LIBS) $(LIBUDNS_LIBS)
sbin_PROGRAMS = sniproxy
......@@ -22,7 +22,9 @@ sniproxy_SOURCES = address.c \
listener.h \
logger.c \
logger.h \
protocol.h \
protocol.h \
resolv.c \
resolv.h \
server.c \
server.h \
sniproxy.c \
......@@ -32,4 +34,4 @@ sniproxy_SOURCES = address.c \
tls.c \
tls.h
sniproxy_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBRT_LIBS)
sniproxy_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBRT_LIBS) $(LIBUDNS_LIBS)
......@@ -67,6 +67,12 @@ accept_backend_arg(struct Backend *backend, char *arg) {
err("invalid address: %s\n", arg);
return -1;
}
#ifndef HAVE_LIBUDNS
if (!address_is_sockaddr(backend->address)) {
fprintf(stderr, "Only socket address backends are permitted when compiled with libudns\n");
return -1;
}
#endif
} else if (address_port(backend->address) == 0 && is_numeric(arg)) {
address_set_port(backend->address, atoi(arg));
} else {
......@@ -111,7 +117,7 @@ lookup_backend(const struct Backend_head *head, const char *hostname) {
struct Backend *iter;
if (hostname == NULL)
hostname = "";
return NULL;
STAILQ_FOREACH(iter, head, entries)
if (pcre_exec(iter->hostname_re, NULL,
......
This diff is collapsed.
......@@ -37,7 +37,9 @@ struct Connection {
enum State {
NEW, /* Before successful accept */
ACCEPTED, /* Newly accepted client connection */
CONNECTED, /* Parsed client hello and connected to server */
PARSED, /* Parsed initial request and extracted hostname */
RESOLVED, /* Server socket address resolved */
CONNECTED, /* Connected to server */
SERVER_CLOSED, /* Client closed socket */
CLIENT_CLOSED, /* Server closed socket */
CLOSED /* Both sockets closed */
......
......@@ -30,6 +30,7 @@
#include <stddef.h> /* offsetof */
#include <strings.h> /* strcasecmp() */
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/queue.h>
#include <sys/types.h>
......@@ -261,6 +262,10 @@ init_listener(struct Listener *listener, const struct Table_head *tables) {
return -4;
}
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
struct ev_io *listener_watcher = &listener->watcher;
ev_io_init(listener_watcher, accept_cb, sockfd, EV_READ);
listener->watcher.data = listener;
......
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef HAVE_LIBUDNS
#include <udns.h>
#endif
#include "resolv.h"
#include "address.h"
#include "logger.h"
#ifndef HAVE_LIBUDNS
int
resolv_init(struct ev_loop *loop) {
return 0;
}
void
resolv_shutdown(struct ev_loop *loop) {
}
void
resolv_query(const char *hostname, void (*client_cb)(struct Address *, void *), void *client_cb_data) {
}
#else
struct cb_data {
void (*client_cb)(struct Address *, void *);
void *client_cb_data;
};
static struct ev_io resolv_io_watcher;
static struct ev_timer resolv_timeout_watcher;
static void resolv_sock_cb(struct ev_loop *, struct ev_io *, int);
static void resolv_timeout_cb(struct ev_loop *, struct ev_timer *, int);
static void dns_query_cb(struct dns_ctx *, struct dns_rr_a4 *, void *);
static void dns_timer_setup_cb(struct dns_ctx *, int, void *);
int
resolv_init(struct ev_loop *loop) {
struct dns_ctx *ctx = &dns_defctx;
dns_init(ctx, 1);
int sockfd = dns_sock(ctx);
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
ev_io_init(&resolv_io_watcher, resolv_sock_cb, sockfd, EV_READ);
resolv_io_watcher.data = ctx;
ev_io_start(loop, &resolv_io_watcher);
ev_timer_init(&resolv_timeout_watcher, resolv_timeout_cb, 0.0, 0.0);
resolv_timeout_watcher.data = ctx;
dns_set_tmcbck(ctx, dns_timer_setup_cb, loop);
return sockfd;
}
void
resolv_shutdown(struct ev_loop * loop) {
struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
ev_io_stop(loop, &resolv_io_watcher);
if (ev_is_active(&resolv_timeout_watcher))
ev_timer_stop(loop, &resolv_timeout_watcher);
dns_close(ctx);
}
void
resolv_query(const char *hostname, void (*client_cb)(struct Address *, void *), void *client_cb_data) {
struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
struct cb_data *cb_data = malloc(sizeof(struct cb_data));
if (cb_data == NULL) {
err("Failed to allocate memory for DNS query callback data.");
return;
}
cb_data->client_cb = client_cb;
cb_data->client_cb_data = client_cb_data;
/* Just resolving A records for now */
struct dns_query *q = dns_submit_a4(ctx,
hostname, 0,
dns_query_cb, cb_data);
if (q == NULL) {
err("Failed to submit DNS query: %s", dns_strerror(dns_status(ctx)));
return;
}
}
static void
resolv_sock_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
struct dns_ctx *ctx = (struct dns_ctx *)w->data;
if (revents & EV_READ)
dns_ioevent(ctx, ev_now(loop));
}
static void
dns_query_cb(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data) {
struct cb_data *cb_data = (struct cb_data *)data;
struct Address *address = NULL;
if (result == NULL) {
info("resolv: %s\n", dns_strerror(dns_status(ctx)));
} else if (result->dnsa4_nrr > 0) {
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = result->dnsa4_addr[0],
};
address = new_address_sa((struct sockaddr *)&sa, sizeof(sa));
if (address == NULL)
err("Failed to allocate memory for DNS query result address");
}
cb_data->client_cb(address, cb_data->client_cb_data);
free(cb_data);
free(address);
}
static void
resolv_timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
struct dns_ctx *ctx = (struct dns_ctx *)w->data;
dns_timeouts(ctx, 30, ev_now(loop));
}
static void
dns_timer_setup_cb(struct dns_ctx *ctx, int timeout, void *data) {
struct ev_loop *loop = (struct ev_loop *)data;
if (ev_is_active(&resolv_timeout_watcher))
ev_timer_stop(loop, &resolv_timeout_watcher);
if (ctx != NULL && timeout >= 0) {
ev_timer_set(&resolv_timeout_watcher, timeout, 0.0);
ev_timer_start(loop, &resolv_timeout_watcher);
}
}
#endif
/*
* Copyright (c) 2014, Dustin Lundquist <dustin@null-ptr.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef RESOLV_H
#define RESOLV_H
#include <ev.h>
#include "address.h"
int resolv_init(struct ev_loop *);
void resolv_query(const char *, void(*cb)(struct Address *, void *), void *);
void resolv_shutdown(struct ev_loop *);
#endif
......@@ -31,6 +31,7 @@
#include "server.h"
#include "listener.h"
#include "connection.h"
#include "resolv.h"
static void signal_cb(struct ev_loop *, struct ev_signal *, int revents);
......@@ -61,11 +62,13 @@ init_server(struct Config *c) {
void
run_server() {
resolv_init(EV_DEFAULT);
init_connections();
ev_run(EV_DEFAULT, 0);
free_connections(EV_DEFAULT);
resolv_shutdown(EV_DEFAULT);
}
static void
......
......@@ -3,6 +3,7 @@ buffer_test
cfg_tokenizer_test
config_test
http_test
resolv_test
tls_test
*.log
*.trs
AM_CPPFLAGS = -I$(top_srcdir)/src -g $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBRT_LIBS)
AM_CPPFLAGS = -I$(top_srcdir)/src -g $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBRT_CFLAGS) $(LIBUDNS_LIBS)
TESTS = http_test \
tls_test \
buffer_test \
address_test \
cfg_tokenizer_test \
config_test \
functional_test \
bad_request_test \
slow_client_test \
connection_reset_test
tls_test \
buffer_test \
address_test \
cfg_tokenizer_test \
config_test \
resolv_test \
functional_test \
bad_request_test \
slow_client_test \
connection_reset_test
check_PROGRAMS = http_test \
tls_test \
buffer_test \
cfg_tokenizer_test \
address_test \
resolv_test \
config_test
http_test_SOURCES = http_test.c \
......@@ -47,7 +49,17 @@ config_test_SOURCES = config_test.c \
../src/connection.c \
../src/buffer.c \
../src/logger.c \
../src/resolv.c \
../src/resolv.h \
../src/tls.c \
../src/http.c
config_test_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBRT_LIBS)
config_test_LDADD = $(LIBEV_LIBS) $(LIBPCRE_LIBS) $(LIBRT_LIBS) $(LIBUDNS_LIBS)
resolv_test_SOURCES = resolv_test.c \
../src/resolv.c \
../src/address.c \
../src/logger.c
resolv_test_LDADD = $(LIBEV_LIBS) $(LIBUDNS_LIBS)
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
......@@ -142,6 +143,8 @@ static void test4() {
buffer_read(buffer, read_fd);
buffer_write(buffer, write_fd);
}
free_buffer(buffer);
}
int main() {
......
#include <stdio.h>
#include <ev.h>
#include <assert.h>
#include "resolv.h"
#include "address.h"
static int query_count = 0;
static void query_cb(struct Address *result, void *data) {
int *query_count = (int *)data;
char ip_buf[128];
if (result != NULL &&
address_is_sockaddr(result) &&
display_address(result, ip_buf, sizeof(ip_buf))) {
fprintf(stderr, "query resolved to %s\n", ip_buf);
query_count++;
}
}
static void
test_init_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
resolv_query("localhost", query_cb, &query_count);
}
static void
timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
ev_break(loop, EVBREAK_ALL);
}
int main() {
struct ev_loop *loop = EV_DEFAULT;
struct ev_timer timeout_watcher;
struct ev_timer init_watcher;
resolv_init(loop);
ev_timer_init(&init_watcher, &test_init_cb, 0.0, 0.0);
ev_timer_start(loop, &init_watcher);
ev_timer_init(&timeout_watcher, &timeout_cb, 5.0, 0.0);
ev_timer_start(loop, &timeout_watcher);
ev_run(loop, 0);
return 0;
}
#!/usr/bin/perl
use strict;
use warnings;
use TestUtils;
use TestHTTPD;
use File::Temp;
my $test_domains = [
'example.com',
'example.net'
];
sub make_wildcard_config($) {
my $proxy_port = shift;
my ($fh, $filename) = File::Temp::tempfile();
# Write out a test config file
print $fh <<END;
# Minimal test configuration
listen 127.0.0.1 $proxy_port {
proto http
}
table {
.* *:80
}
END
close ($fh);
return $filename;
}
sub proxy {
my $config = shift;
exec(@_, '../src/sniproxy', '-f', '-c', $config);
}
sub worker($$) {
my $port = shift;
my $requests = shift;
for (my $i = 0; $i < $requests; $i++) {
my $hostname = $test_domains->[$i % scalar(@$test_domains)];
system('curl',
'-s', '-S',
'-H', "Host: $hostname",
'-o', '/dev/null',
"http://localhost:$port/");
if ($? == -1) {
die "failed to execute: $!\n";
} elsif ($? & 127) {
printf STDERR "child died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without';
exit 255;
} elsif ($? >> 8) {
exit $? >> 8;
}
}
# Success
exit 0;
}
sub main {
my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080;
my $workers = $ENV{WORKERS} || 3;
my $iterations = $ENV{ITERATIONS} || 3;
my $config = make_wildcard_config($proxy_port);
my $proxy_pid = start_child('server', \&proxy, $config, @ARGV);
# Wait for proxy to load and parse config
wait_for_port($proxy_port);
for (my $i = 0; $i < $workers; $i++) {
start_child('worker', \&worker, $proxy_port, $iterations);
}
# Wait for all our children to finish
wait_for_type('worker');
# Give the proxy a second to flush buffers and close server connections
sleep 1;
# For troubleshooting connections stuck in CLOSE_WAIT state
#kill 10, $proxy_pid;
#system("netstat -ptn | grep $proxy_pid\/sniproxy");
# For troubleshooting 100% CPU usage
#system("top -n 1 -p $proxy_pid -b");
# Orderly shutdown of the server
kill 15, $proxy_pid;
sleep 1;
# Delete our test configuration
unlink($config);
# Kill off any remaining children
reap_children();
}
main();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment