Merge ~cgrabowski/maas:backport_lp2056050_fix_to_3.4 into maas:3.4
- Git
- lp:~cgrabowski/maas
- backport_lp2056050_fix_to_3.4
- Merge into 3.4
Proposed by
Christian Grabowski
Status: | Merged |
---|---|
Approved by: | Christian Grabowski |
Approved revision: | 8f81b2aec028c29ef23f7529bb1ba39e1cfba812 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~cgrabowski/maas:backport_lp2056050_fix_to_3.4 |
Merge into: | maas:3.4 |
Diff against target: |
340 lines (+178/-11) 10 files modified
src/maasserver/dns/config.py (+1/-1) src/maasserver/dns/tests/test_config.py (+4/-1) src/maasserver/fields.py (+39/-0) src/maasserver/forms/domain.py (+5/-5) src/maasserver/forms/tests/test_domain.py (+24/-0) src/maasserver/migrations/maasserver/0305_add_port_to_forward_dns_servers.py (+17/-0) src/maasserver/models/forwarddnsserver.py (+12/-1) src/maasserver/tests/test_fields.py (+49/-0) src/provisioningserver/dns/tests/test_config.py (+26/-2) src/provisioningserver/templates/dns/named.conf.template (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Approve | ||
Christian Grabowski | Approve | ||
Review via email: mp+462239@code.launchpad.net |
Commit message
fix: allow non-default ports for forward dns servers
(cherry picked from commit d21ba292d3f8f8b
Description of the change
To post a comment you must log in.
Revision history for this message
Christian Grabowski (cgrabowski) wrote : | # |
self-approving backport
review:
Approve
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b backport_
STATUS: FAILED
LOG: http://
COMMIT: 938865f25ed9fb5
review:
Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b backport_
STATUS: FAILED BUILD
LOG: http://
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b backport_
STATUS: SUCCESS
COMMIT: 8f81b2aec028c29
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py | |||
2 | index b03d47d..4709fe6 100644 | |||
3 | --- a/src/maasserver/dns/config.py | |||
4 | +++ b/src/maasserver/dns/config.py | |||
5 | @@ -60,7 +60,7 @@ def forward_domains_to_forwarded_zones(forward_domains): | |||
6 | 60 | ( | 60 | ( |
7 | 61 | domain.name, | 61 | domain.name, |
8 | 62 | [ | 62 | [ |
10 | 63 | fwd_dns_srvr.ip_address | 63 | (fwd_dns_srvr.ip_address, fwd_dns_srvr.port) |
11 | 64 | for fwd_dns_srvr in domain.forward_dns_servers | 64 | for fwd_dns_srvr in domain.forward_dns_servers |
12 | 65 | ], | 65 | ], |
13 | 66 | ) | 66 | ) |
14 | diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py | |||
15 | index 44507a0..620cf44 100644 | |||
16 | --- a/src/maasserver/dns/tests/test_config.py | |||
17 | +++ b/src/maasserver/dns/tests/test_config.py | |||
18 | @@ -102,7 +102,10 @@ class TestDNSUtilities(MAASServerTestCase): | |||
19 | 102 | ) | 102 | ) |
20 | 103 | self.assertCountEqual( | 103 | self.assertCountEqual( |
21 | 104 | fwd_zones, | 104 | fwd_zones, |
23 | 105 | [(name1, [fwd_srvr1.ip_address]), (name2, [fwd_srvr2.ip_address])], | 105 | [ |
24 | 106 | (name1, [(fwd_srvr1.ip_address, fwd_srvr1.port)]), | ||
25 | 107 | (name2, [(fwd_srvr2.ip_address, fwd_srvr2.port)]), | ||
26 | 108 | ], | ||
27 | 106 | ) | 109 | ) |
28 | 107 | 110 | ||
29 | 108 | 111 | ||
30 | diff --git a/src/maasserver/fields.py b/src/maasserver/fields.py | |||
31 | index 661a4d6..f4bd280 100644 | |||
32 | --- a/src/maasserver/fields.py | |||
33 | +++ b/src/maasserver/fields.py | |||
34 | @@ -5,6 +5,7 @@ | |||
35 | 5 | 5 | ||
36 | 6 | from itertools import chain | 6 | from itertools import chain |
37 | 7 | import re | 7 | import re |
38 | 8 | import urllib | ||
39 | 8 | 9 | ||
40 | 9 | from django import forms | 10 | from django import forms |
41 | 10 | from django.core.exceptions import ObjectDoesNotExist, ValidationError | 11 | from django.core.exceptions import ObjectDoesNotExist, ValidationError |
42 | @@ -332,6 +333,44 @@ class IPListFormField(forms.CharField): | |||
43 | 332 | return " ".join(ips) | 333 | return " ".join(ips) |
44 | 333 | 334 | ||
45 | 334 | 335 | ||
46 | 336 | class IPPortListFormField(IPListFormField): | ||
47 | 337 | def __init__(self, default_port=None, *args, **kwargs): | ||
48 | 338 | super().__init__(*args, **kwargs) | ||
49 | 339 | self._default_port = default_port | ||
50 | 340 | |||
51 | 341 | def clean(self, value): | ||
52 | 342 | if value is None: | ||
53 | 343 | return None | ||
54 | 344 | else: | ||
55 | 345 | ip_ports = re.split(self.separators, value) | ||
56 | 346 | ip_ports = [ | ||
57 | 347 | ip_port.strip() for ip_port in ip_ports if ip_ports != "" | ||
58 | 348 | ] | ||
59 | 349 | result = [] | ||
60 | 350 | for ip_port in ip_ports: | ||
61 | 351 | if "." in ip_port or ("[" == ip_port[0] and "]:" in ip_port): | ||
62 | 352 | sock_addr = urllib.parse.urlsplit("//" + ip_port) | ||
63 | 353 | ip = sock_addr.hostname | ||
64 | 354 | port = ( | ||
65 | 355 | int(sock_addr.port) | ||
66 | 356 | if sock_addr.port | ||
67 | 357 | else self._default_port | ||
68 | 358 | ) | ||
69 | 359 | else: | ||
70 | 360 | ip = ip_port | ||
71 | 361 | port = self._default_port | ||
72 | 362 | try: | ||
73 | 363 | GenericIPAddressField().clean(ip, model_instance=None) | ||
74 | 364 | IntegerField().clean(port, model_instance=None) | ||
75 | 365 | except ValidationError: | ||
76 | 366 | raise ValidationError( | ||
77 | 367 | f"Invalid IP and port combination: {ip_port};" | ||
78 | 368 | f"please provide a list of space-separated IP addresses {'and port' if not self._default_port else ''}" | ||
79 | 369 | ) | ||
80 | 370 | result.append((ip, port)) | ||
81 | 371 | return result | ||
82 | 372 | |||
83 | 373 | |||
84 | 335 | class HostListFormField(forms.CharField): | 374 | class HostListFormField(forms.CharField): |
85 | 336 | """Accepts a space/comma separated list of hostnames or IP addresses. | 375 | """Accepts a space/comma separated list of hostnames or IP addresses. |
86 | 337 | 376 | ||
87 | diff --git a/src/maasserver/forms/domain.py b/src/maasserver/forms/domain.py | |||
88 | index b530f3f..d5739a4 100644 | |||
89 | --- a/src/maasserver/forms/domain.py | |||
90 | +++ b/src/maasserver/forms/domain.py | |||
91 | @@ -7,7 +7,7 @@ | |||
92 | 7 | from django import forms | 7 | from django import forms |
93 | 8 | from django.core.exceptions import ValidationError | 8 | from django.core.exceptions import ValidationError |
94 | 9 | 9 | ||
96 | 10 | from maasserver.fields import IPListFormField | 10 | from maasserver.fields import IPPortListFormField |
97 | 11 | from maasserver.forms import APIEditMixin, MAASModelForm | 11 | from maasserver.forms import APIEditMixin, MAASModelForm |
98 | 12 | from maasserver.models.domain import Domain | 12 | from maasserver.models.domain import Domain |
99 | 13 | from maasserver.models.forwarddnsserver import ForwardDNSServer | 13 | from maasserver.models.forwarddnsserver import ForwardDNSServer |
100 | @@ -24,16 +24,16 @@ class DomainForm(MAASModelForm): | |||
101 | 24 | 24 | ||
102 | 25 | authoritative = forms.NullBooleanField(required=False) | 25 | authoritative = forms.NullBooleanField(required=False) |
103 | 26 | 26 | ||
105 | 27 | forward_dns_servers = IPListFormField(required=False) | 27 | forward_dns_servers = IPPortListFormField(default_port=53, required=False) |
106 | 28 | 28 | ||
107 | 29 | def save(self): | 29 | def save(self): |
108 | 30 | super(MAASModelForm, self).save() | 30 | super(MAASModelForm, self).save() |
109 | 31 | fwd_srvrs = self.cleaned_data.get("forward_dns_servers") | 31 | fwd_srvrs = self.cleaned_data.get("forward_dns_servers") |
110 | 32 | if fwd_srvrs is not None: | 32 | if fwd_srvrs is not None: |
113 | 33 | fwd_srvrs_list = fwd_srvrs.split(" ") | 33 | for fwd_srvr_ip, fwd_srvr_port in fwd_srvrs: |
112 | 34 | for fwd_srvr_ip in fwd_srvrs_list: | ||
114 | 35 | fwd_srvr = ForwardDNSServer.objects.get_or_create( | 34 | fwd_srvr = ForwardDNSServer.objects.get_or_create( |
116 | 36 | ip_address=fwd_srvr_ip | 35 | ip_address=fwd_srvr_ip, |
117 | 36 | port=fwd_srvr_port, | ||
118 | 37 | )[0] | 37 | )[0] |
119 | 38 | fwd_srvr.domains.add(self.instance) | 38 | fwd_srvr.domains.add(self.instance) |
120 | 39 | fwd_srvr.save() | 39 | fwd_srvr.save() |
121 | diff --git a/src/maasserver/forms/tests/test_domain.py b/src/maasserver/forms/tests/test_domain.py | |||
122 | index 043dc47..e9a3a3e 100644 | |||
123 | --- a/src/maasserver/forms/tests/test_domain.py | |||
124 | +++ b/src/maasserver/forms/tests/test_domain.py | |||
125 | @@ -104,6 +104,30 @@ class TestDomainForm(MAASServerTestCase): | |||
126 | 104 | for fwd_dns_srvr in domain.forward_dns_servers | 104 | for fwd_dns_srvr in domain.forward_dns_servers |
127 | 105 | ], | 105 | ], |
128 | 106 | ) | 106 | ) |
129 | 107 | for fwd_dns_srvr in domain.forward_dns_servers: | ||
130 | 108 | self.assertEqual(fwd_dns_srvr.port, 53) | ||
131 | 109 | |||
132 | 110 | def test_can_create_forward_dns_server_with_port(self): | ||
133 | 111 | name = factory.make_name("domain") | ||
134 | 112 | forward_dns_servers = [ | ||
135 | 113 | f"{factory.make_ip_address(ipv6=False)}:5353" for _ in range(0, 2) | ||
136 | 114 | ] | ||
137 | 115 | form = DomainForm( | ||
138 | 116 | { | ||
139 | 117 | "name": name, | ||
140 | 118 | "authoritative": False, | ||
141 | 119 | "forward_dns_servers": " ".join(forward_dns_servers), | ||
142 | 120 | } | ||
143 | 121 | ) | ||
144 | 122 | self.assertTrue(form.is_valid(), form.errors) | ||
145 | 123 | domain = form.save() | ||
146 | 124 | self.assertEqual( | ||
147 | 125 | forward_dns_servers, | ||
148 | 126 | [ | ||
149 | 127 | fwd_dns_srvr.ip_and_port | ||
150 | 128 | for fwd_dns_srvr in domain.forward_dns_servers | ||
151 | 129 | ], | ||
152 | 130 | ) | ||
153 | 107 | 131 | ||
154 | 108 | def test_validate_authority(self): | 132 | def test_validate_authority(self): |
155 | 109 | name = factory.make_name("domain") | 133 | name = factory.make_name("domain") |
156 | diff --git a/src/maasserver/migrations/maasserver/0305_add_port_to_forward_dns_servers.py b/src/maasserver/migrations/maasserver/0305_add_port_to_forward_dns_servers.py | |||
157 | 110 | new file mode 100644 | 134 | new file mode 100644 |
158 | index 0000000..322927f | |||
159 | --- /dev/null | |||
160 | +++ b/src/maasserver/migrations/maasserver/0305_add_port_to_forward_dns_servers.py | |||
161 | @@ -0,0 +1,17 @@ | |||
162 | 1 | # Generated by Django 3.2.12 on 2024-03-07 17:16 | ||
163 | 2 | |||
164 | 3 | from django.db import migrations, models | ||
165 | 4 | |||
166 | 5 | |||
167 | 6 | class Migration(migrations.Migration): | ||
168 | 7 | dependencies = [ | ||
169 | 8 | ("maasserver", "0304_interface_params_no_autoconf"), | ||
170 | 9 | ] | ||
171 | 10 | |||
172 | 11 | operations = [ | ||
173 | 12 | migrations.AddField( | ||
174 | 13 | model_name="forwarddnsserver", | ||
175 | 14 | name="port", | ||
176 | 15 | field=models.IntegerField(default=53), | ||
177 | 16 | ), | ||
178 | 17 | ] | ||
179 | diff --git a/src/maasserver/models/forwarddnsserver.py b/src/maasserver/models/forwarddnsserver.py | |||
180 | index 5dcf740..64eb2e8 100644 | |||
181 | --- a/src/maasserver/models/forwarddnsserver.py | |||
182 | +++ b/src/maasserver/models/forwarddnsserver.py | |||
183 | @@ -6,7 +6,12 @@ __all__ = [ | |||
184 | 6 | ] | 6 | ] |
185 | 7 | 7 | ||
186 | 8 | 8 | ||
188 | 9 | from django.db.models import GenericIPAddressField, Manager, ManyToManyField | 9 | from django.db.models import ( |
189 | 10 | GenericIPAddressField, | ||
190 | 11 | IntegerField, | ||
191 | 12 | Manager, | ||
192 | 13 | ManyToManyField, | ||
193 | 14 | ) | ||
194 | 10 | 15 | ||
195 | 11 | from maasserver.models.cleansave import CleanSave | 16 | from maasserver.models.cleansave import CleanSave |
196 | 12 | from maasserver.models.domain import Domain | 17 | from maasserver.models.domain import Domain |
197 | @@ -31,4 +36,10 @@ class ForwardDNSServer(CleanSave, TimestampedModel): | |||
198 | 31 | null=False, default=None, editable=False, unique=True | 36 | null=False, default=None, editable=False, unique=True |
199 | 32 | ) | 37 | ) |
200 | 33 | 38 | ||
201 | 39 | port = IntegerField(null=False, default=53) | ||
202 | 40 | |||
203 | 34 | domains = ManyToManyField(Domain) | 41 | domains = ManyToManyField(Domain) |
204 | 42 | |||
205 | 43 | @property | ||
206 | 44 | def ip_and_port(self): | ||
207 | 45 | return f"{self.ip_address}:{self.port}" | ||
208 | diff --git a/src/maasserver/tests/test_fields.py b/src/maasserver/tests/test_fields.py | |||
209 | index 643366f..7af6607 100644 | |||
210 | --- a/src/maasserver/tests/test_fields.py | |||
211 | +++ b/src/maasserver/tests/test_fields.py | |||
212 | @@ -12,6 +12,7 @@ from testtools import ExpectedException | |||
213 | 12 | from maasserver.fields import ( | 12 | from maasserver.fields import ( |
214 | 13 | HostListFormField, | 13 | HostListFormField, |
215 | 14 | IPListFormField, | 14 | IPListFormField, |
216 | 15 | IPPortListFormField, | ||
217 | 15 | LargeObjectField, | 16 | LargeObjectField, |
218 | 16 | LargeObjectFile, | 17 | LargeObjectFile, |
219 | 17 | MODEL_NAME_VALIDATOR, | 18 | MODEL_NAME_VALIDATOR, |
220 | @@ -304,6 +305,54 @@ class TestIPListFormField(MAASTestCase): | |||
221 | 304 | ) | 305 | ) |
222 | 305 | 306 | ||
223 | 306 | 307 | ||
224 | 308 | class TestIPPortListFormField(MAASTestCase): | ||
225 | 309 | def test_accepts_ipv4_with_port(self): | ||
226 | 310 | ips = [factory.make_ip_address(ipv6=False) for _ in range(5)] | ||
227 | 311 | ip_ports = [f"{ip}:80" for ip in ips] | ||
228 | 312 | input = ",".join(ip_ports) | ||
229 | 313 | self.assertEqual( | ||
230 | 314 | [(ip, 80) for ip in ips], IPPortListFormField().clean(input) | ||
231 | 315 | ) | ||
232 | 316 | |||
233 | 317 | def test_accepts_ipv4_without_port(self): | ||
234 | 318 | ips = [factory.make_ip_address(ipv6=False) for _ in range(5)] | ||
235 | 319 | input = ",".join(ips) | ||
236 | 320 | self.assertEqual( | ||
237 | 321 | [(ip, 80) for ip in ips], | ||
238 | 322 | IPPortListFormField(default_port=80).clean(input), | ||
239 | 323 | ) | ||
240 | 324 | |||
241 | 325 | def test_accepts_ipv6_with_port(self): | ||
242 | 326 | ips = [factory.make_ip_address(ipv6=True) for _ in range(5)] | ||
243 | 327 | ip_ports = [f"[{ip}]:80" for ip in ips] | ||
244 | 328 | input = ",".join(ip_ports) | ||
245 | 329 | self.assertEqual( | ||
246 | 330 | [(ip, 80) for ip in ips], IPPortListFormField().clean(input) | ||
247 | 331 | ) | ||
248 | 332 | |||
249 | 333 | def test_accepts_ipv6_without_port(self): | ||
250 | 334 | ips = [factory.make_ip_address(ipv6=True) for _ in range(5)] | ||
251 | 335 | input = ",".join(ips) | ||
252 | 336 | self.assertEqual( | ||
253 | 337 | [(ip, 80) for ip in ips], | ||
254 | 338 | IPPortListFormField(default_port=80).clean(input), | ||
255 | 339 | ) | ||
256 | 340 | |||
257 | 341 | def test_rejects_invalid_ip(self): | ||
258 | 342 | ip_ports = [ | ||
259 | 343 | f"{factory.make_ip_address(ipv6=False)}:80" for _ in range(4) | ||
260 | 344 | ] | ||
261 | 345 | invalid = f"{factory.make_name()}:80" | ||
262 | 346 | ip_ports.append(invalid) | ||
263 | 347 | input = ",".join(ip_ports) | ||
264 | 348 | error = self.assertRaises( | ||
265 | 349 | ValidationError, IPPortListFormField().clean, input | ||
266 | 350 | ) | ||
267 | 351 | self.assertIn( | ||
268 | 352 | f"Invalid IP and port combination: {invalid}", error.message | ||
269 | 353 | ) | ||
270 | 354 | |||
271 | 355 | |||
272 | 307 | class TestHostListFormField(MAASTestCase): | 356 | class TestHostListFormField(MAASTestCase): |
273 | 308 | def test_accepts_none(self): | 357 | def test_accepts_none(self): |
274 | 309 | self.assertIsNone(HostListFormField().clean(None)) | 358 | self.assertIsNone(HostListFormField().clean(None)) |
275 | diff --git a/src/provisioningserver/dns/tests/test_config.py b/src/provisioningserver/dns/tests/test_config.py | |||
276 | index 8c57ce4..9c7ad2f 100644 | |||
277 | --- a/src/provisioningserver/dns/tests/test_config.py | |||
278 | +++ b/src/provisioningserver/dns/tests/test_config.py | |||
279 | @@ -601,7 +601,7 @@ class TestDNSConfig(MAASTestCase): | |||
280 | 601 | def test_write_config_with_forwarded_zones(self): | 601 | def test_write_config_with_forwarded_zones(self): |
281 | 602 | name = factory.make_name("domain") | 602 | name = factory.make_name("domain") |
282 | 603 | ip = factory.make_ip_address() | 603 | ip = factory.make_ip_address() |
284 | 604 | forwarded_zones = [(name, [ip])] | 604 | forwarded_zones = [(name, [(ip, None)])] |
285 | 605 | target_dir = patch_dns_config_path(self) | 605 | target_dir = patch_dns_config_path(self) |
286 | 606 | DNSConfig(forwarded_zones=forwarded_zones).write_config() | 606 | DNSConfig(forwarded_zones=forwarded_zones).write_config() |
287 | 607 | config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME) | 607 | config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME) |
288 | @@ -611,7 +611,7 @@ class TestDNSConfig(MAASTestCase): | |||
289 | 611 | type forward; | 611 | type forward; |
290 | 612 | forward only; | 612 | forward only; |
291 | 613 | forwarders {{ | 613 | forwarders {{ |
293 | 614 | {ip}; | 614 | {ip} port 53; |
294 | 615 | }}; | 615 | }}; |
295 | 616 | }}; | 616 | }}; |
296 | 617 | """ | 617 | """ |
297 | @@ -620,6 +620,30 @@ class TestDNSConfig(MAASTestCase): | |||
298 | 620 | expected = parse_isc_string(expected_content) | 620 | expected = parse_isc_string(expected_content) |
299 | 621 | self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"']) | 621 | self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"']) |
300 | 622 | 622 | ||
301 | 623 | def test_write_config_with_forwarded_zones_with_custom_port(self): | ||
302 | 624 | name = factory.make_name("domain") | ||
303 | 625 | ip = factory.make_ip_address() | ||
304 | 626 | port = 5353 | ||
305 | 627 | forwarded_zones = [(name, [(ip, port)])] | ||
306 | 628 | target_dir = patch_dns_config_path(self) | ||
307 | 629 | DNSConfig(forwarded_zones=forwarded_zones).write_config() | ||
308 | 630 | config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME) | ||
309 | 631 | expected_content = dedent( | ||
310 | 632 | f""" | ||
311 | 633 | zone "{name}" {{ | ||
312 | 634 | type forward; | ||
313 | 635 | forward only; | ||
314 | 636 | forwarders {{ | ||
315 | 637 | {ip} port {port}; | ||
316 | 638 | }}; | ||
317 | 639 | }}; | ||
318 | 640 | """ | ||
319 | 641 | ) | ||
320 | 642 | config = read_isc_file(config_path) | ||
321 | 643 | print(config) | ||
322 | 644 | expected = parse_isc_string(expected_content) | ||
323 | 645 | self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"']) | ||
324 | 646 | |||
325 | 623 | def test_write_config_makes_config_world_readable(self): | 647 | def test_write_config_makes_config_world_readable(self): |
326 | 624 | target_dir = patch_dns_config_path(self) | 648 | target_dir = patch_dns_config_path(self) |
327 | 625 | DNSConfig().write_config() | 649 | DNSConfig().write_config() |
328 | diff --git a/src/provisioningserver/templates/dns/named.conf.template b/src/provisioningserver/templates/dns/named.conf.template | |||
329 | index 7217ca8..a909560 100644 | |||
330 | --- a/src/provisioningserver/templates/dns/named.conf.template | |||
331 | +++ b/src/provisioningserver/templates/dns/named.conf.template | |||
332 | @@ -21,7 +21,7 @@ zone "{{forwarded_zone[0]}}" { | |||
333 | 21 | forward only; | 21 | forward only; |
334 | 22 | forwarders { | 22 | forwarders { |
335 | 23 | {{for forward_server in forwarded_zone[1]}} | 23 | {{for forward_server in forwarded_zone[1]}} |
337 | 24 | {{forward_server}}; | 24 | {{forward_server[0]}} port {{ forward_server[1] if forward_server[1] else 53 }}; |
338 | 25 | {{endfor}} | 25 | {{endfor}} |
339 | 26 | }; | 26 | }; |
340 | 27 | }; | 27 | }; |
self-approving backport