|  |  | 
|  | /* | 
|  | * 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)  ((p)[0] << 8 | (p)[1]) | 
|  |  | 
|  |  | 
|  | 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; | 
|  |  | 
|  |  | 
|  | static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, | 
|  | u_char *last); | 
|  |  | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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) | 
|  | && 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; | 
|  | 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 %d", &c->proxy_protocol_addr, | 
|  | c->proxy_protocol_port); | 
|  |  | 
|  | 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_uint_t                          version, command, family, transport; | 
|  | ngx_sockaddr_t                      sockaddr; | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | sockaddr.sockaddr_in.sin_family = AF_INET; | 
|  | sockaddr.sockaddr_in.sin_port = 0; | 
|  | memcpy(&sockaddr.sockaddr_in.sin_addr, in->src_addr, 4); | 
|  |  | 
|  | c->proxy_protocol_port = ngx_proxy_protocol_parse_uint16(in->src_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; | 
|  |  | 
|  | sockaddr.sockaddr_in6.sin6_family = AF_INET6; | 
|  | sockaddr.sockaddr_in6.sin6_port = 0; | 
|  | memcpy(&sockaddr.sockaddr_in6.sin6_addr, in6->src_addr, 16); | 
|  |  | 
|  | c->proxy_protocol_port = ngx_proxy_protocol_parse_uint16(in6->src_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; | 
|  | } | 
|  |  | 
|  | c->proxy_protocol_addr.data = ngx_pnalloc(c->pool, NGX_SOCKADDR_STRLEN); | 
|  | if (c->proxy_protocol_addr.data == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | c->proxy_protocol_addr.len = ngx_sock_ntop(&sockaddr.sockaddr, socklen, | 
|  | c->proxy_protocol_addr.data, | 
|  | NGX_SOCKADDR_STRLEN, 0); | 
|  |  | 
|  | ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0, | 
|  | "PROXY protocol v2 address: %V %d", &c->proxy_protocol_addr, | 
|  | c->proxy_protocol_port); | 
|  |  | 
|  | if (buf < end) { | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, | 
|  | "PROXY protocol v2 %z bytes of tlv ignored", end - buf); | 
|  | } | 
|  |  | 
|  | return end; | 
|  | } |