Commit | Line | Data |
---|---|---|
43a0c675 TH |
1 | /* |
2 | * Stream Parser | |
3 | * | |
4 | * Copyright (c) 2016 Tom Herbert <tom@herbertland.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 | |
8 | * as published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/bpf.h> | |
12 | #include <linux/errno.h> | |
13 | #include <linux/errqueue.h> | |
14 | #include <linux/file.h> | |
15 | #include <linux/in.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/net.h> | |
19 | #include <linux/netdevice.h> | |
20 | #include <linux/poll.h> | |
21 | #include <linux/rculist.h> | |
22 | #include <linux/skbuff.h> | |
23 | #include <linux/socket.h> | |
24 | #include <linux/uaccess.h> | |
25 | #include <linux/workqueue.h> | |
26 | #include <net/strparser.h> | |
27 | #include <net/netns/generic.h> | |
28 | #include <net/sock.h> | |
43a0c675 TH |
29 | |
30 | static struct workqueue_struct *strp_wq; | |
31 | ||
bbb03029 TH |
32 | struct _strp_msg { |
33 | /* Internal cb structure. struct strp_msg must be first for passing | |
43a0c675 TH |
34 | * to upper layer. |
35 | */ | |
bbb03029 | 36 | struct strp_msg strp; |
43a0c675 TH |
37 | int accum_len; |
38 | int early_eaten; | |
39 | }; | |
40 | ||
bbb03029 | 41 | static inline struct _strp_msg *_strp_msg(struct sk_buff *skb) |
43a0c675 | 42 | { |
bbb03029 | 43 | return (struct _strp_msg *)((void *)skb->cb + |
43a0c675 TH |
44 | offsetof(struct qdisc_skb_cb, data)); |
45 | } | |
46 | ||
47 | /* Lower lock held */ | |
bbb03029 | 48 | static void strp_abort_strp(struct strparser *strp, int err) |
43a0c675 | 49 | { |
43a0c675 TH |
50 | /* Unrecoverable error in receive */ |
51 | ||
bbb03029 | 52 | del_timer(&strp->msg_timer); |
43a0c675 | 53 | |
bbb03029 | 54 | if (strp->stopped) |
43a0c675 TH |
55 | return; |
56 | ||
bbb03029 TH |
57 | strp->stopped = 1; |
58 | ||
59 | if (strp->sk) { | |
60 | struct sock *sk = strp->sk; | |
43a0c675 | 61 | |
bbb03029 TH |
62 | /* Report an error on the lower socket */ |
63 | sk->sk_err = err; | |
64 | sk->sk_error_report(sk); | |
65 | } | |
43a0c675 TH |
66 | } |
67 | ||
bbb03029 | 68 | static void strp_start_timer(struct strparser *strp, long timeo) |
43a0c675 | 69 | { |
bbb03029 TH |
70 | if (timeo) |
71 | mod_timer(&strp->msg_timer, timeo); | |
43a0c675 TH |
72 | } |
73 | ||
74 | /* Lower lock held */ | |
75 | static void strp_parser_err(struct strparser *strp, int err, | |
76 | read_descriptor_t *desc) | |
77 | { | |
78 | desc->error = err; | |
bbb03029 TH |
79 | kfree_skb(strp->skb_head); |
80 | strp->skb_head = NULL; | |
43a0c675 TH |
81 | strp->cb.abort_parser(strp, err); |
82 | } | |
83 | ||
96a59083 TH |
84 | static inline int strp_peek_len(struct strparser *strp) |
85 | { | |
bbb03029 TH |
86 | if (strp->sk) { |
87 | struct socket *sock = strp->sk->sk_socket; | |
88 | ||
89 | return sock->ops->peek_len(sock); | |
90 | } | |
91 | ||
92 | /* If we don't have an associated socket there's nothing to peek. | |
93 | * Return int max to avoid stopping the strparser. | |
94 | */ | |
96a59083 | 95 | |
bbb03029 | 96 | return INT_MAX; |
96a59083 TH |
97 | } |
98 | ||
43a0c675 | 99 | /* Lower socket lock held */ |
bbb03029 TH |
100 | static int __strp_recv(read_descriptor_t *desc, struct sk_buff *orig_skb, |
101 | unsigned int orig_offset, size_t orig_len, | |
102 | size_t max_msg_size, long timeo) | |
43a0c675 TH |
103 | { |
104 | struct strparser *strp = (struct strparser *)desc->arg.data; | |
bbb03029 | 105 | struct _strp_msg *stm; |
43a0c675 TH |
106 | struct sk_buff *head, *skb; |
107 | size_t eaten = 0, cand_len; | |
108 | ssize_t extra; | |
109 | int err; | |
110 | bool cloned_orig = false; | |
111 | ||
bbb03029 | 112 | if (strp->paused) |
43a0c675 TH |
113 | return 0; |
114 | ||
bbb03029 | 115 | head = strp->skb_head; |
43a0c675 TH |
116 | if (head) { |
117 | /* Message already in progress */ | |
118 | ||
bbb03029 TH |
119 | stm = _strp_msg(head); |
120 | if (unlikely(stm->early_eaten)) { | |
43a0c675 | 121 | /* Already some number of bytes on the receive sock |
bbb03029 | 122 | * data saved in skb_head, just indicate they |
43a0c675 TH |
123 | * are consumed. |
124 | */ | |
bbb03029 TH |
125 | eaten = orig_len <= stm->early_eaten ? |
126 | orig_len : stm->early_eaten; | |
127 | stm->early_eaten -= eaten; | |
43a0c675 TH |
128 | |
129 | return eaten; | |
130 | } | |
131 | ||
132 | if (unlikely(orig_offset)) { | |
133 | /* Getting data with a non-zero offset when a message is | |
134 | * in progress is not expected. If it does happen, we | |
135 | * need to clone and pull since we can't deal with | |
136 | * offsets in the skbs for a message expect in the head. | |
137 | */ | |
138 | orig_skb = skb_clone(orig_skb, GFP_ATOMIC); | |
139 | if (!orig_skb) { | |
bbb03029 | 140 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
141 | desc->error = -ENOMEM; |
142 | return 0; | |
143 | } | |
144 | if (!pskb_pull(orig_skb, orig_offset)) { | |
bbb03029 | 145 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
146 | kfree_skb(orig_skb); |
147 | desc->error = -ENOMEM; | |
148 | return 0; | |
149 | } | |
150 | cloned_orig = true; | |
151 | orig_offset = 0; | |
152 | } | |
153 | ||
bbb03029 | 154 | if (!strp->skb_nextp) { |
43a0c675 TH |
155 | /* We are going to append to the frags_list of head. |
156 | * Need to unshare the frag_list. | |
157 | */ | |
158 | err = skb_unclone(head, GFP_ATOMIC); | |
159 | if (err) { | |
bbb03029 | 160 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
161 | desc->error = err; |
162 | return 0; | |
163 | } | |
164 | ||
165 | if (unlikely(skb_shinfo(head)->frag_list)) { | |
166 | /* We can't append to an sk_buff that already | |
167 | * has a frag_list. We create a new head, point | |
168 | * the frag_list of that to the old head, and | |
169 | * then are able to use the old head->next for | |
170 | * appending to the message. | |
171 | */ | |
172 | if (WARN_ON(head->next)) { | |
173 | desc->error = -EINVAL; | |
174 | return 0; | |
175 | } | |
176 | ||
177 | skb = alloc_skb(0, GFP_ATOMIC); | |
178 | if (!skb) { | |
bbb03029 | 179 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
180 | desc->error = -ENOMEM; |
181 | return 0; | |
182 | } | |
183 | skb->len = head->len; | |
184 | skb->data_len = head->len; | |
185 | skb->truesize = head->truesize; | |
bbb03029 TH |
186 | *_strp_msg(skb) = *_strp_msg(head); |
187 | strp->skb_nextp = &head->next; | |
43a0c675 | 188 | skb_shinfo(skb)->frag_list = head; |
bbb03029 | 189 | strp->skb_head = skb; |
43a0c675 TH |
190 | head = skb; |
191 | } else { | |
bbb03029 | 192 | strp->skb_nextp = |
43a0c675 TH |
193 | &skb_shinfo(head)->frag_list; |
194 | } | |
195 | } | |
196 | } | |
197 | ||
198 | while (eaten < orig_len) { | |
199 | /* Always clone since we will consume something */ | |
200 | skb = skb_clone(orig_skb, GFP_ATOMIC); | |
201 | if (!skb) { | |
bbb03029 | 202 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
203 | desc->error = -ENOMEM; |
204 | break; | |
205 | } | |
206 | ||
207 | cand_len = orig_len - eaten; | |
208 | ||
bbb03029 | 209 | head = strp->skb_head; |
43a0c675 TH |
210 | if (!head) { |
211 | head = skb; | |
bbb03029 TH |
212 | strp->skb_head = head; |
213 | /* Will set skb_nextp on next packet if needed */ | |
214 | strp->skb_nextp = NULL; | |
215 | stm = _strp_msg(head); | |
216 | memset(stm, 0, sizeof(*stm)); | |
217 | stm->strp.offset = orig_offset + eaten; | |
43a0c675 TH |
218 | } else { |
219 | /* Unclone since we may be appending to an skb that we | |
220 | * already share a frag_list with. | |
221 | */ | |
222 | err = skb_unclone(skb, GFP_ATOMIC); | |
223 | if (err) { | |
bbb03029 | 224 | STRP_STATS_INCR(strp->stats.mem_fail); |
43a0c675 TH |
225 | desc->error = err; |
226 | break; | |
227 | } | |
228 | ||
bbb03029 TH |
229 | stm = _strp_msg(head); |
230 | *strp->skb_nextp = skb; | |
231 | strp->skb_nextp = &skb->next; | |
43a0c675 TH |
232 | head->data_len += skb->len; |
233 | head->len += skb->len; | |
234 | head->truesize += skb->truesize; | |
235 | } | |
236 | ||
bbb03029 | 237 | if (!stm->strp.full_len) { |
43a0c675 TH |
238 | ssize_t len; |
239 | ||
240 | len = (*strp->cb.parse_msg)(strp, head); | |
241 | ||
242 | if (!len) { | |
243 | /* Need more header to determine length */ | |
bbb03029 | 244 | if (!stm->accum_len) { |
43a0c675 | 245 | /* Start RX timer for new message */ |
bbb03029 | 246 | strp_start_timer(strp, timeo); |
43a0c675 | 247 | } |
bbb03029 | 248 | stm->accum_len += cand_len; |
43a0c675 | 249 | eaten += cand_len; |
bbb03029 | 250 | STRP_STATS_INCR(strp->stats.need_more_hdr); |
43a0c675 TH |
251 | WARN_ON(eaten != orig_len); |
252 | break; | |
253 | } else if (len < 0) { | |
bbb03029 | 254 | if (len == -ESTRPIPE && stm->accum_len) { |
43a0c675 | 255 | len = -ENODATA; |
bbb03029 | 256 | strp->unrecov_intr = 1; |
43a0c675 | 257 | } else { |
bbb03029 | 258 | strp->interrupted = 1; |
43a0c675 | 259 | } |
6d3a4c40 | 260 | strp_parser_err(strp, len, desc); |
43a0c675 | 261 | break; |
bbb03029 | 262 | } else if (len > max_msg_size) { |
43a0c675 | 263 | /* Message length exceeds maximum allowed */ |
bbb03029 | 264 | STRP_STATS_INCR(strp->stats.msg_too_big); |
43a0c675 TH |
265 | strp_parser_err(strp, -EMSGSIZE, desc); |
266 | break; | |
267 | } else if (len <= (ssize_t)head->len - | |
bbb03029 | 268 | skb->len - stm->strp.offset) { |
43a0c675 TH |
269 | /* Length must be into new skb (and also |
270 | * greater than zero) | |
271 | */ | |
bbb03029 | 272 | STRP_STATS_INCR(strp->stats.bad_hdr_len); |
43a0c675 TH |
273 | strp_parser_err(strp, -EPROTO, desc); |
274 | break; | |
275 | } | |
276 | ||
bbb03029 | 277 | stm->strp.full_len = len; |
43a0c675 TH |
278 | } |
279 | ||
bbb03029 TH |
280 | extra = (ssize_t)(stm->accum_len + cand_len) - |
281 | stm->strp.full_len; | |
43a0c675 TH |
282 | |
283 | if (extra < 0) { | |
284 | /* Message not complete yet. */ | |
bbb03029 | 285 | if (stm->strp.full_len - stm->accum_len > |
96a59083 | 286 | strp_peek_len(strp)) { |
bbb03029 TH |
287 | /* Don't have the whole message in the socket |
288 | * buffer. Set strp->need_bytes to wait for | |
43a0c675 TH |
289 | * the rest of the message. Also, set "early |
290 | * eaten" since we've already buffered the skb | |
96a59083 | 291 | * but don't consume yet per strp_read_sock. |
43a0c675 TH |
292 | */ |
293 | ||
bbb03029 | 294 | if (!stm->accum_len) { |
43a0c675 | 295 | /* Start RX timer for new message */ |
bbb03029 | 296 | strp_start_timer(strp, timeo); |
43a0c675 TH |
297 | } |
298 | ||
bbb03029 TH |
299 | strp->need_bytes = stm->strp.full_len - |
300 | stm->accum_len; | |
301 | stm->accum_len += cand_len; | |
302 | stm->early_eaten = cand_len; | |
303 | STRP_STATS_ADD(strp->stats.bytes, cand_len); | |
43a0c675 TH |
304 | desc->count = 0; /* Stop reading socket */ |
305 | break; | |
306 | } | |
bbb03029 | 307 | stm->accum_len += cand_len; |
43a0c675 TH |
308 | eaten += cand_len; |
309 | WARN_ON(eaten != orig_len); | |
310 | break; | |
311 | } | |
312 | ||
313 | /* Positive extra indicates ore bytes than needed for the | |
314 | * message | |
315 | */ | |
316 | ||
317 | WARN_ON(extra > cand_len); | |
318 | ||
319 | eaten += (cand_len - extra); | |
320 | ||
321 | /* Hurray, we have a new message! */ | |
bbb03029 TH |
322 | del_timer(&strp->msg_timer); |
323 | strp->skb_head = NULL; | |
324 | STRP_STATS_INCR(strp->stats.msgs); | |
43a0c675 TH |
325 | |
326 | /* Give skb to upper layer */ | |
327 | strp->cb.rcv_msg(strp, head); | |
328 | ||
bbb03029 | 329 | if (unlikely(strp->paused)) { |
43a0c675 TH |
330 | /* Upper layer paused strp */ |
331 | break; | |
332 | } | |
333 | } | |
334 | ||
335 | if (cloned_orig) | |
336 | kfree_skb(orig_skb); | |
337 | ||
bbb03029 | 338 | STRP_STATS_ADD(strp->stats.bytes, eaten); |
43a0c675 TH |
339 | |
340 | return eaten; | |
341 | } | |
342 | ||
bbb03029 TH |
343 | int strp_process(struct strparser *strp, struct sk_buff *orig_skb, |
344 | unsigned int orig_offset, size_t orig_len, | |
345 | size_t max_msg_size, long timeo) | |
346 | { | |
347 | read_descriptor_t desc; /* Dummy arg to strp_recv */ | |
348 | ||
349 | desc.arg.data = strp; | |
350 | ||
351 | return __strp_recv(&desc, orig_skb, orig_offset, orig_len, | |
352 | max_msg_size, timeo); | |
353 | } | |
354 | EXPORT_SYMBOL_GPL(strp_process); | |
355 | ||
356 | static int strp_recv(read_descriptor_t *desc, struct sk_buff *orig_skb, | |
357 | unsigned int orig_offset, size_t orig_len) | |
358 | { | |
359 | struct strparser *strp = (struct strparser *)desc->arg.data; | |
360 | ||
361 | return __strp_recv(desc, orig_skb, orig_offset, orig_len, | |
362 | strp->sk->sk_rcvbuf, strp->sk->sk_rcvtimeo); | |
363 | } | |
364 | ||
43a0c675 TH |
365 | static int default_read_sock_done(struct strparser *strp, int err) |
366 | { | |
367 | return err; | |
368 | } | |
369 | ||
370 | /* Called with lock held on lower socket */ | |
96a59083 | 371 | static int strp_read_sock(struct strparser *strp) |
43a0c675 | 372 | { |
96a59083 | 373 | struct socket *sock = strp->sk->sk_socket; |
43a0c675 TH |
374 | read_descriptor_t desc; |
375 | ||
376 | desc.arg.data = strp; | |
377 | desc.error = 0; | |
378 | desc.count = 1; /* give more than one skb per call */ | |
379 | ||
96a59083 TH |
380 | /* sk should be locked here, so okay to do read_sock */ |
381 | sock->ops->read_sock(strp->sk, &desc, strp_recv); | |
43a0c675 TH |
382 | |
383 | desc.error = strp->cb.read_sock_done(strp, desc.error); | |
384 | ||
385 | return desc.error; | |
386 | } | |
387 | ||
388 | /* Lower sock lock held */ | |
96a59083 | 389 | void strp_data_ready(struct strparser *strp) |
43a0c675 | 390 | { |
bbb03029 | 391 | if (unlikely(strp->stopped)) |
43a0c675 TH |
392 | return; |
393 | ||
bbb03029 TH |
394 | /* This check is needed to synchronize with do_strp_work. |
395 | * do_strp_work acquires a process lock (lock_sock) whereas | |
43a0c675 TH |
396 | * the lock held here is bh_lock_sock. The two locks can be |
397 | * held by different threads at the same time, but bh_lock_sock | |
398 | * allows a thread in BH context to safely check if the process | |
399 | * lock is held. In this case, if the lock is held, queue work. | |
400 | */ | |
96a59083 | 401 | if (sock_owned_by_user(strp->sk)) { |
bbb03029 | 402 | queue_work(strp_wq, &strp->work); |
43a0c675 TH |
403 | return; |
404 | } | |
405 | ||
bbb03029 | 406 | if (strp->paused) |
43a0c675 TH |
407 | return; |
408 | ||
bbb03029 TH |
409 | if (strp->need_bytes) { |
410 | if (strp_peek_len(strp) >= strp->need_bytes) | |
411 | strp->need_bytes = 0; | |
43a0c675 TH |
412 | else |
413 | return; | |
414 | } | |
415 | ||
96a59083 | 416 | if (strp_read_sock(strp) == -ENOMEM) |
bbb03029 | 417 | queue_work(strp_wq, &strp->work); |
43a0c675 | 418 | } |
96a59083 | 419 | EXPORT_SYMBOL_GPL(strp_data_ready); |
43a0c675 | 420 | |
bbb03029 | 421 | static void do_strp_work(struct strparser *strp) |
43a0c675 TH |
422 | { |
423 | read_descriptor_t rd_desc; | |
43a0c675 | 424 | |
96a59083 TH |
425 | /* We need the read lock to synchronize with strp_data_ready. We |
426 | * need the socket lock for calling strp_read_sock. | |
43a0c675 | 427 | */ |
bbb03029 | 428 | strp->cb.lock(strp); |
43a0c675 | 429 | |
bbb03029 | 430 | if (unlikely(strp->stopped)) |
43a0c675 TH |
431 | goto out; |
432 | ||
bbb03029 | 433 | if (strp->paused) |
43a0c675 TH |
434 | goto out; |
435 | ||
436 | rd_desc.arg.data = strp; | |
437 | ||
96a59083 | 438 | if (strp_read_sock(strp) == -ENOMEM) |
bbb03029 | 439 | queue_work(strp_wq, &strp->work); |
43a0c675 TH |
440 | |
441 | out: | |
bbb03029 | 442 | strp->cb.unlock(strp); |
43a0c675 TH |
443 | } |
444 | ||
bbb03029 | 445 | static void strp_work(struct work_struct *w) |
43a0c675 | 446 | { |
bbb03029 | 447 | do_strp_work(container_of(w, struct strparser, work)); |
43a0c675 TH |
448 | } |
449 | ||
bbb03029 | 450 | static void strp_msg_timeout(unsigned long arg) |
43a0c675 TH |
451 | { |
452 | struct strparser *strp = (struct strparser *)arg; | |
453 | ||
454 | /* Message assembly timed out */ | |
bbb03029 TH |
455 | STRP_STATS_INCR(strp->stats.msg_timeouts); |
456 | strp->cb.lock(strp); | |
43a0c675 | 457 | strp->cb.abort_parser(strp, ETIMEDOUT); |
bbb03029 TH |
458 | strp->cb.unlock(strp); |
459 | } | |
460 | ||
461 | static void strp_sock_lock(struct strparser *strp) | |
462 | { | |
463 | lock_sock(strp->sk); | |
464 | } | |
465 | ||
466 | static void strp_sock_unlock(struct strparser *strp) | |
467 | { | |
43a0c675 TH |
468 | release_sock(strp->sk); |
469 | } | |
470 | ||
bbb03029 | 471 | int strp_init(struct strparser *strp, struct sock *sk, |
43a0c675 TH |
472 | struct strp_callbacks *cb) |
473 | { | |
96a59083 | 474 | |
43a0c675 TH |
475 | if (!cb || !cb->rcv_msg || !cb->parse_msg) |
476 | return -EINVAL; | |
477 | ||
bbb03029 TH |
478 | /* The sk (sock) arg determines the mode of the stream parser. |
479 | * | |
480 | * If the sock is set then the strparser is in receive callback mode. | |
481 | * The upper layer calls strp_data_ready to kick receive processing | |
482 | * and strparser calls the read_sock function on the socket to | |
483 | * get packets. | |
484 | * | |
485 | * If the sock is not set then the strparser is in general mode. | |
486 | * The upper layer calls strp_process for each skb to be parsed. | |
487 | */ | |
96a59083 | 488 | |
bbb03029 TH |
489 | if (sk) { |
490 | struct socket *sock = sk->sk_socket; | |
43a0c675 | 491 | |
bbb03029 TH |
492 | if (!sock->ops->read_sock || !sock->ops->peek_len) |
493 | return -EAFNOSUPPORT; | |
494 | } else { | |
495 | if (!cb->lock || !cb->unlock) | |
496 | return -EINVAL; | |
497 | } | |
43a0c675 | 498 | |
bbb03029 | 499 | memset(strp, 0, sizeof(*strp)); |
43a0c675 | 500 | |
bbb03029 | 501 | strp->sk = sk; |
43a0c675 | 502 | |
bbb03029 TH |
503 | strp->cb.lock = cb->lock ? : strp_sock_lock; |
504 | strp->cb.unlock = cb->unlock ? : strp_sock_unlock; | |
43a0c675 TH |
505 | strp->cb.rcv_msg = cb->rcv_msg; |
506 | strp->cb.parse_msg = cb->parse_msg; | |
507 | strp->cb.read_sock_done = cb->read_sock_done ? : default_read_sock_done; | |
bbb03029 TH |
508 | strp->cb.abort_parser = cb->abort_parser ? : strp_abort_strp; |
509 | ||
510 | setup_timer(&strp->msg_timer, strp_msg_timeout, | |
511 | (unsigned long)strp); | |
512 | ||
513 | INIT_WORK(&strp->work, strp_work); | |
43a0c675 TH |
514 | |
515 | return 0; | |
516 | } | |
517 | EXPORT_SYMBOL_GPL(strp_init); | |
518 | ||
cff6a334 TH |
519 | void strp_unpause(struct strparser *strp) |
520 | { | |
bbb03029 | 521 | strp->paused = 0; |
cff6a334 | 522 | |
bbb03029 | 523 | /* Sync setting paused with RX work */ |
cff6a334 TH |
524 | smp_mb(); |
525 | ||
bbb03029 | 526 | queue_work(strp_wq, &strp->work); |
cff6a334 TH |
527 | } |
528 | EXPORT_SYMBOL_GPL(strp_unpause); | |
529 | ||
96a59083 | 530 | /* strp must already be stopped so that strp_recv will no longer be called. |
43a0c675 TH |
531 | * Note that strp_done is not called with the lower socket held. |
532 | */ | |
533 | void strp_done(struct strparser *strp) | |
534 | { | |
bbb03029 | 535 | WARN_ON(!strp->stopped); |
43a0c675 | 536 | |
bbb03029 TH |
537 | del_timer_sync(&strp->msg_timer); |
538 | cancel_work_sync(&strp->work); | |
43a0c675 | 539 | |
bbb03029 TH |
540 | if (strp->skb_head) { |
541 | kfree_skb(strp->skb_head); | |
542 | strp->skb_head = NULL; | |
43a0c675 TH |
543 | } |
544 | } | |
545 | EXPORT_SYMBOL_GPL(strp_done); | |
546 | ||
547 | void strp_stop(struct strparser *strp) | |
548 | { | |
bbb03029 | 549 | strp->stopped = 1; |
43a0c675 TH |
550 | } |
551 | EXPORT_SYMBOL_GPL(strp_stop); | |
552 | ||
553 | void strp_check_rcv(struct strparser *strp) | |
554 | { | |
bbb03029 | 555 | queue_work(strp_wq, &strp->work); |
43a0c675 TH |
556 | } |
557 | EXPORT_SYMBOL_GPL(strp_check_rcv); | |
558 | ||
559 | static int __init strp_mod_init(void) | |
560 | { | |
561 | strp_wq = create_singlethread_workqueue("kstrp"); | |
562 | ||
563 | return 0; | |
564 | } | |
565 | ||
566 | static void __exit strp_mod_exit(void) | |
567 | { | |
f78ef7cd | 568 | destroy_workqueue(strp_wq); |
43a0c675 TH |
569 | } |
570 | module_init(strp_mod_init); | |
571 | module_exit(strp_mod_exit); | |
572 | MODULE_LICENSE("GPL"); |