[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