#include <linux/types.h>
#include <linux/socket.h>
#include <linux/ip.h>
+#include <linux/gfp.h>
#include <net/sock.h>
#include <net/sctp/sctp.h>
#include <net/sctp/sm.h>
sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
SCTP_TO(SCTP_EVENT_TIMEOUT_SACK));
} else {
- if (asoc->a_rwnd > asoc->rwnd)
- asoc->a_rwnd = asoc->rwnd;
+ asoc->a_rwnd = asoc->rwnd;
sack = sctp_make_sack(asoc);
if (!sack)
goto nomem;
*
*/
static void sctp_do_8_2_transport_strike(struct sctp_association *asoc,
- struct sctp_transport *transport)
+ struct sctp_transport *transport,
+ int is_hb)
{
/* The check for association's overall error counter exceeding the
* threshold is done in the state function.
*/
- /* When probing UNCONFIRMED addresses, the association overall
- * error count is NOT incremented
+ /* We are here due to a timer expiration. If the timer was
+ * not a HEARTBEAT, then normal error tracking is done.
+ * If the timer was a heartbeat, we only increment error counts
+ * when we already have an outstanding HEARTBEAT that has not
+ * been acknowledged.
+ * Additionaly, some tranport states inhibit error increments.
*/
- if (transport->state != SCTP_UNCONFIRMED)
+ if (!is_hb) {
asoc->overall_error_count++;
+ if (transport->state != SCTP_INACTIVE)
+ transport->error_count++;
+ } else if (transport->hb_sent) {
+ if (transport->state != SCTP_UNCONFIRMED)
+ asoc->overall_error_count++;
+ if (transport->state != SCTP_INACTIVE)
+ transport->error_count++;
+ }
if (transport->state != SCTP_INACTIVE &&
- (transport->error_count++ >= transport->pathmaxrxt)) {
+ (transport->error_count > transport->pathmaxrxt)) {
SCTP_DEBUG_PRINTK_IPADDR("transport_strike:association %p",
" transport IP: port:%d failed.\n",
asoc,
* expires, set RTO <- RTO * 2 ("back off the timer"). The
* maximum value discussed in rule C7 above (RTO.max) may be
* used to provide an upper bound to this doubling operation.
+ *
+ * Special Case: the first HB doesn't trigger exponential backoff.
+ * The first unacknowledged HB triggers it. We do this with a flag
+ * that indicates that we have an outstanding HB.
*/
- transport->last_rto = transport->rto;
- transport->rto = min((transport->rto * 2), transport->asoc->rto_max);
+ if (!is_hb || transport->hb_sent) {
+ transport->rto = min((transport->rto * 2), transport->asoc->rto_max);
+ }
}
/* Worker routine to handle INIT command failure. */
t->error_count = 0;
t->asoc->overall_error_count = 0;
+ /* Clear the hb_sent flag to signal that we had a good
+ * acknowledgement.
+ */
+ t->hb_sent = 0;
+
/* Mark the destination transport address as active if it is not so
* marked.
*/
sctp_transport_hold(t);
}
-/* Helper function to do a transport reset at the expiry of the hearbeat
- * timer.
- */
-static void sctp_cmd_transport_reset(sctp_cmd_seq_t *cmds,
- struct sctp_association *asoc,
- struct sctp_transport *t)
-{
- sctp_transport_lower_cwnd(t, SCTP_LOWER_CWND_INACTIVE);
-
- /* Mark one strike against a transport. */
- sctp_do_8_2_transport_strike(asoc, t);
-}
/* Helper function to process the process SACK command. */
static int sctp_cmd_process_sack(sctp_cmd_seq_t *cmds,
{
struct sctp_transport *t;
- t = sctp_assoc_choose_shutdown_transport(asoc);
+ t = sctp_assoc_choose_alter_transport(asoc,
+ asoc->shutdown_last_sent_to);
asoc->shutdown_last_sent_to = t;
asoc->timeouts[SCTP_EVENT_TIMEOUT_T2_SHUTDOWN] = t->rto;
chunk->transport = t;
if (sctp_style(sk, TCP)) {
/* Change the sk->sk_state of a TCP-style socket that has
- * sucessfully completed a connect() call.
+ * successfully completed a connect() call.
*/
if (sctp_state(asoc, ESTABLISHED) && sctp_sstate(sk, CLOSED))
sk->sk_state = SCTP_SS_ESTABLISHED;
{
struct sctp_transport *t;
- t = asoc->peer.active_path;
+ t = sctp_assoc_choose_alter_transport(asoc, chunk->transport);
asoc->timeouts[SCTP_EVENT_TIMEOUT_T4_RTO] = t->rto;
chunk->transport = t;
}
struct sctp_association *asoc,
struct sctp_chunk *chunk)
{
- struct sctp_operr_chunk *operr_chunk;
struct sctp_errhdr *err_hdr;
+ struct sctp_ulpevent *ev;
- operr_chunk = (struct sctp_operr_chunk *)chunk->chunk_hdr;
- err_hdr = &operr_chunk->err_hdr;
+ while (chunk->chunk_end > chunk->skb->data) {
+ err_hdr = (struct sctp_errhdr *)(chunk->skb->data);
- switch (err_hdr->cause) {
- case SCTP_ERROR_UNKNOWN_CHUNK:
- {
- struct sctp_chunkhdr *unk_chunk_hdr;
+ ev = sctp_ulpevent_make_remote_error(asoc, chunk, 0,
+ GFP_ATOMIC);
+ if (!ev)
+ return;
- unk_chunk_hdr = (struct sctp_chunkhdr *)err_hdr->variable;
- switch (unk_chunk_hdr->type) {
- /* ADDIP 4.1 A9) If the peer responds to an ASCONF with an
- * ERROR chunk reporting that it did not recognized the ASCONF
- * chunk type, the sender of the ASCONF MUST NOT send any
- * further ASCONF chunks and MUST stop its T-4 timer.
- */
- case SCTP_CID_ASCONF:
- asoc->peer.asconf_capable = 0;
- sctp_add_cmd_sf(cmds, SCTP_CMD_TIMER_STOP,
+ sctp_ulpq_tail_event(&asoc->ulpq, ev);
+
+ switch (err_hdr->cause) {
+ case SCTP_ERROR_UNKNOWN_CHUNK:
+ {
+ sctp_chunkhdr_t *unk_chunk_hdr;
+
+ unk_chunk_hdr = (sctp_chunkhdr_t *)err_hdr->variable;
+ switch (unk_chunk_hdr->type) {
+ /* ADDIP 4.1 A9) If the peer responds to an ASCONF with
+ * an ERROR chunk reporting that it did not recognized
+ * the ASCONF chunk type, the sender of the ASCONF MUST
+ * NOT send any further ASCONF chunks and MUST stop its
+ * T-4 timer.
+ */
+ case SCTP_CID_ASCONF:
+ if (asoc->peer.asconf_capable == 0)
+ break;
+
+ asoc->peer.asconf_capable = 0;
+ sctp_add_cmd_sf(cmds, SCTP_CMD_TIMER_STOP,
SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+ break;
+ default:
+ break;
+ }
break;
+ }
default:
break;
}
- break;
- }
- default:
- break;
}
}
sctp_ulpq_tail_event(&asoc->ulpq, ev);
}
+
+static void sctp_cmd_t1_timer_update(struct sctp_association *asoc,
+ sctp_event_timeout_t timer,
+ char *name)
+{
+ struct sctp_transport *t;
+
+ t = asoc->init_last_sent_to;
+ asoc->init_err_counter++;
+
+ if (t->init_sent_count > (asoc->init_cycle + 1)) {
+ asoc->timeouts[timer] *= 2;
+ if (asoc->timeouts[timer] > asoc->max_init_timeo) {
+ asoc->timeouts[timer] = asoc->max_init_timeo;
+ }
+ asoc->init_cycle++;
+ SCTP_DEBUG_PRINTK(
+ "T1 %s Timeout adjustment"
+ " init_err_counter: %d"
+ " cycle: %d"
+ " timeout: %ld\n",
+ name,
+ asoc->init_err_counter,
+ asoc->init_cycle,
+ asoc->timeouts[timer]);
+ }
+
+}
+
+/* Send the whole message, chunk by chunk, to the outqueue.
+ * This way the whole message is queued up and bundling if
+ * encouraged for small fragments.
+ */
+static int sctp_cmd_send_msg(struct sctp_association *asoc,
+ struct sctp_datamsg *msg)
+{
+ struct sctp_chunk *chunk;
+ int error = 0;
+
+ list_for_each_entry(chunk, &msg->chunks, frag_list) {
+ error = sctp_outq_tail(&asoc->outqueue, chunk);
+ if (error)
+ break;
+ }
+
+ return error;
+}
+
+
+/* Sent the next ASCONF packet currently stored in the association.
+ * This happens after the ASCONF_ACK was succeffully processed.
+ */
+static void sctp_cmd_send_asconf(struct sctp_association *asoc)
+{
+ /* Send the next asconf chunk from the addip chunk
+ * queue.
+ */
+ if (!list_empty(&asoc->addip_chunk_list)) {
+ struct list_head *entry = asoc->addip_chunk_list.next;
+ struct sctp_chunk *asconf = list_entry(entry,
+ struct sctp_chunk, list);
+ list_del_init(entry);
+
+ /* Hold the chunk until an ASCONF_ACK is received. */
+ sctp_chunk_hold(asconf);
+ if (sctp_primitive_ASCONF(asoc, asconf))
+ sctp_chunk_free(asconf);
+ else
+ asoc->addip_last_asconf = asconf;
+ }
+}
+
+
/* These three macros allow us to pull the debugging code out of the
* main flow of sctp_do_sm() to keep attention focused on the real
* functionality there.
case SCTP_CMD_REPORT_TSN:
/* Record the arrival of a TSN. */
- sctp_tsnmap_mark(&asoc->peer.tsn_map, cmd->obj.u32);
+ error = sctp_tsnmap_mark(&asoc->peer.tsn_map,
+ cmd->obj.u32);
break;
case SCTP_CMD_REPORT_FWDTSN:
sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
SCTP_CHUNK(cmd->obj.ptr));
+ if (new_obj->transport) {
+ new_obj->transport->init_sent_count++;
+ asoc->init_last_sent_to = new_obj->transport;
+ }
+
/* FIXME - Eventually come up with a cleaner way to
* enabling COOKIE-ECHO + DATA bundling during
* multihoming stale cookie scenarios, the following
case SCTP_CMD_INIT_CHOOSE_TRANSPORT:
chunk = cmd->obj.ptr;
- t = sctp_assoc_choose_init_transport(asoc);
+ t = sctp_assoc_choose_alter_transport(asoc,
+ asoc->init_last_sent_to);
asoc->init_last_sent_to = t;
chunk->transport = t;
t->init_sent_count++;
+ /* Set the new transport as primary */
+ sctp_assoc_set_primary(asoc, t);
break;
case SCTP_CMD_INIT_RESTART:
* all transports have been tried at the current
* timeout.
*/
- t = asoc->init_last_sent_to;
- asoc->init_err_counter++;
-
- if (t->init_sent_count > (asoc->init_cycle + 1)) {
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_INIT] *= 2;
- if (asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_INIT] >
- asoc->max_init_timeo) {
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_INIT] =
- asoc->max_init_timeo;
- }
- asoc->init_cycle++;
- SCTP_DEBUG_PRINTK(
- "T1 INIT Timeout adjustment"
- " init_err_counter: %d"
- " cycle: %d"
- " timeout: %ld\n",
- asoc->init_err_counter,
- asoc->init_cycle,
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_INIT]);
- }
+ sctp_cmd_t1_timer_update(asoc,
+ SCTP_EVENT_TIMEOUT_T1_INIT,
+ "INIT");
sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
* all transports have been tried at the current
* timeout.
*/
- asoc->init_err_counter++;
-
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_COOKIE] *= 2;
- if (asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_COOKIE] >
- asoc->max_init_timeo) {
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_COOKIE] =
- asoc->max_init_timeo;
- }
- SCTP_DEBUG_PRINTK(
- "T1 COOKIE Timeout adjustment"
- " init_err_counter: %d"
- " timeout: %ld\n",
- asoc->init_err_counter,
- asoc->timeouts[SCTP_EVENT_TIMEOUT_T1_COOKIE]);
+ sctp_cmd_t1_timer_update(asoc,
+ SCTP_EVENT_TIMEOUT_T1_COOKIE,
+ "COOKIE");
/* If we've sent any data bundled with
* COOKIE-ECHO we need to resend.
case SCTP_CMD_INIT_COUNTER_RESET:
asoc->init_err_counter = 0;
asoc->init_cycle = 0;
+ list_for_each_entry(t, &asoc->peer.transport_addr_list,
+ transports) {
+ t->init_sent_count = 0;
+ }
break;
case SCTP_CMD_REPORT_DUP:
case SCTP_CMD_STRIKE:
/* Mark one strike against a transport. */
- sctp_do_8_2_transport_strike(asoc, cmd->obj.transport);
+ sctp_do_8_2_transport_strike(asoc, cmd->obj.transport,
+ 0);
+ break;
+
+ case SCTP_CMD_TRANSPORT_IDLE:
+ t = cmd->obj.transport;
+ sctp_transport_lower_cwnd(t, SCTP_LOWER_CWND_INACTIVE);
break;
- case SCTP_CMD_TRANSPORT_RESET:
+ case SCTP_CMD_TRANSPORT_HB_SENT:
t = cmd->obj.transport;
- sctp_cmd_transport_reset(commands, asoc, t);
+ sctp_do_8_2_transport_strike(asoc, t, 1);
+ t->hb_sent = 1;
break;
case SCTP_CMD_TRANSPORT_ON:
case SCTP_CMD_PROCESS_CTSN:
/* Dummy up a SACK for processing. */
sackh.cum_tsn_ack = cmd->obj.be32;
- sackh.a_rwnd = 0;
+ sackh.a_rwnd = asoc->peer.rwnd +
+ asoc->outqueue.outstanding_bytes;
sackh.num_gap_ack_blocks = 0;
sackh.num_dup_tsns = 0;
sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_SACK,
case SCTP_CMD_UPDATE_INITTAG:
asoc->peer.i.init_tag = cmd->obj.u32;
break;
-
+ case SCTP_CMD_SEND_MSG:
+ if (!asoc->outqueue.cork) {
+ sctp_outq_cork(&asoc->outqueue);
+ local_cork = 1;
+ }
+ error = sctp_cmd_send_msg(asoc, cmd->obj.msg);
+ break;
+ case SCTP_CMD_SEND_NEXT_ASCONF:
+ sctp_cmd_send_asconf(asoc);
+ break;
default:
printk(KERN_WARNING "Impossible command: %u, %p\n",
cmd->verb, cmd->obj.ptr);
*/
if (asoc && SCTP_EVENT_T_CHUNK == event_type && chunk) {
if (chunk->end_of_packet || chunk->singleton)
- sctp_outq_uncork(&asoc->outqueue);
+ error = sctp_outq_uncork(&asoc->outqueue);
} else if (local_cork)
- sctp_outq_uncork(&asoc->outqueue);
+ error = sctp_outq_uncork(&asoc->outqueue);
return error;
nomem:
error = -ENOMEM;