1925 lines · 55.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* SCTP kernel implementation * (C) Copyright IBM Corp. 2001, 2004 * Copyright (c) 1999-2000 Cisco, Inc. * Copyright (c) 1999-2001 Motorola, Inc. * Copyright (c) 2001-2003 Intel Corp. * * This file is part of the SCTP kernel implementation * * These functions implement the sctp_outq class.   The outqueue handles * bundling and queueing of outgoing SCTP chunks. * * Please send any bug reports or fixes you make to the * email address(es): *    lksctp developers <linux-sctp@vger.kernel.org> * * Written or modified by: *    La Monte H.P. Yarroll <piggy@acm.org> *    Karl Knutson          <karl@athena.chicago.il.us> *    Perry Melange         <pmelange@null.cc.uic.edu> *    Xingang Guo           <xingang.guo@intel.com> *    Hui Huang 	    <hui.huang@nokia.com> *    Sridhar Samudrala     <sri@us.ibm.com> *    Jon Grimm             <jgrimm@us.ibm.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/types.h>#include <linux/list.h>   /* For struct list_head */#include <linux/socket.h>#include <linux/ip.h>#include <linux/slab.h>#include <net/sock.h>	  /* For skb_set_owner_w */ #include <net/sctp/sctp.h>#include <net/sctp/sm.h>#include <net/sctp/stream_sched.h>#include <trace/events/sctp.h> /* Declare internal functions here.  */static int sctp_acked(struct sctp_sackhdr *sack, __u32 tsn);static void sctp_check_transmitted(struct sctp_outq *q,				   struct list_head *transmitted_queue,				   struct sctp_transport *transport,				   union sctp_addr *saddr,				   struct sctp_sackhdr *sack,				   __u32 *highest_new_tsn); static void sctp_mark_missing(struct sctp_outq *q,			      struct list_head *transmitted_queue,			      struct sctp_transport *transport,			      __u32 highest_new_tsn,			      int count_of_newacks); static void sctp_outq_flush(struct sctp_outq *q, int rtx_timeout, gfp_t gfp); /* Add data to the front of the queue. */static inline void sctp_outq_head_data(struct sctp_outq *q,				       struct sctp_chunk *ch){	struct sctp_stream_out_ext *oute;	__u16 stream; 	list_add(&ch->list, &q->out_chunk_list);	q->out_qlen += ch->skb->len; 	stream = sctp_chunk_stream_no(ch);	oute = SCTP_SO(&q->asoc->stream, stream)->ext;	list_add(&ch->stream_list, &oute->outq);} /* Take data from the front of the queue. */static inline struct sctp_chunk *sctp_outq_dequeue_data(struct sctp_outq *q){	return q->sched->dequeue(q);} /* Add data chunk to the end of the queue. */static inline void sctp_outq_tail_data(struct sctp_outq *q,				       struct sctp_chunk *ch){	struct sctp_stream_out_ext *oute;	__u16 stream; 	list_add_tail(&ch->list, &q->out_chunk_list);	q->out_qlen += ch->skb->len; 	stream = sctp_chunk_stream_no(ch);	oute = SCTP_SO(&q->asoc->stream, stream)->ext;	list_add_tail(&ch->stream_list, &oute->outq);} /* * SFR-CACC algorithm: * D) If count_of_newacks is greater than or equal to 2 * and t was not sent to the current primary then the * sender MUST NOT increment missing report count for t. */static inline int sctp_cacc_skip_3_1_d(struct sctp_transport *primary,				       struct sctp_transport *transport,				       int count_of_newacks){	if (count_of_newacks >= 2 && transport != primary)		return 1;	return 0;} /* * SFR-CACC algorithm: * F) If count_of_newacks is less than 2, let d be the * destination to which t was sent. If cacc_saw_newack * is 0 for destination d, then the sender MUST NOT * increment missing report count for t. */static inline int sctp_cacc_skip_3_1_f(struct sctp_transport *transport,				       int count_of_newacks){	if (count_of_newacks < 2 &&			(transport && !transport->cacc.cacc_saw_newack))		return 1;	return 0;} /* * SFR-CACC algorithm: * 3.1) If CYCLING_CHANGEOVER is 0, the sender SHOULD * execute steps C, D, F. * * C has been implemented in sctp_outq_sack */static inline int sctp_cacc_skip_3_1(struct sctp_transport *primary,				     struct sctp_transport *transport,				     int count_of_newacks){	if (!primary->cacc.cycling_changeover) {		if (sctp_cacc_skip_3_1_d(primary, transport, count_of_newacks))			return 1;		if (sctp_cacc_skip_3_1_f(transport, count_of_newacks))			return 1;		return 0;	}	return 0;} /* * SFR-CACC algorithm: * 3.2) Else if CYCLING_CHANGEOVER is 1, and t is less * than next_tsn_at_change of the current primary, then * the sender MUST NOT increment missing report count * for t. */static inline int sctp_cacc_skip_3_2(struct sctp_transport *primary, __u32 tsn){	if (primary->cacc.cycling_changeover &&	    TSN_lt(tsn, primary->cacc.next_tsn_at_change))		return 1;	return 0;} /* * SFR-CACC algorithm: * 3) If the missing report count for TSN t is to be * incremented according to [RFC2960] and * [SCTP_STEWART-2002], and CHANGEOVER_ACTIVE is set, * then the sender MUST further execute steps 3.1 and * 3.2 to determine if the missing report count for * TSN t SHOULD NOT be incremented. * * 3.3) If 3.1 and 3.2 do not dictate that the missing * report count for t should not be incremented, then * the sender SHOULD increment missing report count for * t (according to [RFC2960] and [SCTP_STEWART_2002]). */static inline int sctp_cacc_skip(struct sctp_transport *primary,				 struct sctp_transport *transport,				 int count_of_newacks,				 __u32 tsn){	if (primary->cacc.changeover_active &&	    (sctp_cacc_skip_3_1(primary, transport, count_of_newacks) ||	     sctp_cacc_skip_3_2(primary, tsn)))		return 1;	return 0;} /* Initialize an existing sctp_outq.  This does the boring stuff. * You still need to define handlers if you really want to DO * something with this structure... */void sctp_outq_init(struct sctp_association *asoc, struct sctp_outq *q){	memset(q, 0, sizeof(struct sctp_outq)); 	q->asoc = asoc;	INIT_LIST_HEAD(&q->out_chunk_list);	INIT_LIST_HEAD(&q->control_chunk_list);	INIT_LIST_HEAD(&q->retransmit);	INIT_LIST_HEAD(&q->sacked);	INIT_LIST_HEAD(&q->abandoned);	sctp_sched_set_sched(asoc, sctp_sk(asoc->base.sk)->default_ss);} /* Free the outqueue structure and any related pending chunks. */static void __sctp_outq_teardown(struct sctp_outq *q){	struct sctp_transport *transport;	struct list_head *lchunk, *temp;	struct sctp_chunk *chunk, *tmp; 	/* Throw away unacknowledged chunks. */	list_for_each_entry(transport, &q->asoc->peer.transport_addr_list,			transports) {		while ((lchunk = sctp_list_dequeue(&transport->transmitted)) != NULL) {			chunk = list_entry(lchunk, struct sctp_chunk,					   transmitted_list);			/* Mark as part of a failed message. */			sctp_chunk_fail(chunk, q->error);			sctp_chunk_free(chunk);		}	} 	/* Throw away chunks that have been gap ACKed.  */	list_for_each_safe(lchunk, temp, &q->sacked) {		list_del_init(lchunk);		chunk = list_entry(lchunk, struct sctp_chunk,				   transmitted_list);		sctp_chunk_fail(chunk, q->error);		sctp_chunk_free(chunk);	} 	/* Throw away any chunks in the retransmit queue. */	list_for_each_safe(lchunk, temp, &q->retransmit) {		list_del_init(lchunk);		chunk = list_entry(lchunk, struct sctp_chunk,				   transmitted_list);		sctp_chunk_fail(chunk, q->error);		sctp_chunk_free(chunk);	} 	/* Throw away any chunks that are in the abandoned queue. */	list_for_each_safe(lchunk, temp, &q->abandoned) {		list_del_init(lchunk);		chunk = list_entry(lchunk, struct sctp_chunk,				   transmitted_list);		sctp_chunk_fail(chunk, q->error);		sctp_chunk_free(chunk);	} 	/* Throw away any leftover data chunks. */	while ((chunk = sctp_outq_dequeue_data(q)) != NULL) {		sctp_sched_dequeue_done(q, chunk); 		/* Mark as send failure. */		sctp_chunk_fail(chunk, q->error);		sctp_chunk_free(chunk);	} 	/* Throw away any leftover control chunks. */	list_for_each_entry_safe(chunk, tmp, &q->control_chunk_list, list) {		list_del_init(&chunk->list);		sctp_chunk_free(chunk);	}} void sctp_outq_teardown(struct sctp_outq *q){	__sctp_outq_teardown(q);	sctp_outq_init(q->asoc, q);} /* Free the outqueue structure and any related pending chunks.  */void sctp_outq_free(struct sctp_outq *q){	/* Throw away leftover chunks. */	__sctp_outq_teardown(q);} /* Put a new chunk in an sctp_outq.  */void sctp_outq_tail(struct sctp_outq *q, struct sctp_chunk *chunk, gfp_t gfp){	struct net *net = q->asoc->base.net; 	pr_debug("%s: outq:%p, chunk:%p[%s]\n", __func__, q, chunk,		 chunk && chunk->chunk_hdr ?		 sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type)) :		 "illegal chunk"); 	/* If it is data, queue it up, otherwise, send it	 * immediately.	 */	if (sctp_chunk_is_data(chunk)) {		pr_debug("%s: outqueueing: outq:%p, chunk:%p[%s])\n",			 __func__, q, chunk, chunk && chunk->chunk_hdr ?			 sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type)) :			 "illegal chunk"); 		sctp_outq_tail_data(q, chunk);		if (chunk->asoc->peer.prsctp_capable &&		    SCTP_PR_PRIO_ENABLED(chunk->sinfo.sinfo_flags))			chunk->asoc->sent_cnt_removable++;		if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED)			SCTP_INC_STATS(net, SCTP_MIB_OUTUNORDERCHUNKS);		else			SCTP_INC_STATS(net, SCTP_MIB_OUTORDERCHUNKS);	} else {		list_add_tail(&chunk->list, &q->control_chunk_list);		SCTP_INC_STATS(net, SCTP_MIB_OUTCTRLCHUNKS);	} 	if (!q->cork)		sctp_outq_flush(q, 0, gfp);} /* Insert a chunk into the sorted list based on the TSNs.  The retransmit list * and the abandoned list are in ascending order. */static void sctp_insert_list(struct list_head *head, struct list_head *new){	struct list_head *pos;	struct sctp_chunk *nchunk, *lchunk;	__u32 ntsn, ltsn;	int done = 0; 	nchunk = list_entry(new, struct sctp_chunk, transmitted_list);	ntsn = ntohl(nchunk->subh.data_hdr->tsn); 	list_for_each(pos, head) {		lchunk = list_entry(pos, struct sctp_chunk, transmitted_list);		ltsn = ntohl(lchunk->subh.data_hdr->tsn);		if (TSN_lt(ntsn, ltsn)) {			list_add(new, pos->prev);			done = 1;			break;		}	}	if (!done)		list_add_tail(new, head);} static int sctp_prsctp_prune_sent(struct sctp_association *asoc,				  struct sctp_sndrcvinfo *sinfo,				  struct list_head *queue, int msg_len){	struct sctp_chunk *chk, *temp; 	list_for_each_entry_safe(chk, temp, queue, transmitted_list) {		struct sctp_stream_out *streamout; 		if (!chk->msg->abandoned &&		    (!SCTP_PR_PRIO_ENABLED(chk->sinfo.sinfo_flags) ||		     chk->sinfo.sinfo_timetolive <= sinfo->sinfo_timetolive))			continue; 		chk->msg->abandoned = 1;		list_del_init(&chk->transmitted_list);		sctp_insert_list(&asoc->outqueue.abandoned,				 &chk->transmitted_list); 		streamout = SCTP_SO(&asoc->stream, chk->sinfo.sinfo_stream);		asoc->sent_cnt_removable--;		asoc->abandoned_sent[SCTP_PR_INDEX(PRIO)]++;		streamout->ext->abandoned_sent[SCTP_PR_INDEX(PRIO)]++; 		if (queue != &asoc->outqueue.retransmit &&		    !chk->tsn_gap_acked) {			if (chk->transport)				chk->transport->flight_size -=						sctp_data_size(chk);			asoc->outqueue.outstanding_bytes -= sctp_data_size(chk);		} 		msg_len -= chk->skb->truesize + sizeof(struct sctp_chunk);		if (msg_len <= 0)			break;	} 	return msg_len;} static int sctp_prsctp_prune_unsent(struct sctp_association *asoc,				    struct sctp_sndrcvinfo *sinfo, int msg_len){	struct sctp_outq *q = &asoc->outqueue;	struct sctp_chunk *chk, *temp;	struct sctp_stream_out *sout; 	q->sched->unsched_all(&asoc->stream); 	list_for_each_entry_safe(chk, temp, &q->out_chunk_list, list) {		if (!chk->msg->abandoned &&		    (!(chk->chunk_hdr->flags & SCTP_DATA_FIRST_FRAG) ||		     !SCTP_PR_PRIO_ENABLED(chk->sinfo.sinfo_flags) ||		     chk->sinfo.sinfo_timetolive <= sinfo->sinfo_timetolive))			continue; 		chk->msg->abandoned = 1;		sctp_sched_dequeue_common(q, chk);		asoc->sent_cnt_removable--;		asoc->abandoned_unsent[SCTP_PR_INDEX(PRIO)]++; 		sout = SCTP_SO(&asoc->stream, chk->sinfo.sinfo_stream);		sout->ext->abandoned_unsent[SCTP_PR_INDEX(PRIO)]++; 		/* clear out_curr if all frag chunks are pruned */		if (asoc->stream.out_curr == sout &&		    list_is_last(&chk->frag_list, &chk->msg->chunks))			asoc->stream.out_curr = NULL; 		msg_len -= chk->skb->truesize + sizeof(struct sctp_chunk);		sctp_chunk_free(chk);		if (msg_len <= 0)			break;	} 	q->sched->sched_all(&asoc->stream); 	return msg_len;} /* Abandon the chunks according their priorities */void sctp_prsctp_prune(struct sctp_association *asoc,		       struct sctp_sndrcvinfo *sinfo, int msg_len){	struct sctp_transport *transport; 	if (!asoc->peer.prsctp_capable || !asoc->sent_cnt_removable)		return; 	msg_len = sctp_prsctp_prune_sent(asoc, sinfo,					 &asoc->outqueue.retransmit,					 msg_len);	if (msg_len <= 0)		return; 	list_for_each_entry(transport, &asoc->peer.transport_addr_list,			    transports) {		msg_len = sctp_prsctp_prune_sent(asoc, sinfo,						 &transport->transmitted,						 msg_len);		if (msg_len <= 0)			return;	} 	sctp_prsctp_prune_unsent(asoc, sinfo, msg_len);} /* Mark all the eligible packets on a transport for retransmission.  */void sctp_retransmit_mark(struct sctp_outq *q,			  struct sctp_transport *transport,			  __u8 reason){	struct list_head *lchunk, *ltemp;	struct sctp_chunk *chunk; 	/* Walk through the specified transmitted queue.  */	list_for_each_safe(lchunk, ltemp, &transport->transmitted) {		chunk = list_entry(lchunk, struct sctp_chunk,				   transmitted_list); 		/* If the chunk is abandoned, move it to abandoned list. */		if (sctp_chunk_abandoned(chunk)) {			list_del_init(lchunk);			sctp_insert_list(&q->abandoned, lchunk); 			/* If this chunk has not been previousely acked,			 * stop considering it 'outstanding'.  Our peer			 * will most likely never see it since it will			 * not be retransmitted			 */			if (!chunk->tsn_gap_acked) {				if (chunk->transport)					chunk->transport->flight_size -=							sctp_data_size(chunk);				q->outstanding_bytes -= sctp_data_size(chunk);				q->asoc->peer.rwnd += sctp_data_size(chunk);			}			continue;		} 		/* If we are doing  retransmission due to a timeout or pmtu		 * discovery, only the  chunks that are not yet acked should		 * be added to the retransmit queue.		 */		if ((reason == SCTP_RTXR_FAST_RTX  &&			    (chunk->fast_retransmit == SCTP_NEED_FRTX)) ||		    (reason != SCTP_RTXR_FAST_RTX  && !chunk->tsn_gap_acked)) {			/* RFC 2960 6.2.1 Processing a Received SACK			 *			 * C) Any time a DATA chunk is marked for			 * retransmission (via either T3-rtx timer expiration			 * (Section 6.3.3) or via fast retransmit			 * (Section 7.2.4)), add the data size of those			 * chunks to the rwnd.			 */			q->asoc->peer.rwnd += sctp_data_size(chunk);			q->outstanding_bytes -= sctp_data_size(chunk);			if (chunk->transport)				transport->flight_size -= sctp_data_size(chunk);

Lines 1–500 of 1925.