Files
kumomta/assets/policy-extras/shaping.lua
T

190 lines
4.6 KiB
Lua

local mod = {}
local kumo = require 'kumo'
local utils = require 'policy-extras.policy_utils'
--[[
Usage:
Create a `/opt/kumomta/etc/shaping.json` with contents like:
```json
{
"gmail.com": {
// "mx_rollup": false, // if false, is NOT shared with other domains with same mx
"connection_limit": 3,
// Entries that are themselves objects are considered to be
// source configuration. These will take precendence over
// the more general configuration for the domain/site
"sources": {
"source-0": {
"connection_limit": 5
}
}
}
}
```
---
local shaping = require 'policy-extras.shaping'
kumo.on('get_egress_path_config', shaping:setup('/opt/kumomta/etc/shaping.json'))
---
Alternatively, you could use a TOML file instead.
]]
function mod:setup(extra_files)
local function load_shaping_data(file_names)
local site_to_domains = {}
local result = {
by_site = {},
by_domain = {},
}
for _, filename in ipairs(file_names) do
local data = utils.load_json_or_toml_file(filename)
-- print('Loaded', kumo.json_encode_pretty(data))
for domain, config in pairs(data) do
local entry = {
sources = {},
params = {},
}
local mx_rollup = true
if config.mx_rollup ~= nil then
mx_rollup = config.mx_rollup
end
local replace_base = false
if config.replace_base ~= nil then
replace_base = config.replace_base
end
config.mx_rollup = nil
config.replace_base = nil
if domain == 'default' then
mx_rollup = false
end
for k, v in pairs(config) do
if k == 'sources' then
entry.sources = v
else
entry.params[k] = v
end
end
if mx_rollup then
local site_name = kumo.dns.lookup_mx(domain).site_name
if site_name == '' then
error(
string.format(
'domain %s has a NULL MX and cannot be used with mx_rollup=true',
domain
)
)
end
if not replace_base then
utils.merge_into(result.by_site[site_name], entry)
end
result.by_site[site_name] = entry
local site_domains = site_to_domains[site_name] or {}
site_domains[domain] = true
site_to_domains[site_name] = site_domains
else
if not replace_base then
utils.merge_into(result.by_domain[domain], entry)
end
result.by_domain[domain] = entry
end
end
end
local conflicted = {}
for site, domains in pairs(site_to_domains) do
domains = utils.table_keys(domains)
if #domains > 1 then
domains = table.concat(domains, ', ')
table.insert(conflicted, domains)
print(
'Multiple domains rollup to the same site: '
.. site
.. ' -> '
.. domains
)
end
end
if #conflicted > 0 then
-- This will generate a transient failure for every message
-- until the issue is resolved
error(
'multiple conflicting rollup domains '
.. table.concat(conflicted, ' ')
)
end
-- print('Computed', kumo.json_encode_pretty(result))
return result
end
local cached_load_data = kumo.memoize(load_shaping_data, {
name = 'shaping_data',
ttl = '5 minutes',
capacity = 10,
})
local file_names = {
-- './assets/policy-extras/shaping.toml',
'/opt/kumomta/share/policy-extras/shaping.toml',
}
if extra_files then
for _, filename in ipairs(extra_files) do
table.insert(file_names, filename)
end
end
local function get_egress_path_config(domain, egress_source, site_name)
local data = cached_load_data(file_names)
local by_site = data.by_site[site_name]
local by_domain = data.by_domain[domain]
local params = {}
-- apply basic/default configuration
utils.merge_into((data.by_domain['default'] or {}).params, params)
-- then site config
if by_site then
utils.merge_into(by_site.params, params)
end
-- then domain config
if by_domain then
utils.merge_into(by_domain.params, params)
end
-- then source config for the site
if by_site then
utils.merge_into(by_site.sources[egress_source], params)
end
-- then source config for the domain
if by_domain then
utils.merge_into(by_domain.sources[egress_source], params)
end
-- print("going to make egress path", kumo.json_encode(params))
return kumo.make_egress_path(params)
end
return get_egress_path_config
end
return mod