]>
Commit | Line | Data |
---|---|---|
fdccce45 YH |
1 | /* |
2 | * Copyright (c) 2015 FUJITSU LIMITED | |
3 | * Author: Yang Hongyang <[email protected]> | |
4 | * | |
5 | * This work is licensed under the terms of the GNU GPL, version 2 or | |
6 | * later. See the COPYING file in the top-level directory. | |
7 | */ | |
8 | ||
2744d920 | 9 | #include "qemu/osdep.h" |
da34e65c | 10 | #include "qapi/error.h" |
fdccce45 YH |
11 | #include "qapi/qmp/qerror.h" |
12 | #include "qemu/error-report.h" | |
13 | ||
14 | #include "net/filter.h" | |
15 | #include "net/net.h" | |
16 | #include "net/vhost_net.h" | |
17 | #include "qom/object_interfaces.h" | |
7ef7bc85 | 18 | #include "qemu/iov.h" |
0b8fa32f | 19 | #include "qemu/module.h" |
5fbba3d6 ZC |
20 | #include "net/colo.h" |
21 | #include "migration/colo.h" | |
fdccce45 | 22 | |
338d3f41 HZ |
23 | static inline bool qemu_can_skip_netfilter(NetFilterState *nf) |
24 | { | |
25 | return !nf->on; | |
26 | } | |
27 | ||
e64c770d YH |
28 | ssize_t qemu_netfilter_receive(NetFilterState *nf, |
29 | NetFilterDirection direction, | |
30 | NetClientState *sender, | |
31 | unsigned flags, | |
32 | const struct iovec *iov, | |
33 | int iovcnt, | |
34 | NetPacketSent *sent_cb) | |
35 | { | |
338d3f41 HZ |
36 | if (qemu_can_skip_netfilter(nf)) { |
37 | return 0; | |
38 | } | |
e64c770d YH |
39 | if (nf->direction == direction || |
40 | nf->direction == NET_FILTER_DIRECTION_ALL) { | |
41 | return NETFILTER_GET_CLASS(OBJECT(nf))->receive_iov( | |
42 | nf, sender, flags, iov, iovcnt, sent_cb); | |
43 | } | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
25aaadf0 LZ |
48 | static NetFilterState *netfilter_next(NetFilterState *nf, |
49 | NetFilterDirection dir) | |
50 | { | |
51 | NetFilterState *next; | |
52 | ||
53 | if (dir == NET_FILTER_DIRECTION_TX) { | |
54 | /* forward walk through filters */ | |
55 | next = QTAILQ_NEXT(nf, next); | |
56 | } else { | |
57 | /* reverse order */ | |
eae3eb3e | 58 | next = QTAILQ_PREV(nf, next); |
25aaadf0 LZ |
59 | } |
60 | ||
61 | return next; | |
62 | } | |
63 | ||
7ef7bc85 YH |
64 | ssize_t qemu_netfilter_pass_to_next(NetClientState *sender, |
65 | unsigned flags, | |
66 | const struct iovec *iov, | |
67 | int iovcnt, | |
68 | void *opaque) | |
69 | { | |
70 | int ret = 0; | |
71 | int direction; | |
72 | NetFilterState *nf = opaque; | |
25aaadf0 | 73 | NetFilterState *next = NULL; |
7ef7bc85 YH |
74 | |
75 | if (!sender || !sender->peer) { | |
76 | /* no receiver, or sender been deleted, no need to pass it further */ | |
77 | goto out; | |
78 | } | |
79 | ||
80 | if (nf->direction == NET_FILTER_DIRECTION_ALL) { | |
81 | if (sender == nf->netdev) { | |
82 | /* This packet is sent by netdev itself */ | |
83 | direction = NET_FILTER_DIRECTION_TX; | |
84 | } else { | |
85 | direction = NET_FILTER_DIRECTION_RX; | |
86 | } | |
87 | } else { | |
88 | direction = nf->direction; | |
89 | } | |
90 | ||
25aaadf0 | 91 | next = netfilter_next(nf, direction); |
7ef7bc85 YH |
92 | while (next) { |
93 | /* | |
94 | * if qemu_netfilter_pass_to_next been called, means that | |
95 | * the packet has been hold by filter and has already retured size | |
96 | * to the sender, so sent_cb shouldn't be called later, just | |
97 | * pass NULL to next. | |
98 | */ | |
99 | ret = qemu_netfilter_receive(next, direction, sender, flags, iov, | |
100 | iovcnt, NULL); | |
101 | if (ret) { | |
102 | return ret; | |
103 | } | |
25aaadf0 | 104 | next = netfilter_next(next, direction); |
7ef7bc85 YH |
105 | } |
106 | ||
107 | /* | |
108 | * We have gone through all filters, pass it to receiver. | |
109 | * Do the valid check again incase sender or receiver been | |
110 | * deleted while we go through filters. | |
111 | */ | |
112 | if (sender && sender->peer) { | |
113 | qemu_net_queue_send_iov(sender->peer->incoming_queue, | |
114 | sender, flags, iov, iovcnt, NULL); | |
115 | } | |
116 | ||
117 | out: | |
118 | /* no receiver, or sender been deleted */ | |
119 | return iov_size(iov, iovcnt); | |
120 | } | |
121 | ||
fdccce45 YH |
122 | static char *netfilter_get_netdev_id(Object *obj, Error **errp) |
123 | { | |
124 | NetFilterState *nf = NETFILTER(obj); | |
125 | ||
126 | return g_strdup(nf->netdev_id); | |
127 | } | |
128 | ||
129 | static void netfilter_set_netdev_id(Object *obj, const char *str, Error **errp) | |
130 | { | |
131 | NetFilterState *nf = NETFILTER(obj); | |
132 | ||
133 | nf->netdev_id = g_strdup(str); | |
134 | } | |
135 | ||
136 | static int netfilter_get_direction(Object *obj, Error **errp G_GNUC_UNUSED) | |
137 | { | |
138 | NetFilterState *nf = NETFILTER(obj); | |
139 | return nf->direction; | |
140 | } | |
141 | ||
142 | static void netfilter_set_direction(Object *obj, int direction, Error **errp) | |
143 | { | |
144 | NetFilterState *nf = NETFILTER(obj); | |
145 | nf->direction = direction; | |
146 | } | |
147 | ||
338d3f41 HZ |
148 | static char *netfilter_get_status(Object *obj, Error **errp) |
149 | { | |
150 | NetFilterState *nf = NETFILTER(obj); | |
151 | ||
152 | return nf->on ? g_strdup("on") : g_strdup("off"); | |
153 | } | |
154 | ||
155 | static void netfilter_set_status(Object *obj, const char *str, Error **errp) | |
156 | { | |
157 | NetFilterState *nf = NETFILTER(obj); | |
158 | NetFilterClass *nfc = NETFILTER_GET_CLASS(obj); | |
159 | ||
160 | if (strcmp(str, "on") && strcmp(str, "off")) { | |
161 | error_setg(errp, "Invalid value for netfilter status, " | |
162 | "should be 'on' or 'off'"); | |
163 | return; | |
164 | } | |
165 | if (nf->on == !strcmp(str, "on")) { | |
166 | return; | |
167 | } | |
168 | nf->on = !nf->on; | |
e0a039e5 | 169 | if (nf->netdev && nfc->status_changed) { |
338d3f41 HZ |
170 | nfc->status_changed(nf, errp); |
171 | } | |
172 | } | |
173 | ||
19731365 LS |
174 | static char *netfilter_get_position(Object *obj, Error **errp) |
175 | { | |
176 | NetFilterState *nf = NETFILTER(obj); | |
177 | ||
178 | return g_strdup(nf->position); | |
179 | } | |
180 | ||
181 | static void netfilter_set_position(Object *obj, const char *str, Error **errp) | |
182 | { | |
183 | NetFilterState *nf = NETFILTER(obj); | |
184 | ||
185 | nf->position = g_strdup(str); | |
186 | } | |
187 | ||
188 | static char *netfilter_get_insert(Object *obj, Error **errp) | |
189 | { | |
190 | NetFilterState *nf = NETFILTER(obj); | |
191 | ||
192 | return nf->insert_before_flag ? g_strdup("before") : g_strdup("behind"); | |
193 | } | |
194 | ||
195 | static void netfilter_set_insert(Object *obj, const char *str, Error **errp) | |
196 | { | |
197 | NetFilterState *nf = NETFILTER(obj); | |
198 | ||
199 | if (strcmp(str, "before") && strcmp(str, "behind")) { | |
200 | error_setg(errp, "Invalid value for netfilter insert, " | |
201 | "should be 'before' or 'behind'"); | |
202 | return; | |
203 | } | |
204 | ||
205 | nf->insert_before_flag = !strcmp(str, "before"); | |
206 | } | |
207 | ||
fdccce45 YH |
208 | static void netfilter_init(Object *obj) |
209 | { | |
338d3f41 HZ |
210 | NetFilterState *nf = NETFILTER(obj); |
211 | ||
212 | nf->on = true; | |
19731365 LS |
213 | nf->insert_before_flag = false; |
214 | nf->position = g_strdup("tail"); | |
338d3f41 | 215 | |
fdccce45 YH |
216 | object_property_add_str(obj, "netdev", |
217 | netfilter_get_netdev_id, netfilter_set_netdev_id, | |
218 | NULL); | |
219 | object_property_add_enum(obj, "queue", "NetFilterDirection", | |
f7abe0ec | 220 | &NetFilterDirection_lookup, |
fdccce45 YH |
221 | netfilter_get_direction, netfilter_set_direction, |
222 | NULL); | |
338d3f41 HZ |
223 | object_property_add_str(obj, "status", |
224 | netfilter_get_status, netfilter_set_status, | |
225 | NULL); | |
19731365 LS |
226 | object_property_add_str(obj, "position", |
227 | netfilter_get_position, netfilter_set_position, | |
228 | NULL); | |
229 | object_property_add_str(obj, "insert", | |
230 | netfilter_get_insert, netfilter_set_insert, | |
231 | NULL); | |
fdccce45 YH |
232 | } |
233 | ||
234 | static void netfilter_complete(UserCreatable *uc, Error **errp) | |
235 | { | |
236 | NetFilterState *nf = NETFILTER(uc); | |
19731365 | 237 | NetFilterState *position = NULL; |
fdccce45 YH |
238 | NetClientState *ncs[MAX_QUEUE_NUM]; |
239 | NetFilterClass *nfc = NETFILTER_GET_CLASS(uc); | |
240 | int queues; | |
241 | Error *local_err = NULL; | |
242 | ||
243 | if (!nf->netdev_id) { | |
244 | error_setg(errp, "Parameter 'netdev' is required"); | |
245 | return; | |
246 | } | |
247 | ||
248 | queues = qemu_find_net_clients_except(nf->netdev_id, ncs, | |
f394b2e2 | 249 | NET_CLIENT_DRIVER_NIC, |
fdccce45 YH |
250 | MAX_QUEUE_NUM); |
251 | if (queues < 1) { | |
252 | error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "netdev", | |
253 | "a network backend id"); | |
254 | return; | |
255 | } else if (queues > 1) { | |
256 | error_setg(errp, "multiqueue is not supported"); | |
257 | return; | |
258 | } | |
259 | ||
260 | if (get_vhost_net(ncs[0])) { | |
261 | error_setg(errp, "Vhost is not supported"); | |
262 | return; | |
263 | } | |
264 | ||
19731365 LS |
265 | if (strcmp(nf->position, "head") && strcmp(nf->position, "tail")) { |
266 | Object *container; | |
267 | Object *obj; | |
268 | char *position_id; | |
269 | ||
270 | if (!g_str_has_prefix(nf->position, "id=")) { | |
271 | error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "position", | |
272 | "'head', 'tail' or 'id=<id>'"); | |
273 | return; | |
274 | } | |
275 | ||
276 | /* get the id from the string */ | |
277 | position_id = g_strndup(nf->position + 3, strlen(nf->position) - 3); | |
278 | ||
279 | /* Search for the position to insert before/behind */ | |
280 | container = object_get_objects_root(); | |
281 | obj = object_resolve_path_component(container, position_id); | |
282 | if (!obj) { | |
283 | error_setg(errp, "filter '%s' not found", position_id); | |
284 | g_free(position_id); | |
285 | return; | |
286 | } | |
287 | ||
288 | position = NETFILTER(obj); | |
289 | ||
290 | if (position->netdev != ncs[0]) { | |
291 | error_setg(errp, "filter '%s' belongs to a different netdev", | |
292 | position_id); | |
293 | g_free(position_id); | |
294 | return; | |
295 | } | |
296 | ||
297 | g_free(position_id); | |
298 | } | |
299 | ||
fdccce45 YH |
300 | nf->netdev = ncs[0]; |
301 | ||
302 | if (nfc->setup) { | |
303 | nfc->setup(nf, &local_err); | |
304 | if (local_err) { | |
305 | error_propagate(errp, local_err); | |
306 | return; | |
307 | } | |
308 | } | |
19731365 LS |
309 | |
310 | if (position) { | |
311 | if (nf->insert_before_flag) { | |
312 | QTAILQ_INSERT_BEFORE(position, nf, next); | |
313 | } else { | |
314 | QTAILQ_INSERT_AFTER(&nf->netdev->filters, position, nf, next); | |
315 | } | |
316 | } else if (!strcmp(nf->position, "head")) { | |
317 | QTAILQ_INSERT_HEAD(&nf->netdev->filters, nf, next); | |
318 | } else if (!strcmp(nf->position, "tail")) { | |
319 | QTAILQ_INSERT_TAIL(&nf->netdev->filters, nf, next); | |
320 | } | |
fdccce45 YH |
321 | } |
322 | ||
323 | static void netfilter_finalize(Object *obj) | |
324 | { | |
325 | NetFilterState *nf = NETFILTER(obj); | |
326 | NetFilterClass *nfc = NETFILTER_GET_CLASS(obj); | |
327 | ||
328 | if (nfc->cleanup) { | |
329 | nfc->cleanup(nf); | |
330 | } | |
331 | ||
5dd2d45e | 332 | if (nf->netdev && !QTAILQ_EMPTY(&nf->netdev->filters) && |
3b8c1761 | 333 | QTAILQ_IN_USE(nf, next)) { |
fdccce45 YH |
334 | QTAILQ_REMOVE(&nf->netdev->filters, nf, next); |
335 | } | |
671f66f8 | 336 | g_free(nf->netdev_id); |
19731365 | 337 | g_free(nf->position); |
fdccce45 YH |
338 | } |
339 | ||
5fbba3d6 ZC |
340 | static void default_handle_event(NetFilterState *nf, int event, Error **errp) |
341 | { | |
342 | switch (event) { | |
343 | case COLO_EVENT_CHECKPOINT: | |
344 | break; | |
345 | case COLO_EVENT_FAILOVER: | |
346 | object_property_set_str(OBJECT(nf), "off", "status", errp); | |
347 | break; | |
348 | default: | |
349 | break; | |
350 | } | |
351 | } | |
352 | ||
fdccce45 YH |
353 | static void netfilter_class_init(ObjectClass *oc, void *data) |
354 | { | |
355 | UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); | |
5fbba3d6 | 356 | NetFilterClass *nfc = NETFILTER_CLASS(oc); |
fdccce45 YH |
357 | |
358 | ucc->complete = netfilter_complete; | |
5fbba3d6 | 359 | nfc->handle_event = default_handle_event; |
fdccce45 YH |
360 | } |
361 | ||
362 | static const TypeInfo netfilter_info = { | |
363 | .name = TYPE_NETFILTER, | |
364 | .parent = TYPE_OBJECT, | |
365 | .abstract = true, | |
366 | .class_size = sizeof(NetFilterClass), | |
367 | .class_init = netfilter_class_init, | |
368 | .instance_size = sizeof(NetFilterState), | |
369 | .instance_init = netfilter_init, | |
370 | .instance_finalize = netfilter_finalize, | |
371 | .interfaces = (InterfaceInfo[]) { | |
372 | { TYPE_USER_CREATABLE }, | |
373 | { } | |
374 | } | |
375 | }; | |
376 | ||
377 | static void register_types(void) | |
378 | { | |
379 | type_register_static(&netfilter_info); | |
380 | } | |
381 | ||
382 | type_init(register_types); |