net/mlx5: add framework for switch flow rules
[dpdk.git] / drivers / net / mlx5 / mlx5_nl_flow.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2018 6WIND S.A.
3  * Copyright 2018 Mellanox Technologies, Ltd
4  */
5
6 #include <errno.h>
7 #include <libmnl/libmnl.h>
8 #include <linux/if_ether.h>
9 #include <linux/netlink.h>
10 #include <linux/pkt_cls.h>
11 #include <linux/pkt_sched.h>
12 #include <linux/rtnetlink.h>
13 #include <stdalign.h>
14 #include <stddef.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <sys/socket.h>
18
19 #include <rte_byteorder.h>
20 #include <rte_errno.h>
21 #include <rte_flow.h>
22
23 #include "mlx5.h"
24
25 /* Normally found in linux/netlink.h. */
26 #ifndef NETLINK_CAP_ACK
27 #define NETLINK_CAP_ACK 10
28 #endif
29
30 /* Normally found in linux/pkt_sched.h. */
31 #ifndef TC_H_MIN_INGRESS
32 #define TC_H_MIN_INGRESS 0xfff2u
33 #endif
34
35 /* Normally found in linux/pkt_cls.h. */
36 #ifndef TCA_CLS_FLAGS_SKIP_SW
37 #define TCA_CLS_FLAGS_SKIP_SW (1 << 1)
38 #endif
39 #ifndef HAVE_TCA_FLOWER_ACT
40 #define TCA_FLOWER_ACT 3
41 #endif
42 #ifndef HAVE_TCA_FLOWER_FLAGS
43 #define TCA_FLOWER_FLAGS 22
44 #endif
45
46 /** Parser state definitions for mlx5_nl_flow_trans[]. */
47 enum mlx5_nl_flow_trans {
48         INVALID,
49         BACK,
50         ATTR,
51         PATTERN,
52         ITEM_VOID,
53         ACTIONS,
54         ACTION_VOID,
55         END,
56 };
57
58 #define TRANS(...) (const enum mlx5_nl_flow_trans []){ __VA_ARGS__, INVALID, }
59
60 #define PATTERN_COMMON \
61         ITEM_VOID, ACTIONS
62 #define ACTIONS_COMMON \
63         ACTION_VOID, END
64
65 /** Parser state transitions used by mlx5_nl_flow_transpose(). */
66 static const enum mlx5_nl_flow_trans *const mlx5_nl_flow_trans[] = {
67         [INVALID] = NULL,
68         [BACK] = NULL,
69         [ATTR] = TRANS(PATTERN),
70         [PATTERN] = TRANS(PATTERN_COMMON),
71         [ITEM_VOID] = TRANS(BACK),
72         [ACTIONS] = TRANS(ACTIONS_COMMON),
73         [ACTION_VOID] = TRANS(BACK),
74         [END] = NULL,
75 };
76
77 /**
78  * Transpose flow rule description to rtnetlink message.
79  *
80  * This function transposes a flow rule description to a traffic control
81  * (TC) filter creation message ready to be sent over Netlink.
82  *
83  * Target interface is specified as the first entry of the @p ptoi table.
84  * Subsequent entries enable this function to resolve other DPDK port IDs
85  * found in the flow rule.
86  *
87  * @param[out] buf
88  *   Output message buffer. May be NULL when @p size is 0.
89  * @param size
90  *   Size of @p buf. Message may be truncated if not large enough.
91  * @param[in] ptoi
92  *   DPDK port ID to network interface index translation table. This table
93  *   is terminated by an entry with a zero ifindex value.
94  * @param[in] attr
95  *   Flow rule attributes.
96  * @param[in] pattern
97  *   Pattern specification.
98  * @param[in] actions
99  *   Associated actions.
100  * @param[out] error
101  *   Perform verbose error reporting if not NULL.
102  *
103  * @return
104  *   A positive value representing the exact size of the message in bytes
105  *   regardless of the @p size parameter on success, a negative errno value
106  *   otherwise and rte_errno is set.
107  */
108 int
109 mlx5_nl_flow_transpose(void *buf,
110                        size_t size,
111                        const struct mlx5_nl_flow_ptoi *ptoi,
112                        const struct rte_flow_attr *attr,
113                        const struct rte_flow_item *pattern,
114                        const struct rte_flow_action *actions,
115                        struct rte_flow_error *error)
116 {
117         alignas(struct nlmsghdr)
118         uint8_t buf_tmp[mnl_nlmsg_size(sizeof(struct tcmsg) + 1024)];
119         const struct rte_flow_item *item;
120         const struct rte_flow_action *action;
121         unsigned int n;
122         struct nlattr *na_flower;
123         struct nlattr *na_flower_act;
124         const enum mlx5_nl_flow_trans *trans;
125         const enum mlx5_nl_flow_trans *back;
126
127         if (!size)
128                 goto error_nobufs;
129 init:
130         item = pattern;
131         action = actions;
132         n = 0;
133         na_flower = NULL;
134         na_flower_act = NULL;
135         trans = TRANS(ATTR);
136         back = trans;
137 trans:
138         switch (trans[n++]) {
139                 struct nlmsghdr *nlh;
140                 struct tcmsg *tcm;
141
142         case INVALID:
143                 if (item->type)
144                         return rte_flow_error_set
145                                 (error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ITEM,
146                                  item, "unsupported pattern item combination");
147                 else if (action->type)
148                         return rte_flow_error_set
149                                 (error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION,
150                                  action, "unsupported action combination");
151                 return rte_flow_error_set
152                         (error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
153                          "flow rule lacks some kind of fate action");
154         case BACK:
155                 trans = back;
156                 n = 0;
157                 goto trans;
158         case ATTR:
159                 /*
160                  * Supported attributes: no groups, some priorities and
161                  * ingress only. Don't care about transfer as it is the
162                  * caller's problem.
163                  */
164                 if (attr->group)
165                         return rte_flow_error_set
166                                 (error, ENOTSUP,
167                                  RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
168                                  attr, "groups are not supported");
169                 if (attr->priority > 0xfffe)
170                         return rte_flow_error_set
171                                 (error, ENOTSUP,
172                                  RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
173                                  attr, "lowest priority level is 0xfffe");
174                 if (!attr->ingress)
175                         return rte_flow_error_set
176                                 (error, ENOTSUP,
177                                  RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
178                                  attr, "only ingress is supported");
179                 if (attr->egress)
180                         return rte_flow_error_set
181                                 (error, ENOTSUP,
182                                  RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
183                                  attr, "egress is not supported");
184                 if (size < mnl_nlmsg_size(sizeof(*tcm)))
185                         goto error_nobufs;
186                 nlh = mnl_nlmsg_put_header(buf);
187                 nlh->nlmsg_type = 0;
188                 nlh->nlmsg_flags = 0;
189                 nlh->nlmsg_seq = 0;
190                 tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(*tcm));
191                 tcm->tcm_family = AF_UNSPEC;
192                 tcm->tcm_ifindex = ptoi[0].ifindex;
193                 /*
194                  * Let kernel pick a handle by default. A predictable handle
195                  * can be set by the caller on the resulting buffer through
196                  * mlx5_nl_flow_brand().
197                  */
198                 tcm->tcm_handle = 0;
199                 tcm->tcm_parent = TC_H_MAKE(TC_H_INGRESS, TC_H_MIN_INGRESS);
200                 /*
201                  * Priority cannot be zero to prevent the kernel from
202                  * picking one automatically.
203                  */
204                 tcm->tcm_info = TC_H_MAKE((attr->priority + 1) << 16,
205                                           RTE_BE16(ETH_P_ALL));
206                 break;
207         case PATTERN:
208                 if (!mnl_attr_put_strz_check(buf, size, TCA_KIND, "flower"))
209                         goto error_nobufs;
210                 na_flower = mnl_attr_nest_start_check(buf, size, TCA_OPTIONS);
211                 if (!na_flower)
212                         goto error_nobufs;
213                 if (!mnl_attr_put_u32_check(buf, size, TCA_FLOWER_FLAGS,
214                                             TCA_CLS_FLAGS_SKIP_SW))
215                         goto error_nobufs;
216                 break;
217         case ITEM_VOID:
218                 if (item->type != RTE_FLOW_ITEM_TYPE_VOID)
219                         goto trans;
220                 ++item;
221                 break;
222         case ACTIONS:
223                 if (item->type != RTE_FLOW_ITEM_TYPE_END)
224                         goto trans;
225                 assert(na_flower);
226                 assert(!na_flower_act);
227                 na_flower_act =
228                         mnl_attr_nest_start_check(buf, size, TCA_FLOWER_ACT);
229                 if (!na_flower_act)
230                         goto error_nobufs;
231                 break;
232         case ACTION_VOID:
233                 if (action->type != RTE_FLOW_ACTION_TYPE_VOID)
234                         goto trans;
235                 ++action;
236                 break;
237         case END:
238                 if (item->type != RTE_FLOW_ITEM_TYPE_END ||
239                     action->type != RTE_FLOW_ACTION_TYPE_END)
240                         goto trans;
241                 if (na_flower_act)
242                         mnl_attr_nest_end(buf, na_flower_act);
243                 if (na_flower)
244                         mnl_attr_nest_end(buf, na_flower);
245                 nlh = buf;
246                 return nlh->nlmsg_len;
247         }
248         back = trans;
249         trans = mlx5_nl_flow_trans[trans[n - 1]];
250         n = 0;
251         goto trans;
252 error_nobufs:
253         if (buf != buf_tmp) {
254                 buf = buf_tmp;
255                 size = sizeof(buf_tmp);
256                 goto init;
257         }
258         return rte_flow_error_set
259                 (error, ENOBUFS, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
260                  "generated TC message is too large");
261 }
262
263 /**
264  * Brand rtnetlink buffer with unique handle.
265  *
266  * This handle should be unique for a given network interface to avoid
267  * collisions.
268  *
269  * @param buf
270  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
271  * @param handle
272  *   Unique 32-bit handle to use.
273  */
274 void
275 mlx5_nl_flow_brand(void *buf, uint32_t handle)
276 {
277         struct tcmsg *tcm = mnl_nlmsg_get_payload(buf);
278
279         tcm->tcm_handle = handle;
280 }
281
282 /**
283  * Send Netlink message with acknowledgment.
284  *
285  * @param nl
286  *   Libmnl socket to use.
287  * @param nlh
288  *   Message to send. This function always raises the NLM_F_ACK flag before
289  *   sending.
290  *
291  * @return
292  *   0 on success, a negative errno value otherwise and rte_errno is set.
293  */
294 static int
295 mlx5_nl_flow_nl_ack(struct mnl_socket *nl, struct nlmsghdr *nlh)
296 {
297         alignas(struct nlmsghdr)
298         uint8_t ans[mnl_nlmsg_size(sizeof(struct nlmsgerr)) +
299                     nlh->nlmsg_len - sizeof(*nlh)];
300         uint32_t seq = random();
301         int ret;
302
303         nlh->nlmsg_flags |= NLM_F_ACK;
304         nlh->nlmsg_seq = seq;
305         ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
306         if (ret != -1)
307                 ret = mnl_socket_recvfrom(nl, ans, sizeof(ans));
308         if (ret != -1)
309                 ret = mnl_cb_run
310                         (ans, ret, seq, mnl_socket_get_portid(nl), NULL, NULL);
311         if (!ret)
312                 return 0;
313         rte_errno = errno;
314         return -rte_errno;
315 }
316
317 /**
318  * Create a Netlink flow rule.
319  *
320  * @param nl
321  *   Libmnl socket to use.
322  * @param buf
323  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
324  * @param[out] error
325  *   Perform verbose error reporting if not NULL.
326  *
327  * @return
328  *   0 on success, a negative errno value otherwise and rte_errno is set.
329  */
330 int
331 mlx5_nl_flow_create(struct mnl_socket *nl, void *buf,
332                     struct rte_flow_error *error)
333 {
334         struct nlmsghdr *nlh = buf;
335
336         nlh->nlmsg_type = RTM_NEWTFILTER;
337         nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
338         if (!mlx5_nl_flow_nl_ack(nl, nlh))
339                 return 0;
340         return rte_flow_error_set
341                 (error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
342                  "netlink: failed to create TC flow rule");
343 }
344
345 /**
346  * Destroy a Netlink flow rule.
347  *
348  * @param nl
349  *   Libmnl socket to use.
350  * @param buf
351  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
352  * @param[out] error
353  *   Perform verbose error reporting if not NULL.
354  *
355  * @return
356  *   0 on success, a negative errno value otherwise and rte_errno is set.
357  */
358 int
359 mlx5_nl_flow_destroy(struct mnl_socket *nl, void *buf,
360                      struct rte_flow_error *error)
361 {
362         struct nlmsghdr *nlh = buf;
363
364         nlh->nlmsg_type = RTM_DELTFILTER;
365         nlh->nlmsg_flags = NLM_F_REQUEST;
366         if (!mlx5_nl_flow_nl_ack(nl, nlh))
367                 return 0;
368         return rte_flow_error_set
369                 (error, errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
370                  "netlink: failed to destroy TC flow rule");
371 }
372
373 /**
374  * Initialize ingress qdisc of a given network interface.
375  *
376  * @param nl
377  *   Libmnl socket of the @p NETLINK_ROUTE kind.
378  * @param ifindex
379  *   Index of network interface to initialize.
380  * @param[out] error
381  *   Perform verbose error reporting if not NULL.
382  *
383  * @return
384  *   0 on success, a negative errno value otherwise and rte_errno is set.
385  */
386 int
387 mlx5_nl_flow_init(struct mnl_socket *nl, unsigned int ifindex,
388                   struct rte_flow_error *error)
389 {
390         struct nlmsghdr *nlh;
391         struct tcmsg *tcm;
392         alignas(struct nlmsghdr)
393         uint8_t buf[mnl_nlmsg_size(sizeof(*tcm) + 128)];
394
395         /* Destroy existing ingress qdisc and everything attached to it. */
396         nlh = mnl_nlmsg_put_header(buf);
397         nlh->nlmsg_type = RTM_DELQDISC;
398         nlh->nlmsg_flags = NLM_F_REQUEST;
399         tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(*tcm));
400         tcm->tcm_family = AF_UNSPEC;
401         tcm->tcm_ifindex = ifindex;
402         tcm->tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
403         tcm->tcm_parent = TC_H_INGRESS;
404         /* Ignore errors when qdisc is already absent. */
405         if (mlx5_nl_flow_nl_ack(nl, nlh) &&
406             rte_errno != EINVAL && rte_errno != ENOENT)
407                 return rte_flow_error_set
408                         (error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
409                          NULL, "netlink: failed to remove ingress qdisc");
410         /* Create fresh ingress qdisc. */
411         nlh = mnl_nlmsg_put_header(buf);
412         nlh->nlmsg_type = RTM_NEWQDISC;
413         nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
414         tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(*tcm));
415         tcm->tcm_family = AF_UNSPEC;
416         tcm->tcm_ifindex = ifindex;
417         tcm->tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
418         tcm->tcm_parent = TC_H_INGRESS;
419         mnl_attr_put_strz_check(nlh, sizeof(buf), TCA_KIND, "ingress");
420         if (mlx5_nl_flow_nl_ack(nl, nlh))
421                 return rte_flow_error_set
422                         (error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
423                          NULL, "netlink: failed to create ingress qdisc");
424         return 0;
425 }
426
427 /**
428  * Create and configure a libmnl socket for Netlink flow rules.
429  *
430  * @return
431  *   A valid libmnl socket object pointer on success, NULL otherwise and
432  *   rte_errno is set.
433  */
434 struct mnl_socket *
435 mlx5_nl_flow_socket_create(void)
436 {
437         struct mnl_socket *nl = mnl_socket_open(NETLINK_ROUTE);
438
439         if (nl) {
440                 mnl_socket_setsockopt(nl, NETLINK_CAP_ACK, &(int){ 1 },
441                                       sizeof(int));
442                 if (!mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID))
443                         return nl;
444         }
445         rte_errno = errno;
446         if (nl)
447                 mnl_socket_close(nl);
448         return NULL;
449 }
450
451 /**
452  * Destroy a libmnl socket.
453  */
454 void
455 mlx5_nl_flow_socket_destroy(struct mnl_socket *nl)
456 {
457         mnl_socket_close(nl);
458 }