inqueue.c 7.04 KB
Newer Older
1
/* SCTP kernel implementation
Linus Torvalds's avatar
Linus Torvalds committed
2
3
4
 * Copyright (c) 1999-2000 Cisco, Inc.
 * Copyright (c) 1999-2001 Motorola, Inc.
 * Copyright (c) 2002 International Business Machines, Corp.
5
 *
6
 * This file is part of the SCTP kernel implementation
7
 *
Linus Torvalds's avatar
Linus Torvalds committed
8
9
10
11
12
 * These functions are the methods for accessing the SCTP inqueue.
 *
 * An SCTP inqueue is a queue into which you push SCTP packets
 * (which might be bundles or fragments of chunks) and out of which you
 * pop SCTP whole chunks.
13
 *
14
 * This SCTP implementation is free software;
15
 * you can redistribute it and/or modify it under the terms of
Linus Torvalds's avatar
Linus Torvalds committed
16
17
18
 * the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
19
 *
20
 * This SCTP implementation is distributed in the hope that it
Linus Torvalds's avatar
Linus Torvalds committed
21
22
23
24
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *                 ************************
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
25
 *
Linus Torvalds's avatar
Linus Torvalds committed
26
 * You should have received a copy of the GNU General Public License
27
28
 * along with GNU CC; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>.
29
 *
Linus Torvalds's avatar
Linus Torvalds committed
30
31
 * Please send any bug reports or fixes you make to the
 * email address(es):
32
 *    lksctp developers <linux-sctp@vger.kernel.org>
33
34
 *
 * Written or modified by:
Linus Torvalds's avatar
Linus Torvalds committed
35
36
37
38
 *    La Monte H.P. Yarroll <piggy@acm.org>
 *    Karl Knutson <karl@athena.chicago.il.us>
 */

39
40
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

Linus Torvalds's avatar
Linus Torvalds committed
41
42
43
#include <net/sctp/sctp.h>
#include <net/sctp/sm.h>
#include <linux/interrupt.h>
44
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
45
46
47
48

/* Initialize an SCTP inqueue.  */
void sctp_inq_init(struct sctp_inq *queue)
{
49
	INIT_LIST_HEAD(&queue->in_chunk_list);
Linus Torvalds's avatar
Linus Torvalds committed
50
51
52
	queue->in_progress = NULL;

	/* Create a task for delivering data.  */
David Howells's avatar
David Howells committed
53
	INIT_WORK(&queue->immediate, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
54
55
56
57
58
}

/* Release the memory associated with an SCTP inqueue.  */
void sctp_inq_free(struct sctp_inq *queue)
{
59
	struct sctp_chunk *chunk, *tmp;
Linus Torvalds's avatar
Linus Torvalds committed
60
61

	/* Empty the queue.  */
62
63
	list_for_each_entry_safe(chunk, tmp, &queue->in_chunk_list, list) {
		list_del_init(&chunk->list);
Linus Torvalds's avatar
Linus Torvalds committed
64
		sctp_chunk_free(chunk);
65
	}
Linus Torvalds's avatar
Linus Torvalds committed
66
67
68
69

	/* If there is a packet which is currently being worked on,
	 * free it as well.
	 */
70
	if (queue->in_progress) {
Linus Torvalds's avatar
Linus Torvalds committed
71
		sctp_chunk_free(queue->in_progress);
72
73
		queue->in_progress = NULL;
	}
Linus Torvalds's avatar
Linus Torvalds committed
74
75
76
77
78
}

/* Put a new packet in an SCTP inqueue.
 * We assume that packet->sctp_hdr is set and in host byte order.
 */
79
void sctp_inq_push(struct sctp_inq *q, struct sctp_chunk *chunk)
Linus Torvalds's avatar
Linus Torvalds committed
80
81
{
	/* Directly call the packet handling routine. */
82
83
84
85
	if (chunk->rcvr->dead) {
		sctp_chunk_free(chunk);
		return;
	}
Linus Torvalds's avatar
Linus Torvalds committed
86
87
88
89
90
91

	/* We are now calling this either from the soft interrupt
	 * or from the backlog processing.
	 * Eventually, we should clean up inqueue to not rely
	 * on the BH related data structures.
	 */
92
	list_add_tail(&chunk->list, &q->in_chunk_list);
93
94
	if (chunk->asoc)
		chunk->asoc->stats.ipackets++;
David Howells's avatar
David Howells committed
95
	q->immediate.func(&q->immediate);
Linus Torvalds's avatar
Linus Torvalds committed
96
97
}

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/* Peek at the next chunk on the inqeue. */
struct sctp_chunkhdr *sctp_inq_peek(struct sctp_inq *queue)
{
	struct sctp_chunk *chunk;
	sctp_chunkhdr_t *ch = NULL;

	chunk = queue->in_progress;
	/* If there is no more chunks in this packet, say so */
	if (chunk->singleton ||
	    chunk->end_of_packet ||
	    chunk->pdiscard)
		    return NULL;

	ch = (sctp_chunkhdr_t *)chunk->chunk_end;

	return ch;
}


Linus Torvalds's avatar
Linus Torvalds committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* Extract a chunk from an SCTP inqueue.
 *
 * WARNING:  If you need to put the chunk on another queue, you need to
 * make a shallow copy (clone) of it.
 */
struct sctp_chunk *sctp_inq_pop(struct sctp_inq *queue)
{
	struct sctp_chunk *chunk;
	sctp_chunkhdr_t *ch = NULL;

	/* The assumption is that we are safe to process the chunks
	 * at this time.
	 */

131
132
	chunk = queue->in_progress;
	if (chunk) {
Linus Torvalds's avatar
Linus Torvalds committed
133
134
135
136
137
138
		/* There is a packet that we have been working on.
		 * Any post processing work to do before we move on?
		 */
		if (chunk->singleton ||
		    chunk->end_of_packet ||
		    chunk->pdiscard) {
139
140
141
142
143
144
145
146
147
148
149
			if (chunk->head_skb == chunk->skb) {
				chunk->skb = skb_shinfo(chunk->skb)->frag_list;
				goto new_skb;
			}
			if (chunk->skb->next) {
				chunk->skb = chunk->skb->next;
				goto new_skb;
			}

			if (chunk->head_skb)
				chunk->skb = chunk->head_skb;
Linus Torvalds's avatar
Linus Torvalds committed
150
151
152
153
154
155
			sctp_chunk_free(chunk);
			chunk = queue->in_progress = NULL;
		} else {
			/* Nothing to do. Next chunk in the packet, please. */
			ch = (sctp_chunkhdr_t *) chunk->chunk_end;
			/* Force chunk->skb->data to chunk->chunk_end.  */
156
157
			skb_pull(chunk->skb, chunk->chunk_end - chunk->skb->data);
			/* We are guaranteed to pull a SCTP header. */
Linus Torvalds's avatar
Linus Torvalds committed
158
159
160
161
162
		}
	}

	/* Do we need to take the next packet out of the queue to process? */
	if (!chunk) {
163
164
		struct list_head *entry;

165
next_chunk:
Linus Torvalds's avatar
Linus Torvalds committed
166
		/* Is the queue empty?  */
167
168
		entry = sctp_list_dequeue(&queue->in_chunk_list);
		if (!entry)
Linus Torvalds's avatar
Linus Torvalds committed
169
170
			return NULL;

171
		chunk = list_entry(entry, struct sctp_chunk, list);
Linus Torvalds's avatar
Linus Torvalds committed
172

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
		if ((skb_shinfo(chunk->skb)->gso_type & SKB_GSO_SCTP) == SKB_GSO_SCTP) {
			/* GSO-marked skbs but without frags, handle
			 * them normally
			 */
			if (skb_shinfo(chunk->skb)->frag_list)
				chunk->head_skb = chunk->skb;

			/* skbs with "cover letter" */
			if (chunk->head_skb && chunk->skb->data_len == chunk->skb->len)
				chunk->skb = skb_shinfo(chunk->skb)->frag_list;

			if (WARN_ON(!chunk->skb)) {
				__SCTP_INC_STATS(dev_net(chunk->skb->dev), SCTP_MIB_IN_PKT_DISCARDS);
				sctp_chunk_free(chunk);
				goto next_chunk;
			}
		}

		if (chunk->asoc)
			sock_rps_save_rxhash(chunk->asoc->base.sk, chunk->skb);

194
195
		queue->in_progress = chunk;

196
new_skb:
Linus Torvalds's avatar
Linus Torvalds committed
197
198
		/* This is the first chunk in the packet.  */
		ch = (sctp_chunkhdr_t *) chunk->skb->data;
199
		chunk->singleton = 1;
200
		chunk->data_accepted = 0;
201
202
203
204
		chunk->pdiscard = 0;
		chunk->auth = 0;
		chunk->has_asconf = 0;
		chunk->end_of_packet = 0;
205
206
207
208
209
210
		if (chunk->head_skb) {
			struct sctp_input_cb
				*cb = SCTP_INPUT_CB(chunk->skb),
				*head_cb = SCTP_INPUT_CB(chunk->head_skb);

			cb->chunk = head_cb->chunk;
211
			cb->af = head_cb->af;
212
		}
Linus Torvalds's avatar
Linus Torvalds committed
213
214
	}

215
	chunk->chunk_hdr = ch;
216
	chunk->chunk_end = ((__u8 *)ch) + SCTP_PAD4(ntohs(ch->length));
Linus Torvalds's avatar
Linus Torvalds committed
217
218
219
	skb_pull(chunk->skb, sizeof(sctp_chunkhdr_t));
	chunk->subh.v = NULL; /* Subheader is no longer valid.  */

220
221
	if (chunk->chunk_end + sizeof(sctp_chunkhdr_t) <
	    skb_tail_pointer(chunk->skb)) {
Linus Torvalds's avatar
Linus Torvalds committed
222
223
		/* This is not a singleton */
		chunk->singleton = 0;
224
	} else if (chunk->chunk_end > skb_tail_pointer(chunk->skb)) {
225
226
227
		/* Discard inside state machine. */
		chunk->pdiscard = 1;
		chunk->chunk_end = skb_tail_pointer(chunk->skb);
Linus Torvalds's avatar
Linus Torvalds committed
228
229
230
231
232
233
234
	} else {
		/* We are at the end of the packet, so mark the chunk
		 * in case we need to send a SACK.
		 */
		chunk->end_of_packet = 1;
	}

235
236
237
238
	pr_debug("+++sctp_inq_pop+++ chunk:%p[%s], length:%d, skb->len:%d\n",
		 chunk, sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type)),
		 ntohs(chunk->chunk_hdr->length), chunk->skb->len);

Linus Torvalds's avatar
Linus Torvalds committed
239
240
241
242
243
244
245
246
247
248
249
	return chunk;
}

/* Set a top-half handler.
 *
 * Originally, we the top-half handler was scheduled as a BH.  We now
 * call the handler directly in sctp_inq_push() at a time that
 * we know we are lock safe.
 * The intent is that this routine will pull stuff out of the
 * inqueue and process it.
 */
David Howells's avatar
David Howells committed
250
void sctp_inq_set_th_handler(struct sctp_inq *q, work_func_t callback)
Linus Torvalds's avatar
Linus Torvalds committed
251
{
David Howells's avatar
David Howells committed
252
	INIT_WORK(&q->immediate, callback);
Linus Torvalds's avatar
Linus Torvalds committed
253
}