Merge branch 'master' into disable-edns-do

This commit is contained in:
W.C.A. Wijngaards 2023-10-04 13:34:47 +02:00
commit eff3e01ec3
15 changed files with 475 additions and 89 deletions

View file

@ -66,6 +66,7 @@
#include "util/data/msgencode.h"
#include "util/data/dname.h"
#include "util/fptr_wlist.h"
#include "util/proxy_protocol.h"
#include "util/tube.h"
#include "util/edns.h"
#include "util/timeval_func.h"
@ -2317,6 +2318,7 @@ worker_init(struct worker* worker, struct config_file *cfg,
worker->env.cfg->stat_interval);
worker_restart_timer(worker);
}
pp_init(&sldns_write_uint16, &sldns_write_uint32);
return 1;
}

View file

@ -1,3 +1,9 @@
3 October 2023: George
- Merge #881: Generalise the proxy protocol code.
2 October 2023: George
- Fix misplaced comment.
22 September 2023: Wouter
- Fix #942: 1.18.0 libunbound DNS regression when built without
OpenSSL.

View file

@ -62,6 +62,7 @@
#include "util/random.h"
#include "util/config_file.h"
#include "util/netevent.h"
#include "util/proxy_protocol.h"
#include "util/storage/lookup3.h"
#include "util/storage/slabhash.h"
#include "util/net_help.h"
@ -265,6 +266,7 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb)
w->env->kill_sub = &mesh_state_delete;
w->env->detect_cycle = &mesh_detect_cycle;
comm_base_timept(w->base, &w->env->now, &w->env->now_tv);
pp_init(&sldns_write_uint16, &sldns_write_uint32);
return w;
}

View file

@ -550,7 +550,6 @@ reuse_tcp_find(struct outside_network* outnet, struct sockaddr_storage* addr,
log_assert(&key_p.reuse != (struct reuse_tcp*)result);
log_assert(&key_p != ((struct reuse_tcp*)result)->pending);
}
/* not found, return null */
/* It is possible that we search for something before the first element
* in the tree. Replace a null pointer with the first element.
@ -560,6 +559,7 @@ reuse_tcp_find(struct outside_network* outnet, struct sockaddr_storage* addr,
result = rbtree_first(&outnet->tcp_reuse);
}
/* not found, return null */
if(!result || result == RBTREE_NULL)
return NULL;

View file

@ -61,6 +61,17 @@ Specify the server to send the queries to. If not specified localhost (127.0.0.1
.B \-d \fIsecs
Delay after the connection before sending query. This tests the timeout
on the other side, eg. if shorter the connection is closed.
.TP
.B \-p \fIclient
Use proxy protocol to send the query. Specify the ipaddr@portnr of the client
to include in PROXYv2.
.TP
.B IXFR=serial
Pass the type of the query as IXFR=N to send an IXFR query with serial N.
.TP
.B NOTIFY[=serial]
Pass the type of the query as NOTIFY[=N] to send a notify packet. The serial N
of the new zone can be included.
.SH "EXAMPLES"
.LP
Some examples of use.

View file

@ -79,6 +79,8 @@ static void usage(char* argv[])
printf("-d secs delay after connection before sending query\n");
printf("-s use ssl\n");
printf("-h this help text\n");
printf("IXFR=N for the type, sends ixfr query with serial N.\n");
printf("NOTIFY[=N] for the type, sends notify. Can set new zone serial N.\n");
exit(1);
}
@ -115,6 +117,29 @@ open_svr(const char* svr, int udp, struct sockaddr_storage* addr,
return fd;
}
/** Append a SOA record with serial number */
static void
write_soa_serial_to_buf(sldns_buffer* buf, struct query_info* qinfo,
uint32_t serial)
{
sldns_buffer_set_position(buf, sldns_buffer_limit(buf));
sldns_buffer_set_limit(buf, sldns_buffer_capacity(buf));
/* Write compressed reference to the query */
sldns_buffer_write_u16(buf, PTR_CREATE(LDNS_HEADER_SIZE));
sldns_buffer_write_u16(buf, LDNS_RR_TYPE_SOA);
sldns_buffer_write_u16(buf, qinfo->qclass);
sldns_buffer_write_u32(buf, 3600); /* TTL */
sldns_buffer_write_u16(buf, 1+1+4*5); /* rdatalen */
sldns_buffer_write_u8(buf, 0); /* primary "." */
sldns_buffer_write_u8(buf, 0); /* email "." */
sldns_buffer_write_u32(buf, serial); /* serial */
sldns_buffer_write_u32(buf, 0); /* refresh */
sldns_buffer_write_u32(buf, 0); /* retry */
sldns_buffer_write_u32(buf, 0); /* expire */
sldns_buffer_write_u32(buf, 0); /* minimum */
sldns_buffer_flip(buf);
}
/** write a query over the TCP fd */
static void
write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
@ -123,6 +148,8 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
{
struct query_info qinfo;
size_t proxy_buf_limit = sldns_buffer_limit(proxy_buf);
int have_serial = 0, is_notify = 0;
uint32_t serial = 0;
/* qname */
qinfo.qname = sldns_str2wire_dname(strname, &qinfo.qname_len);
if(!qinfo.qname) {
@ -130,12 +157,27 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
exit(1);
}
/* qtype and qclass */
qinfo.qtype = sldns_get_rr_type_by_name(strtype);
if(qinfo.qtype == 0 && strcmp(strtype, "TYPE0") != 0) {
printf("cannot parse query type: '%s'\n", strtype);
exit(1);
/* qtype */
if(strncasecmp(strtype, "IXFR=", 5) == 0) {
serial = (uint32_t)atoi(strtype+5);
have_serial = 1;
qinfo.qtype = LDNS_RR_TYPE_IXFR;
} else if(strcasecmp(strtype, "NOTIFY") == 0) {
is_notify = 1;
qinfo.qtype = LDNS_RR_TYPE_SOA;
} else if(strncasecmp(strtype, "NOTIFY=", 7) == 0) {
serial = (uint32_t)atoi(strtype+7);
have_serial = 1;
is_notify = 1;
qinfo.qtype = LDNS_RR_TYPE_SOA;
} else {
qinfo.qtype = sldns_get_rr_type_by_name(strtype);
if(qinfo.qtype == 0 && strcmp(strtype, "TYPE0") != 0) {
printf("cannot parse query type: '%s'\n", strtype);
exit(1);
}
}
/* qclass */
qinfo.qclass = sldns_get_rr_class_by_name(strclass);
if(qinfo.qclass == 0 && strcmp(strclass, "CLASS0") != 0) {
printf("cannot parse query class: '%s'\n", strclass);
@ -150,6 +192,21 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
sldns_buffer_write_u16_at(buf, 0, id);
sldns_buffer_write_u16_at(buf, 2, BIT_RD);
if(have_serial && qinfo.qtype == LDNS_RR_TYPE_IXFR) {
/* Attach serial to SOA record in the authority section. */
write_soa_serial_to_buf(buf, &qinfo, serial);
LDNS_NSCOUNT_SET(sldns_buffer_begin(buf), 1);
}
if(is_notify) {
LDNS_OPCODE_SET(sldns_buffer_begin(buf), LDNS_PACKET_NOTIFY);
LDNS_RD_CLR(sldns_buffer_begin(buf));
LDNS_AA_SET(sldns_buffer_begin(buf));
if(have_serial) {
write_soa_serial_to_buf(buf, &qinfo, serial);
LDNS_ANCOUNT_SET(sldns_buffer_begin(buf), 1);
}
}
if(1) {
/* add EDNS DO */
struct edns_data edns;
@ -361,6 +418,7 @@ static int parse_pp2_client(const char* pp2_client, int udp,
sldns_buffer* proxy_buf)
{
struct sockaddr_storage pp2_addr;
size_t bytes_written;
socklen_t pp2_addrlen = 0;
memset(&pp2_addr, 0, sizeof(pp2_addr));
if(*pp2_client == 0) return 0;
@ -369,7 +427,9 @@ static int parse_pp2_client(const char* pp2_client, int udp,
exit(1);
}
sldns_buffer_clear(proxy_buf);
pp2_write_to_buf(proxy_buf, &pp2_addr, !udp);
bytes_written = pp2_write_to_buf(sldns_buffer_begin(proxy_buf),
sldns_buffer_remaining(proxy_buf), &pp2_addr, !udp);
sldns_buffer_set_position(proxy_buf, bytes_written);
sldns_buffer_flip(proxy_buf);
return 1;
}
@ -541,6 +601,8 @@ int main(int argc, char** argv)
break;
case 'p':
pp2_client = optarg;
pp_init(&sldns_write_uint16,
&sldns_write_uint32);
break;
case 'a':
onarrival = 1;

View file

@ -0,0 +1,34 @@
server:
verbosity: 7
# num-threads: 1
interface: 127.0.0.1
port: @PORT@
use-syslog: no
directory: ""
pidfile: "unbound.pid"
chroot: ""
username: ""
do-not-query-localhost: no
# for the test, so that DNSSEC verification works.
#val-override-date: 20230929090000
trust-anchor: ". DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"
remote-control:
control-enable: yes
control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
control-use-cert: no
# for the test, an upstream server in the test setup.
stub-zone:
name: "."
stub-addr: 127.0.0.1@@TOPORT@
# hyperlocal root zone
auth-zone:
name: "."
fallback-enabled: yes
for-downstream: no
for-upstream: yes
zonefile: "root.zone"
zonemd-check: yes
zonemd-reject-absence: yes

View file

@ -0,0 +1,16 @@
BaseName: root_zonemd
Version: 1.0
Description: ZONEMD check for root zone
CreationDate: Fri 29 Sep 09:00:00 CEST 2023
Maintainer: dr. W.C.A. Wijngaards
Category:
Component:
CmdDepends:
Depends:
Help:
Pre: root_zonemd.pre
Post: root_zonemd.post
Test: root_zonemd.test
AuxFiles:
Passed:
Failure:

View file

@ -0,0 +1,14 @@
# #-- root_zonemd.post --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# source the test var file when it's there
[ -f .tpkg.var.test ] && source .tpkg.var.test
#
# do your teardown here
. ../common.sh
echo "> cat logfiles"
cat fwd.log
cat unbound.log
kill_pid $FWD_PID
kill_pid $UNBOUND_PID
rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID

View file

@ -0,0 +1,50 @@
# #-- root_zonemd.pre--#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
. ../common.sh
# attempt to download the root zone
from=k.root-servers.net
dig @$from . AXFR > root.txt
if test $? -ne 0; then
echo "could not fetch root zone"
skip_test "could not fetch root zone"
fi
grep " SOA " root.txt | head -1 > root.soa
cat root.soa >> root.zone
grep -v " SOA " root.txt >> root.zone
echo "fetched root.zone"
ls -l root.zone
cat root.soa
get_random_port 2
UNBOUND_PORT=$RND_PORT
FWD_PORT=$(($RND_PORT + 1))
echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
# start forwarder
get_ldns_testns
$LDNS_TESTNS -p $FWD_PORT root_zonemd.testns >fwd.log 2>&1 &
FWD_PID=$!
echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
# make config file
CONTROL_PATH=/tmp
CONTROL_PID=$$
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < root_zonemd.conf > ub.conf
# start unbound in the background
PRE="../.."
$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
UNBOUND_PID=$!
echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
cat .tpkg.var.test
wait_ldns_testns_up fwd.log
wait_unbound_up unbound.log

View file

@ -0,0 +1,51 @@
# #-- root_zonemd.test --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
PRE="../.."
# do the test
echo "> dig www.example.com."
dig @localhost -p $UNBOUND_PORT . SOA | tee outfile
echo "> check answer"
if grep root-servers outfile | grep "nstld.verisign-grs.com"; then
echo "OK"
else
echo "Not OK"
exit 1
fi
echo "> unbound-control status"
$PRE/unbound-control -c ub.conf status
if test $? -ne 0; then
echo "wrong exit value."
exit 1
else
echo "exit value: OK"
fi
# This is the output when an unsupported algorithm is used.
if grep "auth zone . ZONEMD unsupported algorithm" unbound.log; then
echo "OK"
else
echo "ZONEMD verification not OK"
exit 1
fi
echo "> unbound-control auth_zone_reload ."
$PRE/unbound-control -c ub.conf auth_zone_reload . 2>&1 | tee outfile
if test $? -ne 0; then
echo "wrong exit value."
exit 1
fi
# The output of the reload can be checked.
#echo "> check unbound-control output"
#if grep "example.com: ZONEMD verification successful" outfile; then
#echo "OK"
#else
#echo "Not OK"
#exit 1
#fi
exit 0

View file

@ -0,0 +1,9 @@
# reply to everything
ENTRY_BEGIN
MATCH opcode
ADJUST copy_id copy_query
REPLY QR SERVFAIL
SECTION QUESTION
example.com. IN SOA
SECTION ANSWER
ENTRY_END

View file

@ -892,15 +892,18 @@ static int udp_recv_needs_log(int err)
static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
int stream) {
size_t size;
struct pp2_header *header = pp2_read_header(buf);
if(header == NULL) return 0;
struct pp2_header *header;
int err = pp2_read_header(sldns_buffer_begin(buf),
sldns_buffer_remaining(buf));
if(err) return 0;
header = (struct pp2_header*)sldns_buffer_begin(buf);
size = PP2_HEADER_SIZE + ntohs(header->len);
if((header->ver_cmd & 0xF) == PP2_CMD_LOCAL) {
/* A connection from the proxy itself.
* No need to do anything with addresses. */
goto done;
}
if(header->fam_prot == 0x00) {
if(header->fam_prot == PP2_UNSPEC_UNSPEC) {
/* Unspecified family and protocol. This could be used for
* health checks by proxies.
* No need to do anything with addresses. */
@ -908,8 +911,8 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
}
/* Read the proxied address */
switch(header->fam_prot) {
case 0x11: /* AF_INET|STREAM */
case 0x12: /* AF_INET|DGRAM */
case PP2_INET_STREAM:
case PP2_INET_DGRAM:
{
struct sockaddr_in* addr =
(struct sockaddr_in*)&rep->client_addr;
@ -920,8 +923,8 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
}
/* Ignore the destination address; it should be us. */
break;
case 0x21: /* AF_INET6|STREAM */
case 0x22: /* AF_INET6|DGRAM */
case PP2_INET6_STREAM:
case PP2_INET6_DGRAM:
{
struct sockaddr_in6* addr =
(struct sockaddr_in6*)&rep->client_addr;
@ -934,6 +937,10 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
}
/* Ignore the destination address; it should be us. */
break;
default:
log_err("proxy_protocol: unsupported family and "
"protocol 0x%x", (int)header->fam_prot);
return 0;
}
rep->is_proxied = 1;
done:
@ -1813,19 +1820,25 @@ ssl_handle_read(struct comm_point* c)
return 0;
}
c->tcp_byte_count += r;
sldns_buffer_skip(c->buffer, r);
if(c->tcp_byte_count != current_read_size) return 1;
c->pp2_header_state = pp2_header_init;
}
}
if(c->pp2_header_state == pp2_header_init) {
header = pp2_read_header(c->buffer);
if(!header) {
int err;
err = pp2_read_header(
sldns_buffer_begin(c->buffer),
sldns_buffer_limit(c->buffer));
if(err) {
log_err("proxy_protocol: could not parse "
"PROXYv2 header");
"PROXYv2 header (%s)",
pp_lookup_error(err));
return 0;
}
header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
want_read_size = ntohs(header->len);
if(sldns_buffer_remaining(c->buffer) <
if(sldns_buffer_limit(c->buffer) <
PP2_HEADER_SIZE + want_read_size) {
log_err_addr("proxy_protocol: not enough "
"buffer size to read PROXYv2 header", "",
@ -1874,6 +1887,7 @@ ssl_handle_read(struct comm_point* c)
return 0;
}
c->tcp_byte_count += r;
sldns_buffer_skip(c->buffer, r);
if(c->tcp_byte_count != current_read_size) return 1;
c->pp2_header_state = pp2_header_done;
}
@ -1884,6 +1898,7 @@ ssl_handle_read(struct comm_point* c)
c->repinfo.remote_addrlen);
return 0;
}
sldns_buffer_flip(c->buffer);
if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
log_err_addr("proxy_protocol: could not consume "
"PROXYv2 header", "", &c->repinfo.remote_addr,
@ -2205,19 +2220,25 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
goto recv_error_initial;
}
c->tcp_byte_count += r;
sldns_buffer_skip(c->buffer, r);
if(c->tcp_byte_count != current_read_size) return 1;
c->pp2_header_state = pp2_header_init;
}
}
if(c->pp2_header_state == pp2_header_init) {
header = pp2_read_header(c->buffer);
if(!header) {
int err;
err = pp2_read_header(
sldns_buffer_begin(c->buffer),
sldns_buffer_limit(c->buffer));
if(err) {
log_err("proxy_protocol: could not parse "
"PROXYv2 header");
"PROXYv2 header (%s)",
pp_lookup_error(err));
return 0;
}
header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
want_read_size = ntohs(header->len);
if(sldns_buffer_remaining(c->buffer) <
if(sldns_buffer_limit(c->buffer) <
PP2_HEADER_SIZE + want_read_size) {
log_err_addr("proxy_protocol: not enough "
"buffer size to read PROXYv2 header", "",
@ -2244,6 +2265,7 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
goto recv_error;
}
c->tcp_byte_count += r;
sldns_buffer_skip(c->buffer, r);
if(c->tcp_byte_count != current_read_size) return 1;
c->pp2_header_state = pp2_header_done;
}
@ -2254,6 +2276,7 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
c->repinfo.remote_addrlen);
return 0;
}
sldns_buffer_flip(c->buffer);
if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
log_err_addr("proxy_protocol: could not consume "
"PROXYv2 header", "", &c->repinfo.remote_addr,

View file

@ -38,102 +38,162 @@
*
* This file contains PROXY protocol functions.
*/
#include "config.h"
#include "util/log.h"
#include "util/proxy_protocol.h"
int
pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
/**
* Internal struct initialized with function pointers for writing uint16 and
* uint32.
*/
struct proxy_protocol_data {
void (*write_uint16)(void* buf, uint16_t data);
void (*write_uint32)(void* buf, uint32_t data);
};
struct proxy_protocol_data pp_data;
/**
* Internal lookup table; could be further generic like sldns_lookup_table
* for all the future generic stuff.
*/
struct proxy_protocol_lookup_table {
int id;
const char *text;
};
/**
* Internal parsing error text; could be exposed with pp_lookup_error.
*/
static struct proxy_protocol_lookup_table pp_parse_errors_data[] = {
{ PP_PARSE_NOERROR, "no parse error" },
{ PP_PARSE_SIZE, "not enough space for header" },
{ PP_PARSE_WRONG_HEADERv2, "could not match PROXYv2 header" },
{ PP_PARSE_UNKNOWN_CMD, "unknown command" },
{ PP_PARSE_UNKNOWN_FAM_PROT, "unknown family and protocol" },
};
void
pp_init(void (*write_uint16)(void* buf, uint16_t data),
void (*write_uint32)(void* buf, uint32_t data)) {
pp_data.write_uint16 = write_uint16;
pp_data.write_uint32 = write_uint32;
}
const char*
pp_lookup_error(enum pp_parse_errors error) {
return pp_parse_errors_data[error].text;
}
size_t
pp2_write_to_buf(uint8_t* buf, size_t buflen,
#ifdef INET6
struct sockaddr_storage* src,
#else
struct sockaddr_in* src,
#endif
int stream)
{
int af;
size_t expected_size;
if(!src) return 0;
af = (int)((struct sockaddr_in*)src)->sin_family;
if(sldns_buffer_remaining(buf) <
PP2_HEADER_SIZE + (af==AF_INET?12:36)) {
expected_size = PP2_HEADER_SIZE + (af==AF_INET?12:36);
if(buflen < expected_size) {
return 0;
}
/* sig */
sldns_buffer_write(buf, PP2_SIG, PP2_SIG_LEN);
memcpy(buf, PP2_SIG, PP2_SIG_LEN);
buf += PP2_SIG_LEN;
/* version and command */
sldns_buffer_write_u8(buf, (PP2_VERSION << 4) | PP2_CMD_PROXY);
if(af==AF_INET) {
*buf = (PP2_VERSION << 4) | PP2_CMD_PROXY;
buf++;
switch(af) {
case AF_INET:
/* family and protocol */
sldns_buffer_write_u8(buf,
(PP2_AF_INET<<4) |
(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
*buf = (PP2_AF_INET<<4) |
(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
buf++;
/* length */
sldns_buffer_write_u16(buf, 12);
(*pp_data.write_uint16)(buf, 12);
buf += 2;
/* src addr */
sldns_buffer_write(buf,
memcpy(buf,
&((struct sockaddr_in*)src)->sin_addr.s_addr, 4);
buf += 4;
/* dst addr */
sldns_buffer_write_u32(buf, 0);
(*pp_data.write_uint32)(buf, 0);
buf += 4;
/* src port */
sldns_buffer_write(buf,
memcpy(buf,
&((struct sockaddr_in*)src)->sin_port, 2);
/* dst port */
sldns_buffer_write_u16(buf, 0);
} else {
/* family and protocol */
sldns_buffer_write_u8(buf,
(PP2_AF_INET6<<4) |
(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
/* length */
sldns_buffer_write_u16(buf, 36);
/* src addr */
sldns_buffer_write(buf,
&((struct sockaddr_in6*)src)->sin6_addr, 16);
buf += 2;
/* dst addr */
sldns_buffer_set_at(buf,
sldns_buffer_position(buf), 0, 16);
sldns_buffer_skip(buf, 16);
/* src port */
sldns_buffer_write(buf,
&((struct sockaddr_in6*)src)->sin6_port, 2);
/* dst port */
sldns_buffer_write_u16(buf, 0);
(*pp_data.write_uint16)(buf, 12);
break;
#ifdef INET6
case AF_INET6:
/* family and protocol */
*buf = (PP2_AF_INET6<<4) |
(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
buf++;
/* length */
(*pp_data.write_uint16)(buf, 36);
buf += 2;
/* src addr */
memcpy(buf,
&((struct sockaddr_in6*)src)->sin6_addr, 16);
buf += 16;
/* dst addr */
memset(buf, 0, 16);
buf += 16;
/* src port */
memcpy(buf, &((struct sockaddr_in6*)src)->sin6_port, 2);
buf += 2;
/* dst port */
(*pp_data.write_uint16)(buf, 0);
break;
#endif /* INET6 */
case AF_UNIX:
/* fallthrough */
default:
return 0;
}
return 1;
return expected_size;
}
struct pp2_header*
pp2_read_header(struct sldns_buffer* buf)
int
pp2_read_header(uint8_t* buf, size_t buflen)
{
size_t size;
struct pp2_header* header = (struct pp2_header*)sldns_buffer_begin(buf);
struct pp2_header* header = (struct pp2_header*)buf;
/* Try to fail all the unsupported cases first. */
if(sldns_buffer_remaining(buf) < PP2_HEADER_SIZE) {
log_err("proxy_protocol: not enough space for header");
return NULL;
if(buflen < PP2_HEADER_SIZE) {
return PP_PARSE_SIZE;
}
/* Check for PROXYv2 header */
if(memcmp(header, PP2_SIG, PP2_SIG_LEN) != 0 ||
((header->ver_cmd & 0xF0)>>4) != PP2_VERSION) {
log_err("proxy_protocol: could not match PROXYv2 header");
return NULL;
return PP_PARSE_WRONG_HEADERv2;
}
/* Check the length */
size = PP2_HEADER_SIZE + ntohs(header->len);
if(sldns_buffer_remaining(buf) < size) {
log_err("proxy_protocol: not enough space for header");
return NULL;
if(buflen < size) {
return PP_PARSE_SIZE;
}
/* Check for supported commands */
if((header->ver_cmd & 0xF) != PP2_CMD_LOCAL &&
(header->ver_cmd & 0xF) != PP2_CMD_PROXY) {
log_err("proxy_protocol: unsupported command");
return NULL;
return PP_PARSE_UNKNOWN_CMD;
}
/* Check for supported family and protocol */
if(header->fam_prot != 0x00 /* AF_UNSPEC|UNSPEC */ &&
header->fam_prot != 0x11 /* AF_INET|STREAM */ &&
header->fam_prot != 0x12 /* AF_INET|DGRAM */ &&
header->fam_prot != 0x21 /* AF_INET6|STREAM */ &&
header->fam_prot != 0x22 /* AF_INET6|DGRAM */) {
log_err("proxy_protocol: unsupported family and protocol");
return NULL;
if(header->fam_prot != PP2_UNSPEC_UNSPEC &&
header->fam_prot != PP2_INET_STREAM &&
header->fam_prot != PP2_INET_DGRAM &&
header->fam_prot != PP2_INET6_STREAM &&
header->fam_prot != PP2_INET6_DGRAM &&
header->fam_prot != PP2_UNIX_STREAM &&
header->fam_prot != PP2_UNIX_DGRAM) {
return PP_PARSE_UNKNOWN_FAM_PROT;
}
/* We have a correct header */
return header;
return PP_PARSE_NOERROR;
}

View file

@ -42,7 +42,7 @@
#ifndef PROXY_PROTOCOL_H
#define PROXY_PROTOCOL_H
#include "sldns/sbuffer.h"
#include "config.h"
/** PROXYv2 minimum header size */
#define PP2_HEADER_SIZE 16
@ -51,11 +51,11 @@
#define PP2_SIG "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
#define PP2_SIG_LEN 12
/** PROXYv2 version */
/** PROXYv2 version (protocol value) */
#define PP2_VERSION 0x2
/**
* PROXYv2 command.
* PROXYv2 command (protocol value).
*/
enum pp2_command {
PP2_CMD_LOCAL = 0x0,
@ -63,7 +63,7 @@ enum pp2_command {
};
/**
* PROXYv2 address family.
* PROXYv2 address family (protocol value).
*/
enum pp2_af {
PP2_AF_UNSPEC = 0x0,
@ -73,7 +73,7 @@ enum pp2_af {
};
/**
* PROXYv2 protocol.
* PROXYv2 protocol (protocol value).
*/
enum pp2_protocol {
PP2_PROT_UNSPEC = 0x0,
@ -81,6 +81,19 @@ enum pp2_protocol {
PP2_PROT_DGRAM = 0x2
};
/**
* Expected combinations of address family and protocol values used in checks.
*/
enum pp2_af_protocol_combination {
PP2_UNSPEC_UNSPEC = (PP2_AF_UNSPEC<<4)|PP2_PROT_UNSPEC,
PP2_INET_STREAM = (PP2_AF_INET<<4)|PP2_PROT_STREAM,
PP2_INET_DGRAM = (PP2_AF_INET<<4)|PP2_PROT_DGRAM,
PP2_INET6_STREAM = (PP2_AF_INET6<<4)|PP2_PROT_STREAM,
PP2_INET6_DGRAM = (PP2_AF_INET6<<4)|PP2_PROT_DGRAM,
PP2_UNIX_STREAM = (PP2_AF_UNIX<<4)|PP2_PROT_STREAM,
PP2_UNIX_DGRAM = (PP2_AF_UNIX<<4)|PP2_PROT_DGRAM
};
/**
* PROXYv2 header.
*/
@ -109,23 +122,56 @@ struct pp2_header {
} addr;
};
/**
* PROXY parse errors.
*/
enum pp_parse_errors {
PP_PARSE_NOERROR = 0,
PP_PARSE_SIZE,
PP_PARSE_WRONG_HEADERv2,
PP_PARSE_UNKNOWN_CMD,
PP_PARSE_UNKNOWN_FAM_PROT,
};
/**
* Initialize the internal proxy structure.
* @param write_uint16: pointer to a function that can write uint16.
* @param write_uint32: pointer to a function that can write uint32.
*/
void pp_init(void (*write_uint16)(void* buf, uint16_t data),
void (*write_uint32)(void* buf, uint32_t data));
/**
* Lookup the parsing error description.
* @param error: parsing error from pp2_read_header.
* @return the description.
*/
const char* pp_lookup_error(enum pp_parse_errors error);
/**
* Write a PROXYv2 header at the current position of the buffer.
* @param buf: the buffer to write to.
* @param buf: pointer to the buffer to write data to.
* @param buflen: available size on the buffer.
* @param src: the source address.
* @param stream: if the protocol is stream or datagram.
* @return 1 on success, 0 on failure.
*/
int pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
size_t pp2_write_to_buf(uint8_t* buf, size_t buflen,
#ifdef INET6
struct sockaddr_storage* src,
#else
struct sockaddr_in* src,
#endif
int stream);
/**
* Read a PROXYv2 header from the current position of the buffer.
* It does initial validation and returns a pointer to the buffer position on
* success.
* @param buf: the buffer to read from.
* @return the pointer to the buffer position on success, NULL on error.
* @param buf: pointer to the buffer data to read from.
* @param buflen: available size on the buffer.
* @return parsing error, 0 on success.
*/
struct pp2_header* pp2_read_header(struct sldns_buffer* buf);
int pp2_read_header(uint8_t* buf, size_t buflen);
#endif /* PROXY_PROTOCOL_H */