Commit a08de64d authored by Vlad Yasevich's avatar Vlad Yasevich Committed by David S. Miller

[SCTP]: Update ASCONF processing to conform to spec.

The processing of the ASCONF chunks has changed a lot in the
spec.  New items are:
    1. A list of ASCONF-ACK chunks is now cached
    2. The source of the packet is used in response.
    3. New handling for unexpect ASCONF chunks.
Signed-off-by: default avatarVlad Yasevich <vladislav.yasevich@hp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ba8a06da
...@@ -744,6 +744,7 @@ struct sctp_chunk { ...@@ -744,6 +744,7 @@ struct sctp_chunk {
__u8 tsn_missing_report; /* Data chunk missing counter. */ __u8 tsn_missing_report; /* Data chunk missing counter. */
__u8 data_accepted; /* At least 1 chunk in this packet accepted */ __u8 data_accepted; /* At least 1 chunk in this packet accepted */
__u8 auth; /* IN: was auth'ed | OUT: needs auth */ __u8 auth; /* IN: was auth'ed | OUT: needs auth */
__u8 has_asconf; /* IN: have seen an asconf before */
}; };
void sctp_chunk_hold(struct sctp_chunk *); void sctp_chunk_hold(struct sctp_chunk *);
...@@ -1785,20 +1786,16 @@ struct sctp_association { ...@@ -1785,20 +1786,16 @@ struct sctp_association {
*/ */
struct sctp_chunk *addip_last_asconf; struct sctp_chunk *addip_last_asconf;
/* ADDIP Section 4.2 Upon reception of an ASCONF Chunk. /* ADDIP Section 5.2 Upon reception of an ASCONF Chunk.
* *
* IMPLEMENTATION NOTE: As an optimization a receiver may wish * This is needed to implement itmes E1 - E4 of the updated
* to save the last ASCONF-ACK for some predetermined period * spec. Here is the justification:
* of time and instead of re-processing the ASCONF (with the
* same serial number) it may just re-transmit the
* ASCONF-ACK. It may wish to use the arrival of a new serial
* number to discard the previously saved ASCONF-ACK or any
* other means it may choose to expire the saved ASCONF-ACK.
* *
* [This is our saved ASCONF-ACK. We invalidate it when a new * Since the peer may bundle multiple ASCONF chunks toward us,
* ASCONF serial number arrives.] * we now need the ability to cache multiple ACKs. The section
* describes in detail how they are cached and cleaned up.
*/ */
struct sctp_chunk *addip_last_asconf_ack; struct list_head asconf_ack_list;
/* These ASCONF chunks are waiting to be sent. /* These ASCONF chunks are waiting to be sent.
* *
...@@ -1947,6 +1944,11 @@ int sctp_assoc_set_bind_addr_from_cookie(struct sctp_association *, ...@@ -1947,6 +1944,11 @@ int sctp_assoc_set_bind_addr_from_cookie(struct sctp_association *,
struct sctp_cookie*, struct sctp_cookie*,
gfp_t gfp); gfp_t gfp);
int sctp_assoc_set_id(struct sctp_association *, gfp_t); int sctp_assoc_set_id(struct sctp_association *, gfp_t);
void sctp_assoc_clean_asconf_ack_cache(const struct sctp_association *asoc);
struct sctp_chunk *sctp_assoc_lookup_asconf_ack(
const struct sctp_association *asoc,
__be32 serial);
int sctp_cmp_addr_exact(const union sctp_addr *ss1, int sctp_cmp_addr_exact(const union sctp_addr *ss1,
const union sctp_addr *ss2); const union sctp_addr *ss2);
......
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
/* Forward declarations for internal functions. */ /* Forward declarations for internal functions. */
static void sctp_assoc_bh_rcv(struct work_struct *work); static void sctp_assoc_bh_rcv(struct work_struct *work);
static void sctp_assoc_free_asconf_acks(struct sctp_association *asoc);
/* 1st Level Abstractions. */ /* 1st Level Abstractions. */
...@@ -242,6 +243,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a ...@@ -242,6 +243,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
asoc->addip_serial = asoc->c.initial_tsn; asoc->addip_serial = asoc->c.initial_tsn;
INIT_LIST_HEAD(&asoc->addip_chunk_list); INIT_LIST_HEAD(&asoc->addip_chunk_list);
INIT_LIST_HEAD(&asoc->asconf_ack_list);
/* Make an empty list of remote transport addresses. */ /* Make an empty list of remote transport addresses. */
INIT_LIST_HEAD(&asoc->peer.transport_addr_list); INIT_LIST_HEAD(&asoc->peer.transport_addr_list);
...@@ -431,8 +433,7 @@ void sctp_association_free(struct sctp_association *asoc) ...@@ -431,8 +433,7 @@ void sctp_association_free(struct sctp_association *asoc)
asoc->peer.transport_count = 0; asoc->peer.transport_count = 0;
/* Free any cached ASCONF_ACK chunk. */ /* Free any cached ASCONF_ACK chunk. */
if (asoc->addip_last_asconf_ack) sctp_assoc_free_asconf_acks(asoc);
sctp_chunk_free(asoc->addip_last_asconf_ack);
/* Free any cached ASCONF chunk. */ /* Free any cached ASCONF chunk. */
if (asoc->addip_last_asconf) if (asoc->addip_last_asconf)
...@@ -1485,3 +1486,56 @@ int sctp_assoc_set_id(struct sctp_association *asoc, gfp_t gfp) ...@@ -1485,3 +1486,56 @@ int sctp_assoc_set_id(struct sctp_association *asoc, gfp_t gfp)
asoc->assoc_id = (sctp_assoc_t) assoc_id; asoc->assoc_id = (sctp_assoc_t) assoc_id;
return error; return error;
} }
/* Free asconf_ack cache */
static void sctp_assoc_free_asconf_acks(struct sctp_association *asoc)
{
struct sctp_chunk *ack;
struct sctp_chunk *tmp;
list_for_each_entry_safe(ack, tmp, &asoc->asconf_ack_list,
transmitted_list) {
list_del_init(&ack->transmitted_list);
sctp_chunk_free(ack);
}
}
/* Clean up the ASCONF_ACK queue */
void sctp_assoc_clean_asconf_ack_cache(const struct sctp_association *asoc)
{
struct sctp_chunk *ack;
struct sctp_chunk *tmp;
/* We can remove all the entries from the queue upto
* the "Peer-Sequence-Number".
*/
list_for_each_entry_safe(ack, tmp, &asoc->asconf_ack_list,
transmitted_list) {
if (ack->subh.addip_hdr->serial ==
htonl(asoc->peer.addip_serial))
break;
list_del_init(&ack->transmitted_list);
sctp_chunk_free(ack);
}
}
/* Find the ASCONF_ACK whose serial number matches ASCONF */
struct sctp_chunk *sctp_assoc_lookup_asconf_ack(
const struct sctp_association *asoc,
__be32 serial)
{
struct sctp_chunk *ack = NULL;
/* Walk through the list of cached ASCONF-ACKs and find the
* ack chunk whose serial number matches that of the request.
*/
list_for_each_entry(ack, &asoc->asconf_ack_list, transmitted_list) {
if (ack->subh.addip_hdr->serial == serial) {
sctp_chunk_hold(ack);
break;
}
}
return ack;
}
...@@ -716,6 +716,28 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout) ...@@ -716,6 +716,28 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout)
new_transport = chunk->transport; new_transport = chunk->transport;
if (!new_transport) { if (!new_transport) {
/*
* If we have a prior transport pointer, see if
* the destination address of the chunk
* matches the destination address of the
* current transport. If not a match, then
* try to look up the transport with a given
* destination address. We do this because
* after processing ASCONFs, we may have new
* transports created.
*/
if (transport &&
sctp_cmp_addr_exact(&chunk->dest,
&transport->ipaddr))
new_transport = transport;
else
new_transport = sctp_assoc_lookup_paddr(asoc,
&chunk->dest);
/* if we still don't have a new transport, then
* use the current active path.
*/
if (!new_transport)
new_transport = asoc->peer.active_path; new_transport = asoc->peer.active_path;
} else if ((new_transport->state == SCTP_INACTIVE) || } else if ((new_transport->state == SCTP_INACTIVE) ||
(new_transport->state == SCTP_UNCONFIRMED)) { (new_transport->state == SCTP_UNCONFIRMED)) {
...@@ -729,9 +751,12 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout) ...@@ -729,9 +751,12 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout)
* address of the IP datagram containing the * address of the IP datagram containing the
* HEARTBEAT chunk to which this ack is responding. * HEARTBEAT chunk to which this ack is responding.
* ... * ...
*
* ASCONF_ACKs also must be sent to the source.
*/ */
if (chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT && if (chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT &&
chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT_ACK) chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT_ACK &&
chunk->chunk_hdr->type != SCTP_CID_ASCONF_ACK)
new_transport = asoc->peer.active_path; new_transport = asoc->peer.active_path;
} }
......
...@@ -1275,6 +1275,9 @@ struct sctp_chunk *sctp_make_chunk(const struct sctp_association *asoc, ...@@ -1275,6 +1275,9 @@ struct sctp_chunk *sctp_make_chunk(const struct sctp_association *asoc,
/* Release the memory occupied by a chunk. */ /* Release the memory occupied by a chunk. */
static void sctp_chunk_destroy(struct sctp_chunk *chunk) static void sctp_chunk_destroy(struct sctp_chunk *chunk)
{ {
BUG_ON(!list_empty(&chunk->list));
list_del_init(&chunk->transmitted_list);
/* Free the chunk skb data and the SCTP_chunk stub itself. */ /* Free the chunk skb data and the SCTP_chunk stub itself. */
dev_kfree_skb(chunk->skb); dev_kfree_skb(chunk->skb);
...@@ -1285,9 +1288,6 @@ static void sctp_chunk_destroy(struct sctp_chunk *chunk) ...@@ -1285,9 +1288,6 @@ static void sctp_chunk_destroy(struct sctp_chunk *chunk)
/* Possibly, free the chunk. */ /* Possibly, free the chunk. */
void sctp_chunk_free(struct sctp_chunk *chunk) void sctp_chunk_free(struct sctp_chunk *chunk)
{ {
BUG_ON(!list_empty(&chunk->list));
list_del_init(&chunk->transmitted_list);
/* Release our reference on the message tracker. */ /* Release our reference on the message tracker. */
if (chunk->msg) if (chunk->msg)
sctp_datamsg_put(chunk->msg); sctp_datamsg_put(chunk->msg);
...@@ -2980,11 +2980,9 @@ struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc, ...@@ -2980,11 +2980,9 @@ struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc,
* after freeing the reference to old asconf ack if any. * after freeing the reference to old asconf ack if any.
*/ */
if (asconf_ack) { if (asconf_ack) {
if (asoc->addip_last_asconf_ack)
sctp_chunk_free(asoc->addip_last_asconf_ack);
sctp_chunk_hold(asconf_ack); sctp_chunk_hold(asconf_ack);
asoc->addip_last_asconf_ack = asconf_ack; list_add_tail(&asconf_ack->transmitted_list,
&asoc->asconf_ack_list);
} }
return asconf_ack; return asconf_ack;
......
...@@ -3408,42 +3408,62 @@ sctp_disposition_t sctp_sf_do_asconf(const struct sctp_endpoint *ep, ...@@ -3408,42 +3408,62 @@ sctp_disposition_t sctp_sf_do_asconf(const struct sctp_endpoint *ep,
return sctp_sf_violation_paramlen(ep, asoc, type, return sctp_sf_violation_paramlen(ep, asoc, type,
(void *)&err_param, commands); (void *)&err_param, commands);
/* ADDIP 4.2 C1) Compare the value of the serial number to the value /* ADDIP 5.2 E1) Compare the value of the serial number to the value
* the endpoint stored in a new association variable * the endpoint stored in a new association variable
* 'Peer-Serial-Number'. * 'Peer-Serial-Number'.
*/ */
if (serial == asoc->peer.addip_serial + 1) { if (serial == asoc->peer.addip_serial + 1) {
/* ADDIP 4.2 C2) If the value found in the serial number is /* If this is the first instance of ASCONF in the packet,
* equal to the ('Peer-Serial-Number' + 1), the endpoint MUST * we can clean our old ASCONF-ACKs.
* do V1-V5. */
if (!chunk->has_asconf)
sctp_assoc_clean_asconf_ack_cache(asoc);
/* ADDIP 5.2 E4) When the Sequence Number matches the next one
* expected, process the ASCONF as described below and after
* processing the ASCONF Chunk, append an ASCONF-ACK Chunk to
* the response packet and cache a copy of it (in the event it
* later needs to be retransmitted).
*
* Essentially, do V1-V5.
*/ */
asconf_ack = sctp_process_asconf((struct sctp_association *) asconf_ack = sctp_process_asconf((struct sctp_association *)
asoc, chunk); asoc, chunk);
if (!asconf_ack) if (!asconf_ack)
return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_NOMEM;
} else if (serial == asoc->peer.addip_serial) { } else if (serial < asoc->peer.addip_serial + 1) {
/* ADDIP 4.2 C3) If the value found in the serial number is /* ADDIP 5.2 E2)
* equal to the value stored in the 'Peer-Serial-Number' * If the value found in the Sequence Number is less than the
* IMPLEMENTATION NOTE: As an optimization a receiver may wish * ('Peer- Sequence-Number' + 1), simply skip to the next
* to save the last ASCONF-ACK for some predetermined period of * ASCONF, and include in the outbound response packet
* time and instead of re-processing the ASCONF (with the same * any previously cached ASCONF-ACK response that was
* serial number) it may just re-transmit the ASCONF-ACK. * sent and saved that matches the Sequence Number of the
*/ * ASCONF. Note: It is possible that no cached ASCONF-ACK
if (asoc->addip_last_asconf_ack) * Chunk exists. This will occur when an older ASCONF
asconf_ack = asoc->addip_last_asconf_ack; * arrives out of order. In such a case, the receiver
else * should skip the ASCONF Chunk and not include ASCONF-ACK
* Chunk for that chunk.
*/
asconf_ack = sctp_assoc_lookup_asconf_ack(asoc, hdr->serial);
if (!asconf_ack)
return SCTP_DISPOSITION_DISCARD; return SCTP_DISPOSITION_DISCARD;
} else { } else {
/* ADDIP 4.2 C4) Otherwise, the ASCONF Chunk is discarded since /* ADDIP 5.2 E5) Otherwise, the ASCONF Chunk is discarded since
* it must be either a stale packet or from an attacker. * it must be either a stale packet or from an attacker.
*/ */
return SCTP_DISPOSITION_DISCARD; return SCTP_DISPOSITION_DISCARD;
} }
/* ADDIP 4.2 C5) In both cases C2 and C3 the ASCONF-ACK MUST be sent /* ADDIP 5.2 E6) The destination address of the SCTP packet
* back to the source address contained in the IP header of the ASCONF * containing the ASCONF-ACK Chunks MUST be the source address of
* being responded to. * the SCTP packet that held the ASCONF Chunks.
*
* To do this properly, we'll set the destination address of the chunk
* and at the transmit time, will try look up the transport to use.
* Since ASCONFs may be bundled, the correct transport may not be
* created untill we process the entire packet, thus this workaround.
*/ */
asconf_ack->dest = chunk->source;
sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack)); sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack));
return SCTP_DISPOSITION_CONSUME; return SCTP_DISPOSITION_CONSUME;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment