view src/core/ngx_proxy_protocol.c @ 7251:416953ef0428

Core: added processing of version 2 of the PROXY protocol. The protocol used on inbound connection is auto-detected and corresponding parser is used to extract passed addresses. TLV parameters are ignored. The maximum supported size of PROXY protocol header is 107 bytes (similar to version 1).
author Vladimir Homutov <vl@nginx.com>
date Thu, 22 Mar 2018 15:55:28 +0300
parents b3b7e33083ac
children 7bdab16c55f1
line wrap: on
line source


/*
 * Copyright (C) Roman Arutyunyan
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>


#define NGX_PP_V2_SIGLEN         12
#define NGX_PP_V2_CMD_PROXY      1
#define NGX_PP_V2_STREAM         1

#define NGX_PP_V2_AF_UNSPEC      0
#define NGX_PP_V2_AF_INET        1
#define NGX_PP_V2_AF_INET6       2


#define ngx_pp_v2_get_u16(p)                                                  \
    ( ((uint16_t) ((u_char *) (p))[0] << 8)                                   \
    + (           ((u_char *) (p))[1]) )


typedef struct {
    u_char                       signature[NGX_PP_V2_SIGLEN];
    u_char                       ver_cmd;
    u_char                       fam_transp;
    u_char                       len[2];
} ngx_pp_v2_header_t;


typedef struct {
    u_char                       src[4];
    u_char                       dst[4];
    u_char                       sport[2];
    u_char                       dport[2];
} ngx_pp_v2_inet_addrs_t;


typedef struct {
    u_char                       src[16];
    u_char                       dst[16];
    u_char                       sport[2];
    u_char                       dport[2];
} ngx_pp_v2_inet6_addrs_t;


typedef union {
    ngx_pp_v2_inet_addrs_t       inet;
    ngx_pp_v2_inet6_addrs_t      inet6;
} ngx_pp_v2_addrs_t;


static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf,
    u_char *last);

static const u_char ngx_pp_v2_signature[NGX_PP_V2_SIGLEN] =
    { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };


u_char *
ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last)
{
    size_t     len;
    u_char     ch, *p, *addr, *port;
    ngx_int_t  n;

    p = buf;
    len = last - buf;

    if (len >= sizeof(ngx_pp_v2_header_t)
        && memcmp(p, ngx_pp_v2_signature, NGX_PP_V2_SIGLEN) == 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;
    addr = p;

    for ( ;; ) {
        if (p == last) {
            goto invalid;
        }

        ch = *p++;

        if (ch == ' ') {
            break;
        }

        if (ch != ':' && ch != '.'
            && (ch < 'a' || ch > 'f')
            && (ch < 'A' || ch > 'F')
            && (ch < '0' || ch > '9'))
        {
            goto invalid;
        }
    }

    len = p - addr - 1;
    c->proxy_protocol_addr.data = ngx_pnalloc(c->pool, len);

    if (c->proxy_protocol_addr.data == NULL) {
        return NULL;
    }

    ngx_memcpy(c->proxy_protocol_addr.data, addr, len);
    c->proxy_protocol_addr.len = len;

    for ( ;; ) {
        if (p == last) {
            goto invalid;
        }

        if (*p++ == ' ') {
            break;
        }
    }

    port = p;

    for ( ;; ) {
        if (p == last) {
            goto invalid;
        }

        if (*p++ == ' ') {
            break;
        }
    }

    len = p - port - 1;

    n = ngx_atoi(port, len);

    if (n < 0 || n > 65535) {
        goto invalid;
    }

    c->proxy_protocol_port = (in_port_t) n;

    ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
                   "PROXY protocol address: %V %i", &c->proxy_protocol_addr, n);

skip:

    for ( /* void */ ; p < last - 1; p++) {
        if (p[0] == CR && p[1] == LF) {
            return p + 2;
        }
    }

invalid:

    ngx_log_error(NGX_LOG_ERR, c->log, 0,
                  "broken header: \"%*s\"", (size_t) (last - buf), buf);

    return NULL;
}


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_MAX_HEADER) {
        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_str_t           *name;
    ngx_uint_t           ver, cmd, family, transport;
    ngx_sockaddr_t       sockaddr;
    ngx_pp_v2_addrs_t   *addrs;
    ngx_pp_v2_header_t  *hdr;

    hdr = (ngx_pp_v2_header_t *) buf;

    buf += sizeof(ngx_pp_v2_header_t);

    ver = hdr->ver_cmd >> 4;

    if (ver != 2) {
        ngx_log_error(NGX_LOG_ERR, c->log, 0,
                      "unsupported PROXY protocol version: %ui", ver);
        return NULL;
    }

    len = ngx_pp_v2_get_u16(hdr->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;

    cmd = hdr->ver_cmd & 0x0F;

    if (cmd != NGX_PP_V2_CMD_PROXY) {
        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "PROXY protocol v2 unsupported cmd 0x%xi", cmd);
        return end;
    }

    transport = hdr->fam_transp & 0x0F;

    if (transport != NGX_PP_V2_STREAM) {
        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "PROXY protocol v2 unsupported transport 0x%xi",
                       transport);
        return end;
    }

    family = hdr->fam_transp >> 4;

    addrs = (ngx_pp_v2_addrs_t *) buf;

    switch (family) {

    case NGX_PP_V2_AF_UNSPEC:
        ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "PROXY protocol v2 AF_UNSPEC ignored");
        return end;

    case NGX_PP_V2_AF_INET:

        if ((size_t) (end - buf) < sizeof(ngx_pp_v2_inet_addrs_t)) {
            return NULL;
        }

        sockaddr.sockaddr_in.sin_family = AF_INET;
        sockaddr.sockaddr_in.sin_port = 0;
        memcpy(&sockaddr.sockaddr_in.sin_addr, addrs->inet.src, 4);

        c->proxy_protocol_port = ngx_pp_v2_get_u16(addrs->inet.sport);

        socklen = sizeof(struct sockaddr_in);

        buf += sizeof(ngx_pp_v2_inet_addrs_t);

        break;

#if (NGX_HAVE_INET6)

    case NGX_PP_V2_AF_INET6:

        if ((size_t) (end - buf) <  sizeof(ngx_pp_v2_inet6_addrs_t)) {
            return NULL;
        }

        sockaddr.sockaddr_in6.sin6_family = AF_INET6;
        sockaddr.sockaddr_in6.sin6_port = 0;
        memcpy(&sockaddr.sockaddr_in6.sin6_addr, addrs->inet6.src, 16);

        c->proxy_protocol_port = ngx_pp_v2_get_u16(addrs->inet6.sport);

        socklen = sizeof(struct sockaddr_in6);

        buf += sizeof(ngx_pp_v2_inet6_addrs_t);

        break;

#endif

    default:

        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "PROXY_protocol v2 unsupported address family "
                       "0x%xi", family);
        return end;
    }

    name = &c->proxy_protocol_addr;

    name->data = ngx_pnalloc(c->pool, NGX_SOCKADDR_STRLEN);
    if (name->data == NULL) {
        return NULL;
    }

    name->len = ngx_sock_ntop(&sockaddr.sockaddr, socklen, name->data,
                              NGX_SOCKADDR_STRLEN, 0);
    if (name->len == 0) {
        return NULL;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0,
                   "PROXY protocol v2 address: %V %i", name,
                   (ngx_int_t) c->proxy_protocol_port);

    if (buf < end) {
        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "PROXY protocol v2 %z bytes tlv ignored", end - buf);
    }

    return end;
}