[PATCH] Add support for Proxy Protocol to mail SMTP

Maxim Vladimirsky maxim at mailgunhq.com
Fri Nov 4 00:04:11 UTC 2016


Please find below and updated patch that allows enabling accepting Proxy
Protocol header from clients by setting `proxy_protocol` on a listening
socket.

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?

# HG changeset patch
# User Maxim Vladimirskiy <maxim at mailgunhq.com>
# Date 1478216956 25200
#      Thu Nov 03 16:49:16 2016 -0700
# Node ID f7d710f1eaed454b73cc2e9c3318d1384de208d5
# Parent  1a917932db963e7ecf4d786cc02bd54bd4548b88
Add support for Proxy Protocol to mail SMTP

When Nginx is not the first proxy in a proxy chain it needs to be able to
understand the Proxy Protocol header to retrieve client IP and pass it
upstream in an XCLIENT command. With these changes one can configure
Nginx to do that by adding `proxy_protocol` to a listener description.

diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail.c
--- a/src/mail/ngx_mail.c  Thu Nov 03 17:10:29 2016 +0300
+++ b/src/mail/ngx_mail.c  Thu Nov 03 16:49:16 2016 -0700
@@ -403,6 +403,7 @@
         addrs[i].addr = sin->sin_addr.s_addr;

         addrs[i].conf.ctx = addr[i].opt.ctx;
+        addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 #if (NGX_MAIL_SSL)
         addrs[i].conf.ssl = addr[i].opt.ssl;
 #endif
@@ -452,6 +453,7 @@
         addrs6[i].addr6 = sin6->sin6_addr;

         addrs6[i].conf.ctx = addr[i].opt.ctx;
+        addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 #if (NGX_MAIL_SSL)
         addrs6[i].conf.ssl = addr[i].opt.ssl;
 #endif
diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h  Thu Nov 03 17:10:29 2016 +0300
+++ b/src/mail/ngx_mail.h  Thu Nov 03 16:49:16 2016 -0700
@@ -40,6 +40,7 @@
     unsigned                ipv6only:1;
 #endif
     unsigned                so_keepalive:2;
+    unsigned                proxy_protocol:1;
 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
     int                     tcp_keepidle;
     int                     tcp_keepintvl;
@@ -53,6 +54,7 @@
     ngx_mail_conf_ctx_t    *ctx;
     ngx_str_t               addr_text;
     ngx_uint_t              ssl;    /* unsigned   ssl:1; */
+    unsigned                proxy_protocol:1;
 } ngx_mail_addr_conf_t;

 typedef struct {
@@ -185,6 +187,7 @@
     void                  **ctx;
     void                  **main_conf;
     void                  **srv_conf;
+    ngx_mail_addr_conf_t   *addr_conf;

     ngx_resolver_ctx_t     *resolver_ctx;

diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_core_module.c
--- a/src/mail/ngx_mail_core_module.c  Thu Nov 03 17:10:29 2016 +0300
+++ b/src/mail/ngx_mail_core_module.c  Thu Nov 03 16:49:16 2016 -0700
@@ -540,6 +540,11 @@
 #endif
         }

+        if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) {
+            ls->proxy_protocol = 1;
+            continue;
+        }
+
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "the invalid \"%V\" parameter", &value[i]);
         return NGX_CONF_ERROR;
diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c  Thu Nov 03 17:10:29 2016 +0300
+++ b/src/mail/ngx_mail_handler.c  Thu Nov 03 16:49:16 2016 -0700
@@ -12,6 +12,8 @@


 static void ngx_mail_init_session(ngx_connection_t *c);
+static void ngx_mail_proxy_protocol_handler(ngx_event_t *rev);
+static void ngx_mail_init_session_handler(ngx_event_t *rev);

 #if (NGX_MAIL_SSL)
 static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t
*c);
@@ -34,6 +36,7 @@
     ngx_mail_session_t        *s;
     ngx_mail_addr_conf_t      *addr_conf;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_event_t               *rev;
     u_char                     text[NGX_SOCKADDR_STRLEN];
 #if (NGX_HAVE_INET6)
     struct sockaddr_in6       *sin6;
@@ -128,6 +131,7 @@

     s->main_conf = addr_conf->ctx->main_conf;
     s->srv_conf = addr_conf->ctx->srv_conf;
+    s->addr_conf = addr_conf;

     s->addr_text = &addr_conf->addr_text;

@@ -159,10 +163,30 @@

     c->log_error = NGX_ERROR_INFO;

+    rev = c->read;
+
+    if (addr_conf->proxy_protocol) {
+        rev->handler = ngx_mail_proxy_protocol_handler;
+        ngx_mail_proxy_protocol_handler(rev);
+        return;
+    }
+
+    ngx_mail_init_session_handler(rev);
+}
+
+
+static void
+ngx_mail_init_session_handler(ngx_event_t *rev)
+{
+    ngx_connection_t  *c;
+
+    c = rev->data;
+
 #if (NGX_MAIL_SSL)
-    {
     ngx_mail_ssl_conf_t  *sslcf;
+    ngx_mail_session_t   *s;

+    s = c->data;
     sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);

     if (sslcf->enable) {
@@ -172,7 +196,7 @@
         return;
     }

-    if (addr_conf->ssl) {
+    if (s->addr_conf->ssl) {

         c->log->action = "SSL handshaking";

@@ -187,8 +211,6 @@
         ngx_mail_ssl_init_connection(&sslcf->ssl, c);
         return;
     }
-
-    }
 #endif

     ngx_mail_init_session(c);
@@ -898,3 +920,72 @@

     return p;
 }
+
+
+static void
+ngx_mail_proxy_protocol_handler(ngx_event_t *rev)
+{
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+    u_char                    *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER + 1];
+    size_t                     size;
+    ssize_t                    n;
+    ngx_connection_t          *c;
+
+    c = rev->data;
+    s = c->data;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "read proxy protocol header");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed
out");
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (c->close) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http recv(): %z", n);
+
+    if (n == -1) {
+        if (ngx_socket_errno == NGX_EAGAIN) {
+            rev->ready = 0;
+
+            if (!rev->timer_set) {
+                ngx_add_timer(rev, cscf->timeout);
+                ngx_reusable_connection(c, 1);
+            }
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_mail_close_connection(c);
+            }
+            return;
+        }
+
+        ngx_connection_error(c, ngx_socket_errno, "recv() failed");
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    p = ngx_proxy_protocol_read(c, buf, buf + n);
+    if (p == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    size = p - buf;
+    if (c->recv(c, buf, size) != (ssize_t) size) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    ngx_mail_init_session_handler(rev);
+}
\ No newline at end of file
diff -r 1a917932db96 -r f7d710f1eaed src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c Thu Nov 03 17:10:29 2016 +0300
+++ b/src/mail/ngx_mail_proxy_module.c Thu Nov 03 16:49:16 2016 -0700
@@ -456,6 +456,7 @@
     ngx_mail_session_t        *s;
     ngx_mail_proxy_conf_t     *pcf;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_str_t                  client_addr;

     ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
                    "mail proxy smtp auth handler");
@@ -525,9 +526,12 @@

         s->connection->log->action = "sending XCLIENT to upstream";

-        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="
-                          CRLF) - 1
-                   + s->connection->addr_text.len + s->login.len +
s->host.len;
+        client_addr = s->connection->addr_text;
+        if (s->connection->proxy_protocol_addr.data != NULL) {
+            client_addr = s->connection->proxy_protocol_addr;
+        }
+        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME=" CRLF) - 1
+                   + client_addr.len + s->login.len + s->host.len;

 #if (NGX_HAVE_INET6)
         if (s->connection->sockaddr->sa_family == AF_INET6) {
@@ -549,9 +553,7 @@
         }
 #endif

-        p = ngx_copy(p, s->connection->addr_text.data,
-                     s->connection->addr_text.len);
-
+        p = ngx_copy(p, client_addr.data, client_addr.len);
         if (s->login.len) {
             p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1);

             p = ngx_copy(p, s->login.data, s->login.len);

On Thu, Nov 3, 2016 at 10:40 AM, Maxim Vladimirsky <maxim at mailgunhq.com>
wrote:

> Thanks for quick response. See my comment below:
>
> On Thu, Nov 3, 2016 at 10:13 AM, Maxim Dounin <mdounin at mdounin.ru> wrote:
>
>> Hello!
>>
>> On Thu, Nov 03, 2016 at 09:41:03AM -0700, Maxim Vladimirsky wrote:
>>
>> > Hi Folks,
>> >
>> > I was not sure if you would be interested in this, but decided to run
>> it by
>> > you anyway.
>> >
>> > We need to run Nginx as an SMTP proxy sitting behind ELB in AWS, but we
>> > also want the upstream SMTP server to get the real client ip, so Nginx
>> is
>> > configured to provide it via an XCLIENT command. However the stock
>> version
>> > of Nginx provides ELB's ip instead, because it does not recognize the
>> Proxy
>> > Protocol header (http://docs.aws.amazon.com/el
>> asticloadbalancing/latest/
>> > classic/enable-proxy-protocol.html#proxy-protocol) sent to it by ELB.
>>
>> Seems to be perfectly valid use case.
>>
>> > The following patch updates the mail module so that it can be
>> configured to
>> > expect Proxy Protocol header by setting `proxy_protocol on`. In that
>> case
>> > Proxy Protocol header is parsed, a client IP is retrieved and passed to
>> an
>> > SMTP upstream in an XCLIENT command.
>>
>> The "proxy_protocol on" is expected to configure sending PROXY
>> protocol to an upstream server, similar to how it already works in
>> the stream module:
>>
>> http://nginx.org/en/docs/stream/ngx_stream_proxy_module.
>> html#proxy_protocol
>>
>> Accepting PROXY protocol from clients is expected to be enabled
>> using a listening socket option instead, similar to what we have
>> in http and stream modules:
>>
>> http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
>> http://nginx.org/en/docs/stream/ngx_stream_core_module.html#listen
>
>
> I will fix that.
>
>
>>
>>
>> Open question is how it should work though.  I.e., if it should
>> just unconditionally set provided address as a client one, similar
>> to how
>>
>>     listen 80 proxy_protocol;
>>     real_ip_header proxy_protocol;
>>     set_real_ip_from 0.0.0.0/0;
>>
>> works in stream / http, or there should be some advanced control
>> like the realip module in stream / http.
>>
>
> 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.
>
>
>>
>> --
>> Maxim Dounin
>> http://nginx.org/
>>
>> _______________________________________________
>> nginx-devel mailing list
>> nginx-devel at nginx.org
>> http://mailman.nginx.org/mailman/listinfo/nginx-devel
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx-devel/attachments/20161103/80e2dd0d/attachment.html>


More information about the nginx-devel mailing list