Merge ~harald-jensas/cloud-init:bug/1806014 into cloud-init:master
- Git
- lp:~harald-jensas/cloud-init
- bug/1806014
- Merge into master
Status: | Superseded |
---|---|
Proposed branch: | ~harald-jensas/cloud-init:bug/1806014 |
Merge into: | cloud-init:master |
Prerequisite: | ~harald-jensas/cloud-init:bug/1847517 |
Diff against target: |
357 lines (+206/-6) 5 files modified
cloudinit/net/eni.py (+10/-0) cloudinit/net/netplan.py (+4/-1) cloudinit/net/network_state.py (+8/-4) cloudinit/net/sysconfig.py (+9/-1) tests/unittests/test_net.py (+175/-0) |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Needs Fixing | ||
Review via email: mp+374138@code.launchpad.net |
Commit message
Description of the change
- b8ddae4... by Harald Jensås
-
net: fix subnet_is_ipv6() for stateless|stateful
Function return false for ipv6_dhcpv6-
stateless| stateful,
the eni renderer does not add '6' to 'inet' which is
incorrect.The subnet_is_ipv6() function is updated to also return
true if startswith('ipv6').LP: #1848690
Harald Jensås (harald-jensas) wrote : | # |
> Thanks for sorting this out. One more element here is related to kernel
> defaults; at least on Ubuntu the kernel defaults the interfaces accept_ra
> value to 1; which allows RAs on interfaces that aren't forwarding.
>
> The code below which defaults accept-ra to False is going to break that. I
> would like to suggest that only *if* accept-ra is set in the source config
> (for example in the openstack network_data.json where dhcp6 stateful is
> specified, then it makes sense to include accept-ra: true in the interface
> config.
>
> In the renderers, if accept-ra is *not* defined, I'd like to avoid emitting
> any RA related config at all.
>
> Now that cloud-init handles dhcp6-stateless
> enable RA on stateful dhcp6, otherwise we should leave the kernel defaults.
>
> Additionally, ifupdown (eni) can toggle these values as well:
>
> accept_ra [0, 1, 2]
> autoconf [0, 1]
Thanks Ryan, this all makes sense.
I have updated the patch to not just enable if accept-ra: true. But also disable if false. And it should not change the OS defaults if accept-ra is not defined at all.
Unmerged commits
- 577ccfa... by Harald Jensås
-
net: IPv6 implement accept-ra RA's
Router advertisements are required for the default route
to be set up.sysconf: IPV6_FORCE_
ACCEPT_ RA controls accept_ra sysctl.
eni: mode static and mode dhcp 'accept_ra' controls sysctl.Add 'accept-ra: true|false' parameter to config v1 and
v2. When True: accept_ra is set to '1'. When False:
accept_ra is set to '0'. When not defined in config the
value is left to the operating system default.Also, set IPV6_FORCE_
ACCEPT_ RA=yes in sysconfig when
using Openstack and dhcpv6-stateful. - b8ddae4... by Harald Jensås
-
net: fix subnet_is_ipv6() for stateless|stateful
Function return false for ipv6_dhcpv6-
stateless| stateful,
the eni renderer does not add '6' to 'inet' which is
incorrect.The subnet_is_ipv6() function is updated to also return
true if startswith('ipv6').LP: #1848690
Preview Diff
1 | diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py |
2 | index 530922b..458c229 100644 |
3 | --- a/cloudinit/net/eni.py |
4 | +++ b/cloudinit/net/eni.py |
5 | @@ -399,6 +399,7 @@ class Renderer(renderer.Renderer): |
6 | def _render_iface(self, iface, render_hwaddress=False): |
7 | sections = [] |
8 | subnets = iface.get('subnets', {}) |
9 | + accept_ra = iface.pop('accept-ra', None) |
10 | if subnets: |
11 | for index, subnet in enumerate(subnets): |
12 | ipv4_subnet_mtu = None |
13 | @@ -415,9 +416,18 @@ class Renderer(renderer.Renderer): |
14 | subnet['type'] == 'ipv6_dhcpv6-stateful'): |
15 | # Configure network settings using DHCP or DHCPv6 |
16 | iface['mode'] = 'dhcp' |
17 | + if accept_ra is not None: |
18 | + # Accept router advertisements (0=off, 1=on) |
19 | + iface['accept_ra'] = ('1' if util.is_true(accept_ra) |
20 | + else '0') |
21 | elif subnet['type'] == 'ipv6_dhcpv6-stateless': |
22 | # Configure network settings using SLAAC from RAs |
23 | iface['mode'] = 'auto' |
24 | + elif subnet_is_ipv6(subnet) and subnet['type'] == 'static': |
25 | + if accept_ra is not None: |
26 | + # Accept router advertisements (0=off, 1=on) |
27 | + iface['accept_ra'] = ('1' if util.is_true(accept_ra) |
28 | + else '0') |
29 | |
30 | # do not emit multiple 'auto $IFACE' lines as older (precise) |
31 | # ifupdown complains |
32 | diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py |
33 | index e54a34e..78023f8 100644 |
34 | --- a/cloudinit/net/netplan.py |
35 | +++ b/cloudinit/net/netplan.py |
36 | @@ -51,7 +51,8 @@ def _extract_addresses(config, entry, ifname): |
37 | 'mtu': 1480, |
38 | 'netmask': 64, |
39 | 'type': 'static'}], |
40 | - 'type: physical' |
41 | + 'type: physical', |
42 | + 'accept-ra': 'true' |
43 | } |
44 | |
45 | An entry dictionary looks like: |
46 | @@ -144,6 +145,8 @@ def _extract_addresses(config, entry, ifname): |
47 | ns = entry.get('nameservers', {}) |
48 | ns.update({'search': searchdomains}) |
49 | entry.update({'nameservers': ns}) |
50 | + if 'accept-ra' in config: |
51 | + entry.update({'accept-ra': config.get('accept-ra')}) |
52 | |
53 | |
54 | def _extract_bond_slaves_by_name(interfaces, entry, bond_master): |
55 | diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py |
56 | index c0c415d..cc8beaa 100644 |
57 | --- a/cloudinit/net/network_state.py |
58 | +++ b/cloudinit/net/network_state.py |
59 | @@ -22,7 +22,8 @@ NETWORK_STATE_REQUIRED_KEYS = { |
60 | } |
61 | NETWORK_V2_KEY_FILTER = [ |
62 | 'addresses', 'dhcp4', 'dhcp6', 'gateway4', 'gateway6', 'interfaces', |
63 | - 'match', 'mtu', 'nameservers', 'renderer', 'set-name', 'wakeonlan' |
64 | + 'match', 'mtu', 'nameservers', 'renderer', 'set-name', 'wakeonlan', |
65 | + 'accept-ra' |
66 | ] |
67 | |
68 | NET_CONFIG_TO_V2 = { |
69 | @@ -340,7 +341,8 @@ class NetworkStateInterpreter(object): |
70 | 'name': 'eth0', |
71 | 'subnets': [ |
72 | {'type': 'dhcp4'} |
73 | - ] |
74 | + ], |
75 | + 'accept-ra': 'true' |
76 | } |
77 | ''' |
78 | |
79 | @@ -370,6 +372,7 @@ class NetworkStateInterpreter(object): |
80 | 'address': None, |
81 | 'gateway': None, |
82 | 'subnets': subnets, |
83 | + 'accept-ra': command.get('accept-ra') |
84 | }) |
85 | self._network_state['interfaces'].update({command.get('name'): iface}) |
86 | self.dump_network_state() |
87 | @@ -613,6 +616,7 @@ class NetworkStateInterpreter(object): |
88 | driver: ixgbe |
89 | set-name: lom1 |
90 | dhcp6: true |
91 | + accept-ra: true |
92 | switchports: |
93 | match: |
94 | name: enp2* |
95 | @@ -641,7 +645,7 @@ class NetworkStateInterpreter(object): |
96 | driver = match.get('driver', None) |
97 | if driver: |
98 | phy_cmd['params'] = {'driver': driver} |
99 | - for key in ['mtu', 'match', 'wakeonlan']: |
100 | + for key in ['mtu', 'match', 'wakeonlan', 'accept-ra']: |
101 | if key in cfg: |
102 | phy_cmd[key] = cfg[key] |
103 | |
104 | @@ -919,7 +923,7 @@ def is_ipv6_addr(address): |
105 | def subnet_is_ipv6(subnet): |
106 | """Common helper for checking network_state subnets for ipv6.""" |
107 | # 'static6' or 'dhcp6' |
108 | - if subnet['type'].endswith('6'): |
109 | + if subnet['type'].endswith('6') or subnet['type'].startswith('ipv6'): |
110 | # This is a request for DHCPv6. |
111 | return True |
112 | elif subnet['type'] == 'static' and is_ipv6_addr(subnet.get('address')): |
113 | diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py |
114 | index 4e65676..3fe5793 100644 |
115 | --- a/cloudinit/net/sysconfig.py |
116 | +++ b/cloudinit/net/sysconfig.py |
117 | @@ -333,6 +333,9 @@ class Renderer(renderer.Renderer): |
118 | if old_key == 'mac_address' and iface['type'] != 'physical': |
119 | continue |
120 | iface_cfg[new_key] = old_value |
121 | + accept_ra = iface.get('accept-ra', None) |
122 | + if accept_ra is not None: |
123 | + iface_cfg['IPV6_FORCE_ACCEPT_RA'] = util.is_true(accept_ra) |
124 | |
125 | @classmethod |
126 | def _render_subnets(cls, iface_cfg, subnets, has_default_route): |
127 | @@ -343,11 +346,16 @@ class Renderer(renderer.Renderer): |
128 | for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
129 | mtu_key = 'MTU' |
130 | subnet_type = subnet.get('type') |
131 | - if subnet_type == 'dhcp6' or subnet_type == 'ipv6_dhcpv6-stateful': |
132 | + if subnet_type == 'dhcp6': |
133 | # TODO need to set BOOTPROTO to dhcp6 on SUSE |
134 | iface_cfg['IPV6INIT'] = True |
135 | # Configure network settings using DHCPv6 |
136 | iface_cfg['DHCPV6C'] = True |
137 | + elif subnet_type == 'ipv6_dhcpv6-stateful': |
138 | + iface_cfg['IPV6INIT'] = True |
139 | + # Configure network settings using DHCPv6 |
140 | + iface_cfg['DHCPV6C'] = True |
141 | + iface_cfg['IPV6_FORCE_ACCEPT_RA'] = True |
142 | elif subnet_type == 'ipv6_dhcpv6-stateless': |
143 | iface_cfg['IPV6INIT'] = True |
144 | # Configure network settings using SLAAC from RAs |
145 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
146 | index f5a9cae..62b6ac6 100644 |
147 | --- a/tests/unittests/test_net.py |
148 | +++ b/tests/unittests/test_net.py |
149 | @@ -1070,6 +1070,104 @@ NETWORK_CONFIGS = { |
150 | """), |
151 | }, |
152 | }, |
153 | + 'dhcpv6_accept_ra': { |
154 | + 'expected_eni': textwrap.dedent("""\ |
155 | + auto lo |
156 | + iface lo inet loopback |
157 | + |
158 | + auto iface0 |
159 | + iface iface0 inet6 dhcp |
160 | + accept_ra 1 |
161 | + """).rstrip(' '), |
162 | + 'expected_netplan': textwrap.dedent(""" |
163 | + network: |
164 | + version: 2 |
165 | + ethernets: |
166 | + iface0: |
167 | + accept-ra: 'true' |
168 | + dhcp6: true |
169 | + """).rstrip(' '), |
170 | + 'yaml_v1': textwrap.dedent("""\ |
171 | + version: 1 |
172 | + config: |
173 | + - type: 'physical' |
174 | + name: 'iface0' |
175 | + subnets: |
176 | + - {'type': 'dhcp6'} |
177 | + accept-ra: 'true' |
178 | + """).rstrip(' '), |
179 | + 'yaml_v2': textwrap.dedent("""\ |
180 | + version: 2 |
181 | + ethernets: |
182 | + iface0: |
183 | + dhcp6: true |
184 | + accept-ra: 'true' |
185 | + """).rstrip(' '), |
186 | + 'expected_sysconfig': { |
187 | + 'ifcfg-iface0': textwrap.dedent("""\ |
188 | + BOOTPROTO=none |
189 | + DEVICE=iface0 |
190 | + DHCPV6C=yes |
191 | + IPV6INIT=yes |
192 | + IPV6_FORCE_ACCEPT_RA=yes |
193 | + DEVICE=iface0 |
194 | + NM_CONTROLLED=no |
195 | + ONBOOT=yes |
196 | + STARTMODE=auto |
197 | + TYPE=Ethernet |
198 | + USERCTL=no |
199 | + """), |
200 | + }, |
201 | + }, |
202 | + 'dhcpv6_reject_ra': { |
203 | + 'expected_eni': textwrap.dedent("""\ |
204 | + auto lo |
205 | + iface lo inet loopback |
206 | + |
207 | + auto iface0 |
208 | + iface iface0 inet6 dhcp |
209 | + accept_ra 0 |
210 | + """).rstrip(' '), |
211 | + 'expected_netplan': textwrap.dedent(""" |
212 | + network: |
213 | + version: 2 |
214 | + ethernets: |
215 | + iface0: |
216 | + accept-ra: 'false' |
217 | + dhcp6: true |
218 | + """).rstrip(' '), |
219 | + 'yaml_v1': textwrap.dedent("""\ |
220 | + version: 1 |
221 | + config: |
222 | + - type: 'physical' |
223 | + name: 'iface0' |
224 | + subnets: |
225 | + - {'type': 'dhcp6'} |
226 | + accept-ra: 'false' |
227 | + """).rstrip(' '), |
228 | + 'yaml_v2': textwrap.dedent("""\ |
229 | + version: 2 |
230 | + ethernets: |
231 | + iface0: |
232 | + dhcp6: true |
233 | + accept-ra: 'false' |
234 | + """).rstrip(' '), |
235 | + 'expected_sysconfig': { |
236 | + 'ifcfg-iface0': textwrap.dedent("""\ |
237 | + BOOTPROTO=none |
238 | + DEVICE=iface0 |
239 | + DHCPV6C=yes |
240 | + IPV6INIT=yes |
241 | + IPV6_FORCE_ACCEPT_RA=no |
242 | + DEVICE=iface0 |
243 | + NM_CONTROLLED=no |
244 | + ONBOOT=yes |
245 | + STARTMODE=auto |
246 | + TYPE=Ethernet |
247 | + USERCTL=no |
248 | + """), |
249 | + }, |
250 | + }, |
251 | 'dhcpv6_stateless': { |
252 | 'expected_eni': textwrap.dedent("""\ |
253 | auto lo |
254 | @@ -1137,6 +1235,7 @@ NETWORK_CONFIGS = { |
255 | DEVICE=iface0 |
256 | DHCPV6C=yes |
257 | IPV6INIT=yes |
258 | + IPV6_FORCE_ACCEPT_RA=yes |
259 | DEVICE=iface0 |
260 | NM_CONTROLLED=no |
261 | ONBOOT=yes |
262 | @@ -2857,6 +2956,34 @@ USERCTL=no |
263 | self._compare_files_to_expected(entry[self.expected_name], found) |
264 | self._assert_headers(found) |
265 | |
266 | + def test_dhcpv6_accept_ra_config_v1(self): |
267 | + entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] |
268 | + found = self._render_and_read(network_config=yaml.load( |
269 | + entry['yaml_v1'])) |
270 | + self._compare_files_to_expected(entry[self.expected_name], found) |
271 | + self._assert_headers(found) |
272 | + |
273 | + def test_dhcpv6_accept_ra_config_v2(self): |
274 | + entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] |
275 | + found = self._render_and_read(network_config=yaml.load( |
276 | + entry['yaml_v2'])) |
277 | + self._compare_files_to_expected(entry[self.expected_name], found) |
278 | + self._assert_headers(found) |
279 | + |
280 | + def test_dhcpv6_reject_ra_config_v1(self): |
281 | + entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] |
282 | + found = self._render_and_read(network_config=yaml.load( |
283 | + entry['yaml_v1'])) |
284 | + self._compare_files_to_expected(entry[self.expected_name], found) |
285 | + self._assert_headers(found) |
286 | + |
287 | + def test_dhcpv6_reject_ra_config_v2(self): |
288 | + entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] |
289 | + found = self._render_and_read(network_config=yaml.load( |
290 | + entry['yaml_v2'])) |
291 | + self._compare_files_to_expected(entry[self.expected_name], found) |
292 | + self._assert_headers(found) |
293 | + |
294 | def test_dhcpv6_stateless_config(self): |
295 | entry = NETWORK_CONFIGS['dhcpv6_stateless'] |
296 | found = self._render_and_read(network_config=yaml.load(entry['yaml'])) |
297 | @@ -3918,6 +4045,22 @@ class TestNetplanRoundTrip(CiTestCase): |
298 | entry['expected_netplan'].splitlines(), |
299 | files['/etc/netplan/50-cloud-init.yaml'].splitlines()) |
300 | |
301 | + def testsimple_render_dhcpv6_accept_ra(self): |
302 | + entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] |
303 | + files = self._render_and_read(network_config=yaml.load( |
304 | + entry['yaml_v1'])) |
305 | + self.assertEqual( |
306 | + entry['expected_netplan'].splitlines(), |
307 | + files['/etc/netplan/50-cloud-init.yaml'].splitlines()) |
308 | + |
309 | + def testsimple_render_dhcpv6_reject_ra(self): |
310 | + entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] |
311 | + files = self._render_and_read(network_config=yaml.load( |
312 | + entry['yaml_v1'])) |
313 | + self.assertEqual( |
314 | + entry['expected_netplan'].splitlines(), |
315 | + files['/etc/netplan/50-cloud-init.yaml'].splitlines()) |
316 | + |
317 | def testsimple_render_all(self): |
318 | entry = NETWORK_CONFIGS['all'] |
319 | files = self._render_and_read(network_config=yaml.load(entry['yaml'])) |
320 | @@ -4048,6 +4191,38 @@ class TestEniRoundTrip(CiTestCase): |
321 | entry['expected_eni'].splitlines(), |
322 | files['/etc/network/interfaces'].splitlines()) |
323 | |
324 | + def testsimple_render_dhcpv6_stateless(self): |
325 | + entry = NETWORK_CONFIGS['dhcpv6_stateless'] |
326 | + files = self._render_and_read(network_config=yaml.load( |
327 | + entry['yaml'])) |
328 | + self.assertEqual( |
329 | + entry['expected_eni'].splitlines(), |
330 | + files['/etc/network/interfaces'].splitlines()) |
331 | + |
332 | + def testsimple_render_dhcpv6_stateful(self): |
333 | + entry = NETWORK_CONFIGS['dhcpv6_stateless'] |
334 | + files = self._render_and_read(network_config=yaml.load( |
335 | + entry['yaml'])) |
336 | + self.assertEqual( |
337 | + entry['expected_eni'].splitlines(), |
338 | + files['/etc/network/interfaces'].splitlines()) |
339 | + |
340 | + def testsimple_render_dhcpv6_accept_ra(self): |
341 | + entry = NETWORK_CONFIGS['dhcpv6_accept_ra'] |
342 | + files = self._render_and_read(network_config=yaml.load( |
343 | + entry['yaml_v1'])) |
344 | + self.assertEqual( |
345 | + entry['expected_eni'].splitlines(), |
346 | + files['/etc/network/interfaces'].splitlines()) |
347 | + |
348 | + def testsimple_render_dhcpv6_reject_ra(self): |
349 | + entry = NETWORK_CONFIGS['dhcpv6_reject_ra'] |
350 | + files = self._render_and_read(network_config=yaml.load( |
351 | + entry['yaml_v1'])) |
352 | + self.assertEqual( |
353 | + entry['expected_eni'].splitlines(), |
354 | + files['/etc/network/interfaces'].splitlines()) |
355 | + |
356 | def testsimple_render_manual(self): |
357 | """Test rendering of 'manual' for 'type' and 'control'. |
358 |
Thanks for sorting this out. One more element here is related to kernel defaults; at least on Ubuntu the kernel defaults the interfaces accept_ra value to 1; which allows RAs on interfaces that aren't forwarding.
The code below which defaults accept-ra to False is going to break that. I would like to suggest that only *if* accept-ra is set in the source config (for example in the openstack network_data.json where dhcp6 stateful is specified, then it makes sense to include accept-ra: true in the interface config.
In the renderers, if accept-ra is *not* defined, I'd like to avoid emitting any RA related config at all.
Now that cloud-init handles dhcp6-stateless /stateful; I think we only want to enable RA on stateful dhcp6, otherwise we should leave the kernel defaults.
Additionally, ifupdown (eni) can toggle these values as well:
accept_ra [0, 1, 2]
autoconf [0, 1]