Annotate

plugins/mod_limits.lua @ 10049:c523642ea293

util.dependencies: Increase Lua version to warn about to 5.4 No significant problems have been encountered with Lua 5.3 itself, so apart from some odd problems in LuaExpat it seems about time to declare it ready.
author Kim Alvefur <zash@zash.se>
date Wed, 19 Jun 2019 19:16:09 +0200
parent 9943:46773fe2be45
child 10099:7e3196e0263e
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
8453
6b3e7fddd723 mod_limits: Fix typo in comment
Kim Alvefur <zash@zash.se>
parents: 8269
diff changeset
1 -- Because we deal with pre-authed sessions and streams we can't be host-specific
8256
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
2 module:set_global();
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
3
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
4 local filters = require "util.filters";
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
5 local throttle = require "util.throttle";
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
6 local timer = require "util.timer";
8269
25237002aba4 mod_limits: Handle fractional outstanding balance values (caused by e3f7b6fa46ba)
Matthew Wild <mwild1@gmail.com>
parents: 8256
diff changeset
7 local ceil = math.ceil;
8256
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
8
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
9 local limits_cfg = module:get_option("limits", {});
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
10 local limits_resolution = module:get_option_number("limits_resolution", 1);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
11
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
12 local default_bytes_per_second = 3000;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
13 local default_burst = 2;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
14
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
15 local rate_units = { b = 1, k = 3, m = 6, g = 9, t = 12 } -- Plan for the future.
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
16 local function parse_rate(rate, sess_type)
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
17 local quantity, unit, exp;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
18 if rate then
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
19 quantity, unit = rate:match("^(%d+) ?([^/]+)/s$");
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
20 exp = quantity and rate_units[unit:sub(1,1):lower()];
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
21 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
22 if not exp then
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
23 module:log("error", "Error parsing rate for %s: %q, using default rate (%d bytes/s)", sess_type, rate, default_bytes_per_second);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
24 return default_bytes_per_second;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
25 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
26 return quantity*(10^exp);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
27 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
28
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
29 local function parse_burst(burst, sess_type)
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
30 if type(burst) == "string" then
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
31 burst = burst:match("^(%d+) ?s$");
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
32 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
33 local n_burst = tonumber(burst);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
34 if not n_burst then
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
35 module:log("error", "Unable to parse burst for %s: %q, using default burst interval (%ds)", sess_type, tostring(burst), default_burst);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
36 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
37 return n_burst or default_burst;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
38 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
39
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
40 -- Process config option into limits table:
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
41 -- limits = { c2s = { bytes_per_second = X, burst_seconds = Y } }
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
42 local limits = {};
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
43
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
44 for sess_type, sess_limits in pairs(limits_cfg) do
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
45 limits[sess_type] = {
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
46 bytes_per_second = parse_rate(sess_limits.rate, sess_type);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
47 burst_seconds = parse_burst(sess_limits.burst, sess_type);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
48 };
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
49 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
50
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
51 local default_filter_set = {};
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
52
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
53 function default_filter_set.bytes_in(bytes, session)
9941
a2f8d54dd445 mod_limits: Fix indentation
Kim Alvefur <zash@zash.se>
parents: 8803
diff changeset
54 local sess_throttle = session.throttle;
a2f8d54dd445 mod_limits: Fix indentation
Kim Alvefur <zash@zash.se>
parents: 8803
diff changeset
55 if sess_throttle then
a2f8d54dd445 mod_limits: Fix indentation
Kim Alvefur <zash@zash.se>
parents: 8803
diff changeset
56 local ok, balance, outstanding = sess_throttle:poll(#bytes, true);
8256
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
57 if not ok then
9941
a2f8d54dd445 mod_limits: Fix indentation
Kim Alvefur <zash@zash.se>
parents: 8803
diff changeset
58 session.log("debug", "Session over rate limit (%d) with %d (by %d), pausing", sess_throttle.max, #bytes, outstanding);
8269
25237002aba4 mod_limits: Handle fractional outstanding balance values (caused by e3f7b6fa46ba)
Matthew Wild <mwild1@gmail.com>
parents: 8256
diff changeset
59 outstanding = ceil(outstanding);
8256
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
60 session.conn:pause(); -- Read no more data from the connection until there is no outstanding data
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
61 local outstanding_data = bytes:sub(-outstanding);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
62 bytes = bytes:sub(1, #bytes-outstanding);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
63 timer.add_task(limits_resolution, function ()
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
64 if not session.conn then return; end
9941
a2f8d54dd445 mod_limits: Fix indentation
Kim Alvefur <zash@zash.se>
parents: 8803
diff changeset
65 if sess_throttle:peek(#outstanding_data) then
8256
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
66 session.log("debug", "Resuming paused session");
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
67 session.conn:resume();
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
68 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
69 -- Handle what we can of the outstanding data
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
70 session.data(outstanding_data);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
71 end);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
72 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
73 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
74 return bytes;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
75 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
76
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
77 local type_filters = {
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
78 c2s = default_filter_set;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
79 s2sin = default_filter_set;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
80 s2sout = default_filter_set;
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
81 };
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
82
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
83 local function filter_hook(session)
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
84 local session_type = session.type:match("^[^_]+");
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
85 local filter_set, opts = type_filters[session_type], limits[session_type];
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
86 if opts then
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
87 session.throttle = throttle.create(opts.bytes_per_second * opts.burst_seconds, opts.burst_seconds);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
88 filters.add_filter(session, "bytes/in", filter_set.bytes_in, 1000);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
89 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
90 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
91
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
92 function module.load()
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
93 filters.add_filter_hook(filter_hook);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
94 end
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
95
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
96 function module.unload()
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
97 filters.remove_filter_hook(filter_hook);
cdffe33efae4 mod_limits: Import from prosody-modules 2c59f2f0c37d (fixes #129)
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
98 end
9942
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
99
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
100 function module.add_host(module)
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
101 local unlimited_jids = module:get_option_inherited_set("unlimited_jids", {});
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
102
9943
46773fe2be45 mod_limits: Fix typo
Kim Alvefur <zash@zash.se>
parents: 9942
diff changeset
103 if not unlimited_jids:empty() then
9942
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
104 module:hook("authentication-success", function (event)
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
105 local session = event.session;
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
106 local session_type = session.type:match("^[^_]+");
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
107 local jid = session.username .. "@" .. session.host;
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
108 if unlimited_jids:contains(jid) then
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
109 local filter_set = type_filters[session_type];
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
110 filters.remove_filter(session, "bytes/in", filter_set.bytes_in);
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
111 session.throttle = nil;
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
112 end
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
113 end);
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
114 end
b0d5f4ae92b7 mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323)
Kim Alvefur <zash@zash.se>
parents: 9941
diff changeset
115 end