/src/systemd/src/network/netdev/bond.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <linux/if_arp.h> |
4 | | #include <netinet/in.h> |
5 | | |
6 | | #include "sd-netlink.h" |
7 | | |
8 | | #include "alloc-util.h" |
9 | | #include "bond.h" |
10 | | #include "bond-util.h" |
11 | | #include "conf-parser.h" |
12 | | #include "ether-addr-util.h" |
13 | | #include "extract-word.h" |
14 | | #include "in-addr-util.h" |
15 | | #include "networkd-link.h" |
16 | | #include "ordered-set.h" |
17 | | #include "set.h" |
18 | | #include "string-util.h" |
19 | | #include "time-util.h" |
20 | | /* |
21 | | * Number of seconds between instances where the bonding |
22 | | * driver sends learning packets to each slaves peer switch |
23 | | */ |
24 | 742 | #define LEARNING_PACKETS_INTERVAL_MIN_SEC (1 * USEC_PER_SEC) |
25 | 0 | #define LEARNING_PACKETS_INTERVAL_MAX_SEC (0x7fffffff * USEC_PER_SEC) |
26 | | |
27 | | /* Number of IGMP membership reports to be issued after |
28 | | * a failover event. |
29 | | */ |
30 | | #define RESEND_IGMP_MIN 0 |
31 | 0 | #define RESEND_IGMP_MAX 255 |
32 | 742 | #define RESEND_IGMP_DEFAULT 1 |
33 | | |
34 | | /* |
35 | | * Number of packets to transmit through a slave before |
36 | | * moving to the next one. |
37 | | */ |
38 | | #define PACKETS_PER_SLAVE_MIN 0 |
39 | 0 | #define PACKETS_PER_SLAVE_MAX 65535 |
40 | 742 | #define PACKETS_PER_SLAVE_DEFAULT 1 |
41 | | |
42 | | /* |
43 | | * Number of peer notifications (gratuitous ARPs and |
44 | | * unsolicited IPv6 Neighbor Advertisements) to be issued after a |
45 | | * failover event. |
46 | | */ |
47 | | #define GRATUITOUS_ARP_MIN 0 |
48 | 0 | #define GRATUITOUS_ARP_MAX 255 |
49 | 742 | #define GRATUITOUS_ARP_DEFAULT 1 |
50 | | |
51 | 1.68k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_mode, bond_mode, BondMode); |
52 | 1.68k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_xmit_hash_policy, |
53 | 1.55k | bond_xmit_hash_policy, |
54 | 1.55k | BondXmitHashPolicy); |
55 | 1.55k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_lacp_rate, bond_lacp_rate, BondLacpRate); |
56 | 1.55k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_ad_select, bond_ad_select, BondAdSelect); |
57 | 1.55k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_fail_over_mac, bond_fail_over_mac, BondFailOverMac); |
58 | 1.55k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_validate, bond_arp_validate, BondArpValidate); |
59 | 1.55k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_all_targets, bond_arp_all_targets, BondArpAllTargets); |
60 | 1.58k | DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_primary_reselect, bond_primary_reselect, BondPrimaryReselect); |
61 | 1.58k | |
62 | 1.58k | static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) { |
63 | 0 | Bond *b = BOND(netdev); |
64 | 0 | int r; |
65 | |
|
66 | 0 | assert(netdev->manager); |
67 | 0 | assert(!link); |
68 | 0 | assert(m); |
69 | |
|
70 | 0 | if (netdev->ifindex > 0) { |
71 | 0 | r = link_get_by_index(netdev->manager, netdev->ifindex, &link); |
72 | 0 | if (r < 0) |
73 | 0 | return r; |
74 | 0 | } |
75 | | |
76 | 0 | bool up = link && link_is_up(link); |
77 | 0 | bool has_slaves = link && !set_isempty(link->slaves); |
78 | |
|
79 | 0 | if (b->mode != _NETDEV_BOND_MODE_INVALID && !up && !has_slaves) { |
80 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_MODE, b->mode); |
81 | 0 | if (r < 0) |
82 | 0 | return r; |
83 | 0 | } |
84 | | |
85 | 0 | if (b->xmit_hash_policy != _NETDEV_BOND_XMIT_HASH_POLICY_INVALID) { |
86 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_XMIT_HASH_POLICY, b->xmit_hash_policy); |
87 | 0 | if (r < 0) |
88 | 0 | return r; |
89 | 0 | } |
90 | | |
91 | 0 | if (b->lacp_rate != _NETDEV_BOND_LACP_RATE_INVALID && |
92 | 0 | b->mode == NETDEV_BOND_MODE_802_3AD && |
93 | 0 | !up) { |
94 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_LACP_RATE, b->lacp_rate); |
95 | 0 | if (r < 0) |
96 | 0 | return r; |
97 | 0 | } |
98 | | |
99 | 0 | if (b->miimon != 0) { |
100 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_MIIMON, b->miimon / USEC_PER_MSEC); |
101 | 0 | if (r < 0) |
102 | 0 | return r; |
103 | 0 | } |
104 | | |
105 | 0 | if (b->peer_notify_delay != 0) { |
106 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_PEER_NOTIF_DELAY, b->peer_notify_delay / USEC_PER_MSEC); |
107 | 0 | if (r < 0) |
108 | 0 | return r; |
109 | 0 | } |
110 | | |
111 | 0 | if (b->downdelay != 0) { |
112 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_DOWNDELAY, b->downdelay / USEC_PER_MSEC); |
113 | 0 | if (r < 0) |
114 | 0 | return r; |
115 | 0 | } |
116 | | |
117 | 0 | if (b->updelay != 0) { |
118 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_UPDELAY, b->updelay / USEC_PER_MSEC); |
119 | 0 | if (r < 0) |
120 | 0 | return r; |
121 | 0 | } |
122 | | |
123 | 0 | if (b->arp_interval != 0) { |
124 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_INTERVAL, b->arp_interval / USEC_PER_MSEC); |
125 | 0 | if (r < 0) |
126 | 0 | return r; |
127 | | |
128 | 0 | if (b->lp_interval >= LEARNING_PACKETS_INTERVAL_MIN_SEC && |
129 | 0 | b->lp_interval <= LEARNING_PACKETS_INTERVAL_MAX_SEC) { |
130 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_LP_INTERVAL, b->lp_interval / USEC_PER_SEC); |
131 | 0 | if (r < 0) |
132 | 0 | return r; |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | 0 | if (b->ad_select != _NETDEV_BOND_AD_SELECT_INVALID && |
137 | 0 | b->mode == NETDEV_BOND_MODE_802_3AD && |
138 | 0 | !up) { |
139 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_SELECT, b->ad_select); |
140 | 0 | if (r < 0) |
141 | 0 | return r; |
142 | 0 | } |
143 | | |
144 | 0 | if (b->fail_over_mac != _NETDEV_BOND_FAIL_OVER_MAC_INVALID && |
145 | 0 | b->mode == NETDEV_BOND_MODE_ACTIVE_BACKUP && |
146 | 0 | !has_slaves) { |
147 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_FAIL_OVER_MAC, b->fail_over_mac); |
148 | 0 | if (r < 0) |
149 | 0 | return r; |
150 | 0 | } |
151 | | |
152 | 0 | if (b->arp_validate != _NETDEV_BOND_ARP_VALIDATE_INVALID) { |
153 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_VALIDATE, b->arp_validate); |
154 | 0 | if (r < 0) |
155 | 0 | return r; |
156 | 0 | } |
157 | | |
158 | 0 | if (b->arp_all_targets != _NETDEV_BOND_ARP_ALL_TARGETS_INVALID) { |
159 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_ALL_TARGETS, b->arp_all_targets); |
160 | 0 | if (r < 0) |
161 | 0 | return r; |
162 | 0 | } |
163 | | |
164 | 0 | if (b->primary_reselect != _NETDEV_BOND_PRIMARY_RESELECT_INVALID) { |
165 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_PRIMARY_RESELECT, b->primary_reselect); |
166 | 0 | if (r < 0) |
167 | 0 | return r; |
168 | 0 | } |
169 | | |
170 | 0 | if (b->resend_igmp <= RESEND_IGMP_MAX) { |
171 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_RESEND_IGMP, b->resend_igmp); |
172 | 0 | if (r < 0) |
173 | 0 | return r; |
174 | 0 | } |
175 | | |
176 | 0 | if (b->packets_per_slave <= PACKETS_PER_SLAVE_MAX && |
177 | 0 | b->mode == NETDEV_BOND_MODE_BALANCE_RR) { |
178 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_PACKETS_PER_SLAVE, b->packets_per_slave); |
179 | 0 | if (r < 0) |
180 | 0 | return r; |
181 | 0 | } |
182 | | |
183 | 0 | if (b->num_grat_arp <= GRATUITOUS_ARP_MAX) { |
184 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_NUM_PEER_NOTIF, b->num_grat_arp); |
185 | 0 | if (r < 0) |
186 | 0 | return r; |
187 | 0 | } |
188 | | |
189 | 0 | if (b->min_links != 0) { |
190 | 0 | r = sd_netlink_message_append_u32(m, IFLA_BOND_MIN_LINKS, b->min_links); |
191 | 0 | if (r < 0) |
192 | 0 | return r; |
193 | 0 | } |
194 | | |
195 | 0 | if (b->ad_actor_sys_prio != 0) { |
196 | 0 | r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_ACTOR_SYS_PRIO, b->ad_actor_sys_prio); |
197 | 0 | if (r < 0) |
198 | 0 | return r; |
199 | 0 | } |
200 | | |
201 | 0 | if (b->ad_user_port_key != 0 && !up) { |
202 | 0 | r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_USER_PORT_KEY, b->ad_user_port_key); |
203 | 0 | if (r < 0) |
204 | 0 | return r; |
205 | 0 | } |
206 | | |
207 | 0 | if (!ether_addr_is_null(&b->ad_actor_system)) { |
208 | 0 | r = sd_netlink_message_append_ether_addr(m, IFLA_BOND_AD_ACTOR_SYSTEM, &b->ad_actor_system); |
209 | 0 | if (r < 0) |
210 | 0 | return r; |
211 | 0 | } |
212 | | |
213 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_ALL_SLAVES_ACTIVE, b->all_slaves_active); |
214 | 0 | if (r < 0) |
215 | 0 | return r; |
216 | | |
217 | 0 | if (b->tlb_dynamic_lb >= 0 && !up) { |
218 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_TLB_DYNAMIC_LB, b->tlb_dynamic_lb); |
219 | 0 | if (r < 0) |
220 | 0 | return r; |
221 | 0 | } |
222 | | |
223 | 0 | if (b->arp_missed_max > 0) { |
224 | 0 | r = sd_netlink_message_append_u8(m, IFLA_BOND_MISSED_MAX, b->arp_missed_max); |
225 | 0 | if (r < 0) |
226 | 0 | return r; |
227 | 0 | } |
228 | | |
229 | 0 | if (b->arp_interval > 0 && !ordered_set_isempty(b->arp_ip_targets)) { |
230 | 0 | void *val; |
231 | 0 | int n = 0; |
232 | |
|
233 | 0 | r = sd_netlink_message_open_container(m, IFLA_BOND_ARP_IP_TARGET); |
234 | 0 | if (r < 0) |
235 | 0 | return r; |
236 | | |
237 | 0 | ORDERED_SET_FOREACH(val, b->arp_ip_targets) { |
238 | 0 | r = sd_netlink_message_append_u32(m, n++, PTR_TO_UINT32(val)); |
239 | 0 | if (r < 0) |
240 | 0 | return r; |
241 | 0 | } |
242 | | |
243 | 0 | r = sd_netlink_message_close_container(m); |
244 | 0 | if (r < 0) |
245 | 0 | return r; |
246 | 0 | } |
247 | | |
248 | 0 | return 0; |
249 | 0 | } |
250 | | |
251 | | int config_parse_arp_ip_target_address( |
252 | | const char *unit, |
253 | | const char *filename, |
254 | | unsigned line, |
255 | | const char *section, |
256 | | unsigned section_line, |
257 | | const char *lvalue, |
258 | | int ltype, |
259 | | const char *rvalue, |
260 | | void *data, |
261 | 4.91k | void *userdata) { |
262 | | |
263 | 4.91k | assert(filename); |
264 | 4.91k | assert(lvalue); |
265 | 4.91k | assert(rvalue); |
266 | 4.91k | assert(data); |
267 | | |
268 | 4.91k | Bond *b = BOND(userdata); |
269 | 4.91k | int r; |
270 | | |
271 | 4.91k | if (isempty(rvalue)) { |
272 | 603 | b->arp_ip_targets = ordered_set_free(b->arp_ip_targets); |
273 | 603 | return 0; |
274 | 603 | } |
275 | | |
276 | 8.60k | for (const char *p = rvalue;;) { |
277 | 8.60k | _cleanup_free_ char *n = NULL; |
278 | 8.60k | union in_addr_union ip; |
279 | | |
280 | 8.60k | r = extract_first_word(&p, &n, NULL, 0); |
281 | 8.60k | if (r == -ENOMEM) |
282 | 0 | return log_oom(); |
283 | 8.60k | if (r < 0) { |
284 | 194 | log_syntax(unit, LOG_WARNING, filename, line, r, |
285 | 194 | "Failed to parse Bond ARP IP target address, ignoring assignment: %s", |
286 | 194 | rvalue); |
287 | 194 | return 0; |
288 | 194 | } |
289 | 8.41k | if (r == 0) |
290 | 4.11k | return 0; |
291 | | |
292 | 4.30k | r = in_addr_from_string(AF_INET, n, &ip); |
293 | 4.30k | if (r < 0) { |
294 | 483 | log_syntax(unit, LOG_WARNING, filename, line, r, |
295 | 483 | "Bond ARP IP target address is invalid, ignoring assignment: %s", n); |
296 | 483 | continue; |
297 | 483 | } |
298 | | |
299 | 3.81k | if (ordered_set_size(b->arp_ip_targets) >= NETDEV_BOND_ARP_TARGETS_MAX) { |
300 | 209 | log_syntax(unit, LOG_WARNING, filename, line, 0, |
301 | 209 | "Too many ARP IP targets are specified. The maximum number is %d. Ignoring assignment: %s", |
302 | 209 | NETDEV_BOND_ARP_TARGETS_MAX, n); |
303 | 209 | continue; |
304 | 209 | } |
305 | | |
306 | 3.60k | r = ordered_set_ensure_put(&b->arp_ip_targets, NULL, UINT32_TO_PTR(ip.in.s_addr)); |
307 | 3.60k | if (r == -ENOMEM) |
308 | 0 | return log_oom(); |
309 | 3.60k | if (r == -EEXIST) |
310 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
311 | 3.60k | "Bond ARP IP target address is duplicated, ignoring assignment: %s", n); |
312 | 3.60k | if (r < 0) |
313 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
314 | 3.60k | "Failed to store bond ARP IP target address '%s', ignoring assignment: %m", n); |
315 | 3.60k | } |
316 | 4.30k | } |
317 | | |
318 | | int config_parse_ad_actor_sys_prio( |
319 | | const char *unit, |
320 | | const char *filename, |
321 | | unsigned line, |
322 | | const char *section, |
323 | | unsigned section_line, |
324 | | const char *lvalue, |
325 | | int ltype, |
326 | | const char *rvalue, |
327 | | void *data, |
328 | 400 | void *userdata) { |
329 | | |
330 | 400 | assert(filename); |
331 | 400 | assert(lvalue); |
332 | 400 | assert(rvalue); |
333 | 400 | assert(data); |
334 | | |
335 | 400 | Bond *b = ASSERT_PTR(userdata); |
336 | | |
337 | 400 | return config_parse_uint16_bounded( |
338 | 400 | unit, filename, line, section, section_line, lvalue, rvalue, |
339 | 400 | 1, UINT16_MAX, true, |
340 | 400 | &b->ad_actor_sys_prio); |
341 | 400 | } |
342 | | |
343 | | int config_parse_ad_user_port_key( |
344 | | const char *unit, |
345 | | const char *filename, |
346 | | unsigned line, |
347 | | const char *section, |
348 | | unsigned section_line, |
349 | | const char *lvalue, |
350 | | int ltype, |
351 | | const char *rvalue, |
352 | | void *data, |
353 | 396 | void *userdata) { |
354 | | |
355 | 396 | assert(filename); |
356 | 396 | assert(lvalue); |
357 | 396 | assert(rvalue); |
358 | 396 | assert(data); |
359 | | |
360 | 396 | Bond *b = ASSERT_PTR(userdata); |
361 | | |
362 | 396 | return config_parse_uint16_bounded( |
363 | 396 | unit, filename, line, section, section_line, lvalue, rvalue, |
364 | 396 | 0, 1023, /* ignoring= */ true, |
365 | 396 | &b->ad_user_port_key); |
366 | 396 | } |
367 | | |
368 | | int config_parse_ad_actor_system( |
369 | | const char *unit, |
370 | | const char *filename, |
371 | | unsigned line, |
372 | | const char *section, |
373 | | unsigned section_line, |
374 | | const char *lvalue, |
375 | | int ltype, |
376 | | const char *rvalue, |
377 | | void *data, |
378 | 785 | void *userdata) { |
379 | 785 | Bond *b = userdata; |
380 | 785 | struct ether_addr n; |
381 | 785 | int r; |
382 | | |
383 | 785 | assert(filename); |
384 | 785 | assert(lvalue); |
385 | 785 | assert(rvalue); |
386 | 785 | assert(data); |
387 | | |
388 | 785 | r = parse_ether_addr(rvalue, &n); |
389 | 785 | if (r < 0) { |
390 | 197 | log_syntax(unit, LOG_WARNING, filename, line, r, |
391 | 197 | "Not a valid MAC address %s. Ignoring assignment: %m", |
392 | 197 | rvalue); |
393 | 197 | return 0; |
394 | 197 | } |
395 | 588 | if (ether_addr_is_null(&n) || (n.ether_addr_octet[0] & 0x01)) { |
396 | 388 | log_syntax(unit, LOG_WARNING, filename, line, 0, |
397 | 388 | "Not an appropriate MAC address %s, cannot be null or multicast. Ignoring assignment.", |
398 | 388 | rvalue); |
399 | 388 | return 0; |
400 | 388 | } |
401 | | |
402 | 200 | b->ad_actor_system = n; |
403 | | |
404 | 200 | return 0; |
405 | 588 | } |
406 | | |
407 | 742 | static void bond_done(NetDev *netdev) { |
408 | 742 | Bond *b = BOND(netdev); |
409 | | |
410 | 742 | ordered_set_free(b->arp_ip_targets); |
411 | 742 | } |
412 | | |
413 | 742 | static void bond_init(NetDev *netdev) { |
414 | 742 | Bond *b = BOND(netdev); |
415 | | |
416 | 742 | b->mode = _NETDEV_BOND_MODE_INVALID; |
417 | 742 | b->xmit_hash_policy = _NETDEV_BOND_XMIT_HASH_POLICY_INVALID; |
418 | 742 | b->lacp_rate = _NETDEV_BOND_LACP_RATE_INVALID; |
419 | 742 | b->ad_select = _NETDEV_BOND_AD_SELECT_INVALID; |
420 | 742 | b->fail_over_mac = _NETDEV_BOND_FAIL_OVER_MAC_INVALID; |
421 | 742 | b->arp_validate = _NETDEV_BOND_ARP_VALIDATE_INVALID; |
422 | 742 | b->arp_all_targets = _NETDEV_BOND_ARP_ALL_TARGETS_INVALID; |
423 | 742 | b->primary_reselect = _NETDEV_BOND_PRIMARY_RESELECT_INVALID; |
424 | | |
425 | 742 | b->all_slaves_active = false; |
426 | 742 | b->tlb_dynamic_lb = -1; |
427 | | |
428 | 742 | b->resend_igmp = RESEND_IGMP_DEFAULT; |
429 | 742 | b->packets_per_slave = PACKETS_PER_SLAVE_DEFAULT; |
430 | 742 | b->num_grat_arp = GRATUITOUS_ARP_DEFAULT; |
431 | 742 | b->lp_interval = LEARNING_PACKETS_INTERVAL_MIN_SEC; |
432 | 742 | } |
433 | | |
434 | | const NetDevVTable bond_vtable = { |
435 | | .object_size = sizeof(Bond), |
436 | | .init = bond_init, |
437 | | .done = bond_done, |
438 | | .sections = NETDEV_COMMON_SECTIONS "Bond\0", |
439 | | .fill_message_create = netdev_bond_fill_message_create, |
440 | | .create_type = NETDEV_CREATE_INDEPENDENT, |
441 | | .iftype = ARPHRD_ETHER, |
442 | | .generate_mac = true, |
443 | | }; |