summaryrefslogtreecommitdiffstats
path: root/src/contrib/proxyv2/proxyv2.c
blob: 0e5c90b7002a8d8e2048e1e8c577448cd7f29dfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/*  Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
    Copyright (C) 2021 Fastly, Inc.

    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/>.
 */

#include <arpa/inet.h>
#include <stdint.h>
#include <string.h>

#include "contrib/proxyv2/proxyv2.h"
#include "contrib/sockaddr.h"
#include "libknot/errcode.h"

/*
 * Minimal implementation of the haproxy PROXY v2 protocol.
 *
 * Supports extracting the original client address and client port number from
 * the haproxy PROXY v2 protocol's address block.
 *
 * See https://www.haproxy.org/download/2.5/doc/proxy-protocol.txt for the
 * protocol specification.
 */

static const char PROXYV2_SIG[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";

/*
 * The part of the PROXY v2 payload following the signature.
 */
struct proxyv2_hdr {
	/*
	 * The protocol version and command.
	 *
	 * The upper four bits contain the version which must be \x2 and the
	 * receiver must only accept this value.
	 *
	 * The lower four bits represent the command, which is \x0 for LOCAL
	 * and \x1 for PROXY.
	 */
	uint8_t		ver_cmd;

	/*
	 * The transport protocol and address family. The upper four bits
	 * contain the address family and the lower four bits contain the
	 * protocol.
	 *
	 * The relevant values for DNS are:
	 *	\x11: TCP over IPv4
	 *	\x12: UDP over IPv4
	 *	\x21: TCP over IPv6
	 *	\x22: UDP over IPv6
	 */
	uint8_t		fam_addr;

	/*
	 * The number of PROXY v2 payload bytes following this header to skip
	 * to reach the proxied packet (i.e., start of the original DNS message).
	 */
	uint16_t	len;
};

/*
 * The PROXY v2 address block for IPv4.
 */
struct proxyv2_addr_ipv4 {
	uint8_t		src_addr[4];
	uint8_t		dst_addr[4];
	uint16_t	src_port;
	uint16_t	dst_port;
};

/*
 * The PROXY v2 address block for IPv6.
 */
struct proxyv2_addr_ipv6 {
	uint8_t		src_addr[16];
	uint8_t		dst_addr[16];
	uint16_t	src_port;
	uint16_t	dst_port;
};

const size_t PROXYV2_HEADER_MAXLEN = sizeof(PROXYV2_SIG) +
                                     sizeof(struct proxyv2_hdr) +
                                     sizeof(struct proxyv2_addr_ipv6);

/*
 * Make sure the C compiler lays out the PROXY v2 address block structs so that
 * they can be memcpy()'d off the wire.
 */
#if (__STDC_VERSION__ >= 201112L)
_Static_assert(sizeof(struct proxyv2_hdr) == 4,
	       "struct proxyv2_hdr is correct size");
_Static_assert(sizeof(struct proxyv2_addr_ipv4) == 12,
	       "struct proxyv2_addr_ipv4 is correct size");
_Static_assert(sizeof(struct proxyv2_addr_ipv6) == 36,
	       "struct proxyv2_addr_ipv6 is correct size");
#endif

int proxyv2_header_offset(void *base, size_t len_base)
{
	/*
	 * Check that 'base' has enough bytes to read the PROXY v2 signature
	 * and header, and if so whether the PROXY v2 signature is present.
	 */
	if (len_base < (sizeof(PROXYV2_SIG) + sizeof(struct proxyv2_hdr)) ||
	    memcmp(base, PROXYV2_SIG, sizeof(PROXYV2_SIG)) != 0)
	{
		/* Failure. */
		return KNOT_EMALF;
	}

	/* Read the PROXY v2 header. */
	struct proxyv2_hdr *hdr = base + sizeof(PROXYV2_SIG);

	/*
	 * Check that this is a version 2, command "PROXY" payload.
	 *
	 * XXX: The PROXY v2 spec mandates support for the "LOCAL" command
	 * (byte 0x20).
	 */
	if (hdr->ver_cmd != 0x21) {
		/* Failure. */
		return KNOT_EMALF;
	}

	/*
	 * Calculate the offset of the original DNS message inside the packet.
	 * This needs to account for the length of the PROXY v2 signature,
	 * PROXY v2 header, and the bytes of variable length PROXY v2 data
	 * following the PROXY v2 header.
	 */
	const size_t offset_dns = sizeof(PROXYV2_SIG) +
	                          sizeof(struct proxyv2_hdr) + ntohs(hdr->len);
	if (offset_dns < len_base) {
		return offset_dns;
	}

	return KNOT_EMALF;
}

int proxyv2_addr_store(void *base, size_t len_base, struct sockaddr_storage *ss)
{
	/*
	 * Calculate the offset of the PROXY v2 address block. This is the data
	 * immediately following the PROXY v2 header.
	 */
	const size_t offset_proxy_addr = sizeof(PROXYV2_SIG) +
	                                 sizeof(struct proxyv2_hdr);
	struct proxyv2_hdr *hdr = base + sizeof(PROXYV2_SIG);

	/*
	 * Handle proxied UDP-over-IPv4 and UDP-over-IPv6 packets.
	 */
	//TODO What about TCP?
	if (hdr->fam_addr == 0x12) {
		/* This is a proxied UDP-over-IPv4 packet. */
		struct proxyv2_addr_ipv4 *addr;

		/*
		 * Check that the packet is large enough to contain the IPv4
		 * address block.
		 */
		if (offset_proxy_addr + sizeof(*addr) < len_base) {
			/* Read the PROXY v2 address block. */
			addr = base + offset_proxy_addr;

			/* Copy the client's IPv4 address to the caller. */
			sockaddr_set_raw(ss, AF_INET, addr->src_addr,
					 sizeof(addr->src_addr));

			/* Copy the client's port to the caller. */
			sockaddr_port_set(ss, ntohs(addr->src_port));

			/* Success. */
			return KNOT_EOK;
		}
	} else if (hdr->fam_addr == 0x22) {
		/* This is a proxied UDP-over-IPv6 packet. */
		struct proxyv2_addr_ipv6 *addr;

		/*
		 * Check that the packet is large enough to contain the IPv6
		 * address block.
		 */
		if (offset_proxy_addr + sizeof(*addr) < len_base) {
			/* Read the PROXY v2 address block. */
			addr = base + offset_proxy_addr;

			/* Copy the client's IPv6 address to the caller. */
			sockaddr_set_raw(ss, AF_INET6, addr->src_addr,
					 sizeof(addr->src_addr));

			/* Copy the client's port to the caller. */
			sockaddr_port_set(ss, ntohs(addr->src_port));

			/* Success. */
			return KNOT_EOK;
		}
	}

	/* Failure. */
	return KNOT_EMALF;
}

int proxyv2_write_header(char *buf, size_t buflen, int socktype, const struct sockaddr *src,
                         const struct sockaddr *dst)
{
	if (buflen < PROXYV2_HEADER_MAXLEN) {
		return KNOT_EINVAL;
	}

	uint8_t fam_addr = 0;
	int family = src->sa_family;
	if (socktype == SOCK_DGRAM) {
		fam_addr += 0x2;
	} else if (socktype == SOCK_STREAM) {
		fam_addr += 0x1;
	} else {
		return KNOT_EINVAL;
	}
	if (family == AF_INET) {
		fam_addr += 0x10;
	} else if (family == AF_INET6) {
		fam_addr += 0x20;
	} else {
		return KNOT_EINVAL;
	}

	struct proxyv2_hdr hdr = {
		.ver_cmd = 0x21,
		.fam_addr = fam_addr,
		.len = (family == AF_INET)
		       ? htons(sizeof(struct proxyv2_addr_ipv4))
		       : htons(sizeof(struct proxyv2_addr_ipv6))
	};

	size_t offset = 0;
	memcpy(buf, PROXYV2_SIG, sizeof(PROXYV2_SIG));
	offset += sizeof(PROXYV2_SIG);
	memcpy(buf + offset, &hdr, sizeof(hdr));
	offset += sizeof(hdr);

	if (family == AF_INET) {
		struct proxyv2_addr_ipv4 ipv4 = { 0 };
		struct sockaddr_in *p_src = (struct sockaddr_in *)src;
		struct sockaddr_in *p_dst = (struct sockaddr_in *)dst;
		memcpy(ipv4.src_addr, &p_src->sin_addr, sizeof(p_src->sin_addr));
		memcpy(ipv4.dst_addr, &p_dst->sin_addr, sizeof(p_dst->sin_addr));
		ipv4.src_port = p_src->sin_port;
		ipv4.dst_port = p_dst->sin_port;

		// Store in buffer
		memcpy(buf + offset, &ipv4, sizeof(ipv4));
		offset += sizeof(ipv4);
	} else {
		struct proxyv2_addr_ipv6 ipv6 = { 0 };
		struct sockaddr_in6 *p_src = (struct sockaddr_in6 *)src;
		struct sockaddr_in6 *p_dst = (struct sockaddr_in6 *)dst;
		memcpy(ipv6.src_addr, &p_src->sin6_addr, sizeof(p_src->sin6_addr));
		memcpy(ipv6.dst_addr, &p_dst->sin6_addr, sizeof(p_dst->sin6_addr));
		ipv6.src_port = p_src->sin6_port;
		ipv6.dst_port = p_dst->sin6_port;

		// Store in buffer
		memcpy(buf + offset, &ipv6, sizeof(ipv6));
		offset += sizeof(ipv6);
	}

	return offset;
}