Commit 1967937f authored by Sridhar Samudrala's avatar Sridhar Samudrala

[SCTP] ADDIP: Support for processing ASCONF chunks and respond with

       ASCONF_ACK chunks.
parent 018c8581
/* SCTP kernel reference Implementation
* (C) Copyright IBM Corp. 2001, 2003
* Copyright (c) 1999-2000 Cisco, Inc.
* Copyright (c) 1999-2001 Motorola, Inc.
* Copyright (c) 2001 Intel Corp.
* Copyright (c) 2001-2002 International Business Machines Corp.
*
* This file is part of the SCTP kernel reference Implementation
*
......@@ -75,8 +75,6 @@ enum { SCTP_DEFAULT_INSTREAMS = SCTP_MAX_STREAM };
#define SCTP_NUM_BASE_CHUNK_TYPES (SCTP_CID_BASE_MAX + 1)
#define SCTP_NUM_CHUNK_TYPES (SCTP_NUM_BASE_CHUNKTYPES + 2)
#define SCTP_CID_ADDIP_MIN SCTP_CID_ASCONF
#define SCTP_CID_ADDIP_MAX SCTP_CID_ASCONF_ACK
#define SCTP_NUM_ADDIP_CHUNK_TYPES 2
/* These are the different flavours of event. */
......
......@@ -268,15 +268,13 @@ struct sctp_chunk *sctp_make_asconf(struct sctp_association *asoc,
struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *,
union sctp_addr *,
struct sockaddr *,
int, int);
int, __u16);
struct sctp_chunk *sctp_make_asconf_set_prim(struct sctp_association *asoc,
union sctp_addr *addr);
struct sctp_chunk *sctp_make_asconf_ack(struct sctp_association *asoc,
int serial, int vparam_len);
struct sctp_chunk *sctp_make_asconf_ack(const struct sctp_association *asoc,
__u32 serial, int vparam_len);
struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc,
struct sctp_chunk *asconf,
int vparam_len);
struct sctp_chunk *asconf);
void sctp_chunk_assign_tsn(struct sctp_chunk *);
void sctp_chunk_assign_ssn(struct sctp_chunk *);
......
......@@ -1708,6 +1708,8 @@ int sctp_assoc_lookup_laddr(struct sctp_association *asoc,
struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *,
const union sctp_addr *address,
const int gfp);
void sctp_assoc_del_peer(struct sctp_association *asoc,
const union sctp_addr *addr);
void sctp_assoc_control_transport(struct sctp_association *,
struct sctp_transport *,
sctp_transport_cmd_t, sctp_sn_error_t);
......
......@@ -361,6 +361,10 @@ void sctp_association_free(struct sctp_association *asoc)
asoc->eyecatcher = 0;
/* Free any cached ASCONF_ACK chunk. */
if (asoc->addip_last_asconf_ack)
sctp_chunk_free(asoc->addip_last_asconf_ack);
sctp_association_put(asoc);
}
......@@ -524,6 +528,45 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
return peer;
}
/* Delete a transport address from an association. */
void sctp_assoc_del_peer(struct sctp_association *asoc,
const union sctp_addr *addr)
{
struct list_head *pos;
struct list_head *temp;
struct sctp_transport *peer = NULL;
struct sctp_transport *transport;
list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
transport = list_entry(pos, struct sctp_transport, transports);
if (sctp_cmp_addr_exact(addr, &transport->ipaddr)) {
peer = transport;
list_del(pos);
break;
}
}
/* The address we want delete is not in the association. */
if (!peer)
return;
/* Get the first transport of asoc. */
pos = asoc->peer.transport_addr_list.next;
transport = list_entry(pos, struct sctp_transport, transports);
/* Update any entries that match the peer to be deleted. */
if (asoc->peer.primary_path == peer)
sctp_assoc_set_primary(asoc, transport);
if (asoc->peer.active_path == peer)
asoc->peer.active_path = transport;
if (asoc->peer.retran_path == peer)
asoc->peer.retran_path = transport;
if (asoc->peer.last_data_from == peer)
asoc->peer.last_data_from = transport;
sctp_transport_free(peer);
}
/* Lookup a transport by address. */
struct sctp_transport *sctp_assoc_lookup_paddr(
const struct sctp_association *asoc,
......
......@@ -1441,6 +1441,7 @@ struct sctp_association *sctp_unpack_cookie(
retval->next_tsn = retval->c.initial_tsn;
retval->ctsn_ack_point = retval->next_tsn - 1;
retval->addip_serial = retval->c.initial_tsn;
/* The INIT stuff will be done by the side effects. */
return retval;
......@@ -2035,7 +2036,7 @@ struct sctp_chunk *sctp_make_asconf(struct sctp_association *asoc,
if (!retval)
return NULL;
asconf.serial = asoc->addip_serial++;
asconf.serial = htonl(asoc->addip_serial++);
retval->subh.addip_hdr =
sctp_addto_chunk(retval, sizeof(asconf), &asconf);
......@@ -2073,7 +2074,7 @@ struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
union sctp_addr *laddr,
struct sockaddr *addrs,
int addrcnt,
int flags)
__u16 flags)
{
sctp_addip_param_t param;
struct sctp_chunk *retval;
......@@ -2112,7 +2113,7 @@ struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
addr_param_len = af->to_addr_param(addr, &addr_param);
param.param_hdr.type = flags;
param.param_hdr.length = htons(paramlen + addr_param_len);
param.crr_id = htonl(i);
param.crr_id = i;
sctp_addto_chunk(retval, paramlen, &param);
sctp_addto_chunk(retval, addr_param_len, &addr_param);
......@@ -2185,8 +2186,8 @@ struct sctp_chunk *sctp_make_asconf_set_prim(struct sctp_association *asoc,
*
* Create an ASCONF_ACK chunk with enough space for the parameter responses.
*/
struct sctp_chunk *sctp_make_asconf_ack(struct sctp_association *asoc,
int serial, int vparam_len)
struct sctp_chunk *sctp_make_asconf_ack(const struct sctp_association *asoc,
__u32 serial, int vparam_len)
{
sctp_addiphdr_t asconf;
struct sctp_chunk *retval;
......@@ -2197,7 +2198,7 @@ struct sctp_chunk *sctp_make_asconf_ack(struct sctp_association *asoc,
if (!retval)
return NULL;
asconf.serial = serial;
asconf.serial = htonl(serial);
retval->subh.addip_hdr =
sctp_addto_chunk(retval, sizeof(asconf), &asconf);
......@@ -2205,10 +2206,223 @@ struct sctp_chunk *sctp_make_asconf_ack(struct sctp_association *asoc,
return retval;
}
/* Add response parameters to an ASCONF_ACK chunk. */
void sctp_add_asconf_response(struct sctp_chunk *chunk, __u32 crr_id,
__u16 err_code, sctp_addip_param_t *asconf_param)
{
sctp_addip_param_t ack_param;
sctp_errhdr_t err_param;
int asconf_param_len = 0;
int err_param_len = 0;
__u16 response_type;
if (SCTP_ERROR_NO_ERROR == err_code) {
response_type = SCTP_PARAM_SUCCESS_REPORT;
} else {
response_type = SCTP_PARAM_ERR_CAUSE;
err_param_len = sizeof(err_param);
if (asconf_param)
asconf_param_len =
ntohs(asconf_param->param_hdr.length);
}
/* Add Success Indication or Error Cause Indication parameter. */
ack_param.param_hdr.type = response_type;
ack_param.param_hdr.length = htons(sizeof(ack_param) +
err_param_len +
asconf_param_len);
ack_param.crr_id = crr_id;
sctp_addto_chunk(chunk, sizeof(ack_param), &ack_param);
if (SCTP_ERROR_NO_ERROR == err_code)
return;
/* Add Error Cause parameter. */
err_param.cause = err_code;
err_param.length = htons(err_param_len + asconf_param_len);
sctp_addto_chunk(chunk, err_param_len, &err_param);
/* Add the failed TLV copied from ASCONF chunk. */
if (asconf_param)
sctp_addto_chunk(chunk, asconf_param_len, asconf_param);
}
/* Process a asconf parameter. */
__u16 sctp_process_asconf_param(struct sctp_association *asoc,
struct sctp_chunk *asconf,
sctp_addip_param_t *asconf_param,
union sctp_addr_param *addr_param)
{
struct sctp_transport *peer;
struct sctp_af *af;
union sctp_addr addr;
struct list_head *pos;
af = sctp_get_af_specific(param_type2af(addr_param->v4.param_hdr.type));
if (unlikely(!af))
return SCTP_ERROR_INV_PARAM;
af->from_addr_param(&addr, addr_param, asoc->peer.port, 0);
switch (asconf_param->param_hdr.type) {
case SCTP_PARAM_ADD_IP:
/* ADDIP 4.3 D9) If an endpoint receives an ADD IP address
* request and does not have the local resources to add this
* new address to the association, it MUST return an Error
* Cause TLV set to the new error code 'Operation Refused
* Due to Resource Shortage'.
*/
peer = sctp_assoc_add_peer(asoc, &addr, GFP_ATOMIC);
if (!peer)
return SCTP_ERROR_RSRC_LOW;
/* Start the heartbeat timer. */
if (!mod_timer(&peer->hb_timer, sctp_transport_timeout(peer)))
sctp_transport_hold(peer);
break;
case SCTP_PARAM_DEL_IP:
/* ADDIP 4.3 D7) If a request is received to delete the
* last remaining IP address of a peer endpoint, the receiver
* MUST send an Error Cause TLV with the error cause set to the
* new error code 'Request to Delete Last Remaining IP Address'.
*/
pos = asoc->peer.transport_addr_list.next;
if (pos->next == &asoc->peer.transport_addr_list)
return SCTP_ERROR_DEL_LAST_IP;
/* ADDIP 4.3 D8) If a request is received to delete an IP
* address which is also the source address of the IP packet
* which contained the ASCONF chunk, the receiver MUST reject
* this request. To reject the request the receiver MUST send
* an Error Cause TLV set to the new error code 'Request to
* Delete Source IP Address'
*/
if (sctp_cmp_addr_exact(sctp_source(asconf), &addr))
return SCTP_ERROR_DEL_SRC_IP;
sctp_assoc_del_peer(asoc, &addr);
break;
case SCTP_PARAM_SET_PRIMARY:
peer = sctp_assoc_lookup_paddr(asoc, &addr);
if (!peer)
return SCTP_ERROR_INV_PARAM;
sctp_assoc_set_primary(asoc, peer);
break;
default:
return SCTP_ERROR_INV_PARAM;
break;
}
return SCTP_ERROR_NO_ERROR;
}
/* Process an incoming ASCONF chunk with the next expected serial no. and
* return an ASCONF_ACK chunk to be sent in response.
*/
struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc,
struct sctp_chunk *asconf,
int vparam_len)
struct sctp_chunk *asconf)
{
// FIXME: process asconf chunk
return NULL;
sctp_addiphdr_t *hdr;
union sctp_addr_param *addr_param;
sctp_addip_param_t *asconf_param;
struct sctp_chunk *asconf_ack;
__u16 err_code;
int length = 0;
int chunk_len = ntohs(asconf->chunk_hdr->length);
int asconf_param_len;
__u32 serial;
int all_param_pass = 1;
hdr = (sctp_addiphdr_t *)asconf->skb->data;
serial = ntohl(hdr->serial);
/* Skip the chunkhdr. */
chunk_len -= sizeof(sctp_chunkhdr_t);
/* Skip the addiphdr and store a pointer to address parameter. */
length = sizeof(sctp_addiphdr_t);
addr_param = (union sctp_addr_param *)skb_pull(asconf->skb, length);
chunk_len -= length;
/* Skip the address parameter and store a pointer to the first
* asconf paramter.
*/
length = ntohs(addr_param->v4.param_hdr.length);
asconf_param = (sctp_addip_param_t *)skb_pull(asconf->skb, length);
chunk_len -= length;
/* create an ASCONF_ACK chunk.
* Based on the definitions of parameters, we know that the size of
* ASCONF_ACK parameters are less than or equal to the twice of ASCONF
* paramter.
*/
asconf_ack = sctp_make_asconf_ack(asoc, serial, chunk_len * 2);
if (!asconf_ack)
goto done;
/* Process the TLVs contained within the ASCONF chunk. */
while (chunk_len > 0) {
asconf_param_len = ntohs(asconf_param->param_hdr.length);
length = sizeof(sctp_addip_param_t);
/* Unrecognized or unsupported paramter. */
if (asconf_param_len <= length) {
sctp_add_asconf_response(asconf_ack, 0,
SCTP_ERROR_UNKNOWN_PARAM,
NULL);
goto done;
}
addr_param = (union sctp_addr_param *)skb_pull(asconf->skb,
length);
err_code = sctp_process_asconf_param(asoc, asconf, asconf_param,
addr_param);
/* ADDIP 4.1 A7)
* If an error response is received for a TLV parameter,
* all TLVs with no response before the failed TLV are
* considered successful if not reported. All TLVs after
* the failed response are considered unsuccessful unless
* a specific success indication is present for the parameter.
*/
if (SCTP_ERROR_NO_ERROR != err_code)
all_param_pass = 0;
if (!all_param_pass)
sctp_add_asconf_response(asconf_ack,
asconf_param->crr_id, err_code,
asconf_param);
/* ADDIP 4.3 D11) When an endpoint receiving an ASCONF to add
* an IP address sends an 'Out of Resource' in its response, it
* MUST also fail any subsequent add or delete requests bundled
* in the ASCONF.
*/
if (SCTP_ERROR_RSRC_LOW == err_code)
goto done;
/* Move to the next ASCONF param. */
length = ntohs(addr_param->v4.param_hdr.length);
asconf_param = (sctp_addip_param_t *)skb_pull(asconf->skb,
length);
chunk_len -= asconf_param_len;
}
done:
asoc->peer.addip_serial++;
/* If we are sending a new ASCONF_ACK hold a reference to it in assoc
* after freeing the reference to old asconf ack if any.
*/
if (asconf_ack) {
if (asoc->addip_last_asconf_ack)
sctp_chunk_free(asoc->addip_last_asconf_ack);
sctp_chunk_hold(asconf_ack);
asoc->addip_last_asconf_ack = asconf_ack;
}
return asconf_ack;
}
......@@ -3066,19 +3066,57 @@ sctp_disposition_t sctp_sf_do_8_5_1_E_sa(const struct sctp_endpoint *ep,
return sctp_sf_shut_8_4_5(ep, NULL, type, arg, commands);
}
/*
* ADDIP Section 4.2 Upon reception of an ASCONF Chunk
* When an endpoint receive an ASCONF Chunk from the remote peer
* special procedures MAY be needed to identify the association the
* ASCONF Chunk is associated with. To properly find the association
* the following procedures should be L1 to L4 and C1 to C5
*/
/* ADDIP Section 4.2 Upon reception of an ASCONF Chunk. */
sctp_disposition_t sctp_sf_do_asconf(const struct sctp_endpoint *ep,
const struct sctp_association *asoc,
const sctp_subtype_t type, void *arg,
sctp_cmd_seq_t *commands)
const struct sctp_association *asoc,
const sctp_subtype_t type, void *arg,
sctp_cmd_seq_t *commands)
{
// FIXME: Handle the ASCONF chunk
struct sctp_chunk *chunk = arg;
struct sctp_chunk *asconf_ack = NULL;
sctp_addiphdr_t *hdr;
__u32 serial;
hdr = (sctp_addiphdr_t *)chunk->skb->data;
serial = ntohl(hdr->serial);
/* ADDIP 4.2 C1) Compare the value of the serial number to the value
* the endpoint stored in a new association variable
* 'Peer-Serial-Number'.
*/
if (serial == asoc->peer.addip_serial + 1) {
/* ADDIP 4.2 C2) If the value found in the serial number is
* equal to the ('Peer-Serial-Number' + 1), the endpoint MUST
* do V1-V5.
*/
asconf_ack = sctp_process_asconf((struct sctp_association *)
asoc, chunk);
if (!asconf_ack)
return SCTP_DISPOSITION_NOMEM;
} else if (serial == asoc->peer.addip_serial) {
/* ADDIP 4.2 C3) If the value found in the serial number is
* equal to the value stored in the 'Peer-Serial-Number'
* IMPLEMENTATION NOTE: As an optimization a receiver may wish
* to save the last ASCONF-ACK for some predetermined period of * time and instead of re-processing the ASCONF (with the same
* serial number) it may just re-transmit the ASCONF-ACK.
*/
if (asoc->addip_last_asconf_ack)
asconf_ack = asoc->addip_last_asconf_ack;
else
return SCTP_DISPOSITION_DISCARD;
} else {
/* ADDIP 4.2 C4) Otherwise, the ASCONF Chunk is discarded since
* it must be either a stale packet or from an attacker.
*/
return SCTP_DISPOSITION_DISCARD;
}
/* ADDIP 4.2 C5) In both cases C2 and C3 the ASCONF-ACK MUST be sent
* back to the source address contained in the IP header of the ASCONF
* being responded to.
*/
sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack));
return SCTP_DISPOSITION_CONSUME;
}
......
/* SCTP kernel reference Implementation
* (C) Copyright IBM Corp. 2001, 2003
* Copyright (c) 1999-2000 Cisco, Inc.
* Copyright (c) 1999-2001 Motorola, Inc.
* Copyright (c) 2001-2003 International Business Machines, Corp.
* Copyright (c) 2001 Intel Corp.
* Copyright (c) 2001 Nokia, Inc.
*
......@@ -921,14 +921,14 @@ const sctp_sm_table_entry_t *sctp_chunk_event_lookup(sctp_cid_t cid,
if (state > SCTP_STATE_MAX)
return &bug;
if (cid >= 0 && cid <= SCTP_CID_BASE_MAX) {
if (cid >= 0 && cid <= SCTP_CID_BASE_MAX)
return &chunk_event_table[cid][state];
}
if (cid >= SCTP_CID_ADDIP_MIN && cid <= SCTP_CID_ADDIP_MAX) {
return &addip_chunk_event_table
[cid - SCTP_CID_ADDIP_MIN][state];
}
if (cid == SCTP_CID_ASCONF)
return &addip_chunk_event_table[0][state];
if (cid == SCTP_CID_ASCONF_ACK)
return &addip_chunk_event_table[1][state];
return &chunk_event_table_unknown[state];
}
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