Software /
code /
prosody
Comparison
spec/net_resolvers_service_spec.lua @ 12401:c029ddcad258
net.resolvers.service: Honour record 'weight' when picking SRV targets
#NotHappyEyeballs
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Thu, 17 Mar 2022 18:20:26 +0000 |
comparison
equal
deleted
inserted
replaced
12400:728d1c1dc7db | 12401:c029ddcad258 |
---|---|
1 local set = require "util.set"; | |
2 | |
3 insulate("net.resolvers.service", function () | |
4 local adns = { | |
5 resolver = function () | |
6 return { | |
7 lookup = function (_, cb, qname, qtype, qclass) | |
8 if qname == "_xmpp-server._tcp.example.com" | |
9 and (qtype or "SRV") == "SRV" | |
10 and (qclass or "IN") == "IN" then | |
11 cb({ | |
12 { -- 60+35+60 | |
13 srv = { target = "xmpp0-a.example.com", port = 5228, priority = 0, weight = 60 }; | |
14 }; | |
15 { | |
16 srv = { target = "xmpp0-b.example.com", port = 5216, priority = 0, weight = 35 }; | |
17 }; | |
18 { | |
19 srv = { target = "xmpp0-c.example.com", port = 5200, priority = 0, weight = 0 }; | |
20 }; | |
21 { | |
22 srv = { target = "xmpp0-d.example.com", port = 5256, priority = 0, weight = 120 }; | |
23 }; | |
24 | |
25 { | |
26 srv = { target = "xmpp1-a.example.com", port = 5273, priority = 1, weight = 30 }; | |
27 }; | |
28 { | |
29 srv = { target = "xmpp1-b.example.com", port = 5274, priority = 1, weight = 30 }; | |
30 }; | |
31 | |
32 { | |
33 srv = { target = "xmpp2.example.com", port = 5275, priority = 2, weight = 0 }; | |
34 }; | |
35 }); | |
36 elseif qname == "_xmpp-server._tcp.single.example.com" | |
37 and (qtype or "SRV") == "SRV" | |
38 and (qclass or "IN") == "IN" then | |
39 cb({ | |
40 { | |
41 srv = { target = "xmpp0-a.example.com", port = 5269, priority = 0, weight = 0 }; | |
42 }; | |
43 }); | |
44 elseif qname == "_xmpp-server._tcp.half.example.com" | |
45 and (qtype or "SRV") == "SRV" | |
46 and (qclass or "IN") == "IN" then | |
47 cb({ | |
48 { | |
49 srv = { target = "xmpp0-a.example.com", port = 5269, priority = 0, weight = 0 }; | |
50 }; | |
51 { | |
52 srv = { target = "xmpp0-b.example.com", port = 5270, priority = 0, weight = 1 }; | |
53 }; | |
54 }); | |
55 elseif qtype == "A" then | |
56 local l = qname:match("%-(%a)%.example.com$") or "1"; | |
57 local d = ("%d"):format(l:byte()) | |
58 cb({ | |
59 { | |
60 a = "127.0.0."..d; | |
61 }; | |
62 }); | |
63 elseif qtype == "AAAA" then | |
64 local l = qname:match("%-(%a)%.example.com$") or "1"; | |
65 local d = ("%04d"):format(l:byte()) | |
66 cb({ | |
67 { | |
68 aaaa = "fdeb:9619:649e:c7d9::"..d; | |
69 }; | |
70 }); | |
71 else | |
72 cb(nil); | |
73 end | |
74 end; | |
75 }; | |
76 end; | |
77 }; | |
78 package.loaded["net.adns"] = mock(adns); | |
79 local resolver = require "net.resolvers.service"; | |
80 math.randomseed(os.time()); | |
81 it("works for 99% of deployments", function () | |
82 -- Most deployments only have a single SRV record, let's make | |
83 -- sure that works okay | |
84 | |
85 local expected_targets = set.new({ | |
86 -- xmpp0-a | |
87 "tcp4 127.0.0.97 5269"; | |
88 "tcp6 fdeb:9619:649e:c7d9::0097 5269"; | |
89 }); | |
90 local received_targets = set.new({}); | |
91 | |
92 local r = resolver.new("single.example.com", "xmpp-server"); | |
93 local done = false; | |
94 local function handle_target(...) | |
95 if ... == nil then | |
96 done = true; | |
97 -- No more targets | |
98 return; | |
99 end | |
100 received_targets:add(table.concat({ ... }, " ", 1, 3)); | |
101 end | |
102 r:next(handle_target); | |
103 while not done do | |
104 r:next(handle_target); | |
105 end | |
106 | |
107 -- We should have received all expected targets, and no unexpected | |
108 -- ones: | |
109 assert.truthy(set.xor(received_targets, expected_targets):empty()); | |
110 end); | |
111 | |
112 it("supports A/AAAA fallback", function () | |
113 -- Many deployments don't have any SRV records, so we should | |
114 -- fall back to A/AAAA records instead when that is the case | |
115 | |
116 local expected_targets = set.new({ | |
117 -- xmpp0-a | |
118 "tcp4 127.0.0.97 5269"; | |
119 "tcp6 fdeb:9619:649e:c7d9::0097 5269"; | |
120 }); | |
121 local received_targets = set.new({}); | |
122 | |
123 local r = resolver.new("xmpp0-a.example.com", "xmpp-server", "tcp", { default_port = 5269 }); | |
124 local done = false; | |
125 local function handle_target(...) | |
126 if ... == nil then | |
127 done = true; | |
128 -- No more targets | |
129 return; | |
130 end | |
131 received_targets:add(table.concat({ ... }, " ", 1, 3)); | |
132 end | |
133 r:next(handle_target); | |
134 while not done do | |
135 r:next(handle_target); | |
136 end | |
137 | |
138 -- We should have received all expected targets, and no unexpected | |
139 -- ones: | |
140 assert.truthy(set.xor(received_targets, expected_targets):empty()); | |
141 end); | |
142 | |
143 | |
144 it("works", function () | |
145 local expected_targets = set.new({ | |
146 -- xmpp0-a | |
147 "tcp4 127.0.0.97 5228"; | |
148 "tcp6 fdeb:9619:649e:c7d9::0097 5228"; | |
149 "tcp4 127.0.0.97 5273"; | |
150 "tcp6 fdeb:9619:649e:c7d9::0097 5273"; | |
151 | |
152 -- xmpp0-b | |
153 "tcp4 127.0.0.98 5274"; | |
154 "tcp6 fdeb:9619:649e:c7d9::0098 5274"; | |
155 "tcp4 127.0.0.98 5216"; | |
156 "tcp6 fdeb:9619:649e:c7d9::0098 5216"; | |
157 | |
158 -- xmpp0-c | |
159 "tcp4 127.0.0.99 5200"; | |
160 "tcp6 fdeb:9619:649e:c7d9::0099 5200"; | |
161 | |
162 -- xmpp0-d | |
163 "tcp4 127.0.0.100 5256"; | |
164 "tcp6 fdeb:9619:649e:c7d9::0100 5256"; | |
165 | |
166 -- xmpp2 | |
167 "tcp4 127.0.0.49 5275"; | |
168 "tcp6 fdeb:9619:649e:c7d9::0049 5275"; | |
169 | |
170 }); | |
171 local received_targets = set.new({}); | |
172 | |
173 local r = resolver.new("example.com", "xmpp-server"); | |
174 local done = false; | |
175 local function handle_target(...) | |
176 if ... == nil then | |
177 done = true; | |
178 -- No more targets | |
179 return; | |
180 end | |
181 received_targets:add(table.concat({ ... }, " ", 1, 3)); | |
182 end | |
183 r:next(handle_target); | |
184 while not done do | |
185 r:next(handle_target); | |
186 end | |
187 | |
188 -- We should have received all expected targets, and no unexpected | |
189 -- ones: | |
190 assert.truthy(set.xor(received_targets, expected_targets):empty()); | |
191 end); | |
192 | |
193 it("balances across weights correctly #slow", function () | |
194 -- This mimics many repeated connections to 'example.com' (mock | |
195 -- records defined above), and records the port number of the | |
196 -- first target. Therefore it (should) only return priority | |
197 -- 0 records, and the input data is constructed such that the | |
198 -- last two digits of the port number represent the percentage | |
199 -- of times that record should (on average) be picked first. | |
200 | |
201 -- To prevent random test failures, we test across a handful | |
202 -- of fixed (randomly selected) seeds. | |
203 for _, seed in ipairs({ 8401877, 3943829, 7830992 }) do | |
204 math.randomseed(seed); | |
205 | |
206 local results = {}; | |
207 local function run() | |
208 local run_results = {}; | |
209 local r = resolver.new("example.com", "xmpp-server"); | |
210 local function record_target(...) | |
211 if ... == nil then | |
212 -- No more targets | |
213 return; | |
214 end | |
215 run_results = { ... }; | |
216 end | |
217 r:next(record_target); | |
218 return run_results[3]; | |
219 end | |
220 | |
221 for _ = 1, 1000 do | |
222 local port = run(); | |
223 results[port] = (results[port] or 0) + 1; | |
224 end | |
225 | |
226 local ports = {}; | |
227 for port in pairs(results) do | |
228 table.insert(ports, port); | |
229 end | |
230 table.sort(ports); | |
231 for _, port in ipairs(ports) do | |
232 --print("PORT", port, tostring((results[port]/1000) * 100).."% hits (expected "..tostring(port-5200).."%)"); | |
233 local hit_pct = (results[port]/1000) * 100; | |
234 local expected_pct = port - 5200; | |
235 --print(hit_pct, expected_pct, math.abs(hit_pct - expected_pct)); | |
236 assert.is_true(math.abs(hit_pct - expected_pct) < 5); | |
237 end | |
238 --print("---"); | |
239 end | |
240 end); | |
241 end); |