2005-04-17 02:20:36 +04:00
/* SCTP kernel reference Implementation
* ( C ) Copyright IBM Corp . 2001 , 2004
* Copyright ( c ) 1999 - 2000 Cisco , Inc .
* Copyright ( c ) 1999 - 2001 Motorola , Inc .
* Copyright ( c ) 2001 - 2002 Intel Corp .
*
* This file is part of the SCTP kernel reference Implementation
*
* These functions work with the state functions in sctp_sm_statefuns . c
* to implement the state operations . These functions implement the
* steps which require modifying existing data structures .
*
* The SCTP reference implementation is free software ;
* you can redistribute it and / or modify it under the terms of
* the GNU General Public License as published by
* the Free Software Foundation ; either version 2 , or ( at your option )
* any later version .
*
* The SCTP reference implementation is distributed in the hope that it
* will be useful , but WITHOUT ANY WARRANTY ; without even the implied
* * * * * * * * * * * * * * * * * * * * * * * * *
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
* See the GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with GNU CC ; see the file COPYING . If not , write to
* the Free Software Foundation , 59 Temple Place - Suite 330 ,
* Boston , MA 02111 - 1307 , USA .
*
* Please send any bug reports or fixes you make to the
* email address ( es ) :
* lksctp developers < lksctp - developers @ lists . sourceforge . net >
*
* Or submit a bug report through the following website :
* http : //www.sf.net/projects/lksctp
*
* Written or modified by :
* La Monte H . P . Yarroll < piggy @ acm . org >
* Karl Knutson < karl @ athena . chicago . il . us >
* C . Robin < chris @ hundredacre . ac . uk >
* Jon Grimm < jgrimm @ us . ibm . com >
* Xingang Guo < xingang . guo @ intel . com >
* Dajiang Zhang < dajiang . zhang @ nokia . com >
* Sridhar Samudrala < sri @ us . ibm . com >
* Daisy Chang < daisyc @ us . ibm . com >
* Ardelle Fan < ardelle . fan @ intel . com >
* Kevin Gao < kevin . gao @ intel . com >
*
* Any bugs reported given to us we will try to fix . . . any fixes shared will
* be incorporated into the next SCTP release .
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/ip.h>
# include <linux/ipv6.h>
# include <linux/net.h>
# include <linux/inet.h>
# include <asm/scatterlist.h>
# include <linux/crypto.h>
# include <net/sock.h>
# include <linux/skbuff.h>
# include <linux/random.h> /* for get_random_bytes */
# include <net/sctp/sctp.h>
# include <net/sctp/sm.h>
SCTP_STATIC
struct sctp_chunk * sctp_make_chunk ( const struct sctp_association * asoc ,
__u8 type , __u8 flags , int paylen ) ;
static sctp_cookie_param_t * sctp_pack_cookie ( const struct sctp_endpoint * ep ,
const struct sctp_association * asoc ,
const struct sctp_chunk * init_chunk ,
int * cookie_len ,
const __u8 * raw_addrs , int addrs_len ) ;
static int sctp_process_param ( struct sctp_association * asoc ,
union sctp_params param ,
const union sctp_addr * peer_addr ,
2005-10-07 10:46:04 +04:00
gfp_t gfp ) ;
2005-04-17 02:20:36 +04:00
/* What was the inbound interface for this chunk? */
int sctp_chunk_iif ( const struct sctp_chunk * chunk )
{
struct sctp_af * af ;
int iif = 0 ;
2007-04-21 09:47:35 +04:00
af = sctp_get_af_specific ( ipver2af ( ip_hdr ( chunk - > skb ) - > version ) ) ;
2005-04-17 02:20:36 +04:00
if ( af )
iif = af - > skb_iif ( chunk - > skb ) ;
return iif ;
}
/* RFC 2960 3.3.2 Initiation (INIT) (1)
*
* Note 2 : The ECN capable field is reserved for future use of
* Explicit Congestion Notification .
*/
static const struct sctp_paramhdr ecap_param = {
SCTP_PARAM_ECN_CAPABLE ,
__constant_htons ( sizeof ( struct sctp_paramhdr ) ) ,
} ;
static const struct sctp_paramhdr prsctp_param = {
SCTP_PARAM_FWD_TSN_SUPPORT ,
__constant_htons ( sizeof ( struct sctp_paramhdr ) ) ,
} ;
/* A helper to initialize to initialize an op error inside a
* provided chunk , as most cause codes will be embedded inside an
* abort chunk .
*/
2006-11-21 03:59:45 +03:00
void sctp_init_cause ( struct sctp_chunk * chunk , __be16 cause_code ,
2007-08-21 11:50:01 +04:00
size_t paylen )
2005-04-17 02:20:36 +04:00
{
sctp_errhdr_t err ;
__u16 len ;
2007-02-09 17:25:18 +03:00
/* Cause code constants are now defined in network order. */
2005-04-17 02:20:36 +04:00
err . cause = cause_code ;
len = sizeof ( sctp_errhdr_t ) + paylen ;
err . length = htons ( len ) ;
2007-01-10 01:35:51 +03:00
chunk - > subh . err_hdr = sctp_addto_chunk ( chunk , sizeof ( sctp_errhdr_t ) , & err ) ;
2005-04-17 02:20:36 +04:00
}
/* 3.3.2 Initiation (INIT) (1)
*
* This chunk is used to initiate a SCTP association between two
* endpoints . The format of the INIT chunk is shown below :
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 1 | Chunk Flags | Chunk Length |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Initiate Tag |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Advertised Receiver Window Credit ( a_rwnd ) |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Number of Outbound Streams | Number of Inbound Streams |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Initial TSN |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* \ \
* / Optional / Variable - Length Parameters /
* \ \
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
*
* The INIT chunk contains the following parameters . Unless otherwise
* noted , each parameter MUST only be included once in the INIT chunk .
*
* Fixed Parameters Status
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Initiate Tag Mandatory
* Advertised Receiver Window Credit Mandatory
* Number of Outbound Streams Mandatory
* Number of Inbound Streams Mandatory
* Initial TSN Mandatory
*
* Variable Parameters Status Type Value
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* IPv4 Address ( Note 1 ) Optional 5
* IPv6 Address ( Note 1 ) Optional 6
* Cookie Preservative Optional 9
* Reserved for ECN Capable ( Note 2 ) Optional 32768 ( 0x8000 )
* Host Name Address ( Note 3 ) Optional 11
* Supported Address Types ( Note 4 ) Optional 12
*/
struct sctp_chunk * sctp_make_init ( const struct sctp_association * asoc ,
const struct sctp_bind_addr * bp ,
2005-10-07 10:46:04 +04:00
gfp_t gfp , int vparam_len )
2005-04-17 02:20:36 +04:00
{
sctp_inithdr_t init ;
union sctp_params addrs ;
size_t chunksize ;
struct sctp_chunk * retval = NULL ;
int num_types , addrs_len = 0 ;
struct sctp_sock * sp ;
sctp_supported_addrs_param_t sat ;
2006-11-21 04:25:49 +03:00
__be16 types [ 2 ] ;
2006-12-21 03:07:04 +03:00
sctp_adaptation_ind_param_t aiparam ;
2005-04-17 02:20:36 +04:00
/* RFC 2960 3.3.2 Initiation (INIT) (1)
*
* Note 1 : The INIT chunks can contain multiple addresses that
* can be IPv4 and / or IPv6 in any combination .
*/
retval = NULL ;
/* Convert the provided bind address list to raw format. */
addrs = sctp_bind_addrs_to_raw ( bp , & addrs_len , gfp ) ;
init . init_tag = htonl ( asoc - > c . my_vtag ) ;
init . a_rwnd = htonl ( asoc - > rwnd ) ;
init . num_outbound_streams = htons ( asoc - > c . sinit_num_ostreams ) ;
init . num_inbound_streams = htons ( asoc - > c . sinit_max_instreams ) ;
init . initial_tsn = htonl ( asoc - > c . initial_tsn ) ;
/* How many address types are needed? */
sp = sctp_sk ( asoc - > base . sk ) ;
num_types = sp - > pf - > supported_addrs ( sp , types ) ;
chunksize = sizeof ( init ) + addrs_len + SCTP_SAT_LEN ( num_types ) ;
chunksize + = sizeof ( ecap_param ) ;
if ( sctp_prsctp_enable )
chunksize + = sizeof ( prsctp_param ) ;
chunksize + = sizeof ( aiparam ) ;
chunksize + = vparam_len ;
/* RFC 2960 3.3.2 Initiation (INIT) (1)
*
* Note 3 : An INIT chunk MUST NOT contain more than one Host
* Name address parameter . Moreover , the sender of the INIT
* MUST NOT combine any other address types with the Host Name
* address in the INIT . The receiver of INIT MUST ignore any
* other address types if the Host Name address parameter is
* present in the received INIT chunk .
*
* PLEASE DO NOT FIXME [ This version does not support Host Name . ]
*/
retval = sctp_make_chunk ( asoc , SCTP_CID_INIT , 0 , chunksize ) ;
if ( ! retval )
goto nodata ;
retval - > subh . init_hdr =
sctp_addto_chunk ( retval , sizeof ( init ) , & init ) ;
retval - > param_hdr . v =
sctp_addto_chunk ( retval , addrs_len , addrs . v ) ;
/* RFC 2960 3.3.2 Initiation (INIT) (1)
*
* Note 4 : This parameter , when present , specifies all the
* address types the sending endpoint can support . The absence
* of this parameter indicates that the sending endpoint can
* support any address type .
*/
sat . param_hdr . type = SCTP_PARAM_SUPPORTED_ADDRESS_TYPES ;
sat . param_hdr . length = htons ( SCTP_SAT_LEN ( num_types ) ) ;
sctp_addto_chunk ( retval , sizeof ( sat ) , & sat ) ;
sctp_addto_chunk ( retval , num_types * sizeof ( __u16 ) , & types ) ;
sctp_addto_chunk ( retval , sizeof ( ecap_param ) , & ecap_param ) ;
if ( sctp_prsctp_enable )
sctp_addto_chunk ( retval , sizeof ( prsctp_param ) , & prsctp_param ) ;
2006-12-21 03:07:04 +03:00
aiparam . param_hdr . type = SCTP_PARAM_ADAPTATION_LAYER_IND ;
2005-04-17 02:20:36 +04:00
aiparam . param_hdr . length = htons ( sizeof ( aiparam ) ) ;
2006-12-21 03:07:04 +03:00
aiparam . adaptation_ind = htonl ( sp - > adaptation_ind ) ;
2005-04-17 02:20:36 +04:00
sctp_addto_chunk ( retval , sizeof ( aiparam ) , & aiparam ) ;
nodata :
2005-11-08 20:41:34 +03:00
kfree ( addrs . v ) ;
2005-04-17 02:20:36 +04:00
return retval ;
}
struct sctp_chunk * sctp_make_init_ack ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
2005-10-07 10:46:04 +04:00
gfp_t gfp , int unkparam_len )
2005-04-17 02:20:36 +04:00
{
sctp_inithdr_t initack ;
struct sctp_chunk * retval ;
union sctp_params addrs ;
int addrs_len ;
sctp_cookie_param_t * cookie ;
int cookie_len ;
size_t chunksize ;
2006-12-21 03:07:04 +03:00
sctp_adaptation_ind_param_t aiparam ;
2005-04-17 02:20:36 +04:00
retval = NULL ;
/* Note: there may be no addresses to embed. */
addrs = sctp_bind_addrs_to_raw ( & asoc - > base . bind_addr , & addrs_len , gfp ) ;
initack . init_tag = htonl ( asoc - > c . my_vtag ) ;
initack . a_rwnd = htonl ( asoc - > rwnd ) ;
initack . num_outbound_streams = htons ( asoc - > c . sinit_num_ostreams ) ;
initack . num_inbound_streams = htons ( asoc - > c . sinit_max_instreams ) ;
initack . initial_tsn = htonl ( asoc - > c . initial_tsn ) ;
/* FIXME: We really ought to build the cookie right
* into the packet instead of allocating more fresh memory .
*/
cookie = sctp_pack_cookie ( asoc - > ep , asoc , chunk , & cookie_len ,
addrs . v , addrs_len ) ;
if ( ! cookie )
goto nomem_cookie ;
/* Calculate the total size of allocation, include the reserved
* space for reporting unknown parameters if it is specified .
*/
chunksize = sizeof ( initack ) + addrs_len + cookie_len + unkparam_len ;
2007-02-09 17:25:18 +03:00
/* Tell peer that we'll do ECN only if peer advertised such cap. */
2005-04-17 02:20:36 +04:00
if ( asoc - > peer . ecn_capable )
chunksize + = sizeof ( ecap_param ) ;
2007-02-09 17:25:18 +03:00
/* Tell peer that we'll do PR-SCTP only if peer advertised. */
2005-04-17 02:20:36 +04:00
if ( asoc - > peer . prsctp_capable )
chunksize + = sizeof ( prsctp_param ) ;
chunksize + = sizeof ( aiparam ) ;
/* Now allocate and fill out the chunk. */
retval = sctp_make_chunk ( asoc , SCTP_CID_INIT_ACK , 0 , chunksize ) ;
if ( ! retval )
goto nomem_chunk ;
/* Per the advice in RFC 2960 6.4, send this reply to
* the source of the INIT packet .
*/
retval - > transport = chunk - > transport ;
retval - > subh . init_hdr =
sctp_addto_chunk ( retval , sizeof ( initack ) , & initack ) ;
retval - > param_hdr . v = sctp_addto_chunk ( retval , addrs_len , addrs . v ) ;
sctp_addto_chunk ( retval , cookie_len , cookie ) ;
if ( asoc - > peer . ecn_capable )
sctp_addto_chunk ( retval , sizeof ( ecap_param ) , & ecap_param ) ;
if ( asoc - > peer . prsctp_capable )
sctp_addto_chunk ( retval , sizeof ( prsctp_param ) , & prsctp_param ) ;
2006-12-21 03:07:04 +03:00
aiparam . param_hdr . type = SCTP_PARAM_ADAPTATION_LAYER_IND ;
2005-04-17 02:20:36 +04:00
aiparam . param_hdr . length = htons ( sizeof ( aiparam ) ) ;
2006-12-21 03:07:04 +03:00
aiparam . adaptation_ind = htonl ( sctp_sk ( asoc - > base . sk ) - > adaptation_ind ) ;
2005-04-17 02:20:36 +04:00
sctp_addto_chunk ( retval , sizeof ( aiparam ) , & aiparam ) ;
/* We need to remove the const qualifier at this point. */
retval - > asoc = ( struct sctp_association * ) asoc ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it received the DATA or control chunk
* to which it is replying .
*
* [ INIT ACK back to where the INIT came from . ]
*/
if ( chunk )
retval - > transport = chunk - > transport ;
nomem_chunk :
kfree ( cookie ) ;
nomem_cookie :
2005-11-08 20:41:34 +03:00
kfree ( addrs . v ) ;
2005-04-17 02:20:36 +04:00
return retval ;
}
/* 3.3.11 Cookie Echo (COOKIE ECHO) (10):
*
* This chunk is used only during the initialization of an association .
* It is sent by the initiator of an association to its peer to complete
* the initialization process . This chunk MUST precede any DATA chunk
* sent within the association , but MAY be bundled with one or more DATA
* chunks in the same packet .
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 10 | Chunk Flags | Length |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* / Cookie /
* \ \
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
* Chunk Flags : 8 bit
*
* Set to zero on transmit and ignored on receipt .
*
* Length : 16 bits ( unsigned integer )
*
* Set to the size of the chunk in bytes , including the 4 bytes of
* the chunk header and the size of the Cookie .
*
* Cookie : variable size
*
* This field must contain the exact cookie received in the
* State Cookie parameter from the previous INIT ACK .
*
* An implementation SHOULD make the cookie as small as possible
* to insure interoperability .
*/
struct sctp_chunk * sctp_make_cookie_echo ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
void * cookie ;
int cookie_len ;
cookie = asoc - > peer . cookie ;
cookie_len = asoc - > peer . cookie_len ;
/* Build a cookie echo chunk. */
retval = sctp_make_chunk ( asoc , SCTP_CID_COOKIE_ECHO , 0 , cookie_len ) ;
if ( ! retval )
goto nodata ;
retval - > subh . cookie_hdr =
sctp_addto_chunk ( retval , cookie_len , cookie ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ COOKIE ECHO back to where the INIT ACK came from . ]
*/
if ( chunk )
retval - > transport = chunk - > transport ;
nodata :
return retval ;
}
/* 3.3.12 Cookie Acknowledgement (COOKIE ACK) (11):
*
* This chunk is used only during the initialization of an
* association . It is used to acknowledge the receipt of a COOKIE
* ECHO chunk . This chunk MUST precede any DATA or SACK chunk sent
* within the association , but MAY be bundled with one or more DATA
* chunks or SACK chunk in the same SCTP packet .
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 11 | Chunk Flags | Length = 4 |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
* Chunk Flags : 8 bits
*
* Set to zero on transmit and ignored on receipt .
*/
struct sctp_chunk * sctp_make_cookie_ack ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
retval = sctp_make_chunk ( asoc , SCTP_CID_COOKIE_ACK , 0 , 0 ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ COOKIE ACK back to where the COOKIE ECHO came from . ]
*/
if ( retval & & chunk )
retval - > transport = chunk - > transport ;
return retval ;
}
/*
* Appendix A : Explicit Congestion Notification :
* CWR :
*
* RFC 2481 details a specific bit for a sender to send in the header of
* its next outbound TCP segment to indicate to its peer that it has
* reduced its congestion window . This is termed the CWR bit . For
* SCTP the same indication is made by including the CWR chunk .
* This chunk contains one data element , i . e . the TSN number that
* was sent in the ECNE chunk . This element represents the lowest
* TSN number in the datagram that was originally marked with the
* CE bit .
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Chunk Type = 13 | Flags = 00000000 | Chunk Length = 8 |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Lowest TSN Number |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
* Note : The CWR is considered a Control chunk .
*/
struct sctp_chunk * sctp_make_cwr ( const struct sctp_association * asoc ,
const __u32 lowest_tsn ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
sctp_cwrhdr_t cwr ;
cwr . lowest_tsn = htonl ( lowest_tsn ) ;
retval = sctp_make_chunk ( asoc , SCTP_CID_ECN_CWR , 0 ,
sizeof ( sctp_cwrhdr_t ) ) ;
if ( ! retval )
goto nodata ;
retval - > subh . ecn_cwr_hdr =
sctp_addto_chunk ( retval , sizeof ( cwr ) , & cwr ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ Report a reduced congestion window back to where the ECNE
* came from . ]
*/
if ( chunk )
retval - > transport = chunk - > transport ;
nodata :
return retval ;
}
/* Make an ECNE chunk. This is a congestion experienced report. */
struct sctp_chunk * sctp_make_ecne ( const struct sctp_association * asoc ,
const __u32 lowest_tsn )
{
struct sctp_chunk * retval ;
sctp_ecnehdr_t ecne ;
ecne . lowest_tsn = htonl ( lowest_tsn ) ;
retval = sctp_make_chunk ( asoc , SCTP_CID_ECN_ECNE , 0 ,
sizeof ( sctp_ecnehdr_t ) ) ;
if ( ! retval )
goto nodata ;
retval - > subh . ecne_hdr =
sctp_addto_chunk ( retval , sizeof ( ecne ) , & ecne ) ;
nodata :
return retval ;
}
/* Make a DATA chunk for the given association from the provided
* parameters . However , do not populate the data payload .
*/
struct sctp_chunk * sctp_make_datafrag_empty ( struct sctp_association * asoc ,
const struct sctp_sndrcvinfo * sinfo ,
int data_len , __u8 flags , __u16 ssn )
{
struct sctp_chunk * retval ;
struct sctp_datahdr dp ;
int chunk_len ;
/* We assign the TSN as LATE as possible, not here when
* creating the chunk .
*/
dp . tsn = 0 ;
dp . stream = htons ( sinfo - > sinfo_stream ) ;
dp . ppid = sinfo - > sinfo_ppid ;
/* Set the flags for an unordered send. */
2005-10-29 02:10:00 +04:00
if ( sinfo - > sinfo_flags & SCTP_UNORDERED ) {
2005-04-17 02:20:36 +04:00
flags | = SCTP_DATA_UNORDERED ;
dp . ssn = 0 ;
} else
dp . ssn = htons ( ssn ) ;
chunk_len = sizeof ( dp ) + data_len ;
retval = sctp_make_chunk ( asoc , SCTP_CID_DATA , flags , chunk_len ) ;
if ( ! retval )
goto nodata ;
retval - > subh . data_hdr = sctp_addto_chunk ( retval , sizeof ( dp ) , & dp ) ;
memcpy ( & retval - > sinfo , sinfo , sizeof ( struct sctp_sndrcvinfo ) ) ;
nodata :
return retval ;
}
/* Create a selective ackowledgement (SACK) for the given
* association . This reports on which TSN ' s we ' ve seen to date ,
* including duplicates and gaps .
*/
struct sctp_chunk * sctp_make_sack ( const struct sctp_association * asoc )
{
struct sctp_chunk * retval ;
struct sctp_sackhdr sack ;
int len ;
__u32 ctsn ;
__u16 num_gabs , num_dup_tsns ;
struct sctp_tsnmap * map = ( struct sctp_tsnmap * ) & asoc - > peer . tsn_map ;
ctsn = sctp_tsnmap_get_ctsn ( map ) ;
SCTP_DEBUG_PRINTK ( " sackCTSNAck sent: 0x%x. \n " , ctsn ) ;
/* How much room is needed in the chunk? */
num_gabs = sctp_tsnmap_num_gabs ( map ) ;
num_dup_tsns = sctp_tsnmap_num_dups ( map ) ;
/* Initialize the SACK header. */
sack . cum_tsn_ack = htonl ( ctsn ) ;
sack . a_rwnd = htonl ( asoc - > a_rwnd ) ;
sack . num_gap_ack_blocks = htons ( num_gabs ) ;
sack . num_dup_tsns = htons ( num_dup_tsns ) ;
len = sizeof ( sack )
+ sizeof ( struct sctp_gap_ack_block ) * num_gabs
+ sizeof ( __u32 ) * num_dup_tsns ;
/* Create the chunk. */
retval = sctp_make_chunk ( asoc , SCTP_CID_SACK , 0 , len ) ;
if ( ! retval )
goto nodata ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , etc . ) to the same destination transport
* address from which it received the DATA or control chunk to
* which it is replying . This rule should also be followed if
* the endpoint is bundling DATA chunks together with the
* reply chunk .
*
* However , when acknowledging multiple DATA chunks received
* in packets from different source addresses in a single
* SACK , the SACK chunk may be transmitted to one of the
* destination transport addresses from which the DATA or
* control chunks being acknowledged were received .
*
* [ BUG : We do not implement the following paragraph .
* Perhaps we should remember the last transport we used for a
* SACK and avoid that ( if possible ) if we have seen any
* duplicates . - - piggy ]
*
* When a receiver of a duplicate DATA chunk sends a SACK to a
* multi - homed endpoint it MAY be beneficial to vary the
* destination address and not use the source address of the
* DATA chunk . The reason being that receiving a duplicate
* from a multi - homed endpoint might indicate that the return
* path ( as specified in the source address of the DATA chunk )
* for the SACK is broken .
*
* [ Send to the address from which we last received a DATA chunk . ]
*/
retval - > transport = asoc - > peer . last_data_from ;
retval - > subh . sack_hdr =
sctp_addto_chunk ( retval , sizeof ( sack ) , & sack ) ;
/* Add the gap ack block information. */
if ( num_gabs )
sctp_addto_chunk ( retval , sizeof ( __u32 ) * num_gabs ,
sctp_tsnmap_get_gabs ( map ) ) ;
/* Add the duplicate TSN information. */
if ( num_dup_tsns )
sctp_addto_chunk ( retval , sizeof ( __u32 ) * num_dup_tsns ,
sctp_tsnmap_get_dups ( map ) ) ;
nodata :
return retval ;
}
/* Make a SHUTDOWN chunk. */
struct sctp_chunk * sctp_make_shutdown ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
sctp_shutdownhdr_t shut ;
__u32 ctsn ;
ctsn = sctp_tsnmap_get_ctsn ( & asoc - > peer . tsn_map ) ;
shut . cum_tsn_ack = htonl ( ctsn ) ;
retval = sctp_make_chunk ( asoc , SCTP_CID_SHUTDOWN , 0 ,
sizeof ( sctp_shutdownhdr_t ) ) ;
if ( ! retval )
goto nodata ;
retval - > subh . shutdown_hdr =
sctp_addto_chunk ( retval , sizeof ( shut ) , & shut ) ;
if ( chunk )
retval - > transport = chunk - > transport ;
nodata :
return retval ;
}
struct sctp_chunk * sctp_make_shutdown_ack ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
retval = sctp_make_chunk ( asoc , SCTP_CID_SHUTDOWN_ACK , 0 , 0 ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ ACK back to where the SHUTDOWN came from . ]
*/
if ( retval & & chunk )
retval - > transport = chunk - > transport ;
return retval ;
}
struct sctp_chunk * sctp_make_shutdown_complete (
const struct sctp_association * asoc ,
const struct sctp_chunk * chunk )
{
struct sctp_chunk * retval ;
__u8 flags = 0 ;
2005-04-28 22:58:43 +04:00
/* Set the T-bit if we have no association (vtag will be
* reflected )
*/
2005-04-17 02:20:36 +04:00
flags | = asoc ? 0 : SCTP_CHUNK_FLAG_T ;
retval = sctp_make_chunk ( asoc , SCTP_CID_SHUTDOWN_COMPLETE , flags , 0 ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ Report SHUTDOWN COMPLETE back to where the SHUTDOWN ACK
* came from . ]
*/
if ( retval & & chunk )
retval - > transport = chunk - > transport ;
2007-02-09 17:25:18 +03:00
return retval ;
2005-04-17 02:20:36 +04:00
}
/* Create an ABORT. Note that we set the T bit if we have no
2005-04-28 22:58:43 +04:00
* association , except when responding to an INIT ( sctpimpguide 2.41 ) .
2005-04-17 02:20:36 +04:00
*/
struct sctp_chunk * sctp_make_abort ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
const size_t hint )
{
struct sctp_chunk * retval ;
__u8 flags = 0 ;
2005-04-28 22:58:43 +04:00
/* Set the T-bit if we have no association and 'chunk' is not
* an INIT ( vtag will be reflected ) .
*/
if ( ! asoc ) {
if ( chunk & & chunk - > chunk_hdr & &
chunk - > chunk_hdr - > type = = SCTP_CID_INIT )
flags = 0 ;
else
flags = SCTP_CHUNK_FLAG_T ;
}
2005-04-17 02:20:36 +04:00
retval = sctp_make_chunk ( asoc , SCTP_CID_ABORT , flags , hint ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ ABORT back to where the offender came from . ]
*/
if ( retval & & chunk )
retval - > transport = chunk - > transport ;
return retval ;
}
/* Helper to create ABORT with a NO_USER_DATA error. */
struct sctp_chunk * sctp_make_abort_no_data (
const struct sctp_association * asoc ,
const struct sctp_chunk * chunk , __u32 tsn )
{
struct sctp_chunk * retval ;
2006-11-21 04:26:34 +03:00
__be32 payload ;
2005-04-17 02:20:36 +04:00
retval = sctp_make_abort ( asoc , chunk , sizeof ( sctp_errhdr_t )
+ sizeof ( tsn ) ) ;
if ( ! retval )
goto no_mem ;
/* Put the tsn back into network byte order. */
payload = htonl ( tsn ) ;
2007-08-21 11:50:01 +04:00
sctp_init_cause ( retval , SCTP_ERROR_NO_DATA , sizeof ( payload ) ) ;
sctp_addto_chunk ( retval , sizeof ( payload ) , ( const void * ) & payload ) ;
2005-04-17 02:20:36 +04:00
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ ABORT back to where the offender came from . ]
*/
if ( chunk )
retval - > transport = chunk - > transport ;
no_mem :
return retval ;
}
/* Helper to create ABORT with a SCTP_ERROR_USER_ABORT error. */
struct sctp_chunk * sctp_make_abort_user ( const struct sctp_association * asoc ,
2006-08-22 22:50:39 +04:00
const struct msghdr * msg ,
size_t paylen )
2005-04-17 02:20:36 +04:00
{
struct sctp_chunk * retval ;
2006-08-22 22:50:39 +04:00
void * payload = NULL ;
int err ;
2005-04-17 02:20:36 +04:00
2006-08-22 22:50:39 +04:00
retval = sctp_make_abort ( asoc , NULL , sizeof ( sctp_errhdr_t ) + paylen ) ;
2005-04-17 02:20:36 +04:00
if ( ! retval )
goto err_chunk ;
if ( paylen ) {
/* Put the msg_iov together into payload. */
2006-08-22 22:50:39 +04:00
payload = kmalloc ( paylen , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! payload )
goto err_payload ;
2006-08-22 22:50:39 +04:00
err = memcpy_fromiovec ( payload , msg - > msg_iov , paylen ) ;
if ( err < 0 )
goto err_copy ;
2005-04-17 02:20:36 +04:00
}
2007-08-21 11:50:01 +04:00
sctp_init_cause ( retval , SCTP_ERROR_USER_ABORT , paylen ) ;
sctp_addto_chunk ( retval , paylen , payload ) ;
2005-04-17 02:20:36 +04:00
if ( paylen )
kfree ( payload ) ;
return retval ;
err_copy :
kfree ( payload ) ;
err_payload :
sctp_chunk_free ( retval ) ;
retval = NULL ;
err_chunk :
return retval ;
}
2007-02-09 17:25:18 +03:00
/* Make an ABORT chunk with a PROTOCOL VIOLATION cause code. */
2005-04-17 02:20:36 +04:00
struct sctp_chunk * sctp_make_abort_violation (
const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
const __u8 * payload ,
const size_t paylen )
{
struct sctp_chunk * retval ;
struct sctp_paramhdr phdr ;
retval = sctp_make_abort ( asoc , chunk , sizeof ( sctp_errhdr_t ) + paylen
2007-08-21 11:50:01 +04:00
+ sizeof ( sctp_paramhdr_t ) ) ;
2005-04-17 02:20:36 +04:00
if ( ! retval )
goto end ;
2007-08-21 11:50:01 +04:00
sctp_init_cause ( retval , SCTP_ERROR_PROTO_VIOLATION , paylen
+ sizeof ( sctp_paramhdr_t ) ) ;
2005-04-17 02:20:36 +04:00
phdr . type = htons ( chunk - > chunk_hdr - > type ) ;
phdr . length = chunk - > chunk_hdr - > length ;
2007-08-21 11:50:01 +04:00
sctp_addto_chunk ( retval , paylen , payload ) ;
sctp_addto_param ( retval , sizeof ( sctp_paramhdr_t ) , & phdr ) ;
2005-04-17 02:20:36 +04:00
end :
return retval ;
}
/* Make a HEARTBEAT chunk. */
struct sctp_chunk * sctp_make_heartbeat ( const struct sctp_association * asoc ,
const struct sctp_transport * transport ,
const void * payload , const size_t paylen )
{
struct sctp_chunk * retval = sctp_make_chunk ( asoc , SCTP_CID_HEARTBEAT ,
0 , paylen ) ;
if ( ! retval )
goto nodata ;
/* Cast away the 'const', as this is just telling the chunk
* what transport it belongs to .
*/
retval - > transport = ( struct sctp_transport * ) transport ;
retval - > subh . hbs_hdr = sctp_addto_chunk ( retval , paylen , payload ) ;
nodata :
return retval ;
}
struct sctp_chunk * sctp_make_heartbeat_ack ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
const void * payload , const size_t paylen )
{
struct sctp_chunk * retval ;
retval = sctp_make_chunk ( asoc , SCTP_CID_HEARTBEAT_ACK , 0 , paylen ) ;
if ( ! retval )
goto nodata ;
retval - > subh . hbs_hdr = sctp_addto_chunk ( retval , paylen , payload ) ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , * etc . ) to the same destination transport
* address from which it * received the DATA or control chunk
* to which it is replying .
*
* [ HBACK back to where the HEARTBEAT came from . ]
*/
if ( chunk )
retval - > transport = chunk - > transport ;
nodata :
return retval ;
}
/* Create an Operation Error chunk with the specified space reserved.
* This routine can be used for containing multiple causes in the chunk .
*/
static struct sctp_chunk * sctp_make_op_error_space (
const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
size_t size )
{
struct sctp_chunk * retval ;
retval = sctp_make_chunk ( asoc , SCTP_CID_ERROR , 0 ,
sizeof ( sctp_errhdr_t ) + size ) ;
if ( ! retval )
goto nodata ;
/* RFC 2960 6.4 Multi-homed SCTP Endpoints
*
* An endpoint SHOULD transmit reply chunks ( e . g . , SACK ,
* HEARTBEAT ACK , etc . ) to the same destination transport
* address from which it received the DATA or control chunk
* to which it is replying .
*
*/
if ( chunk )
retval - > transport = chunk - > transport ;
nodata :
return retval ;
}
/* Create an Operation Error chunk. */
struct sctp_chunk * sctp_make_op_error ( const struct sctp_association * asoc ,
const struct sctp_chunk * chunk ,
2006-11-21 04:00:05 +03:00
__be16 cause_code , const void * payload ,
2005-04-17 02:20:36 +04:00
size_t paylen )
{
struct sctp_chunk * retval ;
retval = sctp_make_op_error_space ( asoc , chunk , paylen ) ;
if ( ! retval )
goto nodata ;
2007-08-21 11:50:01 +04:00
sctp_init_cause ( retval , cause_code , paylen ) ;
sctp_addto_chunk ( retval , paylen , payload ) ;
2005-04-17 02:20:36 +04:00
nodata :
return retval ;
}
/********************************************************************
* 2 nd Level Abstractions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Turn an skb into a chunk.
* FIXME : Eventually move the structure directly inside the skb - > cb [ ] .
*/
struct sctp_chunk * sctp_chunkify ( struct sk_buff * skb ,
const struct sctp_association * asoc ,
struct sock * sk )
{
struct sctp_chunk * retval ;
2007-02-10 12:45:03 +03:00
retval = kmem_cache_zalloc ( sctp_chunk_cachep , GFP_ATOMIC ) ;
2005-04-17 02:20:36 +04:00
if ( ! retval )
goto nodata ;
if ( ! sk ) {
SCTP_DEBUG_PRINTK ( " chunkifying skb %p w/o an sk \n " , skb ) ;
}
2005-07-09 08:47:49 +04:00
INIT_LIST_HEAD ( & retval - > list ) ;
2005-04-17 02:20:36 +04:00
retval - > skb = skb ;
retval - > asoc = ( struct sctp_association * ) asoc ;
retval - > resent = 0 ;
retval - > has_tsn = 0 ;
retval - > has_ssn = 0 ;
retval - > rtt_in_progress = 0 ;
retval - > sent_at = 0 ;
retval - > singleton = 1 ;
retval - > end_of_packet = 0 ;
retval - > ecn_ce_done = 0 ;
retval - > pdiscard = 0 ;
/* sctpimpguide-05.txt Section 2.8.2
* M1 ) Each time a new DATA chunk is transmitted
* set the ' TSN . Missing . Report ' count for that TSN to 0. The
* ' TSN . Missing . Report ' count will be used to determine missing chunks
* and when to fast retransmit .
*/
retval - > tsn_missing_report = 0 ;
retval - > tsn_gap_acked = 0 ;
retval - > fast_retransmit = 0 ;
/* If this is a fragmented message, track all fragments
* of the message ( for SEND_FAILED ) .
*/
retval - > msg = NULL ;
/* Polish the bead hole. */
INIT_LIST_HEAD ( & retval - > transmitted_list ) ;
INIT_LIST_HEAD ( & retval - > frag_list ) ;
SCTP_DBG_OBJCNT_INC ( chunk ) ;
atomic_set ( & retval - > refcnt , 1 ) ;
nodata :
return retval ;
}
/* Set chunk->source and dest based on the IP header in chunk->skb. */
void sctp_init_addrs ( struct sctp_chunk * chunk , union sctp_addr * src ,
union sctp_addr * dest )
{
2006-11-21 04:09:01 +03:00
memcpy ( & chunk - > source , src , sizeof ( union sctp_addr ) ) ;
2006-11-21 04:13:38 +03:00
memcpy ( & chunk - > dest , dest , sizeof ( union sctp_addr ) ) ;
2005-04-17 02:20:36 +04:00
}
/* Extract the source address from a chunk. */
const union sctp_addr * sctp_source ( const struct sctp_chunk * chunk )
{
/* If we have a known transport, use that. */
if ( chunk - > transport ) {
2006-11-21 04:12:25 +03:00
return & chunk - > transport - > ipaddr ;
2005-04-17 02:20:36 +04:00
} else {
/* Otherwise, extract it from the IP header. */
2006-11-21 04:12:25 +03:00
return & chunk - > source ;
2005-04-17 02:20:36 +04:00
}
}
/* Create a new chunk, setting the type and flags headers from the
* arguments , reserving enough space for a ' paylen ' byte payload .
*/
SCTP_STATIC
struct sctp_chunk * sctp_make_chunk ( const struct sctp_association * asoc ,
__u8 type , __u8 flags , int paylen )
{
struct sctp_chunk * retval ;
sctp_chunkhdr_t * chunk_hdr ;
struct sk_buff * skb ;
struct sock * sk ;
/* No need to allocate LL here, as this is only a chunk. */
skb = alloc_skb ( WORD_ROUND ( sizeof ( sctp_chunkhdr_t ) + paylen ) ,
GFP_ATOMIC ) ;
if ( ! skb )
goto nodata ;
/* Make room for the chunk header. */
chunk_hdr = ( sctp_chunkhdr_t * ) skb_put ( skb , sizeof ( sctp_chunkhdr_t ) ) ;
chunk_hdr - > type = type ;
chunk_hdr - > flags = flags ;
chunk_hdr - > length = htons ( sizeof ( sctp_chunkhdr_t ) ) ;
sk = asoc ? asoc - > base . sk : NULL ;
retval = sctp_chunkify ( skb , asoc , sk ) ;
if ( ! retval ) {
kfree_skb ( skb ) ;
goto nodata ;
}
retval - > chunk_hdr = chunk_hdr ;
retval - > chunk_end = ( ( __u8 * ) chunk_hdr ) + sizeof ( struct sctp_chunkhdr ) ;
/* Set the skb to the belonging sock for accounting. */
skb - > sk = sk ;
return retval ;
nodata :
return NULL ;
}
/* Release the memory occupied by a chunk. */
static void sctp_chunk_destroy ( struct sctp_chunk * chunk )
{
/* Free the chunk skb data and the SCTP_chunk stub itself. */
dev_kfree_skb ( chunk - > skb ) ;
SCTP_DBG_OBJCNT_DEC ( chunk ) ;
kmem_cache_free ( sctp_chunk_cachep , chunk ) ;
}
/* Possibly, free the chunk. */
void sctp_chunk_free ( struct sctp_chunk * chunk )
{
2005-07-09 08:47:49 +04:00
BUG_ON ( ! list_empty ( & chunk - > list ) ) ;
2005-04-17 02:20:36 +04:00
list_del_init ( & chunk - > transmitted_list ) ;
/* Release our reference on the message tracker. */
if ( chunk - > msg )
sctp_datamsg_put ( chunk - > msg ) ;
sctp_chunk_put ( chunk ) ;
}
/* Grab a reference to the chunk. */
void sctp_chunk_hold ( struct sctp_chunk * ch )
{
atomic_inc ( & ch - > refcnt ) ;
}
/* Release a reference to the chunk. */
void sctp_chunk_put ( struct sctp_chunk * ch )
{
if ( atomic_dec_and_test ( & ch - > refcnt ) )
sctp_chunk_destroy ( ch ) ;
}
/* Append bytes to the end of a chunk. Will panic if chunk is not big
* enough .
*/
void * sctp_addto_chunk ( struct sctp_chunk * chunk , int len , const void * data )
{
void * target ;
void * padding ;
int chunklen = ntohs ( chunk - > chunk_hdr - > length ) ;
2007-08-06 09:55:47 +04:00
int padlen = WORD_ROUND ( chunklen ) - chunklen ;
2005-04-17 02:20:36 +04:00
padding = skb_put ( chunk - > skb , padlen ) ;
target = skb_put ( chunk - > skb , len ) ;
memset ( padding , 0 , padlen ) ;
memcpy ( target , data , len ) ;
/* Adjust the chunk length field. */
chunk - > chunk_hdr - > length = htons ( chunklen + padlen + len ) ;
2007-04-20 07:29:13 +04:00
chunk - > chunk_end = skb_tail_pointer ( chunk - > skb ) ;
2005-04-17 02:20:36 +04:00
return target ;
}
2007-08-21 11:50:01 +04:00
/* Append bytes to the end of a parameter. Will panic if chunk is not big
* enough .
*/
void * sctp_addto_param ( struct sctp_chunk * chunk , int len , const void * data )
{
void * target ;
int chunklen = ntohs ( chunk - > chunk_hdr - > length ) ;
target = skb_put ( chunk - > skb , len ) ;
memcpy ( target , data , len ) ;
/* Adjust the chunk length field. */
chunk - > chunk_hdr - > length = htons ( chunklen + len ) ;
chunk - > chunk_end = skb_tail_pointer ( chunk - > skb ) ;
return target ;
}
2005-04-17 02:20:36 +04:00
/* Append bytes from user space to the end of a chunk. Will panic if
* chunk is not big enough .
* Returns a kernel err value .
*/
int sctp_user_addto_chunk ( struct sctp_chunk * chunk , int off , int len ,
struct iovec * data )
{
__u8 * target ;
int err = 0 ;
/* Make room in chunk for data. */
target = skb_put ( chunk - > skb , len ) ;
/* Copy data (whole iovec) into chunk */
if ( ( err = memcpy_fromiovecend ( target , data , off , len ) ) )
goto out ;
/* Adjust the chunk length field. */
chunk - > chunk_hdr - > length =
htons ( ntohs ( chunk - > chunk_hdr - > length ) + len ) ;
2007-04-20 07:29:13 +04:00
chunk - > chunk_end = skb_tail_pointer ( chunk - > skb ) ;
2005-04-17 02:20:36 +04:00
out :
return err ;
}
/* Helper function to assign a TSN if needed. This assumes that both
* the data_hdr and association have already been assigned .
*/
void sctp_chunk_assign_ssn ( struct sctp_chunk * chunk )
{
2007-08-03 00:51:42 +04:00
struct sctp_datamsg * msg ;
struct sctp_chunk * lchunk ;
struct sctp_stream * stream ;
2005-04-17 02:20:36 +04:00
__u16 ssn ;
__u16 sid ;
if ( chunk - > has_ssn )
return ;
2007-08-03 00:51:42 +04:00
/* All fragments will be on the same stream */
sid = ntohs ( chunk - > subh . data_hdr - > stream ) ;
stream = & chunk - > asoc - > ssnmap - > out ;
2005-04-17 02:20:36 +04:00
2007-08-03 00:51:42 +04:00
/* Now assign the sequence number to the entire message.
* All fragments must have the same stream sequence number .
*/
msg = chunk - > msg ;
list_for_each_entry ( lchunk , & msg - > chunks , frag_list ) {
if ( lchunk - > chunk_hdr - > flags & SCTP_DATA_UNORDERED ) {
ssn = 0 ;
} else {
if ( lchunk - > chunk_hdr - > flags & SCTP_DATA_LAST_FRAG )
ssn = sctp_ssn_next ( stream , sid ) ;
else
ssn = sctp_ssn_peek ( stream , sid ) ;
}
lchunk - > subh . data_hdr - > ssn = htons ( ssn ) ;
lchunk - > has_ssn = 1 ;
}
2005-04-17 02:20:36 +04:00
}
/* Helper function to assign a TSN if needed. This assumes that both
* the data_hdr and association have already been assigned .
*/
void sctp_chunk_assign_tsn ( struct sctp_chunk * chunk )
{
if ( ! chunk - > has_tsn ) {
/* This is the last possible instant to
* assign a TSN .
*/
chunk - > subh . data_hdr - > tsn =
htonl ( sctp_association_get_next_tsn ( chunk - > asoc ) ) ;
chunk - > has_tsn = 1 ;
}
}
/* Create a CLOSED association to use with an incoming packet. */
struct sctp_association * sctp_make_temp_asoc ( const struct sctp_endpoint * ep ,
2005-07-12 07:57:47 +04:00
struct sctp_chunk * chunk ,
2005-10-07 10:46:04 +04:00
gfp_t gfp )
2005-04-17 02:20:36 +04:00
{
struct sctp_association * asoc ;
struct sk_buff * skb ;
sctp_scope_t scope ;
struct sctp_af * af ;
/* Create the bare association. */
scope = sctp_scope ( sctp_source ( chunk ) ) ;
asoc = sctp_association_new ( ep , ep - > base . sk , scope , gfp ) ;
if ( ! asoc )
goto nodata ;
asoc - > temp = 1 ;
skb = chunk - > skb ;
/* Create an entry for the source address of the packet. */
2007-04-21 09:47:35 +04:00
af = sctp_get_af_specific ( ipver2af ( ip_hdr ( skb ) - > version ) ) ;
2005-04-17 02:20:36 +04:00
if ( unlikely ( ! af ) )
goto fail ;
2006-11-21 04:09:40 +03:00
af - > from_skb ( & asoc - > c . peer_addr , skb , 1 ) ;
2005-04-17 02:20:36 +04:00
nodata :
return asoc ;
fail :
sctp_association_free ( asoc ) ;
return NULL ;
}
/* Build a cookie representing asoc.
* This INCLUDES the param header needed to put the cookie in the INIT ACK .
*/
static sctp_cookie_param_t * sctp_pack_cookie ( const struct sctp_endpoint * ep ,
const struct sctp_association * asoc ,
const struct sctp_chunk * init_chunk ,
int * cookie_len ,
const __u8 * raw_addrs , int addrs_len )
{
sctp_cookie_param_t * retval ;
struct sctp_signed_cookie * cookie ;
struct scatterlist sg ;
int headersize , bodysize ;
unsigned int keylen ;
char * key ;
2006-01-17 22:52:12 +03:00
/* Header size is static data prior to the actual cookie, including
* any padding .
*/
2007-02-09 17:25:18 +03:00
headersize = sizeof ( sctp_paramhdr_t ) +
( sizeof ( struct sctp_signed_cookie ) -
2006-01-17 22:52:12 +03:00
sizeof ( struct sctp_cookie ) ) ;
2005-04-17 02:20:36 +04:00
bodysize = sizeof ( struct sctp_cookie )
+ ntohs ( init_chunk - > chunk_hdr - > length ) + addrs_len ;
/* Pad out the cookie to a multiple to make the signature
* functions simpler to write .
*/
if ( bodysize % SCTP_COOKIE_MULTIPLE )
bodysize + = SCTP_COOKIE_MULTIPLE
- ( bodysize % SCTP_COOKIE_MULTIPLE ) ;
* cookie_len = headersize + bodysize ;
/* Clear this memory since we are sending this data structure
* out on the network .
*/
2006-11-21 06:20:33 +03:00
retval = kzalloc ( * cookie_len , GFP_ATOMIC ) ;
if ( ! retval )
goto nodata ;
2005-04-17 02:20:36 +04:00
cookie = ( struct sctp_signed_cookie * ) retval - > body ;
/* Set up the parameter header. */
retval - > p . type = SCTP_PARAM_STATE_COOKIE ;
retval - > p . length = htons ( * cookie_len ) ;
/* Copy the cookie part of the association itself. */
cookie - > c = asoc - > c ;
/* Save the raw address list length in the cookie. */
cookie - > c . raw_addr_list_len = addrs_len ;
/* Remember PR-SCTP capability. */
cookie - > c . prsctp_capable = asoc - > peer . prsctp_capable ;
2006-12-21 03:07:04 +03:00
/* Save adaptation indication in the cookie. */
cookie - > c . adaptation_ind = asoc - > peer . adaptation_ind ;
2005-04-17 02:20:36 +04:00
/* Set an expiration time for the cookie. */
do_gettimeofday ( & cookie - > c . expiration ) ;
TIMEVAL_ADD ( asoc - > cookie_life , cookie - > c . expiration ) ;
/* Copy the peer's init packet. */
memcpy ( & cookie - > c . peer_init [ 0 ] , init_chunk - > chunk_hdr ,
ntohs ( init_chunk - > chunk_hdr - > length ) ) ;
/* Copy the raw local address list of the association. */
memcpy ( ( __u8 * ) & cookie - > c . peer_init [ 0 ] +
ntohs ( init_chunk - > chunk_hdr - > length ) , raw_addrs , addrs_len ) ;
2007-02-09 17:25:18 +03:00
if ( sctp_sk ( ep - > base . sk ) - > hmac ) {
2006-08-20 09:07:14 +04:00
struct hash_desc desc ;
2005-04-17 02:20:36 +04:00
/* Sign the message. */
sg . page = virt_to_page ( & cookie - > c ) ;
sg . offset = ( unsigned long ) ( & cookie - > c ) % PAGE_SIZE ;
sg . length = bodysize ;
keylen = SCTP_SECRET_SIZE ;
key = ( char * ) ep - > secret_key [ ep - > current_key ] ;
2007-02-09 17:25:18 +03:00
desc . tfm = sctp_sk ( ep - > base . sk ) - > hmac ;
desc . flags = 0 ;
2005-04-17 02:20:36 +04:00
2006-08-20 09:07:14 +04:00
if ( crypto_hash_setkey ( desc . tfm , key , keylen ) | |
crypto_hash_digest ( & desc , & sg , bodysize , cookie - > signature ) )
goto free_cookie ;
2005-04-17 02:20:36 +04:00
}
return retval ;
2006-08-20 09:07:14 +04:00
free_cookie :
kfree ( retval ) ;
nodata :
* cookie_len = 0 ;
return NULL ;
2005-04-17 02:20:36 +04:00
}
/* Unpack the cookie from COOKIE ECHO chunk, recreating the association. */
struct sctp_association * sctp_unpack_cookie (
const struct sctp_endpoint * ep ,
const struct sctp_association * asoc ,
2005-10-07 10:46:04 +04:00
struct sctp_chunk * chunk , gfp_t gfp ,
2005-04-17 02:20:36 +04:00
int * error , struct sctp_chunk * * errp )
{
struct sctp_association * retval = NULL ;
struct sctp_signed_cookie * cookie ;
struct sctp_cookie * bear_cookie ;
int headersize , bodysize , fixed_size ;
2006-01-17 22:55:57 +03:00
__u8 * digest = ep - > digest ;
2005-04-17 02:20:36 +04:00
struct scatterlist sg ;
unsigned int keylen , len ;
char * key ;
sctp_scope_t scope ;
struct sk_buff * skb = chunk - > skb ;
2005-08-15 04:24:31 +04:00
struct timeval tv ;
2006-08-20 09:07:14 +04:00
struct hash_desc desc ;
2005-04-17 02:20:36 +04:00
2006-01-17 22:52:12 +03:00
/* Header size is static data prior to the actual cookie, including
* any padding .
*/
headersize = sizeof ( sctp_chunkhdr_t ) +
2007-02-09 17:25:18 +03:00
( sizeof ( struct sctp_signed_cookie ) -
2006-01-17 22:52:12 +03:00
sizeof ( struct sctp_cookie ) ) ;
2005-04-17 02:20:36 +04:00
bodysize = ntohs ( chunk - > chunk_hdr - > length ) - headersize ;
fixed_size = headersize + sizeof ( struct sctp_cookie ) ;
/* Verify that the chunk looks like it even has a cookie.
* There must be enough room for our cookie and our peer ' s
* INIT chunk .
*/
len = ntohs ( chunk - > chunk_hdr - > length ) ;
if ( len < fixed_size + sizeof ( struct sctp_chunkhdr ) )
goto malformed ;
/* Verify that the cookie has been padded out. */
if ( bodysize % SCTP_COOKIE_MULTIPLE )
goto malformed ;
/* Process the cookie. */
cookie = chunk - > subh . cookie_hdr ;
bear_cookie = & cookie - > c ;
if ( ! sctp_sk ( ep - > base . sk ) - > hmac )
goto no_hmac ;
/* Check the signature. */
keylen = SCTP_SECRET_SIZE ;
sg . page = virt_to_page ( bear_cookie ) ;
sg . offset = ( unsigned long ) ( bear_cookie ) % PAGE_SIZE ;
sg . length = bodysize ;
key = ( char * ) ep - > secret_key [ ep - > current_key ] ;
2006-08-20 09:07:14 +04:00
desc . tfm = sctp_sk ( ep - > base . sk ) - > hmac ;
desc . flags = 0 ;
2005-04-17 02:20:36 +04:00
2006-06-20 14:26:14 +04:00
memset ( digest , 0x00 , SCTP_SIGNATURE_SIZE ) ;
2006-08-20 09:07:14 +04:00
if ( crypto_hash_setkey ( desc . tfm , key , keylen ) | |
crypto_hash_digest ( & desc , & sg , bodysize , digest ) ) {
* error = - SCTP_IERROR_NOMEM ;
goto fail ;
}
2005-04-17 02:20:36 +04:00
if ( memcmp ( digest , cookie - > signature , SCTP_SIGNATURE_SIZE ) ) {
/* Try the previous key. */
key = ( char * ) ep - > secret_key [ ep - > last_key ] ;
2006-06-20 14:26:14 +04:00
memset ( digest , 0x00 , SCTP_SIGNATURE_SIZE ) ;
2006-08-20 09:07:14 +04:00
if ( crypto_hash_setkey ( desc . tfm , key , keylen ) | |
crypto_hash_digest ( & desc , & sg , bodysize , digest ) ) {
* error = - SCTP_IERROR_NOMEM ;
goto fail ;
}
2005-04-17 02:20:36 +04:00
if ( memcmp ( digest , cookie - > signature , SCTP_SIGNATURE_SIZE ) ) {
/* Yikes! Still bad signature! */
* error = - SCTP_IERROR_BAD_SIG ;
goto fail ;
}
}
no_hmac :
/* IG Section 2.35.2:
* 3 ) Compare the port numbers and the verification tag contained
* within the COOKIE ECHO chunk to the actual port numbers and the
* verification tag within the SCTP common header of the received
* packet . If these values do not match the packet MUST be silently
* discarded ,
*/
if ( ntohl ( chunk - > sctp_hdr - > vtag ) ! = bear_cookie - > my_vtag ) {
* error = - SCTP_IERROR_BAD_TAG ;
goto fail ;
}
2006-11-21 04:09:17 +03:00
if ( chunk - > sctp_hdr - > source ! = bear_cookie - > peer_addr . v4 . sin_port | |
2005-04-17 02:20:36 +04:00
ntohs ( chunk - > sctp_hdr - > dest ) ! = bear_cookie - > my_port ) {
* error = - SCTP_IERROR_BAD_PORTS ;
goto fail ;
}
/* Check to see if the cookie is stale. If there is already
* an association , there is no need to check cookie ' s expiration
* for init collision case of lost COOKIE ACK .
2006-09-30 04:10:03 +04:00
* If skb has been timestamped , then use the stamp , otherwise
* use current time . This introduces a small possibility that
* that a cookie may be considered expired , but his would only slow
* down the new association establishment instead of every packet .
2005-04-17 02:20:36 +04:00
*/
2006-09-30 04:10:03 +04:00
if ( sock_flag ( ep - > base . sk , SOCK_TIMESTAMP ) )
skb_get_timestamp ( skb , & tv ) ;
else
do_gettimeofday ( & tv ) ;
2005-08-15 04:24:31 +04:00
if ( ! asoc & & tv_lt ( bear_cookie - > expiration , tv ) ) {
2005-04-17 02:20:36 +04:00
/*
* Section 3.3 .10 .3 Stale Cookie Error ( 3 )
*
* Cause of error
* - - - - - - - - - - - - - - -
* Stale Cookie Error : Indicates the receipt of a valid State
* Cookie that has expired .
*/
len = ntohs ( chunk - > chunk_hdr - > length ) ;
* errp = sctp_make_op_error_space ( asoc , chunk , len ) ;
if ( * errp ) {
2005-08-15 04:24:31 +04:00
suseconds_t usecs = ( tv . tv_sec -
2005-04-17 02:20:36 +04:00
bear_cookie - > expiration . tv_sec ) * 1000000L +
2005-08-15 04:24:31 +04:00
tv . tv_usec - bear_cookie - > expiration . tv_usec ;
2006-11-21 04:27:15 +03:00
__be32 n = htonl ( usecs ) ;
2005-04-17 02:20:36 +04:00
sctp_init_cause ( * errp , SCTP_ERROR_STALE_COOKIE ,
2007-08-21 11:50:01 +04:00
sizeof ( n ) ) ;
sctp_addto_chunk ( * errp , sizeof ( n ) , & n ) ;
2005-04-17 02:20:36 +04:00
* error = - SCTP_IERROR_STALE_COOKIE ;
} else
* error = - SCTP_IERROR_NOMEM ;
goto fail ;
}
/* Make a new base association. */
scope = sctp_scope ( sctp_source ( chunk ) ) ;
retval = sctp_association_new ( ep , ep - > base . sk , scope , gfp ) ;
if ( ! retval ) {
* error = - SCTP_IERROR_NOMEM ;
goto fail ;
}
/* Set up our peer's port number. */
retval - > peer . port = ntohs ( chunk - > sctp_hdr - > source ) ;
/* Populate the association from the cookie. */
memcpy ( & retval - > c , bear_cookie , sizeof ( * bear_cookie ) ) ;
if ( sctp_assoc_set_bind_addr_from_cookie ( retval , bear_cookie ,
GFP_ATOMIC ) < 0 ) {
* error = - SCTP_IERROR_NOMEM ;
goto fail ;
}
/* Also, add the destination address. */
if ( list_empty ( & retval - > base . bind_addr . address_list ) ) {
2006-11-21 04:13:38 +03:00
sctp_add_bind_addr ( & retval - > base . bind_addr , & chunk - > dest , 1 ,
2005-04-17 02:20:36 +04:00
GFP_ATOMIC ) ;
}
retval - > next_tsn = retval - > c . initial_tsn ;
retval - > ctsn_ack_point = retval - > next_tsn - 1 ;
retval - > addip_serial = retval - > c . initial_tsn ;
retval - > adv_peer_ack_point = retval - > ctsn_ack_point ;
retval - > peer . prsctp_capable = retval - > c . prsctp_capable ;
2006-12-21 03:07:04 +03:00
retval - > peer . adaptation_ind = retval - > c . adaptation_ind ;
2005-04-17 02:20:36 +04:00
/* The INIT stuff will be done by the side effects. */
return retval ;
fail :
if ( retval )
sctp_association_free ( retval ) ;
return NULL ;
malformed :
/* Yikes! The packet is either corrupt or deliberately
* malformed .
*/
* error = - SCTP_IERROR_MALFORMED ;
goto fail ;
}
/********************************************************************
* 3 rd Level Abstractions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct __sctp_missing {
2006-11-21 04:26:34 +03:00
__be32 num_missing ;
__be16 type ;
2005-04-17 02:20:36 +04:00
} __attribute__ ( ( packed ) ) ;
/*
* Report a missing mandatory parameter .
*/
static int sctp_process_missing_param ( const struct sctp_association * asoc ,
sctp_param_t paramtype ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
struct __sctp_missing report ;
__u16 len ;
len = WORD_ROUND ( sizeof ( report ) ) ;
/* Make an ERROR chunk, preparing enough room for
* returning multiple unknown parameters .
*/
if ( ! * errp )
* errp = sctp_make_op_error_space ( asoc , chunk , len ) ;
if ( * errp ) {
report . num_missing = htonl ( 1 ) ;
report . type = paramtype ;
2007-01-16 06:12:31 +03:00
sctp_init_cause ( * errp , SCTP_ERROR_MISS_PARAM ,
2007-08-21 11:50:01 +04:00
sizeof ( report ) ) ;
sctp_addto_chunk ( * errp , sizeof ( report ) , & report ) ;
2005-04-17 02:20:36 +04:00
}
/* Stop processing this chunk. */
return 0 ;
}
/* Report an Invalid Mandatory Parameter. */
static int sctp_process_inv_mandatory ( const struct sctp_association * asoc ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
/* Invalid Mandatory Parameter Error has no payload. */
if ( ! * errp )
* errp = sctp_make_op_error_space ( asoc , chunk , 0 ) ;
if ( * errp )
2007-08-21 11:50:01 +04:00
sctp_init_cause ( * errp , SCTP_ERROR_INV_PARAM , 0 ) ;
2005-04-17 02:20:36 +04:00
/* Stop processing this chunk. */
return 0 ;
}
static int sctp_process_inv_paramlength ( const struct sctp_association * asoc ,
struct sctp_paramhdr * param ,
const struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
char error [ ] = " The following parameter had invalid length: " ;
2007-02-09 17:25:18 +03:00
size_t payload_len = WORD_ROUND ( sizeof ( error ) ) +
2005-04-17 02:20:36 +04:00
sizeof ( sctp_paramhdr_t ) ;
/* Create an error chunk and fill it in with our payload. */
if ( ! * errp )
* errp = sctp_make_op_error_space ( asoc , chunk , payload_len ) ;
if ( * errp ) {
2007-08-21 11:50:01 +04:00
sctp_init_cause ( * errp , SCTP_ERROR_PROTO_VIOLATION ,
sizeof ( error ) + sizeof ( sctp_paramhdr_t ) ) ;
sctp_addto_chunk ( * errp , sizeof ( error ) , error ) ;
sctp_addto_param ( * errp , sizeof ( sctp_paramhdr_t ) , param ) ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
/* Do not attempt to handle the HOST_NAME parm. However, do
* send back an indicator to the peer .
*/
static int sctp_process_hn_param ( const struct sctp_association * asoc ,
union sctp_params param ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
__u16 len = ntohs ( param . p - > length ) ;
/* Make an ERROR chunk. */
if ( ! * errp )
* errp = sctp_make_op_error_space ( asoc , chunk , len ) ;
2007-08-21 11:50:01 +04:00
if ( * errp ) {
sctp_init_cause ( * errp , SCTP_ERROR_DNS_FAILED , len ) ;
sctp_addto_chunk ( * errp , len , param . v ) ;
}
2005-04-17 02:20:36 +04:00
/* Stop processing this chunk. */
return 0 ;
}
/* RFC 3.2.1 & the Implementers Guide 2.2.
*
* The Parameter Types are encoded such that the
* highest - order two bits specify the action that must be
* taken if the processing endpoint does not recognize the
* Parameter Type .
*
* 00 - Stop processing this SCTP chunk and discard it ,
* do not process any further chunks within it .
*
* 01 - Stop processing this SCTP chunk and discard it ,
* do not process any further chunks within it , and report
* the unrecognized parameter in an ' Unrecognized
* Parameter Type ' ( in either an ERROR or in the INIT ACK ) .
*
* 10 - Skip this parameter and continue processing .
*
* 11 - Skip this parameter and continue processing but
* report the unrecognized parameter in an
* ' Unrecognized Parameter Type ' ( in either an ERROR or in
* the INIT ACK ) .
*
* Return value :
* 0 - discard the chunk
* 1 - continue with the chunk
*/
static int sctp_process_unk_param ( const struct sctp_association * asoc ,
union sctp_params param ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
int retval = 1 ;
switch ( param . p - > type & SCTP_PARAM_ACTION_MASK ) {
case SCTP_PARAM_ACTION_DISCARD :
retval = 0 ;
break ;
case SCTP_PARAM_ACTION_DISCARD_ERR :
retval = 0 ;
/* Make an ERROR chunk, preparing enough room for
* returning multiple unknown parameters .
*/
if ( NULL = = * errp )
* errp = sctp_make_op_error_space ( asoc , chunk ,
ntohs ( chunk - > chunk_hdr - > length ) ) ;
2007-08-21 11:50:01 +04:00
if ( * errp ) {
2005-04-17 02:20:36 +04:00
sctp_init_cause ( * errp , SCTP_ERROR_UNKNOWN_PARAM ,
WORD_ROUND ( ntohs ( param . p - > length ) ) ) ;
2007-08-21 11:50:01 +04:00
sctp_addto_chunk ( * errp ,
WORD_ROUND ( ntohs ( param . p - > length ) ) ,
param . v ) ;
}
2005-04-17 02:20:36 +04:00
break ;
case SCTP_PARAM_ACTION_SKIP :
break ;
case SCTP_PARAM_ACTION_SKIP_ERR :
/* Make an ERROR chunk, preparing enough room for
* returning multiple unknown parameters .
*/
if ( NULL = = * errp )
* errp = sctp_make_op_error_space ( asoc , chunk ,
ntohs ( chunk - > chunk_hdr - > length ) ) ;
if ( * errp ) {
sctp_init_cause ( * errp , SCTP_ERROR_UNKNOWN_PARAM ,
WORD_ROUND ( ntohs ( param . p - > length ) ) ) ;
2007-08-21 11:50:01 +04:00
sctp_addto_chunk ( * errp ,
WORD_ROUND ( ntohs ( param . p - > length ) ) ,
param . v ) ;
2005-04-17 02:20:36 +04:00
} else {
/* If there is no memory for generating the ERROR
* report as specified , an ABORT will be triggered
* to the peer and the association won ' t be
* established .
*/
retval = 0 ;
}
break ;
default :
break ;
}
return retval ;
}
/* Find unrecognized parameters in the chunk.
* Return values :
* 0 - discard the chunk
* 1 - continue with the chunk
*/
static int sctp_verify_param ( const struct sctp_association * asoc ,
union sctp_params param ,
sctp_cid_t cid ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * err_chunk )
{
int retval = 1 ;
/* FIXME - This routine is not looking at each parameter per the
* chunk type , i . e . , unrecognized parameters should be further
* identified based on the chunk id .
*/
switch ( param . p - > type ) {
case SCTP_PARAM_IPV4_ADDRESS :
case SCTP_PARAM_IPV6_ADDRESS :
case SCTP_PARAM_COOKIE_PRESERVATIVE :
case SCTP_PARAM_SUPPORTED_ADDRESS_TYPES :
case SCTP_PARAM_STATE_COOKIE :
case SCTP_PARAM_HEARTBEAT_INFO :
case SCTP_PARAM_UNRECOGNIZED_PARAMETERS :
case SCTP_PARAM_ECN_CAPABLE :
2006-12-21 03:07:04 +03:00
case SCTP_PARAM_ADAPTATION_LAYER_IND :
2005-04-17 02:20:36 +04:00
break ;
case SCTP_PARAM_HOST_NAME_ADDRESS :
/* Tell the peer, we won't support this param. */
return sctp_process_hn_param ( asoc , param , chunk , err_chunk ) ;
case SCTP_PARAM_FWD_TSN_SUPPORT :
if ( sctp_prsctp_enable )
break ;
2007-02-09 17:25:18 +03:00
/* Fall Through */
2005-04-17 02:20:36 +04:00
default :
SCTP_DEBUG_PRINTK ( " Unrecognized param: %d for chunk %d. \n " ,
ntohs ( param . p - > type ) , cid ) ;
return sctp_process_unk_param ( asoc , param , chunk , err_chunk ) ;
break ;
}
return retval ;
}
/* Verify the INIT packet before we process it. */
int sctp_verify_init ( const struct sctp_association * asoc ,
sctp_cid_t cid ,
sctp_init_chunk_t * peer_init ,
struct sctp_chunk * chunk ,
struct sctp_chunk * * errp )
{
union sctp_params param ;
int has_cookie = 0 ;
/* Verify stream values are non-zero. */
if ( ( 0 = = peer_init - > init_hdr . num_outbound_streams ) | |
2007-01-16 06:15:45 +03:00
( 0 = = peer_init - > init_hdr . num_inbound_streams ) | |
( 0 = = peer_init - > init_hdr . init_tag ) | |
( SCTP_DEFAULT_MINWINDOW > ntohl ( peer_init - > init_hdr . a_rwnd ) ) ) {
2005-04-17 02:20:36 +04:00
sctp_process_inv_mandatory ( asoc , chunk , errp ) ;
return 0 ;
}
/* Check for missing mandatory parameters. */
sctp_walk_params ( param , peer_init , init_hdr . params ) {
if ( SCTP_PARAM_STATE_COOKIE = = param . p - > type )
has_cookie = 1 ;
} /* for (loop through all parameters) */
/* There is a possibility that a parameter length was bad and
* in that case we would have stoped walking the parameters .
* The current param . p would point at the bad one .
* Current consensus on the mailing list is to generate a PROTOCOL
* VIOLATION error . We build the ERROR chunk here and let the normal
* error handling code build and send the packet .
*/
if ( param . v < ( void * ) chunk - > chunk_end - sizeof ( sctp_paramhdr_t ) ) {
sctp_process_inv_paramlength ( asoc , param . p , chunk , errp ) ;
return 0 ;
}
/* The only missing mandatory param possible today is
* the state cookie for an INIT - ACK chunk .
*/
if ( ( SCTP_CID_INIT_ACK = = cid ) & & ! has_cookie ) {
sctp_process_missing_param ( asoc , SCTP_PARAM_STATE_COOKIE ,
chunk , errp ) ;
return 0 ;
}
/* Find unrecognized parameters. */
sctp_walk_params ( param , peer_init , init_hdr . params ) {
if ( ! sctp_verify_param ( asoc , param , cid , chunk , errp ) ) {
if ( SCTP_PARAM_HOST_NAME_ADDRESS = = param . p - > type )
return 0 ;
else
return 1 ;
}
} /* for (loop through all parameters) */
return 1 ;
}
/* Unpack the parameters in an INIT packet into an association.
* Returns 0 on failure , else success .
* FIXME : This is an association method .
*/
int sctp_process_init ( struct sctp_association * asoc , sctp_cid_t cid ,
const union sctp_addr * peer_addr ,
2005-10-07 10:46:04 +04:00
sctp_init_chunk_t * peer_init , gfp_t gfp )
2005-04-17 02:20:36 +04:00
{
union sctp_params param ;
struct sctp_transport * transport ;
struct list_head * pos , * temp ;
char * cookie ;
/* We must include the address that the INIT packet came from.
* This is the only address that matters for an INIT packet .
* When processing a COOKIE ECHO , we retrieve the from address
* of the INIT from the cookie .
*/
/* This implementation defaults to making the first transport
* added as the primary transport . The source address seems to
* be a a better choice than any of the embedded addresses .
*/
2006-11-21 04:10:20 +03:00
if ( peer_addr ) {
2006-11-21 04:12:25 +03:00
if ( ! sctp_assoc_add_peer ( asoc , peer_addr , gfp , SCTP_ACTIVE ) )
2005-04-17 02:20:36 +04:00
goto nomem ;
2006-11-21 04:10:20 +03:00
}
2005-04-17 02:20:36 +04:00
/* Process the initialization parameters. */
sctp_walk_params ( param , peer_init , init_hdr . params ) {
if ( ! sctp_process_param ( asoc , param , peer_addr , gfp ) )
2007-02-09 17:25:18 +03:00
goto clean_up ;
2005-04-17 02:20:36 +04:00
}
2005-06-21 00:14:57 +04:00
/* Walk list of transports, removing transports in the UNKNOWN state. */
list_for_each_safe ( pos , temp , & asoc - > peer . transport_addr_list ) {
transport = list_entry ( pos , struct sctp_transport , transports ) ;
if ( transport - > state = = SCTP_UNKNOWN ) {
sctp_assoc_rm_peer ( asoc , transport ) ;
}
}
2005-04-17 02:20:36 +04:00
/* The fixed INIT headers are always in network byte
* order .
*/
asoc - > peer . i . init_tag =
ntohl ( peer_init - > init_hdr . init_tag ) ;
asoc - > peer . i . a_rwnd =
ntohl ( peer_init - > init_hdr . a_rwnd ) ;
asoc - > peer . i . num_outbound_streams =
ntohs ( peer_init - > init_hdr . num_outbound_streams ) ;
asoc - > peer . i . num_inbound_streams =
ntohs ( peer_init - > init_hdr . num_inbound_streams ) ;
asoc - > peer . i . initial_tsn =
ntohl ( peer_init - > init_hdr . initial_tsn ) ;
/* Apply the upper bounds for output streams based on peer's
* number of inbound streams .
*/
if ( asoc - > c . sinit_num_ostreams >
ntohs ( peer_init - > init_hdr . num_inbound_streams ) ) {
asoc - > c . sinit_num_ostreams =
ntohs ( peer_init - > init_hdr . num_inbound_streams ) ;
}
if ( asoc - > c . sinit_max_instreams >
ntohs ( peer_init - > init_hdr . num_outbound_streams ) ) {
asoc - > c . sinit_max_instreams =
ntohs ( peer_init - > init_hdr . num_outbound_streams ) ;
}
/* Copy Initiation tag from INIT to VT_peer in cookie. */
asoc - > c . peer_vtag = asoc - > peer . i . init_tag ;
/* Peer Rwnd : Current calculated value of the peer's rwnd. */
asoc - > peer . rwnd = asoc - > peer . i . a_rwnd ;
/* Copy cookie in case we need to resend COOKIE-ECHO. */
cookie = asoc - > peer . cookie ;
if ( cookie ) {
2006-11-21 06:20:33 +03:00
asoc - > peer . cookie = kmemdup ( cookie , asoc - > peer . cookie_len , gfp ) ;
2005-04-17 02:20:36 +04:00
if ( ! asoc - > peer . cookie )
goto clean_up ;
}
/* RFC 2960 7.2.1 The initial value of ssthresh MAY be arbitrarily
* high ( for example , implementations MAY use the size of the receiver
* advertised window ) .
*/
list_for_each ( pos , & asoc - > peer . transport_addr_list ) {
transport = list_entry ( pos , struct sctp_transport , transports ) ;
transport - > ssthresh = asoc - > peer . i . a_rwnd ;
}
/* Set up the TSN tracking pieces. */
sctp_tsnmap_init ( & asoc - > peer . tsn_map , SCTP_TSN_MAP_SIZE ,
asoc - > peer . i . initial_tsn ) ;
/* RFC 2960 6.5 Stream Identifier and Stream Sequence Number
*
* The stream sequence number in all the streams shall start
* from 0 when the association is established . Also , when the
* stream sequence number reaches the value 65535 the next
* stream sequence number shall be set to 0.
*/
2005-06-21 00:14:57 +04:00
/* Allocate storage for the negotiated streams if it is not a temporary
2007-02-09 17:25:18 +03:00
* association .
2005-04-17 02:20:36 +04:00
*/
if ( ! asoc - > temp ) {
int error ;
asoc - > ssnmap = sctp_ssnmap_new ( asoc - > c . sinit_max_instreams ,
asoc - > c . sinit_num_ostreams , gfp ) ;
if ( ! asoc - > ssnmap )
goto clean_up ;
2007-05-05 00:55:27 +04:00
error = sctp_assoc_set_id ( asoc , gfp ) ;
if ( error )
2005-04-17 02:20:36 +04:00
goto clean_up ;
}
/* ADDIP Section 4.1 ASCONF Chunk Procedures
*
* When an endpoint has an ASCONF signaled change to be sent to the
* remote endpoint it should do the following :
* . . .
* A2 ) A serial number should be assigned to the Chunk . The serial
* number should be a monotonically increasing number . All serial
* numbers are defined to be initialized at the start of the
* association to the same value as the Initial TSN .
*/
asoc - > peer . addip_serial = asoc - > peer . i . initial_tsn - 1 ;
return 1 ;
clean_up :
/* Release the transport structures. */
list_for_each_safe ( pos , temp , & asoc - > peer . transport_addr_list ) {
transport = list_entry ( pos , struct sctp_transport , transports ) ;
list_del_init ( pos ) ;
sctp_transport_free ( transport ) ;
}
2005-06-21 00:14:57 +04:00
asoc - > peer . transport_count = 0 ;
2005-04-17 02:20:36 +04:00
nomem :
return 0 ;
}
/* Update asoc with the option described in param.
*
* RFC2960 3.3 .2 .1 Optional / Variable Length Parameters in INIT
*
* asoc is the association to update .
* param is the variable length parameter to use for update .
* cid tells us if this is an INIT , INIT ACK or COOKIE ECHO .
* If the current packet is an INIT we want to minimize the amount of
* work we do . In particular , we should not build transport
* structures for the addresses .
*/
static int sctp_process_param ( struct sctp_association * asoc ,
union sctp_params param ,
const union sctp_addr * peer_addr ,
2005-10-07 10:46:04 +04:00
gfp_t gfp )
2005-04-17 02:20:36 +04:00
{
union sctp_addr addr ;
int i ;
__u16 sat ;
int retval = 1 ;
sctp_scope_t scope ;
time_t stale ;
struct sctp_af * af ;
/* We maintain all INIT parameters in network byte order all the
* time . This allows us to not worry about whether the parameters
* came from a fresh INIT , and INIT ACK , or were stored in a cookie .
*/
switch ( param . p - > type ) {
case SCTP_PARAM_IPV6_ADDRESS :
if ( PF_INET6 ! = asoc - > base . sk - > sk_family )
break ;
/* Fall through. */
case SCTP_PARAM_IPV4_ADDRESS :
af = sctp_get_af_specific ( param_type2af ( param . p - > type ) ) ;
2006-11-21 04:11:13 +03:00
af - > from_addr_param ( & addr , param . addr , htons ( asoc - > peer . port ) , 0 ) ;
2005-04-17 02:20:36 +04:00
scope = sctp_scope ( peer_addr ) ;
2006-11-21 04:11:13 +03:00
if ( sctp_in_scope ( & addr , scope ) )
if ( ! sctp_assoc_add_peer ( asoc , & addr , gfp , SCTP_UNCONFIRMED ) )
2005-04-17 02:20:36 +04:00
return 0 ;
break ;
case SCTP_PARAM_COOKIE_PRESERVATIVE :
if ( ! sctp_cookie_preserve_enable )
break ;
stale = ntohl ( param . life - > lifespan_increment ) ;
/* Suggested Cookie Life span increment's unit is msec,
* ( 1 / 1000 sec ) .
*/
asoc - > cookie_life . tv_sec + = stale / 1000 ;
asoc - > cookie_life . tv_usec + = ( stale % 1000 ) * 1000 ;
break ;
case SCTP_PARAM_HOST_NAME_ADDRESS :
SCTP_DEBUG_PRINTK ( " unimplemented SCTP_HOST_NAME_ADDRESS \n " ) ;
break ;
case SCTP_PARAM_SUPPORTED_ADDRESS_TYPES :
/* Turn off the default values first so we'll know which
* ones are really set by the peer .
*/
asoc - > peer . ipv4_address = 0 ;
asoc - > peer . ipv6_address = 0 ;
/* Cycle through address types; avoid divide by 0. */
sat = ntohs ( param . p - > length ) - sizeof ( sctp_paramhdr_t ) ;
if ( sat )
sat / = sizeof ( __u16 ) ;
for ( i = 0 ; i < sat ; + + i ) {
switch ( param . sat - > types [ i ] ) {
case SCTP_PARAM_IPV4_ADDRESS :
asoc - > peer . ipv4_address = 1 ;
break ;
case SCTP_PARAM_IPV6_ADDRESS :
asoc - > peer . ipv6_address = 1 ;
break ;
case SCTP_PARAM_HOST_NAME_ADDRESS :
asoc - > peer . hostname_address = 1 ;
break ;
default : /* Just ignore anything else. */
break ;
2007-04-21 04:09:22 +04:00
}
2005-04-17 02:20:36 +04:00
}
break ;
case SCTP_PARAM_STATE_COOKIE :
asoc - > peer . cookie_len =
ntohs ( param . p - > length ) - sizeof ( sctp_paramhdr_t ) ;
asoc - > peer . cookie = param . cookie - > body ;
break ;
case SCTP_PARAM_HEARTBEAT_INFO :
/* Would be odd to receive, but it causes no problems. */
break ;
case SCTP_PARAM_UNRECOGNIZED_PARAMETERS :
/* Rejected during verify stage. */
break ;
case SCTP_PARAM_ECN_CAPABLE :
asoc - > peer . ecn_capable = 1 ;
break ;
2006-12-21 03:07:04 +03:00
case SCTP_PARAM_ADAPTATION_LAYER_IND :
asoc - > peer . adaptation_ind = param . aind - > adaptation_ind ;
2005-04-17 02:20:36 +04:00
break ;
case SCTP_PARAM_FWD_TSN_SUPPORT :
if ( sctp_prsctp_enable ) {
asoc - > peer . prsctp_capable = 1 ;
break ;
}
2007-02-09 17:25:18 +03:00
/* Fall Through */
2005-04-17 02:20:36 +04:00
default :
/* Any unrecognized parameters should have been caught
* and handled by sctp_verify_param ( ) which should be
* called prior to this routine . Simply log the error
* here .
*/
SCTP_DEBUG_PRINTK ( " Ignoring param: %d for association %p. \n " ,
ntohs ( param . p - > type ) , asoc ) ;
break ;
2007-04-21 04:09:22 +04:00
}
2005-04-17 02:20:36 +04:00
return retval ;
}
/* Select a new verification tag. */
__u32 sctp_generate_tag ( const struct sctp_endpoint * ep )
{
/* I believe that this random number generator complies with RFC1750.
* A tag of 0 is reserved for special cases ( e . g . INIT ) .
*/
__u32 x ;
do {
get_random_bytes ( & x , sizeof ( __u32 ) ) ;
} while ( x = = 0 ) ;
return x ;
}
/* Select an initial TSN to send during startup. */
__u32 sctp_generate_tsn ( const struct sctp_endpoint * ep )
{
__u32 retval ;
get_random_bytes ( & retval , sizeof ( __u32 ) ) ;
return retval ;
}
/*
* ADDIP 3.1 .1 Address Configuration Change Chunk ( ASCONF )
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 0xC1 | Chunk Flags | Chunk Length |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Serial Number |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Address Parameter |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF Parameter # 1 |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* \ \
* / . . . . /
* \ \
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF Parameter # N |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
2007-02-09 17:25:18 +03:00
* Address Parameter and other parameter will not be wrapped in this function
2005-04-17 02:20:36 +04:00
*/
static struct sctp_chunk * sctp_make_asconf ( struct sctp_association * asoc ,
union sctp_addr * addr ,
int vparam_len )
{
sctp_addiphdr_t asconf ;
struct sctp_chunk * retval ;
int length = sizeof ( asconf ) + vparam_len ;
union sctp_addr_param addrparam ;
int addrlen ;
struct sctp_af * af = sctp_get_af_specific ( addr - > v4 . sin_family ) ;
addrlen = af - > to_addr_param ( addr , & addrparam ) ;
if ( ! addrlen )
return NULL ;
length + = addrlen ;
/* Create the chunk. */
retval = sctp_make_chunk ( asoc , SCTP_CID_ASCONF , 0 , length ) ;
if ( ! retval )
return NULL ;
asconf . serial = htonl ( asoc - > addip_serial + + ) ;
retval - > subh . addip_hdr =
sctp_addto_chunk ( retval , sizeof ( asconf ) , & asconf ) ;
retval - > param_hdr . v =
sctp_addto_chunk ( retval , addrlen , & addrparam ) ;
return retval ;
}
/* ADDIP
* 3.2 .1 Add IP Address
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 0xC001 | Length = Variable |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF - Request Correlation ID |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Address Parameter |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
* 3.2 .2 Delete IP Address
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 0xC002 | Length = Variable |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF - Request Correlation ID |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Address Parameter |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
*/
struct sctp_chunk * sctp_make_asconf_update_ip ( struct sctp_association * asoc ,
union sctp_addr * laddr ,
struct sockaddr * addrs ,
int addrcnt ,
2006-11-21 04:01:42 +03:00
__be16 flags )
2005-04-17 02:20:36 +04:00
{
sctp_addip_param_t param ;
struct sctp_chunk * retval ;
union sctp_addr_param addr_param ;
union sctp_addr * addr ;
void * addr_buf ;
struct sctp_af * af ;
int paramlen = sizeof ( param ) ;
int addr_param_len = 0 ;
int totallen = 0 ;
int i ;
/* Get total length of all the address parameters. */
addr_buf = addrs ;
for ( i = 0 ; i < addrcnt ; i + + ) {
addr = ( union sctp_addr * ) addr_buf ;
af = sctp_get_af_specific ( addr - > v4 . sin_family ) ;
addr_param_len = af - > to_addr_param ( addr , & addr_param ) ;
totallen + = paramlen ;
totallen + = addr_param_len ;
addr_buf + = af - > sockaddr_len ;
}
/* Create an asconf chunk with the required length. */
retval = sctp_make_asconf ( asoc , laddr , totallen ) ;
if ( ! retval )
return NULL ;
/* Add the address parameters to the asconf chunk. */
addr_buf = addrs ;
for ( i = 0 ; i < addrcnt ; i + + ) {
addr = ( union sctp_addr * ) addr_buf ;
af = sctp_get_af_specific ( addr - > v4 . sin_family ) ;
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 = i ;
sctp_addto_chunk ( retval , paramlen , & param ) ;
sctp_addto_chunk ( retval , addr_param_len , & addr_param ) ;
addr_buf + = af - > sockaddr_len ;
}
return retval ;
}
/* ADDIP
* 3.2 .4 Set Primary IP Address
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 0xC004 | Length = Variable |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF - Request Correlation ID |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Address Parameter |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
2007-02-09 17:25:18 +03:00
* Create an ASCONF chunk with Set Primary IP address parameter .
2005-04-17 02:20:36 +04:00
*/
struct sctp_chunk * sctp_make_asconf_set_prim ( struct sctp_association * asoc ,
union sctp_addr * addr )
{
sctp_addip_param_t param ;
struct sctp_chunk * retval ;
int len = sizeof ( param ) ;
union sctp_addr_param addrparam ;
int addrlen ;
struct sctp_af * af = sctp_get_af_specific ( addr - > v4 . sin_family ) ;
addrlen = af - > to_addr_param ( addr , & addrparam ) ;
if ( ! addrlen )
return NULL ;
len + = addrlen ;
/* Create the chunk and make asconf header. */
retval = sctp_make_asconf ( asoc , addr , len ) ;
if ( ! retval )
return NULL ;
param . param_hdr . type = SCTP_PARAM_SET_PRIMARY ;
param . param_hdr . length = htons ( len ) ;
param . crr_id = 0 ;
sctp_addto_chunk ( retval , sizeof ( param ) , & param ) ;
sctp_addto_chunk ( retval , addrlen , & addrparam ) ;
return retval ;
}
/* ADDIP 3.1.2 Address Configuration Acknowledgement Chunk (ASCONF-ACK)
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Type = 0x80 | Chunk Flags | Chunk Length |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | Serial Number |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF Parameter Response # 1 |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* \ \
* / . . . . /
* \ \
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
* | ASCONF Parameter Response # N |
* + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
*
2007-02-09 17:25:18 +03:00
* Create an ASCONF_ACK chunk with enough space for the parameter responses .
2005-04-17 02:20:36 +04:00
*/
static 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 ;
int length = sizeof ( asconf ) + vparam_len ;
/* Create the chunk. */
retval = sctp_make_chunk ( asoc , SCTP_CID_ASCONF_ACK , 0 , length ) ;
if ( ! retval )
return NULL ;
asconf . serial = htonl ( serial ) ;
retval - > subh . addip_hdr =
sctp_addto_chunk ( retval , sizeof ( asconf ) , & asconf ) ;
return retval ;
}
/* Add response parameters to an ASCONF_ACK chunk. */
2006-11-21 04:26:34 +03:00
static void sctp_add_asconf_response ( struct sctp_chunk * chunk , __be32 crr_id ,
2006-11-21 04:01:42 +03:00
__be16 err_code , sctp_addip_param_t * asconf_param )
2005-04-17 02:20:36 +04:00
{
sctp_addip_param_t ack_param ;
sctp_errhdr_t err_param ;
int asconf_param_len = 0 ;
int err_param_len = 0 ;
2006-11-21 04:01:42 +03:00
__be16 response_type ;
2005-04-17 02:20:36 +04:00
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 ) ;
}
2007-02-09 17:25:18 +03:00
/* Add Success Indication or Error Cause Indication parameter. */
2005-04-17 02:20:36 +04:00
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. */
2006-11-21 04:01:42 +03:00
static __be16 sctp_process_asconf_param ( struct sctp_association * asoc ,
2005-04-17 02:20:36 +04:00
struct sctp_chunk * asconf ,
sctp_addip_param_t * asconf_param )
{
struct sctp_transport * peer ;
struct sctp_af * af ;
union sctp_addr addr ;
struct list_head * pos ;
union sctp_addr_param * addr_param ;
2006-11-21 04:05:23 +03:00
2005-04-17 02:20:36 +04:00
addr_param = ( union sctp_addr_param * )
( ( void * ) asconf_param + sizeof ( sctp_addip_param_t ) ) ;
af = sctp_get_af_specific ( param_type2af ( addr_param - > v4 . param_hdr . type ) ) ;
if ( unlikely ( ! af ) )
return SCTP_ERROR_INV_PARAM ;
2006-11-21 04:11:13 +03:00
af - > from_addr_param ( & addr , addr_param , htons ( asoc - > peer . port ) , 0 ) ;
2005-04-17 02:20:36 +04:00
switch ( asconf_param - > param_hdr . type ) {
case SCTP_PARAM_ADD_IP :
/* ADDIP 4.3 D9) If an endpoint receives an ADD IP address
2007-02-09 17:25:18 +03:00
* 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 ' .
*/
2005-04-17 02:20:36 +04:00
2006-11-21 04:11:13 +03:00
peer = sctp_assoc_add_peer ( asoc , & addr , GFP_ATOMIC , SCTP_UNCONFIRMED ) ;
2005-04-17 02:20:36 +04:00
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
2007-02-09 17:25:18 +03:00
* 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 ' .
*/
2005-04-17 02:20:36 +04:00
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 '
*/
2006-11-21 04:12:25 +03:00
if ( sctp_cmp_addr_exact ( sctp_source ( asconf ) , & addr ) )
2005-04-17 02:20:36 +04:00
return SCTP_ERROR_DEL_SRC_IP ;
2006-11-21 04:11:13 +03:00
sctp_assoc_del_peer ( asoc , & addr ) ;
2005-04-17 02:20:36 +04:00
break ;
case SCTP_PARAM_SET_PRIMARY :
2006-11-21 04:11:13 +03:00
peer = sctp_assoc_lookup_paddr ( asoc , & addr ) ;
2005-04-17 02:20:36 +04:00
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 ;
}
2007-02-09 17:25:18 +03:00
/* Process an incoming ASCONF chunk with the next expected serial no. and
2005-04-17 02:20:36 +04:00
* return an ASCONF_ACK chunk to be sent in response .
*/
struct sctp_chunk * sctp_process_asconf ( struct sctp_association * asoc ,
struct sctp_chunk * asconf )
{
sctp_addiphdr_t * hdr ;
union sctp_addr_param * addr_param ;
sctp_addip_param_t * asconf_param ;
struct sctp_chunk * asconf_ack ;
2006-11-21 04:01:42 +03:00
__be16 err_code ;
2005-04-17 02:20:36 +04:00
int length = 0 ;
int chunk_len = asconf - > skb - > len ;
__u32 serial ;
int all_param_pass = 1 ;
hdr = ( sctp_addiphdr_t * ) asconf - > skb - > data ;
serial = ntohl ( hdr - > serial ) ;
2007-02-09 17:25:18 +03:00
/* Skip the addiphdr and store a pointer to address parameter. */
2005-04-17 02:20:36 +04:00
length = sizeof ( sctp_addiphdr_t ) ;
addr_param = ( union sctp_addr_param * ) ( asconf - > skb - > data + length ) ;
chunk_len - = length ;
/* Skip the address parameter and store a pointer to the first
* asconf paramter .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
length = ntohs ( addr_param - > v4 . param_hdr . length ) ;
asconf_param = ( sctp_addip_param_t * ) ( ( void * ) addr_param + length ) ;
chunk_len - = length ;
2007-02-09 17:25:18 +03:00
/* create an ASCONF_ACK chunk.
2005-04-17 02:20:36 +04:00
* 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
* paramters .
*/
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 ) {
err_code = sctp_process_asconf_param ( asoc , asconf ,
asconf_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
2007-02-09 17:25:18 +03:00
* in the ASCONF .
2005-04-17 02:20:36 +04:00
*/
if ( SCTP_ERROR_RSRC_LOW = = err_code )
goto done ;
/* Move to the next ASCONF param. */
length = ntohs ( asconf_param - > param_hdr . length ) ;
asconf_param = ( sctp_addip_param_t * ) ( ( void * ) asconf_param +
length ) ;
chunk_len - = length ;
}
2007-02-09 17:25:18 +03:00
2005-04-17 02:20:36 +04:00
done :
asoc - > peer . addip_serial + + ;
/* If we are sending a new ASCONF_ACK hold a reference to it in assoc
2007-02-09 17:25:18 +03:00
* after freeing the reference to old asconf ack if any .
2005-04-17 02:20:36 +04:00
*/
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 ;
}
/* Process a asconf parameter that is successfully acked. */
static int sctp_asconf_param_success ( struct sctp_association * asoc ,
sctp_addip_param_t * asconf_param )
{
struct sctp_af * af ;
union sctp_addr addr ;
struct sctp_bind_addr * bp = & asoc - > base . bind_addr ;
union sctp_addr_param * addr_param ;
struct list_head * pos ;
struct sctp_transport * transport ;
2006-07-22 01:49:25 +04:00
struct sctp_sockaddr_entry * saddr ;
2005-04-17 02:20:36 +04:00
int retval = 0 ;
addr_param = ( union sctp_addr_param * )
( ( void * ) asconf_param + sizeof ( sctp_addip_param_t ) ) ;
/* We have checked the packet before, so we do not check again. */
af = sctp_get_af_specific ( param_type2af ( addr_param - > v4 . param_hdr . type ) ) ;
2006-11-21 04:11:13 +03:00
af - > from_addr_param ( & addr , addr_param , htons ( bp - > port ) , 0 ) ;
2005-04-17 02:20:36 +04:00
switch ( asconf_param - > param_hdr . type ) {
case SCTP_PARAM_ADD_IP :
sctp_local_bh_disable ( ) ;
sctp_write_lock ( & asoc - > base . addr_lock ) ;
2006-07-22 01:49:25 +04:00
list_for_each ( pos , & bp - > address_list ) {
saddr = list_entry ( pos , struct sctp_sockaddr_entry , list ) ;
2006-11-21 04:11:13 +03:00
if ( sctp_cmp_addr_exact ( & saddr - > a , & addr ) )
2006-07-22 01:49:25 +04:00
saddr - > use_as_src = 1 ;
}
2005-04-17 02:20:36 +04:00
sctp_write_unlock ( & asoc - > base . addr_lock ) ;
sctp_local_bh_enable ( ) ;
break ;
case SCTP_PARAM_DEL_IP :
sctp_local_bh_disable ( ) ;
sctp_write_lock ( & asoc - > base . addr_lock ) ;
2006-11-21 04:11:13 +03:00
retval = sctp_del_bind_addr ( bp , & addr ) ;
2005-04-17 02:20:36 +04:00
sctp_write_unlock ( & asoc - > base . addr_lock ) ;
sctp_local_bh_enable ( ) ;
list_for_each ( pos , & asoc - > peer . transport_addr_list ) {
transport = list_entry ( pos , struct sctp_transport ,
transports ) ;
2006-07-22 01:49:25 +04:00
dst_release ( transport - > dst ) ;
2005-04-17 02:20:36 +04:00
sctp_transport_route ( transport , NULL ,
sctp_sk ( asoc - > base . sk ) ) ;
}
break ;
default :
break ;
}
return retval ;
}
/* Get the corresponding ASCONF response error code from the ASCONF_ACK chunk
* for the given asconf parameter . If there is no response for this parameter ,
2007-02-09 17:25:18 +03:00
* return the error code based on the third argument ' no_err ' .
2005-04-17 02:20:36 +04:00
* 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 .
*/
2006-11-21 04:01:42 +03:00
static __be16 sctp_get_asconf_response ( struct sctp_chunk * asconf_ack ,
2005-04-17 02:20:36 +04:00
sctp_addip_param_t * asconf_param ,
int no_err )
{
sctp_addip_param_t * asconf_ack_param ;
sctp_errhdr_t * err_param ;
int length ;
int asconf_ack_len = asconf_ack - > skb - > len ;
2006-11-21 04:01:42 +03:00
__be16 err_code ;
2005-04-17 02:20:36 +04:00
if ( no_err )
err_code = SCTP_ERROR_NO_ERROR ;
else
err_code = SCTP_ERROR_REQ_REFUSED ;
/* Skip the addiphdr from the asconf_ack chunk and store a pointer to
* the first asconf_ack parameter .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
length = sizeof ( sctp_addiphdr_t ) ;
asconf_ack_param = ( sctp_addip_param_t * ) ( asconf_ack - > skb - > data +
length ) ;
asconf_ack_len - = length ;
while ( asconf_ack_len > 0 ) {
if ( asconf_ack_param - > crr_id = = asconf_param - > crr_id ) {
switch ( asconf_ack_param - > param_hdr . type ) {
case SCTP_PARAM_SUCCESS_REPORT :
return SCTP_ERROR_NO_ERROR ;
case SCTP_PARAM_ERR_CAUSE :
length = sizeof ( sctp_addip_param_t ) ;
err_param = ( sctp_errhdr_t * )
( ( void * ) asconf_ack_param + length ) ;
asconf_ack_len - = length ;
if ( asconf_ack_len > 0 )
return err_param - > cause ;
else
return SCTP_ERROR_INV_PARAM ;
break ;
default :
return SCTP_ERROR_INV_PARAM ;
}
}
length = ntohs ( asconf_ack_param - > param_hdr . length ) ;
asconf_ack_param = ( sctp_addip_param_t * )
( ( void * ) asconf_ack_param + length ) ;
asconf_ack_len - = length ;
}
return err_code ;
}
/* Process an incoming ASCONF_ACK chunk against the cached last ASCONF chunk. */
int sctp_process_asconf_ack ( struct sctp_association * asoc ,
struct sctp_chunk * asconf_ack )
{
struct sctp_chunk * asconf = asoc - > addip_last_asconf ;
union sctp_addr_param * addr_param ;
sctp_addip_param_t * asconf_param ;
int length = 0 ;
int asconf_len = asconf - > skb - > len ;
int all_param_pass = 0 ;
int no_err = 1 ;
int retval = 0 ;
2006-11-21 04:01:42 +03:00
__be16 err_code = SCTP_ERROR_NO_ERROR ;
2005-04-17 02:20:36 +04:00
/* Skip the chunkhdr and addiphdr from the last asconf sent and store
* a pointer to address parameter .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
length = sizeof ( sctp_addip_chunk_t ) ;
addr_param = ( union sctp_addr_param * ) ( asconf - > skb - > data + length ) ;
asconf_len - = length ;
/* Skip the address parameter in the last asconf sent and store a
* pointer to the first asconf paramter .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
length = ntohs ( addr_param - > v4 . param_hdr . length ) ;
asconf_param = ( sctp_addip_param_t * ) ( ( void * ) addr_param + length ) ;
asconf_len - = length ;
/* ADDIP 4.1
* A8 ) If there is no response ( s ) to specific TLV parameter ( s ) , and no
* failures are indicated , then all request ( s ) are considered
* successful .
*/
if ( asconf_ack - > skb - > len = = sizeof ( sctp_addiphdr_t ) )
all_param_pass = 1 ;
/* Process the TLVs contained in the last sent ASCONF chunk. */
while ( asconf_len > 0 ) {
if ( all_param_pass )
err_code = SCTP_ERROR_NO_ERROR ;
else {
err_code = sctp_get_asconf_response ( asconf_ack ,
asconf_param ,
no_err ) ;
if ( no_err & & ( SCTP_ERROR_NO_ERROR ! = err_code ) )
no_err = 0 ;
}
switch ( err_code ) {
case SCTP_ERROR_NO_ERROR :
retval = sctp_asconf_param_success ( asoc , asconf_param ) ;
break ;
case SCTP_ERROR_RSRC_LOW :
retval = 1 ;
break ;
case SCTP_ERROR_INV_PARAM :
/* Disable sending this type of asconf parameter in
* future .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
asoc - > peer . addip_disabled_mask | =
asconf_param - > param_hdr . type ;
break ;
case SCTP_ERROR_REQ_REFUSED :
case SCTP_ERROR_DEL_LAST_IP :
case SCTP_ERROR_DEL_SRC_IP :
default :
break ;
}
/* Skip the processed asconf parameter and move to the next
* one .
2007-02-09 17:25:18 +03:00
*/
2005-04-17 02:20:36 +04:00
length = ntohs ( asconf_param - > param_hdr . length ) ;
asconf_param = ( sctp_addip_param_t * ) ( ( void * ) asconf_param +
length ) ;
asconf_len - = length ;
}
/* Free the cached last sent asconf chunk. */
sctp_chunk_free ( asconf ) ;
asoc - > addip_last_asconf = NULL ;
/* Send the next asconf chunk from the addip chunk queue. */
2005-07-09 08:47:49 +04:00
if ( ! list_empty ( & asoc - > addip_chunk_list ) ) {
struct list_head * entry = asoc - > addip_chunk_list . next ;
asconf = list_entry ( entry , struct sctp_chunk , list ) ;
list_del_init ( entry ) ;
2005-04-17 02:20:36 +04:00
/* 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 ;
}
return retval ;
}
2007-02-09 17:25:18 +03:00
/* Make a FWD TSN chunk. */
2005-04-17 02:20:36 +04:00
struct sctp_chunk * sctp_make_fwdtsn ( const struct sctp_association * asoc ,
__u32 new_cum_tsn , size_t nstreams ,
struct sctp_fwdtsn_skip * skiplist )
{
struct sctp_chunk * retval = NULL ;
struct sctp_fwdtsn_chunk * ftsn_chunk ;
2007-02-09 17:25:18 +03:00
struct sctp_fwdtsn_hdr ftsn_hdr ;
2005-04-17 02:20:36 +04:00
struct sctp_fwdtsn_skip skip ;
size_t hint ;
int i ;
hint = ( nstreams + 1 ) * sizeof ( __u32 ) ;
retval = sctp_make_chunk ( asoc , SCTP_CID_FWD_TSN , 0 , hint ) ;
if ( ! retval )
return NULL ;
ftsn_chunk = ( struct sctp_fwdtsn_chunk * ) retval - > subh . fwdtsn_hdr ;
ftsn_hdr . new_cum_tsn = htonl ( new_cum_tsn ) ;
retval - > subh . fwdtsn_hdr =
sctp_addto_chunk ( retval , sizeof ( ftsn_hdr ) , & ftsn_hdr ) ;
for ( i = 0 ; i < nstreams ; i + + ) {
skip . stream = skiplist [ i ] . stream ;
skip . ssn = skiplist [ i ] . ssn ;
sctp_addto_chunk ( retval , sizeof ( skip ) , & skip ) ;
}
return retval ;
}