[PATCH 4 of 7] Mail: added "limit_rate" and "limit_rate_after" directives

Maxim Dounin mdounin at mdounin.ru
Wed Jun 18 12:37:36 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1750204972 -10800
#      Wed Jun 18 03:02:52 2025 +0300
# Node ID 6c8bdd0c04eda08d0c8ffb8335c6e6b352dc6a28
# Parent  cd85e94f52db98778bac968957277a41bc944927
Mail: added "limit_rate" and "limit_rate_after" directives.

diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -119,6 +119,9 @@ typedef struct {
     ngx_uint_t              max_errors;
     ngx_uint_t              max_commands;
 
+    size_t                  limit_rate;
+    size_t                  limit_rate_after;
+
     ngx_str_t               server_name;
 
     u_char                 *file_name;
@@ -248,6 +251,9 @@ typedef struct {
     ngx_uint_t              commands;
     ngx_uint_t              login_attempt;
 
+    ngx_msec_t              limit_last;
+    off_t                   limit_excess;
+
     /* used to parse POP3/IMAP/SMTP command */
 
     ngx_uint_t              state;
diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c
--- a/src/mail/ngx_mail_core_module.c
+++ b/src/mail/ngx_mail_core_module.c
@@ -99,6 +99,20 @@ static ngx_command_t  ngx_mail_core_comm
       offsetof(ngx_mail_core_srv_conf_t, max_commands),
       NULL },
 
+    { ngx_string("limit_rate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_core_srv_conf_t, limit_rate),
+      NULL },
+
+    { ngx_string("limit_rate_after"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_core_srv_conf_t, limit_rate_after),
+      NULL },
+
       ngx_null_command
 };
 
@@ -180,6 +194,9 @@ ngx_mail_core_create_srv_conf(ngx_conf_t
     cscf->max_errors = NGX_CONF_UNSET_UINT;
     cscf->max_commands = NGX_CONF_UNSET_UINT;
 
+    cscf->limit_rate = NGX_CONF_UNSET_SIZE;
+    cscf->limit_rate_after = NGX_CONF_UNSET_SIZE;
+
     cscf->resolver = NGX_CONF_UNSET_PTR;
 
     cscf->file_name = cf->conf_file->file.name.data;
@@ -202,6 +219,10 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t 
     ngx_conf_merge_uint_value(conf->max_errors, prev->max_errors, 5);
     ngx_conf_merge_uint_value(conf->max_commands, prev->max_commands, 1000);
 
+    ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0);
+    ngx_conf_merge_size_value(conf->limit_rate_after, prev->limit_rate_after,
+                              0);
+
     ngx_conf_merge_str_value(conf->server_name, prev->server_name, "");
 
     if (conf->server_name.len == 0) {
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -1026,7 +1026,10 @@ ngx_mail_auth_oauthbearer(ngx_mail_sessi
 void
 ngx_mail_send(ngx_event_t *wev)
 {
+    off_t                      excess;
     ngx_int_t                  n;
+    ngx_msec_t                 delay;
+    ngx_msec_int_t             ms;
     ngx_connection_t          *c;
     ngx_mail_session_t        *s;
     ngx_mail_core_srv_conf_t  *cscf;
@@ -1034,6 +1037,11 @@ ngx_mail_send(ngx_event_t *wev)
     c = wev->data;
     s = c->data;
 
+    if (wev->delayed && wev->timedout) {
+        wev->delayed = 0;
+        wev->timedout = 0;
+    }
+
     if (wev->timedout) {
         ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
         c->timedout = 1;
@@ -1041,7 +1049,7 @@ ngx_mail_send(ngx_event_t *wev)
         return;
     }
 
-    if (s->out.len == 0) {
+    if (s->out.len == 0 || wev->delayed) {
         if (ngx_handle_write_event(wev, 0) != NGX_OK) {
             ngx_mail_close_connection(c);
         }
@@ -1049,12 +1057,48 @@ ngx_mail_send(ngx_event_t *wev)
         return;
     }
 
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+#if (NGX_SUPPRESS_WARN)
+    excess = 0;
+#endif
+
+    if (cscf->limit_rate) {
+        ms = (ngx_msec_int_t) (ngx_current_msec - s->limit_last);
+        ms = ngx_max(ms, 0);
+
+        excess = (off_t) (s->limit_excess
+                          - (uint64_t) cscf->limit_rate * ms / 1000);
+        excess = ngx_max(excess, 0);
+
+        if (excess > (off_t) cscf->limit_rate
+                     + (off_t) cscf->limit_rate_after)
+        {
+            wev->delayed = 1;
+            excess -= (off_t) cscf->limit_rate_after;
+            excess -= (off_t) cscf->limit_rate / 2;
+            delay = (ngx_msec_t) (excess * 1000 / cscf->limit_rate + 1);
+            ngx_add_timer(wev, delay);
+
+            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+                ngx_mail_close_connection(c);
+            }
+
+            return;
+        }
+    }
+
     n = c->send(c, s->out.data, s->out.len);
 
     if (n > 0) {
         s->out.data += n;
         s->out.len -= n;
 
+        if (cscf->limit_rate) {
+            s->limit_last = ngx_current_msec;
+            s->limit_excess = excess + n;
+        }
+
         if (s->out.len != 0) {
             goto again;
         }
diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c
+++ b/src/mail/ngx_mail_proxy_module.c
@@ -1115,19 +1115,27 @@ ngx_mail_proxy_read_response(ngx_mail_se
 static void
 ngx_mail_proxy_handler(ngx_event_t *ev)
 {
-    char                   *action, *recv_action, *send_action;
-    off_t                   sent;
-    size_t                  size;
-    ssize_t                 n;
-    ngx_buf_t              *b;
-    ngx_uint_t              do_write;
-    ngx_connection_t       *c, *src, *dst;
-    ngx_mail_session_t     *s;
-    ngx_mail_proxy_conf_t  *pcf;
+    char                      *action, *recv_action, *send_action;
+    off_t                      sent, excess, limit;
+    size_t                     size, limit_rate, limit_rate_after;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_uint_t                 do_write;
+    ngx_msec_t                 delay;
+    ngx_msec_int_t             ms;
+    ngx_connection_t          *c, *src, *dst;
+    ngx_mail_session_t        *s;
+    ngx_mail_proxy_conf_t     *pcf;
+    ngx_mail_core_srv_conf_t  *cscf;
 
     c = ev->data;
     s = c->data;
 
+    if (ev->delayed && ev->timedout) {
+        ev->delayed = 0;
+        ev->timedout = 0;
+    }
+
     if (ev->timedout || c->close) {
         c->log->action = "proxying";
 
@@ -1157,17 +1165,27 @@ ngx_mail_proxy_handler(ngx_event_t *ev)
         dst = s->connection;
         b = s->proxy->buffer;
 
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+        limit_rate = cscf->limit_rate;
+        limit_rate_after = cscf->limit_rate_after;
+
     } else {
         recv_action = "proxying and reading from client";
         send_action = "proxying and sending to upstream";
         src = s->connection;
         dst = s->proxy->upstream.connection;
         b = s->buffer;
+        limit_rate = 0;
+        limit_rate_after = 0;
     }
 
     do_write = ev->write ? 1 : 0;
     sent = dst->sent;
 
+#if (NGX_SUPPRESS_WARN)
+    excess = 0;
+#endif
+
     ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0,
                    "mail proxy handler: %ui, #%d > #%d",
                    do_write, src->fd, dst->fd);
@@ -1178,9 +1196,34 @@ ngx_mail_proxy_handler(ngx_event_t *ev)
 
             size = b->last - b->pos;
 
-            if (size && dst->write->ready) {
+            if (size && dst->write->ready && !dst->write->delayed) {
                 c->log->action = send_action;
 
+                if (limit_rate) {
+                    ms = (ngx_msec_int_t) (ngx_current_msec - s->limit_last);
+                    ms = ngx_max(ms, 0);
+
+                    excess = (off_t) (s->limit_excess
+                                      - (uint64_t) limit_rate * ms / 1000);
+                    excess = ngx_max(excess, 0);
+
+                    limit = (off_t) limit_rate + (off_t) limit_rate_after
+                            - excess;
+
+                    if (limit <= 0) {
+                        dst->write->delayed = 1;
+                        excess -= (off_t) limit_rate_after;
+                        excess -= (off_t) limit_rate / 2;
+                        delay = (ngx_msec_t) (excess * 1000 / limit_rate + 1);
+                        ngx_add_timer(dst->write, delay);
+                        break;
+                    }
+
+                    if ((off_t) size > limit) {
+                        size = (size_t) limit;
+                    }
+                }
+
                 n = dst->send(dst, b->pos, size);
 
                 if (n == NGX_ERROR) {
@@ -1195,6 +1238,24 @@ ngx_mail_proxy_handler(ngx_event_t *ev)
                         b->pos = b->start;
                         b->last = b->start;
                     }
+
+                    if (limit_rate) {
+                        excess += n;
+
+                        s->limit_last = ngx_current_msec;
+                        s->limit_excess = excess;
+
+                        excess -= (off_t) limit_rate_after;
+                        excess -= (off_t) limit_rate / 2;
+                        excess = ngx_max(excess, 0);
+
+                        delay = (ngx_msec_t) (excess * 1000 / limit_rate);
+
+                        if (delay > 0) {
+                            dst->write->delayed = 1;
+                            ngx_add_timer(dst->write, delay);
+                        }
+                    }
                 }
             }
         }



More information about the nginx-devel mailing list