Separated the proxy protocol code

This commit is contained in:
Nikos Mavrogiannopoulos
2015-07-16 11:40:49 +02:00
parent 20937ee29a
commit 587fcdfc36
4 changed files with 240 additions and 203 deletions

View File

@@ -82,7 +82,7 @@ ocserv_SOURCES = main.c main-auth.c worker-vpn.c worker-auth.c tlslib.c \
sup-config/file.c sup-config/file.h main-sec-mod-cmd.c \
sup-config/radius.c sup-config/radius.h \
worker-bandwidth.c worker-bandwidth.h ctl.h main-ctl.h \
vasprintf.c vasprintf.h \
vasprintf.c vasprintf.h worker-proxyproto.c \
proc-search.c proc-search.h http-heads.h \
main-ban.c main-ban.h common-config.h \
str.c str.h gettime.h $(CCAN_SOURCES) $(HTTP_PARSER_SOURCES) \

237
src/worker-proxyproto.c Normal file
View File

@@ -0,0 +1,237 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This file is part of ocserv.
*
* ocserv 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 2 of the License, or
* (at your option) any later version.
*
* ocserv 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <worker.h>
/* This file implements the Proxy Protocol v2, as described in:
* http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt
*
* That allows to obtain the detailed peer information even when
* the session is received by a proxy.
*/
#define PROXY_HEADER_V2 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
#define PROXY_HEADER_V2_SIZE (sizeof(PROXY_HEADER_V2)-1)
#define AVAIL_HEADER_SIZE(hsize, want) { \
if (hsize < want) { \
oclog(ws, LOG_ERR, "invalid TLV header"); \
return; \
} \
hsize -= want; \
}
typedef struct proxy_hdr_v2 {
uint8_t sig[PROXY_HEADER_V2_SIZE];
uint8_t ver_cmd;
uint8_t family;
uint16_t len;
uint8_t data[520];
} _ATTR_PACKED proxy_hdr_v2;
#define PP2_TYPE_SSL 0x20
#define PP2_TYPE_SSL_CN 0x22
#define PP2_CLIENT_SSL 0x01
#define PP2_CLIENT_CERT_CONN 0x02
#define PP2_CLIENT_CERT_SESS 0x04
typedef struct pp2_tlv {
uint8_t type;
uint16_t length;
} _ATTR_PACKED pp2_tlv;
typedef struct pp2_tlv_ssl {
uint8_t client;
uint32_t verify;
} _ATTR_PACKED pp2_tlv_ssl;
static void parse_ssl_tlvs(struct worker_st *ws, uint8_t *data, int data_size)
{
pp2_tlv tlv;
while(data_size > 0) {
AVAIL_HEADER_SIZE(data_size, sizeof(pp2_tlv));
memcpy(&tlv, data, sizeof(pp2_tlv));
/* that seems to be in little endian */
tlv.length = htons(tlv.length);
data += sizeof(pp2_tlv);
oclog(ws, LOG_INFO, "proxy-hdr: TLV type %x", (unsigned)tlv.type);
if (tlv.type == PP2_TYPE_SSL) {
pp2_tlv_ssl tssl;
if (tlv.length < sizeof(pp2_tlv_ssl)) {
oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL header size is invalid");
continue;
}
tlv.length = sizeof(pp2_tlv_ssl);
AVAIL_HEADER_SIZE(data_size, tlv.length);
memcpy(&tssl, data, sizeof(pp2_tlv_ssl));
if ((tssl.client & PP2_CLIENT_SSL) &&
(tssl.client & PP2_CLIENT_CERT_SESS) &&
(tssl.verify == 0)) {
oclog(ws, LOG_INFO, "proxy-hdr: user has presented valid certificate");
ws->cert_auth_ok = 1;
}
} else if (tlv.type == PP2_TYPE_SSL_CN && ws->cert_auth_ok) {
if (tlv.length > sizeof(ws->cert_username)-1) {
oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL CN header size is too long");
continue;
}
AVAIL_HEADER_SIZE(data_size, tlv.length);
memcpy(ws->cert_username, data, tlv.length);
ws->cert_username[tlv.length] = 0;
oclog(ws, LOG_INFO, "proxy-hdr: user's name is '%s'", ws->cert_username);
} else {
AVAIL_HEADER_SIZE(data_size, tlv.length);
}
data += tlv.length;
}
}
/* This parses a version 2 Proxy protocol header (from haproxy).
*/
int parse_proxy_proto_header(struct worker_st *ws, int fd)
{
proxy_hdr_v2 hdr;
int data_size;
uint8_t cmd, family, proto;
uint8_t ver;
uint8_t *p;
int ret;
ret = recv_timeout(fd, &hdr, 16, 3);
if (ret < 0) {
oclog(ws, LOG_ERR,
"proxy-hdr: recv timed out");
return -1;
}
if (ret < 16) {
oclog(ws, LOG_ERR, "proxy-hdr: invalid v2 header size");
return -1;
}
if (memcmp(hdr.sig, PROXY_HEADER_V2, PROXY_HEADER_V2_SIZE) != 0) {
oclog(ws, LOG_ERR, "proxy-hdr: invalid v2 header");
return -1;
}
data_size = ntohs(hdr.len);
if (data_size >= sizeof(hdr.data)) {
oclog(ws, LOG_ERR, "proxy-hdr: too long v2 header size");
return -1;
}
ret = recv_timeout(fd, hdr.data, data_size, 3);
if (ret < 0) {
oclog(ws, LOG_ERR,
"proxy-hdr: recv data timed out");
return -1;
}
cmd = hdr.ver_cmd & 0x0f;
ver = (hdr.ver_cmd & 0xf0) >> 4;
if (ver != 0x02) {
oclog(ws, LOG_ERR, "proxy-hdr: unsupported version (%x), skipping message", (unsigned)ver);
return 0;
}
if (cmd != 0x01) {
if (hdr.family == 0)
oclog(ws, LOG_DEBUG, "proxy-hdr: received health check command");
else
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported command %x", (unsigned)cmd);
return -1;
}
family = (hdr.family & 0xf0) >> 4;
proto = hdr.family & 0x0f;
if (family != 0x1 && family != 0x2) {
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported family %x; skipping header", (unsigned)family);
return 0;
}
if ((proto != 0x1 && proto != 0x0)) {
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported protocol %x; skipping header", (unsigned)proto);
return 0;
}
p = hdr.data;
if (family == 0x01) { /* AF_INET */
struct sockaddr_in *sa = (void*)&ws->remote_addr;
if (data_size < 4) {
oclog(ws, LOG_INFO, "proxy-hdr: received not enough IPv4 data");
return 0;
}
memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
sa->sin_family = AF_INET;
memcpy(&sa->sin_port, p+8, 2);
memcpy(&sa->sin_addr, p, 4);
ws->remote_addr_len = sizeof(struct sockaddr_in);
p += 12;
data_size -= 12;
} else if (family == 0x02) { /* AF_INET6 */
struct sockaddr_in6 *sa = (void*)&ws->remote_addr;
if (data_size < 16) {
oclog(ws, LOG_INFO, "proxy-hdr: received not enough IPv6 data");
return 0;
}
memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
sa->sin6_family = AF_INET6;
sa->sin6_port = 0;
memcpy(&sa->sin6_addr, p, 16);
memcpy(&sa->sin6_port, p+32, 2);
ws->remote_addr_len = sizeof(struct sockaddr_in6);
p += 36;
data_size -= 36;
}
/* Find CN if needed */
if (ws->conn_type == SOCK_TYPE_UNIX && data_size > 0) {
parse_ssl_tlvs(ws, p, data_size);
}
return 0;
}

View File

@@ -373,208 +373,6 @@ static void exit_worker_reason(worker_st * ws, unsigned reason)
exit(1);
}
#define PROXY_HEADER_V2 "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
#define PROXY_HEADER_V2_SIZE (sizeof(PROXY_HEADER_V2)-1)
#define AVAIL_HEADER_SIZE(hsize, want) { \
if (hsize < want) { \
oclog(ws, LOG_ERR, "invalid TLV header"); \
return; \
} \
hsize -= want; \
}
typedef struct proxy_hdr_v2 {
uint8_t sig[PROXY_HEADER_V2_SIZE];
uint8_t ver_cmd;
uint8_t family;
uint16_t len;
uint8_t data[520];
} _ATTR_PACKED proxy_hdr_v2;
#define PP2_TYPE_SSL 0x20
#define PP2_TYPE_SSL_CN 0x22
#define PP2_CLIENT_SSL 0x01
#define PP2_CLIENT_CERT_CONN 0x02
#define PP2_CLIENT_CERT_SESS 0x04
typedef struct pp2_tlv {
uint8_t type;
uint16_t length;
} _ATTR_PACKED pp2_tlv;
typedef struct pp2_tlv_ssl {
uint8_t client;
uint32_t verify;
} _ATTR_PACKED pp2_tlv_ssl;
static void parse_ssl_tlvs(struct worker_st *ws, uint8_t *data, int data_size)
{
pp2_tlv tlv;
while(data_size > 0) {
AVAIL_HEADER_SIZE(data_size, sizeof(pp2_tlv));
memcpy(&tlv, data, sizeof(pp2_tlv));
/* that seems to be in little endian */
tlv.length = htons(tlv.length);
data += sizeof(pp2_tlv);
oclog(ws, LOG_INFO, "proxy-hdr: TLV type %x", (unsigned)tlv.type);
if (tlv.type == PP2_TYPE_SSL) {
pp2_tlv_ssl tssl;
if (tlv.length < sizeof(pp2_tlv_ssl)) {
oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL header size is invalid");
continue;
}
tlv.length = sizeof(pp2_tlv_ssl);
AVAIL_HEADER_SIZE(data_size, tlv.length);
memcpy(&tssl, data, sizeof(pp2_tlv_ssl));
if ((tssl.client & PP2_CLIENT_SSL) &&
(tssl.client & PP2_CLIENT_CERT_SESS) &&
(tssl.verify == 0)) {
oclog(ws, LOG_INFO, "proxy-hdr: user has presented valid certificate");
ws->cert_auth_ok = 1;
}
} else if (tlv.type == PP2_TYPE_SSL_CN && ws->cert_auth_ok) {
if (tlv.length > sizeof(ws->cert_username)-1) {
oclog(ws, LOG_ERR, "proxy-hdr: TLV SSL CN header size is too long");
continue;
}
AVAIL_HEADER_SIZE(data_size, tlv.length);
memcpy(ws->cert_username, data, tlv.length);
ws->cert_username[tlv.length] = 0;
oclog(ws, LOG_INFO, "proxy-hdr: user's name is '%s'", ws->cert_username);
} else {
AVAIL_HEADER_SIZE(data_size, tlv.length);
}
data += tlv.length;
}
}
/* This parses a version 2 Proxy protocol header (from haproxy).
*/
static
int parse_proxy_proto_header(struct worker_st *ws, int fd)
{
proxy_hdr_v2 hdr;
int data_size;
uint8_t cmd, family, proto;
uint8_t ver;
uint8_t *p;
int ret;
ret = recv_timeout(fd, &hdr, 16, 3);
if (ret < 0) {
oclog(ws, LOG_ERR,
"proxy-hdr: recv timed out");
return -1;
}
if (ret < 16) {
oclog(ws, LOG_ERR, "proxy-hdr: invalid v2 header size");
return -1;
}
if (memcmp(hdr.sig, PROXY_HEADER_V2, PROXY_HEADER_V2_SIZE) != 0) {
oclog(ws, LOG_ERR, "proxy-hdr: invalid v2 header");
return -1;
}
data_size = ntohs(hdr.len);
if (data_size >= sizeof(hdr.data)) {
oclog(ws, LOG_ERR, "proxy-hdr: too long v2 header size");
return -1;
}
ret = recv_timeout(fd, hdr.data, data_size, 3);
if (ret < 0) {
oclog(ws, LOG_ERR,
"proxy-hdr: recv data timed out");
return -1;
}
cmd = hdr.ver_cmd & 0x0f;
ver = (hdr.ver_cmd & 0xf0) >> 4;
if (ver != 0x02) {
oclog(ws, LOG_ERR, "proxy-hdr: unsupported version (%x), skipping message", (unsigned)ver);
return 0;
}
if (cmd != 0x01) {
if (hdr.family == 0)
oclog(ws, LOG_DEBUG, "proxy-hdr: received health check command");
else
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported command %x", (unsigned)cmd);
return -1;
}
family = (hdr.family & 0xf0) >> 4;
proto = hdr.family & 0x0f;
if (family != 0x1 && family != 0x2) {
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported family %x; skipping header", (unsigned)family);
return 0;
}
if ((proto != 0x1 && proto != 0x0)) {
oclog(ws, LOG_ERR, "proxy-hdr: received unsupported protocol %x; skipping header", (unsigned)proto);
return 0;
}
p = hdr.data;
if (family == 0x01) { /* AF_INET */
struct sockaddr_in *sa = (void*)&ws->remote_addr;
if (data_size < 4) {
oclog(ws, LOG_INFO, "proxy-hdr: received not enough IPv4 data");
return 0;
}
memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
sa->sin_family = AF_INET;
memcpy(&sa->sin_port, p+8, 2);
memcpy(&sa->sin_addr, p, 4);
ws->remote_addr_len = sizeof(struct sockaddr_in);
p += 12;
data_size -= 12;
} else if (family == 0x02) { /* AF_INET6 */
struct sockaddr_in6 *sa = (void*)&ws->remote_addr;
if (data_size < 16) {
oclog(ws, LOG_INFO, "proxy-hdr: received not enough IPv6 data");
return 0;
}
memset(&ws->remote_addr, 0, sizeof(ws->remote_addr));
sa->sin6_family = AF_INET6;
sa->sin6_port = 0;
memcpy(&sa->sin6_addr, p, 16);
memcpy(&sa->sin6_port, p+32, 2);
ws->remote_addr_len = sizeof(struct sockaddr_in6);
p += 36;
data_size -= 36;
}
/* Find CN if needed */
if (ws->conn_type == SOCK_TYPE_UNIX && data_size > 0) {
parse_ssl_tlvs(ws, p, data_size);
}
return 0;
}
/* vpn_server:
* @ws: an initialized worker structure

View File

@@ -359,6 +359,8 @@ int send_msg_to_main(worker_st *ws, uint8_t cmd,
return send_msg(ws, ws->cmd_fd, cmd, msg, get_size, pack);
}
int parse_proxy_proto_header(struct worker_st *ws, int fd);
/* after that time (secs) of inactivity in the UDP part, connection switches to
* TCP (if activity occurs there).
*/