net/tap: add link status notification
[dpdk.git] / drivers / net / tap / tap_tcmsgs.c
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright 2017 6WIND S.A.
5  *   Copyright 2017 Mellanox.
6  *
7  *   Redistribution and use in source and binary forms, with or without
8  *   modification, are permitted provided that the following conditions
9  *   are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *     * Neither the name of 6WIND S.A. nor the names of its
18  *       contributors may be used to endorse or promote products derived
19  *       from this software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include <inttypes.h>
35 #include <linux/netlink.h>
36 #include <net/if.h>
37 #include <string.h>
38
39 #include <rte_log.h>
40 #include <tap_tcmsgs.h>
41
42 struct qdisc {
43         uint32_t handle;
44         uint32_t parent;
45 };
46
47 struct list_args {
48         int nlsk_fd;
49         uint16_t ifindex;
50         void *custom_arg;
51 };
52
53 struct qdisc_custom_arg {
54         uint32_t handle;
55         uint32_t parent;
56         uint8_t exists;
57 };
58
59 /**
60  * Initialize a netlink message with a TC header.
61  *
62  * @param[in, out] msg
63  *   The netlink message to fill.
64  * @param[in] ifindex
65  *   The netdevice ifindex where the rule will be applied.
66  * @param[in] type
67  *   The type of TC message to create (RTM_NEWTFILTER, RTM_NEWQDISC, etc.).
68  * @param[in] flags
69  *   Overrides the default netlink flags for this msg with those specified.
70  */
71 void
72 tc_init_msg(struct nlmsg *msg, uint16_t ifindex, uint16_t type, uint16_t flags)
73 {
74         struct nlmsghdr *n = &msg->nh;
75
76         n->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
77         n->nlmsg_type = type;
78         if (flags)
79                 n->nlmsg_flags = flags;
80         else
81                 n->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
82         msg->t.tcm_family = AF_UNSPEC;
83         msg->t.tcm_ifindex = ifindex;
84 }
85
86 /**
87  * Delete a specific QDISC identified by its iface, and it's handle and parent.
88  *
89  * @param[in] nlsk_fd
90  *   The netlink socket file descriptor used for communication.
91  * @param[in] ifindex
92  *   The netdevice ifindex on whom the deletion will happen.
93  * @param[in] qinfo
94  *   Additional info to identify the QDISC (handle and parent).
95  *
96  * @return
97  *   0 on success, -1 otherwise.
98  */
99 static int
100 qdisc_del(int nlsk_fd, uint16_t ifindex, struct qdisc *qinfo)
101 {
102         struct nlmsg msg;
103         int fd = 0;
104
105         tc_init_msg(&msg, ifindex, RTM_DELQDISC, 0);
106         msg.t.tcm_handle = qinfo->handle;
107         msg.t.tcm_parent = qinfo->parent;
108         /* if no netlink socket is provided, create one */
109         if (!nlsk_fd) {
110                 fd = nl_init(0);
111                 if (fd < 0) {
112                         RTE_LOG(ERR, PMD,
113                                 "Could not delete QDISC: null netlink socket\n");
114                         return -1;
115                 }
116         } else {
117                 fd = nlsk_fd;
118         }
119         if (nl_send(fd, &msg.nh) < 0)
120                 return -1;
121         if (nl_recv_ack(fd) < 0)
122                 return -1;
123         if (!nlsk_fd)
124                 return nl_final(fd);
125         return 0;
126 }
127
128 /**
129  * Add the multiqueue QDISC with MULTIQ_MAJOR_HANDLE handle.
130  *
131  * @param[in] nlsk_fd
132  *   The netlink socket file descriptor used for communication.
133  * @param[in] ifindex
134  *   The netdevice ifindex where to add the multiqueue QDISC.
135  *
136  * @return
137  *   -1 if the qdisc cannot be added, and 0 otherwise.
138  */
139 int
140 qdisc_add_multiq(int nlsk_fd, uint16_t ifindex)
141 {
142         struct tc_multiq_qopt opt;
143         struct nlmsg msg;
144
145         tc_init_msg(&msg, ifindex, RTM_NEWQDISC,
146                     NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
147         msg.t.tcm_handle = TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0);
148         msg.t.tcm_parent = TC_H_ROOT;
149         nlattr_add(&msg.nh, TCA_KIND, sizeof("multiq"), "multiq");
150         nlattr_add(&msg.nh, TCA_OPTIONS, sizeof(opt), &opt);
151         if (nl_send(nlsk_fd, &msg.nh) < 0)
152                 return -1;
153         if (nl_recv_ack(nlsk_fd) < 0)
154                 return -1;
155         return 0;
156 }
157
158 /**
159  * Add the ingress QDISC with default ffff: handle.
160  *
161  * @param[in] nlsk_fd
162  *   The netlink socket file descriptor used for communication.
163  * @param[in] ifindex
164  *   The netdevice ifindex where the QDISC will be added.
165  *
166  * @return
167  *   -1 if the qdisc cannot be added, and 0 otherwise.
168  */
169 int
170 qdisc_add_ingress(int nlsk_fd, uint16_t ifindex)
171 {
172         struct nlmsg msg;
173
174         tc_init_msg(&msg, ifindex, RTM_NEWQDISC,
175                     NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
176         msg.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
177         msg.t.tcm_parent = TC_H_INGRESS;
178         nlattr_add(&msg.nh, TCA_KIND, sizeof("ingress"), "ingress");
179         if (nl_send(nlsk_fd, &msg.nh) < 0)
180                 return -1;
181         if (nl_recv_ack(nlsk_fd) < 0)
182                 return -1;
183         return 0;
184 }
185
186 /**
187  * Callback function to check for QDISC existence.
188  * If the QDISC is found to exist, increment "exists" in the custom arg.
189  *
190  * @param[in] nh
191  *   The netlink message to parse, received from the kernel.
192  * @param[in, out] arg
193  *   Custom arguments for the callback.
194  *
195  * @return
196  *   0.
197  */
198 static int
199 qdisc_exist_cb(struct nlmsghdr *nh, void *arg)
200 {
201         struct list_args *args = (struct list_args *)arg;
202         struct qdisc_custom_arg *custom = args->custom_arg;
203         struct tcmsg *t = NLMSG_DATA(nh);
204
205         /* filter by request iface */
206         if (args->ifindex != (unsigned int)t->tcm_ifindex)
207                 return 0;
208         if (t->tcm_handle != custom->handle || t->tcm_parent != custom->parent)
209                 return 0;
210         custom->exists++;
211         return 0;
212 }
213
214 /**
215  * Callback function to delete a QDISC.
216  *
217  * @param[in] nh
218  *   The netlink message to parse, received from the kernel.
219  * @param[in] arg
220  *   Custom arguments for the callback.
221  *
222  * @return
223  *   0.
224  */
225 static int
226 qdisc_del_cb(struct nlmsghdr *nh, void *arg)
227 {
228         struct tcmsg *t = NLMSG_DATA(nh);
229         struct list_args *args = arg;
230
231         struct qdisc qinfo = {
232                 .handle = t->tcm_handle,
233                 .parent = t->tcm_parent,
234         };
235
236         /* filter out other ifaces' qdiscs */
237         if (args->ifindex != (unsigned int)t->tcm_ifindex)
238                 return 0;
239         /*
240          * Use another nlsk_fd (0) to avoid tampering with the current list
241          * iteration.
242          */
243         return qdisc_del(0, args->ifindex, &qinfo);
244 }
245
246 /**
247  * Iterate over all QDISC, and call the callback() function for each.
248  *
249  * @param[in] nlsk_fd
250  *   The netlink socket file descriptor used for communication.
251  * @param[in] ifindex
252  *   The netdevice ifindex where to find QDISCs.
253  * @param[in] callback
254  *   The function to call for each QDISC.
255  * @param[in, out] arg
256  *   The arguments to provide the callback function with.
257  *
258  * @return
259  *   -1 if either sending the netlink message failed, or if receiving the answer
260  *   failed, or finally if the callback returned a negative value for that
261  *   answer.
262  *   0 is returned otherwise.
263  */
264 static int
265 qdisc_iterate(int nlsk_fd, uint16_t ifindex,
266               int (*callback)(struct nlmsghdr *, void *), void *arg)
267 {
268         struct nlmsg msg;
269         struct list_args args = {
270                 .nlsk_fd = nlsk_fd,
271                 .ifindex = ifindex,
272                 .custom_arg = arg,
273         };
274
275         tc_init_msg(&msg, ifindex, RTM_GETQDISC, NLM_F_REQUEST | NLM_F_DUMP);
276         if (nl_send(nlsk_fd, &msg.nh) < 0)
277                 return -1;
278         if (nl_recv(nlsk_fd, callback, &args) < 0)
279                 return -1;
280         return 0;
281 }
282
283 /**
284  * Check whether a given QDISC already exists for the netdevice.
285  *
286  * @param[in] nlsk_fd
287  *   The netlink socket file descriptor used for communication.
288  * @param[in] ifindex
289  *   The netdevice ifindex to check QDISC existence for.
290  * @param[in] callback
291  *   The function to call for each QDISC.
292  * @param[in, out] arg
293  *   The arguments to provide the callback function with.
294  *
295  * @return
296  *   1 if the qdisc exists, 0 otherwise.
297  */
298 int
299 qdisc_exists(int nlsk_fd, uint16_t ifindex, uint32_t handle, uint32_t parent)
300 {
301         struct qdisc_custom_arg arg = {
302                 .handle = handle,
303                 .parent = parent,
304                 .exists = 0,
305         };
306
307         qdisc_iterate(nlsk_fd, ifindex, qdisc_exist_cb, &arg);
308         if (arg.exists)
309                 return 1;
310         return 0;
311 }
312
313 /**
314  * Delete all QDISCs for a given netdevice.
315  *
316  * @param[in] nlsk_fd
317  *   The netlink socket file descriptor used for communication.
318  * @param[in] ifindex
319  *   The netdevice ifindex where to find QDISCs.
320  *
321  * @return
322  *   -1 if the lookup failed, 0 otherwise.
323  */
324 int
325 qdisc_flush(int nlsk_fd, uint16_t ifindex)
326 {
327         return qdisc_iterate(nlsk_fd, ifindex, qdisc_del_cb, NULL);
328 }
329
330 /**
331  * Create the multiqueue QDISC, only if it does not exist already.
332  *
333  * @param[in] nlsk_fd
334  *   The netlink socket file descriptor used for communication.
335  * @param[in] ifindex
336  *   The netdevice ifindex where to add the multiqueue QDISC.
337  *
338  * @return
339  *   0 if the qdisc exists or if has been successfully added.
340  *   Return -1 otherwise.
341  */
342 int
343 qdisc_create_multiq(int nlsk_fd, uint16_t ifindex)
344 {
345         if (!qdisc_exists(nlsk_fd, ifindex,
346                           TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0), TC_H_ROOT)) {
347                 if (qdisc_add_multiq(nlsk_fd, ifindex) < 0) {
348                         RTE_LOG(ERR, PMD, "Could not add multiq qdisc\n");
349                         return -1;
350                 }
351         }
352         return 0;
353 }
354
355 /**
356  * Create the ingress QDISC, only if it does not exist already.
357  *
358  * @param[in] nlsk_fd
359  *   The netlink socket file descriptor used for communication.
360  * @param[in] ifindex
361  *   The netdevice ifindex where to add the ingress QDISC.
362  *
363  * @return
364  *   0 if the qdisc exists or if has been successfully added.
365  *   Return -1 otherwise.
366  */
367 int
368 qdisc_create_ingress(int nlsk_fd, uint16_t ifindex)
369 {
370         if (!qdisc_exists(nlsk_fd, ifindex,
371                           TC_H_MAKE(TC_H_INGRESS, 0), TC_H_INGRESS)) {
372                 if (qdisc_add_ingress(nlsk_fd, ifindex) < 0) {
373                         RTE_LOG(ERR, PMD, "Could not add ingress qdisc\n");
374                         return -1;
375                 }
376         }
377         return 0;
378 }