1/* Copyright (c) 2017, MariaDB
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
15
16#include <mariadb.h>
17#include <mysql.h>
18#include <mysql_com.h>
19#include <mysqld_error.h>
20#include <my_sys.h>
21#include <m_string.h>
22#include <my_net.h>
23#include <violite.h>
24#include <proxy_protocol.h>
25#include <log.h>
26#include <my_pthread.h>
27
28#define PROXY_PROTOCOL_V1_SIGNATURE "PROXY"
29#define PROXY_PROTOCOL_V2_SIGNATURE "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
30#define MAX_PROXY_HEADER_LEN 256
31
32static mysql_rwlock_t lock;
33
34/*
35 Parse proxy protocol version 1 header (text)
36*/
37static int parse_v1_header(char *hdr, size_t len, proxy_peer_info *peer_info)
38{
39 char address_family[MAX_PROXY_HEADER_LEN + 1];
40 char client_address[MAX_PROXY_HEADER_LEN + 1];
41 char server_address[MAX_PROXY_HEADER_LEN + 1];
42 int client_port;
43 int server_port;
44
45 int ret = sscanf(hdr, "PROXY %s %s %s %d %d",
46 address_family, client_address, server_address,
47 &client_port, &server_port);
48
49 if (ret != 5)
50 {
51 if (ret >= 1 && !strcmp(address_family, "UNKNOWN"))
52 {
53 peer_info->is_local_command= true;
54 return 0;
55 }
56 return -1;
57 }
58
59 if (client_port < 0 || client_port > 0xffff
60 || server_port < 0 || server_port > 0xffff)
61 return -1;
62
63 if (!strcmp(address_family, "UNKNOWN"))
64 {
65 peer_info->is_local_command= true;
66 return 0;
67 }
68 else if (!strcmp(address_family, "TCP4"))
69 {
70 /* Initialize IPv4 peer address.*/
71 peer_info->peer_addr.ss_family= AF_INET;
72 if (!inet_pton(AF_INET, client_address,
73 &((struct sockaddr_in *)(&peer_info->peer_addr))->sin_addr))
74 return -1;
75 }
76 else if (!strcmp(address_family, "TCP6"))
77 {
78 /* Initialize IPv6 peer address.*/
79 peer_info->peer_addr.ss_family= AF_INET6;
80 if (!inet_pton(AF_INET6, client_address,
81 &((struct sockaddr_in6 *)(&peer_info->peer_addr))->sin6_addr))
82 return -1;
83 }
84 peer_info->port= client_port;
85 /* Check if server address is legal.*/
86 char addr_bin[16];
87 if (!inet_pton(peer_info->peer_addr.ss_family,
88 server_address, addr_bin))
89 return -1;
90
91 return 0;
92}
93
94
95/*
96 Parse proxy protocol V2 (binary) header
97*/
98static int parse_v2_header(uchar *hdr, size_t len,proxy_peer_info *peer_info)
99{
100 /* V2 Signature */
101 if (memcmp(hdr, PROXY_PROTOCOL_V2_SIGNATURE, 12))
102 return -1;
103
104 /* version + command */
105 uint8 ver= (hdr[12] & 0xF0);
106 if (ver != 0x20)
107 return -1; /* Wrong version*/
108
109 uint cmd= (hdr[12] & 0xF);
110
111 /* Address family */
112 uchar fam= hdr[13];
113
114 if (cmd == 0)
115 {
116 /* LOCAL command*/
117 peer_info->is_local_command= true;
118 return 0;
119 }
120
121 if (cmd != 0x01)
122 {
123 /* Not PROXY COMMAND */
124 return -1;
125 }
126
127 struct sockaddr_in *sin= (struct sockaddr_in *)(&peer_info->peer_addr);
128 struct sockaddr_in6 *sin6= (struct sockaddr_in6 *)(&peer_info->peer_addr);
129 switch (fam)
130 {
131 case 0x11: /* TCPv4 */
132 sin->sin_family= AF_INET;
133 memcpy(&(sin->sin_addr), hdr + 16, 4);
134 peer_info->port= (hdr[24] << 8) + hdr[25];
135 break;
136 case 0x21: /* TCPv6 */
137 sin6->sin6_family= AF_INET6;
138 memcpy(&(sin6->sin6_addr), hdr + 16, 16);
139 peer_info->port= (hdr[48] << 8) + hdr[49];
140 break;
141 case 0x31: /* AF_UNIX, stream */
142 peer_info->peer_addr.ss_family= AF_UNIX;
143 break;
144 default:
145 return -1;
146 }
147 return 0;
148}
149
150
151bool has_proxy_protocol_header(NET *net)
152{
153 compile_time_assert(NET_HEADER_SIZE < sizeof(PROXY_PROTOCOL_V1_SIGNATURE));
154 compile_time_assert(NET_HEADER_SIZE < sizeof(PROXY_PROTOCOL_V2_SIGNATURE));
155
156 const uchar *preread_bytes= net->buff + net->where_b;
157 return !memcmp(preread_bytes, PROXY_PROTOCOL_V1_SIGNATURE, NET_HEADER_SIZE)||
158 !memcmp(preread_bytes, PROXY_PROTOCOL_V2_SIGNATURE, NET_HEADER_SIZE);
159}
160
161
162/**
163 Try to parse proxy header.
164 https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
165
166 Whenever this function is called, client is connecting, and
167 we have have pre-read 4 bytes (NET_HEADER_SIZE) from the network already.
168 These 4 bytes did not match MySQL packet header, and (unless the client
169 is buggy), those bytes must be proxy header.
170
171 @param[in] net - vio and already preread bytes from the header
172 @param[out] peer_info - parsed proxy header with client host and port
173 @return 0 in case of success, -1 if error.
174*/
175int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info)
176{
177 uchar hdr[MAX_PROXY_HEADER_LEN];
178 size_t pos= 0;
179
180 DBUG_ASSERT(!net->compress);
181 const uchar *preread_bytes= net->buff + net->where_b;
182 bool have_v1_header= !memcmp(preread_bytes, PROXY_PROTOCOL_V1_SIGNATURE, NET_HEADER_SIZE);
183 bool have_v2_header=
184 !have_v1_header && !memcmp(preread_bytes, PROXY_PROTOCOL_V2_SIGNATURE, NET_HEADER_SIZE);
185 if (!have_v1_header && !have_v2_header)
186 {
187 // not a proxy protocol header
188 return -1;
189 }
190 memcpy(hdr, preread_bytes, NET_HEADER_SIZE);
191 pos= NET_HEADER_SIZE;
192 Vio *vio= net->vio;
193 memset(peer_info, 0, sizeof (*peer_info));
194
195 if (have_v1_header)
196 {
197 /* Read until end of header (newline character)*/
198 while(pos < sizeof(hdr))
199 {
200 long len= (long)vio_read(vio, hdr + pos, 1);
201 if (len < 0)
202 return -1;
203 pos++;
204 if (hdr[pos-1] == '\n')
205 break;
206 }
207 hdr[pos]= 0;
208
209 if (parse_v1_header((char *)hdr, pos, peer_info))
210 return -1;
211 }
212 else // if (have_v2_header)
213 {
214#define PROXY_V2_HEADER_LEN 16
215 /* read off 16 bytes of the header.*/
216 ssize_t len= vio_read(vio, hdr + pos, PROXY_V2_HEADER_LEN - pos);
217 if (len < 0)
218 return -1;
219 // 2 last bytes are the length in network byte order of the part following header
220 ushort trail_len= ((ushort)hdr[PROXY_V2_HEADER_LEN-2] >> 8) + hdr[PROXY_V2_HEADER_LEN-1];
221 if (trail_len > sizeof(hdr) - PROXY_V2_HEADER_LEN)
222 return -1;
223 if (trail_len > 0)
224 {
225 len= vio_read(vio, hdr + PROXY_V2_HEADER_LEN, trail_len);
226 if (len < 0)
227 return -1;
228 }
229 pos= PROXY_V2_HEADER_LEN + trail_len;
230 if (parse_v2_header(hdr, pos, peer_info))
231 return -1;
232 }
233
234 if (peer_info->peer_addr.ss_family == AF_INET6)
235 {
236 /*
237 Normalize IPv4 compatible or mapped IPv6 addresses.
238 They will be treated as IPv4.
239 */
240 sockaddr_storage tmp;
241 memset(&tmp, 0, sizeof(tmp));
242 vio_get_normalized_ip((const struct sockaddr *)&peer_info->peer_addr,
243 sizeof(sockaddr_storage), (struct sockaddr *)&tmp);
244 memcpy(&peer_info->peer_addr, &tmp, sizeof(tmp));
245 }
246 return 0;
247}
248
249
250/**
251 CIDR address matching etc (for the proxy_protocol_networks parameter)
252*/
253
254/**
255 Subnetwork address in CIDR format, e.g
256 192.168.1.0/24 or 2001:db8::/32
257*/
258struct subnet
259{
260 char addr[16]; /* Binary representation of the address, big endian*/
261 unsigned short family; /* Address family, AF_INET or AF_INET6 */
262 unsigned short bits; /* subnetwork size */
263};
264
265static subnet* proxy_protocol_subnets;
266size_t proxy_protocol_subnet_count;
267
268#define MAX_MASK_BITS(family) (family == AF_INET ? 32 : 128)
269
270
271/** Convert IPv4 that are compat or mapped IPv4 to "normal" IPv4 */
272static int normalize_subnet(struct subnet *subnet)
273{
274 unsigned char *addr= (unsigned char*)subnet->addr;
275 if (subnet->family == AF_INET6)
276 {
277 const struct in6_addr *src_ip6=(in6_addr *)addr;
278 if (IN6_IS_ADDR_V4MAPPED(src_ip6) || IN6_IS_ADDR_V4COMPAT(src_ip6))
279 {
280 /* Copy the actual IPv4 address (4 last bytes) */
281 if (subnet->bits < 96)
282 return -1;
283 subnet->family= AF_INET;
284 memcpy(addr, addr+12, 4);
285 subnet->bits -= 96;
286 }
287 }
288 return 0;
289}
290
291/**
292 Convert string representation of a subnet to subnet struct.
293*/
294static int parse_subnet(char *addr_str, struct subnet *subnet)
295{
296 if (strchr(addr_str, ':'))
297 subnet->family= AF_INET6;
298 else if (strchr(addr_str, '.'))
299 subnet->family= AF_INET;
300 else if (!strcmp(addr_str, "localhost"))
301 {
302 subnet->family= AF_UNIX;
303 subnet->bits= 0;
304 return 0;
305 }
306
307 char *pmask= strchr(addr_str, '/');
308 if (!pmask)
309 {
310 subnet->bits= MAX_MASK_BITS(subnet->family);
311 }
312 else
313 {
314 *pmask= 0;
315 pmask++;
316 int b= 0;
317
318 do
319 {
320 if (*pmask < '0' || *pmask > '9')
321 return -1;
322 b= 10 * b + *pmask - '0';
323 if (b > MAX_MASK_BITS(subnet->family))
324 return -1;
325 pmask++;
326 }
327 while (*pmask);
328
329 subnet->bits= (unsigned short)b;
330 }
331
332 if (!inet_pton(subnet->family, addr_str, subnet->addr))
333 return -1;
334
335 if (normalize_subnet(subnet))
336 return -1;
337
338 return 0;
339}
340
341/**
342 Parse comma separated string subnet list into subnets array,
343 which is stored in 'proxy_protocol_subnets' variable
344
345 @param[in] subnets_str : networks in CIDR format,
346 separated by comma and/or space
347 @param[out] out_subnets : parsed subnets;
348 @param[out] out_count : number of parsed subnets
349
350 @return 0 if success, otherwise -1
351*/
352static int parse_networks(const char *subnets_str, subnet **out_subnets, size_t *out_count)
353{
354 int ret= -1;
355 subnet *subnets= 0;
356 size_t count= 0;
357 const char *p= subnets_str;
358 size_t max_subnets;
359
360 if (!subnets_str || !*subnets_str)
361 {
362 ret= 0;
363 goto end;
364 }
365
366 max_subnets= MY_MAX(3,strlen(subnets_str)/2);
367 subnets= (subnet *)my_malloc(max_subnets * sizeof(subnet),MY_ZEROFILL);
368
369 /* Check for special case '*'. */
370 if (strcmp(subnets_str, "*") == 0)
371 {
372 subnets[0].family= AF_INET;
373 subnets[1].family= AF_INET6;
374 subnets[2].family= AF_UNIX;
375 count= 3;
376 ret= 0;
377 goto end;
378 }
379
380 char token[256];
381 for(count= 0;; count++)
382 {
383 while(*p && (*p ==',' || *p == ' '))
384 p++;
385 if (!*p)
386 break;
387
388 size_t cnt= 0;
389 while(*p && *p != ',' && *p != ' ' && cnt < sizeof(token)-1)
390 token[cnt++]= *p++;
391
392 token[cnt++]=0;
393 if (cnt == sizeof(token))
394 goto end;
395
396 if (parse_subnet(token, &subnets[count]))
397 {
398 my_printf_error(ER_PARSE_ERROR,"Error parsing proxy_protocol_networks parameter, near '%s'",MYF(0),token);
399 goto end;
400 }
401 }
402
403 ret = 0;
404
405end:
406 if (ret)
407 {
408 my_free(subnets);
409 *out_subnets= NULL;
410 *out_count= 0;
411 return ret;
412 }
413 *out_subnets = subnets;
414 *out_count= count;
415 return 0;
416}
417
418/**
419 Check validity of proxy_protocol_networks parameter
420 @param[in] in - input string
421 @return : true, if input is list of CIDR-style networks
422 separated by command or space
423*/
424bool proxy_protocol_networks_valid(const char *in)
425{
426 subnet *new_subnets;
427 size_t new_count;
428 int ret= parse_networks(in, &new_subnets, &new_count);
429 my_free(new_subnets);
430 return !ret;
431}
432
433
434/**
435 Set 'proxy_protocol_networks' parameter.
436
437 @param[in] spec : networks in CIDR format,
438 separated by comma and/or space
439
440 @return 0 if success, otherwise -1
441*/
442int set_proxy_protocol_networks(const char *spec)
443{
444 subnet *new_subnets;
445 subnet *old_subnet = 0;
446 size_t new_count;
447
448 int ret= parse_networks(spec, &new_subnets, &new_count);
449 if (ret)
450 return ret;
451
452 mysql_rwlock_wrlock(&lock);
453 old_subnet = proxy_protocol_subnets;
454 proxy_protocol_subnets = new_subnets;
455 proxy_protocol_subnet_count = new_count;
456 mysql_rwlock_unlock(&lock);
457 my_free(old_subnet);
458 return ret;
459}
460
461
462/**
463 Compare memory areas, in memcmp().similar fashion.
464 The difference to memcmp() is that size parameter is the
465 bit count, not byte count.
466*/
467static int compare_bits(const void *s1, const void *s2, int bit_count)
468{
469 int result= 0;
470 int byte_count= bit_count / 8;
471 if (byte_count && (result= memcmp(s1, s2, byte_count)))
472 return result;
473 int rem= byte_count % 8;
474 if (rem)
475 {
476 // compare remaining bits i.e partial bytes.
477 unsigned char s1_bits= (((char *)s1)[byte_count]) >> (8 - rem);
478 unsigned char s2_bits= (((char *)s2)[byte_count]) >> (8 - rem);
479 if (s1_bits > s2_bits)
480 return 1;
481 if (s1_bits < s2_bits)
482 return -1;
483 }
484 return 0;
485}
486
487/**
488 Check whether networks address matches network.
489*/
490bool addr_matches_subnet(const sockaddr *sock_addr, const subnet *subnet)
491{
492 DBUG_ASSERT(subnet->family == AF_UNIX ||
493 subnet->family == AF_INET ||
494 subnet->family == AF_INET6);
495
496 if (sock_addr->sa_family != subnet->family)
497 return false;
498
499 if (subnet->family == AF_UNIX)
500 return true;
501
502 void *addr= (subnet->family == AF_INET) ?
503 (void *)&((struct sockaddr_in *)sock_addr)->sin_addr :
504 (void *)&((struct sockaddr_in6 *)sock_addr)->sin6_addr;
505
506 return (compare_bits(subnet->addr, addr, subnet->bits) == 0);
507}
508
509
510/**
511 Check whether proxy header from client is allowed, as per
512 specification in 'proxy_protocol_networks' server variable.
513
514 The non-TCP "localhost" clients (unix socket, shared memory, pipes)
515 are accepted whenever 127.0.0.1 accepted in 'proxy_protocol_networks'
516*/
517bool is_proxy_protocol_allowed(const sockaddr *addr)
518{
519 if (proxy_protocol_subnet_count == 0)
520 return false;
521
522 sockaddr_storage addr_storage;
523 struct sockaddr *normalized_addr= (struct sockaddr *)&addr_storage;
524
525 /*
526 Non-TCP addresses (unix domain socket, windows pipe and shared memory
527 gets tranlated to TCP4 localhost address.
528
529 Note, that vio remote addresses are initialized with binary zeros
530 for these protocols (which is AF_UNSPEC everywhere).
531 */
532 switch(addr->sa_family)
533 {
534 case AF_UNSPEC:
535 case AF_UNIX:
536 normalized_addr->sa_family= AF_UNIX;
537 break;
538 case AF_INET:
539 case AF_INET6:
540 {
541 size_t len=
542 (addr->sa_family == AF_INET)?sizeof(sockaddr_in):sizeof (sockaddr_in6);
543 vio_get_normalized_ip(addr, len,normalized_addr);
544 }
545 break;
546 default:
547 DBUG_ASSERT(0);
548 }
549
550 bool ret= false;
551 mysql_rwlock_rdlock(&lock);
552 for (size_t i= 0; i < proxy_protocol_subnet_count; i++)
553 {
554 if (addr_matches_subnet(normalized_addr, &proxy_protocol_subnets[i]))
555 {
556 ret= true;
557 break;
558 }
559 }
560 mysql_rwlock_unlock(&lock);
561
562 return ret;
563}
564
565
566int init_proxy_protocol_networks(const char *spec)
567{
568#ifdef HAVE_PSI_INTERFACE
569 static PSI_rwlock_key psi_rwlock_key;
570 static PSI_rwlock_info psi_rwlock_info={ &psi_rwlock_key, "rwlock", 0 };
571 mysql_rwlock_register("proxy_proto", &psi_rwlock_info, 1);
572#endif
573
574 mysql_rwlock_init(psi_rwlock_key, &lock);
575 return set_proxy_protocol_networks(spec);
576}
577
578
579void destroy_proxy_protocol_networks()
580{
581 my_free(proxy_protocol_subnets);
582 mysql_rwlock_destroy(&lock);
583}
584