Add proxy protocol support
Adds a server-specific parameter, "use_proxy_protocol". If enabled, a header string is sent to the backend when a routing session connection changes state to MXS_AUTH_STATE_CONNECTED. The string contains the real client IP and port.
This commit is contained in:
@ -94,6 +94,7 @@ typedef struct server
|
|||||||
uint8_t charset; /**< Default server character set */
|
uint8_t charset; /**< Default server character set */
|
||||||
bool is_active; /**< Server is active and has not been "destroyed" */
|
bool is_active; /**< Server is active and has not been "destroyed" */
|
||||||
bool created_online; /**< Whether this server was created after startup */
|
bool created_online; /**< Whether this server was created after startup */
|
||||||
|
bool use_proxy_protocol; /**< Send proxy-protocol header when connecting client sessions. */
|
||||||
#if defined(SS_DEBUG)
|
#if defined(SS_DEBUG)
|
||||||
skygw_chk_t server_chk_tail;
|
skygw_chk_t server_chk_tail;
|
||||||
#endif
|
#endif
|
||||||
@ -190,6 +191,8 @@ enum
|
|||||||
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \
|
(((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \
|
||||||
(SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE))
|
(SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE))
|
||||||
|
|
||||||
|
extern const char USE_PROXY_PROTOCOL[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Allocate a new server
|
* @brief Allocate a new server
|
||||||
*
|
*
|
||||||
|
@ -202,6 +202,7 @@ static const char *server_params[] =
|
|||||||
"ssl_key",
|
"ssl_key",
|
||||||
"ssl_version",
|
"ssl_version",
|
||||||
"ssl_cert_verify_depth",
|
"ssl_cert_verify_depth",
|
||||||
|
USE_PROXY_PROTOCOL,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2795,6 +2796,8 @@ int create_new_server(CONFIG_CONTEXT *obj)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server->use_proxy_protocol = config_get_bool(obj->parameters, USE_PROXY_PROTOCOL);
|
||||||
|
|
||||||
MXS_CONFIG_PARAMETER *params = obj->parameters;
|
MXS_CONFIG_PARAMETER *params = obj->parameters;
|
||||||
|
|
||||||
server->server_ssl = make_ssl_structure(obj, false, &error_count);
|
server->server_ssl = make_ssl_structure(obj, false, &error_count);
|
||||||
|
@ -55,6 +55,8 @@
|
|||||||
/** The latin1 charset */
|
/** The latin1 charset */
|
||||||
#define SERVER_DEFAULT_CHARSET 0x08
|
#define SERVER_DEFAULT_CHARSET 0x08
|
||||||
|
|
||||||
|
const char USE_PROXY_PROTOCOL[] = "use_proxy_protocol";
|
||||||
|
|
||||||
static SPINLOCK server_spin = SPINLOCK_INIT;
|
static SPINLOCK server_spin = SPINLOCK_INIT;
|
||||||
static SERVER *allServers = NULL;
|
static SERVER *allServers = NULL;
|
||||||
|
|
||||||
@ -139,6 +141,7 @@ SERVER* server_alloc(const char *name, const char *address, unsigned short port,
|
|||||||
server->is_active = true;
|
server->is_active = true;
|
||||||
server->created_online = false;
|
server->created_online = false;
|
||||||
server->charset = SERVER_DEFAULT_CHARSET;
|
server->charset = SERVER_DEFAULT_CHARSET;
|
||||||
|
server->use_proxy_protocol = false;
|
||||||
|
|
||||||
spinlock_acquire(&server_spin);
|
spinlock_acquire(&server_spin);
|
||||||
server->next = allServers;
|
server->next = allServers;
|
||||||
@ -622,6 +625,10 @@ dprintServer(DCB *dcb, const SERVER *server)
|
|||||||
dcb_printf(dcb, "\tSSL CA certificate: %s\n",
|
dcb_printf(dcb, "\tSSL CA certificate: %s\n",
|
||||||
l->ssl_ca_cert ? l->ssl_ca_cert : "null");
|
l->ssl_ca_cert ? l->ssl_ca_cert : "null");
|
||||||
}
|
}
|
||||||
|
if (server->use_proxy_protocol)
|
||||||
|
{
|
||||||
|
dcb_printf(dcb, "\tProxy protocol enabled.\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1194,6 +1201,11 @@ static bool create_server_config(const SERVER *server, const char *filename)
|
|||||||
dprintf(file, "persistmaxtime=%ld\n", server->persistmaxtime);
|
dprintf(file, "persistmaxtime=%ld\n", server->persistmaxtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server->use_proxy_protocol)
|
||||||
|
{
|
||||||
|
dprintf(file, "%s=yes\n", USE_PROXY_PROTOCOL);
|
||||||
|
}
|
||||||
|
|
||||||
for (SERVER_PARAM *p = server->parameters; p; p = p->next)
|
for (SERVER_PARAM *p = server->parameters; p; p = p->next)
|
||||||
{
|
{
|
||||||
if (p->active)
|
if (p->active)
|
||||||
|
@ -77,6 +77,9 @@ static int gw_send_change_user_to_backend(char *dbname,
|
|||||||
char *user,
|
char *user,
|
||||||
uint8_t *passwd,
|
uint8_t *passwd,
|
||||||
MySQLProtocol *conn);
|
MySQLProtocol *conn);
|
||||||
|
static void gw_send_proxy_protocol_header(DCB *backend_dcb);
|
||||||
|
static bool get_ip_string_and_port(struct sockaddr_storage *sa, char *ip, int iplen,
|
||||||
|
in_port_t *port_out);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The module entry point routine. It is this routine that
|
* The module entry point routine. It is this routine that
|
||||||
@ -217,6 +220,11 @@ static int gw_create_backend_connection(DCB *backend_dcb,
|
|||||||
server->port,
|
server->port,
|
||||||
protocol->fd,
|
protocol->fd,
|
||||||
session->client_dcb->fd);
|
session->client_dcb->fd);
|
||||||
|
|
||||||
|
if (server->use_proxy_protocol)
|
||||||
|
{
|
||||||
|
gw_send_proxy_protocol_header(backend_dcb);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
@ -919,6 +927,10 @@ static int gw_write_backend_event(DCB *dcb)
|
|||||||
if (backend_protocol->protocol_auth_state == MXS_AUTH_STATE_PENDING_CONNECT)
|
if (backend_protocol->protocol_auth_state == MXS_AUTH_STATE_PENDING_CONNECT)
|
||||||
{
|
{
|
||||||
backend_protocol->protocol_auth_state = MXS_AUTH_STATE_CONNECTED;
|
backend_protocol->protocol_auth_state = MXS_AUTH_STATE_CONNECTED;
|
||||||
|
if (dcb->server->use_proxy_protocol)
|
||||||
|
{
|
||||||
|
gw_send_proxy_protocol_header(dcb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1822,3 +1834,136 @@ gw_send_change_user_to_backend(char *dbname,
|
|||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Send proxy protocol header. See
|
||||||
|
* http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||||
|
* for more information. Currently only supports the text version (v1) of
|
||||||
|
* the protocol. Binary version may be added when the feature has been confirmed
|
||||||
|
* to work.
|
||||||
|
*
|
||||||
|
* @param backend_dcb The target dcb.
|
||||||
|
*/
|
||||||
|
static void gw_send_proxy_protocol_header(DCB *backend_dcb)
|
||||||
|
{
|
||||||
|
// TODO: Add support for chained proxies. Requires reading the client header.
|
||||||
|
|
||||||
|
const DCB *client_dcb = backend_dcb->session->client_dcb;
|
||||||
|
const int client_fd = client_dcb->fd;
|
||||||
|
const sa_family_t family = client_dcb->ip.ss_family;
|
||||||
|
const char *family_str = NULL;
|
||||||
|
|
||||||
|
struct sockaddr_storage sa_peer;
|
||||||
|
struct sockaddr_storage sa_local;
|
||||||
|
socklen_t sa_peer_len = sizeof(sa_peer);
|
||||||
|
socklen_t sa_local_len = sizeof(sa_local);
|
||||||
|
|
||||||
|
/* Fill in peer's socket address. */
|
||||||
|
if (getpeername(client_fd, (struct sockaddr *)&sa_peer, &sa_peer_len) == -1)
|
||||||
|
{
|
||||||
|
MXS_ERROR("'%s' failed on file descriptor '%d'.", "getpeername()", client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill in this socket's local address. */
|
||||||
|
if (getsockname(client_fd, (struct sockaddr *)&sa_local, &sa_local_len) == -1)
|
||||||
|
{
|
||||||
|
MXS_ERROR("'%s' failed on file descriptor '%d'.", "getsockname()", client_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ss_dassert(sa_peer.ss_family == sa_local.ss_family);
|
||||||
|
|
||||||
|
char peer_ip[INET6_ADDRSTRLEN];
|
||||||
|
char maxscale_ip[INET6_ADDRSTRLEN];
|
||||||
|
in_port_t peer_port;
|
||||||
|
in_port_t maxscale_port;
|
||||||
|
|
||||||
|
if (!get_ip_string_and_port(&sa_peer, peer_ip, sizeof(peer_ip), &peer_port) ||
|
||||||
|
!get_ip_string_and_port(&sa_local, maxscale_ip, sizeof(maxscale_ip), &maxscale_port))
|
||||||
|
{
|
||||||
|
MXS_ERROR("Could not convert network address to string form.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
family_str = "TCP4";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AF_INET6:
|
||||||
|
family_str = "TCP6";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
family_str = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rval;
|
||||||
|
char proxy_header[108]; // 108 is the worst-case length
|
||||||
|
if (family == AF_INET || family == AF_INET6)
|
||||||
|
{
|
||||||
|
rval = snprintf(proxy_header, sizeof(proxy_header), "PROXY %s %s %s %d %d\r\n",
|
||||||
|
family_str, peer_ip, maxscale_ip, peer_port, maxscale_port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rval = snprintf(proxy_header, sizeof(proxy_header), "PROXY %s\r\n", family_str);
|
||||||
|
}
|
||||||
|
if (rval < 0 || rval >= sizeof(proxy_header))
|
||||||
|
{
|
||||||
|
MXS_ERROR("Proxy header printing error, produced '%s'.", proxy_header);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GWBUF *headerbuf = gwbuf_alloc_and_load(strlen(proxy_header), proxy_header);
|
||||||
|
if (headerbuf)
|
||||||
|
{
|
||||||
|
if (!dcb_write(backend_dcb, headerbuf))
|
||||||
|
{
|
||||||
|
gwbuf_free(headerbuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read IP and port from socket address structure, return IP as string and port
|
||||||
|
* as host byte order integer.
|
||||||
|
*
|
||||||
|
* @param sa A sockaddr_storage containing either an IPv4 or v6 address
|
||||||
|
* @param ip Pointer to output array
|
||||||
|
* @param iplen Output array length
|
||||||
|
* @param port_out Port number output
|
||||||
|
*/
|
||||||
|
static bool get_ip_string_and_port(struct sockaddr_storage *sa,
|
||||||
|
char *ip, int iplen, in_port_t *port_out)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
in_port_t port;
|
||||||
|
|
||||||
|
switch (sa->ss_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
{
|
||||||
|
struct sockaddr_in *sock_info = (struct sockaddr_in *)sa;
|
||||||
|
struct in_addr *addr = &(sock_info->sin_addr);
|
||||||
|
success = (inet_ntop(AF_INET, addr, ip, iplen) != NULL);
|
||||||
|
port = ntohs(sock_info->sin_port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AF_INET6:
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *sock_info = (struct sockaddr_in6 *)sa;
|
||||||
|
struct in6_addr *addr = &(sock_info->sin6_addr);
|
||||||
|
success = (inet_ntop(AF_INET6, addr, ip, iplen) != NULL);
|
||||||
|
port = ntohs(sock_info->sin6_port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
*port_out = port;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user