]>
Commit | Line | Data |
---|---|---|
0f5bd7fe SS |
1 | /* SPDX-License-Identifier: LGPL-2.1+ |
2 | * Copyright © 2019 VMware, Inc. */ | |
3 | ||
4 | #include <linux/pkt_sched.h> | |
5 | ||
6 | #include "alloc-util.h" | |
7 | #include "conf-parser.h" | |
8 | #include "in-addr-util.h" | |
9 | #include "netlink-util.h" | |
10 | #include "networkd-manager.h" | |
11 | #include "parse-util.h" | |
12 | #include "qdisc.h" | |
13 | #include "set.h" | |
14 | #include "string-util.h" | |
0f5bd7fe | 15 | |
e8c17dc0 | 16 | const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { |
a9a5d632 | 17 | [QDISC_KIND_CODEL] = &codel_vtable, |
7234b915 | 18 | [QDISC_KIND_FQ] = &fq_vtable, |
e8c17dc0 YW |
19 | [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, |
20 | [QDISC_KIND_NETEM] = &netem_vtable, | |
21 | [QDISC_KIND_SFQ] = &sfq_vtable, | |
22 | [QDISC_KIND_TBF] = &tbf_vtable, | |
23 | }; | |
24 | ||
25 | static int qdisc_new(QDiscKind kind, QDisc **ret) { | |
edc54f2f | 26 | QDisc *qdisc; |
ac810b75 | 27 | int r; |
0f5bd7fe | 28 | |
e8c17dc0 YW |
29 | if (kind == _QDISC_KIND_INVALID) { |
30 | qdisc = new(QDisc, 1); | |
31 | if (!qdisc) | |
32 | return -ENOMEM; | |
33 | ||
34 | *qdisc = (QDisc) { | |
35 | .family = AF_UNSPEC, | |
36 | .parent = TC_H_ROOT, | |
37 | .kind = kind, | |
38 | }; | |
39 | } else { | |
40 | qdisc = malloc0(qdisc_vtable[kind]->object_size); | |
41 | if (!qdisc) | |
42 | return -ENOMEM; | |
43 | ||
44 | qdisc->family = AF_UNSPEC; | |
45 | qdisc->parent = TC_H_ROOT; | |
46 | qdisc->kind = kind; | |
ac810b75 YW |
47 | |
48 | if (QDISC_VTABLE(qdisc)->init) { | |
49 | r = QDISC_VTABLE(qdisc)->init(qdisc); | |
50 | if (r < 0) | |
51 | return r; | |
52 | } | |
e8c17dc0 | 53 | } |
0f5bd7fe SS |
54 | |
55 | *ret = TAKE_PTR(qdisc); | |
56 | ||
57 | return 0; | |
58 | } | |
59 | ||
e8c17dc0 | 60 | int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret) { |
0f5bd7fe | 61 | _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; |
edc54f2f | 62 | _cleanup_(qdisc_freep) QDisc *qdisc = NULL; |
e8c17dc0 | 63 | QDisc *existing; |
0f5bd7fe SS |
64 | int r; |
65 | ||
66 | assert(network); | |
67 | assert(ret); | |
e8c17dc0 YW |
68 | assert(filename); |
69 | assert(section_line > 0); | |
0f5bd7fe | 70 | |
e8c17dc0 YW |
71 | r = network_config_section_new(filename, section_line, &n); |
72 | if (r < 0) | |
73 | return r; | |
0f5bd7fe | 74 | |
e8c17dc0 YW |
75 | existing = ordered_hashmap_get(network->qdiscs_by_section, n); |
76 | if (existing) { | |
77 | if (existing->kind != _QDISC_KIND_INVALID && | |
78 | kind != _QDISC_KIND_INVALID && | |
79 | existing->kind != kind) | |
80 | return -EINVAL; | |
0f5bd7fe | 81 | |
e8c17dc0 YW |
82 | if (existing->kind == kind || kind == _QDISC_KIND_INVALID) { |
83 | *ret = existing; | |
0f5bd7fe SS |
84 | return 0; |
85 | } | |
86 | } | |
87 | ||
e8c17dc0 | 88 | r = qdisc_new(kind, &qdisc); |
0f5bd7fe SS |
89 | if (r < 0) |
90 | return r; | |
91 | ||
e8c17dc0 YW |
92 | if (existing) { |
93 | qdisc->family = existing->family; | |
94 | qdisc->handle = existing->handle; | |
95 | qdisc->parent = existing->parent; | |
96 | qdisc->tca_kind = TAKE_PTR(existing->tca_kind); | |
0f5bd7fe | 97 | |
e8c17dc0 YW |
98 | qdisc_free(ordered_hashmap_remove(network->qdiscs_by_section, n)); |
99 | } | |
0f5bd7fe | 100 | |
e8c17dc0 YW |
101 | qdisc->network = network; |
102 | qdisc->section = TAKE_PTR(n); | |
0f5bd7fe | 103 | |
e8c17dc0 YW |
104 | r = ordered_hashmap_ensure_allocated(&network->qdiscs_by_section, &network_config_hash_ops); |
105 | if (r < 0) | |
106 | return r; | |
0f5bd7fe | 107 | |
e8c17dc0 YW |
108 | r = ordered_hashmap_put(network->qdiscs_by_section, qdisc->section, qdisc); |
109 | if (r < 0) | |
110 | return r; | |
0f5bd7fe | 111 | |
e8c17dc0 | 112 | *ret = TAKE_PTR(qdisc); |
0f5bd7fe SS |
113 | return 0; |
114 | } | |
115 | ||
edc54f2f | 116 | void qdisc_free(QDisc *qdisc) { |
0f5bd7fe SS |
117 | if (!qdisc) |
118 | return; | |
119 | ||
120 | if (qdisc->network && qdisc->section) | |
121 | ordered_hashmap_remove(qdisc->network->qdiscs_by_section, qdisc->section); | |
122 | ||
123 | network_config_section_free(qdisc->section); | |
124 | ||
d8081020 | 125 | free(qdisc->tca_kind); |
0f5bd7fe SS |
126 | free(qdisc); |
127 | } | |
128 | ||
129 | static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { | |
130 | int r; | |
131 | ||
132 | assert(link); | |
133 | assert(link->qdisc_messages > 0); | |
134 | link->qdisc_messages--; | |
135 | ||
136 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
137 | return 1; | |
138 | ||
139 | r = sd_netlink_message_get_errno(m); | |
140 | if (r < 0 && r != -EEXIST) { | |
4c272401 | 141 | log_link_message_error_errno(link, m, r, "Could not set QDisc"); |
4ecdcb07 | 142 | link_enter_failed(link); |
0f5bd7fe SS |
143 | return 1; |
144 | } | |
145 | ||
6d62ec61 | 146 | if (link->qdisc_messages == 0) { |
edc54f2f | 147 | log_link_debug(link, "QDisc configured"); |
4ecdcb07 YW |
148 | link->qdiscs_configured = true; |
149 | link_check_ready(link); | |
150 | } | |
151 | ||
0f5bd7fe SS |
152 | return 1; |
153 | } | |
154 | ||
edc54f2f | 155 | int qdisc_configure(Link *link, QDisc *qdisc) { |
0f5bd7fe SS |
156 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
157 | int r; | |
158 | ||
159 | assert(link); | |
160 | assert(link->manager); | |
161 | assert(link->manager->rtnl); | |
162 | assert(link->ifindex > 0); | |
163 | ||
164 | r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex); | |
165 | if (r < 0) | |
166 | return log_link_error_errno(link, r, "Could not create RTM_NEWQDISC message: %m"); | |
167 | ||
168 | r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent); | |
169 | if (r < 0) | |
170 | return log_link_error_errno(link, r, "Could not create tcm_parent message: %m"); | |
171 | ||
d8081020 SS |
172 | if (qdisc->handle != TC_H_UNSPEC) { |
173 | r = sd_rtnl_message_set_qdisc_handle(req, qdisc->handle); | |
0f5bd7fe SS |
174 | if (r < 0) |
175 | return log_link_error_errno(link, r, "Could not set tcm_handle message: %m"); | |
0f5bd7fe SS |
176 | } |
177 | ||
e8c17dc0 YW |
178 | if (QDISC_VTABLE(qdisc)) { |
179 | r = sd_netlink_message_append_string(req, TCA_KIND, QDISC_VTABLE(qdisc)->tca_kind); | |
0f5bd7fe | 180 | if (r < 0) |
e8c17dc0 | 181 | return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); |
4e5ef149 | 182 | |
e8c17dc0 | 183 | r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req); |
4e5ef149 SS |
184 | if (r < 0) |
185 | return r; | |
e8c17dc0 YW |
186 | } else { |
187 | r = sd_netlink_message_append_string(req, TCA_KIND, qdisc->tca_kind); | |
6edfb1f5 SS |
188 | if (r < 0) |
189 | return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); | |
190 | } | |
191 | ||
0f5bd7fe SS |
192 | r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link); |
193 | if (r < 0) | |
194 | return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); | |
195 | ||
196 | link_ref(link); | |
197 | link->qdisc_messages++; | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
edc54f2f | 202 | int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) { |
dcfc23ae | 203 | int r; |
b2340fbb | 204 | |
8efb93f0 YW |
205 | assert(qdisc); |
206 | assert(has_root); | |
207 | assert(has_clsact); | |
208 | ||
209 | if (section_is_invalid(qdisc->section)) | |
210 | return -EINVAL; | |
211 | ||
e8c17dc0 YW |
212 | if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->verify) { |
213 | r = QDISC_VTABLE(qdisc)->verify(qdisc); | |
dcfc23ae YW |
214 | if (r < 0) |
215 | return r; | |
216 | } | |
217 | ||
8efb93f0 YW |
218 | if (qdisc->parent == TC_H_ROOT) { |
219 | if (*has_root) | |
220 | return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), | |
59bae425 YW |
221 | "%s: More than one root qdisc section is defined. " |
222 | "Ignoring the qdisc section from line %u.", | |
8efb93f0 YW |
223 | qdisc->section->filename, qdisc->section->line); |
224 | *has_root = true; | |
d8081020 | 225 | } else if (qdisc->parent == TC_H_CLSACT) { /* TC_H_CLSACT == TC_H_INGRESS */ |
8efb93f0 YW |
226 | if (*has_clsact) |
227 | return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), | |
59bae425 YW |
228 | "%s: More than one clsact or ingress qdisc section is defined. " |
229 | "Ignoring the qdisc section from line %u.", | |
8efb93f0 YW |
230 | qdisc->section->filename, qdisc->section->line); |
231 | *has_clsact = true; | |
232 | } | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
18de0969 | 237 | int config_parse_qdisc_parent( |
0f5bd7fe SS |
238 | const char *unit, |
239 | const char *filename, | |
240 | unsigned line, | |
241 | const char *section, | |
242 | unsigned section_line, | |
243 | const char *lvalue, | |
244 | int ltype, | |
245 | const char *rvalue, | |
246 | void *data, | |
247 | void *userdata) { | |
248 | ||
edc54f2f | 249 | _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; |
0f5bd7fe SS |
250 | Network *network = data; |
251 | int r; | |
252 | ||
253 | assert(filename); | |
254 | assert(lvalue); | |
255 | assert(rvalue); | |
256 | assert(data); | |
257 | ||
18de0969 | 258 | r = qdisc_new_static(ltype, network, filename, section_line, &qdisc); |
0f5bd7fe SS |
259 | if (r < 0) |
260 | return r; | |
261 | ||
d8081020 | 262 | if (streq(rvalue, "root")) { |
0f5bd7fe | 263 | qdisc->parent = TC_H_ROOT; |
d8081020 SS |
264 | qdisc->handle = TC_H_UNSPEC; |
265 | } else if (streq(rvalue, "clsact")) { | |
0f5bd7fe | 266 | qdisc->parent = TC_H_CLSACT; |
d8081020 SS |
267 | qdisc->handle = TC_H_MAKE(TC_H_CLSACT, 0); |
268 | } else if (streq(rvalue, "ingress")) { | |
269 | qdisc->parent = TC_H_INGRESS; | |
270 | qdisc->handle = TC_H_MAKE(TC_H_INGRESS, 0); | |
271 | } else { | |
0f5bd7fe | 272 | log_syntax(unit, LOG_ERR, filename, line, r, |
d8081020 | 273 | "Failed to parse 'Parent=', ignoring assignment: %s", |
0f5bd7fe SS |
274 | rvalue); |
275 | return 0; | |
276 | } | |
277 | ||
d8081020 SS |
278 | if (streq(rvalue, "root")) |
279 | qdisc->tca_kind = mfree(qdisc->tca_kind); | |
280 | else { | |
281 | r = free_and_strdup(&qdisc->tca_kind, rvalue); | |
282 | if (r < 0) | |
283 | return log_oom(); | |
284 | } | |
285 | ||
0f5bd7fe SS |
286 | qdisc = NULL; |
287 | ||
288 | return 0; | |
289 | } |