// 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 Intel Corp. * * This file is part of the SCTP kernel implementation * * This file contains sctp stream maniuplation primitives and helpers. * * 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: * Xin Long <lucien.xin@gmail.com>
*/
sctp_sched_dequeue_common(outq, ch); /* No need to call dequeue_done here because * the chunks are not scheduled by now.
*/
/* Mark as failed send. */
sctp_chunk_fail(ch, (__force __u32)SCTP_ERROR_INV_STRM); if (asoc->peer.prsctp_capable &&
SCTP_PR_PRIO_ENABLED(ch->sinfo.sinfo_flags))
asoc->sent_cnt_removable--;
/* Migrates chunks from stream queues to new stream queues if needed, * but not across associations. Also, removes those chunks to streams * higher than the new max.
*/ staticvoid sctp_stream_outq_migrate(struct sctp_stream *stream, struct sctp_stream *new, __u16 outcnt)
{ int i;
if (stream->outcnt > outcnt)
sctp_stream_shrink_out(stream, outcnt);
if (new) { /* Here we actually move the old ext stuff into the new * buffer, because we want to keep it. Then * sctp_stream_update will swap ->out pointers.
*/ for (i = 0; i < outcnt; i++) {
sctp_stream_free_ext(new, i);
SCTP_SO(new, i)->ext = SCTP_SO(stream, i)->ext;
SCTP_SO(stream, i)->ext = NULL;
}
}
for (i = outcnt; i < stream->outcnt; i++)
sctp_stream_free_ext(stream, i);
}
ret = genradix_prealloc(&stream->in, incnt, gfp); if (ret) return ret;
out:
stream->incnt = incnt; return 0;
}
int sctp_stream_init(struct sctp_stream *stream, __u16 outcnt, __u16 incnt,
gfp_t gfp)
{ struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream); int i, ret = 0;
gfp |= __GFP_NOWARN;
/* Initial stream->out size may be very big, so free it and alloc * a new one with new outcnt to save memory if needed.
*/ if (outcnt == stream->outcnt) goto handle_in;
/* Filter out chunks queued on streams that won't exist anymore */
sched->unsched_all(stream);
sctp_stream_outq_migrate(stream, NULL, outcnt);
sched->sched_all(stream);
ret = sctp_stream_alloc_out(stream, outcnt, gfp); if (ret) return ret;
for (i = 0; i < stream->outcnt; i++)
SCTP_SO(stream, i)->state = SCTP_STREAM_OPEN;
handle_in:
sctp_stream_interleave_init(stream); if (!incnt) return 0;
sched->unsched_all(stream); for (i = 0; i < stream->outcnt; i++)
sctp_stream_free_ext(stream, i);
genradix_free(&stream->out);
genradix_free(&stream->in);
}
void sctp_stream_clear(struct sctp_stream *stream)
{ int i;
for (i = 0; i < stream->outcnt; i++) {
SCTP_SO(stream, i)->mid = 0;
SCTP_SO(stream, i)->mid_uo = 0;
}
for (i = 0; i < stream->incnt; i++)
SCTP_SI(stream, i)->mid = 0;
}
if (out) { if (str_nums) for (i = 0; i < str_nums; i++)
SCTP_SO(stream, str_list[i])->state =
SCTP_STREAM_CLOSED; else for (i = 0; i < stream->outcnt; i++)
SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED;
}
retval = sctp_send_reconf(asoc, chunk); if (retval) {
sctp_chunk_put(asoc->strreset_chunk);
asoc->strreset_chunk = NULL; if (!out) goto out;
if (str_nums) for (i = 0; i < str_nums; i++)
SCTP_SO(stream, str_list[i])->state =
SCTP_STREAM_OPEN; else for (i = 0; i < stream->outcnt; i++)
SCTP_SO(stream, i)->state = SCTP_STREAM_OPEN;
goto out;
}
asoc->strreset_outstanding = out + in;
out: return retval;
}
int sctp_send_reset_assoc(struct sctp_association *asoc)
{ struct sctp_stream *stream = &asoc->stream; struct sctp_chunk *chunk = NULL; int retval;
__u16 i;
if (!asoc->peer.reconf_capable ||
!(asoc->strreset_enable & SCTP_ENABLE_RESET_ASSOC_REQ)) return -ENOPROTOOPT;
if (asoc->strreset_outstanding) return -EINPROGRESS;
if (!sctp_outq_is_empty(&asoc->outqueue)) return -EAGAIN;
chunk = sctp_make_strreset_tsnreq(asoc); if (!chunk) return -ENOMEM;
/* Block further xmit of data until this request is completed */ for (i = 0; i < stream->outcnt; i++)
SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED;
hdr = (struct sctp_reconf_chunk *)chunk->chunk_hdr;
sctp_walk_params(param, hdr) { /* sctp_strreset_tsnreq is actually the basic structure * of all stream reconf params, so it's safe to use it * to access request_seq.
*/ struct sctp_strreset_tsnreq *req = param.v;
if ((!resp_seq || req->request_seq == resp_seq) &&
(!type || type == req->param_hdr.type)) return param.v;
}
if (ntohl(outreq->send_reset_at_tsn) >
sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map)) {
result = SCTP_STRRESET_IN_PROGRESS; goto err;
}
if (TSN_lt(asoc->strreset_inseq, request_seq) ||
TSN_lt(request_seq, asoc->strreset_inseq - 2)) {
result = SCTP_STRRESET_ERR_BAD_SEQNO; goto err;
} elseif (TSN_lt(request_seq, asoc->strreset_inseq)) {
i = asoc->strreset_inseq - request_seq - 1;
result = asoc->strreset_result[i]; goto err;
}
asoc->strreset_inseq++;
/* Check strreset_enable after inseq inc, as sender cannot tell * the peer doesn't enable strreset after receiving response with * result denied, as well as to keep consistent with bsd.
*/ if (!(asoc->strreset_enable & SCTP_ENABLE_RESET_STREAM_REQ)) goto out;
nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16);
str_p = outreq->list_of_streams; for (i = 0; i < nums; i++) { if (ntohs(str_p[i]) >= stream->incnt) {
result = SCTP_STRRESET_ERR_WRONG_SSN; goto out;
}
}
if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param(
asoc, outreq->response_seq,
SCTP_PARAM_RESET_IN_REQUEST)) { /* same process with outstanding isn't 0 */
result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out;
}
if (nums) for (i = 0; i < nums; i++)
SCTP_SO(stream, ntohs(str_p[i]))->state =
SCTP_STREAM_CLOSED; else for (i = 0; i < stream->outcnt; i++)
SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED;
if (!sctp_outq_is_empty(&asoc->outqueue)) {
result = SCTP_STRRESET_IN_PROGRESS; goto err;
}
asoc->strreset_inseq++;
if (!(asoc->strreset_enable & SCTP_ENABLE_RESET_ASSOC_REQ)) goto out;
if (asoc->strreset_outstanding) {
result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out;
}
/* G4: The same processing as though a FWD-TSN chunk (as defined in * [RFC3758]) with all streams affected and a new cumulative TSN * ACK of the Receiver's Next TSN minus 1 were received MUST be * performed.
*/
max_tsn_seen = sctp_tsnmap_get_max_tsn_seen(&asoc->peer.tsn_map);
asoc->stream.si->report_ftsn(&asoc->ulpq, max_tsn_seen);
/* G1: Compute an appropriate value for the Receiver's Next TSN -- the * TSN that the peer should use to send the next DATA chunk. The * value SHOULD be the smallest TSN not acknowledged by the * receiver of the request plus 2^31.
*/
init_tsn = sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map) + (1U << 31);
sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_INITIAL,
init_tsn, GFP_ATOMIC);
/* G3: The same processing as though a SACK chunk with no gap report * and a cumulative TSN ACK of the Sender's Next TSN minus 1 were * received MUST be performed.
*/
sctp_outq_free(&asoc->outqueue);
/* G2: Compute an appropriate value for the local endpoint's next TSN, * i.e., the next TSN assigned by the receiver of the SSN/TSN reset * chunk. The value SHOULD be the highest TSN sent by the receiver * of the request plus 1.
*/
next_tsn = asoc->next_tsn;
asoc->ctsn_ack_point = next_tsn - 1;
asoc->adv_peer_ack_point = asoc->ctsn_ack_point;
/* G5: The next expected and outgoing SSNs MUST be reset to 0 for all * incoming and outgoing streams.
*/ for (i = 0; i < stream->outcnt; i++) {
SCTP_SO(stream, i)->mid = 0;
SCTP_SO(stream, i)->mid_uo = 0;
} for (i = 0; i < stream->incnt; i++)
SCTP_SI(stream, i)->mid = 0;
request_seq = ntohl(addstrm->request_seq); if (TSN_lt(asoc->strreset_inseq, request_seq) ||
TSN_lt(request_seq, asoc->strreset_inseq - 2)) {
result = SCTP_STRRESET_ERR_BAD_SEQNO; goto err;
} elseif (TSN_lt(request_seq, asoc->strreset_inseq)) {
i = asoc->strreset_inseq - request_seq - 1;
result = asoc->strreset_result[i]; goto err;
}
asoc->strreset_inseq++;
if (!(asoc->strreset_enable & SCTP_ENABLE_CHANGE_ASSOC_REQ)) goto out;
in = ntohs(addstrm->number_of_streams);
incnt = stream->incnt + in; if (!in || incnt > SCTP_MAX_STREAM) goto out;
if (sctp_stream_alloc_in(stream, incnt, GFP_ATOMIC)) goto out;
if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param(
asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS)) { /* same process with outstanding isn't 0 */
result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out;
}
req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0); if (!req) return NULL;
result = ntohl(resp->result); if (result != SCTP_STRRESET_PERFORMED) { /* if in progress, do nothing but retransmit */ if (result == SCTP_STRRESET_IN_PROGRESS) return NULL; elseif (result == SCTP_STRRESET_DENIED)
flags = SCTP_STREAM_RESET_DENIED; else
flags = SCTP_STREAM_RESET_FAILED;
}
if (req->type == SCTP_PARAM_RESET_OUT_REQUEST) { struct sctp_strreset_outreq *outreq;
__be16 *str_p;
/* Clean up sacked and abandoned queues only. As the * out_chunk_list may not be empty, splice it to temp, * then get it back after sctp_outq_free is done.
*/
list_splice_init(&asoc->outqueue.out_chunk_list, &temp);
sctp_outq_free(&asoc->outqueue);
list_splice_init(&temp, &asoc->outqueue.out_chunk_list);
/* remove everything for this reconf request */ if (!asoc->strreset_outstanding) {
t = asoc->strreset_chunk->transport; if (timer_delete(&t->reconf_timer))
sctp_transport_put(t);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.