chunk.c 9.25 KB
Newer Older
1
/* SCTP kernel implementation
Linus Torvalds's avatar
Linus Torvalds committed
2
3
 * (C) Copyright IBM Corp. 2003, 2004
 *
4
 * This file is part of the SCTP kernel implementation
Linus Torvalds's avatar
Linus Torvalds committed
5
 *
6
 * This file contains the code relating the chunk abstraction.
Linus Torvalds's avatar
Linus Torvalds committed
7
 *
8
 * This SCTP implementation is free software;
Linus Torvalds's avatar
Linus Torvalds committed
9
10
11
12
13
 * you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
14
 * This SCTP implementation is distributed in the hope that it
Linus Torvalds's avatar
Linus Torvalds committed
15
16
17
18
19
20
 * 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.
 *
 * You should have received a copy of the GNU General Public License
21
22
 * along with GNU CC; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>.
Linus Torvalds's avatar
Linus Torvalds committed
23
24
25
 *
 * Please send any bug reports or fixes you make to the
 * email address(es):
26
 *    lksctp developers <linux-sctp@vger.kernel.org>
Linus Torvalds's avatar
Linus Torvalds committed
27
28
29
30
31
32
 *
 * Written or modified by:
 *    Jon Grimm             <jgrimm@us.ibm.com>
 *    Sridhar Samudrala     <sri@us.ibm.com>
 */

33
34
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

Linus Torvalds's avatar
Linus Torvalds committed
35
36
37
38
39
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/skbuff.h>
40
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <net/sock.h>
#include <net/sctp/sctp.h>
#include <net/sctp/sm.h>

/* This file is mostly in anticipation of future work, but initially
 * populate with fragment tracking for an outbound message.
 */

/* Initialize datamsg from memory. */
static void sctp_datamsg_init(struct sctp_datamsg *msg)
{
	atomic_set(&msg->refcnt, 1);
	msg->send_failed = 0;
	msg->send_error = 0;
	msg->can_abandon = 0;
56
	msg->can_delay = 1;
Linus Torvalds's avatar
Linus Torvalds committed
57
58
59
60
61
	msg->expires_at = 0;
	INIT_LIST_HEAD(&msg->chunks);
}

/* Allocate and initialize datamsg. */
62
static struct sctp_datamsg *sctp_datamsg_new(gfp_t gfp)
Linus Torvalds's avatar
Linus Torvalds committed
63
64
65
{
	struct sctp_datamsg *msg;
	msg = kmalloc(sizeof(struct sctp_datamsg), gfp);
66
	if (msg) {
Linus Torvalds's avatar
Linus Torvalds committed
67
		sctp_datamsg_init(msg);
68
69
		SCTP_DBG_OBJCNT_INC(datamsg);
	}
Linus Torvalds's avatar
Linus Torvalds committed
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
	return msg;
}

/* Final destructruction of datamsg memory. */
static void sctp_datamsg_destroy(struct sctp_datamsg *msg)
{
	struct list_head *pos, *temp;
	struct sctp_chunk *chunk;
	struct sctp_sock *sp;
	struct sctp_ulpevent *ev;
	struct sctp_association *asoc = NULL;
	int error = 0, notify;

	/* If we failed, we may need to notify. */
	notify = msg->send_failed ? -1 : 0;

	/* Release all references. */
	list_for_each_safe(pos, temp, &msg->chunks) {
		list_del_init(pos);
		chunk = list_entry(pos, struct sctp_chunk, frag_list);
		/* Check whether we _really_ need to notify. */
		if (notify < 0) {
			asoc = chunk->asoc;
			if (msg->send_error)
				error = msg->send_error;
			else
				error = asoc->outqueue.error;

			sp = sctp_sk(asoc->base.sk);
			notify = sctp_ulpevent_type_enabled(SCTP_SEND_FAILED,
							    &sp->subscribe);
		}

		/* Generate a SEND FAILED event only if enabled. */
		if (notify > 0) {
			int sent;
			if (chunk->has_tsn)
				sent = SCTP_DATA_SENT;
			else
				sent = SCTP_DATA_UNSENT;

			ev = sctp_ulpevent_make_send_failed(asoc, chunk, sent,
							    error, GFP_ATOMIC);
			if (ev)
				sctp_ulpq_tail_event(&asoc->ulpq, ev);
		}

		sctp_chunk_put(chunk);
	}

	SCTP_DBG_OBJCNT_DEC(datamsg);
	kfree(msg);
}

/* Hold a reference. */
static void sctp_datamsg_hold(struct sctp_datamsg *msg)
{
	atomic_inc(&msg->refcnt);
}

/* Release a reference. */
void sctp_datamsg_put(struct sctp_datamsg *msg)
{
	if (atomic_dec_and_test(&msg->refcnt))
		sctp_datamsg_destroy(msg);
}

/* Assign a chunk to this datamsg. */
static void sctp_datamsg_assign(struct sctp_datamsg *msg, struct sctp_chunk *chunk)
{
	sctp_datamsg_hold(msg);
	chunk->msg = msg;
}


/* A data chunk can have a maximum payload of (2^16 - 20).  Break
 * down any such message into smaller chunks.  Opportunistically, fragment
 * the chunks down to the current MTU constraints.  We may get refragmented
 * later if the PMTU changes, but it is _much better_ to fragment immediately
 * with a reasonable guess than always doing our fragmentation on the
 * soft-interrupt.
 */
struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
					    struct sctp_sndrcvinfo *sinfo,
154
					    struct iov_iter *from)
Linus Torvalds's avatar
Linus Torvalds committed
155
156
157
{
	int max, whole, i, offset, over, err;
	int len, first_len;
158
	int max_data;
Linus Torvalds's avatar
Linus Torvalds committed
159
160
161
	struct sctp_chunk *chunk;
	struct sctp_datamsg *msg;
	struct list_head *pos, *temp;
162
	size_t msg_len = iov_iter_count(from);
Linus Torvalds's avatar
Linus Torvalds committed
163
164
165
166
	__u8 frag;

	msg = sctp_datamsg_new(GFP_KERNEL);
	if (!msg)
167
		return ERR_PTR(-ENOMEM);
Linus Torvalds's avatar
Linus Torvalds committed
168
169
170
171
172
173
174
175
176

	/* Note: Calculate this outside of the loop, so that all fragments
	 * have the same expiration.
	 */
	if (sinfo->sinfo_timetolive) {
		/* sinfo_timetolive is in milliseconds */
		msg->expires_at = jiffies +
				    msecs_to_jiffies(sinfo->sinfo_timetolive);
		msg->can_abandon = 1;
177
178
179

		pr_debug("%s: msg:%p expires_at:%ld jiffies:%ld\n", __func__,
			 msg, msg->expires_at, jiffies);
Linus Torvalds's avatar
Linus Torvalds committed
180
181
	}

182
183
184
	/* This is the biggest possible DATA chunk that can fit into
	 * the packet
	 */
185
	max_data = (asoc->pathmtu -
186
		sctp_sk(asoc->base.sk)->pf->af->net_header_len -
187
		sizeof(struct sctphdr) - sizeof(struct sctp_data_chunk)) & ~3;
Linus Torvalds's avatar
Linus Torvalds committed
188

189
	max = asoc->frag_point;
190
	/* If the the peer requested that we authenticate DATA chunks
191
	 * we need to account for bundling of the AUTH chunks along with
192
193
194
195
196
197
	 * DATA.
	 */
	if (sctp_auth_send_cid(SCTP_CID_DATA, asoc)) {
		struct sctp_hmac *hmac_desc = sctp_auth_asoc_get_hmac(asoc);

		if (hmac_desc)
198
			max_data -= WORD_ROUND(sizeof(sctp_auth_chunk_t) +
199
200
201
					    hmac_desc->hmac_len);
	}

202
203
204
205
	/* Now, check if we need to reduce our max */
	if (max > max_data)
		max = max_data;

Linus Torvalds's avatar
Linus Torvalds committed
206
207
208
	whole = 0;
	first_len = max;

209
210
211
212
213
214
215
216
217
218
219
220
	/* Check to see if we have a pending SACK and try to let it be bundled
	 * with this message.  Do this if we don't have any data queued already.
	 * To check that, look at out_qlen and retransmit list.
	 * NOTE: we will not reduce to account for SACK, if the message would
	 * not have been fragmented.
	 */
	if (timer_pending(&asoc->timers[SCTP_EVENT_TIMEOUT_SACK]) &&
	    asoc->outqueue.out_qlen == 0 &&
	    list_empty(&asoc->outqueue.retransmit) &&
	    msg_len > max)
		max_data -= WORD_ROUND(sizeof(sctp_sack_chunk_t));

Linus Torvalds's avatar
Linus Torvalds committed
221
	/* Encourage Cookie-ECHO bundling. */
222
	if (asoc->state < SCTP_STATE_COOKIE_ECHOED)
223
		max_data -= SCTP_ARBITRARY_COOKIE_ECHO_LEN;
Linus Torvalds's avatar
Linus Torvalds committed
224

225
226
227
	/* Now that we adjusted completely, reset first_len */
	if (first_len > max_data)
		first_len = max_data;
228
229
230
231
232

	/* Account for a different sized first fragment */
	if (msg_len >= first_len) {
		msg_len -= first_len;
		whole = 1;
233
		msg->can_delay = 0;
Linus Torvalds's avatar
Linus Torvalds committed
234
235
236
237
238
239
240
241
	}

	/* How many full sized?  How many bytes leftover? */
	whole += msg_len / max;
	over = msg_len % max;
	offset = 0;

	if ((whole > 1) || (whole && over))
242
		SCTP_INC_STATS_USER(sock_net(asoc->base.sk), SCTP_MIB_FRAGUSRMSGS);
Linus Torvalds's avatar
Linus Torvalds committed
243
244

	/* Create chunks for all the full sized DATA chunks. */
245
	for (i = 0, len = first_len; i < whole; i++) {
Linus Torvalds's avatar
Linus Torvalds committed
246
247
248
249
250
		frag = SCTP_DATA_MIDDLE_FRAG;

		if (0 == i)
			frag |= SCTP_DATA_FIRST_FRAG;

251
		if ((i == (whole - 1)) && !over) {
Linus Torvalds's avatar
Linus Torvalds committed
252
253
			frag |= SCTP_DATA_LAST_FRAG;

254
255
256
257
258
259
260
261
262
			/* The application requests to set the I-bit of the
			 * last DATA chunk of a user message when providing
			 * the user message to the SCTP implementation.
			 */
			if ((sinfo->sinfo_flags & SCTP_EOF) ||
			    (sinfo->sinfo_flags & SCTP_SACK_IMMEDIATELY))
				frag |= SCTP_DATA_SACK_IMM;
		}

263
264
		chunk = sctp_make_datafrag_empty(asoc, sinfo, len, frag,
						 0, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
265

266
267
		if (!chunk) {
			err = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
268
			goto errout;
269
270
		}

271
		err = sctp_user_addto_chunk(chunk, len, from);
Linus Torvalds's avatar
Linus Torvalds committed
272
		if (err < 0)
273
			goto errout_chunk_free;
Linus Torvalds's avatar
Linus Torvalds committed
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295

		/* Put the chunk->skb back into the form expected by send.  */
		__skb_pull(chunk->skb, (__u8 *)chunk->chunk_hdr
			   - (__u8 *)chunk->skb->data);

		sctp_datamsg_assign(msg, chunk);
		list_add_tail(&chunk->frag_list, &msg->chunks);

		/* The first chunk, the first chunk was likely short
		 * to allow bundling, so reset to full size.
		 */
		if (0 == i)
			len = max;
	}

	/* .. now the leftover bytes. */
	if (over) {
		if (!whole)
			frag = SCTP_DATA_NOT_FRAG;
		else
			frag = SCTP_DATA_LAST_FRAG;

296
297
298
299
		if ((sinfo->sinfo_flags & SCTP_EOF) ||
		    (sinfo->sinfo_flags & SCTP_SACK_IMMEDIATELY))
			frag |= SCTP_DATA_SACK_IMM;

300
301
		chunk = sctp_make_datafrag_empty(asoc, sinfo, over, frag,
						 0, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
302

303
304
		if (!chunk) {
			err = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
305
			goto errout;
306
		}
Linus Torvalds's avatar
Linus Torvalds committed
307

308
		err = sctp_user_addto_chunk(chunk, over, from);
Linus Torvalds's avatar
Linus Torvalds committed
309
310
311
312
313

		/* Put the chunk->skb back into the form expected by send.  */
		__skb_pull(chunk->skb, (__u8 *)chunk->chunk_hdr
			   - (__u8 *)chunk->skb->data);
		if (err < 0)
314
			goto errout_chunk_free;
Linus Torvalds's avatar
Linus Torvalds committed
315
316
317
318
319
320
321

		sctp_datamsg_assign(msg, chunk);
		list_add_tail(&chunk->frag_list, &msg->chunks);
	}

	return msg;

322
323
324
errout_chunk_free:
	sctp_chunk_free(chunk);

Linus Torvalds's avatar
Linus Torvalds committed
325
326
327
328
329
330
errout:
	list_for_each_safe(pos, temp, &msg->chunks) {
		list_del_init(pos);
		chunk = list_entry(pos, struct sctp_chunk, frag_list);
		sctp_chunk_free(chunk);
	}
331
	sctp_datamsg_put(msg);
332
	return ERR_PTR(err);
Linus Torvalds's avatar
Linus Torvalds committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
}

/* Check whether this message has expired. */
int sctp_chunk_abandoned(struct sctp_chunk *chunk)
{
	struct sctp_datamsg *msg = chunk->msg;

	if (!msg->can_abandon)
		return 0;

	if (time_after(jiffies, msg->expires_at))
		return 1;

	return 0;
}

/* This chunk (and consequently entire message) has failed in its sending. */
void sctp_chunk_fail(struct sctp_chunk *chunk, int error)
{
	chunk->msg->send_failed = 1;
	chunk->msg->send_error = error;
}