...
 
Commits (74)
......@@ -22,6 +22,7 @@ script:
- bash -c "cd tests && ./wildcard_test valgrind --leak-check=full --error-exitcode=1"
- bash -c "cd tests && ./bind_source_test valgrind --leak-check=full --error-exitcode=1"
- bash -c "cd tests && ./reload_test valgrind --leak-check=full --error-exitcode=1"
- bash -c "cd tests && ./proxy_header_test valgrind --leak-check=full --error-exitcode=1"
- echo "Testing Debian package build"
- dpkg-buildpackage -us -uc
- echo "Testing RPM package build"
......
2018-12-05 Dustin Lundquist <dustin@null-ptr.net>
0.6.0 Release
* PROXY v1 protocol support
* SO_REUSEPORT support on Linux 3.9 and later
* Listener ipv6_only directive to accept only IPv6 connections
* TCP keepalive
2017-04-26 Dustin Lundquist <dustin@null-ptr.net>
0.5.0 Release
......
......@@ -14,6 +14,8 @@ Features
+ Supports IPv4, IPv6 and Unix domain sockets for both back end servers and
listeners.
+ Supports multiple listening sockets per instance.
+ Supports HAProxy proxy protocol to propagate original source address to
backend servers.
Usage
-----
......@@ -141,19 +143,3 @@ build without UDNS, but these features will be unavailable.
UDNS uses a single UDP socket for all queries, so it is recommended you use a
local caching DNS resolver (with a single socket each DNS query is protected by
spoofing by a single 16 bit query ID, which makes it relatively easy to spoof).
UDNS is currently not available in Debian stable, but a package can be easily
built from the Debian testing or Ubuntu source packages:
mkdir udns_packaging
cd udns_packaging
wget http://archive.ubuntu.com/ubuntu/pool/universe/u/udns/udns_0.4-1.dsc
wget http://archive.ubuntu.com/ubuntu/pool/universe/u/udns/udns_0.4.orig.tar.gz
wget http://archive.ubuntu.com/ubuntu/pool/universe/u/udns/udns_0.4-1.debian.tar.gz
tar xfz udns_0.4.orig.tar.gz
cd udns-0.4/
tar xfz ../udns_0.4-1.debian.tar.gz
dpkg-buildpackage
cd ..
sudo dpkg -i libudns-dev_0.4-1_amd64.deb libudns0_0.4-1_amd64.deb
......@@ -2,10 +2,11 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.60])
AC_INIT([sniproxy], [0.5.0])
AC_INIT([sniproxy], [0.6.0])
AC_CONFIG_SRCDIR([src/sniproxy.c])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([subdir-objects])
AM_SILENT_RULES([yes])
AC_GNU_SOURCE
# Checks for programs.
......@@ -58,21 +59,24 @@ AS_IF([test "x$rfc3339_timestamps" = "xyes"],
[AC_DEFINE([RFC3339_TIMESTAMP], 1, [RFC3339 timestamps enabled])])
# Checks for header files.
AC_FUNC_ALLOCA
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stddef.h stdlib.h string.h strings.h sys/socket.h sys/time.h syslog.h unistd.h],,
AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h syslog.h unistd.h],,
AC_MSG_ERROR([required header(s) not found]))
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_PID_T
AC_TYPE_UID_T
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINT16_T
AC_TYPE_UINT8_T
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_FUNC_STRTOD
AC_CHECK_FUNCS([daemon memset socket strcasecmp strdup strerror strncasecmp],,
AC_CHECK_FUNCS([atexit daemon memset socket strcasecmp strchr strdup strerror strncasecmp strrchr strspn strtoul],,
AC_MSG_ERROR([required functions(s) not found]))
AC_CHECK_FUNCS([accept4])
......
......@@ -54,7 +54,9 @@ Specify how error messages should be handled. Messages can be either logged to
syslog using the syslog directive specifies that logs should to a given syslog
facility. Alternatively the filename directive may be specified to log to file,
these two options are mutually exclusive. The priority directive indicates what
severity of messages should be logged.
severity of messages should be logged. Accepted priorities the standard syslog
priorities, in increasing verbosity: emergency, alert, critical, error,
warning, notice, info, and debug.
.SS ACCESS_LOG
......@@ -113,6 +115,7 @@ improve performance.
.nf
listener 192.0.2.10:80 {
protocol http
reuseport yes
table http_hosts
fallback 192.0.2.100:80
bad_requests log
......@@ -122,6 +125,14 @@ listener 192.0.2.10:80 {
filename /var/log/sniproxy/http_access.log
}
}
listener [::]:80 {
protocol http
ipv6_v6only yes
table http_hosts
fallback unix:/var/run/http_fallback_unix.sock
}
.fi
.PP
......@@ -133,6 +144,18 @@ path prefixed with 'unix:'.
Protocol defines how the client request should be parsed to obtain the
requested hostname, two protocols are supported http and tls.
Reuseport directive controls if the port is opened in SO_REUSEPORT mode,
which allows to run several sniproxy instances on the same ip:port pair.
This enables us to evenly load-balance incoming connections between these instances
without the use of any external load-balancing proxy. Requires Linux kernel 3.9+.
Setting reuseport to "yes" enables this functionality.
The ipv6_v6only directive controls if listening on the IPv6 any address '::'
will accepting connections to any IPv4 address as well as an IPv6 address. This
is useful if the user wants different configurations for IPv4 and IPv6 or
wishes to handle IPv4 traffic with another server/proxy entirely. This is only
applicable to IPv6 listeners, and is ignored on other listeners.
Table specifies the name of the table used to lookup which server to forward
the connection to based on the hostname extracted from the initial client
request. If no table directive is specified the default, unnamed, table will be
......@@ -161,7 +184,7 @@ The access log configuration may be overridden on each listener.
table http_hosts {
^example\\.com$ 192.0.2.101
^example\\.net$ 192.0.2.102
^example\\.org$ 192.0.2.103
^example\\.org$ 192.0.2.103 proxy_protocol
}
.fi
.PP
......@@ -172,6 +195,10 @@ found and that server is used. The server address may be either IP, an IP and
port, a unix socket path, a hostname or '*'. If no port is specified, the port
of the listener which connection was received on will be used.
The optional proxy_protocol option will prepend a HAProxy PROXY v1 protocol
header to the proxied connection allowing supporting webservers to obtain the
source and destination IP and port of the original incoming TCP connection.
.SH "SEE ALSO"
.PP
......
Name: sniproxy
Version: 0.5.0
Version: 0.6.0
Release: 1%{?dist}
Summary: Transparent TLS and HTTP layer 4 proxy with SNI support
......@@ -46,6 +46,12 @@ rm -rf $RPM_BUILD_ROOT
%changelog
* Wed Dec 5 2018 Dustin Lundquist <dustin@null-ptr.net> 0.6.0-1
- PROXY v1 protocol support
- SO_REUSEPORT support on Linux 3.9 and later
- Listener ipv6_only directive to accept only IPv6 connections
- TCP keepalive
* Wed Apr 26 2017 Dustin Lundquist <dustin@null-ptr.net> 0.5.0-1
- Transparent proxy support
- Use accept4() on Linix
......
#!/bin/sh
VERSION=0.5.0
VERSION=0.6.0
SOURCE_DIR=$(dirname $0)
GIT_DIR=${SOURCE_DIR}/.git
......
......@@ -6,7 +6,7 @@ user nobody
group nogroup
# PID file, needs to be placed in directory writable by user
pidfile /var/tmp/sniproxy.pid
pidfile /var/run/sniproxy.pid
# The DNS resolver is required for tables configured using wildcard or hostname
# targets. If no resolver is specified, the nameserver and search domain are
......@@ -55,6 +55,9 @@ listen 80 {
proto http
table http_hosts
# Enable SO_REUSEPORT to allow multiple processess to bind to this ip:port pair
reuseport no
# Fallback server to use if we can not parse the client request
fallback localhost:8080
......@@ -81,7 +84,17 @@ listen 80 {
}
}
listen 443 {
listen [::]:443 {
proto tls
# controls if this listener will accept IPv4 connections as well on
# supported operating systems such as Linux or FreeBSD, but not OpenBSD.
ipv6_v6only on
table https_hosts
}
listen 0.0.0.0 443 {
# This listener will only accept IPv4 connections since it is bound to the
# IPv4 any address.
proto tls
table https_hosts
}
......@@ -105,7 +118,7 @@ listen unix:/var/run/proxy.sock {
table http_hosts {
example.com 192.0.2.10:8001
example.net 192.0.2.10:8002
example.org 192.0.2.10:8003
example.org 192.0.2.10:8003 proxy_protocol
# Each table entry is composed of three parts:
#
......
......@@ -24,6 +24,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> /* tolower */
......@@ -86,9 +87,10 @@ new_address(const char *hostname_or_ip) {
/* Unix socket */
memset(&sa, 0, sizeof(sa));
if (strncmp("unix:", hostname_or_ip, 5) == 0) {
/* XXX: only supporting pathname unix sockets */
((struct sockaddr_un *)&sa)->sun_family = AF_UNIX;
strncpy(((struct sockaddr_un *)&sa)->sun_path,
hostname_or_ip + 5, sizeof(sa) -
hostname_or_ip + 5, sizeof(struct sockaddr_un) -
offsetof(struct sockaddr_un, sun_path));
return new_address_sa(
......@@ -97,8 +99,9 @@ new_address(const char *hostname_or_ip) {
}
/* Trailing port */
if ((port = strrchr(hostname_or_ip, ':')) != NULL && is_numeric(port + 1)) {
len = port - hostname_or_ip;
if ((port = strrchr(hostname_or_ip, ':')) != NULL &&
is_numeric(port + 1)) {
len = (size_t)(port - hostname_or_ip);
int port_num = atoi(port + 1);
if (len < sizeof(ip_buf) && port_num >= 0 && port_num <= 65535) {
......@@ -137,8 +140,9 @@ new_address(const char *hostname_or_ip) {
/* [IPv6 address] */
memset(&sa, 0, sizeof(sa));
if (hostname_or_ip[0] == '[' && (port = strchr(hostname_or_ip, ']'))) {
len = port - hostname_or_ip - 1;
if (hostname_or_ip[0] == '[' &&
(port = strchr(hostname_or_ip, ']')) != NULL) {
len = (size_t)(port - hostname_or_ip - 1);
/* inet_pton() will not parse the IP correctly unless it is in a
* separate string.
......@@ -235,8 +239,8 @@ address_compare(const struct Address *addr_1, const struct Address *addr_2) {
if (addr_1->type > addr_2->type)
return 1;
int addr1_len = addr_1->len;
int addr2_len = addr_2->len;
size_t addr1_len = addr_1->len;
size_t addr2_len = addr_2->len;
int result = memcmp(addr_1->data, addr_2->data, MIN(addr1_len, addr2_len));
if (result == 0) { /* they match, find a tie breaker */
......@@ -371,7 +375,7 @@ display_address(const struct Address *addr, char *buffer, size_t buffer_len) {
switch (addr->type) {
case HOSTNAME:
if (addr->port != 0)
snprintf(buffer, buffer_len, "%s:%d",
snprintf(buffer, buffer_len, "%s:%" PRIu16,
addr->data,
addr->port);
else
......@@ -382,7 +386,7 @@ display_address(const struct Address *addr, char *buffer, size_t buffer_len) {
return display_sockaddr(addr->data, buffer, buffer_len);
case WILDCARD:
if (addr->port != 0)
snprintf(buffer, buffer_len, "*:%d",
snprintf(buffer, buffer_len, "*:%" PRIu16,
addr->port);
else
snprintf(buffer, buffer_len, "*");
......@@ -406,7 +410,7 @@ display_sockaddr(const void *sa, char *buffer, size_t buffer_len) {
ip, sizeof(ip));
if (((struct sockaddr_in *)sa)->sin_port != 0)
snprintf(buffer, buffer_len, "%s:%d", ip,
snprintf(buffer, buffer_len, "%s:%" PRIu16, ip,
ntohs(((struct sockaddr_in *)sa)->sin_port));
else
snprintf(buffer, buffer_len, "%s", ip);
......@@ -418,7 +422,7 @@ display_sockaddr(const void *sa, char *buffer, size_t buffer_len) {
ip, sizeof(ip));
if (((struct sockaddr_in6 *)sa)->sin6_port != 0)
snprintf(buffer, buffer_len, "[%s]:%d", ip,
snprintf(buffer, buffer_len, "[%s]:%" PRIu16, ip,
ntohs(((struct sockaddr_in6 *)sa)->sin6_port));
else
snprintf(buffer, buffer_len, "[%s]", ip);
......@@ -463,13 +467,13 @@ valid_hostname(const char *hostname) {
if (hostname[0] == '.')
return 0;
const char *label = hostname;
while (label < hostname + hostname_len) {
size_t label_len = hostname_len - (label - hostname);
const char *hostname_end = hostname + hostname_len;
for (const char *label = hostname; label < hostname_end;) {
size_t label_len = (size_t)(hostname_end - label);
char *next_dot = strchr(label, '.');
if (next_dot != NULL)
label_len = next_dot - label;
assert(label + label_len <= hostname + hostname_len);
label_len = (size_t)(next_dot - label);
assert(label + label_len <= hostname_end);
if (label_len > 63 || label_len < 1)
return 0;
......
......@@ -35,6 +35,7 @@
static void free_backend(struct Backend *);
static char *backend_config_options(const struct Backend *);
struct Backend *
......@@ -76,6 +77,9 @@ accept_backend_arg(struct Backend *backend, const char *arg) {
err("Invalid port: %s", arg);
return -1;
}
} else if (backend->use_proxy_header == 0 &&
strcasecmp(arg, "proxy_protocol") == 0) {
backend->use_proxy_header = 1;
} else {
err("Unexpected table backend argument: %s", arg);
return -1;
......@@ -136,9 +140,18 @@ void
print_backend_config(FILE *file, const struct Backend *backend) {
char address[ADDRESS_BUFFER_SIZE];
fprintf(file, "\t%s %s\n",
fprintf(file, "\t%s %s%s\n",
backend->pattern,
display_address(backend->address, address, sizeof(address)));
display_address(backend->address, address, sizeof(address)),
backend_config_options(backend));
}
static char *
backend_config_options(const struct Backend *backend) {
if (backend->use_proxy_header)
return " proxy_protocol";
else
return "";
}
void
......
......@@ -36,6 +36,7 @@ STAILQ_HEAD(Backend_head, Backend);
struct Backend {
char *pattern;
struct Address *address;
int use_proxy_header;
/* Runtime fields */
pcre *pattern_re;
......
......@@ -28,11 +28,6 @@
#include <unistd.h>
#include <string.h> /* memcpy() */
#include <errno.h> /* errno */
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
......@@ -89,6 +84,10 @@ start_binder() {
int
bind_socket(const struct sockaddr *addr, size_t addr_len) {
struct binder_request *request;
struct msghdr msg;
struct iovec iov[1];
char control_buf[64];
char data_buf[256];
if (binder_pid <= 0) {
......@@ -97,8 +96,9 @@ bind_socket(const struct sockaddr *addr, size_t addr_len) {
}
size_t request_len = sizeof(request) + addr_len;
request = alloca(request_len);
if (request_len > sizeof(data_buf))
fatal("bind_socket: request length %zu exceeds buffer", request_len);
request = (struct binder_request *)data_buf;
request->address_len = addr_len;
memcpy(&request->address, addr, addr_len);
......@@ -107,10 +107,6 @@ bind_socket(const struct sockaddr *addr, size_t addr_len) {
return -1;
}
struct msghdr msg;
struct iovec iov[1];
char control_buf[64];
char data_buf[256];
memset(&msg, 0, sizeof(msg));
iov[0].iov_base = data_buf;
iov[0].iov_len = sizeof(data_buf);
......
This diff is collapsed.
......@@ -33,16 +33,16 @@
struct Buffer {
char *buffer;
size_t size;
size_t head;
size_t len;
size_t size_mask; /* bit mask for buffer size */
size_t head; /* index of first byte of content */
size_t len; /* size of content */
ev_tstamp last_recv;
ev_tstamp last_send;
size_t tx_bytes;
size_t rx_bytes;
};
struct Buffer *new_buffer(int, struct ev_loop *);
struct Buffer *new_buffer(size_t, struct ev_loop *);
void free_buffer(struct Buffer *);
ssize_t buffer_recv(struct Buffer *, int, int, struct ev_loop *);
......@@ -55,13 +55,13 @@ size_t buffer_coalesce(struct Buffer *, const void **);
size_t buffer_pop(struct Buffer *, void *, size_t);
size_t buffer_push(struct Buffer *, const void *, size_t);
static inline size_t buffer_size(const struct Buffer *b) {
return b->size;
return b->size_mask + 1;
}
static inline size_t buffer_len(const struct Buffer *b) {
return b->len;
}
static inline size_t buffer_room(const struct Buffer *b) {
return b->size - b->len;
return buffer_size(b) - b->len;
}
#endif
......@@ -34,14 +34,13 @@ static const struct Keyword *find_keyword(const struct Keyword *, const char *);
int
parse_config(void *context, FILE *cfg, const struct Keyword *grammar) {
enum Token token;
char buffer[256];
const struct Keyword *keyword = NULL;
void *sub_context = NULL;
int result;
while ((token = next_token(cfg, buffer, sizeof(buffer))) != TOKEN_END) {
switch (token) {
for (;;) {
switch (next_token(cfg, buffer, sizeof(buffer))) {
case TOKEN_ERROR:
err("%s: tokenizer error", __func__);
return -1;
......@@ -52,14 +51,14 @@ parse_config(void *context, FILE *cfg, const struct Keyword *grammar) {
return result;
} else if ((keyword = find_keyword(grammar, buffer))) {
if (keyword->create)
if (keyword->create) {
sub_context = keyword->create();
else
if (sub_context == NULL) {
err("failed to create subcontext");
return -1;
}
} else {
sub_context = context;
if (sub_context == NULL) {
err("failed to create subcontext");
return -1;
}
/* Special case for wildcard grammars i.e. tables */
......@@ -68,7 +67,6 @@ parse_config(void *context, FILE *cfg, const struct Keyword *grammar) {
if (result <= 0)
return result;
}
} else {
err("%s: unknown keyword %s", __func__, buffer);
return -1;
......@@ -77,9 +75,15 @@ parse_config(void *context, FILE *cfg, const struct Keyword *grammar) {
case TOKEN_OBRACE:
if (keyword && sub_context && keyword->block_grammar) {
result = parse_config(sub_context, cfg,
keyword->block_grammar);
keyword->block_grammar);
if (result > 0 && keyword->finalize)
result = keyword->finalize(context, sub_context);
if (result <= 0)
return result;
keyword = NULL;
sub_context = NULL;
} else {
err("%s: block without context", __func__);
return -1;
......@@ -94,21 +98,14 @@ parse_config(void *context, FILE *cfg, const struct Keyword *grammar) {
keyword = NULL;
sub_context = NULL;
break;
case TOKEN_CBRACE:
/* Finalize the current subcontext before returning */
if (keyword && sub_context && keyword->finalize) {
result = keyword->finalize(context, sub_context);
if (result <= 0)
return result;
}
/* fall through */
case TOKEN_END:
return 1;
}
}
return 1;
}
static const struct Keyword *
......
......@@ -29,11 +29,11 @@
#include <stdio.h>
struct Keyword {
const char *keyword;
void *(*create)();
int (*parse_arg)(void *, char *);
struct Keyword *block_grammar;
int (*finalize)(void *, void *);
const char *const keyword;
void *(*const create)();
int (*const parse_arg)(void *, const char *);
const struct Keyword *const block_grammar;
int (*const finalize)(void *, void *);
};
......
This diff is collapsed.
This diff is collapsed.
......@@ -46,16 +46,18 @@ struct Connection {
} state;
struct {
struct sockaddr_storage addr;
socklen_t addr_len;
struct sockaddr_storage addr, local_addr;
socklen_t addr_len, local_addr_len;
struct ev_io watcher;
struct Buffer *buffer;
} client, server;
struct Listener *listener;
const char *hostname; /* Requested hostname */
size_t hostname_len;
size_t header_len;
struct ResolvQuery *query_handle;
ev_tstamp established_timestamp;
int use_proxy_header;
TAILQ_ENTRY(Connection) entries;
};
......
......@@ -27,30 +27,31 @@
#include <stdlib.h> /* malloc() */
#include <string.h> /* strncpy() */
#include <strings.h> /* strncasecmp() */
#include <ctype.h> /* isblank() */
#include <ctype.h> /* isblank(), isdigit() */
#include "http.h"
#include "protocol.h"
#define SERVER_NAME_LEN 256
static int parse_http_header(const char *, size_t, char **);
static int get_header(const char *, const char *, size_t, char **);
static size_t next_header(const char **, size_t *);
static const char http_503[] =
"HTTP/1.1 503 Service Temporarily Unavailable\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n"
"Backend not available";
static int parse_http_header(const char *, size_t, char **);
static int get_header(const char *, const char *, int, char **);
static int next_header(const char **, int *);
static const struct Protocol http_protocol_st = {
const struct Protocol *const http_protocol = &(struct Protocol){
.name = "http",
.default_port = 80,
.parse_packet = &parse_http_header,
.abort_message = http_503,
.abort_message_len = sizeof(http_503)
.abort_message_len = sizeof(http_503) - 1,
};
const struct Protocol *const http_protocol = &http_protocol_st;
/*
* Parses a HTTP request for the Host: header
......@@ -79,6 +80,7 @@ parse_http_header(const char* data, size_t data_len, char **hostname) {
/*
* if the user specifies the port in the request, it is included here.
* Host: example.com:80
* Host: [2001:db8::1]:8080
* so we trim off port portion
*/
for (i = result - 1; i >= 0; i--)
......@@ -86,14 +88,16 @@ parse_http_header(const char* data, size_t data_len, char **hostname) {
(*hostname)[i] = '\0';
result = i;
break;
} else if (!isdigit((*hostname)[i])) {
break;
}
return result;
}
static int
get_header(const char *header, const char *data, int data_len, char **value) {
int len, header_len;
get_header(const char *header, const char *data, size_t data_len, char **value) {
size_t len, header_len;
header_len = strlen(header);
......@@ -122,9 +126,9 @@ get_header(const char *header, const char *data, int data_len, char **value) {
return -2;
}
static int
next_header(const char **data, int *len) {
int header_len;
static size_t
next_header(const char **data, size_t *len) {
size_t header_len;
/* perhaps we can optimize this to reuse the value of header_len, rather
* than scanning twice.
......
This diff is collapsed.
......@@ -39,24 +39,28 @@ struct Listener {
const struct Protocol *protocol;
char *table_name;
struct Logger *access_log;
int transparent_proxy, log_bad_requests;
int log_bad_requests, reuseport, transparent_proxy, ipv6_v6only;
int fallback_use_proxy_header;
/* Runtime fields */
int reference_count;
struct ev_io watcher;
struct ev_timer backoff_timer;
struct Table *table;
int (*accept_cb)(struct Listener *, struct ev_loop *);
SLIST_ENTRY(Listener) entries;
};
struct Listener *new_listener();
int accept_listener_arg(struct Listener *, char *);
int accept_listener_table_name(struct Listener *, char *);
int accept_listener_fallback_address(struct Listener *, char *);
int accept_listener_source_address(struct Listener *, char *);
int accept_listener_protocol(struct Listener *, char *);
int accept_listener_bad_request_action(struct Listener *, char *);
int accept_listener_arg(struct Listener *, const char *);
int accept_listener_table_name(struct Listener *, const char *);
int accept_listener_fallback_address(struct Listener *, const char *);
int accept_listener_source_address(struct Listener *, const char *);
int accept_listener_protocol(struct Listener *, const char *);
int accept_listener_reuseport(struct Listener *, const char *);
int accept_listener_ipv6_v6only(struct Listener *, const char *);
int accept_listener_bad_request_action(struct Listener *, const char *);
void add_listener(struct Listener_head *, struct Listener *);
void init_listeners(struct Listener_head *, const struct Table_head *, struct ev_loop *);
......@@ -65,11 +69,10 @@ void remove_listener(struct Listener_head *, struct Listener *, struct ev_loop *
void free_listeners(struct Listener_head *, struct ev_loop *);
int valid_listener(const struct Listener *);
struct Address *listener_lookup_server_address(const struct Listener *,
struct LookupResult listener_lookup_server_address(const struct Listener *,
const char *, size_t);
void print_listener_config(FILE *, const struct Listener *);
void listener_ref_put(struct Listener *);
struct Listener *listener_ref_get(struct Listener *);
#endif
......@@ -47,7 +47,8 @@ void reopen_loggers();
/* Shorthand to log to global error log */
void fatal(const char *, ...)
__attribute__ ((format (printf, 1, 2)));
__attribute__ ((format (printf, 1, 2)))
__attribute__ ((noreturn));
void err(const char *, ...)
__attribute__ ((format (printf, 1, 2)));
void warn(const char *, ...)
......
......@@ -82,7 +82,7 @@ struct ResolvQuery {
};
static int default_resolv_mode = 1;
static int default_resolv_mode = 1 /* RESOLV_MODE_IPV4_ONLY */;
static struct ev_io resolv_io_watcher;
static struct ev_timer resolv_timeout_watcher;
......@@ -169,7 +169,8 @@ resolv_query(const char *hostname, int mode,
cb_data->client_cb = client_cb;
cb_data->client_free_cb = client_free_cb;
cb_data->client_cb_data = client_cb_data;
cb_data->resolv_mode = mode > 0 ? mode : default_resolv_mode;
cb_data->resolv_mode = mode != RESOLV_MODE_DEFAULT ?
mode : default_resolv_mode;
memset(cb_data->queries, 0, sizeof(cb_data->queries));
cb_data->response_count = 0;
cb_data->responses = NULL;
......@@ -242,7 +243,7 @@ dns_query_v4_cb(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data) {
info("resolv: %s\n", dns_strerror(dns_status(ctx)));
} else if (result->dnsa4_nrr > 0) {
struct Address **new_responses = realloc(cb_data->responses,
(cb_data->response_count + result->dnsa4_nrr) *
(cb_data->response_count + (size_t)result->dnsa4_nrr) *
sizeof(struct Address *));
if (new_responses == NULL) {
err("Failed to allocate memory for additional DNS responses");
......@@ -282,7 +283,7 @@ dns_query_v6_cb(struct dns_ctx *ctx, struct dns_rr_a6 *result, void *data) {
info("resolv: %s\n", dns_strerror(dns_status(ctx)));
} else if (result->dnsa6_nrr > 0) {
struct Address **new_responses = realloc(cb_data->responses,
(cb_data->response_count + result->dnsa6_nrr) *
(cb_data->response_count + (size_t)result->dnsa6_nrr) *
sizeof(struct Address *));
if (new_responses == NULL) {
err("Failed to allocate memory for additional DNS responses");
......
......@@ -49,7 +49,7 @@
static void usage();
static void daemonize(void);
static void write_pidfile(const char *, pid_t);
static void set_limits(int);
static void set_limits(rlim_t);
static void drop_perms(const char* username, const char* groupname);
static void perror_exit(const char *);
static void signal_cb(struct ev_loop *, struct ev_signal *, int revents);
......@@ -68,7 +68,7 @@ int
main(int argc, char **argv) {
const char *config_file = "/etc/sniproxy.conf";
int background_flag = 1;
int max_nofiles = 65536;
rlim_t max_nofiles = 65536;
int opt;
while ((opt = getopt(argc, argv, "fc:n:V")) != -1) {
......@@ -80,7 +80,7 @@ main(int argc, char **argv) {
background_flag = 0;
break;
case 'n':
max_nofiles = atoi(optarg);
max_nofiles = strtoul(optarg, NULL, 10);
break;
case 'V':
printf("sniproxy %s\n", sniproxy_version);
......@@ -198,7 +198,7 @@ daemonize(void) {
* At some point we should make this a config parameter
*/
static void
set_limits(int max_nofiles) {
set_limits(rlim_t max_nofiles) {
struct rlimit fd_limit = {
.rlim_cur = max_nofiles,
.rlim_max = max_nofiles,
......
......@@ -59,6 +59,7 @@ new_table() {
}
table->name = NULL;
table->use_proxy_header = 0;
table->reference_count = 0;
STAILQ_INIT(&table->backends);
......@@ -127,17 +128,16 @@ remove_table(struct Table_head *tables, struct Table *table) {
table_ref_put(table);
}
const struct Address *
struct LookupResult
table_lookup_server_address(const struct Table *table, const char *name, size_t name_len) {
struct Backend *b;
b = table_lookup_backend(table, name, name_len);
struct Backend *b = table_lookup_backend(table, name, name_len);
if (b == NULL) {
info("No match found for %.*s", (int)name_len, name);
return NULL;
return (struct LookupResult){.address = NULL};
}
return b->address;
return (struct LookupResult){.address = b->address,
.use_proxy_header = b->use_proxy_header};
}
void
......
......@@ -37,6 +37,7 @@ SLIST_HEAD(Table_head, Table);
struct Table {
char *name;
int use_proxy_header;
/* Runtime fields */
int reference_count;
......@@ -44,12 +45,18 @@ struct Table {
SLIST_ENTRY(Table) entries;
};
struct LookupResult {
const struct Address *address;
int caller_free_address;
int use_proxy_header;
};
struct Table *new_table();
int accept_table_arg(struct Table *, const char *);
void add_table(struct Table_head *, struct Table *);
struct Table *table_lookup(const struct Table_head *, const char *);
const struct Address *table_lookup_server_address(const struct Table *,
const char *, size_t);
struct LookupResult table_lookup_server_address(const struct Table *,
const char *, size_t);
void reload_tables(struct Table_head *, struct Table_head *);
void print_table_config(FILE *, struct Table *);
int valid_table(struct Table *);
......
......@@ -30,8 +30,10 @@
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <stdint.h>
#include <string.h> /* strncpy() */
#include <sys/socket.h>
#include <sys/types.h>
#include "tls.h"
#include "protocol.h"
#include "logger.h"
......@@ -45,6 +47,12 @@
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
static int parse_tls_header(const uint8_t*, size_t, char **);
static int parse_extensions(const uint8_t*, size_t, char **);
static int parse_server_name_extension(const uint8_t*, size_t, char **);
static const char tls_alert[] = {
0x15, /* TLS Alert */
0x03, 0x01, /* TLS version */
......@@ -52,18 +60,13 @@ static const char tls_alert[] = {
0x02, 0x28, /* Fatal, handshake failure */
};
static int parse_tls_header(const char *, size_t, char **);
static int parse_extensions(const char *, size_t, char **);
static int parse_server_name_extension(const char *, size_t, char **);
static const struct Protocol tls_protocol_st = {
const struct Protocol *const tls_protocol = &(struct Protocol){
.name = "tls",
.default_port = 443,
.parse_packet = &parse_tls_header,
.parse_packet = (int (*const)(const char *, size_t, char **))&parse_tls_header,
.abort_message = tls_alert,
.abort_message_len = sizeof(tls_alert)
};
const struct Protocol *const tls_protocol = &tls_protocol_st;
/* Parse a TLS packet for the Server Name Indication extension in the client
......@@ -80,10 +83,10 @@ const struct Protocol *const tls_protocol = &tls_protocol_st;
* < -4 - Invalid TLS client hello
*/
static int
parse_tls_header(const char *data, size_t data_len, char **hostname) {
char tls_content_type;
char tls_version_major;
char tls_version_minor;
parse_tls_header(const uint8_t *data, size_t data_len, char **hostname) {
uint8_t tls_content_type;
uint8_t tls_version_major;
uint8_t tls_version_minor;
size_t pos = TLS_HEADER_LEN;
size_t len;
......@@ -114,15 +117,15 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
tls_version_major = data[1];
tls_version_minor = data[2];
if (tls_version_major < 3) {
debug("Received SSL %d.%d handshake which can not support SNI.",
debug("Received SSL %" PRIu8 ".%" PRIu8 " handshake which can not support SNI.",
tls_version_major, tls_version_minor);
return -2;
}
/* TLS record length */
len = ((unsigned char)data[3] << 8) +
(unsigned char)data[4] + TLS_HEADER_LEN;
len = ((size_t)data[3] << 8) +
(size_t)data[4] + TLS_HEADER_LEN;
data_len = MIN(data_len, len);
/* Check we received entire TLS record length */
......@@ -153,19 +156,19 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
/* Session ID */
if (pos + 1 > data_len)
return -5;
len = (unsigned char)data[pos];
len = (size_t)data[pos];
pos += 1 + len;
/* Cipher Suites */
if (pos + 2 > data_len)
return -5;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1];
pos += 2 + len;
/* Compression Methods */
if (pos + 1 > data_len)
return -5;
len = (unsigned char)data[pos];
len = (size_t)data[pos];
pos += 1 + len;
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
......@@ -176,7 +179,7 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
/* Extensions */
if (pos + 2 > data_len)
return -5;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1];
pos += 2;
if (pos + len > data_len)
......@@ -185,15 +188,15 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
}
static int
parse_extensions(const char *data, size_t data_len, char **hostname) {
parse_extensions(const uint8_t *data, size_t data_len, char **hostname) {
size_t pos = 0;
size_t len;
/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
/* Extension Length */
len = ((unsigned char)data[pos + 2] << 8) +
(unsigned char)data[pos + 3];
len = ((size_t)data[pos + 2] << 8) +
(size_t)data[pos + 3];
/* Check if it's a server name extension */
if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
......@@ -213,14 +216,14 @@ parse_extensions(const char *data, size_t data_len, char **hostname) {
}
static int
parse_server_name_extension(const char *data, size_t data_len,
parse_server_name_extension(const uint8_t *data, size_t data_len,
char **hostname) {
size_t pos = 2; /* skip server name list length */
size_t len;
while (pos + 3 < data_len) {
len = ((unsigned char)data[pos + 1] << 8) +
(unsigned char)data[pos + 2];
len = ((size_t)data[pos + 1] << 8) +
(size_t)data[pos + 2];
if (pos + 3 + len > data_len)
return -5;
......@@ -233,13 +236,13 @@ parse_server_name_extension(const char *data, size_t data_len,
return -4;
}
strncpy(*hostname, data + pos + 3, len);
strncpy(*hostname, (const char *)(data + pos + 3), len);
(*hostname)[len] = '\0';
return len;
default:
debug("Unknown server name extension name type: %d",
debug("Unknown server name extension name type: %" PRIu8,
data[pos]);
}
pos += 3 + len;
......
AM_CPPFLAGS = -I$(top_srcdir)/src -g $(LIBEV_CFLAGS) $(LIBPCRE_CFLAGS) $(LIBUDNS_CFLAGS)
TESTS = http_test \
tls_test \
TESTS = address_test \
buffer_test \
table_test \
address_test \
cfg_tokenizer_test \
binder_test \
functional_test \
bad_request_test \
slow_client_test \
fallback_test \
reload_test \
connection_reset_test \
bind_source_test \
fd_limit_test
table_test \
http_test \
tls_test \
binder_test
TESTS += functional_test \
bad_request_test \
bind_source_test \
connection_reset_test \
fallback_test \
fd_limit_test \
ipv6_v6only_test \
proxy_header_test \
reload_test \
reuseport_test \
slow_client_test \
transparent_proxy_test
if DNS_ENABLED
TESTS += config_test \
resolv_test \
......@@ -41,7 +45,7 @@ tls_test_SOURCES = tls_test.c \
binder_test_SOURCES = binder_test.c \
../src/binder.c \
../src/logger.c
../src/logger.c
buffer_test_SOURCES = buffer_test.c \
../src/buffer.c
......
......@@ -10,6 +10,35 @@ our @ISA = qw(Exporter);
our @EXPORT = qw(new);
our $VERSION = '0.01';
my $http_methods = {
'GET' => 1,
'POST' => 1,
'HEAD' => 1,
'PUT' => 1,
'DELETE' => 1,
'TRACE' => 1,
'DEBUG' => 1,
'CONNECT' => 1,
'OPTIONS' => 1,
};
sub default_response_parser {
my $sock = shift;
my $status = 500;
# Read HTTP request
for (my $i = 0; my $line = $sock->getline(); $i++) {
if ($i == 0 && $line =~ m/\A(\S+) (\S+) HTTP\/(\S+)\r\n\z/) {
$status = 200 if exists($http_methods->{$1});
}
# Wait for blank line indicating the end of the request
last if $i > 0 && $line eq "\r\n";
}
return $status;
};
my $count = 0;
......@@ -22,17 +51,24 @@ my $responses = [
[ 20, 1, 1, 1, 1, 1, 1, 200 ],
];
my $http_status_line = {
200 => 'OK',
203 => 'Non-Authoritative Information',
500 => 'Internal Server Error',
};
sub default_response_generator {
$count ++;
return sub($) {
return sub($$) {
my $sock = shift;
my $status = shift;
my @chunks = @{$responses->[$count % scalar @{$responses}]};
my $content_length = 0;
map { $content_length += $_ } @chunks;
print $sock "HTTP/1.1 200 OK\r\n";
print $sock "HTTP/1.1 $status " . $http_status_line->{$status} . "\r\n";
print $sock "Server: TestHTTPD/$VERSION\r\n";
print $sock "Content-Type: text/plain\r\n";
print $sock "Content-Length: $content_length\r\n";
......@@ -52,6 +88,7 @@ sub httpd {
my %args = @_;
my $ip = $args{'ip'} || 'localhost';
my $port = $args{'port'} || 8081;
my $request_parser = $args{'parser'} || \&default_response_parser;
my $responder_generator = $args{'generator'} || \&default_response_generator;
my $server = IO::Socket::INET->new(Listen => Socket::SOMAXCONN(),
......@@ -72,14 +109,10 @@ sub httpd {
# Child
#
# Read HTTP request
while (my $line = $client->getline()) {
# Wait for blank line indicating the end of the request
last if $line eq "\r\n";
}
my $status = $request_parser->($client);
# Assume a GET request
$responder->($client);
$responder->($client, $status);
$client->close();
exit 0;
......
......@@ -101,7 +101,7 @@ int main() {
display_address(addr, buffer, sizeof(buffer));
if (strcmp(buffer, good[i].output)) {
fprintf(stderr, "display_address(%p) returned \"%s\", expected \"%s\"\n", addr, buffer, good[i].output);
fprintf(stderr, "display_address(%p) returned \"%s\", expected \"%s\"\n", (void *)addr, buffer, good[i].output);
return 1;
}
......@@ -110,14 +110,14 @@ int main() {
port = address_port(addr);
if (good[i].port != port) {
fprintf(stderr, "address_port(%p) return %d, expected %d\n", addr, port, good[i].port);
fprintf(stderr, "address_port(%p) return %d, expected %d\n", (void *)addr, port, good[i].port);
return 1;
}
address_set_port(addr, port);
if (good[i].port != port) {
fprintf(stderr, "address_port(%p) return %d, expected %d\n", addr, port, good[i].port);
fprintf(stderr, "address_port(%p) return %d, expected %d\n", (void *)addr, port, good[i].port);
return 1;
}
......
#!/usr/bin/perl
#!/usr/bin/env perl
use strict;
use warnings;
......
#!/usr/bin/perl
#!/usr/bin/env perl
use strict;
use warnings;
......
#!/usr/bin/perl
#!/usr/bin/env perl
use strict;
use warnings;
......
......@@ -25,8 +25,8 @@ int main() {
static int
test_binder(int port) {
int fd;
struct sockaddr_in addr;
struct sockaddr_storage addr_verify;
struct sockaddr_in addr = { 0 };
struct sockaddr_storage addr_verify = { 0 };
socklen_t len;