aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/tap/tap_tcmsgs.c
blob: 3c9d0366725e77757696c8dc3d1185221eb7020d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright 2017 6WIND S.A.
 * Copyright 2017 Mellanox Technologies, Ltd
 */

#include <inttypes.h>
#include <linux/netlink.h>
#include <net/if.h>
#include <string.h>

#include <rte_log.h>
#include <tap_tcmsgs.h>
#include "tap_log.h"

struct qdisc {
	uint32_t handle;
	uint32_t parent;
};

struct list_args {
	int nlsk_fd;
	uint16_t ifindex;
	void *custom_arg;
};

struct qdisc_custom_arg {
	uint32_t handle;
	uint32_t parent;
	uint8_t exists;
};

/**
 * Initialize a netlink message with a TC header.
 *
 * @param[in, out] msg
 *   The netlink message to fill.
 * @param[in] ifindex
 *   The netdevice ifindex where the rule will be applied.
 * @param[in] type
 *   The type of TC message to create (RTM_NEWTFILTER, RTM_NEWQDISC, etc.).
 * @param[in] flags
 *   Overrides the default netlink flags for this msg with those specified.
 */
void
tc_init_msg(struct nlmsg *msg, uint16_t ifindex, uint16_t type, uint16_t flags)
{
	struct nlmsghdr *n = &msg->nh;

	n->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
	n->nlmsg_type = type;
	if (flags)
		n->nlmsg_flags = flags;
	else
		n->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	msg->t.tcm_family = AF_UNSPEC;
	msg->t.tcm_ifindex = ifindex;
}

/**
 * Delete a specific QDISC identified by its iface, and it's handle and parent.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex on whom the deletion will happen.
 * @param[in] qinfo
 *   Additional info to identify the QDISC (handle and parent).
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
static int
qdisc_del(int nlsk_fd, uint16_t ifindex, struct qdisc *qinfo)
{
	struct nlmsg msg;
	int fd = 0;

	tc_init_msg(&msg, ifindex, RTM_DELQDISC, 0);
	msg.t.tcm_handle = qinfo->handle;
	msg.t.tcm_parent = qinfo->parent;
	/* if no netlink socket is provided, create one */
	if (!nlsk_fd) {
		fd = tap_nl_init(0);
		if (fd < 0) {
			TAP_LOG(ERR,
				"Could not delete QDISC: null netlink socket");
			return -1;
		}
	} else {
		fd = nlsk_fd;
	}
	if (tap_nl_send(fd, &msg.nh) < 0)
		goto error;
	if (tap_nl_recv_ack(fd) < 0)
		goto error;
	if (!nlsk_fd)
		return tap_nl_final(fd);
	return 0;
error:
	if (!nlsk_fd)
		tap_nl_final(fd);
	return -1;
}

/**
 * Add the multiqueue QDISC with MULTIQ_MAJOR_HANDLE handle.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where to add the multiqueue QDISC.
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
int
qdisc_add_multiq(int nlsk_fd, uint16_t ifindex)
{
	struct tc_multiq_qopt opt;
	struct nlmsg msg;

	tc_init_msg(&msg, ifindex, RTM_NEWQDISC,
		    NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
	msg.t.tcm_handle = TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0);
	msg.t.tcm_parent = TC_H_ROOT;
	tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("multiq"), "multiq");
	tap_nlattr_add(&msg.nh, TCA_OPTIONS, sizeof(opt), &opt);
	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
		return -1;
	if (tap_nl_recv_ack(nlsk_fd) < 0)
		return -1;
	return 0;
}

/**
 * Add the ingress QDISC with default ffff: handle.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where the QDISC will be added.
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
int
qdisc_add_ingress(int nlsk_fd, uint16_t ifindex)
{
	struct nlmsg msg;

	tc_init_msg(&msg, ifindex, RTM_NEWQDISC,
		    NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
	msg.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
	msg.t.tcm_parent = TC_H_INGRESS;
	tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("ingress"), "ingress");
	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
		return -1;
	if (tap_nl_recv_ack(nlsk_fd) < 0)
		return -1;
	return 0;
}

/**
 * Callback function to delete a QDISC.
 *
 * @param[in] nh
 *   The netlink message to parse, received from the kernel.
 * @param[in] arg
 *   Custom arguments for the callback.
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
static int
qdisc_del_cb(struct nlmsghdr *nh, void *arg)
{
	struct tcmsg *t = NLMSG_DATA(nh);
	struct list_args *args = arg;

	struct qdisc qinfo = {
		.handle = t->tcm_handle,
		.parent = t->tcm_parent,
	};

	/* filter out other ifaces' qdiscs */
	if (args->ifindex != (unsigned int)t->tcm_ifindex)
		return 0;
	/*
	 * Use another nlsk_fd (0) to avoid tampering with the current list
	 * iteration.
	 */
	return qdisc_del(0, args->ifindex, &qinfo);
}

/**
 * Iterate over all QDISC, and call the callback() function for each.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where to find QDISCs.
 * @param[in] callback
 *   The function to call for each QDISC.
 * @param[in, out] arg
 *   The arguments to provide the callback function with.
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
static int
qdisc_iterate(int nlsk_fd, uint16_t ifindex,
	      int (*callback)(struct nlmsghdr *, void *), void *arg)
{
	struct nlmsg msg;
	struct list_args args = {
		.nlsk_fd = nlsk_fd,
		.ifindex = ifindex,
		.custom_arg = arg,
	};

	tc_init_msg(&msg, ifindex, RTM_GETQDISC, NLM_F_REQUEST | NLM_F_DUMP);
	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
		return -1;
	if (tap_nl_recv(nlsk_fd, callback, &args) < 0)
		return -1;
	return 0;
}

/**
 * Delete all QDISCs for a given netdevice.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where to find QDISCs.
 *
 * @return
 *   0 on success, -1 otherwise with errno set.
 */
int
qdisc_flush(int nlsk_fd, uint16_t ifindex)
{
	return qdisc_iterate(nlsk_fd, ifindex, qdisc_del_cb, NULL);
}

/**
 * Create the multiqueue QDISC, only if it does not exist already.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where to add the multiqueue QDISC.
 *
 * @return
 *   0 if the qdisc exists or if has been successfully added.
 *   Return -1 otherwise.
 */
int
qdisc_create_multiq(int nlsk_fd, uint16_t ifindex)
{
	int err = 0;

	err = qdisc_add_multiq(nlsk_fd, ifindex);
	if (err < 0 && errno != -EEXIST) {
		TAP_LOG(ERR, "Could not add multiq qdisc (%d): %s",
			errno, strerror(errno));
		return -1;
	}
	return 0;
}

/**
 * Create the ingress QDISC, only if it does not exist already.
 *
 * @param[in] nlsk_fd
 *   The netlink socket file descriptor used for communication.
 * @param[in] ifindex
 *   The netdevice ifindex where to add the ingress QDISC.
 *
 * @return
 *   0 if the qdisc exists or if has been successfully added.
 *   Return -1 otherwise.
 */
int
qdisc_create_ingress(int nlsk_fd, uint16_t ifindex)
{
	int err = 0;

	err = qdisc_add_ingress(nlsk_fd, ifindex);
	if (err < 0 && errno != -EEXIST) {
		TAP_LOG(ERR, "Could not add ingress qdisc (%d): %s",
			errno, strerror(errno));
		return -1;
	}
	return 0;
}