nginx/src/core/ngx_proxy_protocol.c

615 lines
16 KiB
C

/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_PROXY_PROTOCOL_AF_INET 1
#define NGX_PROXY_PROTOCOL_AF_INET6 2
#define ngx_proxy_protocol_parse_uint16(p) \
( ((uint16_t) (p)[0] << 8) \
+ ( (p)[1]) )
#define ngx_proxy_protocol_parse_uint32(p) \
( ((uint32_t) (p)[0] << 24) \
+ ( (p)[1] << 16) \
+ ( (p)[2] << 8) \
+ ( (p)[3]) )
typedef struct {
u_char signature[12];
u_char version_command;
u_char family_transport;
u_char len[2];
} ngx_proxy_protocol_header_t;
typedef struct {
u_char src_addr[4];
u_char dst_addr[4];
u_char src_port[2];
u_char dst_port[2];
} ngx_proxy_protocol_inet_addrs_t;
typedef struct {
u_char src_addr[16];
u_char dst_addr[16];
u_char src_port[2];
u_char dst_port[2];
} ngx_proxy_protocol_inet6_addrs_t;
typedef struct {
u_char type;
u_char len[2];
} ngx_proxy_protocol_tlv_t;
typedef struct {
u_char client;
u_char verify[4];
} ngx_proxy_protocol_tlv_ssl_t;
typedef struct {
ngx_str_t name;
ngx_uint_t type;
} ngx_proxy_protocol_tlv_entry_t;
static u_char *ngx_proxy_protocol_read_addr(ngx_connection_t *c, u_char *p,
u_char *last, ngx_str_t *addr);
static u_char *ngx_proxy_protocol_read_port(u_char *p, u_char *last,
in_port_t *port, u_char sep);
static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf,
u_char *last);
static ngx_int_t ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c,
ngx_str_t *tlvs, ngx_uint_t type, ngx_str_t *value);
static ngx_proxy_protocol_tlv_entry_t ngx_proxy_protocol_tlv_entries[] = {
{ ngx_string("alpn"), 0x01 },
{ ngx_string("authority"), 0x02 },
{ ngx_string("unique_id"), 0x05 },
{ ngx_string("ssl"), 0x20 },
{ ngx_string("netns"), 0x30 },
{ ngx_null_string, 0x00 }
};
static ngx_proxy_protocol_tlv_entry_t ngx_proxy_protocol_tlv_ssl_entries[] = {
{ ngx_string("version"), 0x21 },
{ ngx_string("cn"), 0x22 },
{ ngx_string("cipher"), 0x23 },
{ ngx_string("sig_alg"), 0x24 },
{ ngx_string("key_alg"), 0x25 },
{ ngx_null_string, 0x00 }
};
u_char *
ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last)
{
size_t len;
u_char *p;
ngx_proxy_protocol_t *pp;
static const u_char signature[] = "\r\n\r\n\0\r\nQUIT\n";
p = buf;
len = last - buf;
if (len >= sizeof(ngx_proxy_protocol_header_t)
&& ngx_memcmp(p, signature, sizeof(signature) - 1) == 0)
{
return ngx_proxy_protocol_v2_read(c, buf, last);
}
if (len < 8 || ngx_strncmp(p, "PROXY ", 6) != 0) {
goto invalid;
}
p += 6;
len -= 6;
if (len >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol unknown protocol");
p += 7;
goto skip;
}
if (len < 5 || ngx_strncmp(p, "TCP", 3) != 0
|| (p[3] != '4' && p[3] != '6') || p[4] != ' ')
{
goto invalid;
}
p += 5;
pp = ngx_pcalloc(c->pool, sizeof(ngx_proxy_protocol_t));
if (pp == NULL) {
return NULL;
}
p = ngx_proxy_protocol_read_addr(c, p, last, &pp->src_addr);
if (p == NULL) {
goto invalid;
}
p = ngx_proxy_protocol_read_addr(c, p, last, &pp->dst_addr);
if (p == NULL) {
goto invalid;
}
p = ngx_proxy_protocol_read_port(p, last, &pp->src_port, ' ');
if (p == NULL) {
goto invalid;
}
p = ngx_proxy_protocol_read_port(p, last, &pp->dst_port, CR);
if (p == NULL) {
goto invalid;
}
if (p == last) {
goto invalid;
}
if (*p++ != LF) {
goto invalid;
}
ngx_log_debug4(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol src: %V %d, dst: %V %d",
&pp->src_addr, pp->src_port, &pp->dst_addr, pp->dst_port);
c->proxy_protocol = pp;
return p;
skip:
for ( /* void */ ; p < last - 1; p++) {
if (p[0] == CR && p[1] == LF) {
return p + 2;
}
}
invalid:
for (p = buf; p < last; p++) {
if (*p == CR || *p == LF) {
break;
}
}
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"broken header: \"%*s\"", (size_t) (p - buf), buf);
return NULL;
}
static u_char *
ngx_proxy_protocol_read_addr(ngx_connection_t *c, u_char *p, u_char *last,
ngx_str_t *addr)
{
size_t len;
u_char ch, *pos;
pos = p;
for ( ;; ) {
if (p == last) {
return NULL;
}
ch = *p++;
if (ch == ' ') {
break;
}
if (ch != ':' && ch != '.'
&& (ch < 'a' || ch > 'f')
&& (ch < 'A' || ch > 'F')
&& (ch < '0' || ch > '9'))
{
return NULL;
}
}
len = p - pos - 1;
addr->data = ngx_pnalloc(c->pool, len);
if (addr->data == NULL) {
return NULL;
}
ngx_memcpy(addr->data, pos, len);
addr->len = len;
return p;
}
static u_char *
ngx_proxy_protocol_read_port(u_char *p, u_char *last, in_port_t *port,
u_char sep)
{
size_t len;
u_char *pos;
ngx_int_t n;
pos = p;
for ( ;; ) {
if (p == last) {
return NULL;
}
if (*p++ == sep) {
break;
}
}
len = p - pos - 1;
n = ngx_atoi(pos, len);
if (n < 0 || n > 65535) {
return NULL;
}
*port = (in_port_t) n;
return p;
}
u_char *
ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last)
{
ngx_uint_t port, lport;
if (last - buf < NGX_PROXY_PROTOCOL_V1_MAX_HEADER) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"too small buffer for PROXY protocol");
return NULL;
}
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
return NULL;
}
switch (c->sockaddr->sa_family) {
case AF_INET:
buf = ngx_cpymem(buf, "PROXY TCP4 ", sizeof("PROXY TCP4 ") - 1);
break;
#if (NGX_HAVE_INET6)
case AF_INET6:
buf = ngx_cpymem(buf, "PROXY TCP6 ", sizeof("PROXY TCP6 ") - 1);
break;
#endif
default:
return ngx_cpymem(buf, "PROXY UNKNOWN" CRLF,
sizeof("PROXY UNKNOWN" CRLF) - 1);
}
buf += ngx_sock_ntop(c->sockaddr, c->socklen, buf, last - buf, 0);
*buf++ = ' ';
buf += ngx_sock_ntop(c->local_sockaddr, c->local_socklen, buf, last - buf,
0);
port = ngx_inet_get_port(c->sockaddr);
lport = ngx_inet_get_port(c->local_sockaddr);
return ngx_slprintf(buf, last, " %ui %ui" CRLF, port, lport);
}
static u_char *
ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last)
{
u_char *end;
size_t len;
socklen_t socklen;
ngx_uint_t version, command, family, transport;
ngx_sockaddr_t src_sockaddr, dst_sockaddr;
ngx_proxy_protocol_t *pp;
ngx_proxy_protocol_header_t *header;
ngx_proxy_protocol_inet_addrs_t *in;
#if (NGX_HAVE_INET6)
ngx_proxy_protocol_inet6_addrs_t *in6;
#endif
header = (ngx_proxy_protocol_header_t *) buf;
buf += sizeof(ngx_proxy_protocol_header_t);
version = header->version_command >> 4;
if (version != 2) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"unknown PROXY protocol version: %ui", version);
return NULL;
}
len = ngx_proxy_protocol_parse_uint16(header->len);
if ((size_t) (last - buf) < len) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "header is too large");
return NULL;
}
end = buf + len;
command = header->version_command & 0x0f;
/* only PROXY is supported */
if (command != 1) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 unsupported command %ui", command);
return end;
}
transport = header->family_transport & 0x0f;
/* only STREAM is supported */
if (transport != 1) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 unsupported transport %ui",
transport);
return end;
}
pp = ngx_pcalloc(c->pool, sizeof(ngx_proxy_protocol_t));
if (pp == NULL) {
return NULL;
}
family = header->family_transport >> 4;
switch (family) {
case NGX_PROXY_PROTOCOL_AF_INET:
if ((size_t) (end - buf) < sizeof(ngx_proxy_protocol_inet_addrs_t)) {
return NULL;
}
in = (ngx_proxy_protocol_inet_addrs_t *) buf;
src_sockaddr.sockaddr_in.sin_family = AF_INET;
src_sockaddr.sockaddr_in.sin_port = 0;
ngx_memcpy(&src_sockaddr.sockaddr_in.sin_addr, in->src_addr, 4);
dst_sockaddr.sockaddr_in.sin_family = AF_INET;
dst_sockaddr.sockaddr_in.sin_port = 0;
ngx_memcpy(&dst_sockaddr.sockaddr_in.sin_addr, in->dst_addr, 4);
pp->src_port = ngx_proxy_protocol_parse_uint16(in->src_port);
pp->dst_port = ngx_proxy_protocol_parse_uint16(in->dst_port);
socklen = sizeof(struct sockaddr_in);
buf += sizeof(ngx_proxy_protocol_inet_addrs_t);
break;
#if (NGX_HAVE_INET6)
case NGX_PROXY_PROTOCOL_AF_INET6:
if ((size_t) (end - buf) < sizeof(ngx_proxy_protocol_inet6_addrs_t)) {
return NULL;
}
in6 = (ngx_proxy_protocol_inet6_addrs_t *) buf;
src_sockaddr.sockaddr_in6.sin6_family = AF_INET6;
src_sockaddr.sockaddr_in6.sin6_port = 0;
ngx_memcpy(&src_sockaddr.sockaddr_in6.sin6_addr, in6->src_addr, 16);
dst_sockaddr.sockaddr_in6.sin6_family = AF_INET6;
dst_sockaddr.sockaddr_in6.sin6_port = 0;
ngx_memcpy(&dst_sockaddr.sockaddr_in6.sin6_addr, in6->dst_addr, 16);
pp->src_port = ngx_proxy_protocol_parse_uint16(in6->src_port);
pp->dst_port = ngx_proxy_protocol_parse_uint16(in6->dst_port);
socklen = sizeof(struct sockaddr_in6);
buf += sizeof(ngx_proxy_protocol_inet6_addrs_t);
break;
#endif
default:
ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 unsupported address family %ui",
family);
return end;
}
pp->src_addr.data = ngx_pnalloc(c->pool, NGX_SOCKADDR_STRLEN);
if (pp->src_addr.data == NULL) {
return NULL;
}
pp->src_addr.len = ngx_sock_ntop(&src_sockaddr.sockaddr, socklen,
pp->src_addr.data, NGX_SOCKADDR_STRLEN, 0);
pp->dst_addr.data = ngx_pnalloc(c->pool, NGX_SOCKADDR_STRLEN);
if (pp->dst_addr.data == NULL) {
return NULL;
}
pp->dst_addr.len = ngx_sock_ntop(&dst_sockaddr.sockaddr, socklen,
pp->dst_addr.data, NGX_SOCKADDR_STRLEN, 0);
ngx_log_debug4(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 src: %V %d, dst: %V %d",
&pp->src_addr, pp->src_port, &pp->dst_addr, pp->dst_port);
if (buf < end) {
pp->tlvs.data = ngx_pnalloc(c->pool, end - buf);
if (pp->tlvs.data == NULL) {
return NULL;
}
ngx_memcpy(pp->tlvs.data, buf, end - buf);
pp->tlvs.len = end - buf;
}
c->proxy_protocol = pp;
return end;
}
ngx_int_t
ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name,
ngx_str_t *value)
{
u_char *p;
size_t n;
uint32_t verify;
ngx_str_t ssl, *tlvs;
ngx_int_t rc, type;
ngx_proxy_protocol_tlv_ssl_t *tlv_ssl;
ngx_proxy_protocol_tlv_entry_t *te;
if (c->proxy_protocol == NULL) {
return NGX_DECLINED;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 get tlv \"%V\"", name);
te = ngx_proxy_protocol_tlv_entries;
tlvs = &c->proxy_protocol->tlvs;
p = name->data;
n = name->len;
if (n >= 4 && p[0] == 's' && p[1] == 's' && p[2] == 'l' && p[3] == '_') {
rc = ngx_proxy_protocol_lookup_tlv(c, tlvs, 0x20, &ssl);
if (rc != NGX_OK) {
return rc;
}
if (ssl.len < sizeof(ngx_proxy_protocol_tlv_ssl_t)) {
return NGX_ERROR;
}
p += 4;
n -= 4;
if (n == 6 && ngx_strncmp(p, "verify", 6) == 0) {
tlv_ssl = (ngx_proxy_protocol_tlv_ssl_t *) ssl.data;
verify = ngx_proxy_protocol_parse_uint32(tlv_ssl->verify);
value->data = ngx_pnalloc(c->pool, NGX_INT32_LEN);
if (value->data == NULL) {
return NGX_ERROR;
}
value->len = ngx_sprintf(value->data, "%uD", verify)
- value->data;
return NGX_OK;
}
ssl.data += sizeof(ngx_proxy_protocol_tlv_ssl_t);
ssl.len -= sizeof(ngx_proxy_protocol_tlv_ssl_t);
te = ngx_proxy_protocol_tlv_ssl_entries;
tlvs = &ssl;
}
if (n >= 2 && p[0] == '0' && p[1] == 'x') {
type = ngx_hextoi(p + 2, n - 2);
if (type == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"invalid PROXY protocol TLV \"%V\"", name);
return NGX_ERROR;
}
return ngx_proxy_protocol_lookup_tlv(c, tlvs, type, value);
}
for ( /* void */ ; te->type; te++) {
if (te->name.len == n && ngx_strncmp(te->name.data, p, n) == 0) {
return ngx_proxy_protocol_lookup_tlv(c, tlvs, te->type, value);
}
}
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"unknown PROXY protocol TLV \"%V\"", name);
return NGX_DECLINED;
}
static ngx_int_t
ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c, ngx_str_t *tlvs,
ngx_uint_t type, ngx_str_t *value)
{
u_char *p;
size_t n, len;
ngx_proxy_protocol_tlv_t *tlv;
ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
"PROXY protocol v2 lookup tlv:%02xi", type);
p = tlvs->data;
n = tlvs->len;
while (n) {
if (n < sizeof(ngx_proxy_protocol_tlv_t)) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV");
return NGX_ERROR;
}
tlv = (ngx_proxy_protocol_tlv_t *) p;
len = ngx_proxy_protocol_parse_uint16(tlv->len);
p += sizeof(ngx_proxy_protocol_tlv_t);
n -= sizeof(ngx_proxy_protocol_tlv_t);
if (n < len) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV");
return NGX_ERROR;
}
if (tlv->type == type) {
value->data = p;
value->len = len;
return NGX_OK;
}
p += len;
n -= len;
}
return NGX_DECLINED;
}