]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
02b59d57 | 2 | |
987efa17 | 3 | #include <net/if.h> |
f2000933 | 4 | #include <netinet/in.h> |
007899f4 | 5 | #include <linux/if_arp.h> |
a0b191b7 | 6 | #include <unistd.h> |
987efa17 | 7 | |
b5efdb8a | 8 | #include "alloc-util.h" |
007899f4 | 9 | #include "arphrd-util.h" |
e6980c72 | 10 | #include "bareudp.h" |
c0267a59 | 11 | #include "batadv.h" |
737f1405 YW |
12 | #include "bond.h" |
13 | #include "bridge.h" | |
02b59d57 TG |
14 | #include "conf-files.h" |
15 | #include "conf-parser.h" | |
737f1405 | 16 | #include "dummy.h" |
3ffd4af2 | 17 | #include "fd-util.h" |
737f1405 YW |
18 | #include "fou-tunnel.h" |
19 | #include "geneve.h" | |
3295a461 | 20 | #include "ifb.h" |
b90d0f83 | 21 | #include "ipoib.h" |
737f1405 YW |
22 | #include "ipvlan.h" |
23 | #include "l2tp-tunnel.h" | |
02b59d57 | 24 | #include "list.h" |
737f1405 YW |
25 | #include "macsec.h" |
26 | #include "macvlan.h" | |
27 | #include "netdev.h" | |
28 | #include "netdevsim.h" | |
007899f4 | 29 | #include "netif-util.h" |
3a56e697 | 30 | #include "netlink-util.h" |
3a56e697 | 31 | #include "networkd-manager.h" |
71a754f7 | 32 | #include "networkd-queue.h" |
9b682672 | 33 | #include "networkd-setlink.h" |
737f1405 | 34 | #include "nlmon.h" |
b0c82192 | 35 | #include "path-lookup.h" |
3a56e697 SS |
36 | #include "siphash24.h" |
37 | #include "stat-util.h" | |
38 | #include "string-table.h" | |
39 | #include "string-util.h" | |
40 | #include "strv.h" | |
737f1405 YW |
41 | #include "tunnel.h" |
42 | #include "tuntap.h" | |
43 | #include "vcan.h" | |
44 | #include "veth.h" | |
45 | #include "vlan.h" | |
46 | #include "vrf.h" | |
47 | #include "vxcan.h" | |
48 | #include "vxlan.h" | |
49 | #include "wireguard.h" | |
dedf2d00 | 50 | #include "wlan.h" |
737f1405 | 51 | #include "xfrm.h" |
23f53b99 | 52 | |
3be1d7e0 | 53 | const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { |
bc945c2b YW |
54 | [NETDEV_KIND_BAREUDP] = &bare_udp_vtable, |
55 | [NETDEV_KIND_BATADV] = &batadv_vtable, | |
56 | [NETDEV_KIND_BOND] = &bond_vtable, | |
57 | [NETDEV_KIND_BRIDGE] = &bridge_vtable, | |
58 | [NETDEV_KIND_DUMMY] = &dummy_vtable, | |
59 | [NETDEV_KIND_ERSPAN] = &erspan_vtable, | |
60 | [NETDEV_KIND_FOU] = &foutnl_vtable, | |
61 | [NETDEV_KIND_GENEVE] = &geneve_vtable, | |
62 | [NETDEV_KIND_GRE] = &gre_vtable, | |
63 | [NETDEV_KIND_GRETAP] = &gretap_vtable, | |
64 | [NETDEV_KIND_IFB] = &ifb_vtable, | |
65 | [NETDEV_KIND_IP6GRE] = &ip6gre_vtable, | |
b16492f8 | 66 | [NETDEV_KIND_IP6GRETAP] = &ip6gretap_vtable, |
bc945c2b YW |
67 | [NETDEV_KIND_IP6TNL] = &ip6tnl_vtable, |
68 | [NETDEV_KIND_IPIP] = &ipip_vtable, | |
b90d0f83 | 69 | [NETDEV_KIND_IPOIB] = &ipoib_vtable, |
bc945c2b YW |
70 | [NETDEV_KIND_IPVLAN] = &ipvlan_vtable, |
71 | [NETDEV_KIND_IPVTAP] = &ipvtap_vtable, | |
72 | [NETDEV_KIND_L2TP] = &l2tptnl_vtable, | |
73 | [NETDEV_KIND_MACSEC] = &macsec_vtable, | |
74 | [NETDEV_KIND_MACVLAN] = &macvlan_vtable, | |
75 | [NETDEV_KIND_MACVTAP] = &macvtap_vtable, | |
56e7fb50 | 76 | [NETDEV_KIND_NETDEVSIM] = &netdevsim_vtable, |
bc945c2b YW |
77 | [NETDEV_KIND_NLMON] = &nlmon_vtable, |
78 | [NETDEV_KIND_SIT] = &sit_vtable, | |
79 | [NETDEV_KIND_TAP] = &tap_vtable, | |
80 | [NETDEV_KIND_TUN] = &tun_vtable, | |
81 | [NETDEV_KIND_VCAN] = &vcan_vtable, | |
82 | [NETDEV_KIND_VETH] = &veth_vtable, | |
83 | [NETDEV_KIND_VLAN] = &vlan_vtable, | |
84 | [NETDEV_KIND_VRF] = &vrf_vtable, | |
85 | [NETDEV_KIND_VTI6] = &vti6_vtable, | |
86 | [NETDEV_KIND_VTI] = &vti_vtable, | |
87 | [NETDEV_KIND_VXCAN] = &vxcan_vtable, | |
88 | [NETDEV_KIND_VXLAN] = &vxlan_vtable, | |
89 | [NETDEV_KIND_WIREGUARD] = &wireguard_vtable, | |
dedf2d00 | 90 | [NETDEV_KIND_WLAN] = &wlan_vtable, |
bc945c2b | 91 | [NETDEV_KIND_XFRM] = &xfrm_vtable, |
3be1d7e0 TG |
92 | }; |
93 | ||
2c5859af | 94 | static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { |
bc945c2b YW |
95 | [NETDEV_KIND_BAREUDP] = "bareudp", |
96 | [NETDEV_KIND_BATADV] = "batadv", | |
97 | [NETDEV_KIND_BOND] = "bond", | |
98 | [NETDEV_KIND_BRIDGE] = "bridge", | |
99 | [NETDEV_KIND_DUMMY] = "dummy", | |
100 | [NETDEV_KIND_ERSPAN] = "erspan", | |
101 | [NETDEV_KIND_FOU] = "fou", | |
102 | [NETDEV_KIND_GENEVE] = "geneve", | |
103 | [NETDEV_KIND_GRE] = "gre", | |
104 | [NETDEV_KIND_GRETAP] = "gretap", | |
105 | [NETDEV_KIND_IFB] = "ifb", | |
106 | [NETDEV_KIND_IP6GRE] = "ip6gre", | |
b16492f8 | 107 | [NETDEV_KIND_IP6GRETAP] = "ip6gretap", |
bc945c2b YW |
108 | [NETDEV_KIND_IP6TNL] = "ip6tnl", |
109 | [NETDEV_KIND_IPIP] = "ipip", | |
b90d0f83 | 110 | [NETDEV_KIND_IPOIB] = "ipoib", |
bc945c2b YW |
111 | [NETDEV_KIND_IPVLAN] = "ipvlan", |
112 | [NETDEV_KIND_IPVTAP] = "ipvtap", | |
113 | [NETDEV_KIND_L2TP] = "l2tp", | |
114 | [NETDEV_KIND_MACSEC] = "macsec", | |
115 | [NETDEV_KIND_MACVLAN] = "macvlan", | |
116 | [NETDEV_KIND_MACVTAP] = "macvtap", | |
56e7fb50 | 117 | [NETDEV_KIND_NETDEVSIM] = "netdevsim", |
bc945c2b YW |
118 | [NETDEV_KIND_NLMON] = "nlmon", |
119 | [NETDEV_KIND_SIT] = "sit", | |
120 | [NETDEV_KIND_TAP] = "tap", | |
121 | [NETDEV_KIND_TUN] = "tun", | |
122 | [NETDEV_KIND_VCAN] = "vcan", | |
123 | [NETDEV_KIND_VETH] = "veth", | |
124 | [NETDEV_KIND_VLAN] = "vlan", | |
125 | [NETDEV_KIND_VRF] = "vrf", | |
126 | [NETDEV_KIND_VTI6] = "vti6", | |
127 | [NETDEV_KIND_VTI] = "vti", | |
128 | [NETDEV_KIND_VXCAN] = "vxcan", | |
129 | [NETDEV_KIND_VXLAN] = "vxlan", | |
130 | [NETDEV_KIND_WIREGUARD] = "wireguard", | |
dedf2d00 | 131 | [NETDEV_KIND_WLAN] = "virtual-wlan", |
bc945c2b | 132 | [NETDEV_KIND_XFRM] = "xfrm", |
52433f6b | 133 | }; |
02b59d57 | 134 | |
1a436809 | 135 | DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind); |
62facba1 | 136 | |
9e2bbf99 YW |
137 | bool netdev_is_managed(NetDev *netdev) { |
138 | if (!netdev || !netdev->manager || !netdev->ifname) | |
139 | return false; | |
140 | ||
141 | return hashmap_get(netdev->manager->netdevs, netdev->ifname) == netdev; | |
142 | } | |
143 | ||
798e5dc8 YW |
144 | static bool netdev_is_stacked_and_independent(NetDev *netdev) { |
145 | assert(netdev); | |
146 | ||
3d24b0dd | 147 | if (netdev_get_create_type(netdev) != NETDEV_CREATE_STACKED) |
798e5dc8 YW |
148 | return false; |
149 | ||
150 | switch (netdev->kind) { | |
151 | case NETDEV_KIND_ERSPAN: | |
152 | return ERSPAN(netdev)->independent; | |
153 | case NETDEV_KIND_GRE: | |
154 | return GRE(netdev)->independent; | |
155 | case NETDEV_KIND_GRETAP: | |
156 | return GRETAP(netdev)->independent; | |
157 | case NETDEV_KIND_IP6GRE: | |
158 | return IP6GRE(netdev)->independent; | |
159 | case NETDEV_KIND_IP6GRETAP: | |
160 | return IP6GRETAP(netdev)->independent; | |
161 | case NETDEV_KIND_IP6TNL: | |
162 | return IP6TNL(netdev)->independent; | |
163 | case NETDEV_KIND_IPIP: | |
164 | return IPIP(netdev)->independent; | |
165 | case NETDEV_KIND_SIT: | |
166 | return SIT(netdev)->independent; | |
167 | case NETDEV_KIND_VTI: | |
168 | return VTI(netdev)->independent; | |
169 | case NETDEV_KIND_VTI6: | |
170 | return VTI6(netdev)->independent; | |
171 | case NETDEV_KIND_VXLAN: | |
172 | return VXLAN(netdev)->independent; | |
173 | case NETDEV_KIND_XFRM: | |
174 | return XFRM(netdev)->independent; | |
175 | default: | |
176 | return false; | |
177 | } | |
178 | } | |
179 | ||
2f117922 YW |
180 | static bool netdev_is_stacked(NetDev *netdev) { |
181 | assert(netdev); | |
182 | ||
3d24b0dd | 183 | if (netdev_get_create_type(netdev) != NETDEV_CREATE_STACKED) |
2f117922 YW |
184 | return false; |
185 | ||
186 | if (netdev_is_stacked_and_independent(netdev)) | |
187 | return false; | |
188 | ||
189 | return true; | |
190 | } | |
191 | ||
c4397d94 YW |
192 | static void netdev_detach_from_manager(NetDev *netdev) { |
193 | if (netdev->ifname && netdev->manager) | |
194 | hashmap_remove(netdev->manager->netdevs, netdev->ifname); | |
c4397d94 YW |
195 | } |
196 | ||
8301aa0b YW |
197 | static NetDev *netdev_free(NetDev *netdev) { |
198 | assert(netdev); | |
59cb64e6 | 199 | |
c4397d94 | 200 | netdev_detach_from_manager(netdev); |
02b59d57 | 201 | |
52433f6b | 202 | free(netdev->filename); |
02b59d57 | 203 | |
52433f6b | 204 | free(netdev->description); |
af4e9e2c | 205 | free(netdev->ifname); |
c4f58dea | 206 | condition_free_list(netdev->conditions); |
79e16ce3 | 207 | |
f3c33b23 LP |
208 | /* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that |
209 | * because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure | |
210 | * allocation, with no room for per-kind fields), and once to read the kind's properties (with a full, | |
211 | * comprehensive NetDev structure allocation with enough space for whatever the specific kind needs). Now, in | |
212 | * the first case we shouldn't try to destruct the per-kind NetDev fields on destruction, in the second case we | |
213 | * should. We use the state field to discern the two cases: it's _NETDEV_STATE_INVALID on the first "raw" | |
214 | * call. */ | |
215 | if (netdev->state != _NETDEV_STATE_INVALID && | |
216 | NETDEV_VTABLE(netdev) && | |
aa9f1140 TG |
217 | NETDEV_VTABLE(netdev)->done) |
218 | NETDEV_VTABLE(netdev)->done(netdev); | |
219 | ||
8301aa0b | 220 | return mfree(netdev); |
14b746f7 TG |
221 | } |
222 | ||
8301aa0b | 223 | DEFINE_TRIVIAL_REF_UNREF_FUNC(NetDev, netdev, netdev_free); |
14b746f7 | 224 | |
2cc7e981 | 225 | void netdev_drop(NetDev *netdev) { |
2f117922 | 226 | if (!netdev) |
2cc7e981 TG |
227 | return; |
228 | ||
2f117922 YW |
229 | if (netdev_is_stacked(netdev)) { |
230 | /* The netdev may be removed due to the underlying device removal, and the device may | |
231 | * be re-added later. */ | |
232 | netdev->state = NETDEV_STATE_LOADING; | |
233 | netdev->ifindex = 0; | |
234 | ||
235 | log_netdev_debug(netdev, "netdev removed"); | |
236 | return; | |
237 | } | |
238 | ||
2cc7e981 TG |
239 | netdev->state = NETDEV_STATE_LINGER; |
240 | ||
79008bdd | 241 | log_netdev_debug(netdev, "netdev removed"); |
370e9930 | 242 | |
c4397d94 | 243 | netdev_detach_from_manager(netdev); |
2cc7e981 | 244 | netdev_unref(netdev); |
2cc7e981 TG |
245 | return; |
246 | } | |
247 | ||
1a436809 TG |
248 | int netdev_get(Manager *manager, const char *name, NetDev **ret) { |
249 | NetDev *netdev; | |
02b59d57 TG |
250 | |
251 | assert(manager); | |
252 | assert(name); | |
253 | assert(ret); | |
254 | ||
52433f6b | 255 | netdev = hashmap_get(manager->netdevs, name); |
fef805b9 | 256 | if (!netdev) |
02b59d57 | 257 | return -ENOENT; |
02b59d57 | 258 | |
52433f6b | 259 | *ret = netdev; |
02b59d57 TG |
260 | |
261 | return 0; | |
262 | } | |
263 | ||
8f65304c | 264 | void netdev_enter_failed(NetDev *netdev) { |
52433f6b | 265 | netdev->state = NETDEV_STATE_FAILED; |
02b59d57 TG |
266 | } |
267 | ||
1a436809 | 268 | static int netdev_enter_ready(NetDev *netdev) { |
52433f6b | 269 | assert(netdev); |
af4e9e2c | 270 | assert(netdev->ifname); |
924fe430 | 271 | |
ba5596ec TG |
272 | if (netdev->state != NETDEV_STATE_CREATING) |
273 | return 0; | |
274 | ||
52433f6b | 275 | netdev->state = NETDEV_STATE_READY; |
02b59d57 | 276 | |
98b32556 | 277 | log_netdev_info(netdev, "netdev ready"); |
02b59d57 | 278 | |
540eb5f0 | 279 | if (NETDEV_VTABLE(netdev)->post_create) |
c2b19b8f | 280 | NETDEV_VTABLE(netdev)->post_create(netdev, NULL); |
540eb5f0 | 281 | |
02b59d57 TG |
282 | return 0; |
283 | } | |
8469c1d3 | 284 | |
d5b3d845 | 285 | /* callback for netdev's created without a backing Link */ |
302a796f | 286 | static int netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { |
172f6635 | 287 | int r; |
02b59d57 | 288 | |
1046bf9b | 289 | assert(netdev); |
52433f6b | 290 | assert(netdev->state != _NETDEV_STATE_INVALID); |
02b59d57 | 291 | |
1c4baffc | 292 | r = sd_netlink_message_get_errno(m); |
e09826dc | 293 | if (r == -EEXIST) |
98b32556 | 294 | log_netdev_info(netdev, "netdev exists, using existing without changing its parameters"); |
505f8da7 | 295 | else if (r < 0) { |
6a7a4e4d | 296 | log_netdev_warning_errno(netdev, r, "netdev could not be created: %m"); |
8f65304c | 297 | netdev_enter_failed(netdev); |
dd3efc09 TG |
298 | |
299 | return 1; | |
02b59d57 TG |
300 | } |
301 | ||
6a7a4e4d | 302 | log_netdev_debug(netdev, "Created"); |
aa9f1140 | 303 | |
dd3efc09 | 304 | return 1; |
02b59d57 TG |
305 | } |
306 | ||
1c4baffc | 307 | int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *message) { |
c3ebdce3 | 308 | uint16_t type; |
d39edfc7 | 309 | const char *kind; |
ca4e095a LP |
310 | const char *received_kind; |
311 | const char *received_name; | |
d39edfc7 TG |
312 | int r, ifindex; |
313 | ||
50add290 | 314 | assert(netdev); |
c3ebdce3 | 315 | assert(message); |
02b59d57 | 316 | |
1c4baffc | 317 | r = sd_netlink_message_get_type(message, &type); |
6a7a4e4d LP |
318 | if (r < 0) |
319 | return log_netdev_error_errno(netdev, r, "Could not get rtnl message type: %m"); | |
c3ebdce3 | 320 | |
5a9494be YW |
321 | if (type != RTM_NEWLINK) |
322 | return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), "Cannot set ifindex from unexpected rtnl message type."); | |
d39edfc7 | 323 | |
a21df104 TG |
324 | r = sd_rtnl_message_link_get_ifindex(message, &ifindex); |
325 | if (r < 0) { | |
6a7a4e4d | 326 | log_netdev_error_errno(netdev, r, "Could not get ifindex: %m"); |
a21df104 TG |
327 | netdev_enter_failed(netdev); |
328 | return r; | |
329 | } else if (ifindex <= 0) { | |
79008bdd | 330 | log_netdev_error(netdev, "Got invalid ifindex: %d", ifindex); |
a21df104 | 331 | netdev_enter_failed(netdev); |
6a7a4e4d | 332 | return -EINVAL; |
a21df104 TG |
333 | } |
334 | ||
a21df104 TG |
335 | if (netdev->ifindex > 0) { |
336 | if (netdev->ifindex != ifindex) { | |
79008bdd | 337 | log_netdev_error(netdev, "Could not set ifindex to %d, already set to %d", |
a21df104 TG |
338 | ifindex, netdev->ifindex); |
339 | netdev_enter_failed(netdev); | |
340 | return -EEXIST; | |
341 | } else | |
342 | /* ifindex already set to the same for this netdev */ | |
343 | return 0; | |
344 | } | |
345 | ||
1c4baffc | 346 | r = sd_netlink_message_read_string(message, IFLA_IFNAME, &received_name); |
6a7a4e4d LP |
347 | if (r < 0) |
348 | return log_netdev_error_errno(netdev, r, "Could not get IFNAME: %m"); | |
c6315a7a | 349 | |
af4e9e2c | 350 | if (!streq(netdev->ifname, received_name)) { |
6a7a4e4d | 351 | log_netdev_error(netdev, "Received newlink with wrong IFNAME %s", received_name); |
c6315a7a | 352 | netdev_enter_failed(netdev); |
72f09281 | 353 | return -EINVAL; |
c6315a7a TG |
354 | } |
355 | ||
1d4312d5 YW |
356 | if (!NETDEV_VTABLE(netdev)->skip_netdev_kind_check) { |
357 | ||
358 | r = sd_netlink_message_enter_container(message, IFLA_LINKINFO); | |
359 | if (r < 0) | |
360 | return log_netdev_error_errno(netdev, r, "Could not get LINKINFO: %m"); | |
361 | ||
362 | r = sd_netlink_message_read_string(message, IFLA_INFO_KIND, &received_kind); | |
363 | if (r < 0) | |
364 | return log_netdev_error_errno(netdev, r, "Could not get KIND: %m"); | |
365 | ||
366 | r = sd_netlink_message_exit_container(message); | |
367 | if (r < 0) | |
368 | return log_netdev_error_errno(netdev, r, "Could not exit container: %m"); | |
369 | ||
370 | if (netdev->kind == NETDEV_KIND_TAP) | |
371 | /* the kernel does not distinguish between tun and tap */ | |
372 | kind = "tun"; | |
373 | else { | |
374 | kind = netdev_kind_to_string(netdev->kind); | |
375 | if (!kind) { | |
376 | log_netdev_error(netdev, "Could not get kind"); | |
377 | netdev_enter_failed(netdev); | |
378 | return -EINVAL; | |
379 | } | |
380 | } | |
d39edfc7 | 381 | |
1d4312d5 YW |
382 | if (!streq(kind, received_kind)) { |
383 | log_netdev_error(netdev, "Received newlink with wrong KIND %s, expected %s", | |
384 | received_kind, kind); | |
30ae9dfd SS |
385 | netdev_enter_failed(netdev); |
386 | return -EINVAL; | |
387 | } | |
c3ebdce3 TG |
388 | } |
389 | ||
50add290 | 390 | netdev->ifindex = ifindex; |
52433f6b | 391 | |
79008bdd | 392 | log_netdev_debug(netdev, "netdev has index %d", netdev->ifindex); |
5261692f | 393 | |
52433f6b | 394 | netdev_enter_ready(netdev); |
02b59d57 TG |
395 | |
396 | return 0; | |
397 | } | |
398 | ||
5c8f858d TG |
399 | #define HASH_KEY SD_ID128_MAKE(52,e1,45,bd,00,6f,29,96,21,c6,30,6d,83,71,04,48) |
400 | ||
a8ee2b8e YW |
401 | int netdev_generate_hw_addr( |
402 | NetDev *netdev, | |
b90d0f83 | 403 | Link *parent, |
a8ee2b8e YW |
404 | const char *name, |
405 | const struct hw_addr_data *hw_addr, | |
406 | struct hw_addr_data *ret) { | |
407 | ||
408 | struct hw_addr_data a = HW_ADDR_NULL; | |
45aa0e84 | 409 | bool is_static = false; |
5c8f858d TG |
410 | int r; |
411 | ||
007899f4 YW |
412 | assert(netdev); |
413 | assert(name); | |
414 | assert(hw_addr); | |
a8ee2b8e | 415 | assert(ret); |
5c8f858d | 416 | |
a8ee2b8e YW |
417 | if (hw_addr_equal(hw_addr, &HW_ADDR_NONE)) { |
418 | *ret = HW_ADDR_NULL; | |
aaa5ca57 | 419 | return 0; |
a8ee2b8e | 420 | } |
aaa5ca57 | 421 | |
007899f4 YW |
422 | if (hw_addr->length == 0) { |
423 | uint64_t result; | |
5c8f858d | 424 | |
007899f4 | 425 | /* HardwareAddress= is not specified. */ |
5c8f858d | 426 | |
007899f4 | 427 | if (!NETDEV_VTABLE(netdev)->generate_mac) |
a8ee2b8e | 428 | goto finalize; |
5c8f858d | 429 | |
b90d0f83 | 430 | if (!IN_SET(NETDEV_VTABLE(netdev)->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) |
a8ee2b8e | 431 | goto finalize; |
5c8f858d | 432 | |
007899f4 YW |
433 | r = net_get_unique_predictable_data_from_name(name, &HASH_KEY, &result); |
434 | if (r < 0) { | |
435 | log_netdev_warning_errno(netdev, r, | |
436 | "Failed to generate persistent MAC address, ignoring: %m"); | |
a8ee2b8e | 437 | goto finalize; |
007899f4 | 438 | } |
5c8f858d | 439 | |
007899f4 | 440 | a.length = arphrd_to_hw_addr_len(NETDEV_VTABLE(netdev)->iftype); |
5c8f858d | 441 | |
b90d0f83 YW |
442 | switch (NETDEV_VTABLE(netdev)->iftype) { |
443 | case ARPHRD_ETHER: | |
444 | assert(a.length <= sizeof(result)); | |
445 | memcpy(a.bytes, &result, a.length); | |
446 | ||
447 | if (ether_addr_is_null(&a.ether) || ether_addr_is_broadcast(&a.ether)) { | |
448 | log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), | |
449 | "Failed to generate persistent MAC address, ignoring: %m"); | |
450 | a = HW_ADDR_NULL; | |
451 | goto finalize; | |
452 | } | |
453 | ||
454 | break; | |
455 | case ARPHRD_INFINIBAND: | |
456 | if (result == 0) { | |
457 | log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), | |
458 | "Failed to generate persistent MAC address: %m"); | |
459 | goto finalize; | |
460 | } | |
461 | ||
462 | assert(a.length >= sizeof(result)); | |
463 | memzero(a.bytes, a.length - sizeof(result)); | |
464 | memcpy(a.bytes + a.length - sizeof(result), &result, sizeof(result)); | |
465 | break; | |
466 | default: | |
467 | assert_not_reached(); | |
007899f4 | 468 | } |
b90d0f83 | 469 | |
007899f4 YW |
470 | } else { |
471 | a = *hw_addr; | |
45aa0e84 | 472 | is_static = true; |
007899f4 YW |
473 | } |
474 | ||
45aa0e84 | 475 | r = net_verify_hardware_address(name, is_static, NETDEV_VTABLE(netdev)->iftype, |
b90d0f83 | 476 | parent ? &parent->hw_addr : NULL, &a); |
007899f4 YW |
477 | if (r < 0) |
478 | return r; | |
479 | ||
a8ee2b8e YW |
480 | finalize: |
481 | *ret = a; | |
5c8f858d TG |
482 | return 0; |
483 | } | |
484 | ||
0c50cb50 | 485 | static int netdev_create_message(NetDev *netdev, Link *link, sd_netlink_message *m) { |
aa9f1140 TG |
486 | int r; |
487 | ||
28a8cc0a YW |
488 | r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname); |
489 | if (r < 0) | |
0c50cb50 | 490 | return r; |
aa9f1140 | 491 | |
0c50cb50 | 492 | struct hw_addr_data hw_addr; |
b90d0f83 | 493 | r = netdev_generate_hw_addr(netdev, link, netdev->ifname, &netdev->hw_addr, &hw_addr); |
a8ee2b8e YW |
494 | if (r < 0) |
495 | return r; | |
496 | ||
497 | if (hw_addr.length > 0) { | |
498 | log_netdev_debug(netdev, "Using MAC address: %s", HW_ADDR_TO_STR(&hw_addr)); | |
499 | r = netlink_message_append_hw_addr(m, IFLA_ADDRESS, &hw_addr); | |
6a7a4e4d | 500 | if (r < 0) |
0c50cb50 | 501 | return r; |
28a8cc0a | 502 | } |
aa9f1140 | 503 | |
28a8cc0a YW |
504 | if (netdev->mtu != 0) { |
505 | r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu); | |
6a7a4e4d | 506 | if (r < 0) |
0c50cb50 | 507 | return r; |
28a8cc0a | 508 | } |
aa9f1140 | 509 | |
28a8cc0a YW |
510 | if (link) { |
511 | r = sd_netlink_message_append_u32(m, IFLA_LINK, link->ifindex); | |
512 | if (r < 0) | |
0c50cb50 | 513 | return r; |
28a8cc0a | 514 | } |
aa9f1140 | 515 | |
28a8cc0a YW |
516 | r = sd_netlink_message_open_container(m, IFLA_LINKINFO); |
517 | if (r < 0) | |
0c50cb50 | 518 | return r; |
aa9f1140 | 519 | |
28a8cc0a YW |
520 | if (NETDEV_VTABLE(netdev)->fill_message_create) { |
521 | r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind)); | |
522 | if (r < 0) | |
0c50cb50 | 523 | return r; |
aa9f1140 | 524 | |
28a8cc0a | 525 | r = NETDEV_VTABLE(netdev)->fill_message_create(netdev, link, m); |
6a7a4e4d | 526 | if (r < 0) |
0c50cb50 | 527 | return r; |
aa9f1140 | 528 | |
1c4baffc | 529 | r = sd_netlink_message_close_container(m); |
6a7a4e4d | 530 | if (r < 0) |
0c50cb50 | 531 | return r; |
28a8cc0a YW |
532 | } else { |
533 | r = sd_netlink_message_append_string(m, IFLA_INFO_KIND, netdev_kind_to_string(netdev->kind)); | |
534 | if (r < 0) | |
0c50cb50 | 535 | return r; |
28a8cc0a | 536 | } |
aa9f1140 | 537 | |
28a8cc0a YW |
538 | r = sd_netlink_message_close_container(m); |
539 | if (r < 0) | |
0c50cb50 ZJS |
540 | return r; |
541 | ||
542 | return 0; | |
543 | } | |
544 | ||
b4d6ae63 | 545 | static int independent_netdev_create(NetDev *netdev) { |
0c50cb50 ZJS |
546 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
547 | int r; | |
548 | ||
549 | assert(netdev); | |
0c50cb50 ZJS |
550 | |
551 | /* create netdev */ | |
552 | if (NETDEV_VTABLE(netdev)->create) { | |
0c50cb50 ZJS |
553 | r = NETDEV_VTABLE(netdev)->create(netdev); |
554 | if (r < 0) | |
555 | return r; | |
556 | ||
557 | log_netdev_debug(netdev, "Created"); | |
558 | return 0; | |
559 | } | |
560 | ||
561 | r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); | |
562 | if (r < 0) | |
b4d6ae63 | 563 | return r; |
0c50cb50 | 564 | |
b4d6ae63 | 565 | r = netdev_create_message(netdev, NULL, m); |
0c50cb50 | 566 | if (r < 0) |
b4d6ae63 | 567 | return r; |
aa9f1140 | 568 | |
b4d6ae63 YW |
569 | r = netlink_call_async(netdev->manager->rtnl, NULL, m, netdev_create_handler, |
570 | netdev_destroy_callback, netdev); | |
571 | if (r < 0) | |
572 | return r; | |
28a8cc0a | 573 | |
b4d6ae63 | 574 | netdev_ref(netdev); |
aa9f1140 | 575 | |
28a8cc0a | 576 | netdev->state = NETDEV_STATE_CREATING; |
28a8cc0a | 577 | log_netdev_debug(netdev, "Creating"); |
aa9f1140 TG |
578 | return 0; |
579 | } | |
580 | ||
54ff39f7 | 581 | static int stacked_netdev_create(NetDev *netdev, Link *link, Request *req) { |
b4d6ae63 | 582 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
aa9f1140 TG |
583 | int r; |
584 | ||
585 | assert(netdev); | |
586 | assert(netdev->manager); | |
b4d6ae63 | 587 | assert(link); |
54ff39f7 | 588 | assert(req); |
aa9f1140 | 589 | |
b4d6ae63 YW |
590 | r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); |
591 | if (r < 0) | |
592 | return r; | |
aa9f1140 | 593 | |
b4d6ae63 YW |
594 | r = netdev_create_message(netdev, link, m); |
595 | if (r < 0) | |
596 | return r; | |
597 | ||
80d62d4f | 598 | r = request_call_netlink_async(netdev->manager->rtnl, m, req); |
b4d6ae63 YW |
599 | if (r < 0) |
600 | return r; | |
601 | ||
b4d6ae63 YW |
602 | netdev->state = NETDEV_STATE_CREATING; |
603 | log_netdev_debug(netdev, "Creating"); | |
aa9f1140 TG |
604 | return 0; |
605 | } | |
606 | ||
d708bc6e | 607 | static int netdev_is_ready_to_create(NetDev *netdev, Link *link) { |
71a754f7 | 608 | assert(netdev); |
71a754f7 YW |
609 | |
610 | if (netdev->state != NETDEV_STATE_LOADING) | |
611 | return false; | |
543240f5 | 612 | |
5d4a925a YW |
613 | if (link) { |
614 | if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) | |
615 | return false; | |
543240f5 | 616 | |
5d4a925a YW |
617 | if (link->set_link_messages > 0) |
618 | return false; | |
047b9991 YW |
619 | |
620 | /* If stacked netdevs are created before the underlying interface being activated, then | |
621 | * the activation policy for the netdevs are ignored. See issue #22593. */ | |
622 | if (!link->activated) | |
623 | return false; | |
5d4a925a | 624 | } |
71a754f7 | 625 | |
562729d7 YW |
626 | if (NETDEV_VTABLE(netdev)->is_ready_to_create) |
627 | return NETDEV_VTABLE(netdev)->is_ready_to_create(netdev, link); | |
628 | ||
71a754f7 YW |
629 | return true; |
630 | } | |
631 | ||
09d09207 | 632 | static int stacked_netdev_process_request(Request *req, Link *link, void *userdata) { |
ff51134c | 633 | NetDev *netdev = ASSERT_PTR(userdata); |
71a754f7 YW |
634 | int r; |
635 | ||
636 | assert(req); | |
ff51134c | 637 | assert(link); |
b4d6ae63 YW |
638 | |
639 | r = netdev_is_ready_to_create(netdev, link); | |
d708bc6e YW |
640 | if (r <= 0) |
641 | return r; | |
71a754f7 | 642 | |
54ff39f7 | 643 | r = stacked_netdev_create(netdev, link, req); |
71a754f7 | 644 | if (r < 0) |
b4d6ae63 | 645 | return log_netdev_warning_errno(netdev, r, "Failed to create netdev: %m"); |
71a754f7 YW |
646 | |
647 | return 1; | |
648 | } | |
649 | ||
80d62d4f | 650 | static int create_stacked_netdev_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { |
71a754f7 YW |
651 | int r; |
652 | ||
653 | assert(m); | |
654 | assert(link); | |
71a754f7 YW |
655 | |
656 | r = sd_netlink_message_get_errno(m); | |
657 | if (r < 0 && r != -EEXIST) { | |
658 | log_link_message_warning_errno(link, m, r, "Could not create stacked netdev"); | |
659 | link_enter_failed(link); | |
660 | return 0; | |
661 | } | |
662 | ||
71a754f7 YW |
663 | if (link->create_stacked_netdev_messages == 0) { |
664 | link->stacked_netdevs_created = true; | |
665 | log_link_debug(link, "Stacked netdevs created."); | |
bb916f35 | 666 | link_check_ready(link); |
71a754f7 YW |
667 | } |
668 | ||
669 | return 0; | |
670 | } | |
671 | ||
b14686ff | 672 | int link_request_stacked_netdev(Link *link, NetDev *netdev) { |
71a754f7 YW |
673 | int r; |
674 | ||
675 | assert(link); | |
676 | assert(netdev); | |
677 | ||
2f117922 | 678 | if (!netdev_is_stacked(netdev)) |
71a754f7 YW |
679 | return -EINVAL; |
680 | ||
2f117922 YW |
681 | if (!IN_SET(netdev->state, NETDEV_STATE_LOADING, NETDEV_STATE_FAILED) || netdev->ifindex > 0) |
682 | return 0; /* Already created. */ | |
71a754f7 | 683 | |
3d24b0dd | 684 | link->stacked_netdevs_created = false; |
09d09207 YW |
685 | r = link_queue_request_full(link, REQUEST_TYPE_NETDEV_STACKED, |
686 | netdev_ref(netdev), (mfree_func_t) netdev_unref, | |
687 | trivial_hash_func, trivial_compare_func, | |
688 | stacked_netdev_process_request, | |
689 | &link->create_stacked_netdev_messages, | |
690 | create_stacked_netdev_handler, NULL); | |
71a754f7 | 691 | if (r < 0) |
b14686ff | 692 | return log_link_error_errno(link, r, "Failed to request stacked netdev '%s': %m", |
71a754f7 YW |
693 | netdev->ifname); |
694 | ||
b14686ff | 695 | log_link_debug(link, "Requested stacked netdev '%s'", netdev->ifname); |
71a754f7 YW |
696 | return 0; |
697 | } | |
698 | ||
09d09207 | 699 | static int independent_netdev_process_request(Request *req, Link *link, void *userdata) { |
ff51134c | 700 | NetDev *netdev = ASSERT_PTR(userdata); |
5d4a925a YW |
701 | int r; |
702 | ||
ff51134c | 703 | assert(!link); |
b4d6ae63 YW |
704 | |
705 | r = netdev_is_ready_to_create(netdev, NULL); | |
5d4a925a YW |
706 | if (r <= 0) |
707 | return r; | |
708 | ||
b4d6ae63 | 709 | r = independent_netdev_create(netdev); |
5d4a925a | 710 | if (r < 0) |
b4d6ae63 | 711 | return log_netdev_warning_errno(netdev, r, "Failed to create netdev: %m"); |
5d4a925a YW |
712 | |
713 | return 1; | |
714 | } | |
715 | ||
b4d6ae63 | 716 | static int netdev_request_to_create(NetDev *netdev) { |
5d4a925a YW |
717 | int r; |
718 | ||
719 | assert(netdev); | |
720 | ||
eae0d010 | 721 | if (netdev_is_stacked(netdev)) |
5d4a925a YW |
722 | return 0; |
723 | ||
724 | r = netdev_is_ready_to_create(netdev, NULL); | |
725 | if (r < 0) | |
726 | return r; | |
b4d6ae63 | 727 | if (r > 0) { |
5d4a925a | 728 | /* If the netdev has no dependency, then create it now. */ |
b4d6ae63 YW |
729 | r = independent_netdev_create(netdev); |
730 | if (r < 0) | |
731 | return log_netdev_warning_errno(netdev, r, "Failed to create netdev: %m"); | |
732 | ||
733 | } else { | |
734 | /* Otherwise, wait for the dependencies being resolved. */ | |
09d09207 | 735 | r = netdev_queue_request(netdev, independent_netdev_process_request, NULL); |
b4d6ae63 | 736 | if (r < 0) |
09d09207 | 737 | return log_netdev_warning_errno(netdev, r, "Failed to request to create netdev: %m"); |
b4d6ae63 | 738 | } |
5d4a925a | 739 | |
b4d6ae63 | 740 | return 0; |
5d4a925a YW |
741 | } |
742 | ||
e27aac11 | 743 | int netdev_load_one(Manager *manager, const char *filename) { |
8e766630 | 744 | _cleanup_(netdev_unrefp) NetDev *netdev_raw = NULL, *netdev = NULL; |
2cc34d5b | 745 | const char *dropin_dirname; |
02b59d57 TG |
746 | int r; |
747 | ||
bf1bc670 TA |
748 | assert(manager); |
749 | assert(filename); | |
750 | ||
4e54a17d YW |
751 | r = null_or_empty_path(filename); |
752 | if (r == -ENOENT) | |
753 | return 0; | |
754 | if (r < 0) | |
755 | return r; | |
756 | if (r > 0) { | |
ed88bcfb ZJS |
757 | log_debug("Skipping empty file: %s", filename); |
758 | return 0; | |
759 | } | |
760 | ||
17f9c355 | 761 | netdev_raw = new(NetDev, 1); |
aa9f1140 | 762 | if (!netdev_raw) |
02b59d57 TG |
763 | return log_oom(); |
764 | ||
17f9c355 YW |
765 | *netdev_raw = (NetDev) { |
766 | .n_ref = 1, | |
767 | .kind = _NETDEV_KIND_INVALID, | |
768 | .state = _NETDEV_STATE_INVALID, /* an invalid state means done() of the implementation won't be called on destruction */ | |
769 | }; | |
02b59d57 | 770 | |
281bb5c1 | 771 | dropin_dirname = strjoina(basename(filename), ".d"); |
4f9ff96a | 772 | r = config_parse_many( |
8b8024f1 | 773 | STRV_MAKE_CONST(filename), NETWORK_DIRS, dropin_dirname, |
4f9ff96a LP |
774 | NETDEV_COMMON_SECTIONS NETDEV_OTHER_SECTIONS, |
775 | config_item_perf_lookup, network_netdev_gperf_lookup, | |
776 | CONFIG_PARSE_WARN, | |
777 | netdev_raw, | |
778 | NULL); | |
36f822c4 | 779 | if (r < 0) |
02b59d57 | 780 | return r; |
02b59d57 | 781 | |
2023dc8a | 782 | /* skip out early if configuration does not match the environment */ |
a0b191b7 | 783 | if (!condition_test_list(netdev_raw->conditions, environ, NULL, NULL, NULL)) { |
a6779fec | 784 | log_debug("%s: Conditions in the file do not match the system environment, skipping.", filename); |
2023dc8a | 785 | return 0; |
a6779fec | 786 | } |
2023dc8a | 787 | |
2f27e2c5 | 788 | if (netdev_raw->kind == _NETDEV_KIND_INVALID) { |
c6e77d7b | 789 | log_warning("NetDev has no Kind= configured in %s. Ignoring", filename); |
52433f6b | 790 | return 0; |
3be1d7e0 | 791 | } |
2023dc8a | 792 | |
aa9f1140 | 793 | if (!netdev_raw->ifname) { |
c6e77d7b | 794 | log_warning("NetDev without Name= configured in %s. Ignoring", filename); |
326cb406 SS |
795 | return 0; |
796 | } | |
797 | ||
aa9f1140 TG |
798 | netdev = malloc0(NETDEV_VTABLE(netdev_raw)->object_size); |
799 | if (!netdev) | |
800 | return log_oom(); | |
fe6b2d55 | 801 | |
aa9f1140 TG |
802 | netdev->n_ref = 1; |
803 | netdev->manager = manager; | |
aa9f1140 | 804 | netdev->kind = netdev_raw->kind; |
c6e77d7b YW |
805 | netdev->state = NETDEV_STATE_LOADING; /* we initialize the state here for the first time, |
806 | so that done() will be called on destruction */ | |
326cb406 | 807 | |
aa9f1140 TG |
808 | if (NETDEV_VTABLE(netdev)->init) |
809 | NETDEV_VTABLE(netdev)->init(netdev); | |
810 | ||
4f9ff96a | 811 | r = config_parse_many( |
8b8024f1 | 812 | STRV_MAKE_CONST(filename), NETWORK_DIRS, dropin_dirname, |
4f9ff96a LP |
813 | NETDEV_VTABLE(netdev)->sections, |
814 | config_item_perf_lookup, network_netdev_gperf_lookup, | |
815 | CONFIG_PARSE_WARN, | |
816 | netdev, NULL); | |
aa9f1140 TG |
817 | if (r < 0) |
818 | return r; | |
819 | ||
820 | /* verify configuration */ | |
821 | if (NETDEV_VTABLE(netdev)->config_verify) { | |
822 | r = NETDEV_VTABLE(netdev)->config_verify(netdev, filename); | |
823 | if (r < 0) | |
824 | return 0; | |
fe6b2d55 TG |
825 | } |
826 | ||
52433f6b TG |
827 | netdev->filename = strdup(filename); |
828 | if (!netdev->filename) | |
02b59d57 TG |
829 | return log_oom(); |
830 | ||
c26f9a9d SS |
831 | r = hashmap_ensure_put(&netdev->manager->netdevs, &string_hash_ops, netdev->ifname, netdev); |
832 | if (r == -ENOMEM) | |
833 | return log_oom(); | |
b519908c YW |
834 | if (r == -EEXIST) { |
835 | NetDev *n = hashmap_get(netdev->manager->netdevs, netdev->ifname); | |
836 | ||
837 | assert(n); | |
e272b621 YW |
838 | if (!streq(netdev->filename, n->filename)) |
839 | log_netdev_warning_errno(netdev, r, | |
74bd6ad0 YW |
840 | "Device was already configured by file %s, ignoring %s.", |
841 | n->filename, netdev->filename); | |
b519908c YW |
842 | |
843 | /* Clear ifname before netdev_free() is called. Otherwise, the NetDev object 'n' is | |
844 | * removed from the hashmap 'manager->netdevs'. */ | |
845 | netdev->ifname = mfree(netdev->ifname); | |
846 | return 0; | |
847 | } | |
02b59d57 TG |
848 | if (r < 0) |
849 | return r; | |
850 | ||
79008bdd | 851 | log_netdev_debug(netdev, "loaded %s", netdev_kind_to_string(netdev->kind)); |
3be1d7e0 | 852 | |
b4d6ae63 | 853 | r = netdev_request_to_create(netdev); |
5d4a925a | 854 | if (r < 0) |
b4d6ae63 | 855 | return r; |
02b59d57 | 856 | |
5d4a925a | 857 | TAKE_PTR(netdev); |
02b59d57 TG |
858 | return 0; |
859 | } | |
860 | ||
e272b621 | 861 | int netdev_load(Manager *manager, bool reload) { |
6a7a4e4d | 862 | _cleanup_strv_free_ char **files = NULL; |
02b59d57 TG |
863 | int r; |
864 | ||
865 | assert(manager); | |
866 | ||
e272b621 YW |
867 | if (!reload) |
868 | hashmap_clear_with_destructor(manager->netdevs, netdev_unref); | |
02b59d57 | 869 | |
dc0d4078 | 870 | r = conf_files_list_strv(&files, ".netdev", NULL, 0, NETWORK_DIRS); |
f647962d MS |
871 | if (r < 0) |
872 | return log_error_errno(r, "Failed to enumerate netdev files: %m"); | |
02b59d57 | 873 | |
b519908c | 874 | STRV_FOREACH(f, files) { |
52433f6b | 875 | r = netdev_load_one(manager, *f); |
02b59d57 | 876 | if (r < 0) |
be711082 | 877 | log_error_errno(r, "Failed to load %s, ignoring: %m", *f); |
02b59d57 TG |
878 | } |
879 | ||
02b59d57 TG |
880 | return 0; |
881 | } | |
5dcc5b1a YW |
882 | |
883 | int config_parse_netdev_kind( | |
884 | const char *unit, | |
885 | const char *filename, | |
886 | unsigned line, | |
887 | const char *section, | |
888 | unsigned section_line, | |
889 | const char *lvalue, | |
890 | int ltype, | |
891 | const char *rvalue, | |
892 | void *data, | |
893 | void *userdata) { | |
894 | ||
895 | NetDevKind k, *kind = data; | |
896 | ||
897 | assert(filename); | |
898 | assert(rvalue); | |
899 | assert(data); | |
900 | ||
901 | k = netdev_kind_from_string(rvalue); | |
902 | if (k < 0) { | |
903 | log_syntax(unit, LOG_WARNING, filename, line, k, "Failed to parse netdev kind, ignoring assignment: %s", rvalue); | |
904 | return 0; | |
905 | } | |
906 | ||
907 | if (*kind != _NETDEV_KIND_INVALID && *kind != k) { | |
908 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
909 | "Specified netdev kind is different from the previous value '%s', ignoring assignment: %s", | |
910 | netdev_kind_to_string(*kind), rvalue); | |
911 | return 0; | |
912 | } | |
913 | ||
914 | *kind = k; | |
915 | ||
916 | return 0; | |
917 | } | |
aaa5ca57 YW |
918 | |
919 | int config_parse_netdev_hw_addr( | |
920 | const char *unit, | |
921 | const char *filename, | |
922 | unsigned line, | |
923 | const char *section, | |
924 | unsigned section_line, | |
925 | const char *lvalue, | |
926 | int ltype, | |
927 | const char *rvalue, | |
928 | void *data, | |
929 | void *userdata) { | |
930 | ||
931 | struct hw_addr_data *hw_addr = data; | |
932 | ||
933 | assert(rvalue); | |
934 | assert(data); | |
935 | ||
936 | if (streq(rvalue, "none")) { | |
937 | *hw_addr = HW_ADDR_NONE; | |
938 | return 0; | |
939 | } | |
940 | ||
941 | return config_parse_hw_addr(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); | |
942 | } |