Commit Graph
2712 Commits
Author SHA1 Message Date
Wez Furlong cc0b96814e switch opportunistic_tls_reconnect_on_failed_handshake to non-recursive
We had a report of a stack overflow while using this option in some
heavy load scenarios.

It's not clear that it is 100% correlated, but I'd feel better
not having recursion here so that we can rule that out.
2024-12-18 10:21:16 -07:00
Wez Furlong d1657258f2 Clarify what is logged in Expiration records
Make the message more human readable.

Leave the site empty instead of `localhost`; no site was responsible
for the decision to expire the message, that is due to local policy.
2024-12-18 10:06:07 -07:00
Wez Furlong cc90c278fa docs: fixup mermaidjs issue
This used to work fine, but recently started to bleed through
some control characters

closes: https://github.com/KumoCorp/kumomta/issues/323
2024-12-18 06:41:45 -07:00
Wez Furlong 832c898b75 Add unix domain socket support in smtp dispatcher
This commit allows setting the queue config to use a unix domain socket
path:

```
        protocol = {
          smtp = {
            mx_list = { '/some/path' },
          },
        },
```

and the smtp dispatcher will attempt to connect to it via a unix domain
socket.

It is anticipated that this will be used together with an egress path
config set to `use_lmtp = true`.

I've tested the ability to attempt to connect, but I don't have a
convenient implementation of SMTP/LMTP to test against here, so all I've
tested if that we can successfully fail to connect to a unix domain
socket.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 11:00:49 -07:00
Wez Furlong 4b12c4ca1c add kumo-address crate for HostAddress and SocketAddress types
One frustration I have with the Rust standard library types is
that while it has a SocketAddr type, that type doesn't include
unix domain addressing like the underlying unix OS does in the
underlying system type.

I understand why it is that way, it's still a bit of a PITA.

Since we want to enable the use of unix domain sockets for
outbound LMTP support, our internal addressing type needs to
be able to represent a unix domain address.

This commit introduces our own HostAddress (equivalent to IpAddr,
but not limited to IP addresses) and SocketAddress (equivalent to
SockAddr, but not limited to IP sockets) types.

The ResolvedAddress type has been cut over from IpAddr to HostAddress
and the fan out addressed.

This commit does not add support for establishing unix domain
connections, it is merely the ability to recognize/report on them.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 09:53:35 -07:00
Wez Furlong 8be4ad82a1 add basic outbound LMTP support
This enables LMTP over TCP by setting `use_lmtp = true` in the
egress path configuration.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 06:48:52 -07:00
Wez Furlong 05295e415a add opportunistic_tls_reconnect_on_failed_handshake option
When used together with an Opportunistic TLS mode, if the handshake
or subsequent EHLO fails, we will re-connect to the current host
and disable TLS.

This is implemented as a recursive solution, which I'm not totally
keen on, but the recursion is limited to a single level so it's
not so bad.
2024-12-16 15:20:13 -07:00
Wez Furlong 44eacf1807 providers: add HostName to enable exactly matching MX hostnames 2024-12-16 14:09:01 -07:00
Wez Furlong 48e89105a7 fix provider rule matching with MXSuffix
Given a provider with the following config:

```json
      "match": [
        {
          "MXSuffix": "mta5.am0.yahoodns.net"
        },
        {
          "MXSuffix": "mta6.am0.yahoodns.net"
        },
        {
          "MXSuffix": "mta7.am0.yahoodns.net"
        }
      ]
```

(Note that this configuration is not ideal because someone with
`notreallymta5.am0.yahoodns.net` in their MX records will match
this.  If you were using SMTP auth for such a site, then you risk
leaking your credentials to it! We should consider adding an exact
match option for this case)

we could never match this because the logic had the inner and outer
loops swapped.

For a provider to match, all of the resolved host names must match
at least one of the MXSuffixes defined in the rule.

The flipped logic prevented that from matching.

Most of this commit is adding stuff to help trace this down
and debug it.

In particular, `resolve-queue-config` will tell you what the
effective value of the get-queue-config event is for a given
queue name, and `resolve-shaping-domain` will show you the shaping
configuration for a (bogus) source.
2024-12-16 12:46:31 -07:00
Wez Furlong 5f19f7ce04 add deferred_queue option to the smtp listener
This option should be used with caution, and ideally only
for trusted networks.

The purpose is to absorb the latency of post-DATA processing
and hide it from the trust injector.

It defers processing that would normally happen in smtp_server_message_received
and instead will, at some (ideally) near-future time trigger an
smtp_server_message_deferred_inject event instead.

This will marginally increase your average injection latency but should
clamp your worst case injection latency much lower because the outliers
will not happen inline with the injecting client.
2024-12-16 11:27:40 -07:00
Wez Furlong 1037b1a11e smtp_dispatcher: expand protocol sync handling to 503
Expand the 3xx-is-impossible logic to include a 503 response,
which can only indicate a bad sequence of commands.

We should retry the message in that situation.
2024-12-13 15:25:28 -07:00
Wez Furlong 98a5479ce9 rfc5321/client: handle obscure DATA case in rfc2920
The pipelining spec allows for a server to proceed with a transaction
in which there were no valid recipients and validly enter the DATA
portion of the transaction.

I suspect that this might be responsible for the occasional protocol
synchronization issue that we've seen.

Handle that case per the recommendations in the RFC by sending
a dummy DATA in the case that some of the commands failed by
DATA succeeded.
2024-12-13 15:18:58 -07:00
Wez Furlong 40e29b28b8 cidr-map: use more compact Debug representation
It's bothered me for a while how verbose the debug print for
prohibited_hosts and skip_hosts is in diagnostics shared from
users.

This commit adopts a more succint map-like representation of
the cidr structure, and similar set-like representation for
the set equivalent.
2024-12-13 10:09:46 -07:00
Wez Furlong 8620dd56db smtp_dispatcher: include session id in protocol sync error message 2024-12-13 09:43:31 -07:00
Wez Furlong b3aa6d4154 shaping.lua: explicit gc in kumo.tsa.config.monitor
The config monitor task is a long-lived task that runs
inside the same lua context for its entire lifetime.

That task obtains a reference to the current shaping data
every 30 seconds to decide whether it needs to synthesize
an update to the config epoch.

Lua's memory management cannot see the total amount of
ram consumed by the shaping data because the shaping data
is a userdata type whose ram is managed externally.
That means that lua things that this task is generally not
using many resources (~ a handful of pointers per loop iteration)
and gc doesn't kick in very aggressively.

For sites with large TSA shaping overrides, or otherwise with
very large shaping data, this can result in an accumulation
of stale shaping data in that long-lived lua context.

This commit adds an explicit gc trigger before the task
sleeps on each iteration so that we can release those references.
2024-12-13 06:38:34 -07:00
Mike Hillyer 77832e81a6 Typo 2024-12-12 13:59:35 -05:00
Wez Furlong f19b6bc7f3 apply timerwheel batch wait to singleton wheel as well
In 4a0a4d6a1e I fixed an issue where
the bucketing used in the timewheel could trick the throttling
event handler into thinking that the message had been explicitly
throttled by the event, when in reality it was just a 20ms discrepancy.

I overlooked that this same logic should also apply to the equivalent
logic for the singleton timer wheel, so that is what this commit does.
2024-12-12 10:44:24 -07:00
Wez Furlong 81987604e9 memory: include jemalloc stats in api endpoint
The goal here was to show the lowest level of stats available from the
allocator by querying jemalloc for that info, so that we can understand
the gap between what Rust sees and what any embedded C stuff might see.

```
; curl -s 'http://127.0.0.1:8000/api/admin/memory/stats'
JemallocStats {
    allocated: 25654488 (25.65 MB),
    active: 35651584 (35.65 MB),
    metadata: 29685520 (29.69 MB),
    resident: 85446656 (85.45 MB),
    mapped: 364630016 (364.63 MB),
    retained: 252981248 (252.98 MB),
}
RSS = 142651392 (142.65 MB)
soft limit = Some(202282060800 (202.28 GB))
hard limit = Some(269709414400 (269.71 GB))
live = CountAndSize { count: 109237 (109,237), size: 12111580 (12.11 MB) }

use kumo.enable_memory_callstack_tracking(true) to enable additional stats
```
2024-12-12 09:08:50 -07:00
Wez Furlong 8aee36a3ce throttle: increase tolerance in test cases
refs: https://github.com/KumoCorp/kumomta/issues/297
2024-12-12 07:16:48 -07:00
Wez Furlong d6733198cd shaping.toml: switch to remember_broken_tls
Now that we've had a couple of users successfully running with
remember_broken_tls, let's make it the default so that we can
reduce the typical size of the TSA-generated configuration
2024-12-12 07:10:40 -07:00
Wez Furlong 335fbd96e5 summaryize-memory: show sampled range for the notable values too 2024-12-12 06:50:35 -07:00
Wez Furlong 6ce88f40ed lruttl: periodically prune out expired entries
So far, we've deferred proactively aging out expired entries from
our TTL-based LRU caches, instead relying on the next lookup operation
on the expired key to detect and replace the stale value.

Recently we have been focused on keeping the memory footprint lower.

This commit adds a periodic (every 30 seconds) background task
that will determine all cache entries that have expired, and
remove them from the cache.
2024-12-11 22:25:46 -07:00
Wez Furlong 4381995ec5 memory: another run at waiting for memory status subscriber
in 1c25a0d8a4 I only partially
handled this race condition in CI.

This commit handles it more generally.
2024-12-11 16:46:46 -07:00
Wez Furlong 88c28b62c0 memory: implement our own tracking allocator
The re_memory crate was good for a first pass at implementing call stack
tracking, but unfortunately, it was symbolicating the stack traces in
the allocation path which had terrible performance implications.

This commit implements our own somewhat leaner and meaner version of
this functionality that defers resolving the call stack symbols until
the top callstacks are computed.

This makes things run faster than they would have when using re_memory.

Shedding re_memory also allows us to avoid pulling in the various other
dependencies that it pulled in that were otherwise unused, which makes
me feel even better about this.

This commit also improves the output of the summarize-memory tool to
make it clearer that we are uncertain about the full extent of the
sampled allocations: the stochastic sampling only nominally samples
every 64 of the medium-sized allocations. That means that the reported
total size for those may be 1/64 of what we printed previously.  We now
print the range of allocations and use a ~ character to indicate that
we're uncertain about the number of allocations that were made for those
sampled allocations.
2024-12-11 15:51:26 -07:00
Wez Furlong 414a9b617d ready_queue: switch update_capacity to MessageList
Rather than build a vec of the excess, use a list since
it requires no additional heap memory.
2024-12-10 17:59:24 -07:00
Wez Furlong 68c3606420 ready_queue: use MessageList when taking ready queue
Now that we have doubly-linked-lists of Messages for these things,
it is more efficient to take the whole list when we're doing
bulk ready queue operations, so let's do that.
2024-12-10 17:55:12 -07:00
Wez Furlong 1c25a0d8a4 server-memory: allow subscribe_to_memory_status_changes to return None
In some cases in CI, we might have a race condition where we spawn
the lruttl background thread before the memory monitor thread
completes it startup.
2024-12-10 16:29:18 -07:00
Wez Furlong 5a53af04a5 logging: async-channel -> flume
The flume bounded queue doesn't eagerly allocate its full capacity
and otherwise runs fast enough for our logging that we don't notice
the difference.  This reduces the "static" footprint of kumomta
in the (hopefully) common case where logging is keeping up with
the system throughput and is not approaching the limit of the
default logging backlog.

Remove unused reference to async-channel from kumo-server-runtime.
2024-12-10 15:42:46 -07:00
Wez Furlong a0ec332115 log-types: reduce size of JsonLogRecord by ~30% in common case
One of the things that shows up in the large allocation list
is the pre-allocated channel used by the logging infrastructure.

I'd like to adjust that to not require pre-allocation (or at least,
not require so much pre-allocation), but an easy memory reduction
win is to Box out the FBL portion of the record; incoming FBL
reports are incredibly rare compared to the bulk of the messages
processed by the system so it is reasonable to use an extra heap
allocation for those if it means we can save a nice chunk of
memory for the common case.
2024-12-10 15:18:33 -07:00
Wez Furlong 59c0bc7db7 queue: shrink the singleton hashset after popping
Let's try to keep our memory usage a bit more trim by shrinking
the hashset after we've popped messages from the singleton timer
wheel.
2024-12-10 14:59:11 -07:00
Wez Furlong dbcc831b82 simple_policy: tweak my test config for more throughput 2024-12-10 14:49:12 -07:00
Wez Furlong c13dde9ecb message: introduce intrusive MessageList type, replace Fifo internals
I've been recently troubleshooting a couple of systems with high memory
usage, and in one of them there were very large amounts of
memory being allocated to ready queues.  That could be partially
mitigated by reducing `max_ready` to a more reasonable and small value,
but it is difficult to compute the right balance between large-enough
for high throughput and small-enough to keep memory usage reasonable.

This commit switches away from crossbeam's ArrayQueue, which
pre-allocates sufficient space to hold exactly `max_ready` messages for
each instantiated ready queue, and to a newly introduced MessageList,
which is an intrusive doubly-linked list.

The intrusive list, in exchange for some small additional overhead
per-Message, requires no auxilliary additional memory allocations to
track the membership of that Message in some other list.

That means that `max_ready` is no longer a pre-allocated minimum amount
of additional storage, and changes the memory overhead from
`O(number-of-queues * max_ready)` to `O(number-of-ready-messages)`,
which is typically a lot smaller.  This is independent of the individual
messages metadata and bodies that are nominally associated with being in
a ready queue.
2024-12-10 14:29:41 -07:00
Wez Furlong fa276973c4 validate-shaping: show approx memory usage of processed files 2024-12-10 11:16:16 -07:00
Wez Furlong db7d846e3a shaping.lua: reduce memoize cache capacity to 1
Since we produce one large Shaping object from the static set
of inputs (eg: the list of files), there isn't a lot of benefit
from caching the prior 9 generations of the shaping config.

Some sites have especially large sets of TSA-produced shaping
overrides which can add to memory pressure.
2024-12-10 09:09:06 -07:00
Wez Furlong 66fd22631f summarize-memory: add more higher level aggregation
sum allocations of similar purpose by looking for certain "nexus"
symbols in the call stacks.
2024-12-10 09:05:11 -07:00
Wez Furlong f2de5011e5 summarize-memory: add aggregation of call stacks 2024-12-10 08:54:49 -07:00
Wez Furlong c4816930d9 cargo update 2024-12-10 07:58:32 -07:00
Wez Furlong 5812d84d38 add a little utility to summarize memory stats 2024-12-10 07:56:56 -07:00
Wez Furlong 6ddf3c87d6 tsa: refactor: introduce PreferRollup enum
This makes the non-description true/false that was used previously
much more descriptive.
2024-12-09 08:18:18 -07:00
Wez Furlong 8689d4daf0 docs: document new TSA Bounce actions
closes: https://github.com/KumoCorp/kumomta/issues/272
2024-12-09 07:27:14 -07:00
Wez Furlong 6da455d42b kumod: process TSA Bounce rules and apply as bounces
Accepts scheduled queue bounce events from the event subscription,
and translates them into local admin bounce records.

refs: https://github.com/KumoCorp/kumomta/issues/272
2024-12-09 07:15:45 -07:00
Wez Furlong a316f7a9c4 shaping.lua: use new tsa endpoint, falling back to old
Adjust our subscription to try the new generic event endpoint,
and if that 404's, fall back to the suspension endpoint.

This commit doesn't add support for the new bounce records,
it is focused on ensuring that suspension information is
returned correctly for both old and new endpoints.

The integration tests will use the new endpoint only
because it is too much of a faff to run them against
a legacy tsa-daemon (since we can only build and run
the current one here).

I tested this commit in phases though:

* Deliberately used the wrong URL for the new endpoint to
  trigger the 404 logic case and handle fallback
* Inserted an error in the fallback case to ensure that
  the fallback wasn't happening once the new endpoint URL
  was set to the correct one.

So this should enable folks to upgrade either TSA or kumod
in either order across their deployment, without the "mismatch"
in TSA and kumod versions harming their traffic.

refs: https://github.com/KumoCorp/kumomta/issues/272
2024-12-09 06:22:32 -07:00
Wez Furlong 80c61ec3df tsa: add plumbing to support new Bounce action
Full docs will be written up once the kumod side is done.

This commit:

* Adds a table to record bounces
* bounces can be scoped to scheduled queues (not ready queues) keyed
  either by:
   * domain
   * domain + tenant
   * domain + tenant + optional campaign

Rather than define one websocket endpoint per event type, define
a new endpoint that can support more than just suspensions.

The existing suspension endpoint taps into the same source of
events, but filters it down to just suspension data for
backwards compatibility.

The integration tests for kumod+tsa that validate suspensions
continue to operate correctly with this change, proving that
this works.

In the next commit, the client side will be adjusted to be aware
of the bounces on the new endpoint in a way that will tolerate
version splay during deployment.

One thing I noticed while implementing this is that we were not
reporting the list of scheduled q suspensions in the initial
websocket (re)connection.  This would impact newly restarted
kumod instances the most, but they would eventually right
themselves because the node that missed the data would likely
pass traffic that would trigger the rule anew, or they wouldn't
and it wouldn't matter anyway.

refs: https://github.com/KumoCorp/kumomta/issues/272
2024-12-09 06:17:31 -07:00
Wez Furlong c92cff2a60 maildir: support template expansion of maildir_path
closes: https://github.com/KumoCorp/kumomta/issues/109
2024-12-08 10:15:53 -07:00
Wez Furlong 1a83f72b6d maildir: expose dir_mode and file_mode to protocol config
You can now specify the dir and file modes in your maildir
protocol configuration.

refs: https://github.com/KumoCorp/kumomta/issues/109
2024-12-08 09:41:55 -07:00
Wez Furlong 0199bb0097 maildir: add ability to set dir_mode and file_mode
This adds this capability to the crate, but doesn't expose it
to kumomta.

refs: https://github.com/KumoCorp/kumomta/issues/109
2024-12-08 08:34:34 -07:00
Wez Furlong 79fb57d2e2 maildir: switch to explicit constructor
previously you'd use .into() to create a maildir from either
a path or a string type.

Now you use `with_path` with any type that can be converted to
a PathBuf.
2024-12-08 07:44:27 -07:00
Wez Furlong ec030ce0a3 maildir: remove mmap feature
we're not using it and it simplifies the code a little to
remove it.

refs: https://github.com/KumoCorp/kumomta/issues/109
2024-12-08 07:34:24 -07:00
Wez Furlong c676e58dd9 refactor: extract jinja template logic to kumo-template crate
It's a little bitty to setup and run the template engine,
and the explicit lifetime is annoying to deal with.

Extract it to its own crate to streamline the interface for
the way that we use it in kumomta.

refs: https://github.com/KumoCorp/kumomta/issues/109
2024-12-08 07:18:25 -07:00
Wez Furlong c0472959eb smtp_server: adjust initialization
The recent changes to the runtime introduced a minor race condition
for the integration tests; previously it was guaranteed that all
listeners would output their bound listener address before we
printed out the "initialization complete" string that the test
harness looks for to decide when the service is up and running.

The recent changes meant that this ordering was no longer guaranteed,
which resulted in some of the tests failing in CI because they
couldn't resolve the smtp listener.

This commit refactors the spawning of the smtp listener so that
the printing of the bound address is synchronous wrt. the initialization
callback completing, and spawns the actual accept loop into the
runtime.

This should hopefully resolve the test failures.
2024-12-07 13:58:09 -07:00