<div dir="ltr">Please find below and updated patch that allows enabling accepting Proxy Protocol header from clients by setting `proxy_protocol` on a listening socket.<br><br>As for how the feature should work in the great schema things, I propose the following: Taking client IP from a Proxy Protocol header and passing it to an upstream SMTP server seems to be a good enough default behavior imho. If somebody needs a more complex logic similar to `real_ip_header` and `set_real_ip_from`, that can always be implemented later on top of these changes. How about that?<br><br># HG changeset patch<br># User Maxim Vladimirskiy <<a href="mailto:maxim@mailgunhq.com">maxim@mailgunhq.com</a>><br># Date 1478216956 25200<br>#      Thu Nov 03 16:49:16 2016 -0700<br># Node ID f7d710f1eaed454b73cc2e9c3318d1384de208d5<br># Parent  1a917932db963e7ecf4d786cc02bd54bd4548b88<br>Add support for Proxy Protocol to mail SMTP<br><br>When Nginx is not the first proxy in a proxy chain it needs to be able to<br>understand the Proxy Protocol header to retrieve client IP and pass it<br>upstream in an XCLIENT command. With these changes one can configure<br>Nginx to do that by adding `proxy_protocol` to a listener description.<br><br>diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail.c<br>--- a/src/mail/ngx_mail.c  Thu Nov 03 17:10:29 2016 +0300<br>+++ b/src/mail/ngx_mail.c  Thu Nov 03 16:49:16 2016 -0700<br>@@ -403,6 +403,7 @@<br>         addrs[i].addr = sin->sin_addr.s_addr;<br> <br>         addrs[i].conf.ctx = addr[i].opt.ctx;<br>+        addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;<br> #if (NGX_MAIL_SSL)<br>         addrs[i].conf.ssl = addr[i].opt.ssl;<br> #endif<br>@@ -452,6 +453,7 @@<br>         addrs6[i].addr6 = sin6->sin6_addr;<br> <br>         addrs6[i].conf.ctx = addr[i].opt.ctx;<br>+        addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;<br> #if (NGX_MAIL_SSL)<br>         addrs6[i].conf.ssl = addr[i].opt.ssl;<br> #endif<br>diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail.h<br>--- a/src/mail/ngx_mail.h  Thu Nov 03 17:10:29 2016 +0300<br>+++ b/src/mail/ngx_mail.h  Thu Nov 03 16:49:16 2016 -0700<br>@@ -40,6 +40,7 @@<br>     unsigned                ipv6only:1;<br> #endif<br>     unsigned                so_keepalive:2;<br>+    unsigned                proxy_protocol:1;<br> #if (NGX_HAVE_KEEPALIVE_TUNABLE)<br>     int                     tcp_keepidle;<br>     int                     tcp_keepintvl;<br>@@ -53,6 +54,7 @@<br>     ngx_mail_conf_ctx_t    *ctx;<br>     ngx_str_t               addr_text;<br>     ngx_uint_t              ssl;    /* unsigned   ssl:1; */<br>+    unsigned                proxy_protocol:1;<br> } ngx_mail_addr_conf_t;<br> <br> typedef struct {<br>@@ -185,6 +187,7 @@<br>     void                  **ctx;<br>     void                  **main_conf;<br>     void                  **srv_conf;<br>+    ngx_mail_addr_conf_t   *addr_conf;<br> <br>     ngx_resolver_ctx_t     *resolver_ctx;<br> <br>diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_core_module.c<br>--- a/src/mail/ngx_mail_core_module.c  Thu Nov 03 17:10:29 2016 +0300<br>+++ b/src/mail/ngx_mail_core_module.c  Thu Nov 03 16:49:16 2016 -0700<br>@@ -540,6 +540,11 @@<br> #endif<br>         }<br> <br>+        if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) {<br>+            ls->proxy_protocol = 1;<br>+            continue;<br>+        }<br>+<br>         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,<br>                            "the invalid \"%V\" parameter", &value[i]);<br>         return NGX_CONF_ERROR;<br>diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_handler.c<br>--- a/src/mail/ngx_mail_handler.c  Thu Nov 03 17:10:29 2016 +0300<br>+++ b/src/mail/ngx_mail_handler.c  Thu Nov 03 16:49:16 2016 -0700<br>@@ -12,6 +12,8 @@<br> <br> <br> static void ngx_mail_init_session(ngx_connection_t *c);<br>+static void ngx_mail_proxy_protocol_handler(ngx_event_t *rev);<br>+static void ngx_mail_init_session_handler(ngx_event_t *rev);<br> <br> #if (NGX_MAIL_SSL)<br> static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);<br>@@ -34,6 +36,7 @@<br>     ngx_mail_session_t        *s;<br>     ngx_mail_addr_conf_t      *addr_conf;<br>     ngx_mail_core_srv_conf_t  *cscf;<br>+    ngx_event_t               *rev;<br>     u_char                     text[NGX_SOCKADDR_STRLEN];<br> #if (NGX_HAVE_INET6)<br>     struct sockaddr_in6       *sin6;<br>@@ -128,6 +131,7 @@<br> <br>     s->main_conf = addr_conf->ctx->main_conf;<br>     s->srv_conf = addr_conf->ctx->srv_conf;<br>+    s->addr_conf = addr_conf;<br> <br>     s->addr_text = &addr_conf->addr_text;<br> <br>@@ -159,10 +163,30 @@<br> <br>     c->log_error = NGX_ERROR_INFO;<br> <br>+    rev = c->read;<br>+<br>+    if (addr_conf->proxy_protocol) {<br>+        rev->handler = ngx_mail_proxy_protocol_handler;<br>+        ngx_mail_proxy_protocol_handler(rev);<br>+        return;<br>+    }<br>+<br>+    ngx_mail_init_session_handler(rev);<br>+}<br>+<br>+<br>+static void<br>+ngx_mail_init_session_handler(ngx_event_t *rev)<br>+{<br>+    ngx_connection_t  *c;<br>+<br>+    c = rev->data;<br>+<br> #if (NGX_MAIL_SSL)<br>-    {<br>     ngx_mail_ssl_conf_t  *sslcf;<br>+    ngx_mail_session_t   *s;<br> <br>+    s = c->data;<br>     sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);<br> <br>     if (sslcf->enable) {<br>@@ -172,7 +196,7 @@<br>         return;<br>     }<br> <br>-    if (addr_conf->ssl) {<br>+    if (s->addr_conf->ssl) {<br> <br>         c->log->action = "SSL handshaking";<br> <br>@@ -187,8 +211,6 @@<br>         ngx_mail_ssl_init_connection(&sslcf->ssl, c);<br>         return;<br>     }<br>-<br>-    }<br> #endif<br> <br>     ngx_mail_init_session(c);<br>@@ -898,3 +920,72 @@<br> <br>     return p;<br> }<br>+<br>+<br>+static void<br>+ngx_mail_proxy_protocol_handler(ngx_event_t *rev)<br>+{<br>+    ngx_mail_session_t        *s;<br>+    ngx_mail_core_srv_conf_t  *cscf;<br>+    u_char                    *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER + 1];<br>+    size_t                     size;<br>+    ssize_t                    n;<br>+    ngx_connection_t          *c;<br>+<br>+    c = rev->data;<br>+    s = c->data;<br>+<br>+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);<br>+<br>+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,<br>+                   "read proxy protocol header");<br>+<br>+    if (rev->timedout) {<br>+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");<br>+        ngx_mail_close_connection(c);<br>+        return;<br>+    }<br>+<br>+    if (c->close) {<br>+        ngx_mail_close_connection(c);<br>+        return;<br>+    }<br>+<br>+    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);<br>+<br>+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http recv(): %z", n);<br>+<br>+    if (n == -1) {<br>+        if (ngx_socket_errno == NGX_EAGAIN) {<br>+            rev->ready = 0;<br>+<br>+            if (!rev->timer_set) {<br>+                ngx_add_timer(rev, cscf->timeout);<br>+                ngx_reusable_connection(c, 1);<br>+            }<br>+<br>+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {<br>+                ngx_mail_close_connection(c);<br>+            }<br>+            return;<br>+        }<br>+<br>+        ngx_connection_error(c, ngx_socket_errno, "recv() failed");<br>+        ngx_mail_close_connection(c);<br>+        return;<br>+    }<br>+<br>+    p = ngx_proxy_protocol_read(c, buf, buf + n);<br>+    if (p == NULL) {<br>+        ngx_mail_close_connection(c);<br>+        return;<br>+    }<br>+<br>+    size = p - buf;<br>+    if (c->recv(c, buf, size) != (ssize_t) size) {<br>+        ngx_mail_close_connection(c);<br>+        return;<br>+    }<br>+<br>+    ngx_mail_init_session_handler(rev);<br>+}<br>\ No newline at end of file<br>diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_proxy_module.c<br>--- a/src/mail/ngx_mail_proxy_module.c Thu Nov 03 17:10:29 2016 +0300<br>+++ b/src/mail/ngx_mail_proxy_module.c Thu Nov 03 16:49:16 2016 -0700<br>@@ -456,6 +456,7 @@<br>     ngx_mail_session_t        *s;<br>     ngx_mail_proxy_conf_t     *pcf;<br>     ngx_mail_core_srv_conf_t  *cscf;<br>+    ngx_str_t                  client_addr;<br> <br>     ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,<br>                    "mail proxy smtp auth handler");<br>@@ -525,9 +526,12 @@<br> <br>         s->connection->log->action = "sending XCLIENT to upstream";<br> <br>-        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="<br>-                          CRLF) - 1<br>-                   + s->connection->addr_text.len + s->login.len + s->host.len;<br>+        client_addr = s->connection->addr_text;<br>+        if (s->connection->proxy_protocol_addr.data != NULL) {<br>+            client_addr = s->connection->proxy_protocol_addr;<br>+        }<br>+        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME=" CRLF) - 1<br>+                   + client_addr.len + s->login.len + s->host.len;<br> <br> #if (NGX_HAVE_INET6)<br>         if (s->connection->sockaddr->sa_family == AF_INET6) {<br>@@ -549,9 +553,7 @@<br>         }<br> #endif<br> <br>-        p = ngx_copy(p, s->connection->addr_text.data,<br>-                     s->connection->addr_text.len);<br>-<br>+        p = ngx_copy(p, client_addr.data, client_addr.len);<br>         if (s->login.len) {<br>             p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1);<br><br>             p = ngx_copy(p, s->login.data, s->login.len);</div><div class="gmail_extra"><br><div class="gmail_quote">On Thu, Nov 3, 2016 at 10:40 AM, Maxim Vladimirsky <span dir="ltr"><<a href="mailto:maxim@mailgunhq.com" target="_blank">maxim@mailgunhq.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">Thanks for quick response. See my comment below:<div class="gmail_extra"><br><div class="gmail_quote"><div><div class="h5">On Thu, Nov 3, 2016 at 10:13 AM, Maxim Dounin <span dir="ltr"><<a href="mailto:mdounin@mdounin.ru" target="_blank">mdounin@mdounin.ru</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hello!<br>
<span><br>
On Thu, Nov 03, 2016 at 09:41:03AM -0700, Maxim Vladimirsky wrote:<br>
<br>
> Hi Folks,<br>
><br>
> I was not sure if you would be interested in this, but decided to run it by<br>
> you anyway.<br>
><br>
> We need to run Nginx as an SMTP proxy sitting behind ELB in AWS, but we<br>
> also want the upstream SMTP server to get the real client ip, so Nginx is<br>
> configured to provide it via an XCLIENT command. However the stock version<br>
> of Nginx provides ELB's ip instead, because it does not recognize the Proxy<br>
> Protocol header (<a href="http://docs.aws.amazon.com/elasticloadbalancing/latest/" rel="noreferrer" target="_blank">http://docs.aws.amazon.com/el<wbr>asticloadbalancing/latest/</a><br>
> classic/enable-proxy-protocol.<wbr>html#proxy-protocol) sent to it by ELB.<br>
<br>
</span>Seems to be perfectly valid use case.<br>
<span><br>
> The following patch updates the mail module so that it can be configured to<br>
> expect Proxy Protocol header by setting `proxy_protocol on`. In that case<br>
> Proxy Protocol header is parsed, a client IP is retrieved and passed to an<br>
> SMTP upstream in an XCLIENT command.<br>
<br>
</span>The "proxy_protocol on" is expected to configure sending PROXY<br>
protocol to an upstream server, similar to how it already works in<br>
the stream module:<br>
<br>
<a href="http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_protocol" rel="noreferrer" target="_blank">http://nginx.org/en/docs/strea<wbr>m/ngx_stream_proxy_module.<wbr>html#proxy_protocol</a><br>
<br>
Accepting PROXY protocol from clients is expected to be enabled<br>
using a listening socket option instead, similar to what we have<br>
in http and stream modules:<br>
<br>
<a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#listen" rel="noreferrer" target="_blank">http://nginx.org/en/docs/http/<wbr>ngx_http_core_module.html#list<wbr>en</a><br>
<a href="http://nginx.org/en/docs/stream/ngx_stream_core_module.html#listen" rel="noreferrer" target="_blank">http://nginx.org/en/docs/strea<wbr>m/ngx_stream_core_module.html#<wbr>listen</a></blockquote><div><br></div></div></div><div>I will fix that.</div><span class=""><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
<br>
Open question is how it should work though.  I.e., if it should<br>
just unconditionally set provided address as a client one, similar<br>
to how<br>
<br>
    listen 80 proxy_protocol;<br>
    real_ip_header proxy_protocol;<br>
    set_real_ip_from <a href="http://0.0.0.0/0" rel="noreferrer" target="_blank">0.0.0.0/0</a>;<br>
<br>
works in stream / http, or there should be some advanced control<br>
like the realip module in stream / http.<br></blockquote><div><br></div></span><div>Taking ip from Proxy Protocol header and passing in via XCLIENT is really all we need, and I cannot even think of a scenario where we would need any kind of fine tuning of this logic. So this is probably a question to a broader audience.</div><span class=""><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<span class="m_3256169237933485102HOEnZb"><font color="#888888"><br>
--<br>
Maxim Dounin<br>
<a href="http://nginx.org/" rel="noreferrer" target="_blank">http://nginx.org/</a><br>
<br>
______________________________<wbr>_________________<br>
nginx-devel mailing list<br>
<a href="mailto:nginx-devel@nginx.org" target="_blank">nginx-devel@nginx.org</a><br>
<a href="http://mailman.nginx.org/mailman/listinfo/nginx-devel" rel="noreferrer" target="_blank">http://mailman.nginx.org/mailm<wbr>an/listinfo/nginx-devel</a><br>
</font></span></blockquote></span></div><br></div></div>
</blockquote></div><br></div>