Merge ~nicolasbock/netplan:tristate-jammy into ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu/jammy

Proposed by Nicolas Bock
Status: Rejected
Rejected by: Lukas Märdian
Proposed branch: ~nicolasbock/netplan:tristate-jammy
Merge into: ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu/jammy
Diff against target: 1067 lines (+779/-73)
12 files modified
debian/changelog (+7/-2)
debian/patches/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch (+534/-0)
debian/patches/series (+1/-0)
doc/netplan.md (+18/-18)
netplan/cli/commands/apply.py (+10/-1)
src/netplan.c (+14/-7)
src/networkd.c (+28/-21)
src/parse.c (+48/-7)
src/types.c (+8/-0)
src/types.h (+28/-7)
tests/generator/test_ethernets.py (+24/-10)
tests/integration/ethernets.py (+59/-0)
Reviewer Review Type Date Requested Status
Lukas Märdian Needs Fixing
Review via email: mp+424017@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Lukas Märdian (slyon) wrote :

Thank you very much Nicolas for preparing a debdiff/merge-proposal on top of the currently pending netplan Jammy SRU changeset!

The suggested patch is looking good so far, but I left a few inline comments (see below). Furthermore, there might be some misunderstanding of how to apply distro patches. We do not want to modify any files of the "upstream source" in this repository (e.g. src/ doc/ netplan/ ...), but ONLY files in the debian/ packaging subdirectory. Please revert those changes to the upstream source, the new "distro-patch" (quilt) you added in debian/patches/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch will apply those changes for us during the package build.

Another thing: You missed to add the "ethtool" test-dependency to the "ethernets" test in debian/tests/control, this needs to be added in order to make the new offloading autopkgtests PASS (as has been done here: http://launchpadlibrarian.net/601540356/netplan.io_0.104-0ubuntu2_0.104-0ubuntu3.diff.gz).

Some of my suggested changes/fixes:
https://paste.ubuntu.com/p/vKJR7yMN92/

Last but not least, please adopt the bug report in LP: #1956264 according to the SRU template in https://wiki.ubuntu.com/StableReleaseUpdates#SRU_Bug_Template, describing a proper test case/reporducer and impact analysis, to make it fit for SRU.

Keep up the good work!

review: Needs Fixing
Revision history for this message
Lukas Märdian (slyon) wrote :

I've integrated the changes and uploaded them for Focal & Jammy SRU.

Unmerged commits

4371541... by Nicolas Bock

Backport offloading tristate patches (dc3b335), LP: #1956264

- d/p/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch

Signed-off-by: Nicolas Bock <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 0a35b63..3548933 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,9 +1,14 @@
6-netplan.io (0.104-0ubuntu2.1) UNRELEASED; urgency=medium
7+netplan.io (0.104-0ubuntu2.2) UNRELEASED; urgency=medium
8
9+ [ Lukas Märdian ]
10 * Cherry-pick fix for rendering WPA3 password (8934a1b), LP: #1975576
11 + d/p/0010-nm-fix-rendering-of-password-for-unknown-passthrough.patch
12
13- -- Lukas Märdian <slyon@ubuntu.com> Tue, 24 May 2022 11:15:29 +0200
14+ [ Nicolas Bock ]
15+ * Backport offloading tristate patches (dc3b335), LP: #1956264
16+ + d/p/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch
17+
18+ -- Nicolas Bock <nicolas.bock@canonical.com> Mon, 06 Jun 2022 16:20:44 -0600
19
20 netplan.io (0.104-0ubuntu2) jammy; urgency=medium
21
22diff --git a/debian/patches/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch b/debian/patches/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch
23new file mode 100644
24index 0000000..62af06c
25--- /dev/null
26+++ b/debian/patches/0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch
27@@ -0,0 +1,534 @@
28+From: Nicolas Bock <nicolas.bock@canonical.com>
29+Date: Thu, 12 May 2022 01:18:36 -0600
30+Subject: Add tristate type for offload options (LP: #1956264) (#270)
31+MIME-Version: 1.0
32+Content-Type: text/plain; charset="utf-8"
33+Content-Transfer-Encoding: 8bit
34+
35+Closes: https://bugs.launchpad.net/netplan/+bug/1956264
36+
37+COMMITS:
38+* Add tristate type for offload options
39+* Clarify name matching issues for `networkd`
40+* Fix tests for offload
41+* cli: apply: properly change udev/.link offloading settings
42+* Re-used existing offloading fields, they are ABI compatible
43+Size:
44+Both enum and gboolean reduce to integer, so they are of same size.
45+Content:
46+An old consumer looking at these will interpret UNSET as if it was TRUE,
47+which is the kernel's default (=UNSET) value.
48+* CI: quirk to add ethtool test dependency
49+* tests: ethernets: link offloading validation
50+* doc: be more specific with the offloading docs
51+
52+Co-authored-by: Lukas Märdian <slyon@ubuntu.com>
53+---
54+ doc/netplan.md | 36 ++++++++++++------------
55+ netplan/cli/commands/apply.py | 11 +++++++-
56+ src/netplan.c | 21 +++++++++-----
57+ src/networkd.c | 49 ++++++++++++++++++--------------
58+ src/parse.c | 55 +++++++++++++++++++++++++++++++-----
59+ src/types.c | 8 ++++++
60+ src/types.h | 35 ++++++++++++++++++-----
61+ tests/generator/test_ethernets.py | 34 +++++++++++++++-------
62+ tests/integration/ethernets.py | 59 +++++++++++++++++++++++++++++++++++++++
63+ 9 files changed, 237 insertions(+), 71 deletions(-)
64+
65+diff --git a/doc/netplan.md b/doc/netplan.md
66+index e9b4e92..373e05d 100644
67+--- a/doc/netplan.md
68++++ b/doc/netplan.md
69+@@ -79,6 +79,10 @@ Virtual devices
70+
71+ ## Common properties for physical device types
72+
73++**Note:** Some options will not work reliably for devices matched by name only
74++and rendered by networkd, due to interactions with device renaming in udev.
75++Match devices by MAC when setting options like: ``wakeonlan`` or ``*-offload``.
76++
77+ ``match`` (mapping)
78+
79+ : This selects a subset of available physical devices by various hardware
80+@@ -139,54 +143,50 @@ Virtual devices
81+
82+ : Enable wake on LAN. Off by default.
83+
84+- **Note:** This will not work reliably for devices matched by name
85+- only and rendered by networkd, due to interactions with device
86+- renaming in udev. Match devices by MAC when setting wake on LAN.
87+-
88+ ``emit-lldp`` (bool) – since **0.99**
89+
90+ : (networkd backend only) Whether to emit LLDP packets. Off by default.
91+
92+ ``receive-checksum-offload`` (bool) – since **0.104**
93+
94+-: (networkd backend only) If set to true, the hardware offload for
95+- checksumming of ingress network packets is enabled. When unset,
96++: (networkd backend only) If set to true (false), the hardware offload for
97++ checksumming of ingress network packets is enabled (disabled). When unset,
98+ the kernel's default will be used.
99+
100+ ``transmit-checksum-offload`` (bool) – since **0.104**
101+
102+-: (networkd backend only) If set to true, the hardware offload for
103+- checksumming of egress network packets is enabled. When unset,
104++: (networkd backend only) If set to true (false), the hardware offload for
105++ checksumming of egress network packets is enabled (disabled). When unset,
106+ the kernel's default will be used.
107+
108+ ``tcp-segmentation-offload`` (bool) – since **0.104**
109+
110+-: (networkd backend only) If set to true, the TCP Segmentation
111+- Offload (TSO) is enabled. When unset, the kernel's default will
112++: (networkd backend only) If set to true (false), the TCP Segmentation
113++ Offload (TSO) is enabled (disabled). When unset, the kernel's default will
114+ be used.
115+
116+ ``tcp6-segmentation-offload`` (bool) – since **0.104**
117+
118+-: (networkd backend only) If set to true, the TCP6 Segmentation
119+- Offload (tx-tcp6-segmentation) is enabled. When unset, the
120++: (networkd backend only) If set to true (false), the TCP6 Segmentation
121++ Offload (tx-tcp6-segmentation) is enabled (disabled). When unset, the
122+ kernel's default will be used.
123+
124+ ``generic-segmentation-offload`` (bool) – since **0.104**
125+
126+-: (networkd backend only) If set to true, the Generic Segmentation
127+- Offload (GSO) is enabled. When unset, the kernel's default will
128++: (networkd backend only) If set to true (false), the Generic Segmentation
129++ Offload (GSO) is enabled (disabled). When unset, the kernel's default will
130+ be used.
131+
132+ ``generic-receive-offload`` (bool) – since **0.104**
133+
134+-: (networkd backend only) If set to true, the Generic Receive
135+- Offload (GRO) is enabled. When unset, the kernel's default will
136++: (networkd backend only) If set to true (false), the Generic Receive
137++ Offload (GRO) is enabled (disabled). When unset, the kernel's default will
138+ be used.
139+
140+ ``large-receive-offload`` (bool) – since **0.104**
141+
142+-: (networkd backend only) If set to true, the Generic Receive
143+- Offload (GRO) is enabled. When unset, the kernel's default will
144++: (networkd backend only) If set to true (false), the Large Receive Offload
145++ (LRO) is enabled (disabled). When unset, the kernel's default will
146+ be used.
147+
148+ ``openvswitch`` (mapping) – since **0.100**
149+diff --git a/netplan/cli/commands/apply.py b/netplan/cli/commands/apply.py
150+index b36662a..9d4511f 100644
151+--- a/netplan/cli/commands/apply.py
152++++ b/netplan/cli/commands/apply.py
153+@@ -221,13 +221,22 @@ class NetplanApply(utils.NetplanCommand):
154+ '/sys/class/net/' + device],
155+ stdout=subprocess.DEVNULL,
156+ stderr=subprocess.DEVNULL)
157++ subprocess.check_call(['udevadm', 'test',
158++ '/sys/class/net/' + device],
159++ stdout=subprocess.DEVNULL,
160++ stderr=subprocess.DEVNULL)
161+ except subprocess.CalledProcessError:
162+ logging.debug('Ignoring device without syspath: %s', device)
163+
164++ devices_after_udev = netifaces.interfaces()
165+ # apply some more changes manually
166+ for iface, settings in changes.items():
167+ # rename non-critical network interfaces
168+- if settings.get('name'):
169++ new_name = settings.get('name')
170++ if new_name:
171++ if iface in devices and new_name in devices_after_udev:
172++ logging.debug('Interface rename {} -> {} already happened.'.format(iface, new_name))
173++ continue # re-name already happened via 'udevadm test'
174+ # bring down the interface, using its current (matched) interface name
175+ subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'down'],
176+ stdout=subprocess.DEVNULL,
177+diff --git a/src/netplan.c b/src/netplan.c
178+index 7b387b4..d1a27a6 100644
179+--- a/src/netplan.c
180++++ b/src/netplan.c
181+@@ -752,13 +752,20 @@ _serialize_yaml(
182+ YAML_BOOL_TRUE(def, event, emitter, "wakeonlan", def->wake_on_lan);
183+
184+ /* Offload options */
185+- YAML_BOOL_TRUE(def, event, emitter, "receive-checksum-offload", def->receive_checksum_offload);
186+- YAML_BOOL_TRUE(def, event, emitter, "transmit-checksum-offload", def->transmit_checksum_offload);
187+- YAML_BOOL_TRUE(def, event, emitter, "tcp-segmentation-offload", def->tcp_segmentation_offload);
188+- YAML_BOOL_TRUE(def, event, emitter, "tcp6-segmentation-offload", def->tcp6_segmentation_offload);
189+- YAML_BOOL_TRUE(def, event, emitter, "generic-segmentation-offload", def->generic_segmentation_offload);
190+- YAML_BOOL_TRUE(def, event, emitter, "generic-receive-offload", def->generic_receive_offload);
191+- YAML_BOOL_TRUE(def, event, emitter, "large-receive-offload", def->large_receive_offload);
192++ if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
193++ YAML_BOOL_TRUE(def, event, emitter, "receive-checksum-offload", def->receive_checksum_offload);
194++ if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
195++ YAML_BOOL_TRUE(def, event, emitter, "transmit-checksum-offload", def->transmit_checksum_offload);
196++ if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
197++ YAML_BOOL_TRUE(def, event, emitter, "tcp-segmentation-offload", def->tcp_segmentation_offload);
198++ if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
199++ YAML_BOOL_TRUE(def, event, emitter, "tcp6-segmentation-offload", def->tcp6_segmentation_offload);
200++ if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
201++ YAML_BOOL_TRUE(def, event, emitter, "generic-segmentation-offload", def->generic_segmentation_offload);
202++ if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
203++ YAML_BOOL_TRUE(def, event, emitter, "generic-receive-offload", def->generic_receive_offload);
204++ if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
205++ YAML_BOOL_TRUE(def, event, emitter, "large-receive-offload", def->large_receive_offload);
206+
207+ if (def->wowlan && def->wowlan != NETPLAN_WIFI_WOWLAN_DEFAULT) {
208+ YAML_SCALAR_PLAIN(event, emitter, "wakeonwlan");
209+diff --git a/src/networkd.c b/src/networkd.c
210+index 6d26047..62c87ce 100644
211+--- a/src/networkd.c
212++++ b/src/networkd.c
213+@@ -243,13 +243,13 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char
214+ if (!def->set_name &&
215+ !def->wake_on_lan &&
216+ !def->mtubytes &&
217+- !def->receive_checksum_offload &&
218+- !def->transmit_checksum_offload &&
219+- !def->tcp_segmentation_offload &&
220+- !def->tcp6_segmentation_offload &&
221+- !def->generic_segmentation_offload &&
222+- !def->generic_receive_offload &&
223+- !def->large_receive_offload)
224++ (def->receive_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
225++ (def->transmit_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
226++ (def->tcp_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
227++ (def->tcp6_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
228++ (def->generic_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
229++ (def->generic_receive_offload == NETPLAN_TRISTATE_UNSET) &&
230++ (def->large_receive_offload == NETPLAN_TRISTATE_UNSET))
231+ return;
232+
233+ /* build file contents */
234+@@ -265,26 +265,33 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char
235+ g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes);
236+
237+ /* Offload options */
238+- if (def->receive_checksum_offload)
239+- g_string_append_printf(s, "ReceiveChecksumOffload=%u\n", def->receive_checksum_offload);
240++ if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
241++ g_string_append_printf(s, "ReceiveChecksumOffload=%s\n",
242++ (def->receive_checksum_offload ? "true" : "false"));
243+
244+- if (def->transmit_checksum_offload)
245+- g_string_append_printf(s, "TransmitChecksumOffload=%u\n", def->transmit_checksum_offload);
246++ if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
247++ g_string_append_printf(s, "TransmitChecksumOffload=%s\n",
248++ (def->transmit_checksum_offload ? "true" : "false"));
249+
250+- if (def->tcp_segmentation_offload)
251+- g_string_append_printf(s, "TCPSegmentationOffload=%u\n", def->tcp_segmentation_offload);
252++ if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
253++ g_string_append_printf(s, "TCPSegmentationOffload=%s\n",
254++ (def->tcp_segmentation_offload ? "true" : "false"));
255+
256+- if (def->tcp6_segmentation_offload)
257+- g_string_append_printf(s, "TCP6SegmentationOffload=%u\n", def->tcp6_segmentation_offload);
258++ if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
259++ g_string_append_printf(s, "TCP6SegmentationOffload=%s\n",
260++ (def->tcp6_segmentation_offload ? "true" : "false"));
261+
262+- if (def->generic_segmentation_offload)
263+- g_string_append_printf(s, "GenericSegmentationOffload=%u\n", def->generic_segmentation_offload);
264++ if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
265++ g_string_append_printf(s, "GenericSegmentationOffload=%s\n",
266++ (def->generic_segmentation_offload ? "true" : "false"));
267+
268+- if (def->generic_receive_offload)
269+- g_string_append_printf(s, "GenericReceiveOffload=%u\n", def->generic_receive_offload);
270++ if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
271++ g_string_append_printf(s, "GenericReceiveOffload=%s\n",
272++ (def->generic_receive_offload ? "true" : "false"));
273+
274+- if (def->large_receive_offload)
275+- g_string_append_printf(s, "LargeReceiveOffload=%u\n", def->large_receive_offload);
276++ if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
277++ g_string_append_printf(s, "LargeReceiveOffload=%s\n",
278++ (def->large_receive_offload ? "true" : "false"));
279+
280+ orig_umask = umask(022);
281+ g_string_free_to_file(s, rootdir, path, ".link");
282+diff --git a/src/parse.c b/src/parse.c
283+index 350c508..0cb07d2 100644
284+--- a/src/parse.c
285++++ b/src/parse.c
286+@@ -370,6 +370,37 @@ handle_generic_bool(NetplanParser* npp, yaml_node_t* node, void* entryptr, const
287+ return TRUE;
288+ }
289+
290++/*
291++ * Handler for setting a HashTable field from a mapping node, inside a given struct
292++ * @entryptr: pointer to the beginning of the to-be-modified data structure
293++ * @data: offset into entryptr struct where the boolean field to write is located
294++ */
295++static gboolean
296++handle_generic_tristate(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
297++{
298++ g_assert(entryptr);
299++ NetplanTristate v;
300++ guint offset = GPOINTER_TO_UINT(data);
301++ NetplanTristate* dest = ((void*) entryptr + offset);
302++
303++ if (g_ascii_strcasecmp(scalar(node), "true") == 0 ||
304++ g_ascii_strcasecmp(scalar(node), "on") == 0 ||
305++ g_ascii_strcasecmp(scalar(node), "yes") == 0 ||
306++ g_ascii_strcasecmp(scalar(node), "y") == 0)
307++ v = NETPLAN_TRISTATE_TRUE;
308++ else if (g_ascii_strcasecmp(scalar(node), "false") == 0 ||
309++ g_ascii_strcasecmp(scalar(node), "off") == 0 ||
310++ g_ascii_strcasecmp(scalar(node), "no") == 0 ||
311++ g_ascii_strcasecmp(scalar(node), "n") == 0)
312++ v = NETPLAN_TRISTATE_FALSE;
313++ else
314++ return yaml_error(npp, node, error, "invalid boolean value '%s'", scalar(node));
315++
316++ *dest = v;
317++ mark_data_as_dirty(npp, dest);
318++ return TRUE;
319++}
320++
321+ /*
322+ * Handler for setting a HashTable field from a mapping node, inside a given struct
323+ * @entryptr: pointer to the beginning of the to-be-modified data structure
324+@@ -516,6 +547,16 @@ handle_netdef_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GErr
325+ return handle_generic_bool(npp, node, npp->current.netdef, data, error);
326+ }
327+
328++/**
329++ * Generic handler for tri-state settings that can bei "UNSET", "TRUE", or "FALSE".
330++ * @data: offset into NetplanNetDefinition where the guint field to write is located
331++ */
332++static gboolean
333++handle_netdef_tristate(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
334++{
335++ return handle_generic_tristate(npp, node, npp->current.netdef, data, error);
336++}
337++
338+ /**
339+ * Generic handler for setting a npp->current.netdef guint field from a scalar node
340+ * @data: offset into NetplanNetDefinition where the guint field to write is located
341+@@ -2356,13 +2397,13 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = {
342+ {"wakeonlan", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(wake_on_lan)}, \
343+ {"wakeonwlan", YAML_SEQUENCE_NODE, {.generic=handle_wowlan}, netdef_offset(wowlan)}, \
344+ {"emit-lldp", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(emit_lldp)}, \
345+- {"receive-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(receive_checksum_offload)}, \
346+- {"transmit-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(transmit_checksum_offload)}, \
347+- {"tcp-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(tcp_segmentation_offload)}, \
348+- {"tcp6-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(tcp6_segmentation_offload)}, \
349+- {"generic-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(generic_segmentation_offload)}, \
350+- {"generic-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(generic_receive_offload)}, \
351+- {"large-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(large_receive_offload)}
352++ {"receive-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(receive_checksum_offload)}, \
353++ {"transmit-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(transmit_checksum_offload)}, \
354++ {"tcp-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp_segmentation_offload)}, \
355++ {"tcp6-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp6_segmentation_offload)}, \
356++ {"generic-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_segmentation_offload)}, \
357++ {"generic-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_receive_offload)}, \
358++ {"large-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(large_receive_offload)}
359+
360+ static const mapping_entry_handler ethernet_def_handlers[] = {
361+ COMMON_LINK_HANDLERS,
362+diff --git a/src/types.c b/src/types.c
363+index eb9f780..00c2b0f 100644
364+--- a/src/types.c
365++++ b/src/types.c
366+@@ -335,6 +335,14 @@ reset_netdef(NetplanNetDefinition* netdef, NetplanDefType new_type, NetplanBacke
367+
368+ reset_private_netdef_data(netdef->_private);
369+ FREE_AND_NULLIFY(netdef->_private);
370++
371++ netdef->receive_checksum_offload = NETPLAN_TRISTATE_UNSET;
372++ netdef->transmit_checksum_offload = NETPLAN_TRISTATE_UNSET;
373++ netdef->tcp_segmentation_offload = NETPLAN_TRISTATE_UNSET;
374++ netdef->tcp6_segmentation_offload = NETPLAN_TRISTATE_UNSET;
375++ netdef->generic_segmentation_offload = NETPLAN_TRISTATE_UNSET;
376++ netdef->generic_receive_offload = NETPLAN_TRISTATE_UNSET;
377++ netdef->large_receive_offload = NETPLAN_TRISTATE_UNSET;
378+ }
379+
380+ static void
381+diff --git a/src/types.h b/src/types.h
382+index 27a23fc..710b1f1 100644
383+--- a/src/types.h
384++++ b/src/types.h
385+@@ -171,6 +171,27 @@ typedef union {
386+ } networkd;
387+ } NetplanBackendSettings;
388+
389++typedef enum
390++{
391++ /**
392++ * @brief Tristate enum type
393++ *
394++ * This type defines a boolean which can be unset, i.e.
395++ * this type has three states. The enum is ordered so
396++ * that
397++ *
398++ * UNSET -> -1
399++ * FALSE -> 0
400++ * TRUE -> 1
401++ *
402++ * And the integer values can be used directly when
403++ * converting to string.
404++ */
405++ NETPLAN_TRISTATE_UNSET = -1, /* -1 */
406++ NETPLAN_TRISTATE_FALSE, /* 0 */
407++ NETPLAN_TRISTATE_TRUE, /* 1 */
408++} NetplanTristate;
409++
410+ struct netplan_net_definition {
411+ NetplanDefType type;
412+ NetplanBackend backend;
413+@@ -333,13 +354,13 @@ struct netplan_net_definition {
414+ gboolean ignore_carrier;
415+
416+ /* offload options */
417+- gboolean receive_checksum_offload;
418+- gboolean transmit_checksum_offload;
419+- gboolean tcp_segmentation_offload;
420+- gboolean tcp6_segmentation_offload;
421+- gboolean generic_segmentation_offload;
422+- gboolean generic_receive_offload;
423+- gboolean large_receive_offload;
424++ NetplanTristate receive_checksum_offload;
425++ NetplanTristate transmit_checksum_offload;
426++ NetplanTristate tcp_segmentation_offload;
427++ NetplanTristate tcp6_segmentation_offload;
428++ NetplanTristate generic_segmentation_offload;
429++ NetplanTristate generic_receive_offload;
430++ NetplanTristate large_receive_offload;
431+
432+ struct private_netdef_data* _private;
433+
434+diff --git a/tests/generator/test_ethernets.py b/tests/generator/test_ethernets.py
435+index 46bf764..e81941b 100644
436+--- a/tests/generator/test_ethernets.py
437++++ b/tests/generator/test_ethernets.py
438+@@ -772,11 +772,11 @@ method=ignore
439+ ethernets:
440+ eth1:
441+ receive-checksum-offload: true
442+- transmit-checksum-offload: true
443++ transmit-checksum-offload: off
444+ tcp-segmentation-offload: true
445+- tcp6-segmentation-offload: true
446++ tcp6-segmentation-offload: false
447+ generic-segmentation-offload: true
448+- generic-receive-offload: true
449++ generic-receive-offload: no
450+ large-receive-offload: true''')
451+
452+ self.assert_networkd({'eth1.link': '''[Match]
453+@@ -784,13 +784,13 @@ OriginalName=eth1
454+
455+ [Link]
456+ WakeOnLan=off
457+-ReceiveChecksumOffload=1
458+-TransmitChecksumOffload=1
459+-TCPSegmentationOffload=1
460+-TCP6SegmentationOffload=1
461+-GenericSegmentationOffload=1
462+-GenericReceiveOffload=1
463+-LargeReceiveOffload=1
464++ReceiveChecksumOffload=true
465++TransmitChecksumOffload=false
466++TCPSegmentationOffload=true
467++TCP6SegmentationOffload=false
468++GenericSegmentationOffload=true
469++GenericReceiveOffload=false
470++LargeReceiveOffload=true
471+ ''',
472+ 'eth1.network': '''[Match]
473+ Name=eth1
474+@@ -799,3 +799,17 @@ Name=eth1
475+ LinkLocalAddressing=ipv6
476+ '''})
477+ self.assert_networkd_udev(None)
478++
479++ def test_offload_invalid(self):
480++ err = self.generate('''network:
481++ version: 2
482++ ethernets:
483++ eth1:
484++ generic-receive-offload: n
485++ receive-checksum-offload: true
486++ tcp-segmentation-offload: true
487++ tcp6-segmentation-offload: false
488++ generic-segmentation-offload: true
489++ transmit-checksum-offload: xx
490++ large-receive-offload: true''', expect_fail=True)
491++ self.assertIn('invalid boolean value \'xx\'', err)
492+diff --git a/tests/integration/ethernets.py b/tests/integration/ethernets.py
493+index 865c0d4..06ac069 100644
494+--- a/tests/integration/ethernets.py
495++++ b/tests/integration/ethernets.py
496+@@ -236,6 +236,65 @@ class _CommonTests():
497+ self.assert_iface_up('iface1', ['inet 10.10.10.11'])
498+ self.assert_iface_up('iface2', ['inet 10.10.10.22'])
499+
500++ def test_link_offloading(self):
501++ self.setup_eth(None, False)
502++ # check kernel defaults
503++ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
504++ self.assertIn(b'rx-checksumming: on', out)
505++ self.assertIn(b'tx-checksumming: on', out)
506++ self.assertIn(b'tcp-segmentation-offload: on', out)
507++ self.assertIn(b'tx-tcp6-segmentation: on', out)
508++ self.assertIn(b'generic-segmentation-offload: on', out)
509++ self.assertIn(b'generic-receive-offload: off', out) # off by default
510++ # validate turning off
511++ with open(self.config, 'w') as f:
512++ f.write('''network:
513++ renderer: %(r)s
514++ ethernets:
515++ %(ec)s:
516++ addresses: [10.10.10.22/24]
517++ receive-checksum-offload: off
518++ transmit-checksum-offload: off
519++ tcp-segmentation-offload: off
520++ tcp6-segmentation-offload: off
521++ generic-segmentation-offload: off
522++ generic-receive-offload: off
523++ #large-receive-offload: off # not possible on veth
524++''' % {'r': self.backend, 'ec': self.dev_e_client})
525++ self.generate_and_settle([self.dev_e_client])
526++ self.assert_iface_up(self.dev_e_client, ['inet 10.10.10.22'])
527++ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
528++ self.assertIn(b'rx-checksumming: off', out)
529++ self.assertIn(b'tx-checksumming: off', out)
530++ self.assertIn(b'tcp-segmentation-offload: off', out)
531++ self.assertIn(b'tx-tcp6-segmentation: off', out)
532++ self.assertIn(b'generic-segmentation-offload: off', out)
533++ self.assertIn(b'generic-receive-offload: off', out)
534++ # validate turning on
535++ with open(self.config, 'w') as f:
536++ f.write('''network:
537++ renderer: %(r)s
538++ ethernets:
539++ %(ec)s:
540++ addresses: [10.10.10.22/24]
541++ receive-checksum-offload: true
542++ transmit-checksum-offload: true
543++ tcp-segmentation-offload: true
544++ tcp6-segmentation-offload: true
545++ generic-segmentation-offload: true
546++ generic-receive-offload: true
547++ #large-receive-offload: true # not possible on veth
548++''' % {'r': self.backend, 'ec': self.dev_e_client})
549++ self.generate_and_settle([self.dev_e_client])
550++ self.assert_iface_up(self.dev_e_client, ['inet 10.10.10.22'])
551++ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
552++ self.assertIn(b'rx-checksumming: on', out)
553++ self.assertIn(b'tx-checksumming: on', out)
554++ self.assertIn(b'tcp-segmentation-offload: on', out)
555++ self.assertIn(b'tx-tcp6-segmentation: on', out)
556++ self.assertIn(b'generic-segmentation-offload: on', out)
557++ self.assertIn(b'generic-receive-offload: on', out)
558++
559+
560+ @unittest.skipIf("networkd" not in test_backends,
561+ "skipping as networkd backend tests are disabled")
562diff --git a/debian/patches/series b/debian/patches/series
563index 27d8728..77cbaaa 100644
564--- a/debian/patches/series
565+++ b/debian/patches/series
566@@ -1,3 +1,4 @@
567+0003-Add-tristate-type-for-offload-options-LP-1956264-270.patch
568 autopkgtest-fixes.patch
569 0002-cli-apply-fix-potential-race-with-rename-creation-of.patch
570 0010-nm-fix-rendering-of-password-for-unknown-passthrough.patch
571diff --git a/doc/netplan.md b/doc/netplan.md
572index e9b4e92..373e05d 100644
573--- a/doc/netplan.md
574+++ b/doc/netplan.md
575@@ -79,6 +79,10 @@ Virtual devices
576
577 ## Common properties for physical device types
578
579+**Note:** Some options will not work reliably for devices matched by name only
580+and rendered by networkd, due to interactions with device renaming in udev.
581+Match devices by MAC when setting options like: ``wakeonlan`` or ``*-offload``.
582+
583 ``match`` (mapping)
584
585 : This selects a subset of available physical devices by various hardware
586@@ -139,54 +143,50 @@ Virtual devices
587
588 : Enable wake on LAN. Off by default.
589
590- **Note:** This will not work reliably for devices matched by name
591- only and rendered by networkd, due to interactions with device
592- renaming in udev. Match devices by MAC when setting wake on LAN.
593-
594 ``emit-lldp`` (bool) – since **0.99**
595
596 : (networkd backend only) Whether to emit LLDP packets. Off by default.
597
598 ``receive-checksum-offload`` (bool) – since **0.104**
599
600-: (networkd backend only) If set to true, the hardware offload for
601- checksumming of ingress network packets is enabled. When unset,
602+: (networkd backend only) If set to true (false), the hardware offload for
603+ checksumming of ingress network packets is enabled (disabled). When unset,
604 the kernel's default will be used.
605
606 ``transmit-checksum-offload`` (bool) – since **0.104**
607
608-: (networkd backend only) If set to true, the hardware offload for
609- checksumming of egress network packets is enabled. When unset,
610+: (networkd backend only) If set to true (false), the hardware offload for
611+ checksumming of egress network packets is enabled (disabled). When unset,
612 the kernel's default will be used.
613
614 ``tcp-segmentation-offload`` (bool) – since **0.104**
615
616-: (networkd backend only) If set to true, the TCP Segmentation
617- Offload (TSO) is enabled. When unset, the kernel's default will
618+: (networkd backend only) If set to true (false), the TCP Segmentation
619+ Offload (TSO) is enabled (disabled). When unset, the kernel's default will
620 be used.
621
622 ``tcp6-segmentation-offload`` (bool) – since **0.104**
623
624-: (networkd backend only) If set to true, the TCP6 Segmentation
625- Offload (tx-tcp6-segmentation) is enabled. When unset, the
626+: (networkd backend only) If set to true (false), the TCP6 Segmentation
627+ Offload (tx-tcp6-segmentation) is enabled (disabled). When unset, the
628 kernel's default will be used.
629
630 ``generic-segmentation-offload`` (bool) – since **0.104**
631
632-: (networkd backend only) If set to true, the Generic Segmentation
633- Offload (GSO) is enabled. When unset, the kernel's default will
634+: (networkd backend only) If set to true (false), the Generic Segmentation
635+ Offload (GSO) is enabled (disabled). When unset, the kernel's default will
636 be used.
637
638 ``generic-receive-offload`` (bool) – since **0.104**
639
640-: (networkd backend only) If set to true, the Generic Receive
641- Offload (GRO) is enabled. When unset, the kernel's default will
642+: (networkd backend only) If set to true (false), the Generic Receive
643+ Offload (GRO) is enabled (disabled). When unset, the kernel's default will
644 be used.
645
646 ``large-receive-offload`` (bool) – since **0.104**
647
648-: (networkd backend only) If set to true, the Generic Receive
649- Offload (GRO) is enabled. When unset, the kernel's default will
650+: (networkd backend only) If set to true (false), the Large Receive Offload
651+ (LRO) is enabled (disabled). When unset, the kernel's default will
652 be used.
653
654 ``openvswitch`` (mapping) – since **0.100**
655diff --git a/netplan/cli/commands/apply.py b/netplan/cli/commands/apply.py
656index d481184..5bf4ae9 100644
657--- a/netplan/cli/commands/apply.py
658+++ b/netplan/cli/commands/apply.py
659@@ -221,13 +221,22 @@ class NetplanApply(utils.NetplanCommand):
660 '/sys/class/net/' + device],
661 stdout=subprocess.DEVNULL,
662 stderr=subprocess.DEVNULL)
663+ subprocess.check_call(['udevadm', 'test',
664+ '/sys/class/net/' + device],
665+ stdout=subprocess.DEVNULL,
666+ stderr=subprocess.DEVNULL)
667 except subprocess.CalledProcessError:
668 logging.debug('Ignoring device without syspath: %s', device)
669
670+ devices_after_udev = netifaces.interfaces()
671 # apply some more changes manually
672 for iface, settings in changes.items():
673 # rename non-critical network interfaces
674- if settings.get('name'):
675+ new_name = settings.get('name')
676+ if new_name:
677+ if iface in devices and new_name in devices_after_udev:
678+ logging.debug('Interface rename {} -> {} already happened.'.format(iface, new_name))
679+ continue # re-name already happened via 'udevadm test'
680 # bring down the interface, using its current (matched) interface name
681 subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'down'],
682 stdout=subprocess.DEVNULL,
683diff --git a/src/netplan.c b/src/netplan.c
684index 7b387b4..d1a27a6 100644
685--- a/src/netplan.c
686+++ b/src/netplan.c
687@@ -752,13 +752,20 @@ _serialize_yaml(
688 YAML_BOOL_TRUE(def, event, emitter, "wakeonlan", def->wake_on_lan);
689
690 /* Offload options */
691- YAML_BOOL_TRUE(def, event, emitter, "receive-checksum-offload", def->receive_checksum_offload);
692- YAML_BOOL_TRUE(def, event, emitter, "transmit-checksum-offload", def->transmit_checksum_offload);
693- YAML_BOOL_TRUE(def, event, emitter, "tcp-segmentation-offload", def->tcp_segmentation_offload);
694- YAML_BOOL_TRUE(def, event, emitter, "tcp6-segmentation-offload", def->tcp6_segmentation_offload);
695- YAML_BOOL_TRUE(def, event, emitter, "generic-segmentation-offload", def->generic_segmentation_offload);
696- YAML_BOOL_TRUE(def, event, emitter, "generic-receive-offload", def->generic_receive_offload);
697- YAML_BOOL_TRUE(def, event, emitter, "large-receive-offload", def->large_receive_offload);
698+ if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
699+ YAML_BOOL_TRUE(def, event, emitter, "receive-checksum-offload", def->receive_checksum_offload);
700+ if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
701+ YAML_BOOL_TRUE(def, event, emitter, "transmit-checksum-offload", def->transmit_checksum_offload);
702+ if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
703+ YAML_BOOL_TRUE(def, event, emitter, "tcp-segmentation-offload", def->tcp_segmentation_offload);
704+ if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
705+ YAML_BOOL_TRUE(def, event, emitter, "tcp6-segmentation-offload", def->tcp6_segmentation_offload);
706+ if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
707+ YAML_BOOL_TRUE(def, event, emitter, "generic-segmentation-offload", def->generic_segmentation_offload);
708+ if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
709+ YAML_BOOL_TRUE(def, event, emitter, "generic-receive-offload", def->generic_receive_offload);
710+ if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
711+ YAML_BOOL_TRUE(def, event, emitter, "large-receive-offload", def->large_receive_offload);
712
713 if (def->wowlan && def->wowlan != NETPLAN_WIFI_WOWLAN_DEFAULT) {
714 YAML_SCALAR_PLAIN(event, emitter, "wakeonwlan");
715diff --git a/src/networkd.c b/src/networkd.c
716index 6d26047..62c87ce 100644
717--- a/src/networkd.c
718+++ b/src/networkd.c
719@@ -243,13 +243,13 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char
720 if (!def->set_name &&
721 !def->wake_on_lan &&
722 !def->mtubytes &&
723- !def->receive_checksum_offload &&
724- !def->transmit_checksum_offload &&
725- !def->tcp_segmentation_offload &&
726- !def->tcp6_segmentation_offload &&
727- !def->generic_segmentation_offload &&
728- !def->generic_receive_offload &&
729- !def->large_receive_offload)
730+ (def->receive_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
731+ (def->transmit_checksum_offload == NETPLAN_TRISTATE_UNSET) &&
732+ (def->tcp_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
733+ (def->tcp6_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
734+ (def->generic_segmentation_offload == NETPLAN_TRISTATE_UNSET) &&
735+ (def->generic_receive_offload == NETPLAN_TRISTATE_UNSET) &&
736+ (def->large_receive_offload == NETPLAN_TRISTATE_UNSET))
737 return;
738
739 /* build file contents */
740@@ -265,26 +265,33 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char
741 g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes);
742
743 /* Offload options */
744- if (def->receive_checksum_offload)
745- g_string_append_printf(s, "ReceiveChecksumOffload=%u\n", def->receive_checksum_offload);
746+ if (def->receive_checksum_offload != NETPLAN_TRISTATE_UNSET)
747+ g_string_append_printf(s, "ReceiveChecksumOffload=%s\n",
748+ (def->receive_checksum_offload ? "true" : "false"));
749
750- if (def->transmit_checksum_offload)
751- g_string_append_printf(s, "TransmitChecksumOffload=%u\n", def->transmit_checksum_offload);
752+ if (def->transmit_checksum_offload != NETPLAN_TRISTATE_UNSET)
753+ g_string_append_printf(s, "TransmitChecksumOffload=%s\n",
754+ (def->transmit_checksum_offload ? "true" : "false"));
755
756- if (def->tcp_segmentation_offload)
757- g_string_append_printf(s, "TCPSegmentationOffload=%u\n", def->tcp_segmentation_offload);
758+ if (def->tcp_segmentation_offload != NETPLAN_TRISTATE_UNSET)
759+ g_string_append_printf(s, "TCPSegmentationOffload=%s\n",
760+ (def->tcp_segmentation_offload ? "true" : "false"));
761
762- if (def->tcp6_segmentation_offload)
763- g_string_append_printf(s, "TCP6SegmentationOffload=%u\n", def->tcp6_segmentation_offload);
764+ if (def->tcp6_segmentation_offload != NETPLAN_TRISTATE_UNSET)
765+ g_string_append_printf(s, "TCP6SegmentationOffload=%s\n",
766+ (def->tcp6_segmentation_offload ? "true" : "false"));
767
768- if (def->generic_segmentation_offload)
769- g_string_append_printf(s, "GenericSegmentationOffload=%u\n", def->generic_segmentation_offload);
770+ if (def->generic_segmentation_offload != NETPLAN_TRISTATE_UNSET)
771+ g_string_append_printf(s, "GenericSegmentationOffload=%s\n",
772+ (def->generic_segmentation_offload ? "true" : "false"));
773
774- if (def->generic_receive_offload)
775- g_string_append_printf(s, "GenericReceiveOffload=%u\n", def->generic_receive_offload);
776+ if (def->generic_receive_offload != NETPLAN_TRISTATE_UNSET)
777+ g_string_append_printf(s, "GenericReceiveOffload=%s\n",
778+ (def->generic_receive_offload ? "true" : "false"));
779
780- if (def->large_receive_offload)
781- g_string_append_printf(s, "LargeReceiveOffload=%u\n", def->large_receive_offload);
782+ if (def->large_receive_offload != NETPLAN_TRISTATE_UNSET)
783+ g_string_append_printf(s, "LargeReceiveOffload=%s\n",
784+ (def->large_receive_offload ? "true" : "false"));
785
786 orig_umask = umask(022);
787 g_string_free_to_file(s, rootdir, path, ".link");
788diff --git a/src/parse.c b/src/parse.c
789index 350c508..0cb07d2 100644
790--- a/src/parse.c
791+++ b/src/parse.c
792@@ -374,6 +374,37 @@ handle_generic_bool(NetplanParser* npp, yaml_node_t* node, void* entryptr, const
793 * Handler for setting a HashTable field from a mapping node, inside a given struct
794 * @entryptr: pointer to the beginning of the to-be-modified data structure
795 * @data: offset into entryptr struct where the boolean field to write is located
796+ */
797+static gboolean
798+handle_generic_tristate(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
799+{
800+ g_assert(entryptr);
801+ NetplanTristate v;
802+ guint offset = GPOINTER_TO_UINT(data);
803+ NetplanTristate* dest = ((void*) entryptr + offset);
804+
805+ if (g_ascii_strcasecmp(scalar(node), "true") == 0 ||
806+ g_ascii_strcasecmp(scalar(node), "on") == 0 ||
807+ g_ascii_strcasecmp(scalar(node), "yes") == 0 ||
808+ g_ascii_strcasecmp(scalar(node), "y") == 0)
809+ v = NETPLAN_TRISTATE_TRUE;
810+ else if (g_ascii_strcasecmp(scalar(node), "false") == 0 ||
811+ g_ascii_strcasecmp(scalar(node), "off") == 0 ||
812+ g_ascii_strcasecmp(scalar(node), "no") == 0 ||
813+ g_ascii_strcasecmp(scalar(node), "n") == 0)
814+ v = NETPLAN_TRISTATE_FALSE;
815+ else
816+ return yaml_error(npp, node, error, "invalid boolean value '%s'", scalar(node));
817+
818+ *dest = v;
819+ mark_data_as_dirty(npp, dest);
820+ return TRUE;
821+}
822+
823+/*
824+ * Handler for setting a HashTable field from a mapping node, inside a given struct
825+ * @entryptr: pointer to the beginning of the to-be-modified data structure
826+ * @data: offset into entryptr struct where the boolean field to write is located
827 */
828 static gboolean
829 handle_generic_map(NetplanParser *npp, yaml_node_t* node, void* entryptr, const void* data, GError** error)
830@@ -517,6 +548,16 @@ handle_netdef_bool(NetplanParser* npp, yaml_node_t* node, const void* data, GErr
831 }
832
833 /**
834+ * Generic handler for tri-state settings that can bei "UNSET", "TRUE", or "FALSE".
835+ * @data: offset into NetplanNetDefinition where the guint field to write is located
836+ */
837+static gboolean
838+handle_netdef_tristate(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error)
839+{
840+ return handle_generic_tristate(npp, node, npp->current.netdef, data, error);
841+}
842+
843+/**
844 * Generic handler for setting a npp->current.netdef guint field from a scalar node
845 * @data: offset into NetplanNetDefinition where the guint field to write is located
846 */
847@@ -2356,13 +2397,13 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = {
848 {"wakeonlan", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(wake_on_lan)}, \
849 {"wakeonwlan", YAML_SEQUENCE_NODE, {.generic=handle_wowlan}, netdef_offset(wowlan)}, \
850 {"emit-lldp", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(emit_lldp)}, \
851- {"receive-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(receive_checksum_offload)}, \
852- {"transmit-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(transmit_checksum_offload)}, \
853- {"tcp-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(tcp_segmentation_offload)}, \
854- {"tcp6-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(tcp6_segmentation_offload)}, \
855- {"generic-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(generic_segmentation_offload)}, \
856- {"generic-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(generic_receive_offload)}, \
857- {"large-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_bool}, netdef_offset(large_receive_offload)}
858+ {"receive-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(receive_checksum_offload)}, \
859+ {"transmit-checksum-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(transmit_checksum_offload)}, \
860+ {"tcp-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp_segmentation_offload)}, \
861+ {"tcp6-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(tcp6_segmentation_offload)}, \
862+ {"generic-segmentation-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_segmentation_offload)}, \
863+ {"generic-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(generic_receive_offload)}, \
864+ {"large-receive-offload", YAML_SCALAR_NODE, {.generic=handle_netdef_tristate}, netdef_offset(large_receive_offload)}
865
866 static const mapping_entry_handler ethernet_def_handlers[] = {
867 COMMON_LINK_HANDLERS,
868diff --git a/src/types.c b/src/types.c
869index eb9f780..00c2b0f 100644
870--- a/src/types.c
871+++ b/src/types.c
872@@ -335,6 +335,14 @@ reset_netdef(NetplanNetDefinition* netdef, NetplanDefType new_type, NetplanBacke
873
874 reset_private_netdef_data(netdef->_private);
875 FREE_AND_NULLIFY(netdef->_private);
876+
877+ netdef->receive_checksum_offload = NETPLAN_TRISTATE_UNSET;
878+ netdef->transmit_checksum_offload = NETPLAN_TRISTATE_UNSET;
879+ netdef->tcp_segmentation_offload = NETPLAN_TRISTATE_UNSET;
880+ netdef->tcp6_segmentation_offload = NETPLAN_TRISTATE_UNSET;
881+ netdef->generic_segmentation_offload = NETPLAN_TRISTATE_UNSET;
882+ netdef->generic_receive_offload = NETPLAN_TRISTATE_UNSET;
883+ netdef->large_receive_offload = NETPLAN_TRISTATE_UNSET;
884 }
885
886 static void
887diff --git a/src/types.h b/src/types.h
888index 27a23fc..710b1f1 100644
889--- a/src/types.h
890+++ b/src/types.h
891@@ -171,6 +171,27 @@ typedef union {
892 } networkd;
893 } NetplanBackendSettings;
894
895+typedef enum
896+{
897+ /**
898+ * @brief Tristate enum type
899+ *
900+ * This type defines a boolean which can be unset, i.e.
901+ * this type has three states. The enum is ordered so
902+ * that
903+ *
904+ * UNSET -> -1
905+ * FALSE -> 0
906+ * TRUE -> 1
907+ *
908+ * And the integer values can be used directly when
909+ * converting to string.
910+ */
911+ NETPLAN_TRISTATE_UNSET = -1, /* -1 */
912+ NETPLAN_TRISTATE_FALSE, /* 0 */
913+ NETPLAN_TRISTATE_TRUE, /* 1 */
914+} NetplanTristate;
915+
916 struct netplan_net_definition {
917 NetplanDefType type;
918 NetplanBackend backend;
919@@ -333,13 +354,13 @@ struct netplan_net_definition {
920 gboolean ignore_carrier;
921
922 /* offload options */
923- gboolean receive_checksum_offload;
924- gboolean transmit_checksum_offload;
925- gboolean tcp_segmentation_offload;
926- gboolean tcp6_segmentation_offload;
927- gboolean generic_segmentation_offload;
928- gboolean generic_receive_offload;
929- gboolean large_receive_offload;
930+ NetplanTristate receive_checksum_offload;
931+ NetplanTristate transmit_checksum_offload;
932+ NetplanTristate tcp_segmentation_offload;
933+ NetplanTristate tcp6_segmentation_offload;
934+ NetplanTristate generic_segmentation_offload;
935+ NetplanTristate generic_receive_offload;
936+ NetplanTristate large_receive_offload;
937
938 struct private_netdef_data* _private;
939
940diff --git a/tests/generator/test_ethernets.py b/tests/generator/test_ethernets.py
941index 46bf764..e81941b 100644
942--- a/tests/generator/test_ethernets.py
943+++ b/tests/generator/test_ethernets.py
944@@ -772,11 +772,11 @@ method=ignore
945 ethernets:
946 eth1:
947 receive-checksum-offload: true
948- transmit-checksum-offload: true
949+ transmit-checksum-offload: off
950 tcp-segmentation-offload: true
951- tcp6-segmentation-offload: true
952+ tcp6-segmentation-offload: false
953 generic-segmentation-offload: true
954- generic-receive-offload: true
955+ generic-receive-offload: no
956 large-receive-offload: true''')
957
958 self.assert_networkd({'eth1.link': '''[Match]
959@@ -784,13 +784,13 @@ OriginalName=eth1
960
961 [Link]
962 WakeOnLan=off
963-ReceiveChecksumOffload=1
964-TransmitChecksumOffload=1
965-TCPSegmentationOffload=1
966-TCP6SegmentationOffload=1
967-GenericSegmentationOffload=1
968-GenericReceiveOffload=1
969-LargeReceiveOffload=1
970+ReceiveChecksumOffload=true
971+TransmitChecksumOffload=false
972+TCPSegmentationOffload=true
973+TCP6SegmentationOffload=false
974+GenericSegmentationOffload=true
975+GenericReceiveOffload=false
976+LargeReceiveOffload=true
977 ''',
978 'eth1.network': '''[Match]
979 Name=eth1
980@@ -799,3 +799,17 @@ Name=eth1
981 LinkLocalAddressing=ipv6
982 '''})
983 self.assert_networkd_udev(None)
984+
985+ def test_offload_invalid(self):
986+ err = self.generate('''network:
987+ version: 2
988+ ethernets:
989+ eth1:
990+ generic-receive-offload: n
991+ receive-checksum-offload: true
992+ tcp-segmentation-offload: true
993+ tcp6-segmentation-offload: false
994+ generic-segmentation-offload: true
995+ transmit-checksum-offload: xx
996+ large-receive-offload: true''', expect_fail=True)
997+ self.assertIn('invalid boolean value \'xx\'', err)
998diff --git a/tests/integration/ethernets.py b/tests/integration/ethernets.py
999index 865c0d4..06ac069 100644
1000--- a/tests/integration/ethernets.py
1001+++ b/tests/integration/ethernets.py
1002@@ -236,6 +236,65 @@ class _CommonTests():
1003 self.assert_iface_up('iface1', ['inet 10.10.10.11'])
1004 self.assert_iface_up('iface2', ['inet 10.10.10.22'])
1005
1006+ def test_link_offloading(self):
1007+ self.setup_eth(None, False)
1008+ # check kernel defaults
1009+ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
1010+ self.assertIn(b'rx-checksumming: on', out)
1011+ self.assertIn(b'tx-checksumming: on', out)
1012+ self.assertIn(b'tcp-segmentation-offload: on', out)
1013+ self.assertIn(b'tx-tcp6-segmentation: on', out)
1014+ self.assertIn(b'generic-segmentation-offload: on', out)
1015+ self.assertIn(b'generic-receive-offload: off', out) # off by default
1016+ # validate turning off
1017+ with open(self.config, 'w') as f:
1018+ f.write('''network:
1019+ renderer: %(r)s
1020+ ethernets:
1021+ %(ec)s:
1022+ addresses: [10.10.10.22/24]
1023+ receive-checksum-offload: off
1024+ transmit-checksum-offload: off
1025+ tcp-segmentation-offload: off
1026+ tcp6-segmentation-offload: off
1027+ generic-segmentation-offload: off
1028+ generic-receive-offload: off
1029+ #large-receive-offload: off # not possible on veth
1030+''' % {'r': self.backend, 'ec': self.dev_e_client})
1031+ self.generate_and_settle([self.dev_e_client])
1032+ self.assert_iface_up(self.dev_e_client, ['inet 10.10.10.22'])
1033+ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
1034+ self.assertIn(b'rx-checksumming: off', out)
1035+ self.assertIn(b'tx-checksumming: off', out)
1036+ self.assertIn(b'tcp-segmentation-offload: off', out)
1037+ self.assertIn(b'tx-tcp6-segmentation: off', out)
1038+ self.assertIn(b'generic-segmentation-offload: off', out)
1039+ self.assertIn(b'generic-receive-offload: off', out)
1040+ # validate turning on
1041+ with open(self.config, 'w') as f:
1042+ f.write('''network:
1043+ renderer: %(r)s
1044+ ethernets:
1045+ %(ec)s:
1046+ addresses: [10.10.10.22/24]
1047+ receive-checksum-offload: true
1048+ transmit-checksum-offload: true
1049+ tcp-segmentation-offload: true
1050+ tcp6-segmentation-offload: true
1051+ generic-segmentation-offload: true
1052+ generic-receive-offload: true
1053+ #large-receive-offload: true # not possible on veth
1054+''' % {'r': self.backend, 'ec': self.dev_e_client})
1055+ self.generate_and_settle([self.dev_e_client])
1056+ self.assert_iface_up(self.dev_e_client, ['inet 10.10.10.22'])
1057+ out = subprocess.check_output(['ethtool', '-k', self.dev_e_client])
1058+ self.assertIn(b'rx-checksumming: on', out)
1059+ self.assertIn(b'tx-checksumming: on', out)
1060+ self.assertIn(b'tcp-segmentation-offload: on', out)
1061+ self.assertIn(b'tx-tcp6-segmentation: on', out)
1062+ self.assertIn(b'generic-segmentation-offload: on', out)
1063+ self.assertIn(b'generic-receive-offload: on', out)
1064+
1065
1066 @unittest.skipIf("networkd" not in test_backends,
1067 "skipping as networkd backend tests are disabled")

Subscribers

People subscribed via source and target branches