diff --git a/include/maxscale/server.h b/include/maxscale/server.h index a2f88fea3..3c062953e 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -94,6 +94,7 @@ typedef struct server uint8_t charset; /**< Default server character set */ bool is_active; /**< Server is active and has not been "destroyed" */ 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) skygw_chk_t server_chk_tail; #endif @@ -190,6 +191,8 @@ enum (((server)->status & (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE|SERVER_MAINT)) == \ (SERVER_RUNNING|SERVER_MASTER|SERVER_SLAVE)) +extern const char USE_PROXY_PROTOCOL[]; + /** * @brief Allocate a new server * diff --git a/server/core/config.cc b/server/core/config.cc index 1967f0f62..1bed3e9c6 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -202,6 +202,7 @@ static const char *server_params[] = "ssl_key", "ssl_version", "ssl_cert_verify_depth", + USE_PROXY_PROTOCOL, 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; server->server_ssl = make_ssl_structure(obj, false, &error_count); diff --git a/server/core/server.cc b/server/core/server.cc index 89e0d0c41..11a905af4 100644 --- a/server/core/server.cc +++ b/server/core/server.cc @@ -55,6 +55,8 @@ /** The latin1 charset */ #define SERVER_DEFAULT_CHARSET 0x08 +const char USE_PROXY_PROTOCOL[] = "use_proxy_protocol"; + static SPINLOCK server_spin = SPINLOCK_INIT; 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->created_online = false; server->charset = SERVER_DEFAULT_CHARSET; + server->use_proxy_protocol = false; spinlock_acquire(&server_spin); server->next = allServers; @@ -622,6 +625,10 @@ dprintServer(DCB *dcb, const SERVER *server) dcb_printf(dcb, "\tSSL CA certificate: %s\n", 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); } + if (server->use_proxy_protocol) + { + dprintf(file, "%s=yes\n", USE_PROXY_PROTOCOL); + } + for (SERVER_PARAM *p = server->parameters; p; p = p->next) { if (p->active) diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 9e91bc2c3..b457ef067 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -77,6 +77,9 @@ static int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, 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 @@ -217,6 +220,11 @@ static int gw_create_backend_connection(DCB *backend_dcb, server->port, protocol->fd, session->client_dcb->fd); + + if (server->use_proxy_protocol) + { + gw_send_proxy_protocol_header(backend_dcb); + } break; 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) { backend_protocol->protocol_auth_state = MXS_AUTH_STATE_CONNECTED; + if (dcb->server->use_proxy_protocol) + { + gw_send_proxy_protocol_header(dcb); + } } else { @@ -1822,3 +1834,136 @@ gw_send_change_user_to_backend(char *dbname, } 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; +}