-
Notifications
You must be signed in to change notification settings - Fork 978
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RFC] V2.1.1: support HAProxy PROXY protocol V1 on MySQL frontends
This adds basic support for the HAProxy PROXY protocol V1 on MySQL frontend client connections. This allows the true client IP to be seen in the output of `SHOW FULL PROCESSLIST` in ProxySQL's admin frontend when the ProxySQL servers sit behind a load balancer such as HAProxy or AWS classic ELBs with the PROXY protocol feature enabled. I believe this should resolve #2497 (Support for proxy_protocol for proxysql behind aws load balancer). The patch adds a Proxy_Protocol class which handles: - parsing of PROXY protocol headers (only the V1 header supported for now) - matching client IPs against a list of configured network CIDRs A new option, `proxy_protocol_frontend_nets` (list of network CIDRs), is introduced to allow the PROXY protocol feature to be selectively turned on for specific subnets where e.g. load balancers are running. This is similar to how the PROXY protocol is implemented in MariaDB. This variable can be set either in the config file or via the ProxySQL Admin interface. Configuration file example: mysql_variables= { ... proxy_protocol_frontend_nets="10.42.0.0/28 127.10.0.0/16" ... } ProxySQL Admin interface example: UPDATE global_variables SET variable_value="192.168.42.0/24" WHERE variable_name="mysql-proxy_protocol_frontend_nets"; LOAD MYSQL VARIABLES TO RUNTIME; The network CIDRs are stored as a thread-local variable in the MySQL_Thread class. It's only updated by MySQL_Thread::refresh_variables(). A new MySQL Data Stream state (DSS), `STATE_PROXY_PROTOCOL`, is introduced to handle the initial parsing of the PROXY protocol header. Once this has completed, the state is reset back to `STATE_SERVER_HANDSHAKE`. The patch hooks into the MySQL_Thread's main loop where `accept()` is called to handle incoming connections. Next, the actual parsing of the PROXY protocol header is invoked from the `MySQL_Data_Stream::read_pkts()` method, which is also called from MySQL_Thread's main loop (where incoming data is handled). I've only tested this with a HAProxy configuration block like: listen proxysql-with-proxy bind *:6030 option tcp-check mode tcp timeout server 6h timeout client 6h balance roundrobin server proxysql-backend-1 127.0.0.1:6033 check send-proxy listen proxysql-without-proxy bind *:6031 option tcp-check mode tcp timeout server 6h timeout client 6h balance roundrobin server proxysql-backend-2 192.168.1.123:6033 check
- Loading branch information
1 parent
6d49f5f
commit c3e187c
Showing
10 changed files
with
293 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#ifndef __CLASS_PROXY_PROTOCOL_H | ||
#define __CLASS_PROXY_PROTOCOL_H | ||
|
||
#include <vector> | ||
#include <sys/types.h> | ||
#include <netinet/in.h> | ||
#include <sys/socket.h> | ||
|
||
|
||
class Proxy_Protocol { | ||
private: | ||
Proxy_Protocol() {} | ||
~Proxy_Protocol() {} | ||
static bool add_subnet(const char *cidr, std::vector<proxy_protocol_subnet_t> &subnets); | ||
|
||
public: | ||
static bool parse_subnets(const char *list, std::vector<proxy_protocol_subnet_t> &subnets); | ||
static bool match_subnet(struct sockaddr *addr, socklen_t addrlen, std::vector<proxy_protocol_subnet_t> &subnets); | ||
static bool parse_header(unsigned char *, size_t n, struct sockaddr_storage *out); | ||
}; | ||
|
||
#endif /* __CLASS_PROXY_PROTOCOL_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
#include "proxysql.h" | ||
|
||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/types.h> | ||
#include <sys/socket.h> | ||
#include <netinet/in.h> | ||
#include <arpa/inet.h> | ||
|
||
#include "Proxy_Protocol.h" | ||
|
||
|
||
bool Proxy_Protocol::parse_subnets(const char *list, std::vector<proxy_protocol_subnet_t> &subnets) { | ||
char *input = strdup(list), *cidr, *saveptr = NULL; | ||
const char *delim = ",; "; | ||
|
||
subnets.clear(); | ||
for(cidr = strtok_r(input, delim, &saveptr); cidr != NULL; cidr = strtok_r(NULL, delim, &saveptr)) { | ||
if(!Proxy_Protocol::add_subnet(cidr, subnets)) { | ||
proxy_warning("PROXY protocol subnet parsing failed, invalid CIDR: %s\n", cidr); | ||
free(input); | ||
return false; | ||
}; | ||
} | ||
|
||
free(input); | ||
return true; | ||
} | ||
|
||
bool Proxy_Protocol::add_subnet(const char *cidr, std::vector<proxy_protocol_subnet_t> &subnets) { | ||
char *addr = strdup(cidr), *mask; | ||
proxy_protocol_subnet_t subnet = { | ||
.family = AF_INET | ||
}; | ||
|
||
if (strchr(addr, ':')) { | ||
subnet.family = AF_INET6; | ||
} | ||
|
||
subnet.bits = (unsigned short)(subnet.family == AF_INET? 32: 128); | ||
mask = strchr(addr, '/'); | ||
if (mask) { | ||
*mask++ = '\0'; | ||
subnet.bits = (unsigned short)atoi(mask); | ||
if (subnet.bits > (subnet.family == AF_INET? 32: 128)) { | ||
free(addr); | ||
return false; | ||
} | ||
} | ||
|
||
if (inet_pton(subnet.family, addr, subnet.addr) == 1) { | ||
subnets.push_back(subnet); | ||
free(addr); | ||
return true; | ||
} | ||
|
||
free(addr); | ||
return false; | ||
} | ||
|
||
bool Proxy_Protocol::match_subnet(struct sockaddr *addr, socklen_t addrlen, std::vector<proxy_protocol_subnet_t> &subnets) { | ||
struct sockaddr_in *sin; | ||
struct sockaddr_in6 *sin6; | ||
unsigned char *a1, *a2; | ||
|
||
for (size_t i = 0; i < subnets.size(); i++) { | ||
if (addr->sa_family != subnets[i].family) { | ||
continue; | ||
} | ||
|
||
switch (addr->sa_family) { | ||
case AF_INET: | ||
sin = (struct sockaddr_in *)addr; | ||
a1 = (unsigned char *)&(sin->sin_addr); | ||
break; | ||
case AF_INET6: | ||
sin6 = (struct sockaddr_in6 *)addr; | ||
a1 = (unsigned char *)&(sin6->sin6_addr); | ||
break; | ||
default: | ||
return false; | ||
break; | ||
} | ||
a2 = subnets[i].addr; | ||
|
||
int n_bytes = subnets[i].bits / 8; | ||
if (n_bytes && memcmp(a1, a2, n_bytes)) { | ||
continue; | ||
} | ||
|
||
int n_bits = subnets[i].bits % 8; | ||
if (n_bits) { | ||
int mask = (1<<n_bits)-1; | ||
if ((a1[n_bytes] & mask) != (a2[n_bytes] & mask)) { | ||
continue; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt | ||
// TODO: parse TCP6, parse V2 headers | ||
bool Proxy_Protocol::parse_header(unsigned char *header, size_t n, struct sockaddr_storage *out) { | ||
char src_ip[64], dst_ip[64]; | ||
int src_port, dst_port; | ||
|
||
// skip parsing if there's less data than the minimum V1 header | ||
if (n < 15) return false; | ||
if (memcmp(header, "PROXY ", 6)) return false; | ||
|
||
header[n - 2] = '\0'; // Zap CRLF | ||
header[n - 1] = '\0'; | ||
header += 6; | ||
if (!memcmp(header, "TCP4 ", 5)) { | ||
struct sockaddr_in *sin = (struct sockaddr_in *)out; | ||
|
||
header += 5; | ||
if (sscanf((char *)header, "%15s %15s %d %d", src_ip, dst_ip, &src_port, &dst_port) != 4) | ||
return false; | ||
src_ip[15] = '\0'; | ||
dst_ip[15] = '\0'; | ||
sin->sin_family = AF_INET; | ||
sin->sin_port = htons(src_port); | ||
if (inet_pton(sin->sin_family, src_ip, (void *)&sin->sin_addr) != 1) | ||
return false; | ||
} | ||
else if(!memcmp(header, "TCP6 ", 5)) { | ||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)out; | ||
|
||
header += 5; | ||
if (sscanf((char *)header, "%63s %63s %d %d", src_ip, dst_ip, &src_port, &dst_port) != 4) | ||
return false; | ||
src_ip[sizeof(dst_ip)-1] = '\0'; | ||
dst_ip[sizeof(dst_ip)-1] = '\0'; | ||
sin6->sin6_family = AF_INET; | ||
sin6->sin6_port = htons(src_port); | ||
if (inet_pton(sin6->sin6_family, src_ip, (void *)&sin6->sin6_addr) != 1) | ||
return false; | ||
} | ||
else if(!memcmp(header, "UNKNOWN", 7)) { | ||
// not sure how to deal with this | ||
out->ss_family = AF_UNIX; | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
|
||
return true; | ||
} |
Oops, something went wrong.