diff --git a/sys/netinet/sctp_auth.c b/sys/netinet/sctp_auth.c index 208634247321..61263b7a12a9 100644 --- a/sys/netinet/sctp_auth.c +++ b/sys/netinet/sctp_auth.c @@ -1,2417 +1,2417 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #ifdef SCTP_DEBUG #define SCTP_AUTH_DEBUG (sctp_debug_on & SCTP_DEBUG_AUTH1) #define SCTP_AUTH_DEBUG2 (sctp_debug_on & SCTP_DEBUG_AUTH2) #endif /* SCTP_DEBUG */ inline void sctp_clear_chunklist(sctp_auth_chklist_t * chklist) { bzero(chklist, sizeof(*chklist)); /* chklist->num_chunks = 0; */ } sctp_auth_chklist_t * sctp_alloc_chunklist(void) { sctp_auth_chklist_t *chklist; SCTP_MALLOC(chklist, sctp_auth_chklist_t *, sizeof(*chklist), "AUTH chklist"); if (chklist == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_AUTH_DEBUG) { printf("sctp_alloc_chunklist: failed to get memory!\n"); } #endif /* SCTP_DEBUG */ } else { sctp_clear_chunklist(chklist); } return (chklist); } void sctp_free_chunklist(sctp_auth_chklist_t * list) { if (list != NULL) SCTP_FREE(list); } sctp_auth_chklist_t * sctp_copy_chunklist(sctp_auth_chklist_t * list) { sctp_auth_chklist_t *new_list; if (list == NULL) return (NULL); /* get a new list */ new_list = sctp_alloc_chunklist(); if (new_list == NULL) return (NULL); /* copy it */ bcopy(list, new_list, sizeof(*new_list)); return (new_list); } /* * add a chunk to the required chunks list */ int sctp_auth_add_chunk(uint8_t chunk, sctp_auth_chklist_t * list) { if (list == NULL) return (-1); /* is chunk restricted? */ if ((chunk == SCTP_INITIATION) || (chunk == SCTP_INITIATION_ACK) || (chunk == SCTP_SHUTDOWN_COMPLETE) || (chunk == SCTP_AUTHENTICATION)) { return (-1); } if (list->chunks[chunk] == 0) { list->chunks[chunk] = 1; list->num_chunks++; #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP: added chunk %u (0x%02x) to Auth list\n", chunk, chunk); #endif } return (0); } /* * delete a chunk from the required chunks list */ int sctp_auth_delete_chunk(uint8_t chunk, sctp_auth_chklist_t * list) { if (list == NULL) return (-1); /* is chunk restricted? */ if ((chunk == SCTP_ASCONF) || (chunk == SCTP_ASCONF_ACK)) { return (-1); } if (list->chunks[chunk] == 1) { list->chunks[chunk] = 0; list->num_chunks--; #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP: deleted chunk %u (0x%02x) from Auth list\n", chunk, chunk); #endif } return (0); } inline size_t sctp_auth_get_chklist_size(const sctp_auth_chklist_t * list) { if (list == NULL) return (0); else return (list->num_chunks); } /* * set the default list of chunks requiring AUTH */ void sctp_auth_set_default_chunks(sctp_auth_chklist_t * list) { - sctp_auth_add_chunk(SCTP_ASCONF, list); - sctp_auth_add_chunk(SCTP_ASCONF_ACK, list); + (void)sctp_auth_add_chunk(SCTP_ASCONF, list); + (void)sctp_auth_add_chunk(SCTP_ASCONF_ACK, list); } /* * return the current number and list of required chunks caller must * guarantee ptr has space for up to 256 bytes */ int sctp_serialize_auth_chunks(const sctp_auth_chklist_t * list, uint8_t * ptr) { int i, count = 0; if (list == NULL) return (0); for (i = 0; i < 256; i++) { if (list->chunks[i] != 0) { *ptr++ = i; count++; } } return (count); } int sctp_pack_auth_chunks(const sctp_auth_chklist_t * list, uint8_t * ptr) { int i, size = 0; if (list == NULL) return (0); if (list->num_chunks <= 32) { /* just list them, one byte each */ for (i = 0; i < 256; i++) { if (list->chunks[i] != 0) { *ptr++ = i; size++; } } } else { int index, offset; /* pack into a 32 byte bitfield */ for (i = 0; i < 256; i++) { if (list->chunks[i] != 0) { index = i / 8; offset = i % 8; ptr[index] |= (1 << offset); } } size = 32; } return (size); } int sctp_unpack_auth_chunks(const uint8_t * ptr, uint8_t num_chunks, sctp_auth_chklist_t * list) { int i; int size; if (list == NULL) return (0); if (num_chunks <= 32) { /* just pull them, one byte each */ for (i = 0; i < num_chunks; i++) { - sctp_auth_add_chunk(*ptr++, list); + (void)sctp_auth_add_chunk(*ptr++, list); } size = num_chunks; } else { int index, offset; /* unpack from a 32 byte bitfield */ for (index = 0; index < 32; index++) { for (offset = 0; offset < 8; offset++) { if (ptr[index] & (1 << offset)) { - sctp_auth_add_chunk((index * 8) + offset, list); + (void)sctp_auth_add_chunk((index * 8) + offset, list); } } } size = 32; } return (size); } /* * allocate structure space for a key of length keylen */ sctp_key_t * sctp_alloc_key(uint32_t keylen) { sctp_key_t *new_key; SCTP_MALLOC(new_key, sctp_key_t *, sizeof(*new_key) + keylen, "AUTH key"); if (new_key == NULL) { /* out of memory */ return (NULL); } new_key->keylen = keylen; return (new_key); } void sctp_free_key(sctp_key_t * key) { if (key != NULL) SCTP_FREE(key); } void sctp_print_key(sctp_key_t * key, const char *str) { uint32_t i; if (key == NULL) { printf("%s: [Null key]\n", str); return; } printf("%s: len %u, ", str, key->keylen); if (key->keylen) { for (i = 0; i < key->keylen; i++) printf("%02x", key->key[i]); printf("\n"); } else { printf("[Null key]\n"); } } void sctp_show_key(sctp_key_t * key, const char *str) { uint32_t i; if (key == NULL) { printf("%s: [Null key]\n", str); return; } printf("%s: len %u, ", str, key->keylen); if (key->keylen) { for (i = 0; i < key->keylen; i++) printf("%02x", key->key[i]); printf("\n"); } else { printf("[Null key]\n"); } } static inline uint32_t sctp_get_keylen(sctp_key_t * key) { if (key != NULL) return (key->keylen); else return (0); } /* * generate a new random key of length 'keylen' */ sctp_key_t * sctp_generate_random_key(uint32_t keylen) { sctp_key_t *new_key; /* validate keylen */ if (keylen > SCTP_AUTH_RANDOM_SIZE_MAX) keylen = SCTP_AUTH_RANDOM_SIZE_MAX; new_key = sctp_alloc_key(keylen); if (new_key == NULL) { /* out of memory */ return (NULL); } SCTP_READ_RANDOM(new_key->key, keylen); new_key->keylen = keylen; return (new_key); } sctp_key_t * sctp_set_key(uint8_t * key, uint32_t keylen) { sctp_key_t *new_key; new_key = sctp_alloc_key(keylen); if (new_key == NULL) { /* out of memory */ return (NULL); } bcopy(key, new_key->key, keylen); return (new_key); } /* * given two keys of variable size, compute which key is "larger/smaller" * returns: 1 if key1 > key2 -1 if key1 < key2 0 if key1 = key2 */ static int sctp_compare_key(sctp_key_t * key1, sctp_key_t * key2) { uint32_t maxlen; uint32_t i; uint32_t key1len, key2len; uint8_t *key_1, *key_2; uint8_t temp[SCTP_AUTH_RANDOM_SIZE_MAX]; /* sanity/length check */ key1len = sctp_get_keylen(key1); key2len = sctp_get_keylen(key2); if ((key1len == 0) && (key2len == 0)) return (0); else if (key1len == 0) return (-1); else if (key2len == 0) return (1); if (key1len != key2len) { if (key1len >= key2len) maxlen = key1len; else maxlen = key2len; bzero(temp, maxlen); if (key1len < maxlen) { /* prepend zeroes to key1 */ bcopy(key1->key, temp + (maxlen - key1len), key1len); key_1 = temp; key_2 = key2->key; } else { /* prepend zeroes to key2 */ bcopy(key2->key, temp + (maxlen - key2len), key2len); key_1 = key1->key; key_2 = temp; } } else { maxlen = key1len; key_1 = key1->key; key_2 = key2->key; } for (i = 0; i < maxlen; i++) { if (*key_1 > *key_2) return (1); else if (*key_1 < *key_2) return (-1); key_1++; key_2++; } /* keys are equal value, so check lengths */ if (key1len == key2len) return (0); else if (key1len < key2len) return (-1); else return (1); } /* * generate the concatenated keying material based on the two keys and the * shared key (if available). draft-ietf-tsvwg-auth specifies the specific * order for concatenation */ sctp_key_t * sctp_compute_hashkey(sctp_key_t * key1, sctp_key_t * key2, sctp_key_t * shared) { uint32_t keylen; sctp_key_t *new_key; uint8_t *key_ptr; keylen = sctp_get_keylen(key1) + sctp_get_keylen(key2) + sctp_get_keylen(shared); if (keylen > 0) { /* get space for the new key */ new_key = sctp_alloc_key(keylen); if (new_key == NULL) { /* out of memory */ return (NULL); } new_key->keylen = keylen; key_ptr = new_key->key; } else { /* all keys empty/null?! */ return (NULL); } /* concatenate the keys */ if (sctp_compare_key(key1, key2) <= 0) { /* key is key1 + shared + key2 */ if (sctp_get_keylen(key1)) { bcopy(key1->key, key_ptr, key1->keylen); key_ptr += key1->keylen; } if (sctp_get_keylen(shared)) { bcopy(shared->key, key_ptr, shared->keylen); key_ptr += shared->keylen; } if (sctp_get_keylen(key2)) { bcopy(key2->key, key_ptr, key2->keylen); key_ptr += key2->keylen; } } else { /* key is key2 + shared + key1 */ if (sctp_get_keylen(key2)) { bcopy(key2->key, key_ptr, key2->keylen); key_ptr += key2->keylen; } if (sctp_get_keylen(shared)) { bcopy(shared->key, key_ptr, shared->keylen); key_ptr += shared->keylen; } if (sctp_get_keylen(key1)) { bcopy(key1->key, key_ptr, key1->keylen); key_ptr += key1->keylen; } } return (new_key); } sctp_sharedkey_t * sctp_alloc_sharedkey(void) { sctp_sharedkey_t *new_key; SCTP_MALLOC(new_key, sctp_sharedkey_t *, sizeof(*new_key), "AUTH skey"); if (new_key == NULL) { /* out of memory */ return (NULL); } new_key->keyid = 0; new_key->key = NULL; return (new_key); } void sctp_free_sharedkey(sctp_sharedkey_t * skey) { if (skey != NULL) { if (skey->key != NULL) sctp_free_key(skey->key); SCTP_FREE(skey); } } sctp_sharedkey_t * sctp_find_sharedkey(struct sctp_keyhead *shared_keys, uint16_t key_id) { sctp_sharedkey_t *skey; LIST_FOREACH(skey, shared_keys, next) { if (skey->keyid == key_id) return (skey); } return (NULL); } void sctp_insert_sharedkey(struct sctp_keyhead *shared_keys, sctp_sharedkey_t * new_skey) { sctp_sharedkey_t *skey; if ((shared_keys == NULL) || (new_skey == NULL)) return; /* insert into an empty list? */ if (SCTP_LIST_EMPTY(shared_keys)) { LIST_INSERT_HEAD(shared_keys, new_skey, next); return; } /* insert into the existing list, ordered by key id */ LIST_FOREACH(skey, shared_keys, next) { if (new_skey->keyid < skey->keyid) { /* insert it before here */ LIST_INSERT_BEFORE(skey, new_skey, next); return; } else if (new_skey->keyid == skey->keyid) { /* replace the existing key */ #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("replacing shared key id %u\n", new_skey->keyid); #endif LIST_INSERT_BEFORE(skey, new_skey, next); LIST_REMOVE(skey, next); sctp_free_sharedkey(skey); return; } if (LIST_NEXT(skey, next) == NULL) { /* belongs at the end of the list */ LIST_INSERT_AFTER(skey, new_skey, next); return; } } } static sctp_sharedkey_t * sctp_copy_sharedkey(const sctp_sharedkey_t * skey) { sctp_sharedkey_t *new_skey; if (skey == NULL) return (NULL); new_skey = sctp_alloc_sharedkey(); if (new_skey == NULL) return (NULL); if (skey->key != NULL) new_skey->key = sctp_set_key(skey->key->key, skey->key->keylen); else new_skey->key = NULL; new_skey->keyid = skey->keyid; return (new_skey); } int sctp_copy_skeylist(const struct sctp_keyhead *src, struct sctp_keyhead *dest) { sctp_sharedkey_t *skey, *new_skey; int count = 0; if ((src == NULL) || (dest == NULL)) return (0); LIST_FOREACH(skey, src, next) { new_skey = sctp_copy_sharedkey(skey); if (new_skey != NULL) { sctp_insert_sharedkey(dest, new_skey); count++; } } return (count); } sctp_hmaclist_t * sctp_alloc_hmaclist(uint8_t num_hmacs) { sctp_hmaclist_t *new_list; int alloc_size; alloc_size = sizeof(*new_list) + num_hmacs * sizeof(new_list->hmac[0]); SCTP_MALLOC(new_list, sctp_hmaclist_t *, alloc_size, "AUTH HMAC list"); if (new_list == NULL) { /* out of memory */ return (NULL); } new_list->max_algo = num_hmacs; new_list->num_algo = 0; return (new_list); } void sctp_free_hmaclist(sctp_hmaclist_t * list) { if (list != NULL) { SCTP_FREE(list); list = NULL; } } int sctp_auth_add_hmacid(sctp_hmaclist_t * list, uint16_t hmac_id) { if (list == NULL) return (-1); if (list->num_algo == list->max_algo) { #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP: HMAC id list full, ignoring add %u\n", hmac_id); #endif return (-1); } if ((hmac_id != SCTP_AUTH_HMAC_ID_SHA1) && #ifdef HAVE_SHA224 (hmac_id != SCTP_AUTH_HMAC_ID_SHA224) && #endif #ifdef HAVE_SHA2 (hmac_id != SCTP_AUTH_HMAC_ID_SHA256) && (hmac_id != SCTP_AUTH_HMAC_ID_SHA384) && (hmac_id != SCTP_AUTH_HMAC_ID_SHA512) && #endif (hmac_id != SCTP_AUTH_HMAC_ID_MD5)) { return (-1); } #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP: add HMAC id %u to list\n", hmac_id); #endif list->hmac[list->num_algo++] = hmac_id; return (0); } sctp_hmaclist_t * sctp_copy_hmaclist(sctp_hmaclist_t * list) { sctp_hmaclist_t *new_list; int i; if (list == NULL) return (NULL); /* get a new list */ new_list = sctp_alloc_hmaclist(list->max_algo); if (new_list == NULL) return (NULL); /* copy it */ new_list->max_algo = list->max_algo; new_list->num_algo = list->num_algo; for (i = 0; i < list->num_algo; i++) new_list->hmac[i] = list->hmac[i]; return (new_list); } sctp_hmaclist_t * sctp_default_supported_hmaclist(void) { sctp_hmaclist_t *new_list; new_list = sctp_alloc_hmaclist(2); if (new_list == NULL) return (NULL); - sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA1); - sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA256); + (void)sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA1); + (void)sctp_auth_add_hmacid(new_list, SCTP_AUTH_HMAC_ID_SHA256); return (new_list); } /* * HMAC algos are listed in priority/preference order find the best HMAC id * to use for the peer based on local support */ uint16_t sctp_negotiate_hmacid(sctp_hmaclist_t * peer, sctp_hmaclist_t * local) { int i, j; if ((local == NULL) || (peer == NULL)) return (SCTP_AUTH_HMAC_ID_RSVD); for (i = 0; i < peer->num_algo; i++) { for (j = 0; j < local->num_algo; j++) { if (peer->hmac[i] == local->hmac[j]) { #ifndef SCTP_AUTH_DRAFT_04 /* "skip" MD5 as it's been deprecated */ if (peer->hmac[i] == SCTP_AUTH_HMAC_ID_MD5) continue; #endif /* found the "best" one */ #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP: negotiated peer HMAC id %u\n", peer->hmac[i]); #endif return (peer->hmac[i]); } } } /* didn't find one! */ return (SCTP_AUTH_HMAC_ID_RSVD); } /* * serialize the HMAC algo list and return space used caller must guarantee * ptr has appropriate space */ int sctp_serialize_hmaclist(sctp_hmaclist_t * list, uint8_t * ptr) { int i; uint16_t hmac_id; if (list == NULL) return (0); for (i = 0; i < list->num_algo; i++) { hmac_id = htons(list->hmac[i]); bcopy(&hmac_id, ptr, sizeof(hmac_id)); ptr += sizeof(hmac_id); } return (list->num_algo * sizeof(hmac_id)); } int sctp_verify_hmac_param(struct sctp_auth_hmac_algo *hmacs, uint32_t num_hmacs) { uint32_t i; uint16_t hmac_id; uint32_t sha1_supported = 0; for (i = 0; i < num_hmacs; i++) { hmac_id = ntohs(hmacs->hmac_ids[i]); if (hmac_id == SCTP_AUTH_HMAC_ID_SHA1) sha1_supported = 1; } /* all HMAC id's are supported */ if (sha1_supported == 0) return (-1); else return (0); } sctp_authinfo_t * sctp_alloc_authinfo(void) { sctp_authinfo_t *new_authinfo; SCTP_MALLOC(new_authinfo, sctp_authinfo_t *, sizeof(*new_authinfo), "AUTH info"); if (new_authinfo == NULL) { /* out of memory */ return (NULL); } bzero(&new_authinfo, sizeof(*new_authinfo)); return (new_authinfo); } void sctp_free_authinfo(sctp_authinfo_t * authinfo) { if (authinfo == NULL) return; if (authinfo->random != NULL) sctp_free_key(authinfo->random); if (authinfo->peer_random != NULL) sctp_free_key(authinfo->peer_random); if (authinfo->assoc_key != NULL) sctp_free_key(authinfo->assoc_key); if (authinfo->recv_key != NULL) sctp_free_key(authinfo->recv_key); /* We are NOT dynamically allocating authinfo's right now... */ /* SCTP_FREE(authinfo); */ } inline uint32_t sctp_get_auth_chunk_len(uint16_t hmac_algo) { int size; size = sizeof(struct sctp_auth_chunk) + sctp_get_hmac_digest_len(hmac_algo); return (SCTP_SIZE32(size)); } uint32_t sctp_get_hmac_digest_len(uint16_t hmac_algo) { switch (hmac_algo) { case SCTP_AUTH_HMAC_ID_SHA1: return (SCTP_AUTH_DIGEST_LEN_SHA1); case SCTP_AUTH_HMAC_ID_MD5: return (SCTP_AUTH_DIGEST_LEN_MD5); #ifdef HAVE_SHA224 case SCTP_AUTH_HMAC_ID_SHA224: return (SCTP_AUTH_DIGEST_LEN_SHA224); #endif #ifdef HAVE_SHA2 case SCTP_AUTH_HMAC_ID_SHA256: return (SCTP_AUTH_DIGEST_LEN_SHA256); case SCTP_AUTH_HMAC_ID_SHA384: return (SCTP_AUTH_DIGEST_LEN_SHA384); case SCTP_AUTH_HMAC_ID_SHA512: return (SCTP_AUTH_DIGEST_LEN_SHA512); #endif default: /* unknown HMAC algorithm: can't do anything */ return (0); } /* end switch */ } static inline int sctp_get_hmac_block_len(uint16_t hmac_algo) { switch (hmac_algo) { case SCTP_AUTH_HMAC_ID_SHA1: case SCTP_AUTH_HMAC_ID_MD5: #ifdef HAVE_SHA224 case SCTP_AUTH_HMAC_ID_SHA224: return (64); #endif #ifdef HAVE_SHA2 case SCTP_AUTH_HMAC_ID_SHA256: return (64); case SCTP_AUTH_HMAC_ID_SHA384: case SCTP_AUTH_HMAC_ID_SHA512: return (128); #endif case SCTP_AUTH_HMAC_ID_RSVD: default: /* unknown HMAC algorithm: can't do anything */ return (0); } /* end switch */ } static void sctp_hmac_init(uint16_t hmac_algo, sctp_hash_context_t * ctx) { switch (hmac_algo) { case SCTP_AUTH_HMAC_ID_SHA1: SHA1_Init(&ctx->sha1); break; case SCTP_AUTH_HMAC_ID_MD5: MD5_Init(&ctx->md5); break; #ifdef HAVE_SHA224 case SCTP_AUTH_HMAC_ID_SHA224: break; #endif #ifdef HAVE_SHA2 case SCTP_AUTH_HMAC_ID_SHA256: SHA256_Init(&ctx->sha256); break; case SCTP_AUTH_HMAC_ID_SHA384: SHA384_Init(&ctx->sha384); break; case SCTP_AUTH_HMAC_ID_SHA512: SHA512_Init(&ctx->sha512); break; #endif case SCTP_AUTH_HMAC_ID_RSVD: default: /* unknown HMAC algorithm: can't do anything */ return; } /* end switch */ } static void sctp_hmac_update(uint16_t hmac_algo, sctp_hash_context_t * ctx, uint8_t * text, uint32_t textlen) { switch (hmac_algo) { case SCTP_AUTH_HMAC_ID_SHA1: SHA1_Update(&ctx->sha1, text, textlen); break; case SCTP_AUTH_HMAC_ID_MD5: MD5_Update(&ctx->md5, text, textlen); break; #ifdef HAVE_SHA224 case SCTP_AUTH_HMAC_ID_SHA224: break; #endif #ifdef HAVE_SHA2 case SCTP_AUTH_HMAC_ID_SHA256: SHA256_Update(&ctx->sha256, text, textlen); break; case SCTP_AUTH_HMAC_ID_SHA384: SHA384_Update(&ctx->sha384, text, textlen); break; case SCTP_AUTH_HMAC_ID_SHA512: SHA512_Update(&ctx->sha512, text, textlen); break; #endif case SCTP_AUTH_HMAC_ID_RSVD: default: /* unknown HMAC algorithm: can't do anything */ return; } /* end switch */ } static void sctp_hmac_final(uint16_t hmac_algo, sctp_hash_context_t * ctx, uint8_t * digest) { switch (hmac_algo) { case SCTP_AUTH_HMAC_ID_SHA1: SHA1_Final(digest, &ctx->sha1); break; case SCTP_AUTH_HMAC_ID_MD5: MD5_Final(digest, &ctx->md5); break; #ifdef HAVE_SHA224 case SCTP_AUTH_HMAC_ID_SHA224: break; #endif #ifdef HAVE_SHA2 case SCTP_AUTH_HMAC_ID_SHA256: SHA256_Final(digest, &ctx->sha256); break; case SCTP_AUTH_HMAC_ID_SHA384: /* SHA384 is truncated SHA512 */ SHA384_Final(digest, &ctx->sha384); break; case SCTP_AUTH_HMAC_ID_SHA512: SHA512_Final(digest, &ctx->sha512); break; #endif case SCTP_AUTH_HMAC_ID_RSVD: default: /* unknown HMAC algorithm: can't do anything */ return; } /* end switch */ } /* * Keyed-Hashing for Message Authentication: FIPS 198 (RFC 2104) * * Compute the HMAC digest using the desired hash key, text, and HMAC * algorithm. Resulting digest is placed in 'digest' and digest length * is returned, if the HMAC was performed. * * WARNING: it is up to the caller to supply sufficient space to hold the * resultant digest. */ uint32_t sctp_hmac(uint16_t hmac_algo, uint8_t * key, uint32_t keylen, uint8_t * text, uint32_t textlen, uint8_t * digest) { uint32_t digestlen; uint32_t blocklen; sctp_hash_context_t ctx; uint8_t ipad[128], opad[128]; /* keyed hash inner/outer pads */ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX]; uint32_t i; /* sanity check the material and length */ if ((key == NULL) || (keylen == 0) || (text == NULL) || (textlen == 0) || (digest == NULL)) { /* can't do HMAC with empty key or text or digest store */ return (0); } /* validate the hmac algo and get the digest length */ digestlen = sctp_get_hmac_digest_len(hmac_algo); if (digestlen == 0) return (0); /* hash the key if it is longer than the hash block size */ blocklen = sctp_get_hmac_block_len(hmac_algo); if (keylen > blocklen) { sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, key, keylen); sctp_hmac_final(hmac_algo, &ctx, temp); /* set the hashed key as the key */ keylen = digestlen; key = temp; } /* initialize the inner/outer pads with the key and "append" zeroes */ bzero(ipad, blocklen); bzero(opad, blocklen); bcopy(key, ipad, keylen); bcopy(key, opad, keylen); /* XOR the key with ipad and opad values */ for (i = 0; i < blocklen; i++) { ipad[i] ^= 0x36; opad[i] ^= 0x5c; } /* perform inner hash */ sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, ipad, blocklen); sctp_hmac_update(hmac_algo, &ctx, text, textlen); sctp_hmac_final(hmac_algo, &ctx, temp); /* perform outer hash */ sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, opad, blocklen); sctp_hmac_update(hmac_algo, &ctx, temp, digestlen); sctp_hmac_final(hmac_algo, &ctx, digest); return (digestlen); } /* mbuf version */ uint32_t sctp_hmac_m(uint16_t hmac_algo, uint8_t * key, uint32_t keylen, struct mbuf *m, uint32_t m_offset, uint8_t * digest) { uint32_t digestlen; uint32_t blocklen; sctp_hash_context_t ctx; uint8_t ipad[128], opad[128]; /* keyed hash inner/outer pads */ uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX]; uint32_t i; struct mbuf *m_tmp; /* sanity check the material and length */ if ((key == NULL) || (keylen == 0) || (m == NULL) || (digest == NULL)) { /* can't do HMAC with empty key or text or digest store */ return (0); } /* validate the hmac algo and get the digest length */ digestlen = sctp_get_hmac_digest_len(hmac_algo); if (digestlen == 0) return (0); /* hash the key if it is longer than the hash block size */ blocklen = sctp_get_hmac_block_len(hmac_algo); if (keylen > blocklen) { sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, key, keylen); sctp_hmac_final(hmac_algo, &ctx, temp); /* set the hashed key as the key */ keylen = digestlen; key = temp; } /* initialize the inner/outer pads with the key and "append" zeroes */ bzero(ipad, blocklen); bzero(opad, blocklen); bcopy(key, ipad, keylen); bcopy(key, opad, keylen); /* XOR the key with ipad and opad values */ for (i = 0; i < blocklen; i++) { ipad[i] ^= 0x36; opad[i] ^= 0x5c; } /* perform inner hash */ sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, ipad, blocklen); /* find the correct starting mbuf and offset (get start of text) */ m_tmp = m; while ((m_tmp != NULL) && (m_offset >= (uint32_t) SCTP_BUF_LEN(m_tmp))) { m_offset -= SCTP_BUF_LEN(m_tmp); m_tmp = SCTP_BUF_NEXT(m_tmp); } /* now use the rest of the mbuf chain for the text */ while (m_tmp != NULL) { sctp_hmac_update(hmac_algo, &ctx, mtod(m_tmp, uint8_t *) + m_offset, SCTP_BUF_LEN(m_tmp) - m_offset); /* clear the offset since it's only for the first mbuf */ m_offset = 0; m_tmp = SCTP_BUF_NEXT(m_tmp); } sctp_hmac_final(hmac_algo, &ctx, temp); /* perform outer hash */ sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, opad, blocklen); sctp_hmac_update(hmac_algo, &ctx, temp, digestlen); sctp_hmac_final(hmac_algo, &ctx, digest); return (digestlen); } /* * verify the HMAC digest using the desired hash key, text, and HMAC * algorithm. Returns -1 on error, 0 on success. */ int sctp_verify_hmac(uint16_t hmac_algo, uint8_t * key, uint32_t keylen, uint8_t * text, uint32_t textlen, uint8_t * digest, uint32_t digestlen) { uint32_t len; uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX]; /* sanity check the material and length */ if ((key == NULL) || (keylen == 0) || (text == NULL) || (textlen == 0) || (digest == NULL)) { /* can't do HMAC with empty key or text or digest */ return (-1); } len = sctp_get_hmac_digest_len(hmac_algo); if ((len == 0) || (digestlen != len)) return (-1); /* compute the expected hash */ if (sctp_hmac(hmac_algo, key, keylen, text, textlen, temp) != len) return (-1); if (memcmp(digest, temp, digestlen) != 0) return (-1); else return (0); } /* * computes the requested HMAC using a key struct (which may be modified if * the keylen exceeds the HMAC block len). */ uint32_t sctp_compute_hmac(uint16_t hmac_algo, sctp_key_t * key, uint8_t * text, uint32_t textlen, uint8_t * digest) { uint32_t digestlen; uint32_t blocklen; sctp_hash_context_t ctx; uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX]; /* sanity check */ if ((key == NULL) || (text == NULL) || (textlen == 0) || (digest == NULL)) { /* can't do HMAC with empty key or text or digest store */ return (0); } /* validate the hmac algo and get the digest length */ digestlen = sctp_get_hmac_digest_len(hmac_algo); if (digestlen == 0) return (0); /* hash the key if it is longer than the hash block size */ blocklen = sctp_get_hmac_block_len(hmac_algo); if (key->keylen > blocklen) { sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, key->key, key->keylen); sctp_hmac_final(hmac_algo, &ctx, temp); /* save the hashed key as the new key */ key->keylen = digestlen; bcopy(temp, key->key, key->keylen); } return (sctp_hmac(hmac_algo, key->key, key->keylen, text, textlen, digest)); } /* mbuf version */ uint32_t sctp_compute_hmac_m(uint16_t hmac_algo, sctp_key_t * key, struct mbuf *m, uint32_t m_offset, uint8_t * digest) { uint32_t digestlen; uint32_t blocklen; sctp_hash_context_t ctx; uint8_t temp[SCTP_AUTH_DIGEST_LEN_MAX]; /* sanity check */ if ((key == NULL) || (m == NULL) || (digest == NULL)) { /* can't do HMAC with empty key or text or digest store */ return (0); } /* validate the hmac algo and get the digest length */ digestlen = sctp_get_hmac_digest_len(hmac_algo); if (digestlen == 0) return (0); /* hash the key if it is longer than the hash block size */ blocklen = sctp_get_hmac_block_len(hmac_algo); if (key->keylen > blocklen) { sctp_hmac_init(hmac_algo, &ctx); sctp_hmac_update(hmac_algo, &ctx, key->key, key->keylen); sctp_hmac_final(hmac_algo, &ctx, temp); /* save the hashed key as the new key */ key->keylen = digestlen; bcopy(temp, key->key, key->keylen); } return (sctp_hmac_m(hmac_algo, key->key, key->keylen, m, m_offset, digest)); } int sctp_auth_is_supported_hmac(sctp_hmaclist_t * list, uint16_t id) { int i; if ((list == NULL) || (id == SCTP_AUTH_HMAC_ID_RSVD)) return (0); for (i = 0; i < list->num_algo; i++) if (list->hmac[i] == id) return (1); /* not in the list */ return (0); } /* * clear any cached key(s) if they match the given key id on an association * the cached key(s) will be recomputed and re-cached at next use. ASSUMES * TCB_LOCK is already held */ void sctp_clear_cachedkeys(struct sctp_tcb *stcb, uint16_t keyid) { if (stcb == NULL) return; if (keyid == stcb->asoc.authinfo.assoc_keyid) { sctp_free_key(stcb->asoc.authinfo.assoc_key); stcb->asoc.authinfo.assoc_key = NULL; } if (keyid == stcb->asoc.authinfo.recv_keyid) { sctp_free_key(stcb->asoc.authinfo.recv_key); stcb->asoc.authinfo.recv_key = NULL; } } /* * clear any cached key(s) if they match the given key id for all assocs on * an association ASSUMES INP_WLOCK is already held */ void sctp_clear_cachedkeys_ep(struct sctp_inpcb *inp, uint16_t keyid) { struct sctp_tcb *stcb; if (inp == NULL) return; /* clear the cached keys on all assocs on this instance */ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { SCTP_TCB_LOCK(stcb); sctp_clear_cachedkeys(stcb, keyid); SCTP_TCB_UNLOCK(stcb); } } /* * delete a shared key from an association ASSUMES TCB_LOCK is already held */ int sctp_delete_sharedkey(struct sctp_tcb *stcb, uint16_t keyid) { sctp_sharedkey_t *skey; if (stcb == NULL) return (-1); /* is the keyid the assoc active sending key */ if (keyid == stcb->asoc.authinfo.assoc_keyid) return (-1); /* does the key exist? */ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid); if (skey == NULL) return (-1); /* remove it */ LIST_REMOVE(skey, next); sctp_free_sharedkey(skey); /* frees skey->key as well */ /* clear any cached keys */ sctp_clear_cachedkeys(stcb, keyid); return (0); } /* * deletes a shared key from the endpoint ASSUMES INP_WLOCK is already held */ int sctp_delete_sharedkey_ep(struct sctp_inpcb *inp, uint16_t keyid) { sctp_sharedkey_t *skey; struct sctp_tcb *stcb; if (inp == NULL) return (-1); /* is the keyid the active sending key on the endpoint or any assoc */ if (keyid == inp->sctp_ep.default_keyid) return (-1); LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { SCTP_TCB_LOCK(stcb); if (keyid == stcb->asoc.authinfo.assoc_keyid) { SCTP_TCB_UNLOCK(stcb); return (-1); } SCTP_TCB_UNLOCK(stcb); } /* does the key exist? */ skey = sctp_find_sharedkey(&inp->sctp_ep.shared_keys, keyid); if (skey == NULL) return (-1); /* remove it */ LIST_REMOVE(skey, next); sctp_free_sharedkey(skey); /* frees skey->key as well */ /* clear any cached keys */ sctp_clear_cachedkeys_ep(inp, keyid); return (0); } /* * set the active key on an association ASSUME TCB_LOCK is already held */ int sctp_auth_setactivekey(struct sctp_tcb *stcb, uint16_t keyid) { sctp_sharedkey_t *skey = NULL; sctp_key_t *key = NULL; int using_ep_key = 0; /* find the key on the assoc */ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, keyid); if (skey == NULL) { /* if not on the assoc, find the key on the endpoint */ SCTP_INP_RLOCK(stcb->sctp_ep); skey = sctp_find_sharedkey(&stcb->sctp_ep->sctp_ep.shared_keys, keyid); using_ep_key = 1; } if (skey == NULL) { /* that key doesn't exist */ if (using_ep_key) SCTP_INP_RUNLOCK(stcb->sctp_ep); return (-1); } /* get the shared key text */ key = skey->key; /* free any existing cached key */ if (stcb->asoc.authinfo.assoc_key != NULL) sctp_free_key(stcb->asoc.authinfo.assoc_key); /* compute a new assoc key and cache it */ stcb->asoc.authinfo.assoc_key = sctp_compute_hashkey(stcb->asoc.authinfo.random, stcb->asoc.authinfo.peer_random, key); stcb->asoc.authinfo.assoc_keyid = keyid; #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) sctp_print_key(stcb->asoc.authinfo.assoc_key, "Assoc Key"); #endif if (using_ep_key) SCTP_INP_RUNLOCK(stcb->sctp_ep); return (0); } /* * set the active key on an endpoint ASSUMES INP_WLOCK is already held */ int sctp_auth_setactivekey_ep(struct sctp_inpcb *inp, uint16_t keyid) { sctp_sharedkey_t *skey; /* find the key */ skey = sctp_find_sharedkey(&inp->sctp_ep.shared_keys, keyid); if (skey == NULL) { /* that key doesn't exist */ return (-1); } inp->sctp_ep.default_keyid = keyid; return (0); } /* * get local authentication parameters from cookie (from INIT-ACK) */ void sctp_auth_get_cookie_params(struct sctp_tcb *stcb, struct mbuf *m, uint32_t offset, uint32_t length) { struct sctp_paramhdr *phdr, tmp_param; uint16_t plen, ptype; uint8_t random_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_random *p_random = NULL; uint16_t random_len = 0; uint8_t hmacs_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_hmac_algo *hmacs = NULL; uint16_t hmacs_len = 0; uint8_t chunks_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_chunk_list *chunks = NULL; uint16_t num_chunks = 0; sctp_key_t *new_key; uint32_t keylen; /* convert to upper bound */ length += offset; phdr = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), (uint8_t *) & tmp_param); while (phdr != NULL) { ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if ((plen == 0) || (offset + plen > length)) break; if (ptype == SCTP_RANDOM) { if (plen > sizeof(random_store)) break; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)random_store, min(plen, sizeof(random_store))); if (phdr == NULL) return; /* save the random and length for the key */ p_random = (struct sctp_auth_random *)phdr; random_len = plen - sizeof(*p_random); } else if (ptype == SCTP_HMAC_LIST) { int num_hmacs; int i; if (plen > sizeof(hmacs_store)) break; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)hmacs_store, min(plen, sizeof(hmacs_store))); if (phdr == NULL) return; /* save the hmacs list and num for the key */ hmacs = (struct sctp_auth_hmac_algo *)phdr; hmacs_len = plen - sizeof(*hmacs); num_hmacs = hmacs_len / sizeof(hmacs->hmac_ids[0]); if (stcb->asoc.local_hmacs != NULL) sctp_free_hmaclist(stcb->asoc.local_hmacs); stcb->asoc.local_hmacs = sctp_alloc_hmaclist(num_hmacs); if (stcb->asoc.local_hmacs != NULL) { for (i = 0; i < num_hmacs; i++) { - sctp_auth_add_hmacid(stcb->asoc.local_hmacs, + (void)sctp_auth_add_hmacid(stcb->asoc.local_hmacs, ntohs(hmacs->hmac_ids[i])); } } } else if (ptype == SCTP_CHUNK_LIST) { int i; if (plen > sizeof(chunks_store)) break; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)chunks_store, min(plen, sizeof(chunks_store))); if (phdr == NULL) return; chunks = (struct sctp_auth_chunk_list *)phdr; num_chunks = plen - sizeof(*chunks); /* save chunks list and num for the key */ if (stcb->asoc.local_auth_chunks != NULL) sctp_clear_chunklist(stcb->asoc.local_auth_chunks); else stcb->asoc.local_auth_chunks = sctp_alloc_chunklist(); for (i = 0; i < num_chunks; i++) { - sctp_auth_add_chunk(chunks->chunk_types[i], + (void)sctp_auth_add_chunk(chunks->chunk_types[i], stcb->asoc.local_auth_chunks); } } /* get next parameter */ offset += SCTP_SIZE32(plen); if (offset + sizeof(struct sctp_paramhdr) > length) break; phdr = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), (uint8_t *) & tmp_param); } /* concatenate the full random key */ #ifdef SCTP_AUTH_DRAFT_04 keylen = random_len; new_key = sctp_alloc_key(keylen); if (new_key != NULL) { /* copy in the RANDOM */ if (p_random != NULL) bcopy(p_random->random_data, new_key->key, random_len); } #else keylen = sizeof(*p_random) + random_len + sizeof(*chunks) + num_chunks + sizeof(*hmacs) + hmacs_len; new_key = sctp_alloc_key(keylen); if (new_key != NULL) { /* copy in the RANDOM */ if (p_random != NULL) { keylen = sizeof(*p_random) + random_len; bcopy(p_random, new_key->key, keylen); } /* append in the AUTH chunks */ if (chunks != NULL) { bcopy(chunks, new_key->key + keylen, sizeof(*chunks) + num_chunks); keylen += sizeof(*chunks) + num_chunks; } /* append in the HMACs */ if (hmacs != NULL) { bcopy(hmacs, new_key->key + keylen, sizeof(*hmacs) + hmacs_len); } } #endif if (stcb->asoc.authinfo.random != NULL) sctp_free_key(stcb->asoc.authinfo.random); stcb->asoc.authinfo.random = new_key; stcb->asoc.authinfo.random_len = random_len; #ifdef SCTP_AUTH_DRAFT_04 /* don't include the chunks and hmacs for draft -04 */ stcb->asoc.authinfo.random->keylen = random_len; #endif sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.assoc_keyid); sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.recv_keyid); /* negotiate what HMAC to use for the peer */ stcb->asoc.peer_hmac_id = sctp_negotiate_hmacid(stcb->asoc.peer_hmacs, stcb->asoc.local_hmacs); /* copy defaults from the endpoint */ /* FIX ME: put in cookie? */ stcb->asoc.authinfo.assoc_keyid = stcb->sctp_ep->sctp_ep.default_keyid; } /* * compute and fill in the HMAC digest for a packet */ void sctp_fill_hmac_digest_m(struct mbuf *m, uint32_t auth_offset, struct sctp_auth_chunk *auth, struct sctp_tcb *stcb) { uint32_t digestlen; sctp_sharedkey_t *skey; sctp_key_t *key; if ((stcb == NULL) || (auth == NULL)) return; /* zero the digest + chunk padding */ digestlen = sctp_get_hmac_digest_len(stcb->asoc.peer_hmac_id); bzero(auth->hmac, SCTP_SIZE32(digestlen)); /* is an assoc key cached? */ if (stcb->asoc.authinfo.assoc_key == NULL) { skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, stcb->asoc.authinfo.assoc_keyid); if (skey == NULL) { /* not in the assoc list, so check the endpoint list */ skey = sctp_find_sharedkey(&stcb->sctp_ep->sctp_ep.shared_keys, stcb->asoc.authinfo.assoc_keyid); } /* the only way skey is NULL is if null key id 0 is used */ if (skey != NULL) key = skey->key; else key = NULL; /* compute a new assoc key and cache it */ stcb->asoc.authinfo.assoc_key = sctp_compute_hashkey(stcb->asoc.authinfo.random, stcb->asoc.authinfo.peer_random, key); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) { printf("caching key id %u\n", stcb->asoc.authinfo.assoc_keyid); sctp_print_key(stcb->asoc.authinfo.assoc_key, "Assoc Key"); } #endif } /* set in the active key id */ auth->shared_key_id = htons(stcb->asoc.authinfo.assoc_keyid); /* compute and fill in the digest */ (void)sctp_compute_hmac_m(stcb->asoc.peer_hmac_id, stcb->asoc.authinfo.assoc_key, m, auth_offset, auth->hmac); } static void sctp_bzero_m(struct mbuf *m, uint32_t m_offset, uint32_t size) { struct mbuf *m_tmp; uint8_t *data; /* sanity check */ if (m == NULL) return; /* find the correct starting mbuf and offset (get start position) */ m_tmp = m; while ((m_tmp != NULL) && (m_offset >= (uint32_t) SCTP_BUF_LEN(m_tmp))) { m_offset -= SCTP_BUF_LEN(m_tmp); m_tmp = SCTP_BUF_NEXT(m_tmp); } /* now use the rest of the mbuf chain */ while ((m_tmp != NULL) && (size > 0)) { data = mtod(m_tmp, uint8_t *) + m_offset; if (size > (uint32_t) SCTP_BUF_LEN(m_tmp)) { bzero(data, SCTP_BUF_LEN(m_tmp)); size -= SCTP_BUF_LEN(m_tmp); } else { bzero(data, size); size = 0; } /* clear the offset since it's only for the first mbuf */ m_offset = 0; m_tmp = SCTP_BUF_NEXT(m_tmp); } } /* * process the incoming Authentication chunk return codes: -1 on any * authentication error 0 on authentication verification */ int sctp_handle_auth(struct sctp_tcb *stcb, struct sctp_auth_chunk *auth, struct mbuf *m, uint32_t offset) { uint16_t chunklen; uint16_t shared_key_id; uint16_t hmac_id; sctp_sharedkey_t *skey; uint32_t digestlen; uint8_t digest[SCTP_AUTH_DIGEST_LEN_MAX]; uint8_t computed_digest[SCTP_AUTH_DIGEST_LEN_MAX]; /* auth is checked for NULL by caller */ chunklen = ntohs(auth->ch.chunk_length); if (chunklen < sizeof(*auth)) { SCTP_STAT_INCR(sctps_recvauthfailed); return (-1); } SCTP_STAT_INCR(sctps_recvauth); /* get the auth params */ shared_key_id = ntohs(auth->shared_key_id); hmac_id = ntohs(auth->hmac_id); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP AUTH Chunk: shared key %u, HMAC id %u\n", shared_key_id, hmac_id); #endif /* is the indicated HMAC supported? */ if (!sctp_auth_is_supported_hmac(stcb->asoc.local_hmacs, hmac_id)) { struct mbuf *m_err; struct sctp_auth_invalid_hmac *err; SCTP_STAT_INCR(sctps_recvivalhmacid); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP Auth: unsupported HMAC id %u\n", hmac_id); #endif /* * report this in an Error Chunk: Unsupported HMAC * Identifier */ m_err = sctp_get_mbuf_for_msg(sizeof(*err), 0, M_DONTWAIT, 1, MT_HEADER); if (m_err != NULL) { /* pre-reserve some space */ SCTP_BUF_RESV_UF(m_err, sizeof(struct sctp_chunkhdr)); /* fill in the error */ err = mtod(m_err, struct sctp_auth_invalid_hmac *); bzero(err, sizeof(*err)); err->ph.param_type = htons(SCTP_CAUSE_UNSUPPORTED_HMACID); err->ph.param_length = htons(sizeof(*err)); err->hmac_id = ntohs(hmac_id); SCTP_BUF_LEN(m_err) = sizeof(*err); /* queue it */ sctp_queue_op_err(stcb, m_err); } return (-1); } /* get the indicated shared key, if available */ if ((stcb->asoc.authinfo.recv_key == NULL) || (stcb->asoc.authinfo.recv_keyid != shared_key_id)) { /* find the shared key on the assoc first */ skey = sctp_find_sharedkey(&stcb->asoc.shared_keys, shared_key_id); if (skey == NULL) { /* if not on the assoc, find it on the endpoint */ skey = sctp_find_sharedkey(&stcb->sctp_ep->sctp_ep.shared_keys, shared_key_id); } /* if the shared key isn't found, discard the chunk */ if (skey == NULL) { SCTP_STAT_INCR(sctps_recvivalkeyid); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP Auth: unknown key id %u\n", shared_key_id); #endif return (-1); } /* generate a notification if this is a new key id */ if (stcb->asoc.authinfo.recv_keyid != shared_key_id) /* * sctp_ulp_notify(SCTP_NOTIFY_AUTH_NEW_KEY, stcb, * shared_key_id, (void * *)stcb->asoc.authinfo.recv_keyid); */ sctp_notify_authentication(stcb, SCTP_AUTH_NEWKEY, shared_key_id, stcb->asoc.authinfo.recv_keyid); /* compute a new recv assoc key and cache it */ if (stcb->asoc.authinfo.recv_key != NULL) sctp_free_key(stcb->asoc.authinfo.recv_key); stcb->asoc.authinfo.recv_key = sctp_compute_hashkey(stcb->asoc.authinfo.random, stcb->asoc.authinfo.peer_random, skey->key); stcb->asoc.authinfo.recv_keyid = shared_key_id; #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) sctp_print_key(stcb->asoc.authinfo.recv_key, "Recv Key"); #endif } /* validate the digest length */ digestlen = sctp_get_hmac_digest_len(hmac_id); if (chunklen < (sizeof(*auth) + digestlen)) { /* invalid digest length */ SCTP_STAT_INCR(sctps_recvauthfailed); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP Auth: chunk too short for HMAC\n"); #endif return (-1); } /* save a copy of the digest, zero the pseudo header, and validate */ bcopy(auth->hmac, digest, digestlen); sctp_bzero_m(m, offset + sizeof(*auth), SCTP_SIZE32(digestlen)); (void)sctp_compute_hmac_m(hmac_id, stcb->asoc.authinfo.recv_key, m, offset, computed_digest); /* compare the computed digest with the one in the AUTH chunk */ if (memcmp(digest, computed_digest, digestlen) != 0) { SCTP_STAT_INCR(sctps_recvauthfailed); #ifdef SCTP_DEBUG if (SCTP_AUTH_DEBUG) printf("SCTP Auth: HMAC digest check failed\n"); #endif return (-1); } return (0); } /* * Generate NOTIFICATION */ void sctp_notify_authentication(struct sctp_tcb *stcb, uint32_t indication, uint16_t keyid, uint16_t alt_keyid) { struct mbuf *m_notify; struct sctp_authkey_event *auth; struct sctp_queued_to_read *control; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_AUTHEVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_authkey_event), 0, M_DONTWAIT, 1, MT_HEADER); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; auth = mtod(m_notify, struct sctp_authkey_event *); auth->auth_type = SCTP_AUTHENTICATION_EVENT; auth->auth_flags = 0; auth->auth_length = sizeof(*auth); auth->auth_keynumber = keyid; auth->auth_altkeynumber = alt_keyid; auth->auth_indication = indication; auth->auth_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(*auth); SCTP_BUF_NEXT(m_notify) = NULL; /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; control->length = SCTP_BUF_LEN(m_notify); /* not that we need this */ control->tail_mbuf = m_notify; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } /* * validates the AUTHentication related parameters in an INIT/INIT-ACK * Note: currently only used for INIT as INIT-ACK is handled inline * with sctp_load_addresses_from_init() */ int sctp_validate_init_auth_params(struct mbuf *m, int offset, int limit) { struct sctp_paramhdr *phdr, parm_buf; uint16_t ptype, plen; int peer_supports_asconf = 0; int peer_supports_auth = 0; int got_random = 0, got_hmacs = 0, got_chklist = 0; /* go through each of the params. */ phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); while (phdr) { ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if (offset + plen > limit) { break; } if (plen == 0) { break; } if (ptype == SCTP_SUPPORTED_CHUNK_EXT) { /* A supported extension chunk */ struct sctp_supported_chunk_types_param *pr_supported; uint8_t local_store[SCTP_PARAM_BUFFER_SIZE]; int num_ent, i; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&local_store, min(plen, sizeof(local_store))); if (phdr == NULL) { return (-1); } pr_supported = (struct sctp_supported_chunk_types_param *)phdr; num_ent = plen - sizeof(struct sctp_paramhdr); for (i = 0; i < num_ent; i++) { switch (pr_supported->chunk_types[i]) { case SCTP_ASCONF: case SCTP_ASCONF_ACK: peer_supports_asconf = 1; break; case SCTP_AUTHENTICATION: peer_supports_auth = 1; break; default: /* one we don't care about */ break; } } } else if (ptype == SCTP_RANDOM) { got_random = 1; /* enforce the random length */ if (plen != (sizeof(struct sctp_auth_random) + SCTP_AUTH_RANDOM_SIZE_REQUIRED)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("SCTP: invalid RANDOM len\n"); #endif return (-1); } } else if (ptype == SCTP_HMAC_LIST) { uint8_t store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_hmac_algo *hmacs; int num_hmacs; if (plen > sizeof(store)) break; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)store, min(plen, sizeof(store))); if (phdr == NULL) return (-1); hmacs = (struct sctp_auth_hmac_algo *)phdr; num_hmacs = (plen - sizeof(*hmacs)) / sizeof(hmacs->hmac_ids[0]); /* validate the hmac list */ if (sctp_verify_hmac_param(hmacs, num_hmacs)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("SCTP: invalid HMAC param\n"); #endif return (-1); } got_hmacs = 1; } else if (ptype == SCTP_CHUNK_LIST) { /* did the peer send a non-empty chunk list? */ if (plen > 0) got_chklist = 1; } offset += SCTP_SIZE32(plen); if (offset >= limit) { break; } phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); } /* validate authentication required parameters */ if (got_random && got_hmacs) { peer_supports_auth = 1; } else { peer_supports_auth = 0; } if (!peer_supports_auth && got_chklist) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("SCTP: peer sent chunk list w/o AUTH\n"); #endif return (-1); } if (!sctp_asconf_auth_nochk && peer_supports_asconf && !peer_supports_auth) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("SCTP: peer supports ASCONF but not AUTH\n"); #endif return (-1); } return (0); } void sctp_initialize_auth_params(struct sctp_inpcb *inp, struct sctp_tcb *stcb) { uint16_t chunks_len = 0; uint16_t hmacs_len = 0; uint16_t random_len = SCTP_AUTH_RANDOM_SIZE_DEFAULT; sctp_key_t *new_key; uint16_t keylen; /* initialize hmac list from endpoint */ stcb->asoc.local_hmacs = sctp_copy_hmaclist(inp->sctp_ep.local_hmacs); if (stcb->asoc.local_hmacs != NULL) { hmacs_len = stcb->asoc.local_hmacs->num_algo * sizeof(stcb->asoc.local_hmacs->hmac[0]); } /* initialize auth chunks list from endpoint */ stcb->asoc.local_auth_chunks = sctp_copy_chunklist(inp->sctp_ep.local_auth_chunks); if (stcb->asoc.local_auth_chunks != NULL) { int i; for (i = 0; i < 256; i++) { if (stcb->asoc.local_auth_chunks->chunks[i]) chunks_len++; } } /* copy defaults from the endpoint */ stcb->asoc.authinfo.assoc_keyid = inp->sctp_ep.default_keyid; /* now set the concatenated key (random + chunks + hmacs) */ #ifdef SCTP_AUTH_DRAFT_04 /* don't include the chunks and hmacs for draft -04 */ keylen = random_len; new_key = sctp_generate_random_key(keylen); #else /* key includes parameter headers */ keylen = (3 * sizeof(struct sctp_paramhdr)) + random_len + chunks_len + hmacs_len; new_key = sctp_alloc_key(keylen); if (new_key != NULL) { struct sctp_paramhdr *ph; int plen; /* generate and copy in the RANDOM */ ph = (struct sctp_paramhdr *)new_key->key; ph->param_type = htons(SCTP_RANDOM); plen = sizeof(*ph) + random_len; ph->param_length = htons(plen); SCTP_READ_RANDOM(new_key->key + sizeof(*ph), random_len); keylen = plen; /* append in the AUTH chunks */ /* NOTE: currently we always have chunks to list */ ph = (struct sctp_paramhdr *)(new_key->key + keylen); ph->param_type = htons(SCTP_CHUNK_LIST); plen = sizeof(*ph) + chunks_len; ph->param_length = htons(plen); keylen += sizeof(*ph); if (stcb->asoc.local_auth_chunks) { int i; for (i = 0; i < 256; i++) { if (stcb->asoc.local_auth_chunks->chunks[i]) new_key->key[keylen++] = i; } } /* append in the HMACs */ ph = (struct sctp_paramhdr *)(new_key->key + keylen); ph->param_type = htons(SCTP_HMAC_LIST); plen = sizeof(*ph) + hmacs_len; ph->param_length = htons(plen); keylen += sizeof(*ph); sctp_serialize_hmaclist(stcb->asoc.local_hmacs, new_key->key + keylen); } #endif if (stcb->asoc.authinfo.random != NULL) sctp_free_key(stcb->asoc.authinfo.random); stcb->asoc.authinfo.random = new_key; stcb->asoc.authinfo.random_len = random_len; } #ifdef SCTP_HMAC_TEST /* * HMAC and key concatenation tests */ static void sctp_print_digest(uint8_t * digest, uint32_t digestlen, const char *str) { uint32_t i; printf("\n%s: 0x", str); if (digest == NULL) return; for (i = 0; i < digestlen; i++) printf("%02x", digest[i]); } static int sctp_test_hmac(const char *str, uint16_t hmac_id, uint8_t * key, uint32_t keylen, uint8_t * text, uint32_t textlen, uint8_t * digest, uint32_t digestlen) { uint8_t computed_digest[SCTP_AUTH_DIGEST_LEN_MAX]; printf("\n%s:", str); sctp_hmac(hmac_id, key, keylen, text, textlen, computed_digest); sctp_print_digest(digest, digestlen, "Expected digest"); sctp_print_digest(computed_digest, digestlen, "Computed digest"); if (memcmp(digest, computed_digest, digestlen) != 0) { printf("\nFAILED"); return (-1); } else { printf("\nPASSED"); return (0); } } /* * RFC 2202: HMAC-SHA1 test cases */ void sctp_test_hmac_sha1(void) { uint8_t *digest; uint8_t key[128]; uint32_t keylen; uint8_t text[128]; uint32_t textlen; uint32_t digestlen = 20; int failed = 0; /* * test_case = 1 key = * 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b key_len = 20 * data = "Hi There" data_len = 8 digest = * 0xb617318655057264e28bc0b6fb378c8ef146be00 */ keylen = 20; memset(key, 0x0b, keylen); textlen = 8; strcpy(text, "Hi There"); digest = "\xb6\x17\x31\x86\x55\x05\x72\x64\xe2\x8b\xc0\xb6\xfb\x37\x8c\x8e\xf1\x46\xbe\x00"; if (sctp_test_hmac("SHA1 test case 1", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 2 key = "Jefe" key_len = 4 data = * "what do ya want for nothing?" data_len = 28 digest = * 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79 */ keylen = 4; strcpy(key, "Jefe"); textlen = 28; strcpy(text, "what do ya want for nothing?"); digest = "\xef\xfc\xdf\x6a\xe5\xeb\x2f\xa2\xd2\x74\x16\xd5\xf1\x84\xdf\x9c\x25\x9a\x7c\x79"; if (sctp_test_hmac("SHA1 test case 2", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 3 key = * 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa key_len = 20 * data = 0xdd repeated 50 times data_len = 50 digest * = 0x125d7342b9ac11cd91a39af48aa17b4f63f175d3 */ keylen = 20; memset(key, 0xaa, keylen); textlen = 50; memset(text, 0xdd, textlen); digest = "\x12\x5d\x73\x42\xb9\xac\x11\xcd\x91\xa3\x9a\xf4\x8a\xa1\x7b\x4f\x63\xf1\x75\xd3"; if (sctp_test_hmac("SHA1 test case 3", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 4 key = * 0x0102030405060708090a0b0c0d0e0f10111213141516171819 key_len = 25 * data = 0xcd repeated 50 times data_len = 50 digest * = 0x4c9007f4026250c6bc8414f9bf50c86c2d7235da */ keylen = 25; memcpy(key, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", keylen); textlen = 50; memset(text, 0xcd, textlen); digest = "\x4c\x90\x07\xf4\x02\x62\x50\xc6\xbc\x84\x14\xf9\xbf\x50\xc8\x6c\x2d\x72\x35\xda"; if (sctp_test_hmac("SHA1 test case 4", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 5 key = * 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c key_len = 20 * data = "Test With Truncation" data_len = 20 digest * = 0x4c1a03424b55e07fe7f27be1d58bb9324a9a5a04 digest-96 = * 0x4c1a03424b55e07fe7f27be1 */ keylen = 20; memset(key, 0x0c, keylen); textlen = 20; strcpy(text, "Test With Truncation"); digest = "\x4c\x1a\x03\x42\x4b\x55\xe0\x7f\xe7\xf2\x7b\xe1\xd5\x8b\xb9\x32\x4a\x9a\x5a\x04"; if (sctp_test_hmac("SHA1 test case 5", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 6 key = 0xaa repeated 80 times key_len * = 80 data = "Test Using Larger Than Block-Size Key - * Hash Key First" data_len = 54 digest = * 0xaa4ae5e15272d00e95705637ce8a3b55ed402112 */ keylen = 80; memset(key, 0xaa, keylen); textlen = 54; strcpy(text, "Test Using Larger Than Block-Size Key - Hash Key First"); digest = "\xaa\x4a\xe5\xe1\x52\x72\xd0\x0e\x95\x70\x56\x37\xce\x8a\x3b\x55\xed\x40\x21\x12"; if (sctp_test_hmac("SHA1 test case 6", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 7 key = 0xaa repeated 80 times key_len * = 80 data = "Test Using Larger Than Block-Size Key and * Larger Than One Block-Size Data" data_len = 73 digest = * 0xe8e99d0f45237d786d6bbaa7965c7808bbff1a91 */ keylen = 80; memset(key, 0xaa, keylen); textlen = 73; strcpy(text, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"); digest = "\xe8\xe9\x9d\x0f\x45\x23\x7d\x78\x6d\x6b\xba\xa7\x96\x5c\x78\x08\xbb\xff\x1a\x91"; if (sctp_test_hmac("SHA1 test case 7", SCTP_AUTH_HMAC_ID_SHA1, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* done with all tests */ if (failed) printf("\nSHA1 test results: %d cases failed", failed); else printf("\nSHA1 test results: all test cases passed"); } /* * RFC 2202: HMAC-MD5 test cases */ void sctp_test_hmac_md5(void) { uint8_t *digest; uint8_t key[128]; uint32_t keylen; uint8_t text[128]; uint32_t textlen; uint32_t digestlen = 16; int failed = 0; /* * test_case = 1 key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b * key_len = 16 data = "Hi There" data_len = 8 digest = * 0x9294727a3638bb1c13f48ef8158bfc9d */ keylen = 16; memset(key, 0x0b, keylen); textlen = 8; strcpy(text, "Hi There"); digest = "\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d"; if (sctp_test_hmac("MD5 test case 1", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 2 key = "Jefe" key_len = 4 data = * "what do ya want for nothing?" data_len = 28 digest = * 0x750c783e6ab0b503eaa86e310a5db738 */ keylen = 4; strcpy(key, "Jefe"); textlen = 28; strcpy(text, "what do ya want for nothing?"); digest = "\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38"; if (sctp_test_hmac("MD5 test case 2", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 3 key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * key_len = 16 data = 0xdd repeated 50 times data_len = 50 * digest = 0x56be34521d144c88dbb8c733f0e8b3f6 */ keylen = 16; memset(key, 0xaa, keylen); textlen = 50; memset(text, 0xdd, textlen); digest = "\x56\xbe\x34\x52\x1d\x14\x4c\x88\xdb\xb8\xc7\x33\xf0\xe8\xb3\xf6"; if (sctp_test_hmac("MD5 test case 3", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 4 key = * 0x0102030405060708090a0b0c0d0e0f10111213141516171819 key_len = 25 * data = 0xcd repeated 50 times data_len = 50 digest * = 0x697eaf0aca3a3aea3a75164746ffaa79 */ keylen = 25; memcpy(key, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", keylen); textlen = 50; memset(text, 0xcd, textlen); digest = "\x69\x7e\xaf\x0a\xca\x3a\x3a\xea\x3a\x75\x16\x47\x46\xff\xaa\x79"; if (sctp_test_hmac("MD5 test case 4", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 5 key = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c * key_len = 16 data = "Test With Truncation" data_len = 20 * digest = 0x56461ef2342edc00f9bab995690efd4c digest-96 * 0x56461ef2342edc00f9bab995 */ keylen = 16; memset(key, 0x0c, keylen); textlen = 20; strcpy(text, "Test With Truncation"); digest = "\x56\x46\x1e\xf2\x34\x2e\xdc\x00\xf9\xba\xb9\x95\x69\x0e\xfd\x4c"; if (sctp_test_hmac("MD5 test case 5", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 6 key = 0xaa repeated 80 times key_len * = 80 data = "Test Using Larger Than Block-Size Key - * Hash Key First" data_len = 54 digest = * 0x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd */ keylen = 80; memset(key, 0xaa, keylen); textlen = 54; strcpy(text, "Test Using Larger Than Block-Size Key - Hash Key First"); digest = "\x6b\x1a\xb7\xfe\x4b\xd7\xbf\x8f\x0b\x62\xe6\xce\x61\xb9\xd0\xcd"; if (sctp_test_hmac("MD5 test case 6", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* * test_case = 7 key = 0xaa repeated 80 times key_len * = 80 data = "Test Using Larger Than Block-Size Key and * Larger Than One Block-Size Data" data_len = 73 digest = * 0x6f630fad67cda0ee1fb1f562db3aa53e */ keylen = 80; memset(key, 0xaa, keylen); textlen = 73; strcpy(text, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"); digest = "\x6f\x63\x0f\xad\x67\xcd\xa0\xee\x1f\xb1\xf5\x62\xdb\x3a\xa5\x3e"; if (sctp_test_hmac("MD5 test case 7", SCTP_AUTH_HMAC_ID_MD5, key, keylen, text, textlen, digest, digestlen) < 0) failed++; /* done with all tests */ if (failed) printf("\nMD5 test results: %d cases failed", failed); else printf("\nMD5 test results: all test cases passed"); } /* * test assoc key concatenation */ static int sctp_test_key_concatenation(sctp_key_t * key1, sctp_key_t * key2, sctp_key_t * expected_key) { sctp_key_t *key; int ret_val; sctp_show_key(key1, "\nkey1"); sctp_show_key(key2, "\nkey2"); key = sctp_compute_hashkey(key1, key2, NULL); sctp_show_key(expected_key, "\nExpected"); sctp_show_key(key, "\nComputed"); if (memcmp(key, expected_key, expected_key->keylen) != 0) { printf("\nFAILED"); ret_val = -1; } else { printf("\nPASSED"); ret_val = 0; } sctp_free_key(key1); sctp_free_key(key2); sctp_free_key(expected_key); sctp_free_key(key); return (ret_val); } void sctp_test_authkey(void) { sctp_key_t *key1, *key2, *expected_key; int failed = 0; /* test case 1 */ key1 = sctp_set_key("\x01\x01\x01\x01", 4); key2 = sctp_set_key("\x01\x02\x03\x04", 4); expected_key = sctp_set_key("\x01\x01\x01\x01\x01\x02\x03\x04", 8); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 2 */ key1 = sctp_set_key("\x00\x00\x00\x01", 4); key2 = sctp_set_key("\x02", 1); expected_key = sctp_set_key("\x00\x00\x00\x01\x02", 5); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 3 */ key1 = sctp_set_key("\x01", 1); key2 = sctp_set_key("\x00\x00\x00\x02", 4); expected_key = sctp_set_key("\x01\x00\x00\x00\x02", 5); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 4 */ key1 = sctp_set_key("\x00\x00\x00\x01", 4); key2 = sctp_set_key("\x01", 1); expected_key = sctp_set_key("\x01\x00\x00\x00\x01", 5); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 5 */ key1 = sctp_set_key("\x01", 1); key2 = sctp_set_key("\x00\x00\x00\x01", 4); expected_key = sctp_set_key("\x01\x00\x00\x00\x01", 5); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 6 */ key1 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07", 11); key2 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 11); expected_key = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 22); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* test case 7 */ key1 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 11); key2 = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07", 11); expected_key = sctp_set_key("\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x08", 22); if (sctp_test_key_concatenation(key1, key2, expected_key) < 0) failed++; /* done with all tests */ if (failed) printf("\nKey concatenation test results: %d cases failed", failed); else printf("\nKey concatenation test results: all test cases passed"); } #if defined(STANDALONE_HMAC_TEST) int main(void) { sctp_test_hmac_sha1(); sctp_test_hmac_md5(); sctp_test_authkey(); } #endif /* STANDALONE_HMAC_TEST */ #endif /* SCTP_HMAC_TEST */ diff --git a/sys/netinet/sctp_indata.c b/sys/netinet/sctp_indata.c index 3f30220c1a1a..6ca9985e71bc 100644 --- a/sys/netinet/sctp_indata.c +++ b/sys/netinet/sctp_indata.c @@ -1,5990 +1,5990 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_indata.c,v 1.36 2005/03/06 16:04:17 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include /* * NOTES: On the outbound side of things I need to check the sack timer to * see if I should generate a sack into the chunk queue (if I have data to * send that is and will be sending it .. for bundling. * * The callback in sctp_usrreq.c will get called when the socket is read from. * This will cause sctp_service_queues() to get called on the top entry in * the list. */ __inline void sctp_set_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc) { uint32_t calc, calc_w_oh; /* * This is really set wrong with respect to a 1-2-m socket. Since * the sb_cc is the count that everyone as put up. When we re-write * sctp_soreceive then we will fix this so that ONLY this * associations data is taken into account. */ if (stcb->sctp_socket == NULL) return; if (stcb->asoc.sb_cc == 0 && asoc->size_on_reasm_queue == 0 && asoc->size_on_all_streams == 0) { /* Full rwnd granted */ asoc->my_rwnd = max(SCTP_SB_LIMIT_RCV(stcb->sctp_socket), SCTP_MINIMAL_RWND); return; } /* get actual space */ calc = (uint32_t) sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv); /* * take out what has NOT been put on socket queue and we yet hold * for putting up. */ calc = sctp_sbspace_sub(calc, (uint32_t) asoc->size_on_reasm_queue); calc = sctp_sbspace_sub(calc, (uint32_t) asoc->size_on_all_streams); if (calc == 0) { /* out of space */ asoc->my_rwnd = 0; return; } /* what is the overhead of all these rwnd's */ calc_w_oh = sctp_sbspace_sub(calc, stcb->asoc.my_rwnd_control_len); asoc->my_rwnd = calc; if (calc_w_oh == 0) { /* * If our overhead is greater than the advertised rwnd, we * clamp the rwnd to 1. This lets us still accept inbound * segments, but hopefully will shut the sender down when he * finally gets the message. */ asoc->my_rwnd = 1; } else { /* SWS threshold */ if (asoc->my_rwnd && (asoc->my_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_receiver)) { /* SWS engaged, tell peer none left */ asoc->my_rwnd = 1; } } } /* Calculate what the rwnd would be */ __inline uint32_t sctp_calc_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc) { uint32_t calc = 0, calc_w_oh; /* * This is really set wrong with respect to a 1-2-m socket. Since * the sb_cc is the count that everyone as put up. When we re-write * sctp_soreceive then we will fix this so that ONLY this * associations data is taken into account. */ if (stcb->sctp_socket == NULL) return (calc); if (stcb->asoc.sb_cc == 0 && asoc->size_on_reasm_queue == 0 && asoc->size_on_all_streams == 0) { /* Full rwnd granted */ calc = max(SCTP_SB_LIMIT_RCV(stcb->sctp_socket), SCTP_MINIMAL_RWND); return (calc); } /* get actual space */ calc = (uint32_t) sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv); /* * take out what has NOT been put on socket queue and we yet hold * for putting up. */ calc = sctp_sbspace_sub(calc, (uint32_t) asoc->size_on_reasm_queue); calc = sctp_sbspace_sub(calc, (uint32_t) asoc->size_on_all_streams); if (calc == 0) { /* out of space */ return (calc); } /* what is the overhead of all these rwnd's */ calc_w_oh = sctp_sbspace_sub(calc, stcb->asoc.my_rwnd_control_len); if (calc_w_oh == 0) { /* * If our overhead is greater than the advertised rwnd, we * clamp the rwnd to 1. This lets us still accept inbound * segments, but hopefully will shut the sender down when he * finally gets the message. */ calc = 1; } else { /* SWS threshold */ if (calc && (calc < stcb->sctp_ep->sctp_ep.sctp_sws_receiver)) { /* SWS engaged, tell peer none left */ calc = 1; } } return (calc); } /* * Build out our readq entry based on the incoming packet. */ struct sctp_queued_to_read * sctp_build_readq_entry(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t tsn, uint32_t ppid, uint32_t context, uint16_t stream_no, uint16_t stream_seq, uint8_t flags, struct mbuf *dm) { struct sctp_queued_to_read *read_queue_e = NULL; sctp_alloc_a_readq(stcb, read_queue_e); if (read_queue_e == NULL) { goto failed_build; } read_queue_e->sinfo_stream = stream_no; read_queue_e->sinfo_ssn = stream_seq; read_queue_e->sinfo_flags = (flags << 8); read_queue_e->sinfo_ppid = ppid; read_queue_e->sinfo_context = stcb->asoc.context; read_queue_e->sinfo_timetolive = 0; read_queue_e->sinfo_tsn = tsn; read_queue_e->sinfo_cumtsn = tsn; read_queue_e->sinfo_assoc_id = sctp_get_associd(stcb); read_queue_e->whoFrom = net; read_queue_e->length = 0; atomic_add_int(&net->ref_count, 1); read_queue_e->data = dm; read_queue_e->spec_flags = 0; read_queue_e->tail_mbuf = NULL; read_queue_e->aux_data = NULL; read_queue_e->stcb = stcb; read_queue_e->port_from = stcb->rport; read_queue_e->do_not_ref_stcb = 0; read_queue_e->end_added = 0; read_queue_e->some_taken = 0; read_queue_e->pdapi_aborted = 0; failed_build: return (read_queue_e); } /* * Build out our readq entry based on the incoming packet. */ static struct sctp_queued_to_read * sctp_build_readq_entry_chk(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk) { struct sctp_queued_to_read *read_queue_e = NULL; sctp_alloc_a_readq(stcb, read_queue_e); if (read_queue_e == NULL) { goto failed_build; } read_queue_e->sinfo_stream = chk->rec.data.stream_number; read_queue_e->sinfo_ssn = chk->rec.data.stream_seq; read_queue_e->sinfo_flags = (chk->rec.data.rcv_flags << 8); read_queue_e->sinfo_ppid = chk->rec.data.payloadtype; read_queue_e->sinfo_context = stcb->asoc.context; read_queue_e->sinfo_timetolive = 0; read_queue_e->sinfo_tsn = chk->rec.data.TSN_seq; read_queue_e->sinfo_cumtsn = chk->rec.data.TSN_seq; read_queue_e->sinfo_assoc_id = sctp_get_associd(stcb); read_queue_e->whoFrom = chk->whoTo; read_queue_e->aux_data = NULL; read_queue_e->length = 0; atomic_add_int(&chk->whoTo->ref_count, 1); read_queue_e->data = chk->data; read_queue_e->tail_mbuf = NULL; read_queue_e->stcb = stcb; read_queue_e->port_from = stcb->rport; read_queue_e->spec_flags = 0; read_queue_e->do_not_ref_stcb = 0; read_queue_e->end_added = 0; read_queue_e->some_taken = 0; read_queue_e->pdapi_aborted = 0; failed_build: return (read_queue_e); } struct mbuf * sctp_build_ctl_nchunk(struct sctp_inpcb *inp, struct sctp_sndrcvinfo *sinfo) { struct sctp_sndrcvinfo *outinfo; struct cmsghdr *cmh; struct mbuf *ret; int len; int use_extended = 0; if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) { /* user does not want the sndrcv ctl */ return (NULL); } if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO)) { use_extended = 1; len = CMSG_LEN(sizeof(struct sctp_extrcvinfo)); } else { len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); } ret = sctp_get_mbuf_for_msg(len, 0, M_DONTWAIT, 1, MT_DATA); if (ret == NULL) { /* No space */ return (ret); } /* We need a CMSG header followed by the struct */ cmh = mtod(ret, struct cmsghdr *); outinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmh); cmh->cmsg_level = IPPROTO_SCTP; if (use_extended) { cmh->cmsg_type = SCTP_EXTRCV; cmh->cmsg_len = len; memcpy(outinfo, sinfo, len); } else { cmh->cmsg_type = SCTP_SNDRCV; cmh->cmsg_len = len; *outinfo = *sinfo; } SCTP_BUF_LEN(ret) = cmh->cmsg_len; return (ret); } char * sctp_build_ctl_cchunk(struct sctp_inpcb *inp, int *control_len, struct sctp_sndrcvinfo *sinfo) { struct sctp_sndrcvinfo *outinfo; struct cmsghdr *cmh; char *buf; int len; int use_extended = 0; if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) { /* user does not want the sndrcv ctl */ return (NULL); } if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO)) { use_extended = 1; len = CMSG_LEN(sizeof(struct sctp_extrcvinfo)); } else { len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); } SCTP_MALLOC(buf, char *, len, "SCTP_CMSG"); if (buf == NULL) { /* No space */ return (buf); } /* We need a CMSG header followed by the struct */ cmh = (struct cmsghdr *)buf; outinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmh); cmh->cmsg_level = IPPROTO_SCTP; if (use_extended) { cmh->cmsg_type = SCTP_EXTRCV; cmh->cmsg_len = len; memcpy(outinfo, sinfo, len); } else { cmh->cmsg_type = SCTP_SNDRCV; cmh->cmsg_len = len; *outinfo = *sinfo; } *control_len = len; return (buf); } /* * We are delivering currently from the reassembly queue. We must continue to * deliver until we either: 1) run out of space. 2) run out of sequential * TSN's 3) hit the SCTP_DATA_LAST_FRAG flag. */ static void sctp_service_reassembly(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; uint16_t nxt_todel; uint16_t stream_no; int end = 0; int cntDel; struct sctp_queued_to_read *control, *ctl, *ctlat; cntDel = stream_no = 0; if (stcb && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET))) { /* socket above is long gone */ asoc->fragmented_delivery_inprogress = 0; chk = TAILQ_FIRST(&asoc->reasmqueue); while (chk) { TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); asoc->size_on_reasm_queue -= chk->send_size; sctp_ucount_decr(asoc->cnt_on_reasm_queue); /* * Lose the data pointer, since its in the socket * buffer */ if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } /* Now free the address and data */ sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); chk = TAILQ_FIRST(&asoc->reasmqueue); } return; } SCTP_TCB_LOCK_ASSERT(stcb); do { chk = TAILQ_FIRST(&asoc->reasmqueue); if (chk == NULL) { return; } if (chk->rec.data.TSN_seq != (asoc->tsn_last_delivered + 1)) { /* Can't deliver more :< */ return; } stream_no = chk->rec.data.stream_number; nxt_todel = asoc->strmin[stream_no].last_sequence_delivered + 1; if (nxt_todel != chk->rec.data.stream_seq && (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) { /* * Not the next sequence to deliver in its stream OR * unordered */ return; } if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { control = sctp_build_readq_entry_chk(stcb, chk); if (control == NULL) { /* out of memory? */ return; } /* save it off for our future deliveries */ stcb->asoc.control_pdapi = control; if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) end = 1; else end = 0; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, end); cntDel++; } else { if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) end = 1; else end = 0; if (sctp_append_to_readq(stcb->sctp_ep, stcb, stcb->asoc.control_pdapi, chk->data, end, chk->rec.data.TSN_seq, &stcb->sctp_socket->so_rcv)) { /* * something is very wrong, either * control_pdapi is NULL, or the tail_mbuf * is corrupt, or there is a EOM already on * the mbuf chain. */ if (stcb->asoc.control_pdapi == NULL) { panic("This should not happen control_pdapi NULL?"); } if (stcb->asoc.control_pdapi->tail_mbuf == NULL) { panic("This should not happen, tail_mbuf not being maintained?"); } /* if we did not panic, it was a EOM */ panic("Bad chunking ??"); } cntDel++; } /* pull it we did it */ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { asoc->fragmented_delivery_inprogress = 0; if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) { asoc->strmin[stream_no].last_sequence_delivered++; } if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) { SCTP_STAT_INCR_COUNTER64(sctps_reasmusrmsgs); } } else if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { /* * turn the flag back on since we just delivered * yet another one. */ asoc->fragmented_delivery_inprogress = 1; } asoc->tsn_of_pdapi_last_delivered = chk->rec.data.TSN_seq; asoc->last_flags_delivered = chk->rec.data.rcv_flags; asoc->last_strm_seq_delivered = chk->rec.data.stream_seq; asoc->last_strm_no_delivered = chk->rec.data.stream_number; asoc->tsn_last_delivered = chk->rec.data.TSN_seq; asoc->size_on_reasm_queue -= chk->send_size; sctp_ucount_decr(asoc->cnt_on_reasm_queue); /* free up the chk */ chk->data = NULL; sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); if (asoc->fragmented_delivery_inprogress == 0) { /* * Now lets see if we can deliver the next one on * the stream */ uint16_t nxt_todel; struct sctp_stream_in *strm; strm = &asoc->strmin[stream_no]; nxt_todel = strm->last_sequence_delivered + 1; ctl = TAILQ_FIRST(&strm->inqueue); if (ctl && (nxt_todel == ctl->sinfo_ssn)) { while (ctl != NULL) { /* Deliver more if we can. */ if (nxt_todel == ctl->sinfo_ssn) { ctlat = TAILQ_NEXT(ctl, next); TAILQ_REMOVE(&strm->inqueue, ctl, next); asoc->size_on_all_streams -= ctl->length; sctp_ucount_decr(asoc->cnt_on_all_streams); strm->last_sequence_delivered++; sctp_add_to_readq(stcb->sctp_ep, stcb, ctl, &stcb->sctp_socket->so_rcv, 1); ctl = ctlat; } else { break; } nxt_todel = strm->last_sequence_delivered + 1; } } break; } chk = TAILQ_FIRST(&asoc->reasmqueue); } while (chk); } /* * Queue the chunk either right into the socket buffer if it is the next one * to go OR put it in the correct place in the delivery queue. If we do * append to the so_buf, keep doing so until we are out of order. One big * question still remains, what to do when the socket buffer is FULL?? */ static void sctp_queue_data_to_stream(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_queued_to_read *control, int *abort_flag) { /* * FIX-ME maybe? What happens when the ssn wraps? If we are getting * all the data in one stream this could happen quite rapidly. One * could use the TSN to keep track of things, but this scheme breaks * down in the other type of stream useage that could occur. Send a * single msg to stream 0, send 4Billion messages to stream 1, now * send a message to stream 0. You have a situation where the TSN * has wrapped but not in the stream. Is this worth worrying about * or should we just change our queue sort at the bottom to be by * TSN. * * Could it also be legal for a peer to send ssn 1 with TSN 2 and ssn 2 * with TSN 1? If the peer is doing some sort of funky TSN/SSN * assignment this could happen... and I don't see how this would be * a violation. So for now I am undecided an will leave the sort by * SSN alone. Maybe a hybred approach is the answer * */ struct sctp_stream_in *strm; struct sctp_queued_to_read *at; int queue_needed; uint16_t nxt_todel; struct mbuf *oper; queue_needed = 1; asoc->size_on_all_streams += control->length; sctp_ucount_incr(asoc->cnt_on_all_streams); strm = &asoc->strmin[control->sinfo_stream]; nxt_todel = strm->last_sequence_delivered + 1; #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_INTO_STRD); #endif #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("queue to stream called for ssn:%u lastdel:%u nxt:%u\n", (uint32_t) control->sinfo_stream, (uint32_t) strm->last_sequence_delivered, (uint32_t) nxt_todel); } #endif if (compare_with_wrap(strm->last_sequence_delivered, control->sinfo_ssn, MAX_SEQ) || (strm->last_sequence_delivered == control->sinfo_ssn)) { /* The incoming sseq is behind where we last delivered? */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Duplicate S-SEQ:%d delivered:%d from peer, Abort association\n", control->sinfo_ssn, strm->last_sequence_delivered); } #endif /* * throw it in the stream so it gets cleaned up in * association destruction */ TAILQ_INSERT_HEAD(&strm->inqueue, control, next); oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (sizeof(uint32_t) * 3); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_1); ippp++; *ippp = control->sinfo_tsn; ippp++; *ippp = ((control->sinfo_stream << 16) | control->sinfo_ssn); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_1; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } if (nxt_todel == control->sinfo_ssn) { /* can be delivered right away? */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_IMMED_DEL); #endif queue_needed = 0; asoc->size_on_all_streams -= control->length; sctp_ucount_decr(asoc->cnt_on_all_streams); strm->last_sequence_delivered++; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); control = TAILQ_FIRST(&strm->inqueue); while (control != NULL) { /* all delivered */ nxt_todel = strm->last_sequence_delivered + 1; if (nxt_todel == control->sinfo_ssn) { at = TAILQ_NEXT(control, next); TAILQ_REMOVE(&strm->inqueue, control, next); asoc->size_on_all_streams -= control->length; sctp_ucount_decr(asoc->cnt_on_all_streams); strm->last_sequence_delivered++; /* * We ignore the return of deliver_data here * since we always can hold the chunk on the * d-queue. And we have a finite number that * can be delivered from the strq. */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_IMMED_DEL); #endif sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); control = at; continue; } break; } } if (queue_needed) { /* * Ok, we did not deliver this guy, find the correct place * to put it on the queue. */ if (TAILQ_EMPTY(&strm->inqueue)) { /* Empty queue */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, NULL, SCTP_STR_LOG_FROM_INSERT_HD); #endif TAILQ_INSERT_HEAD(&strm->inqueue, control, next); } else { TAILQ_FOREACH(at, &strm->inqueue, next) { if (compare_with_wrap(at->sinfo_ssn, control->sinfo_ssn, MAX_SEQ)) { /* * one in queue is bigger than the * new one, insert before this one */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, at, SCTP_STR_LOG_FROM_INSERT_MD); #endif TAILQ_INSERT_BEFORE(at, control, next); break; } else if (at->sinfo_ssn == control->sinfo_ssn) { /* * Gak, He sent me a duplicate str * seq number */ /* * foo bar, I guess I will just free * this new guy, should we abort * too? FIX ME MAYBE? Or it COULD be * that the SSN's have wrapped. * Maybe I should compare to TSN * somehow... sigh for now just blow * away the chunk! */ if (control->data) sctp_m_freem(control->data); control->data = NULL; asoc->size_on_all_streams -= control->length; sctp_ucount_decr(asoc->cnt_on_all_streams); sctp_free_remote_addr(control->whoFrom); sctp_free_a_readq(stcb, control); return; } else { if (TAILQ_NEXT(at, next) == NULL) { /* * We are at the end, insert * it after this one */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del(control, at, SCTP_STR_LOG_FROM_INSERT_TL); #endif TAILQ_INSERT_AFTER(&strm->inqueue, at, control, next); break; } } } } } } /* * Returns two things: You get the total size of the deliverable parts of the * first fragmented message on the reassembly queue. And you get a 1 back if * all of the message is ready or a 0 back if the message is still incomplete */ static int sctp_is_all_msg_on_reasm(struct sctp_association *asoc, uint32_t * t_size) { struct sctp_tmit_chunk *chk; uint32_t tsn; *t_size = 0; chk = TAILQ_FIRST(&asoc->reasmqueue); if (chk == NULL) { /* nothing on the queue */ return (0); } if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) { /* Not a first on the queue */ return (0); } tsn = chk->rec.data.TSN_seq; while (chk) { if (tsn != chk->rec.data.TSN_seq) { return (0); } *t_size += chk->send_size; if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { return (1); } tsn++; chk = TAILQ_NEXT(chk, sctp_next); } return (0); } static void sctp_deliver_reasm_check(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; uint16_t nxt_todel; uint32_t tsize; doit_again: chk = TAILQ_FIRST(&asoc->reasmqueue); if (chk == NULL) { /* Huh? */ asoc->size_on_reasm_queue = 0; asoc->cnt_on_reasm_queue = 0; return; } if (asoc->fragmented_delivery_inprogress == 0) { nxt_todel = asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1; if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) && (nxt_todel == chk->rec.data.stream_seq || (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) { /* * Yep the first one is here and its ok to deliver * but should we? */ if ((sctp_is_all_msg_on_reasm(asoc, &tsize) || (tsize > stcb->sctp_ep->partial_delivery_point))) { /* * Yes, we setup to start reception, by * backing down the TSN just in case we * can't deliver. If we */ asoc->fragmented_delivery_inprogress = 1; asoc->tsn_last_delivered = chk->rec.data.TSN_seq - 1; asoc->str_of_pdapi = chk->rec.data.stream_number; asoc->ssn_of_pdapi = chk->rec.data.stream_seq; asoc->pdapi_ppid = chk->rec.data.payloadtype; asoc->fragment_flags = chk->rec.data.rcv_flags; sctp_service_reassembly(stcb, asoc); } } } else { /* * Service re-assembly will deliver stream data queued at * the end of fragmented delivery.. but it wont know to go * back and call itself again... we do that here with the * got doit_again */ sctp_service_reassembly(stcb, asoc); if (asoc->fragmented_delivery_inprogress == 0) { /* * finished our Fragmented delivery, could be more * waiting? */ goto doit_again; } } } /* * Dump onto the re-assembly queue, in its proper place. After dumping on the * queue, see if anthing can be delivered. If so pull it off (or as much as * we can. If we run out of space then we must dump what we can and set the * appropriate flag to say we queued what we could. */ static void sctp_queue_data_for_reasm(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_tmit_chunk *chk, int *abort_flag) { struct mbuf *oper; uint32_t cum_ackp1, last_tsn, prev_tsn, post_tsn; u_char last_flags; struct sctp_tmit_chunk *at, *prev, *next; prev = next = NULL; cum_ackp1 = asoc->tsn_last_delivered + 1; if (TAILQ_EMPTY(&asoc->reasmqueue)) { /* This is the first one on the queue */ TAILQ_INSERT_HEAD(&asoc->reasmqueue, chk, sctp_next); /* * we do not check for delivery of anything when only one * fragment is here */ asoc->size_on_reasm_queue = chk->send_size; sctp_ucount_incr(asoc->cnt_on_reasm_queue); if (chk->rec.data.TSN_seq == cum_ackp1) { if (asoc->fragmented_delivery_inprogress == 0 && (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) != SCTP_DATA_FIRST_FRAG) { /* * An empty queue, no delivery inprogress, * we hit the next one and it does NOT have * a FIRST fragment mark. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Gak, Evil plot, its not first, no fragmented delivery in progress\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (sizeof(uint32_t) * 3); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_2); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_2; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; } else if (asoc->fragmented_delivery_inprogress && (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == SCTP_DATA_FIRST_FRAG) { /* * We are doing a partial delivery and the * NEXT chunk MUST be either the LAST or * MIDDLE fragment NOT a FIRST */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Gak, Evil plot, it IS a first and fragmented delivery in progress\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_3); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_3; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; } else if (asoc->fragmented_delivery_inprogress) { /* * Here we are ok with a MIDDLE or LAST * piece */ if (chk->rec.data.stream_number != asoc->str_of_pdapi) { /* Got to be the right STR No */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Gak, Evil plot, it IS not same stream number %d vs %d\n", chk->rec.data.stream_number, asoc->str_of_pdapi); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (sizeof(uint32_t) * 3); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_4); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_4; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; } else if ((asoc->fragment_flags & SCTP_DATA_UNORDERED) != SCTP_DATA_UNORDERED && chk->rec.data.stream_seq != asoc->ssn_of_pdapi) { /* Got to be the right STR Seq */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Gak, Evil plot, it IS not same stream seq %d vs %d\n", chk->rec.data.stream_seq, asoc->ssn_of_pdapi); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_5); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_5; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; } } } return; } /* Find its place */ TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) { if (compare_with_wrap(at->rec.data.TSN_seq, chk->rec.data.TSN_seq, MAX_TSN)) { /* * one in queue is bigger than the new one, insert * before this one */ /* A check */ asoc->size_on_reasm_queue += chk->send_size; sctp_ucount_incr(asoc->cnt_on_reasm_queue); next = at; TAILQ_INSERT_BEFORE(at, chk, sctp_next); break; } else if (at->rec.data.TSN_seq == chk->rec.data.TSN_seq) { /* Gak, He sent me a duplicate str seq number */ /* * foo bar, I guess I will just free this new guy, * should we abort too? FIX ME MAYBE? Or it COULD be * that the SSN's have wrapped. Maybe I should * compare to TSN somehow... sigh for now just blow * away the chunk! */ if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); return; } else { last_flags = at->rec.data.rcv_flags; last_tsn = at->rec.data.TSN_seq; prev = at; if (TAILQ_NEXT(at, sctp_next) == NULL) { /* * We are at the end, insert it after this * one */ /* check it first */ asoc->size_on_reasm_queue += chk->send_size; sctp_ucount_incr(asoc->cnt_on_reasm_queue); TAILQ_INSERT_AFTER(&asoc->reasmqueue, at, chk, sctp_next); break; } } } /* Now the audits */ if (prev) { prev_tsn = chk->rec.data.TSN_seq - 1; if (prev_tsn == prev->rec.data.TSN_seq) { /* * Ok the one I am dropping onto the end is the * NEXT. A bit of valdiation here. */ if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_FIRST_FRAG || (prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_MIDDLE_FRAG) { /* * Insert chk MUST be a MIDDLE or LAST * fragment */ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_FIRST_FRAG) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Prev check - It can be a midlle or last but not a first\n"); printf("Gak, Evil plot, it's a FIRST!\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_6); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_6; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } if (chk->rec.data.stream_number != prev->rec.data.stream_number) { /* * Huh, need the correct STR here, * they must be the same. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Prev check - Gak, Evil plot, ssn:%d not the same as at:%d\n", chk->rec.data.stream_number, prev->rec.data.stream_number); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_7); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_7; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } if ((prev->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 && chk->rec.data.stream_seq != prev->rec.data.stream_seq) { /* * Huh, need the correct STR here, * they must be the same. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Prev check - Gak, Evil plot, sseq:%d not the same as at:%d\n", chk->rec.data.stream_seq, prev->rec.data.stream_seq); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_8); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_8; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } } else if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_LAST_FRAG) { /* Insert chk MUST be a FIRST */ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != SCTP_DATA_FIRST_FRAG) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Prev check - Gak, evil plot, its not FIRST and it must be!\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_9); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_9; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } } } } if (next) { post_tsn = chk->rec.data.TSN_seq + 1; if (post_tsn == next->rec.data.TSN_seq) { /* * Ok the one I am inserting ahead of is my NEXT * one. A bit of valdiation here. */ if (next->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { /* Insert chk MUST be a last fragment */ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != SCTP_DATA_LAST_FRAG) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Next chk - Next is FIRST, we must be LAST\n"); printf("Gak, Evil plot, its not a last!\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_10); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_10; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } } else if ((next->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_MIDDLE_FRAG || (next->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_LAST_FRAG) { /* * Insert chk CAN be MIDDLE or FIRST NOT * LAST */ if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == SCTP_DATA_LAST_FRAG) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Next chk - Next is a MIDDLE/LAST\n"); printf("Gak, Evil plot, new prev chunk is a LAST\n"); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_11); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_11; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } if (chk->rec.data.stream_number != next->rec.data.stream_number) { /* * Huh, need the correct STR here, * they must be the same. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Next chk - Gak, Evil plot, ssn:%d not the same as at:%d\n", chk->rec.data.stream_number, next->rec.data.stream_number); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_12); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_12; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } if ((next->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 && chk->rec.data.stream_seq != next->rec.data.stream_seq) { /* * Huh, need the correct STR here, * they must be the same. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Next chk - Gak, Evil plot, sseq:%d not the same as at:%d\n", chk->rec.data.stream_seq, next->rec.data.stream_seq); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_13); ippp++; *ippp = chk->rec.data.TSN_seq; ippp++; *ippp = ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_13; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return; } } } } /* Do we need to do some delivery? check */ sctp_deliver_reasm_check(stcb, asoc); } /* * This is an unfortunate routine. It checks to make sure a evil guy is not * stuffing us full of bad packet fragments. A broken peer could also do this * but this is doubtful. It is to bad I must worry about evil crackers sigh * :< more cycles. */ static int sctp_does_tsn_belong_to_reasm(struct sctp_association *asoc, uint32_t TSN_seq) { struct sctp_tmit_chunk *at; uint32_t tsn_est; TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) { if (compare_with_wrap(TSN_seq, at->rec.data.TSN_seq, MAX_TSN)) { /* is it one bigger? */ tsn_est = at->rec.data.TSN_seq + 1; if (tsn_est == TSN_seq) { /* yep. It better be a last then */ if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != SCTP_DATA_LAST_FRAG) { /* * Ok this guy belongs next to a guy * that is NOT last, it should be a * middle/last, not a complete * chunk. */ return (1); } else { /* * This guy is ok since its a LAST * and the new chunk is a fully * self- contained one. */ return (0); } } } else if (TSN_seq == at->rec.data.TSN_seq) { /* Software error since I have a dup? */ return (1); } else { /* * Ok, 'at' is larger than new chunk but does it * need to be right before it. */ tsn_est = TSN_seq + 1; if (tsn_est == at->rec.data.TSN_seq) { /* Yep, It better be a first */ if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != SCTP_DATA_FIRST_FRAG) { return (1); } else { return (0); } } } } return (0); } static int sctp_process_a_data_chunk(struct sctp_tcb *stcb, struct sctp_association *asoc, struct mbuf **m, int offset, struct sctp_data_chunk *ch, int chk_length, struct sctp_nets *net, uint32_t * high_tsn, int *abort_flag, int *break_flag, int last_chunk) { /* Process a data chunk */ /* struct sctp_tmit_chunk *chk; */ struct sctp_tmit_chunk *chk; uint32_t tsn, gap; struct mbuf *dmbuf; int indx, the_len; int need_reasm_check = 0; uint16_t strmno, strmseq; struct mbuf *oper; struct sctp_queued_to_read *control; int ordered; uint32_t protocol_id; uint8_t chunk_flags; struct sctp_stream_reset_list *liste; chk = NULL; tsn = ntohl(ch->dp.tsn); chunk_flags = ch->ch.chunk_flags; protocol_id = ch->dp.protocol_id; ordered = ((ch->ch.chunk_flags & SCTP_DATA_UNORDERED) == 0); #ifdef SCTP_MAP_LOGGING sctp_log_map(0, tsn, asoc->cumulative_tsn, SCTP_MAP_PREPARE_SLIDE); #endif if (compare_with_wrap(asoc->cumulative_tsn, tsn, MAX_TSN) || asoc->cumulative_tsn == tsn) { /* It is a duplicate */ SCTP_STAT_INCR(sctps_recvdupdata); if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) { /* Record a dup for the next outbound sack */ asoc->dup_tsns[asoc->numduptsns] = tsn; asoc->numduptsns++; } return (0); } /* Calculate the number of TSN's between the base and this TSN */ if (tsn >= asoc->mapping_array_base_tsn) { gap = tsn - asoc->mapping_array_base_tsn; } else { gap = (MAX_TSN - asoc->mapping_array_base_tsn) + tsn + 1; } if (gap >= (SCTP_MAPPING_ARRAY << 3)) { /* Can't hold the bit in the mapping at max array, toss it */ return (0); } if (gap >= (uint32_t) (asoc->mapping_array_size << 3)) { if (sctp_expand_mapping_array(asoc)) { /* Can't expand, drop it */ return (0); } } if (compare_with_wrap(tsn, *high_tsn, MAX_TSN)) { *high_tsn = tsn; } /* See if we have received this one already */ if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) { SCTP_STAT_INCR(sctps_recvdupdata); if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) { /* Record a dup for the next outbound sack */ asoc->dup_tsns[asoc->numduptsns] = tsn; asoc->numduptsns++; } asoc->send_sack = 1; return (0); } /* * Check to see about the GONE flag, duplicates would cause a sack * to be sent up above */ if (stcb && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) ) { /* * wait a minute, this guy is gone, there is no longer a * receiver. Send peer an ABORT! */ struct mbuf *op_err; op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); sctp_abort_an_association(stcb->sctp_ep, stcb, 0, op_err); *abort_flag = 1; return (0); } /* * Now before going further we see if there is room. If NOT then we * MAY let one through only IF this TSN is the one we are waiting * for on a partial delivery API. */ /* now do the tests */ if (((asoc->cnt_on_all_streams + asoc->cnt_on_reasm_queue + asoc->cnt_msg_on_sb) > sctp_max_chunks_on_queue) || (((int)asoc->my_rwnd) <= 0)) { /* * When we have NO room in the rwnd we check to make sure * the reader is doing its job... */ if (stcb->sctp_socket->so_rcv.sb_cc) { /* some to read, wake-up */ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); } /* now is it in the mapping array of what we have accepted? */ if (compare_with_wrap(tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { /* Nope not in the valid range dump it */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("My rwnd overrun1:tsn:%lx rwnd %lu sbspace:%ld\n", (u_long)tsn, (u_long)asoc->my_rwnd, sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv)); } #endif sctp_set_rwnd(stcb, asoc); if ((asoc->cnt_on_all_streams + asoc->cnt_on_reasm_queue + asoc->cnt_msg_on_sb) > sctp_max_chunks_on_queue) { SCTP_STAT_INCR(sctps_datadropchklmt); } else { SCTP_STAT_INCR(sctps_datadroprwnd); } indx = *break_flag; *break_flag = 1; return (0); } } strmno = ntohs(ch->dp.stream_id); if (strmno >= asoc->streamincnt) { struct sctp_paramhdr *phdr; struct mbuf *mb; mb = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) * 2), 0, M_DONTWAIT, 1, MT_DATA); if (mb != NULL) { /* add some space up front so prepend will work well */ SCTP_BUF_RESV_UF(mb, sizeof(struct sctp_chunkhdr)); phdr = mtod(mb, struct sctp_paramhdr *); /* * Error causes are just param's and this one has * two back to back phdr, one with the error type * and size, the other with the streamid and a rsvd */ SCTP_BUF_LEN(mb) = (sizeof(struct sctp_paramhdr) * 2); phdr->param_type = htons(SCTP_CAUSE_INVALID_STREAM); phdr->param_length = htons(sizeof(struct sctp_paramhdr) * 2); phdr++; /* We insert the stream in the type field */ phdr->param_type = ch->dp.stream_id; /* And set the length to 0 for the rsvd field */ phdr->param_length = 0; sctp_queue_op_err(stcb, mb); } SCTP_STAT_INCR(sctps_badsid); SCTP_SET_TSN_PRESENT(asoc->mapping_array, gap); if (compare_with_wrap(tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { /* we have a new high score */ asoc->highest_tsn_inside_map = tsn; #ifdef SCTP_MAP_LOGGING sctp_log_map(0, 2, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif } if (tsn == (asoc->cumulative_tsn + 1)) { /* Update cum-ack */ asoc->cumulative_tsn = tsn; } return (0); } /* * Before we continue lets validate that we are not being fooled by * an evil attacker. We can only have 4k chunks based on our TSN * spread allowed by the mapping array 512 * 8 bits, so there is no * way our stream sequence numbers could have wrapped. We of course * only validate the FIRST fragment so the bit must be set. */ strmseq = ntohs(ch->dp.stream_sequence); #ifdef SCTP_ASOCLOG_OF_TSNS asoc->in_tsnlog[asoc->tsn_in_at].tsn = tsn; asoc->in_tsnlog[asoc->tsn_in_at].strm = strmno; asoc->in_tsnlog[asoc->tsn_in_at].seq = strmseq; asoc->in_tsnlog[asoc->tsn_in_at].sz = chk_length; asoc->in_tsnlog[asoc->tsn_in_at].flgs = chunk_flags; asoc->tsn_in_at++; if (asoc->tsn_in_at >= SCTP_TSN_LOG_SIZE) { asoc->tsn_in_at = 0; asoc->tsn_in_wrapped = 1; } #endif if ((chunk_flags & SCTP_DATA_FIRST_FRAG) && (chunk_flags & SCTP_DATA_UNORDERED) == 0 && (compare_with_wrap(asoc->strmin[strmno].last_sequence_delivered, strmseq, MAX_SEQ) || asoc->strmin[strmno].last_sequence_delivered == strmseq)) { /* The incoming sseq is behind where we last delivered? */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("EVIL/Broken-Dup S-SEQ:%d delivered:%d from peer, Abort!\n", strmseq, asoc->strmin[strmno].last_sequence_delivered); } #endif oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_14); ippp++; *ippp = tsn; ippp++; *ippp = ((strmno << 16) | strmseq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_14; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return (0); } /************************************ * From here down we may find ch-> invalid * so its a good idea NOT to use it. *************************************/ the_len = (chk_length - sizeof(struct sctp_data_chunk)); if (last_chunk == 0) { dmbuf = SCTP_M_COPYM(*m, (offset + sizeof(struct sctp_data_chunk)), the_len, M_DONTWAIT); #ifdef SCTP_MBUF_LOGGING { struct mbuf *mat; mat = dmbuf; while (mat) { if (SCTP_BUF_IS_EXTENDED(mat)) { sctp_log_mb(mat, SCTP_MBUF_ICOPY); } mat = SCTP_BUF_NEXT(mat); } } #endif } else { /* We can steal the last chunk */ int l_len; dmbuf = *m; /* lop off the top part */ m_adj(dmbuf, (offset + sizeof(struct sctp_data_chunk))); if (SCTP_BUF_NEXT(dmbuf) == NULL) { l_len = SCTP_BUF_LEN(dmbuf); } else { /* * need to count up the size hopefully does not hit * this to often :-0 */ struct mbuf *lat; l_len = 0; lat = dmbuf; while (lat) { l_len += SCTP_BUF_LEN(lat); lat = SCTP_BUF_NEXT(lat); } } if (l_len > the_len) { /* Trim the end round bytes off too */ m_adj(dmbuf, -(l_len - the_len)); } } if (dmbuf == NULL) { SCTP_STAT_INCR(sctps_nomem); return (0); } if ((chunk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG && asoc->fragmented_delivery_inprogress == 0 && TAILQ_EMPTY(&asoc->resetHead) && ((ordered == 0) || ((asoc->strmin[strmno].last_sequence_delivered + 1) == strmseq && TAILQ_EMPTY(&asoc->strmin[strmno].inqueue)))) { /* Candidate for express delivery */ /* * Its not fragmented, No PD-API is up, Nothing in the * delivery queue, Its un-ordered OR ordered and the next to * deliver AND nothing else is stuck on the stream queue, * And there is room for it in the socket buffer. Lets just * stuff it up the buffer.... */ /* It would be nice to avoid this copy if we could :< */ sctp_alloc_a_readq(stcb, control); sctp_build_readq_entry_mac(control, stcb, asoc->context, net, tsn, protocol_id, stcb->asoc.context, strmno, strmseq, chunk_flags, dmbuf); if (control == NULL) { goto failed_express_del; } sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); if ((chunk_flags & SCTP_DATA_UNORDERED) == 0) { /* for ordered, bump what we delivered */ asoc->strmin[strmno].last_sequence_delivered++; } SCTP_STAT_INCR(sctps_recvexpress); #ifdef SCTP_STR_LOGGING sctp_log_strm_del_alt(stcb, tsn, strmseq, strmno, SCTP_STR_LOG_FROM_EXPRS_DEL); #endif control = NULL; goto finish_express_del; } failed_express_del: /* If we reach here this is a new chunk */ chk = NULL; control = NULL; /* Express for fragmented delivery? */ if ((asoc->fragmented_delivery_inprogress) && (stcb->asoc.control_pdapi) && (asoc->str_of_pdapi == strmno) && (asoc->ssn_of_pdapi == strmseq) ) { control = stcb->asoc.control_pdapi; if ((chunk_flags & SCTP_DATA_FIRST_FRAG) == SCTP_DATA_FIRST_FRAG) { /* Can't be another first? */ goto failed_pdapi_express_del; } if (tsn == (control->sinfo_tsn + 1)) { /* Yep, we can add it on */ int end = 0; uint32_t cumack; if (chunk_flags & SCTP_DATA_LAST_FRAG) { end = 1; } cumack = asoc->cumulative_tsn; if ((cumack + 1) == tsn) cumack = tsn; if (sctp_append_to_readq(stcb->sctp_ep, stcb, control, dmbuf, end, tsn, &stcb->sctp_socket->so_rcv)) { printf("Append fails end:%d\n", end); goto failed_pdapi_express_del; } SCTP_STAT_INCR(sctps_recvexpressm); control->sinfo_tsn = tsn; asoc->tsn_last_delivered = tsn; asoc->fragment_flags = chunk_flags; asoc->tsn_of_pdapi_last_delivered = tsn; asoc->last_flags_delivered = chunk_flags; asoc->last_strm_seq_delivered = strmseq; asoc->last_strm_no_delivered = strmno; if (end) { /* clean up the flags and such */ asoc->fragmented_delivery_inprogress = 0; if ((chunk_flags & SCTP_DATA_UNORDERED) == 0) { asoc->strmin[strmno].last_sequence_delivered++; } stcb->asoc.control_pdapi = NULL; if (TAILQ_EMPTY(&asoc->reasmqueue) == 0) { /* * There could be another message * ready */ need_reasm_check = 1; } } control = NULL; goto finish_express_del; } } failed_pdapi_express_del: control = NULL; if ((chunk_flags & SCTP_DATA_NOT_FRAG) != SCTP_DATA_NOT_FRAG) { sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* No memory so we drop the chunk */ SCTP_STAT_INCR(sctps_nomem); if (last_chunk == 0) { /* we copied it, free the copy */ sctp_m_freem(dmbuf); } return (0); } chk->rec.data.TSN_seq = tsn; chk->no_fr_allowed = 0; chk->rec.data.stream_seq = strmseq; chk->rec.data.stream_number = strmno; chk->rec.data.payloadtype = protocol_id; chk->rec.data.context = stcb->asoc.context; chk->rec.data.doing_fast_retransmit = 0; chk->rec.data.rcv_flags = chunk_flags; chk->asoc = asoc; chk->send_size = the_len; chk->whoTo = net; atomic_add_int(&net->ref_count, 1); chk->data = dmbuf; } else { sctp_alloc_a_readq(stcb, control); sctp_build_readq_entry_mac(control, stcb, asoc->context, net, tsn, protocol_id, stcb->asoc.context, strmno, strmseq, chunk_flags, dmbuf); if (control == NULL) { /* No memory so we drop the chunk */ SCTP_STAT_INCR(sctps_nomem); if (last_chunk == 0) { /* we copied it, free the copy */ sctp_m_freem(dmbuf); } return (0); } control->length = the_len; } /* Mark it as received */ /* Now queue it where it belongs */ if (control != NULL) { /* First a sanity check */ if (asoc->fragmented_delivery_inprogress) { /* * Ok, we have a fragmented delivery in progress if * this chunk is next to deliver OR belongs in our * view to the reassembly, the peer is evil or * broken. */ uint32_t estimate_tsn; estimate_tsn = asoc->tsn_last_delivered + 1; if (TAILQ_EMPTY(&asoc->reasmqueue) && (estimate_tsn == control->sinfo_tsn)) { /* Evil/Broke peer */ sctp_m_freem(control->data); control->data = NULL; sctp_free_remote_addr(control->whoFrom); sctp_free_a_readq(stcb, control); oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_15); ippp++; *ippp = tsn; ippp++; *ippp = ((strmno << 16) | strmseq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_15; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return (0); } else { if (sctp_does_tsn_belong_to_reasm(asoc, control->sinfo_tsn)) { sctp_m_freem(control->data); control->data = NULL; sctp_free_remote_addr(control->whoFrom); sctp_free_a_readq(stcb, control); oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_16); ippp++; *ippp = tsn; ippp++; *ippp = ((strmno << 16) | strmseq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_16; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return (0); } } } else { /* No PDAPI running */ if (!TAILQ_EMPTY(&asoc->reasmqueue)) { /* * Reassembly queue is NOT empty validate * that this tsn does not need to be in * reasembly queue. If it does then our peer * is broken or evil. */ if (sctp_does_tsn_belong_to_reasm(asoc, control->sinfo_tsn)) { sctp_m_freem(control->data); control->data = NULL; sctp_free_remote_addr(control->whoFrom); sctp_free_a_readq(stcb, control); oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (3 * sizeof(uint32_t)); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_17); ippp++; *ippp = tsn; ippp++; *ippp = ((strmno << 16) | strmseq); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_17; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); *abort_flag = 1; return (0); } } } /* ok, if we reach here we have passed the sanity checks */ if (chunk_flags & SCTP_DATA_UNORDERED) { /* queue directly into socket buffer */ sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } else { /* * Special check for when streams are resetting. We * could be more smart about this and check the * actual stream to see if it is not being reset.. * that way we would not create a HOLB when amongst * streams being reset and those not being reset. * * We take complete messages that have a stream reset * intervening (aka the TSN is after where our * cum-ack needs to be) off and put them on a * pending_reply_queue. The reassembly ones we do * not have to worry about since they are all sorted * and proceessed by TSN order. It is only the * singletons I must worry about. */ struct sctp_stream_reset_list *liste; if (((liste = TAILQ_FIRST(&asoc->resetHead)) != NULL) && ((compare_with_wrap(tsn, ntohl(liste->tsn), MAX_TSN)) || (tsn == ntohl(liste->tsn))) ) { /* * yep its past where we need to reset... go * ahead and queue it. */ if (TAILQ_EMPTY(&asoc->pending_reply_queue)) { /* first one on */ TAILQ_INSERT_TAIL(&asoc->pending_reply_queue, control, next); } else { struct sctp_queued_to_read *ctlOn; unsigned char inserted = 0; ctlOn = TAILQ_FIRST(&asoc->pending_reply_queue); while (ctlOn) { if (compare_with_wrap(control->sinfo_tsn, ctlOn->sinfo_tsn, MAX_TSN)) { ctlOn = TAILQ_NEXT(ctlOn, next); } else { /* found it */ TAILQ_INSERT_BEFORE(ctlOn, control, next); inserted = 1; break; } } if (inserted == 0) { /* * must be put at end, use * prevP (all setup from * loop) to setup nextP. */ TAILQ_INSERT_TAIL(&asoc->pending_reply_queue, control, next); } } } else { sctp_queue_data_to_stream(stcb, asoc, control, abort_flag); if (*abort_flag) { return (0); } } } } else { /* Into the re-assembly queue */ sctp_queue_data_for_reasm(stcb, asoc, chk, abort_flag); if (*abort_flag) { /* * the assoc is now gone and chk was put onto the * reasm queue, which has all been freed. */ *m = NULL; return (0); } } finish_express_del: if (compare_with_wrap(tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { /* we have a new high score */ asoc->highest_tsn_inside_map = tsn; #ifdef SCTP_MAP_LOGGING sctp_log_map(0, 2, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif } if (tsn == (asoc->cumulative_tsn + 1)) { /* Update cum-ack */ asoc->cumulative_tsn = tsn; } if (last_chunk) { *m = NULL; } if (ordered) { SCTP_STAT_INCR_COUNTER64(sctps_inorderchunks); } else { SCTP_STAT_INCR_COUNTER64(sctps_inunorderchunks); } SCTP_STAT_INCR(sctps_recvdata); /* Set it present please */ #ifdef SCTP_STR_LOGGING sctp_log_strm_del_alt(stcb, tsn, strmseq, strmno, SCTP_STR_LOG_FROM_MARK_TSN); #endif #ifdef SCTP_MAP_LOGGING sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, asoc->highest_tsn_inside_map, SCTP_MAP_PREPARE_SLIDE); #endif SCTP_SET_TSN_PRESENT(asoc->mapping_array, gap); /* check the special flag for stream resets */ if (((liste = TAILQ_FIRST(&asoc->resetHead)) != NULL) && ((compare_with_wrap(asoc->cumulative_tsn, ntohl(liste->tsn), MAX_TSN)) || (asoc->cumulative_tsn == ntohl(liste->tsn))) ) { /* * we have finished working through the backlogged TSN's now * time to reset streams. 1: call reset function. 2: free * pending_reply space 3: distribute any chunks in * pending_reply_queue. */ struct sctp_queued_to_read *ctl; sctp_reset_in_stream(stcb, liste->number_entries, liste->req.list_of_streams); TAILQ_REMOVE(&asoc->resetHead, liste, next_resp); SCTP_FREE(liste); liste = TAILQ_FIRST(&asoc->resetHead); ctl = TAILQ_FIRST(&asoc->pending_reply_queue); if (ctl && (liste == NULL)) { /* All can be removed */ while (ctl) { TAILQ_REMOVE(&asoc->pending_reply_queue, ctl, next); sctp_queue_data_to_stream(stcb, asoc, ctl, abort_flag); if (*abort_flag) { return (0); } ctl = TAILQ_FIRST(&asoc->pending_reply_queue); } } else if (ctl) { /* more than one in queue */ while (!compare_with_wrap(ctl->sinfo_tsn, ntohl(liste->tsn), MAX_TSN)) { /* * if ctl->sinfo_tsn is <= liste->tsn we can * process it which is the NOT of * ctl->sinfo_tsn > liste->tsn */ TAILQ_REMOVE(&asoc->pending_reply_queue, ctl, next); sctp_queue_data_to_stream(stcb, asoc, ctl, abort_flag); if (*abort_flag) { return (0); } ctl = TAILQ_FIRST(&asoc->pending_reply_queue); } } /* * Now service re-assembly to pick up anything that has been * held on reassembly queue? */ sctp_deliver_reasm_check(stcb, asoc); need_reasm_check = 0; } if (need_reasm_check) { /* Another one waits ? */ sctp_deliver_reasm_check(stcb, asoc); } return (1); } int8_t sctp_map_lookup_tab[256] = { -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 4, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 5, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 4, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 6, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 4, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 5, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 4, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 3, -1, 0, -1, 1, -1, 0, -1, 2, -1, 0, -1, 1, -1, 0, -1, 7, }; void sctp_sack_check(struct sctp_tcb *stcb, int ok_to_sack, int was_a_gap, int *abort_flag) { /* * Now we also need to check the mapping array in a couple of ways. * 1) Did we move the cum-ack point? */ struct sctp_association *asoc; int i, at; int all_ones, last_all_ones = 0; int slide_from, slide_end, lgap, distance; #ifdef SCTP_MAP_LOGGING uint32_t old_cumack, old_base, old_highest; unsigned char aux_array[64]; #endif asoc = &stcb->asoc; at = 0; #ifdef SCTP_MAP_LOGGING old_cumack = asoc->cumulative_tsn; old_base = asoc->mapping_array_base_tsn; old_highest = asoc->highest_tsn_inside_map; if (asoc->mapping_array_size < 64) memcpy(aux_array, asoc->mapping_array, asoc->mapping_array_size); else memcpy(aux_array, asoc->mapping_array, 64); #endif /* * We could probably improve this a small bit by calculating the * offset of the current cum-ack as the starting point. */ all_ones = 1; at = 0; for (i = 0; i < stcb->asoc.mapping_array_size; i++) { if (asoc->mapping_array[i] == 0xff) { at += 8; last_all_ones = 1; } else { /* there is a 0 bit */ all_ones = 0; at += sctp_map_lookup_tab[asoc->mapping_array[i]]; last_all_ones = 0; break; } } asoc->cumulative_tsn = asoc->mapping_array_base_tsn + (at - last_all_ones); /* at is one off, since in the table a embedded -1 is present */ at++; if (compare_with_wrap(asoc->cumulative_tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { #ifdef INVARIANTS panic("huh, cumack greater than high-tsn in map"); #else printf("huh, cumack greater than high-tsn in map - should panic?\n"); asoc->highest_tsn_inside_map = asoc->cumulative_tsn; #endif } if (all_ones || (asoc->cumulative_tsn == asoc->highest_tsn_inside_map && at >= 8)) { /* The complete array was completed by a single FR */ /* higest becomes the cum-ack */ int clr; asoc->cumulative_tsn = asoc->highest_tsn_inside_map; /* clear the array */ if (all_ones) clr = asoc->mapping_array_size; else { clr = (at >> 3) + 1; /* * this should be the allones case but just in case * :> */ if (clr > asoc->mapping_array_size) clr = asoc->mapping_array_size; } memset(asoc->mapping_array, 0, clr); /* base becomes one ahead of the cum-ack */ asoc->mapping_array_base_tsn = asoc->cumulative_tsn + 1; #ifdef SCTP_MAP_LOGGING sctp_log_map(old_base, old_cumack, old_highest, SCTP_MAP_PREPARE_SLIDE); sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_CLEARED); #endif } else if (at >= 8) { /* we can slide the mapping array down */ /* Calculate the new byte postion we can move down */ slide_from = at >> 3; /* * now calculate the ceiling of the move using our highest * TSN value */ if (asoc->highest_tsn_inside_map >= asoc->mapping_array_base_tsn) { lgap = asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn; } else { lgap = (MAX_TSN - asoc->mapping_array_base_tsn) + asoc->highest_tsn_inside_map + 1; } slide_end = lgap >> 3; if (slide_end < slide_from) { panic("impossible slide"); } distance = (slide_end - slide_from) + 1; #ifdef SCTP_MAP_LOGGING sctp_log_map(old_base, old_cumack, old_highest, SCTP_MAP_PREPARE_SLIDE); sctp_log_map((uint32_t) slide_from, (uint32_t) slide_end, (uint32_t) lgap, SCTP_MAP_SLIDE_FROM); #endif if (distance + slide_from > asoc->mapping_array_size || distance < 0) { /* * Here we do NOT slide forward the array so that * hopefully when more data comes in to fill it up * we will be able to slide it forward. Really I * don't think this should happen :-0 */ #ifdef SCTP_MAP_LOGGING sctp_log_map((uint32_t) distance, (uint32_t) slide_from, (uint32_t) asoc->mapping_array_size, SCTP_MAP_SLIDE_NONE); #endif } else { int ii; for (ii = 0; ii < distance; ii++) { asoc->mapping_array[ii] = asoc->mapping_array[slide_from + ii]; } for (ii = distance; ii <= slide_end; ii++) { asoc->mapping_array[ii] = 0; } asoc->mapping_array_base_tsn += (slide_from << 3); #ifdef SCTP_MAP_LOGGING sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif } } /* * Now we need to see if we need to queue a sack or just start the * timer (if allowed). */ if (ok_to_sack) { if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { /* * Ok special case, in SHUTDOWN-SENT case. here we * maker sure SACK timer is off and instead send a * SHUTDOWN and a SACK */ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL, SCTP_FROM_SCTP_INDATA + SCTP_LOC_18); } sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_send_sack(stcb); } else { int is_a_gap; /* is there a gap now ? */ is_a_gap = compare_with_wrap(stcb->asoc.highest_tsn_inside_map, stcb->asoc.cumulative_tsn, MAX_TSN); /* * CMT DAC algorithm: increase number of packets * received since last ack */ stcb->asoc.cmt_dac_pkts_rcvd++; if ((stcb->asoc.send_sack == 1) || /* We need to send a * SACK */ ((was_a_gap) && (is_a_gap == 0)) || /* was a gap, but no * longer is one */ (stcb->asoc.numduptsns) || /* we have dup's */ (is_a_gap) || /* is still a gap */ (stcb->asoc.delayed_ack == 0) || /* Delayed sack disabled */ (stcb->asoc.data_pkts_seen >= stcb->asoc.sack_freq) /* hit limit of pkts */ ) { if ((sctp_cmt_on_off) && (sctp_cmt_use_dac) && (stcb->asoc.send_sack == 0) && (stcb->asoc.numduptsns == 0) && (stcb->asoc.delayed_ack) && (!SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer))) { /* * CMT DAC algorithm: With CMT, * delay acks even in the face of * * reordering. Therefore, if acks that * do not have to be sent because of * the above reasons, will be * delayed. That is, acks that would * have been sent due to gap reports * will be delayed with DAC. Start * the delayed ack timer. */ sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL); } else { /* * Ok we must build a SACK since the * timer is pending, we got our * first packet OR there are gaps or * duplicates. */ SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer); sctp_send_sack(stcb); } } else { if (!SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL); } } } } } void sctp_service_queues(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; uint32_t tsize; uint16_t nxt_todel; if (asoc->fragmented_delivery_inprogress) { sctp_service_reassembly(stcb, asoc); } /* Can we proceed further, i.e. the PD-API is complete */ if (asoc->fragmented_delivery_inprogress) { /* no */ return; } /* * Now is there some other chunk I can deliver from the reassembly * queue. */ doit_again: chk = TAILQ_FIRST(&asoc->reasmqueue); if (chk == NULL) { asoc->size_on_reasm_queue = 0; asoc->cnt_on_reasm_queue = 0; return; } nxt_todel = asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1; if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) && ((nxt_todel == chk->rec.data.stream_seq) || (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) { /* * Yep the first one is here. We setup to start reception, * by backing down the TSN just in case we can't deliver. */ /* * Before we start though either all of the message should * be here or 1/4 the socket buffer max or nothing on the * delivery queue and something can be delivered. */ if ((sctp_is_all_msg_on_reasm(asoc, &tsize) || (tsize > stcb->sctp_ep->partial_delivery_point))) { asoc->fragmented_delivery_inprogress = 1; asoc->tsn_last_delivered = chk->rec.data.TSN_seq - 1; asoc->str_of_pdapi = chk->rec.data.stream_number; asoc->ssn_of_pdapi = chk->rec.data.stream_seq; asoc->pdapi_ppid = chk->rec.data.payloadtype; asoc->fragment_flags = chk->rec.data.rcv_flags; sctp_service_reassembly(stcb, asoc); if (asoc->fragmented_delivery_inprogress == 0) { goto doit_again; } } } } int sctp_process_data(struct mbuf **mm, int iphlen, int *offset, int length, struct sctphdr *sh, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t * high_tsn) { struct sctp_data_chunk *ch, chunk_buf; struct sctp_association *asoc; int num_chunks = 0; /* number of control chunks processed */ int stop_proc = 0; int chk_length, break_flag, last_chunk; int abort_flag = 0, was_a_gap = 0; struct mbuf *m; /* set the rwnd */ sctp_set_rwnd(stcb, &stcb->asoc); m = *mm; SCTP_TCB_LOCK_ASSERT(stcb); asoc = &stcb->asoc; if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) { /* * wait a minute, this guy is gone, there is no longer a * receiver. Send peer an ABORT! */ struct mbuf *op_err; op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); sctp_abort_an_association(stcb->sctp_ep, stcb, 0, op_err); return (2); } if (compare_with_wrap(stcb->asoc.highest_tsn_inside_map, stcb->asoc.cumulative_tsn, MAX_TSN)) { /* there was a gap before this data was processed */ was_a_gap = 1; } /* * setup where we got the last DATA packet from for any SACK that * may need to go out. Don't bump the net. This is done ONLY when a * chunk is assigned. */ asoc->last_data_chunk_from = net; /*- * Now before we proceed we must figure out if this is a wasted * cluster... i.e. it is a small packet sent in and yet the driver * underneath allocated a full cluster for it. If so we must copy it * to a smaller mbuf and free up the cluster mbuf. This will help * with cluster starvation. Note for __Panda__ we don't do this * since it has clusters all the way down to 64 bytes. */ if (SCTP_BUF_LEN(m) < (long)MLEN && SCTP_BUF_NEXT(m) == NULL) { /* we only handle mbufs that are singletons.. not chains */ m = sctp_get_mbuf_for_msg(SCTP_BUF_LEN(m), 0, M_DONTWAIT, 1, MT_DATA); if (m) { /* ok lets see if we can copy the data up */ caddr_t *from, *to; /* get the pointers and copy */ to = mtod(m, caddr_t *); from = mtod((*mm), caddr_t *); memcpy(to, from, SCTP_BUF_LEN((*mm))); /* copy the length and free up the old */ SCTP_BUF_LEN(m) = SCTP_BUF_LEN((*mm)); sctp_m_freem(*mm); /* sucess, back copy */ *mm = m; } else { /* We are in trouble in the mbuf world .. yikes */ m = *mm; } } /* get pointer to the first chunk header */ ch = (struct sctp_data_chunk *)sctp_m_getptr(m, *offset, sizeof(struct sctp_data_chunk), (uint8_t *) & chunk_buf); if (ch == NULL) { return (1); } /* * process all DATA chunks... */ *high_tsn = asoc->cumulative_tsn; break_flag = 0; asoc->data_pkts_seen++; while (stop_proc == 0) { /* validate chunk length */ chk_length = ntohs(ch->ch.chunk_length); if (length - *offset < chk_length) { /* all done, mutulated chunk */ stop_proc = 1; break; } if (ch->ch.chunk_type == SCTP_DATA) { if ((size_t)chk_length < sizeof(struct sctp_data_chunk) + 1) { /* * Need to send an abort since we had a * invalid data chunk. */ struct mbuf *op_err; op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 2 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(op_err) = sizeof(struct sctp_paramhdr) + (2 * sizeof(uint32_t)); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_19); ippp++; *ippp = asoc->cumulative_tsn; } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_19; sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, 0, 0); return (2); } #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xB1, 0); #endif if (SCTP_SIZE32(chk_length) == (length - *offset)) { last_chunk = 1; } else { last_chunk = 0; } if (sctp_process_a_data_chunk(stcb, asoc, mm, *offset, ch, chk_length, net, high_tsn, &abort_flag, &break_flag, last_chunk)) { num_chunks++; } if (abort_flag) return (2); if (break_flag) { /* * Set because of out of rwnd space and no * drop rep space left. */ stop_proc = 1; break; } } else { /* not a data chunk in the data region */ switch (ch->ch.chunk_type) { case SCTP_INITIATION: case SCTP_INITIATION_ACK: case SCTP_SELECTIVE_ACK: case SCTP_HEARTBEAT_REQUEST: case SCTP_HEARTBEAT_ACK: case SCTP_ABORT_ASSOCIATION: case SCTP_SHUTDOWN: case SCTP_SHUTDOWN_ACK: case SCTP_OPERATION_ERROR: case SCTP_COOKIE_ECHO: case SCTP_COOKIE_ACK: case SCTP_ECN_ECHO: case SCTP_ECN_CWR: case SCTP_SHUTDOWN_COMPLETE: case SCTP_AUTHENTICATION: case SCTP_ASCONF_ACK: case SCTP_PACKET_DROPPED: case SCTP_STREAM_RESET: case SCTP_FORWARD_CUM_TSN: case SCTP_ASCONF: /* * Now, what do we do with KNOWN chunks that * are NOT in the right place? * * For now, I do nothing but ignore them. We * may later want to add sysctl stuff to * switch out and do either an ABORT() or * possibly process them. */ if (sctp_strict_data_order) { struct mbuf *op_err; op_err = sctp_generate_invmanparam(SCTP_CAUSE_PROTOCOL_VIOLATION); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, 0, 0); return (2); } break; default: /* unknown chunk type, use bit rules */ if (ch->ch.chunk_type & 0x40) { /* Add a error report to the queue */ struct mbuf *mm; struct sctp_paramhdr *phd; mm = sctp_get_mbuf_for_msg(sizeof(*phd), 0, M_DONTWAIT, 1, MT_DATA); if (mm) { phd = mtod(mm, struct sctp_paramhdr *); /* * We cheat and use param * type since we did not * bother to define a error * cause struct. They are * the same basic format * with different names. */ phd->param_type = htons(SCTP_CAUSE_UNRECOG_CHUNK); phd->param_length = htons(chk_length + sizeof(*phd)); SCTP_BUF_LEN(mm) = sizeof(*phd); SCTP_BUF_NEXT(mm) = SCTP_M_COPYM(m, *offset, SCTP_SIZE32(chk_length), M_DONTWAIT); if (SCTP_BUF_NEXT(mm)) { sctp_queue_op_err(stcb, mm); } else { sctp_m_freem(mm); } } } if ((ch->ch.chunk_type & 0x80) == 0) { /* discard the rest of this packet */ stop_proc = 1; } /* else skip this bad chunk and * continue... */ break; }; /* switch of chunk type */ } *offset += SCTP_SIZE32(chk_length); if ((*offset >= length) || stop_proc) { /* no more data left in the mbuf chain */ stop_proc = 1; continue; } ch = (struct sctp_data_chunk *)sctp_m_getptr(m, *offset, sizeof(struct sctp_data_chunk), (uint8_t *) & chunk_buf); if (ch == NULL) { *offset = length; stop_proc = 1; break; } } /* while */ if (break_flag) { /* * we need to report rwnd overrun drops. */ sctp_send_packet_dropped(stcb, net, *mm, iphlen, 0); } if (num_chunks) { /* * Did we get data, if so update the time for auto-close and * give peer credit for being alive. */ SCTP_STAT_INCR(sctps_recvpktwithdata); stcb->asoc.overall_error_count = 0; - SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_last_rcvd); + (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_last_rcvd); } /* now service all of the reassm queue if needed */ if (!(TAILQ_EMPTY(&asoc->reasmqueue))) sctp_service_queues(stcb, asoc); if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { /* Assure that we ack right away */ stcb->asoc.send_sack = 1; } /* Start a sack timer or QUEUE a SACK for sending */ if ((stcb->asoc.cumulative_tsn == stcb->asoc.highest_tsn_inside_map) && (stcb->asoc.mapping_array[0] != 0xff)) { if ((stcb->asoc.data_pkts_seen >= stcb->asoc.sack_freq) || (stcb->asoc.delayed_ack == 0) || (stcb->asoc.send_sack == 1)) { if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer); } sctp_send_sack(stcb); } else { if (!SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL); } } } else { sctp_sack_check(stcb, 1, was_a_gap, &abort_flag); } if (abort_flag) return (2); return (0); } static void sctp_handle_segments(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_sack_chunk *ch, uint32_t last_tsn, uint32_t * biggest_tsn_acked, uint32_t * biggest_newly_acked_tsn, uint32_t * this_sack_lowest_newack, int num_seg, int *ecn_seg_sums) { /************************************************/ /* process fragments and update sendqueue */ /************************************************/ struct sctp_sack *sack; struct sctp_gap_ack_block *frag; struct sctp_tmit_chunk *tp1; int i; unsigned int j; #ifdef SCTP_FR_LOGGING int num_frs = 0; #endif uint16_t frag_strt, frag_end, primary_flag_set; u_long last_frag_high; /* * @@@ JRI : TODO: This flag is not used anywhere .. remove? */ if (asoc->primary_destination->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { primary_flag_set = 1; } else { primary_flag_set = 0; } sack = &ch->sack; frag = (struct sctp_gap_ack_block *)((caddr_t)sack + sizeof(struct sctp_sack)); tp1 = NULL; last_frag_high = 0; for (i = 0; i < num_seg; i++) { frag_strt = ntohs(frag->start); frag_end = ntohs(frag->end); /* some sanity checks on the fargment offsets */ if (frag_strt > frag_end) { /* this one is malformed, skip */ frag++; continue; } if (compare_with_wrap((frag_end + last_tsn), *biggest_tsn_acked, MAX_TSN)) *biggest_tsn_acked = frag_end + last_tsn; /* mark acked dgs and find out the highestTSN being acked */ if (tp1 == NULL) { tp1 = TAILQ_FIRST(&asoc->sent_queue); /* save the locations of the last frags */ last_frag_high = frag_end + last_tsn; } else { /* * now lets see if we need to reset the queue due to * a out-of-order SACK fragment */ if (compare_with_wrap(frag_strt + last_tsn, last_frag_high, MAX_TSN)) { /* * if the new frag starts after the last TSN * frag covered, we are ok and this one is * beyond the last one */ ; } else { /* * ok, they have reset us, so we need to * reset the queue this will cause extra * hunting but hey, they chose the * performance hit when they failed to order * there gaps.. */ tp1 = TAILQ_FIRST(&asoc->sent_queue); } last_frag_high = frag_end + last_tsn; } for (j = frag_strt + last_tsn; j <= frag_end + last_tsn; j++) { while (tp1) { #ifdef SCTP_FR_LOGGING if (tp1->rec.data.doing_fast_retransmit) num_frs++; #endif /* * CMT: CUCv2 algorithm. For each TSN being * processed from the sent queue, track the * next expected pseudo-cumack, or * rtx_pseudo_cumack, if required. Separate * cumack trackers for first transmissions, * and retransmissions. */ if ((tp1->whoTo->find_pseudo_cumack == 1) && (tp1->sent < SCTP_DATAGRAM_RESEND) && (tp1->snd_count == 1)) { tp1->whoTo->pseudo_cumack = tp1->rec.data.TSN_seq; tp1->whoTo->find_pseudo_cumack = 0; } if ((tp1->whoTo->find_rtx_pseudo_cumack == 1) && (tp1->sent < SCTP_DATAGRAM_RESEND) && (tp1->snd_count > 1)) { tp1->whoTo->rtx_pseudo_cumack = tp1->rec.data.TSN_seq; tp1->whoTo->find_rtx_pseudo_cumack = 0; } if (tp1->rec.data.TSN_seq == j) { if (tp1->sent != SCTP_DATAGRAM_UNSENT) { /* * must be held until * cum-ack passes */ /* * ECN Nonce: Add the nonce * value to the sender's * nonce sum */ if (tp1->sent < SCTP_DATAGRAM_RESEND) { /*- * If it is less than RESEND, it is * now no-longer in flight. * Higher values may already be set * via previous Gap Ack Blocks... * i.e. ACKED or RESEND. */ if (compare_with_wrap(tp1->rec.data.TSN_seq, *biggest_newly_acked_tsn, MAX_TSN)) { *biggest_newly_acked_tsn = tp1->rec.data.TSN_seq; } /* * CMT: SFR algo * (and HTNA) - set * saw_newack to 1 * for dest being * newly acked. * update * this_sack_highest_ * newack if * appropriate. */ if (tp1->rec.data.chunk_was_revoked == 0) tp1->whoTo->saw_newack = 1; if (compare_with_wrap(tp1->rec.data.TSN_seq, tp1->whoTo->this_sack_highest_newack, MAX_TSN)) { tp1->whoTo->this_sack_highest_newack = tp1->rec.data.TSN_seq; } /* * CMT DAC algo: * also update * this_sack_lowest_n * ewack */ if (*this_sack_lowest_newack == 0) { #ifdef SCTP_SACK_LOGGING sctp_log_sack(*this_sack_lowest_newack, last_tsn, tp1->rec.data.TSN_seq, 0, 0, SCTP_LOG_TSN_ACKED); #endif *this_sack_lowest_newack = tp1->rec.data.TSN_seq; } /* * CMT: CUCv2 * algorithm. If * (rtx-)pseudo-cumac * k for corresp * dest is being * acked, then we * have a new * (rtx-)pseudo-cumac * k. Set * new_(rtx_)pseudo_c * umack to TRUE so * that the cwnd for * this dest can be * updated. Also * trigger search * for the next * expected * (rtx-)pseudo-cumac * k. Separate * pseudo_cumack * trackers for * first * transmissions and * retransmissions. */ if (tp1->rec.data.TSN_seq == tp1->whoTo->pseudo_cumack) { if (tp1->rec.data.chunk_was_revoked == 0) { tp1->whoTo->new_pseudo_cumack = 1; } tp1->whoTo->find_pseudo_cumack = 1; } #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.TSN_seq, SCTP_CWND_LOG_FROM_SACK); #endif if (tp1->rec.data.TSN_seq == tp1->whoTo->rtx_pseudo_cumack) { if (tp1->rec.data.chunk_was_revoked == 0) { tp1->whoTo->new_pseudo_cumack = 1; } tp1->whoTo->find_rtx_pseudo_cumack = 1; } #ifdef SCTP_SACK_LOGGING sctp_log_sack(*biggest_newly_acked_tsn, last_tsn, tp1->rec.data.TSN_seq, frag_strt, frag_end, SCTP_LOG_TSN_ACKED); #endif #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_GAP, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_decrease(tp1); sctp_total_flight_decrease(stcb, tp1); tp1->whoTo->net_ack += tp1->send_size; if (tp1->snd_count < 2) { /* * True * non-retran * smited * chunk */ tp1->whoTo->net_ack2 += tp1->send_size; /* * update RTO * too ? */ if (tp1->do_rtt) { tp1->whoTo->RTO = sctp_calculate_rto(stcb, asoc, tp1->whoTo, &tp1->sent_rcv_time); tp1->do_rtt = 0; } } } if (tp1->sent <= SCTP_DATAGRAM_RESEND) { (*ecn_seg_sums) += tp1->rec.data.ect_nonce; (*ecn_seg_sums) &= SCTP_SACK_NONCE_SUM; if (compare_with_wrap(tp1->rec.data.TSN_seq, asoc->this_sack_highest_gap, MAX_TSN)) { asoc->this_sack_highest_gap = tp1->rec.data.TSN_seq; } if (tp1->sent == SCTP_DATAGRAM_RESEND) { sctp_ucount_decr(asoc->sent_queue_retran_cnt); #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xB2, (asoc->sent_queue_retran_cnt & 0x000000ff)); #endif } } /* * All chunks NOT UNSENT * fall through here and are * marked */ tp1->sent = SCTP_DATAGRAM_MARKED; if (tp1->rec.data.chunk_was_revoked) { /* deflate the cwnd */ tp1->whoTo->cwnd -= tp1->book_size; tp1->rec.data.chunk_was_revoked = 0; } } break; } /* if (tp1->TSN_seq == j) */ if (compare_with_wrap(tp1->rec.data.TSN_seq, j, MAX_TSN)) break; tp1 = TAILQ_NEXT(tp1, sctp_next); } /* end while (tp1) */ } /* end for (j = fragStart */ frag++; /* next one */ } #ifdef SCTP_FR_LOGGING /* * if (num_frs) sctp_log_fr(*biggest_tsn_acked, * *biggest_newly_acked_tsn, last_tsn, SCTP_FR_LOG_BIGGEST_TSNS); */ #endif } static void sctp_check_for_revoked(struct sctp_tcb *stcb, struct sctp_association *asoc, uint32_t cumack, u_long biggest_tsn_acked) { struct sctp_tmit_chunk *tp1; int tot_revoked = 0; tp1 = TAILQ_FIRST(&asoc->sent_queue); while (tp1) { if (compare_with_wrap(tp1->rec.data.TSN_seq, cumack, MAX_TSN)) { /* * ok this guy is either ACK or MARKED. If it is * ACKED it has been previously acked but not this * time i.e. revoked. If it is MARKED it was ACK'ed * again. */ if (compare_with_wrap(tp1->rec.data.TSN_seq, biggest_tsn_acked, MAX_TSN)) break; if (tp1->sent == SCTP_DATAGRAM_ACKED) { /* it has been revoked */ tp1->sent = SCTP_DATAGRAM_SENT; tp1->rec.data.chunk_was_revoked = 1; /* * We must add this stuff back in to assure * timers and such get started. */ #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_UP_REVOKE, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_increase(tp1); sctp_total_flight_increase(stcb, tp1); /* * We inflate the cwnd to compensate for our * artificial inflation of the flight_size. */ tp1->whoTo->cwnd += tp1->book_size; tot_revoked++; #ifdef SCTP_SACK_LOGGING sctp_log_sack(asoc->last_acked_seq, cumack, tp1->rec.data.TSN_seq, 0, 0, SCTP_LOG_TSN_REVOKED); #endif } else if (tp1->sent == SCTP_DATAGRAM_MARKED) { /* it has been re-acked in this SACK */ tp1->sent = SCTP_DATAGRAM_ACKED; } } if (tp1->sent == SCTP_DATAGRAM_UNSENT) break; tp1 = TAILQ_NEXT(tp1, sctp_next); } if (tot_revoked > 0) { /* * Setup the ecn nonce re-sync point. We do this since once * data is revoked we begin to retransmit things, which do * NOT have the ECN bits set. This means we are now out of * sync and must wait until we get back in sync with the * peer to check ECN bits. */ tp1 = TAILQ_FIRST(&asoc->send_queue); if (tp1 == NULL) { asoc->nonce_resync_tsn = asoc->sending_seq; } else { asoc->nonce_resync_tsn = tp1->rec.data.TSN_seq; } asoc->nonce_wait_for_ecne = 0; asoc->nonce_sum_check = 0; } } static void sctp_strike_gap_ack_chunks(struct sctp_tcb *stcb, struct sctp_association *asoc, u_long biggest_tsn_acked, u_long biggest_tsn_newly_acked, u_long this_sack_lowest_newack, int accum_moved) { struct sctp_tmit_chunk *tp1; int strike_flag = 0; struct timeval now; int tot_retrans = 0; uint32_t sending_seq; struct sctp_nets *net; int num_dests_sacked = 0; /* * select the sending_seq, this is either the next thing ready to be * sent but not transmitted, OR, the next seq we assign. */ tp1 = TAILQ_FIRST(&stcb->asoc.send_queue); if (tp1 == NULL) { sending_seq = asoc->sending_seq; } else { sending_seq = tp1->rec.data.TSN_seq; } /* CMT DAC algo: finding out if SACK is a mixed SACK */ if (sctp_cmt_on_off && sctp_cmt_use_dac) { TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (net->saw_newack) num_dests_sacked++; } } if (stcb->asoc.peer_supports_prsctp) { - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); } tp1 = TAILQ_FIRST(&asoc->sent_queue); while (tp1) { strike_flag = 0; if (tp1->no_fr_allowed) { /* this one had a timeout or something */ tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } #ifdef SCTP_FR_LOGGING if (tp1->sent < SCTP_DATAGRAM_RESEND) sctp_log_fr(biggest_tsn_newly_acked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_CHECK_STRIKE); #endif if (compare_with_wrap(tp1->rec.data.TSN_seq, biggest_tsn_acked, MAX_TSN) || tp1->sent == SCTP_DATAGRAM_UNSENT) { /* done */ break; } if (stcb->asoc.peer_supports_prsctp) { if ((PR_SCTP_TTL_ENABLED(tp1->flags)) && tp1->sent < SCTP_DATAGRAM_ACKED) { /* Is it expired? */ if ( (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) ) { /* Yes so drop it */ if (tp1->data != NULL) { sctp_release_pr_sctp_chunk(stcb, tp1, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), &asoc->sent_queue); } tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } } if ((PR_SCTP_RTX_ENABLED(tp1->flags)) && tp1->sent < SCTP_DATAGRAM_ACKED) { /* Has it been retransmitted tv_sec times? */ if (tp1->snd_count > tp1->rec.data.timetodrop.tv_sec) { /* Yes, so drop it */ if (tp1->data != NULL) { sctp_release_pr_sctp_chunk(stcb, tp1, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), &asoc->sent_queue); } tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } } } if (compare_with_wrap(tp1->rec.data.TSN_seq, asoc->this_sack_highest_gap, MAX_TSN)) { /* we are beyond the tsn in the sack */ break; } if (tp1->sent >= SCTP_DATAGRAM_RESEND) { /* either a RESEND, ACKED, or MARKED */ /* skip */ tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } /* * CMT : SFR algo (covers part of DAC and HTNA as well) */ if (tp1->whoTo->saw_newack == 0) { /* * No new acks were receieved for data sent to this * dest. Therefore, according to the SFR algo for * CMT, no data sent to this dest can be marked for * FR using this SACK. */ tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } else if (compare_with_wrap(tp1->rec.data.TSN_seq, tp1->whoTo->this_sack_highest_newack, MAX_TSN)) { /* * CMT: New acks were receieved for data sent to * this dest. But no new acks were seen for data * sent after tp1. Therefore, according to the SFR * algo for CMT, tp1 cannot be marked for FR using * this SACK. This step covers part of the DAC algo * and the HTNA algo as well. */ tp1 = TAILQ_NEXT(tp1, sctp_next); continue; } /* * Here we check to see if we were have already done a FR * and if so we see if the biggest TSN we saw in the sack is * smaller than the recovery point. If so we don't strike * the tsn... otherwise we CAN strike the TSN. */ /* * @@@ JRI: Check for CMT if (accum_moved && * asoc->fast_retran_loss_recovery && (sctp_cmt_on_off == * 0)) { */ if (accum_moved && asoc->fast_retran_loss_recovery) { /* * Strike the TSN if in fast-recovery and cum-ack * moved. */ #ifdef SCTP_FR_LOGGING sctp_log_fr(biggest_tsn_newly_acked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif if (tp1->sent < SCTP_DATAGRAM_RESEND) { tp1->sent++; } if (sctp_cmt_on_off && sctp_cmt_use_dac) { /* * CMT DAC algorithm: If SACK flag is set to * 0, then lowest_newack test will not pass * because it would have been set to the * cumack earlier. If not already to be * rtx'd, If not a mixed sack and if tp1 is * not between two sacked TSNs, then mark by * one more. NOTE that we are marking by one * additional time since the SACK DAC flag * indicates that two packets have been * received after this missing TSN. */ if ((tp1->sent < SCTP_DATAGRAM_RESEND) && (num_dests_sacked == 1) && compare_with_wrap(this_sack_lowest_newack, tp1->rec.data.TSN_seq, MAX_TSN)) { #ifdef SCTP_FR_LOGGING sctp_log_fr(16 + num_dests_sacked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif tp1->sent++; } } } else if (tp1->rec.data.doing_fast_retransmit) { /* * For those that have done a FR we must take * special consideration if we strike. I.e the * biggest_newly_acked must be higher than the * sending_seq at the time we did the FR. */ if ( #ifdef SCTP_FR_TO_ALTERNATE /* * If FR's go to new networks, then we must only do * this for singly homed asoc's. However if the FR's * go to the same network (Armando's work) then its * ok to FR multiple times. */ (asoc->numnets < 2) #else (1) #endif ) { if ((compare_with_wrap(biggest_tsn_newly_acked, tp1->rec.data.fast_retran_tsn, MAX_TSN)) || (biggest_tsn_newly_acked == tp1->rec.data.fast_retran_tsn)) { /* * Strike the TSN, since this ack is * beyond where things were when we * did a FR. */ #ifdef SCTP_FR_LOGGING sctp_log_fr(biggest_tsn_newly_acked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif if (tp1->sent < SCTP_DATAGRAM_RESEND) { tp1->sent++; } strike_flag = 1; if (sctp_cmt_on_off && sctp_cmt_use_dac) { /* * CMT DAC algorithm: If * SACK flag is set to 0, * then lowest_newack test * will not pass because it * would have been set to * the cumack earlier. If * not already to be rtx'd, * If not a mixed sack and * if tp1 is not between two * sacked TSNs, then mark by * one more. NOTE that we * are marking by one * additional time since the * SACK DAC flag indicates * that two packets have * been received after this * missing TSN. */ if ((tp1->sent < SCTP_DATAGRAM_RESEND) && (num_dests_sacked == 1) && compare_with_wrap(this_sack_lowest_newack, tp1->rec.data.TSN_seq, MAX_TSN)) { #ifdef SCTP_FR_LOGGING sctp_log_fr(32 + num_dests_sacked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif if (tp1->sent < SCTP_DATAGRAM_RESEND) { tp1->sent++; } } } } } /* * JRI: TODO: remove code for HTNA algo. CMT's SFR * algo covers HTNA. */ } else if (compare_with_wrap(tp1->rec.data.TSN_seq, biggest_tsn_newly_acked, MAX_TSN)) { /* * We don't strike these: This is the HTNA * algorithm i.e. we don't strike If our TSN is * larger than the Highest TSN Newly Acked. */ ; } else { /* Strike the TSN */ #ifdef SCTP_FR_LOGGING sctp_log_fr(biggest_tsn_newly_acked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif if (tp1->sent < SCTP_DATAGRAM_RESEND) { tp1->sent++; } if (sctp_cmt_on_off && sctp_cmt_use_dac) { /* * CMT DAC algorithm: If SACK flag is set to * 0, then lowest_newack test will not pass * because it would have been set to the * cumack earlier. If not already to be * rtx'd, If not a mixed sack and if tp1 is * not between two sacked TSNs, then mark by * one more. NOTE that we are marking by one * additional time since the SACK DAC flag * indicates that two packets have been * received after this missing TSN. */ if ((tp1->sent < SCTP_DATAGRAM_RESEND) && (num_dests_sacked == 1) && compare_with_wrap(this_sack_lowest_newack, tp1->rec.data.TSN_seq, MAX_TSN)) { #ifdef SCTP_FR_LOGGING sctp_log_fr(48 + num_dests_sacked, tp1->rec.data.TSN_seq, tp1->sent, SCTP_FR_LOG_STRIKE_CHUNK); #endif tp1->sent++; } } } if (tp1->sent == SCTP_DATAGRAM_RESEND) { /* Increment the count to resend */ struct sctp_nets *alt; /* printf("OK, we are now ready to FR this guy\n"); */ #ifdef SCTP_FR_LOGGING sctp_log_fr(tp1->rec.data.TSN_seq, tp1->snd_count, 0, SCTP_FR_MARKED); #endif if (strike_flag) { /* This is a subsequent FR */ SCTP_STAT_INCR(sctps_sendmultfastretrans); } sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); if (sctp_cmt_on_off) { /* * CMT: Using RTX_SSTHRESH policy for CMT. * If CMT is being used, then pick dest with * largest ssthresh for any retransmission. */ tp1->no_fr_allowed = 1; alt = tp1->whoTo; alt = sctp_find_alternate_net(stcb, alt, 1); /* * CUCv2: If a different dest is picked for * the retransmission, then new * (rtx-)pseudo_cumack needs to be tracked * for orig dest. Let CUCv2 track new (rtx-) * pseudo-cumack always. */ tp1->whoTo->find_pseudo_cumack = 1; tp1->whoTo->find_rtx_pseudo_cumack = 1; } else {/* CMT is OFF */ #ifdef SCTP_FR_TO_ALTERNATE /* Can we find an alternate? */ alt = sctp_find_alternate_net(stcb, tp1->whoTo, 0); #else /* * default behavior is to NOT retransmit * FR's to an alternate. Armando Caro's * paper details why. */ alt = tp1->whoTo; #endif } tp1->rec.data.doing_fast_retransmit = 1; tot_retrans++; /* mark the sending seq for possible subsequent FR's */ /* * printf("Marking TSN for FR new value %x\n", * (uint32_t)tpi->rec.data.TSN_seq); */ if (TAILQ_EMPTY(&asoc->send_queue)) { /* * If the queue of send is empty then its * the next sequence number that will be * assigned so we subtract one from this to * get the one we last sent. */ tp1->rec.data.fast_retran_tsn = sending_seq; } else { /* * If there are chunks on the send queue * (unsent data that has made it from the * stream queues but not out the door, we * take the first one (which will have the * lowest TSN) and subtract one to get the * one we last sent. */ struct sctp_tmit_chunk *ttt; ttt = TAILQ_FIRST(&asoc->send_queue); tp1->rec.data.fast_retran_tsn = ttt->rec.data.TSN_seq; } if (tp1->do_rtt) { /* * this guy had a RTO calculation pending on * it, cancel it */ tp1->do_rtt = 0; } /* fix counts and things */ #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_RSND, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif tp1->whoTo->net_ack++; sctp_flight_size_decrease(tp1); #ifdef SCTP_LOG_RWND sctp_log_rwnd(SCTP_INCREASE_PEER_RWND, asoc->peers_rwnd, tp1->send_size, sctp_peer_chunk_oh); #endif /* add back to the rwnd */ asoc->peers_rwnd += (tp1->send_size + sctp_peer_chunk_oh); /* remove from the total flight */ sctp_total_flight_decrease(stcb, tp1); if (alt != tp1->whoTo) { /* yes, there is an alternate. */ sctp_free_remote_addr(tp1->whoTo); tp1->whoTo = alt; atomic_add_int(&alt->ref_count, 1); } } tp1 = TAILQ_NEXT(tp1, sctp_next); } /* while (tp1) */ if (tot_retrans > 0) { /* * Setup the ecn nonce re-sync point. We do this since once * we go to FR something we introduce a Karn's rule scenario * and won't know the totals for the ECN bits. */ asoc->nonce_resync_tsn = sending_seq; asoc->nonce_wait_for_ecne = 0; asoc->nonce_sum_check = 0; } } struct sctp_tmit_chunk * sctp_try_advance_peer_ack_point(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *tp1, *tp2, *a_adv = NULL; struct timeval now; int now_filled = 0; if (asoc->peer_supports_prsctp == 0) { return (NULL); } tp1 = TAILQ_FIRST(&asoc->sent_queue); while (tp1) { if (tp1->sent != SCTP_FORWARD_TSN_SKIP && tp1->sent != SCTP_DATAGRAM_RESEND) { /* no chance to advance, out of here */ break; } if (!PR_SCTP_ENABLED(tp1->flags)) { /* * We can't fwd-tsn past any that are reliable aka * retransmitted until the asoc fails. */ break; } if (!now_filled) { - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); now_filled = 1; } tp2 = TAILQ_NEXT(tp1, sctp_next); /* * now we got a chunk which is marked for another * retransmission to a PR-stream but has run out its chances * already maybe OR has been marked to skip now. Can we skip * it if its a resend? */ if (tp1->sent == SCTP_DATAGRAM_RESEND && (PR_SCTP_TTL_ENABLED(tp1->flags))) { /* * Now is this one marked for resend and its time is * now up? */ if (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) { /* Yes so drop it */ if (tp1->data) { sctp_release_pr_sctp_chunk(stcb, tp1, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), &asoc->sent_queue); } } else { /* * No, we are done when hit one for resend * whos time as not expired. */ break; } } /* * Ok now if this chunk is marked to drop it we can clean up * the chunk, advance our peer ack point and we can check * the next chunk. */ if (tp1->sent == SCTP_FORWARD_TSN_SKIP) { /* advance PeerAckPoint goes forward */ asoc->advanced_peer_ack_point = tp1->rec.data.TSN_seq; a_adv = tp1; /* * we don't want to de-queue it here. Just wait for * the next peer SACK to come with a new cumTSN and * then the chunk will be droped in the normal * fashion. */ if (tp1->data) { sctp_free_bufspace(stcb, asoc, tp1, 1); /* * Maybe there should be another * notification type */ sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), tp1); sctp_m_freem(tp1->data); tp1->data = NULL; if (stcb->sctp_socket) { sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); #ifdef SCTP_WAKE_LOGGING sctp_wakeup_log(stcb, tp1->rec.data.TSN_seq, 1, SCTP_WAKESND_FROM_FWDTSN); #endif } } } else { /* * If it is still in RESEND we can advance no * further */ break; } /* * If we hit here we just dumped tp1, move to next tsn on * sent queue. */ tp1 = tp2; } return (a_adv); } #ifdef SCTP_HIGH_SPEED struct sctp_hs_raise_drop { int32_t cwnd; int32_t increase; int32_t drop_percent; }; #define SCTP_HS_TABLE_SIZE 73 struct sctp_hs_raise_drop sctp_cwnd_adjust[SCTP_HS_TABLE_SIZE] = { {38, 1, 50}, /* 0 */ {118, 2, 44}, /* 1 */ {221, 3, 41}, /* 2 */ {347, 4, 38}, /* 3 */ {495, 5, 37}, /* 4 */ {663, 6, 35}, /* 5 */ {851, 7, 34}, /* 6 */ {1058, 8, 33}, /* 7 */ {1284, 9, 32}, /* 8 */ {1529, 10, 31}, /* 9 */ {1793, 11, 30}, /* 10 */ {2076, 12, 29}, /* 11 */ {2378, 13, 28}, /* 12 */ {2699, 14, 28}, /* 13 */ {3039, 15, 27}, /* 14 */ {3399, 16, 27}, /* 15 */ {3778, 17, 26}, /* 16 */ {4177, 18, 26}, /* 17 */ {4596, 19, 25}, /* 18 */ {5036, 20, 25}, /* 19 */ {5497, 21, 24}, /* 20 */ {5979, 22, 24}, /* 21 */ {6483, 23, 23}, /* 22 */ {7009, 24, 23}, /* 23 */ {7558, 25, 22}, /* 24 */ {8130, 26, 22}, /* 25 */ {8726, 27, 22}, /* 26 */ {9346, 28, 21}, /* 27 */ {9991, 29, 21}, /* 28 */ {10661, 30, 21}, /* 29 */ {11358, 31, 20}, /* 30 */ {12082, 32, 20}, /* 31 */ {12834, 33, 20}, /* 32 */ {13614, 34, 19}, /* 33 */ {14424, 35, 19}, /* 34 */ {15265, 36, 19}, /* 35 */ {16137, 37, 19}, /* 36 */ {17042, 38, 18}, /* 37 */ {17981, 39, 18}, /* 38 */ {18955, 40, 18}, /* 39 */ {19965, 41, 17}, /* 40 */ {21013, 42, 17}, /* 41 */ {22101, 43, 17}, /* 42 */ {23230, 44, 17}, /* 43 */ {24402, 45, 16}, /* 44 */ {25618, 46, 16}, /* 45 */ {26881, 47, 16}, /* 46 */ {28193, 48, 16}, /* 47 */ {29557, 49, 15}, /* 48 */ {30975, 50, 15}, /* 49 */ {32450, 51, 15}, /* 50 */ {33986, 52, 15}, /* 51 */ {35586, 53, 14}, /* 52 */ {37253, 54, 14}, /* 53 */ {38992, 55, 14}, /* 54 */ {40808, 56, 14}, /* 55 */ {42707, 57, 13}, /* 56 */ {44694, 58, 13}, /* 57 */ {46776, 59, 13}, /* 58 */ {48961, 60, 13}, /* 59 */ {51258, 61, 13}, /* 60 */ {53677, 62, 12}, /* 61 */ {56230, 63, 12}, /* 62 */ {58932, 64, 12}, /* 63 */ {61799, 65, 12}, /* 64 */ {64851, 66, 11}, /* 65 */ {68113, 67, 11}, /* 66 */ {71617, 68, 11}, /* 67 */ {75401, 69, 10}, /* 68 */ {79517, 70, 10}, /* 69 */ {84035, 71, 10}, /* 70 */ {89053, 72, 10}, /* 71 */ {94717, 73, 9} /* 72 */ }; static void sctp_hs_cwnd_increase(struct sctp_tcb *stcb, struct sctp_nets *net) { int cur_val, i, indx, incr; cur_val = net->cwnd >> 10; indx = SCTP_HS_TABLE_SIZE - 1; if (cur_val < sctp_cwnd_adjust[0].cwnd) { /* normal mode */ if (net->net_ack > net->mtu) { net->cwnd += net->mtu; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->mtu, SCTP_CWND_LOG_FROM_SS); #endif } else { net->cwnd += net->net_ack; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->net_ack, SCTP_CWND_LOG_FROM_SS); #endif } } else { for (i = net->last_hs_used; i < SCTP_HS_TABLE_SIZE; i++) { if (cur_val < sctp_cwnd_adjust[i].cwnd) { indx = i; break; } } net->last_hs_used = indx; incr = ((sctp_cwnd_adjust[indx].increase) << 10); net->cwnd += incr; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, incr, SCTP_CWND_LOG_FROM_SS); #endif } } static void sctp_hs_cwnd_decrease(struct sctp_tcb *stcb, struct sctp_nets *net) { int cur_val, i, indx; #ifdef SCTP_CWND_MONITOR int old_cwnd = net->cwnd; #endif cur_val = net->cwnd >> 10; indx = net->last_hs_used; if (cur_val < sctp_cwnd_adjust[0].cwnd) { /* normal mode */ net->ssthresh = net->cwnd / 2; if (net->ssthresh < (net->mtu * 2)) { net->ssthresh = 2 * net->mtu; } net->cwnd = net->ssthresh; } else { /* drop by the proper amount */ net->ssthresh = net->cwnd - (int)((net->cwnd / 100) * sctp_cwnd_adjust[net->last_hs_used].drop_percent); net->cwnd = net->ssthresh; /* now where are we */ indx = net->last_hs_used; cur_val = net->cwnd >> 10; /* reset where we are in the table */ if (cur_val < sctp_cwnd_adjust[0].cwnd) { /* feel out of hs */ net->last_hs_used = 0; } else { for (i = indx; i >= 1; i--) { if (cur_val > sctp_cwnd_adjust[i - 1].cwnd) { break; } } net->last_hs_used = indx; } } #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_FR); #endif } #endif static __inline void sctp_cwnd_update(struct sctp_tcb *stcb, struct sctp_association *asoc, int accum_moved, int reneged_all, int will_exit) { struct sctp_nets *net; /******************************/ /* update cwnd and Early FR */ /******************************/ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { #ifdef JANA_CMT_FAST_RECOVERY /* * CMT fast recovery code. Need to debug. */ if (net->fast_retran_loss_recovery && net->new_pseudo_cumack) { if (compare_with_wrap(asoc->last_acked_seq, net->fast_recovery_tsn, MAX_TSN) || (asoc->last_acked_seq == net->fast_recovery_tsn) || compare_with_wrap(net->pseudo_cumack, net->fast_recovery_tsn, MAX_TSN) || (net->pseudo_cumack == net->fast_recovery_tsn)) { net->will_exit_fast_recovery = 1; } } #endif if (sctp_early_fr) { /* * So, first of all do we need to have a Early FR * timer running? */ if (((TAILQ_FIRST(&asoc->sent_queue)) && (net->ref_count > 1) && (net->flight_size < net->cwnd)) || (reneged_all)) { /* * yes, so in this case stop it if its * running, and then restart it. Reneging * all is a special case where we want to * run the Early FR timer and then force the * last few unacked to be sent, causing us * to illicit a sack with gaps to force out * the others. */ if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck2); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_20); } SCTP_STAT_INCR(sctps_earlyfrstrid); sctp_timer_start(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net); } else { /* No, stop it if its running */ if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck3); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_21); } } } /* if nothing was acked on this destination skip it */ if (net->net_ack == 0) { #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, 0, SCTP_CWND_LOG_FROM_SACK); #endif continue; } if (net->net_ack2 > 0) { /* * Karn's rule applies to clearing error count, this * is optional. */ net->error_count = 0; if ((net->dest_state & SCTP_ADDR_NOT_REACHABLE) == SCTP_ADDR_NOT_REACHABLE) { /* addr came good */ net->dest_state &= ~SCTP_ADDR_NOT_REACHABLE; net->dest_state |= SCTP_ADDR_REACHABLE; sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, SCTP_RECEIVED_SACK, (void *)net); /* now was it the primary? if so restore */ if (net->dest_state & SCTP_ADDR_WAS_PRIMARY) { sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, net); } } } #ifdef JANA_CMT_FAST_RECOVERY /* * CMT fast recovery code */ /* * if (sctp_cmt_on_off == 1 && * net->fast_retran_loss_recovery && * net->will_exit_fast_recovery == 0) { // @@@ Do something * } else if (sctp_cmt_on_off == 0 && * asoc->fast_retran_loss_recovery && will_exit == 0) { */ #endif if (asoc->fast_retran_loss_recovery && will_exit == 0 && sctp_cmt_on_off == 0) { /* * If we are in loss recovery we skip any cwnd * update */ goto skip_cwnd_update; } /* * CMT: CUC algorithm. Update cwnd if pseudo-cumack has * moved. */ if (accum_moved || (sctp_cmt_on_off && net->new_pseudo_cumack)) { /* If the cumulative ack moved we can proceed */ if (net->cwnd <= net->ssthresh) { /* We are in slow start */ if (net->flight_size + net->net_ack >= net->cwnd) { #ifdef SCTP_HIGH_SPEED sctp_hs_cwnd_increase(stcb, net); #else if (net->net_ack > (net->mtu * sctp_L2_abc_variable)) { net->cwnd += (net->mtu * sctp_L2_abc_variable); #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->mtu, SCTP_CWND_LOG_FROM_SS); #endif } else { net->cwnd += net->net_ack; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->net_ack, SCTP_CWND_LOG_FROM_SS); #endif } #endif } else { unsigned int dif; dif = net->cwnd - (net->flight_size + net->net_ack); #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, net->net_ack, SCTP_CWND_LOG_NOADV_SS); #endif } } else { /* We are in congestion avoidance */ if (net->flight_size + net->net_ack >= net->cwnd) { /* * add to pba only if we had a * cwnd's worth (or so) in flight OR * the burst limit was applied. */ net->partial_bytes_acked += net->net_ack; /* * Do we need to increase (if pba is * > cwnd)? */ if (net->partial_bytes_acked >= net->cwnd) { if (net->cwnd < net->partial_bytes_acked) { net->partial_bytes_acked -= net->cwnd; } else { net->partial_bytes_acked = 0; } net->cwnd += net->mtu; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->mtu, SCTP_CWND_LOG_FROM_CA); #endif } #ifdef SCTP_CWND_LOGGING else { sctp_log_cwnd(stcb, net, net->net_ack, SCTP_CWND_LOG_NOADV_CA); } #endif } else { unsigned int dif; #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, net->net_ack, SCTP_CWND_LOG_NOADV_CA); #endif dif = net->cwnd - (net->flight_size + net->net_ack); } } } else { #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, net->mtu, SCTP_CWND_LOG_NO_CUMACK); #endif } skip_cwnd_update: /* * NOW, according to Karn's rule do we need to restore the * RTO timer back? Check our net_ack2. If not set then we * have a ambiguity.. i.e. all data ack'd was sent to more * than one place. */ if (net->net_ack2) { /* restore any doubled timers */ net->RTO = ((net->lastsa >> 2) + net->lastsv) >> 1; if (net->RTO < stcb->asoc.minrto) { net->RTO = stcb->asoc.minrto; } if (net->RTO > stcb->asoc.maxrto) { net->RTO = stcb->asoc.maxrto; } } } } static void sctp_fs_audit(struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; int inflight = 0, resend = 0, inbetween = 0, acked = 0, above = 0; TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { if (chk->sent < SCTP_DATAGRAM_RESEND) { inflight++; } else if (chk->sent == SCTP_DATAGRAM_RESEND) { resend++; } else if (chk->sent < SCTP_DATAGRAM_ACKED) { inbetween++; } else if (chk->sent > SCTP_DATAGRAM_ACKED) { above++; } else { acked++; } } if ((inflight > 0) || (inbetween > 0)) { #ifdef INVARIANTS panic("Flight size-express incorrect? \n"); #else printf("Flight size-express incorrect inflight:%d inbetween:%d\n", inflight, inbetween); #endif } } static void sctp_window_probe_recovery(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_nets *net, struct sctp_tmit_chunk *tp1) { struct sctp_tmit_chunk *chk; /* First setup this one and get it moved back */ tp1->sent = SCTP_DATAGRAM_UNSENT; tp1->window_probe = 0; #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_WP, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_decrease(tp1); sctp_total_flight_decrease(stcb, tp1); TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next); TAILQ_INSERT_HEAD(&asoc->send_queue, tp1, sctp_next); asoc->sent_queue_cnt--; asoc->send_queue_cnt++; /* * Now all guys marked for RESEND on the sent_queue must be moved * back too. */ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { if (chk->sent == SCTP_DATAGRAM_RESEND) { /* Another chunk to move */ chk->sent = SCTP_DATAGRAM_UNSENT; chk->window_probe = 0; /* It should not be in flight */ TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); TAILQ_INSERT_AFTER(&asoc->send_queue, tp1, chk, sctp_next); asoc->sent_queue_cnt--; asoc->send_queue_cnt++; sctp_ucount_decr(asoc->sent_queue_retran_cnt); } } } void sctp_express_handle_sack(struct sctp_tcb *stcb, uint32_t cumack, uint32_t rwnd, int nonce_sum_flag, int *abort_now) { struct sctp_nets *net; struct sctp_association *asoc; struct sctp_tmit_chunk *tp1, *tp2; uint32_t old_rwnd; int win_probe_recovery = 0; int win_probe_recovered = 0; int j, done_once = 0; #ifdef SCTP_LOG_SACK_ARRIVALS sctp_misc_ints(SCTP_SACK_LOG_EXPRESS, cumack, rwnd, stcb->asoc.last_acked_seq, stcb->asoc.peers_rwnd); #endif SCTP_TCB_LOCK_ASSERT(stcb); asoc = &stcb->asoc; old_rwnd = asoc->peers_rwnd; if (compare_with_wrap(asoc->last_acked_seq, cumack, MAX_TSN)) { /* old ack */ return; } else if (asoc->last_acked_seq == cumack) { /* Window update sack */ asoc->peers_rwnd = sctp_sbspace_sub(rwnd, (uint32_t) (asoc->total_flight + (asoc->sent_queue_cnt * sctp_peer_chunk_oh))); if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } if (asoc->peers_rwnd > old_rwnd) { goto again; } return; } /* First setup for CC stuff */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->prev_cwnd = net->cwnd; net->net_ack = 0; net->net_ack2 = 0; /* * CMT: Reset CUC and Fast recovery algo variables before * SACK processing */ net->new_pseudo_cumack = 0; net->will_exit_fast_recovery = 0; } if (sctp_strict_sacks) { uint32_t send_s; if (!TAILQ_EMPTY(&asoc->sent_queue)) { tp1 = TAILQ_LAST(&asoc->sent_queue, sctpchunk_listhead); send_s = tp1->rec.data.TSN_seq + 1; } else { send_s = asoc->sending_seq; } if ((cumack == send_s) || compare_with_wrap(cumack, send_s, MAX_TSN)) { #ifndef INVARIANTS struct mbuf *oper; #endif #ifdef INVARIANTS panic("Impossible sack 1"); #else *abort_now = 1; /* XXX */ oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_25); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_25; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); return; #endif } } asoc->this_sack_highest_gap = cumack; stcb->asoc.overall_error_count = 0; if (compare_with_wrap(cumack, asoc->last_acked_seq, MAX_TSN)) { /* process the new consecutive TSN first */ tp1 = TAILQ_FIRST(&asoc->sent_queue); while (tp1) { tp2 = TAILQ_NEXT(tp1, sctp_next); if (compare_with_wrap(cumack, tp1->rec.data.TSN_seq, MAX_TSN) || cumack == tp1->rec.data.TSN_seq) { if (tp1->sent != SCTP_DATAGRAM_UNSENT) { /* * ECN Nonce: Add the nonce to the * sender's nonce sum */ asoc->nonce_sum_expect_base += tp1->rec.data.ect_nonce; if (tp1->sent < SCTP_DATAGRAM_ACKED) { /* * If it is less than ACKED, * it is now no-longer in * flight. Higher values may * occur during marking */ if (tp1->sent < SCTP_DATAGRAM_RESEND) { #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_CA, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_decrease(tp1); sctp_total_flight_decrease(stcb, tp1); } tp1->whoTo->net_ack += tp1->send_size; if (tp1->snd_count < 2) { /* * True * non-retransmited * chunk */ tp1->whoTo->net_ack2 += tp1->send_size; /* update RTO too? */ if (tp1->do_rtt) { tp1->whoTo->RTO = sctp_calculate_rto(stcb, asoc, tp1->whoTo, &tp1->sent_rcv_time); tp1->do_rtt = 0; } } /* * CMT: CUCv2 algorithm. * From the cumack'd TSNs, * for each TSN being acked * for the first time, set * the following variables * for the corresp * destination. * new_pseudo_cumack will * trigger a cwnd update. * find_(rtx_)pseudo_cumack * will trigger search for * the next expected * (rtx-)pseudo-cumack. */ tp1->whoTo->new_pseudo_cumack = 1; tp1->whoTo->find_pseudo_cumack = 1; tp1->whoTo->find_rtx_pseudo_cumack = 1; #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.TSN_seq, SCTP_CWND_LOG_FROM_SACK); #endif } if (tp1->sent == SCTP_DATAGRAM_RESEND) { sctp_ucount_decr(asoc->sent_queue_retran_cnt); } if (tp1->rec.data.chunk_was_revoked) { /* deflate the cwnd */ tp1->whoTo->cwnd -= tp1->book_size; tp1->rec.data.chunk_was_revoked = 0; } tp1->sent = SCTP_DATAGRAM_ACKED; } } else { break; } TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next); if (tp1->data) { sctp_free_bufspace(stcb, asoc, tp1, 1); sctp_m_freem(tp1->data); } #ifdef SCTP_SACK_LOGGING sctp_log_sack(asoc->last_acked_seq, cumack, tp1->rec.data.TSN_seq, 0, 0, SCTP_LOG_FREE_SENT); #endif tp1->data = NULL; asoc->sent_queue_cnt--; sctp_free_remote_addr(tp1->whoTo); sctp_free_a_chunk(stcb, tp1); tp1 = tp2; } } if (stcb->sctp_socket) { SOCKBUF_LOCK(&stcb->sctp_socket->so_snd); #ifdef SCTP_WAKE_LOGGING sctp_wakeup_log(stcb, cumack, 1, SCTP_WAKESND_FROM_SACK); #endif sctp_sowwakeup_locked(stcb->sctp_ep, stcb->sctp_socket); #ifdef SCTP_WAKE_LOGGING } else { sctp_wakeup_log(stcb, cumack, 1, SCTP_NOWAKE_FROM_SACK); #endif } if (asoc->last_acked_seq != cumack) sctp_cwnd_update(stcb, asoc, 1, 0, 0); asoc->last_acked_seq = cumack; if (TAILQ_EMPTY(&asoc->sent_queue)) { /* nothing left in-flight */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->flight_size = 0; net->partial_bytes_acked = 0; } asoc->total_flight = 0; asoc->total_flight_count = 0; } /* Fix up the a-p-a-p for future PR-SCTP sends */ if (compare_with_wrap(cumack, asoc->advanced_peer_ack_point, MAX_TSN)) { asoc->advanced_peer_ack_point = cumack; } /* ECN Nonce updates */ if (asoc->ecn_nonce_allowed) { if (asoc->nonce_sum_check) { if (nonce_sum_flag != ((asoc->nonce_sum_expect_base) & SCTP_SACK_NONCE_SUM)) { if (asoc->nonce_wait_for_ecne == 0) { struct sctp_tmit_chunk *lchk; lchk = TAILQ_FIRST(&asoc->send_queue); asoc->nonce_wait_for_ecne = 1; if (lchk) { asoc->nonce_wait_tsn = lchk->rec.data.TSN_seq; } else { asoc->nonce_wait_tsn = asoc->sending_seq; } } else { if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_wait_tsn, MAX_TSN) || (asoc->last_acked_seq == asoc->nonce_wait_tsn)) { /* * Misbehaving peer. We need * to react to this guy */ asoc->ecn_allowed = 0; asoc->ecn_nonce_allowed = 0; } } } } else { /* See if Resynchronization Possible */ if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_resync_tsn, MAX_TSN)) { asoc->nonce_sum_check = 1; /* * now we must calculate what the base is. * We do this based on two things, we know * the total's for all the segments * gap-acked in the SACK (none), We also * know the SACK's nonce sum, its in * nonce_sum_flag. So we can build a truth * table to back-calculate the new value of * asoc->nonce_sum_expect_base: * * SACK-flag-Value Seg-Sums Base 0 0 0 * 1 0 1 0 1 1 1 * 1 0 */ asoc->nonce_sum_expect_base = (0 ^ nonce_sum_flag) & SCTP_SACK_NONCE_SUM; } } } /* RWND update */ asoc->peers_rwnd = sctp_sbspace_sub(rwnd, (uint32_t) (asoc->total_flight + (asoc->sent_queue_cnt * sctp_peer_chunk_oh))); if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } if (asoc->peers_rwnd > old_rwnd) { win_probe_recovery = 1; } /* Now assure a timer where data is queued at */ again: j = 0; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (win_probe_recovery && (net->window_probe)) { net->window_probe = 0; win_probe_recovered = 1; /* * Find first chunk that was used with window probe * and clear the sent */ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) { if (tp1->window_probe) { /* move back to data send queue */ sctp_window_probe_recovery(stcb, asoc, net, tp1); break; } } } if (net->flight_size) { int to_ticks; if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } j++; SCTP_OS_TIMER_START(&net->rxt_timer.timer, to_ticks, sctp_timeout_handler, &net->rxt_timer); } else { if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_22); } if (sctp_early_fr) { if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck4); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_23); } } } } if ((j == 0) && (!TAILQ_EMPTY(&asoc->sent_queue)) && (asoc->sent_queue_retran_cnt == 0) && (win_probe_recovered == 0) && (done_once == 0)) { /* huh, this should not happen */ sctp_fs_audit(asoc); TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->flight_size = 0; } asoc->total_flight = 0; asoc->total_flight_count = 0; asoc->sent_queue_retran_cnt = 0; TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) { if (tp1->sent < SCTP_DATAGRAM_RESEND) { sctp_flight_size_increase(tp1); sctp_total_flight_increase(stcb, tp1); } else if (tp1->sent == SCTP_DATAGRAM_RESEND) { asoc->sent_queue_retran_cnt++; } } done_once = 1; goto again; } /**********************************/ /* Now what about shutdown issues */ /**********************************/ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue)) { /* nothing left on sendqueue.. consider done */ /* clean up */ if ((asoc->stream_queue_cnt == 1) && ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) || (asoc->state & SCTP_STATE_SHUTDOWN_RECEIVED)) && (asoc->locked_on_sending) ) { struct sctp_stream_queue_pending *sp; /* * I may be in a state where we got all across.. but * cannot write more due to a shutdown... we abort * since the user did not indicate EOR in this case. * The sp will be cleaned during free of the asoc. */ sp = TAILQ_LAST(&((asoc->locked_on_sending)->outqueue), sctp_streamhead); if ((sp) && (sp->length == 0) && (sp->msg_is_complete == 0)) { asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; asoc->locked_on_sending = NULL; asoc->stream_queue_cnt--; } } if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) && (asoc->stream_queue_cnt == 0)) { if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) { /* Need to abort here */ struct mbuf *oper; abort_out_now: *abort_now = 1; /* XXX */ oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_24); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_24; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, oper); } else { if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_stop_timers_for_shutdown(stcb); sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } else if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) && (asoc->stream_queue_cnt == 0)) { if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) { goto abort_out_now; } SCTP_STAT_DECR_GAUGE32(sctps_currestab); asoc->state = SCTP_STATE_SHUTDOWN_ACK_SENT; sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep, stcb, asoc->primary_destination); } } #ifdef SCTP_SACK_RWND_LOGGING sctp_misc_ints(SCTP_SACK_RWND_UPDATE, rwnd, stcb->asoc.peers_rwnd, stcb->asoc.total_flight, stcb->asoc.total_output_queue_size); #endif } void sctp_handle_sack(struct sctp_sack_chunk *ch, struct sctp_tcb *stcb, struct sctp_nets *net_from, int *abort_now, int sack_len, uint32_t rwnd) { struct sctp_association *asoc; struct sctp_sack *sack; struct sctp_tmit_chunk *tp1, *tp2; uint32_t cum_ack, last_tsn, biggest_tsn_acked, biggest_tsn_newly_acked, this_sack_lowest_newack; uint32_t sav_cum_ack; uint16_t num_seg, num_dup; uint16_t wake_him = 0; unsigned int sack_length; uint32_t send_s = 0; long j; int accum_moved = 0; int will_exit_fast_recovery = 0; uint32_t a_rwnd, old_rwnd; int win_probe_recovery = 0; int win_probe_recovered = 0; struct sctp_nets *net = NULL; int nonce_sum_flag, ecn_seg_sums = 0; int done_once; uint8_t reneged_all = 0; uint8_t cmt_dac_flag; /* * we take any chance we can to service our queues since we cannot * get awoken when the socket is read from :< */ /* * Now perform the actual SACK handling: 1) Verify that it is not an * old sack, if so discard. 2) If there is nothing left in the send * queue (cum-ack is equal to last acked) then you have a duplicate * too, update any rwnd change and verify no timers are running. * then return. 3) Process any new consequtive data i.e. cum-ack * moved process these first and note that it moved. 4) Process any * sack blocks. 5) Drop any acked from the queue. 6) Check for any * revoked blocks and mark. 7) Update the cwnd. 8) Nothing left, * sync up flightsizes and things, stop all timers and also check * for shutdown_pending state. If so then go ahead and send off the * shutdown. If in shutdown recv, send off the shutdown-ack and * start that timer, Ret. 9) Strike any non-acked things and do FR * procedure if needed being sure to set the FR flag. 10) Do pr-sctp * procedures. 11) Apply any FR penalties. 12) Assure we will SACK * if in shutdown_recv state. */ SCTP_TCB_LOCK_ASSERT(stcb); sack = &ch->sack; /* CMT DAC algo */ this_sack_lowest_newack = 0; j = 0; sack_length = (unsigned int)sack_len; /* ECN Nonce */ SCTP_STAT_INCR(sctps_slowpath_sack); nonce_sum_flag = ch->ch.chunk_flags & SCTP_SACK_NONCE_SUM; cum_ack = last_tsn = ntohl(sack->cum_tsn_ack); num_seg = ntohs(sack->num_gap_ack_blks); a_rwnd = rwnd; #ifdef SCTP_LOG_SACK_ARRIVALS sctp_misc_ints(SCTP_SACK_LOG_NORMAL, cum_ack, rwnd, stcb->asoc.last_acked_seq, stcb->asoc.peers_rwnd); #endif /* CMT DAC algo */ cmt_dac_flag = ch->ch.chunk_flags & SCTP_SACK_CMT_DAC; num_dup = ntohs(sack->num_dup_tsns); old_rwnd = stcb->asoc.peers_rwnd; stcb->asoc.overall_error_count = 0; asoc = &stcb->asoc; #ifdef SCTP_SACK_LOGGING sctp_log_sack(asoc->last_acked_seq, cum_ack, 0, num_seg, num_dup, SCTP_LOG_NEW_SACK); #endif #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) if (num_dup) { int off_to_dup, iii; uint32_t *dupdata; off_to_dup = (num_seg * sizeof(struct sctp_gap_ack_block)) + sizeof(struct sctp_sack_chunk); if ((off_to_dup + (num_dup * sizeof(uint32_t))) <= sack_length) { dupdata = (uint32_t *) ((caddr_t)ch + off_to_dup); for (iii = 0; iii < num_dup; iii++) { sctp_log_fr(*dupdata, 0, 0, SCTP_FR_DUPED); dupdata++; } } else { printf("Size invalid offset to dups:%d number dups:%d sack_len:%d num gaps:%d\n", off_to_dup, num_dup, sack_length, num_seg); } } #endif if (sctp_strict_sacks) { /* reality check */ if (!TAILQ_EMPTY(&asoc->sent_queue)) { tp1 = TAILQ_LAST(&asoc->sent_queue, sctpchunk_listhead); send_s = tp1->rec.data.TSN_seq + 1; } else { send_s = asoc->sending_seq; } if (cum_ack == send_s || compare_with_wrap(cum_ack, send_s, MAX_TSN)) { #ifndef INVARIANTS struct mbuf *oper; #endif #ifdef INVARIANTS hopeless_peer: panic("Impossible sack 1"); #else /* * no way, we have not even sent this TSN out yet. * Peer is hopelessly messed up with us. */ hopeless_peer: *abort_now = 1; /* XXX */ oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_25); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_25; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); return; #endif } } /**********************/ /* 1) check the range */ /**********************/ if (compare_with_wrap(asoc->last_acked_seq, last_tsn, MAX_TSN)) { /* acking something behind */ return; } sav_cum_ack = asoc->last_acked_seq; /* update the Rwnd of the peer */ if (TAILQ_EMPTY(&asoc->sent_queue) && TAILQ_EMPTY(&asoc->send_queue) && (asoc->stream_queue_cnt == 0) ) { /* nothing left on send/sent and strmq */ #ifdef SCTP_LOG_RWND sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, asoc->peers_rwnd, 0, 0, a_rwnd); #endif asoc->peers_rwnd = a_rwnd; if (asoc->sent_queue_retran_cnt) { asoc->sent_queue_retran_cnt = 0; } if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } /* stop any timers */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_26); if (sctp_early_fr) { if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck1); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_26); } } net->partial_bytes_acked = 0; net->flight_size = 0; } asoc->total_flight = 0; asoc->total_flight_count = 0; return; } /* * We init netAckSz and netAckSz2 to 0. These are used to track 2 * things. The total byte count acked is tracked in netAckSz AND * netAck2 is used to track the total bytes acked that are un- * amibguious and were never retransmitted. We track these on a per * destination address basis. */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->prev_cwnd = net->cwnd; net->net_ack = 0; net->net_ack2 = 0; /* * CMT: Reset CUC and Fast recovery algo variables before * SACK processing */ net->new_pseudo_cumack = 0; net->will_exit_fast_recovery = 0; } /* process the new consecutive TSN first */ tp1 = TAILQ_FIRST(&asoc->sent_queue); while (tp1) { if (compare_with_wrap(last_tsn, tp1->rec.data.TSN_seq, MAX_TSN) || last_tsn == tp1->rec.data.TSN_seq) { if (tp1->sent != SCTP_DATAGRAM_UNSENT) { /* * ECN Nonce: Add the nonce to the sender's * nonce sum */ asoc->nonce_sum_expect_base += tp1->rec.data.ect_nonce; accum_moved = 1; if (tp1->sent < SCTP_DATAGRAM_ACKED) { /* * If it is less than ACKED, it is * now no-longer in flight. Higher * values may occur during marking */ if ((tp1->whoTo->dest_state & SCTP_ADDR_UNCONFIRMED) && (tp1->snd_count < 2)) { /* * If there was no retran * and the address is * un-confirmed and we sent * there and are now * sacked.. its confirmed, * mark it so. */ tp1->whoTo->dest_state &= ~SCTP_ADDR_UNCONFIRMED; } if (tp1->sent < SCTP_DATAGRAM_RESEND) { #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_CA, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_decrease(tp1); sctp_total_flight_decrease(stcb, tp1); } tp1->whoTo->net_ack += tp1->send_size; /* CMT SFR and DAC algos */ this_sack_lowest_newack = tp1->rec.data.TSN_seq; tp1->whoTo->saw_newack = 1; if (tp1->snd_count < 2) { /* * True non-retransmited * chunk */ tp1->whoTo->net_ack2 += tp1->send_size; /* update RTO too? */ if (tp1->do_rtt) { tp1->whoTo->RTO = sctp_calculate_rto(stcb, asoc, tp1->whoTo, &tp1->sent_rcv_time); tp1->do_rtt = 0; } } /* * CMT: CUCv2 algorithm. From the * cumack'd TSNs, for each TSN being * acked for the first time, set the * following variables for the * corresp destination. * new_pseudo_cumack will trigger a * cwnd update. * find_(rtx_)pseudo_cumack will * trigger search for the next * expected (rtx-)pseudo-cumack. */ tp1->whoTo->new_pseudo_cumack = 1; tp1->whoTo->find_pseudo_cumack = 1; tp1->whoTo->find_rtx_pseudo_cumack = 1; #ifdef SCTP_SACK_LOGGING sctp_log_sack(asoc->last_acked_seq, cum_ack, tp1->rec.data.TSN_seq, 0, 0, SCTP_LOG_TSN_ACKED); #endif #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, tp1->whoTo, tp1->rec.data.TSN_seq, SCTP_CWND_LOG_FROM_SACK); #endif } if (tp1->sent == SCTP_DATAGRAM_RESEND) { sctp_ucount_decr(asoc->sent_queue_retran_cnt); #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xB3, (asoc->sent_queue_retran_cnt & 0x000000ff)); #endif } if (tp1->rec.data.chunk_was_revoked) { /* deflate the cwnd */ tp1->whoTo->cwnd -= tp1->book_size; tp1->rec.data.chunk_was_revoked = 0; } tp1->sent = SCTP_DATAGRAM_ACKED; } } else { break; } tp1 = TAILQ_NEXT(tp1, sctp_next); } biggest_tsn_newly_acked = biggest_tsn_acked = last_tsn; /* always set this up to cum-ack */ asoc->this_sack_highest_gap = last_tsn; if (((num_seg * (sizeof(struct sctp_gap_ack_block))) + sizeof(struct sctp_sack_chunk)) > sack_length) { /* skip corrupt segments */ goto skip_segments; } if (num_seg > 0) { /* * CMT: SFR algo (and HTNA) - this_sack_highest_newack has * to be greater than the cumack. Also reset saw_newack to 0 * for all dests. */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->saw_newack = 0; net->this_sack_highest_newack = last_tsn; } /* * thisSackHighestGap will increase while handling NEW * segments this_sack_highest_newack will increase while * handling NEWLY ACKED chunks. this_sack_lowest_newack is * used for CMT DAC algo. saw_newack will also change. */ sctp_handle_segments(stcb, asoc, ch, last_tsn, &biggest_tsn_acked, &biggest_tsn_newly_acked, &this_sack_lowest_newack, num_seg, &ecn_seg_sums); if (sctp_strict_sacks) { /* * validate the biggest_tsn_acked in the gap acks if * strict adherence is wanted. */ if ((biggest_tsn_acked == send_s) || (compare_with_wrap(biggest_tsn_acked, send_s, MAX_TSN))) { /* * peer is either confused or we are under * attack. We must abort. */ goto hopeless_peer; } } } skip_segments: /*******************************************/ /* cancel ALL T3-send timer if accum moved */ /*******************************************/ if (sctp_cmt_on_off) { TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (net->new_pseudo_cumack) sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_27); } } else { if (accum_moved) { TAILQ_FOREACH(net, &asoc->nets, sctp_next) { sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_28); } } } /********************************************/ /* drop the acked chunks from the sendqueue */ /********************************************/ asoc->last_acked_seq = cum_ack; tp1 = TAILQ_FIRST(&asoc->sent_queue); if (tp1 == NULL) goto done_with_it; do { if (compare_with_wrap(tp1->rec.data.TSN_seq, cum_ack, MAX_TSN)) { break; } if (tp1->sent == SCTP_DATAGRAM_UNSENT) { /* no more sent on list */ break; } tp2 = TAILQ_NEXT(tp1, sctp_next); TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next); /* * Friendlier printf in lieu of panic now that I think its * fixed */ if (tp1->pr_sctp_on) { if (asoc->pr_sctp_cnt != 0) asoc->pr_sctp_cnt--; } if ((TAILQ_FIRST(&asoc->sent_queue) == NULL) && (asoc->total_flight > 0)) { #ifdef INVARIANTS panic("Warning flight size is postive and should be 0"); #else printf("Warning flight size incorrect should be 0 is %d\n", asoc->total_flight); #endif asoc->total_flight = 0; } if (tp1->data) { sctp_free_bufspace(stcb, asoc, tp1, 1); sctp_m_freem(tp1->data); if (PR_SCTP_BUF_ENABLED(tp1->flags)) { asoc->sent_queue_cnt_removeable--; } } #ifdef SCTP_SACK_LOGGING sctp_log_sack(asoc->last_acked_seq, cum_ack, tp1->rec.data.TSN_seq, 0, 0, SCTP_LOG_FREE_SENT); #endif tp1->data = NULL; asoc->sent_queue_cnt--; sctp_free_remote_addr(tp1->whoTo); sctp_free_a_chunk(stcb, tp1); wake_him++; tp1 = tp2; } while (tp1 != NULL); done_with_it: if ((wake_him) && (stcb->sctp_socket)) { SOCKBUF_LOCK(&stcb->sctp_socket->so_snd); #ifdef SCTP_WAKE_LOGGING sctp_wakeup_log(stcb, cum_ack, wake_him, SCTP_WAKESND_FROM_SACK); #endif sctp_sowwakeup_locked(stcb->sctp_ep, stcb->sctp_socket); #ifdef SCTP_WAKE_LOGGING } else { sctp_wakeup_log(stcb, cum_ack, wake_him, SCTP_NOWAKE_FROM_SACK); #endif } if (asoc->fast_retran_loss_recovery && accum_moved) { if (compare_with_wrap(asoc->last_acked_seq, asoc->fast_recovery_tsn, MAX_TSN) || asoc->last_acked_seq == asoc->fast_recovery_tsn) { /* Setup so we will exit RFC2582 fast recovery */ will_exit_fast_recovery = 1; } } /* * Check for revoked fragments: * * if Previous sack - Had no frags then we can't have any revoked if * Previous sack - Had frag's then - If we now have frags aka * num_seg > 0 call sctp_check_for_revoked() to tell if peer revoked * some of them. else - The peer revoked all ACKED fragments, since * we had some before and now we have NONE. */ if (num_seg) sctp_check_for_revoked(stcb, asoc, cum_ack, biggest_tsn_acked); else if (asoc->saw_sack_with_frags) { int cnt_revoked = 0; tp1 = TAILQ_FIRST(&asoc->sent_queue); if (tp1 != NULL) { /* Peer revoked all dg's marked or acked */ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) { if ((tp1->sent > SCTP_DATAGRAM_RESEND) && (tp1->sent < SCTP_FORWARD_TSN_SKIP)) { tp1->sent = SCTP_DATAGRAM_SENT; #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_UP_REVOKE, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) tp1->whoTo, tp1->rec.data.TSN_seq); #endif sctp_flight_size_increase(tp1); sctp_total_flight_increase(stcb, tp1); tp1->rec.data.chunk_was_revoked = 1; /* * To ensure that this increase in * flightsize, which is artificial, * does not throttle the sender, we * also increase the cwnd * artificially. */ tp1->whoTo->cwnd += tp1->book_size; cnt_revoked++; } } if (cnt_revoked) { reneged_all = 1; } } asoc->saw_sack_with_frags = 0; } if (num_seg) asoc->saw_sack_with_frags = 1; else asoc->saw_sack_with_frags = 0; sctp_cwnd_update(stcb, asoc, accum_moved, reneged_all, will_exit_fast_recovery); if (TAILQ_EMPTY(&asoc->sent_queue)) { /* nothing left in-flight */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { /* stop all timers */ if (sctp_early_fr) { if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck4); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_29); } } sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_30); net->flight_size = 0; net->partial_bytes_acked = 0; } asoc->total_flight = 0; asoc->total_flight_count = 0; } /**********************************/ /* Now what about shutdown issues */ /**********************************/ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue)) { /* nothing left on sendqueue.. consider done */ #ifdef SCTP_LOG_RWND sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, asoc->peers_rwnd, 0, 0, a_rwnd); #endif asoc->peers_rwnd = a_rwnd; if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } /* clean up */ if ((asoc->stream_queue_cnt == 1) && ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) || (asoc->state & SCTP_STATE_SHUTDOWN_RECEIVED)) && (asoc->locked_on_sending) ) { struct sctp_stream_queue_pending *sp; /* * I may be in a state where we got all across.. but * cannot write more due to a shutdown... we abort * since the user did not indicate EOR in this case. */ sp = TAILQ_LAST(&((asoc->locked_on_sending)->outqueue), sctp_streamhead); if ((sp) && (sp->length == 0) && (sp->msg_is_complete == 0)) { asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; asoc->locked_on_sending = NULL; asoc->stream_queue_cnt--; } } if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) && (asoc->stream_queue_cnt == 0)) { if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) { /* Need to abort here */ struct mbuf *oper; abort_out_now: *abort_now = 1; /* XXX */ oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_31); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_31; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, oper); return; } else { if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_stop_timers_for_shutdown(stcb); sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } return; } else if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) && (asoc->stream_queue_cnt == 0)) { if (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT) { goto abort_out_now; } SCTP_STAT_DECR_GAUGE32(sctps_currestab); asoc->state = SCTP_STATE_SHUTDOWN_ACK_SENT; sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep, stcb, asoc->primary_destination); return; } } /* * Now here we are going to recycle net_ack for a different use... * HEADS UP. */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->net_ack = 0; } /* * CMT DAC algorithm: If SACK DAC flag was 0, then no extra marking * to be done. Setting this_sack_lowest_newack to the cum_ack will * automatically ensure that. */ if (sctp_cmt_on_off && sctp_cmt_use_dac && (cmt_dac_flag == 0)) { this_sack_lowest_newack = cum_ack; } if (num_seg > 0) { sctp_strike_gap_ack_chunks(stcb, asoc, biggest_tsn_acked, biggest_tsn_newly_acked, this_sack_lowest_newack, accum_moved); } /*********************************************/ /* Here we perform PR-SCTP procedures */ /* (section 4.2) */ /*********************************************/ /* C1. update advancedPeerAckPoint */ if (compare_with_wrap(cum_ack, asoc->advanced_peer_ack_point, MAX_TSN)) { asoc->advanced_peer_ack_point = cum_ack; } /* C2. try to further move advancedPeerAckPoint ahead */ if ((asoc->peer_supports_prsctp) && (asoc->pr_sctp_cnt > 0)) { struct sctp_tmit_chunk *lchk; lchk = sctp_try_advance_peer_ack_point(stcb, asoc); /* C3. See if we need to send a Fwd-TSN */ if (compare_with_wrap(asoc->advanced_peer_ack_point, cum_ack, MAX_TSN)) { /* * ISSUE with ECN, see FWD-TSN processing for notes * on issues that will occur when the ECN NONCE * stuff is put into SCTP for cross checking. */ send_forward_tsn(stcb, asoc); /* * ECN Nonce: Disable Nonce Sum check when FWD TSN * is sent and store resync tsn */ asoc->nonce_sum_check = 0; asoc->nonce_resync_tsn = asoc->advanced_peer_ack_point; if (lchk) { /* Assure a timer is up */ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, lchk->whoTo); } } } /* * CMT fast recovery code. Need to debug. ((sctp_cmt_on_off == 1) && * (net->fast_retran_loss_recovery == 0))) */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if ((asoc->fast_retran_loss_recovery == 0) || (sctp_cmt_on_off == 1)) { /* out of a RFC2582 Fast recovery window? */ if (net->net_ack > 0) { /* * per section 7.2.3, are there any * destinations that had a fast retransmit * to them. If so what we need to do is * adjust ssthresh and cwnd. */ struct sctp_tmit_chunk *lchk; #ifdef SCTP_HIGH_SPEED sctp_hs_cwnd_decrease(stcb, net); #else #ifdef SCTP_CWND_MONITOR int old_cwnd = net->cwnd; #endif net->ssthresh = net->cwnd / 2; if (net->ssthresh < (net->mtu * 2)) { net->ssthresh = 2 * net->mtu; } net->cwnd = net->ssthresh; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_FR); #endif #endif lchk = TAILQ_FIRST(&asoc->send_queue); net->partial_bytes_acked = 0; /* Turn on fast recovery window */ asoc->fast_retran_loss_recovery = 1; if (lchk == NULL) { /* Mark end of the window */ asoc->fast_recovery_tsn = asoc->sending_seq - 1; } else { asoc->fast_recovery_tsn = lchk->rec.data.TSN_seq - 1; } /* * CMT fast recovery -- per destination * recovery variable. */ net->fast_retran_loss_recovery = 1; if (lchk == NULL) { /* Mark end of the window */ net->fast_recovery_tsn = asoc->sending_seq - 1; } else { net->fast_recovery_tsn = lchk->rec.data.TSN_seq - 1; } /* * Disable Nonce Sum Checking and store the * resync tsn */ asoc->nonce_sum_check = 0; asoc->nonce_resync_tsn = asoc->fast_recovery_tsn + 1; sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_32); sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net); } } else if (net->net_ack > 0) { /* * Mark a peg that we WOULD have done a cwnd * reduction but RFC2582 prevented this action. */ SCTP_STAT_INCR(sctps_fastretransinrtt); } } /****************************************************************** * Here we do the stuff with ECN Nonce checking. * We basically check to see if the nonce sum flag was incorrect * or if resynchronization needs to be done. Also if we catch a * misbehaving receiver we give him the kick. ******************************************************************/ if (asoc->ecn_nonce_allowed) { if (asoc->nonce_sum_check) { if (nonce_sum_flag != ((asoc->nonce_sum_expect_base + ecn_seg_sums) & SCTP_SACK_NONCE_SUM)) { if (asoc->nonce_wait_for_ecne == 0) { struct sctp_tmit_chunk *lchk; lchk = TAILQ_FIRST(&asoc->send_queue); asoc->nonce_wait_for_ecne = 1; if (lchk) { asoc->nonce_wait_tsn = lchk->rec.data.TSN_seq; } else { asoc->nonce_wait_tsn = asoc->sending_seq; } } else { if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_wait_tsn, MAX_TSN) || (asoc->last_acked_seq == asoc->nonce_wait_tsn)) { /* * Misbehaving peer. We need * to react to this guy */ asoc->ecn_allowed = 0; asoc->ecn_nonce_allowed = 0; } } } } else { /* See if Resynchronization Possible */ if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_resync_tsn, MAX_TSN)) { asoc->nonce_sum_check = 1; /* * now we must calculate what the base is. * We do this based on two things, we know * the total's for all the segments * gap-acked in the SACK, its stored in * ecn_seg_sums. We also know the SACK's * nonce sum, its in nonce_sum_flag. So we * can build a truth table to back-calculate * the new value of * asoc->nonce_sum_expect_base: * * SACK-flag-Value Seg-Sums Base 0 0 0 * 1 0 1 0 1 1 1 * 1 0 */ asoc->nonce_sum_expect_base = (ecn_seg_sums ^ nonce_sum_flag) & SCTP_SACK_NONCE_SUM; } } } /* Now are we exiting loss recovery ? */ if (will_exit_fast_recovery) { /* Ok, we must exit fast recovery */ asoc->fast_retran_loss_recovery = 0; } if ((asoc->sat_t3_loss_recovery) && ((compare_with_wrap(asoc->last_acked_seq, asoc->sat_t3_recovery_tsn, MAX_TSN) || (asoc->last_acked_seq == asoc->sat_t3_recovery_tsn)))) { /* end satellite t3 loss recovery */ asoc->sat_t3_loss_recovery = 0; } /* * CMT Fast recovery */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (net->will_exit_fast_recovery) { /* Ok, we must exit fast recovery */ net->fast_retran_loss_recovery = 0; } } /* Adjust and set the new rwnd value */ #ifdef SCTP_LOG_RWND sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, asoc->peers_rwnd, asoc->total_flight, (asoc->sent_queue_cnt * sctp_peer_chunk_oh), a_rwnd); #endif asoc->peers_rwnd = sctp_sbspace_sub(a_rwnd, (uint32_t) (asoc->total_flight + (asoc->sent_queue_cnt * sctp_peer_chunk_oh))); if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } if (asoc->peers_rwnd > old_rwnd) { win_probe_recovery = 1; } /* * Now we must setup so we have a timer up for anyone with * outstanding data. */ done_once = 0; again: j = 0; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (win_probe_recovery && (net->window_probe)) { net->window_probe = 0; win_probe_recovered = 1; /*- * Find first chunk that was used with * window probe and clear the event. Put * it back into the send queue as if has * not been sent. */ TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) { if (tp1->window_probe) { sctp_window_probe_recovery(stcb, asoc, net, tp1); break; } } } if (net->flight_size) { j++; sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net); } else { if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_22); } if (sctp_early_fr) { if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpidsck4); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INDATA + SCTP_LOC_23); } } } } if ((j == 0) && (!TAILQ_EMPTY(&asoc->sent_queue)) && (asoc->sent_queue_retran_cnt == 0) && (win_probe_recovered == 0) && (done_once == 0)) { /* huh, this should not happen */ sctp_fs_audit(asoc); TAILQ_FOREACH(net, &asoc->nets, sctp_next) { net->flight_size = 0; } asoc->total_flight = 0; asoc->total_flight_count = 0; asoc->sent_queue_retran_cnt = 0; TAILQ_FOREACH(tp1, &asoc->sent_queue, sctp_next) { if (tp1->sent < SCTP_DATAGRAM_RESEND) { sctp_flight_size_increase(tp1); sctp_total_flight_increase(stcb, tp1); } else if (tp1->sent == SCTP_DATAGRAM_RESEND) { asoc->sent_queue_retran_cnt++; } } done_once = 1; goto again; } #ifdef SCTP_SACK_RWND_LOGGING sctp_misc_ints(SCTP_SACK_RWND_UPDATE, a_rwnd, stcb->asoc.peers_rwnd, stcb->asoc.total_flight, stcb->asoc.total_output_queue_size); #endif } void sctp_update_acked(struct sctp_tcb *stcb, struct sctp_shutdown_chunk *cp, struct sctp_nets *netp, int *abort_flag) { /* Copy cum-ack */ uint32_t cum_ack, a_rwnd; cum_ack = ntohl(cp->cumulative_tsn_ack); /* Arrange so a_rwnd does NOT change */ a_rwnd = stcb->asoc.peers_rwnd + stcb->asoc.total_flight; /* Now call the express sack handling */ sctp_express_handle_sack(stcb, cum_ack, a_rwnd, 0, abort_flag); } static void sctp_kick_prsctp_reorder_queue(struct sctp_tcb *stcb, struct sctp_stream_in *strmin) { struct sctp_queued_to_read *ctl, *nctl; struct sctp_association *asoc; int tt; asoc = &stcb->asoc; tt = strmin->last_sequence_delivered; /* * First deliver anything prior to and including the stream no that * came in */ ctl = TAILQ_FIRST(&strmin->inqueue); while (ctl) { nctl = TAILQ_NEXT(ctl, next); if (compare_with_wrap(tt, ctl->sinfo_ssn, MAX_SEQ) || (tt == ctl->sinfo_ssn)) { /* this is deliverable now */ TAILQ_REMOVE(&strmin->inqueue, ctl, next); /* subtract pending on streams */ asoc->size_on_all_streams -= ctl->length; sctp_ucount_decr(asoc->cnt_on_all_streams); /* deliver it to at least the delivery-q */ if (stcb->sctp_socket) { sctp_add_to_readq(stcb->sctp_ep, stcb, ctl, &stcb->sctp_socket->so_rcv, 1); } } else { /* no more delivery now. */ break; } ctl = nctl; } /* * now we must deliver things in queue the normal way if any are * now ready. */ tt = strmin->last_sequence_delivered + 1; ctl = TAILQ_FIRST(&strmin->inqueue); while (ctl) { nctl = TAILQ_NEXT(ctl, next); if (tt == ctl->sinfo_ssn) { /* this is deliverable now */ TAILQ_REMOVE(&strmin->inqueue, ctl, next); /* subtract pending on streams */ asoc->size_on_all_streams -= ctl->length; sctp_ucount_decr(asoc->cnt_on_all_streams); /* deliver it to at least the delivery-q */ strmin->last_sequence_delivered = ctl->sinfo_ssn; if (stcb->sctp_socket) { sctp_add_to_readq(stcb->sctp_ep, stcb, ctl, &stcb->sctp_socket->so_rcv, 1); } tt = strmin->last_sequence_delivered + 1; } else { break; } ctl = nctl; } } void sctp_handle_forward_tsn(struct sctp_tcb *stcb, struct sctp_forward_tsn_chunk *fwd, int *abort_flag) { /* * ISSUES that MUST be fixed for ECN! When we are the sender of the * forward TSN, when the SACK comes back that acknowledges the * FWD-TSN we must reset the NONCE sum to match correctly. This will * get quite tricky since we may have sent more data interveneing * and must carefully account for what the SACK says on the nonce * and any gaps that are reported. This work will NOT be done here, * but I note it here since it is really related to PR-SCTP and * FWD-TSN's */ /* The pr-sctp fwd tsn */ /* * here we will perform all the data receiver side steps for * processing FwdTSN, as required in by pr-sctp draft: * * Assume we get FwdTSN(x): * * 1) update local cumTSN to x 2) try to further advance cumTSN to x + * others we have 3) examine and update re-ordering queue on * pr-in-streams 4) clean up re-assembly queue 5) Send a sack to * report where we are. */ struct sctp_strseq *stseq; struct sctp_association *asoc; uint32_t new_cum_tsn, gap, back_out_htsn; unsigned int i, cnt_gone, fwd_sz, cumack_set_flag, m_size; struct sctp_stream_in *strm; struct sctp_tmit_chunk *chk, *at; cumack_set_flag = 0; asoc = &stcb->asoc; cnt_gone = 0; if ((fwd_sz = ntohs(fwd->ch.chunk_length)) < sizeof(struct sctp_forward_tsn_chunk)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Bad size too small/big fwd-tsn\n"); } #endif return; } m_size = (stcb->asoc.mapping_array_size << 3); /*************************************************************/ /* 1. Here we update local cumTSN and shift the bitmap array */ /*************************************************************/ new_cum_tsn = ntohl(fwd->new_cumulative_tsn); if (compare_with_wrap(asoc->cumulative_tsn, new_cum_tsn, MAX_TSN) || asoc->cumulative_tsn == new_cum_tsn) { /* Already got there ... */ return; } back_out_htsn = asoc->highest_tsn_inside_map; if (compare_with_wrap(new_cum_tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { asoc->highest_tsn_inside_map = new_cum_tsn; #ifdef SCTP_MAP_LOGGING sctp_log_map(0, 0, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif } /* * now we know the new TSN is more advanced, let's find the actual * gap */ if ((compare_with_wrap(new_cum_tsn, asoc->mapping_array_base_tsn, MAX_TSN)) || (new_cum_tsn == asoc->mapping_array_base_tsn)) { gap = new_cum_tsn - asoc->mapping_array_base_tsn; } else { /* try to prevent underflow here */ gap = new_cum_tsn + (MAX_TSN - asoc->mapping_array_base_tsn) + 1; } if (gap > m_size) { asoc->highest_tsn_inside_map = back_out_htsn; if ((long)gap > sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv)) { struct mbuf *oper; /* * out of range (of single byte chunks in the rwnd I * give out). This must be an attacker. */ *abort_flag = 1; oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + 3 * sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + (sizeof(uint32_t) * 3); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_INDATA + SCTP_LOC_33); ippp++; *ippp = asoc->highest_tsn_inside_map; ippp++; *ippp = new_cum_tsn; } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_INDATA + SCTP_LOC_33; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); return; } if (asoc->highest_tsn_inside_map > asoc->mapping_array_base_tsn) { gap = asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn; } else { gap = asoc->highest_tsn_inside_map + (MAX_TSN - asoc->mapping_array_base_tsn) + 1; } cumack_set_flag = 1; } for (i = 0; i <= gap; i++) { SCTP_SET_TSN_PRESENT(asoc->mapping_array, i); } /* * Now after marking all, slide thing forward but no sack please. */ sctp_sack_check(stcb, 0, 0, abort_flag); if (*abort_flag) return; if (cumack_set_flag) { /* * fwd-tsn went outside my gap array - not a common * occurance. Do the same thing we do when a cookie-echo * arrives. */ asoc->highest_tsn_inside_map = new_cum_tsn - 1; asoc->mapping_array_base_tsn = new_cum_tsn; asoc->cumulative_tsn = asoc->highest_tsn_inside_map; #ifdef SCTP_MAP_LOGGING sctp_log_map(0, 3, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif asoc->last_echo_tsn = asoc->highest_tsn_inside_map; } /*************************************************************/ /* 2. Clear up re-assembly queue */ /*************************************************************/ /* * First service it if pd-api is up, just in case we can progress it * forward */ if (asoc->fragmented_delivery_inprogress) { sctp_service_reassembly(stcb, asoc); } if (!TAILQ_EMPTY(&asoc->reasmqueue)) { /* For each one on here see if we need to toss it */ /* * For now large messages held on the reasmqueue that are * complete will be tossed too. We could in theory do more * work to spin through and stop after dumping one msg aka * seeing the start of a new msg at the head, and call the * delivery function... to see if it can be delivered... But * for now we just dump everything on the queue. */ chk = TAILQ_FIRST(&asoc->reasmqueue); while (chk) { at = TAILQ_NEXT(chk, sctp_next); if (compare_with_wrap(asoc->cumulative_tsn, chk->rec.data.TSN_seq, MAX_TSN) || asoc->cumulative_tsn == chk->rec.data.TSN_seq) { /* It needs to be tossed */ TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); if (compare_with_wrap(chk->rec.data.TSN_seq, asoc->tsn_last_delivered, MAX_TSN)) { asoc->tsn_last_delivered = chk->rec.data.TSN_seq; asoc->str_of_pdapi = chk->rec.data.stream_number; asoc->ssn_of_pdapi = chk->rec.data.stream_seq; asoc->fragment_flags = chk->rec.data.rcv_flags; } asoc->size_on_reasm_queue -= chk->send_size; sctp_ucount_decr(asoc->cnt_on_reasm_queue); cnt_gone++; /* Clear up any stream problem */ if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) != SCTP_DATA_UNORDERED && (compare_with_wrap(chk->rec.data.stream_seq, asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered, MAX_SEQ))) { /* * We must dump forward this streams * sequence number if the chunk is * not unordered that is being * skipped. There is a chance that * if the peer does not include the * last fragment in its FWD-TSN we * WILL have a problem here since * you would have a partial chunk in * queue that may not be * deliverable. Also if a Partial * delivery API as started the user * may get a partial chunk. The next * read returning a new chunk... * really ugly but I see no way * around it! Maybe a notify?? */ asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered = chk->rec.data.stream_seq; } if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); } else { /* * Ok we have gone beyond the end of the * fwd-tsn's mark. Some checks... */ if ((asoc->fragmented_delivery_inprogress) && (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG)) { uint32_t str_seq; /* * Special case PD-API is up and * what we fwd-tsn' over includes * one that had the LAST_FRAG. We no * longer need to do the PD-API. */ asoc->fragmented_delivery_inprogress = 0; str_seq = (asoc->str_of_pdapi << 16) | asoc->ssn_of_pdapi; sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION, stcb, SCTP_PARTIAL_DELIVERY_ABORTED, (void *)&str_seq); } break; } chk = at; } } if (asoc->fragmented_delivery_inprogress) { /* * Ok we removed cnt_gone chunks in the PD-API queue that * were being delivered. So now we must turn off the flag. */ uint32_t str_seq; str_seq = (asoc->str_of_pdapi << 16) | asoc->ssn_of_pdapi; sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION, stcb, SCTP_PARTIAL_DELIVERY_ABORTED, (void *)&str_seq); asoc->fragmented_delivery_inprogress = 0; } /*************************************************************/ /* 3. Update the PR-stream re-ordering queues */ /*************************************************************/ stseq = (struct sctp_strseq *)((caddr_t)fwd + sizeof(*fwd)); fwd_sz -= sizeof(*fwd); { /* New method. */ int num_str, i; num_str = fwd_sz / sizeof(struct sctp_strseq); for (i = 0; i < num_str; i++) { uint16_t st; unsigned char *xx; /* Convert */ xx = (unsigned char *)&stseq[i]; st = ntohs(stseq[i].stream); stseq[i].stream = st; st = ntohs(stseq[i].sequence); stseq[i].sequence = st; /* now process */ if (stseq[i].stream > asoc->streamincnt) { /* * It is arguable if we should continue. * Since the peer sent bogus stream info we * may be in deep trouble.. a return may be * a better choice? */ continue; } strm = &asoc->strmin[stseq[i].stream]; if (compare_with_wrap(stseq[i].sequence, strm->last_sequence_delivered, MAX_SEQ)) { /* Update the sequence number */ strm->last_sequence_delivered = stseq[i].sequence; } /* now kick the stream the new way */ sctp_kick_prsctp_reorder_queue(stcb, strm); } } if (TAILQ_FIRST(&asoc->reasmqueue)) { /* now lets kick out and check for more fragmented delivery */ sctp_deliver_reasm_check(stcb, &stcb->asoc); } } diff --git a/sys/netinet/sctp_input.c b/sys/netinet/sctp_input.c index 97ca903cd82b..68b1636c9d27 100644 --- a/sys/netinet/sctp_input.c +++ b/sys/netinet/sctp_input.c @@ -1,5051 +1,5092 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_input.c,v 1.27 2005/03/06 16:04:17 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include static void sctp_stop_all_cookie_timers(struct sctp_tcb *stcb) { struct sctp_nets *net; /* * This now not only stops all cookie timers it also stops any INIT * timers as well. This will make sure that the timers are stopped * in all collision cases. */ SCTP_TCB_LOCK_ASSERT(stcb); TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->rxt_timer.type == SCTP_TIMER_TYPE_COOKIE) { sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_1); } else if (net->rxt_timer.type == SCTP_TIMER_TYPE_INIT) { sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_2); } } } /* INIT handler */ static void sctp_handle_init(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_init_chunk *cp, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_no_unlock, uint32_t vrf_id, uint32_t table_id) { struct sctp_init *init; struct mbuf *op_err; uint32_t init_limit; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_init: handling INIT tcb:%p\n", stcb); } #endif op_err = NULL; init = &cp->init; /* First are we accepting? */ if ((inp->sctp_socket->so_qlimit == 0) && (stcb == NULL)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_init: Abort, so_qlimit:%d\n", inp->sctp_socket->so_qlimit); } #endif /* * FIX ME ?? What about TCP model and we have a * match/restart case? */ sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_chunk)) { /* Invalid length */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } /* validate parameters */ if (init->initiate_tag == 0) { /* protocol error... send abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } if (ntohl(init->a_rwnd) < SCTP_MIN_RWND) { /* invalid parameter... send abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); return; } if (init->num_inbound_streams == 0) { /* protocol error... send abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } if (init->num_outbound_streams == 0) { /* protocol error... send abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(inp, stcb, m, iphlen, sh, op_err, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } init_limit = offset + ntohs(cp->ch.chunk_length); if (sctp_validate_init_auth_params(m, offset + sizeof(*cp), init_limit)) { /* auth parameter(s) error... send abort */ sctp_abort_association(inp, stcb, m, iphlen, sh, NULL, vrf_id, table_id); if (stcb) *abort_no_unlock = 1; return; } /* send an INIT-ACK w/cookie */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("sctp_handle_init: sending INIT-ACK\n"); } #endif sctp_send_initiate_ack(inp, stcb, m, iphlen, offset, sh, cp, vrf_id, table_id); } /* * process peer "INIT/INIT-ACK" chunk returns value < 0 on error */ static int sctp_process_init(struct sctp_init_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_init *init; struct sctp_association *asoc; struct sctp_nets *lnet; unsigned int i; init = &cp->init; asoc = &stcb->asoc; /* save off parameters */ asoc->peer_vtag = ntohl(init->initiate_tag); asoc->peers_rwnd = ntohl(init->a_rwnd); if (TAILQ_FIRST(&asoc->nets)) { /* update any ssthresh's that may have a default */ TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) { lnet->ssthresh = asoc->peers_rwnd; #if defined(SCTP_CWND_MONITOR) || defined(SCTP_CWND_LOGGING) sctp_log_cwnd(stcb, lnet, 0, SCTP_CWND_INITIALIZATION); #endif } } SCTP_TCB_SEND_LOCK(stcb); if (asoc->pre_open_streams > ntohs(init->num_inbound_streams)) { unsigned int newcnt; struct sctp_stream_out *outs; struct sctp_stream_queue_pending *sp; /* cut back on number of streams */ newcnt = ntohs(init->num_inbound_streams); /* This if is probably not needed but I am cautious */ if (asoc->strmout) { /* First make sure no data chunks are trapped */ for (i = newcnt; i < asoc->pre_open_streams; i++) { outs = &asoc->strmout[i]; sp = TAILQ_FIRST(&outs->outqueue); while (sp) { TAILQ_REMOVE(&outs->outqueue, sp, next); asoc->stream_queue_cnt--; sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb, SCTP_NOTIFY_DATAGRAM_UNSENT, sp); if (sp->data) { sctp_m_freem(sp->data); sp->data = NULL; } sctp_free_remote_addr(sp->net); sp->net = NULL; /* Free the chunk */ printf("sp:%p tcb:%p weird free case\n", sp, stcb); sctp_free_a_strmoq(stcb, sp); sp = TAILQ_FIRST(&outs->outqueue); } } } /* cut back the count and abandon the upper streams */ asoc->pre_open_streams = newcnt; } SCTP_TCB_SEND_UNLOCK(stcb); asoc->streamoutcnt = asoc->pre_open_streams; /* init tsn's */ asoc->highest_tsn_inside_map = asoc->asconf_seq_in = ntohl(init->initial_tsn) - 1; #ifdef SCTP_MAP_LOGGING sctp_log_map(0, 5, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); #endif /* This is the next one we expect */ asoc->str_reset_seq_in = asoc->asconf_seq_in + 1; asoc->mapping_array_base_tsn = ntohl(init->initial_tsn); asoc->cumulative_tsn = asoc->asconf_seq_in; asoc->last_echo_tsn = asoc->asconf_seq_in; asoc->advanced_peer_ack_point = asoc->last_acked_seq; /* open the requested streams */ if (asoc->strmin != NULL) { /* Free the old ones */ struct sctp_queued_to_read *ctl; for (i = 0; i < asoc->streamincnt; i++) { ctl = TAILQ_FIRST(&asoc->strmin[i].inqueue); while (ctl) { TAILQ_REMOVE(&asoc->strmin[i].inqueue, ctl, next); sctp_free_remote_addr(ctl->whoFrom); sctp_m_freem(ctl->data); ctl->data = NULL; sctp_free_a_readq(stcb, ctl); ctl = TAILQ_FIRST(&asoc->strmin[i].inqueue); } } SCTP_FREE(asoc->strmin); } asoc->streamincnt = ntohs(init->num_outbound_streams); if (asoc->streamincnt > MAX_SCTP_STREAMS) { asoc->streamincnt = MAX_SCTP_STREAMS; } SCTP_MALLOC(asoc->strmin, struct sctp_stream_in *, asoc->streamincnt * sizeof(struct sctp_stream_in), "StreamsIn"); if (asoc->strmin == NULL) { /* we didn't get memory for the streams! */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("process_init: couldn't get memory for the streams!\n"); } #endif return (-1); } for (i = 0; i < asoc->streamincnt; i++) { asoc->strmin[i].stream_no = i; asoc->strmin[i].last_sequence_delivered = 0xffff; /* * U-stream ranges will be set when the cookie is unpacked. * Or for the INIT sender they are un set (if pr-sctp not * supported) when the INIT-ACK arrives. */ TAILQ_INIT(&asoc->strmin[i].inqueue); asoc->strmin[i].delivery_started = 0; } /* * load_address_from_init will put the addresses into the * association when the COOKIE is processed or the INIT-ACK is * processed. Both types of COOKIE's existing and new call this * routine. It will remove addresses that are no longer in the * association (for the restarting case where addresses are * removed). Up front when the INIT arrives we will discard it if it * is a restart and new addresses have been added. */ return (0); } /* * INIT-ACK message processing/consumption returns value < 0 on error */ static int sctp_process_init_ack(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_no_unlock, uint32_t vrf_id, uint32_t table_id) { struct sctp_association *asoc; struct mbuf *op_err; int retval, abort_flag; uint32_t initack_limit; /* First verify that we have no illegal param's */ abort_flag = 0; op_err = NULL; op_err = sctp_arethere_unrecognized_parameters(m, (offset + sizeof(struct sctp_init_chunk)), &abort_flag, (struct sctp_chunkhdr *)cp); if (abort_flag) { /* Send an abort and notify peer */ if (op_err != NULL) { sctp_send_operr_to(m, iphlen, op_err, cp->init.initiate_tag, vrf_id, table_id); } else { /* * Just notify (abort_assoc does this if we send an * abort). */ sctp_abort_notification(stcb, 0); /* * No sense in further INIT's since we will get the * same param back */ sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_3); *abort_no_unlock = 1; } return (-1); } asoc = &stcb->asoc; /* process the peer's parameters in the INIT-ACK */ retval = sctp_process_init((struct sctp_init_chunk *)cp, stcb, net); if (retval < 0) { return (retval); } initack_limit = offset + ntohs(cp->ch.chunk_length); /* load all addresses */ if ((retval = sctp_load_addresses_from_init(stcb, m, iphlen, (offset + sizeof(struct sctp_init_chunk)), initack_limit, sh, NULL))) { /* Huh, we should abort */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Load addresses from INIT causes an abort %d\n", retval); } #endif sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, NULL, 0, 0); *abort_no_unlock = 1; return (-1); } stcb->asoc.peer_hmac_id = sctp_negotiate_hmacid(stcb->asoc.peer_hmacs, stcb->asoc.local_hmacs); if (op_err) { sctp_queue_op_err(stcb, op_err); /* queuing will steal away the mbuf chain to the out queue */ op_err = NULL; } /* extract the cookie and queue it to "echo" it back... */ stcb->asoc.overall_error_count = 0; net->error_count = 0; /* * Cancel the INIT timer, We do this first before queueing the * cookie. We always cancel at the primary to assue that we are * canceling the timer started by the INIT which always goes to the * primary. */ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, stcb, asoc->primary_destination, SCTP_FROM_SCTP_INPUT + SCTP_LOC_4); /* calculate the RTO */ net->RTO = sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered); retval = sctp_send_cookie_echo(m, offset, stcb, net); if (retval < 0) { /* * No cookie, we probably should send a op error. But in any * case if there is no cookie in the INIT-ACK, we can * abandon the peer, its broke. */ if (retval == -3) { /* We abort with an error of missing mandatory param */ struct mbuf *op_err; op_err = sctp_generate_invmanparam(SCTP_CAUSE_MISSING_PARAM); if (op_err) { /* * Expand beyond to include the mandatory * param cookie */ struct sctp_inv_mandatory_param *mp; SCTP_BUF_LEN(op_err) = sizeof(struct sctp_inv_mandatory_param); mp = mtod(op_err, struct sctp_inv_mandatory_param *); /* Subtract the reserved param */ mp->length = htons(sizeof(struct sctp_inv_mandatory_param) - 2); mp->num_param = htonl(1); mp->param = htons(SCTP_STATE_COOKIE); mp->resv = 0; } sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; } return (retval); } return (0); } static void sctp_handle_heartbeat_ack(struct sctp_heartbeat_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sockaddr_storage store; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sctp_nets *r_net; struct timeval tv; if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_heartbeat_chunk)) { /* Invalid length */ return; } sin = (struct sockaddr_in *)&store; sin6 = (struct sockaddr_in6 *)&store; memset(&store, 0, sizeof(store)); if (cp->heartbeat.hb_info.addr_family == AF_INET && cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in)) { sin->sin_family = cp->heartbeat.hb_info.addr_family; sin->sin_len = cp->heartbeat.hb_info.addr_len; sin->sin_port = stcb->rport; memcpy(&sin->sin_addr, cp->heartbeat.hb_info.address, sizeof(sin->sin_addr)); } else if (cp->heartbeat.hb_info.addr_family == AF_INET6 && cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in6)) { sin6->sin6_family = cp->heartbeat.hb_info.addr_family; sin6->sin6_len = cp->heartbeat.hb_info.addr_len; sin6->sin6_port = stcb->rport; memcpy(&sin6->sin6_addr, cp->heartbeat.hb_info.address, sizeof(sin6->sin6_addr)); } else { return; } r_net = sctp_findnet(stcb, (struct sockaddr *)sin); if (r_net == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Huh? I can't find the address I sent it to, discard\n"); } #endif return; } if ((r_net && (r_net->dest_state & SCTP_ADDR_UNCONFIRMED)) && (r_net->heartbeat_random1 == cp->heartbeat.hb_info.random_value1) && (r_net->heartbeat_random2 == cp->heartbeat.hb_info.random_value2)) { /* * If the its a HB and it's random value is correct when can * confirm the destination. */ r_net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; if (r_net->dest_state & SCTP_ADDR_REQ_PRIMARY) { stcb->asoc.primary_destination = r_net; r_net->dest_state &= ~SCTP_ADDR_WAS_PRIMARY; r_net->dest_state &= ~SCTP_ADDR_REQ_PRIMARY; r_net = TAILQ_FIRST(&stcb->asoc.nets); if (r_net != stcb->asoc.primary_destination) { /* * first one on the list is NOT the primary * sctp_cmpaddr() is much more efficent if * the primary is the first on the list, * make it so. */ TAILQ_REMOVE(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); TAILQ_INSERT_HEAD(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); } } sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, stcb, 0, (void *)r_net); } r_net->error_count = 0; r_net->hb_responded = 1; tv.tv_sec = cp->heartbeat.hb_info.time_value_1; tv.tv_usec = cp->heartbeat.hb_info.time_value_2; if (r_net->dest_state & SCTP_ADDR_NOT_REACHABLE) { r_net->dest_state &= ~SCTP_ADDR_NOT_REACHABLE; r_net->dest_state |= SCTP_ADDR_REACHABLE; sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, SCTP_HEARTBEAT_SUCCESS, (void *)r_net); /* now was it the primary? if so restore */ if (r_net->dest_state & SCTP_ADDR_WAS_PRIMARY) { sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, r_net); } } /* Now lets do a RTO with this */ r_net->RTO = sctp_calculate_rto(stcb, &stcb->asoc, r_net, &tv); } static void sctp_handle_abort(struct sctp_abort_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_abort: handling ABORT\n"); } #endif if (stcb == NULL) return; /* verify that the destination addr is in the association */ /* ignore abort for addresses being deleted */ /* stop any receive timers */ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_5); /* notify user of the abort and clean up... */ sctp_abort_notification(stcb, 0); /* free the tcb */ SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } #ifdef SCTP_ASOCLOG_OF_TSNS sctp_print_out_track_log(stcb); #endif sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_6); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_abort: finished\n"); } #endif } static void sctp_handle_shutdown(struct sctp_shutdown_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_flag) { struct sctp_association *asoc; int some_on_streamwheel; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_shutdown: handling SHUTDOWN\n"); } #endif if (stcb == NULL) return; asoc = &stcb->asoc; if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) { return; } if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_shutdown_chunk)) { /* Shutdown NOT the expected size */ return; } else { sctp_update_acked(stcb, cp, net, abort_flag); } if (asoc->control_pdapi) { /* * With a normal shutdown we assume the end of last record. */ SCTP_INP_READ_LOCK(stcb->sctp_ep); asoc->control_pdapi->end_added = 1; asoc->control_pdapi->pdapi_aborted = 1; asoc->control_pdapi = NULL; SCTP_INP_READ_UNLOCK(stcb->sctp_ep); sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); } /* goto SHUTDOWN_RECEIVED state to block new requests */ if (stcb->sctp_socket) { if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT)) { asoc->state = SCTP_STATE_SHUTDOWN_RECEIVED; /* * notify upper layer that peer has initiated a * shutdown */ sctp_ulp_notify(SCTP_NOTIFY_PEER_SHUTDOWN, stcb, 0, NULL); /* reset time */ - SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered); } } if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { /* * stop the shutdown timer, since we WILL move to * SHUTDOWN-ACK-SENT. */ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_7); } /* Now are we there yet? */ some_on_streamwheel = 0; if (!TAILQ_EMPTY(&asoc->out_wheel)) { /* Check to see if some data queued */ struct sctp_stream_out *outs; TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { if (!TAILQ_EMPTY(&outs->outqueue)) { some_on_streamwheel = 1; break; } } } if (!TAILQ_EMPTY(&asoc->send_queue) || !TAILQ_EMPTY(&asoc->sent_queue) || some_on_streamwheel) { /* By returning we will push more data out */ return; } else { /* no outstanding data to send, so move on... */ /* send SHUTDOWN-ACK */ sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); /* move to SHUTDOWN-ACK-SENT state */ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_ACK_SENT; /* start SHUTDOWN timer */ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep, stcb, net); } } static void sctp_handle_shutdown_ack(struct sctp_shutdown_ack_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_association *asoc; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_shutdown_ack: handling SHUTDOWN ACK\n"); } #endif if (stcb == NULL) return; asoc = &stcb->asoc; /* process according to association state */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* unexpected SHUTDOWN-ACK... so ignore... */ SCTP_TCB_UNLOCK(stcb); return; } if (asoc->control_pdapi) { /* * With a normal shutdown we assume the end of last record. */ SCTP_INP_READ_LOCK(stcb->sctp_ep); asoc->control_pdapi->end_added = 1; asoc->control_pdapi->pdapi_aborted = 1; asoc->control_pdapi = NULL; SCTP_INP_READ_UNLOCK(stcb->sctp_ep); sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); } /* are the queues empty? */ if (!TAILQ_EMPTY(&asoc->send_queue) || !TAILQ_EMPTY(&asoc->sent_queue) || !TAILQ_EMPTY(&asoc->out_wheel)) { sctp_report_all_outbound(stcb, 0); } /* stop the timer */ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_8); /* send SHUTDOWN-COMPLETE */ sctp_send_shutdown_complete(stcb, net); /* notify upper layer protocol */ if (stcb->sctp_socket) { sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL); if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* Set the connected flag to disconnected */ stcb->sctp_ep->sctp_socket->so_snd.sb_cc = 0; } } SCTP_STAT_INCR_COUNTER32(sctps_shutdown); /* free the TCB but first save off the ep */ sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_9); } /* * Skip past the param header and then we will find the chunk that caused the * problem. There are two possiblities ASCONF or FWD-TSN other than that and * our peer must be broken. */ static void sctp_process_unrecog_chunk(struct sctp_tcb *stcb, struct sctp_paramhdr *phdr, struct sctp_nets *net) { struct sctp_chunkhdr *chk; chk = (struct sctp_chunkhdr *)((caddr_t)phdr + sizeof(*phdr)); switch (chk->chunk_type) { case SCTP_ASCONF_ACK: case SCTP_ASCONF: sctp_asconf_cleanup(stcb, net); break; case SCTP_FORWARD_CUM_TSN: stcb->asoc.peer_supports_prsctp = 0; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("Peer does not support chunk type %d(%x)??\n", chk->chunk_type, (uint32_t) chk->chunk_type); } #endif break; } } /* * Skip past the param header and then we will find the param that caused the * problem. There are a number of param's in a ASCONF OR the prsctp param * these will turn of specific features. */ static void sctp_process_unrecog_param(struct sctp_tcb *stcb, struct sctp_paramhdr *phdr) { struct sctp_paramhdr *pbad; pbad = phdr + 1; switch (ntohs(pbad->param_type)) { /* pr-sctp draft */ case SCTP_PRSCTP_SUPPORTED: stcb->asoc.peer_supports_prsctp = 0; break; case SCTP_SUPPORTED_CHUNK_EXT: break; /* draft-ietf-tsvwg-addip-sctp */ case SCTP_ECN_NONCE_SUPPORTED: stcb->asoc.peer_supports_ecn_nonce = 0; stcb->asoc.ecn_nonce_allowed = 0; stcb->asoc.ecn_allowed = 0; break; case SCTP_ADD_IP_ADDRESS: case SCTP_DEL_IP_ADDRESS: case SCTP_SET_PRIM_ADDR: stcb->asoc.peer_supports_asconf = 0; break; case SCTP_SUCCESS_REPORT: case SCTP_ERROR_CAUSE_IND: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("Huh, the peer does not support success? or error cause?\n"); printf("Turning off ASCONF to this strange peer\n"); } #endif stcb->asoc.peer_supports_asconf = 0; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("Peer does not support param type %d(%x)??\n", pbad->param_type, (uint32_t) pbad->param_type); } #endif break; } } static int sctp_handle_error(struct sctp_chunkhdr *ch, struct sctp_tcb *stcb, struct sctp_nets *net) { int chklen; struct sctp_paramhdr *phdr; uint16_t error_type; uint16_t error_len; struct sctp_association *asoc; int adjust; /* parse through all of the errors and process */ asoc = &stcb->asoc; phdr = (struct sctp_paramhdr *)((caddr_t)ch + sizeof(struct sctp_chunkhdr)); chklen = ntohs(ch->chunk_length) - sizeof(struct sctp_chunkhdr); while ((size_t)chklen >= sizeof(struct sctp_paramhdr)) { /* Process an Error Cause */ error_type = ntohs(phdr->param_type); error_len = ntohs(phdr->param_length); if ((error_len > chklen) || (error_len == 0)) { /* invalid param length for this param */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Bogus length in error param- chunk left:%d errorlen:%d\n", chklen, error_len); } #endif /* SCTP_DEBUG */ return (0); } switch (error_type) { case SCTP_CAUSE_INVALID_STREAM: case SCTP_CAUSE_MISSING_PARAM: case SCTP_CAUSE_INVALID_PARAM: case SCTP_CAUSE_NO_USER_DATA: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Software error we got a %d back? We have a bug :/ (or do they?)\n", error_type); } #endif break; case SCTP_CAUSE_STALE_COOKIE: /* * We only act if we have echoed a cookie and are * waiting. */ if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) { int *p; p = (int *)((caddr_t)phdr + sizeof(*phdr)); /* Save the time doubled */ asoc->cookie_preserve_req = ntohl(*p) << 1; asoc->stale_cookie_count++; if (asoc->stale_cookie_count > asoc->max_init_times) { sctp_abort_notification(stcb, 0); /* now free the asoc */ sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_10); return (-1); } /* blast back to INIT state */ asoc->state &= ~SCTP_STATE_COOKIE_ECHOED; asoc->state |= SCTP_STATE_COOKIE_WAIT; sctp_stop_all_cookie_timers(stcb); sctp_send_initiate(stcb->sctp_ep, stcb); } break; case SCTP_CAUSE_UNRESOLVABLE_ADDR: /* * Nothing we can do here, we don't do hostname * addresses so if the peer does not like my IPv6 * (or IPv4 for that matter) it does not matter. If * they don't support that type of address, they can * NOT possibly get that packet type... i.e. with no * IPv6 you can't recieve a IPv6 packet. so we can * safely ignore this one. If we ever added support * for HOSTNAME Addresses, then we would need to do * something here. */ break; case SCTP_CAUSE_UNRECOG_CHUNK: sctp_process_unrecog_chunk(stcb, phdr, net); break; case SCTP_CAUSE_UNRECOG_PARAM: sctp_process_unrecog_param(stcb, phdr); break; case SCTP_CAUSE_COOKIE_IN_SHUTDOWN: /* * We ignore this since the timer will drive out a * new cookie anyway and there timer will drive us * to send a SHUTDOWN_COMPLETE. We can't send one * here since we don't have their tag. */ break; case SCTP_CAUSE_DELETING_LAST_ADDR: case SCTP_CAUSE_RESOURCE_SHORTAGE: case SCTP_CAUSE_DELETING_SRC_ADDR: /* * We should NOT get these here, but in a * ASCONF-ACK. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("Peer sends ASCONF errors in a Operational Error?<%d>?\n", error_type); } #endif break; case SCTP_CAUSE_OUT_OF_RESC: /* * And what, pray tell do we do with the fact that * the peer is out of resources? Not really sure we * could do anything but abort. I suspect this * should have came WITH an abort instead of in a * OP-ERROR. */ break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { /* don't know what this error cause is... */ printf("sctp_handle_error: unknown error type = 0x%xh\n", error_type); } #endif /* SCTP_DEBUG */ break; } adjust = SCTP_SIZE32(error_len); chklen -= adjust; phdr = (struct sctp_paramhdr *)((caddr_t)phdr + adjust); } return (0); } static int sctp_handle_init_ack(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_no_unlock, uint32_t vrf_id, uint32_t table_id) { struct sctp_init_ack *init_ack; int *state; struct mbuf *op_err; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_init_ack: handling INIT-ACK\n"); } #endif if (stcb == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_init_ack: TCB is null\n"); } #endif return (-1); } if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_ack_chunk)) { /* Invalid length */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; return (-1); } init_ack = &cp->init; /* validate parameters */ if (init_ack->initiate_tag == 0) { /* protocol error... send an abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; return (-1); } if (ntohl(init_ack->a_rwnd) < SCTP_MIN_RWND) { /* protocol error... send an abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; return (-1); } if (init_ack->num_inbound_streams == 0) { /* protocol error... send an abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; return (-1); } if (init_ack->num_outbound_streams == 0) { /* protocol error... send an abort */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, op_err, 0, 0); *abort_no_unlock = 1; return (-1); } /* process according to association state... */ state = &stcb->asoc.state; switch (*state & SCTP_STATE_MASK) { case SCTP_STATE_COOKIE_WAIT: /* this is the expected state for this chunk */ /* process the INIT-ACK parameters */ if (stcb->asoc.primary_destination->dest_state & SCTP_ADDR_UNCONFIRMED) { /* * The primary is where we sent the INIT, we can * always consider it confirmed when the INIT-ACK is * returned. Do this before we load addresses * though. */ stcb->asoc.primary_destination->dest_state &= ~SCTP_ADDR_UNCONFIRMED; sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, stcb, 0, (void *)stcb->asoc.primary_destination); } if (sctp_process_init_ack(m, iphlen, offset, sh, cp, stcb, net, abort_no_unlock, vrf_id, table_id) < 0) { /* error in parsing parameters */ return (-1); } /* update our state */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("moving to COOKIE-ECHOED state\n"); } #endif if (*state & SCTP_STATE_SHUTDOWN_PENDING) { *state = SCTP_STATE_COOKIE_ECHOED | SCTP_STATE_SHUTDOWN_PENDING; } else { *state = SCTP_STATE_COOKIE_ECHOED; } /* reset the RTO calc */ stcb->asoc.overall_error_count = 0; - SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); /* * collapse the init timer back in case of a exponential * backoff */ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep, stcb, net); /* * the send at the end of the inbound data processing will * cause the cookie to be sent */ break; case SCTP_STATE_SHUTDOWN_SENT: /* incorrect state... discard */ break; case SCTP_STATE_COOKIE_ECHOED: /* incorrect state... discard */ break; case SCTP_STATE_OPEN: /* incorrect state... discard */ break; case SCTP_STATE_EMPTY: case SCTP_STATE_INUSE: default: /* incorrect state... discard */ return (-1); break; } /* end switch asoc state */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Leaving handle-init-ack end\n"); } #endif return (0); } /* * handle a state cookie for an existing association m: input packet mbuf * chain-- assumes a pullup on IP/SCTP/COOKIE-ECHO chunk note: this is a * "split" mbuf and the cookie signature does not exist offset: offset into * mbuf to the cookie-echo chunk */ static struct sctp_tcb * sctp_process_cookie_existing(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, struct sockaddr *init_src, int *notification, sctp_assoc_t * sac_assoc_id, uint32_t vrf_id, uint32_t table_id) { struct sctp_association *asoc; struct sctp_init_chunk *init_cp, init_buf; struct sctp_init_ack_chunk *initack_cp, initack_buf; int chk_length; int init_offset, initack_offset, i; int retval; int spec_flag = 0; int how_indx; /* I know that the TCB is non-NULL from the caller */ asoc = &stcb->asoc; for (how_indx = 0; how_indx < sizeof(asoc->cookie_how); how_indx++) { if (asoc->cookie_how[how_indx] == 0) break; } if (how_indx < sizeof(asoc->cookie_how)) { asoc->cookie_how[how_indx] = 1; } if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) { /* SHUTDOWN came in after sending INIT-ACK */ struct mbuf *op_err; struct sctp_paramhdr *ph; sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (op_err == NULL) { /* FOOBAR */ return (NULL); } /* pre-reserve some space */ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr)); /* Set the len */ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_paramhdr); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_COOKIE_IN_SHUTDOWN); ph->param_length = htons(sizeof(struct sctp_paramhdr)); sctp_send_operr_to(m, iphlen, op_err, cookie->peers_vtag, vrf_id, table_id); if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 2; return (NULL); } /* * find and validate the INIT chunk in the cookie (peer's info) the * INIT should start after the cookie-echo header struct (chunk * header, state cookie header struct) */ init_offset = offset += sizeof(struct sctp_cookie_echo_chunk); init_cp = (struct sctp_init_chunk *) sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk), (uint8_t *) & init_buf); if (init_cp == NULL) { /* could not pull a INIT chunk in cookie */ return (NULL); } chk_length = ntohs(init_cp->ch.chunk_length); if (init_cp->ch.chunk_type != SCTP_INITIATION) { return (NULL); } /* * find and validate the INIT-ACK chunk in the cookie (my info) the * INIT-ACK follows the INIT chunk */ initack_offset = init_offset + SCTP_SIZE32(chk_length); initack_cp = (struct sctp_init_ack_chunk *) sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk), (uint8_t *) & initack_buf); if (initack_cp == NULL) { /* could not pull INIT-ACK chunk in cookie */ return (NULL); } chk_length = ntohs(initack_cp->ch.chunk_length); if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) { return (NULL); } if ((ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag) && (ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag)) { /* * case D in Section 5.2.4 Table 2: MMAA process accordingly * to get into the OPEN state */ if (ntohl(initack_cp->init.initial_tsn) != asoc->init_seq_number) { #ifdef INVARIANTS panic("Case D and non-match seq?"); #else printf("Case D, seq non-match %x vs %x?\n", ntohl(initack_cp->init.initial_tsn), asoc->init_seq_number); #endif } switch SCTP_GET_STATE (asoc) { case SCTP_STATE_COOKIE_WAIT: case SCTP_STATE_COOKIE_ECHOED: /* * INIT was sent but got a COOKIE_ECHO with the * correct tags... just accept it...but we must * process the init so that we can make sure we have * the right seq no's. */ /* First we must process the INIT !! */ retval = sctp_process_init(init_cp, stcb, net); if (retval < 0) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 3; return (NULL); } /* we have already processed the INIT so no problem */ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_11); sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_12); /* update current state */ if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) SCTP_STAT_INCR_COUNTER32(sctps_activeestab); else SCTP_STAT_INCR_COUNTER32(sctps_collisionestab); if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } else { /* if ok, move to OPEN state */ asoc->state = SCTP_STATE_OPEN; } SCTP_STAT_INCR_GAUGE32(sctps_currestab); sctp_stop_all_cookie_timers(stcb); if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && (inp->sctp_socket->so_qlimit == 0) ) { /* * Here is where collision would go if we * did a connect() and instead got a * init/init-ack/cookie done before the * init-ack came back.. */ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; soisconnected(stcb->sctp_ep->sctp_socket); } /* notify upper layer */ *notification = SCTP_NOTIFY_ASSOC_UP; /* * since we did not send a HB make sure we don't * double things */ net->hb_responded = 1; if (stcb->asoc.sctp_autoclose_ticks && (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE))) { sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL); } break; default: /* * we're in the OPEN state (or beyond), so peer must * have simply lost the COOKIE-ACK */ break; } /* end switch */ sctp_stop_all_cookie_timers(stcb); /* * We ignore the return code here.. not sure if we should * somehow abort.. but we do have an existing asoc. This * really should not fail. */ if (sctp_load_addresses_from_init(stcb, m, iphlen, init_offset + sizeof(struct sctp_init_chunk), initack_offset, sh, init_src)) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 4; return (NULL); } /* respond with a COOKIE-ACK */ sctp_toss_old_cookies(stcb, asoc); sctp_send_cookie_ack(stcb); if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 5; return (stcb); } if (ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag && ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag && cookie->tie_tag_my_vtag == 0 && cookie->tie_tag_peer_vtag == 0) { /* * case C in Section 5.2.4 Table 2: XMOO silently discard */ if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 6; return (NULL); } if (ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag && (ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag || init_cp->init.initiate_tag == 0)) { /* * case B in Section 5.2.4 Table 2: MXAA or MOAA my info * should be ok, re-accept peer info */ if (ntohl(initack_cp->init.initial_tsn) != asoc->init_seq_number) { /* * Extension of case C. If we hit this, then the * random number generator returned the same vtag * when we first sent our INIT-ACK and when we later * sent our INIT. The side with the seq numbers that * are different will be the one that normnally * would have hit case C. This in effect "extends" * our vtags in this collision case to be 64 bits. * The same collision could occur aka you get both * vtag and seq number the same twice in a row.. but * is much less likely. If it did happen then we * would proceed through and bring up the assoc.. we * may end up with the wrong stream setup however.. * which would be bad.. but there is no way to * tell.. until we send on a stream that does not * exist :-) */ if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 7; return (NULL); } if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 8; sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_13); sctp_stop_all_cookie_timers(stcb); /* * since we did not send a HB make sure we don't double * things */ net->hb_responded = 1; if (stcb->asoc.sctp_autoclose_ticks && sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) { sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL); } asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); asoc->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams); /* Note last_cwr_tsn? where is this used? */ asoc->last_cwr_tsn = asoc->init_seq_number - 1; if (ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) { /* * Ok the peer probably discarded our data (if we * echoed a cookie+data). So anything on the * sent_queue should be marked for retransmit, we * may not get something to kick us so it COULD * still take a timeout to move these.. but it can't * hurt to mark them. */ struct sctp_tmit_chunk *chk; TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if (chk->sent < SCTP_DATAGRAM_RESEND) { chk->sent = SCTP_DATAGRAM_RESEND; sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); spec_flag++; } } } /* process the INIT info (peer's info) */ retval = sctp_process_init(init_cp, stcb, net); if (retval < 0) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 9; return (NULL); } if (sctp_load_addresses_from_init(stcb, m, iphlen, init_offset + sizeof(struct sctp_init_chunk), initack_offset, sh, init_src)) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 10; return (NULL); } if ((asoc->state & SCTP_STATE_COOKIE_WAIT) || (asoc->state & SCTP_STATE_COOKIE_ECHOED)) { *notification = SCTP_NOTIFY_ASSOC_UP; if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && (inp->sctp_socket->so_qlimit == 0)) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; soisconnected(stcb->sctp_ep->sctp_socket); } if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) SCTP_STAT_INCR_COUNTER32(sctps_activeestab); else SCTP_STAT_INCR_COUNTER32(sctps_collisionestab); SCTP_STAT_INCR_COUNTER32(sctps_activeestab); SCTP_STAT_INCR_GAUGE32(sctps_currestab); } else if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) { SCTP_STAT_INCR_COUNTER32(sctps_restartestab); } else { SCTP_STAT_INCR_COUNTER32(sctps_collisionestab); } if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } else { asoc->state = SCTP_STATE_OPEN; } sctp_stop_all_cookie_timers(stcb); sctp_toss_old_cookies(stcb, asoc); sctp_send_cookie_ack(stcb); if (spec_flag) { /* * only if we have retrans set do we do this. What * this call does is get only the COOKIE-ACK out and * then when we return the normal call to * sctp_chunk_output will get the retrans out behind * this. */ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_COOKIE_ACK); } if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 11; return (stcb); } if ((ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag && ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) && cookie->tie_tag_my_vtag == asoc->my_vtag_nonce && cookie->tie_tag_peer_vtag == asoc->peer_vtag_nonce && cookie->tie_tag_peer_vtag != 0) { struct sctpasochead *head; /* * case A in Section 5.2.4 Table 2: XXMM (peer restarted) */ /* temp code */ if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 12; sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_14); sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_15); *sac_assoc_id = sctp_get_associd(stcb); /* notify upper layer */ *notification = SCTP_NOTIFY_ASSOC_RESTART; atomic_add_int(&stcb->asoc.refcnt, 1); if ((SCTP_GET_STATE(asoc) != SCTP_STATE_OPEN) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT)) { SCTP_STAT_INCR_GAUGE32(sctps_currestab); } if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) { SCTP_STAT_INCR_GAUGE32(sctps_restartestab); } else if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) { SCTP_STAT_INCR_GAUGE32(sctps_collisionestab); } if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } else if (!(asoc->state & SCTP_STATE_SHUTDOWN_SENT)) { /* move to OPEN state, if not in SHUTDOWN_SENT */ asoc->state = SCTP_STATE_OPEN; } asoc->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams); asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn); asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number; asoc->last_cwr_tsn = asoc->init_seq_number - 1; asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1; asoc->str_reset_seq_in = asoc->init_seq_number; asoc->advanced_peer_ack_point = asoc->last_acked_seq; if (asoc->mapping_array) memset(asoc->mapping_array, 0, asoc->mapping_array_size); SCTP_TCB_UNLOCK(stcb); SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(stcb->sctp_ep); SCTP_TCB_LOCK(stcb); atomic_add_int(&stcb->asoc.refcnt, -1); /* send up all the data */ SCTP_TCB_SEND_LOCK(stcb); sctp_report_all_outbound(stcb, 1); for (i = 0; i < stcb->asoc.streamoutcnt; i++) { stcb->asoc.strmout[i].stream_no = i; stcb->asoc.strmout[i].next_sequence_sent = 0; stcb->asoc.strmout[i].last_msg_incomplete = 0; } /* process the INIT-ACK info (my info) */ asoc->my_vtag = ntohl(initack_cp->init.initiate_tag); asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); /* pull from vtag hash */ LIST_REMOVE(stcb, sctp_asocs); /* re-insert to new vtag position */ head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, sctppcbinfo.hashasocmark)]; /* * put it in the bucket in the vtag hash of assoc's for the * system */ LIST_INSERT_HEAD(head, stcb, sctp_asocs); /* Is this the first restart? */ if (stcb->asoc.in_restart_hash == 0) { /* Ok add it to assoc_id vtag hash */ head = &sctppcbinfo.sctp_restarthash[SCTP_PCBHASH_ASOC(stcb->asoc.assoc_id, sctppcbinfo.hashrestartmark)]; LIST_INSERT_HEAD(head, stcb, sctp_tcbrestarhash); stcb->asoc.in_restart_hash = 1; } /* process the INIT info (peer's info) */ SCTP_TCB_SEND_UNLOCK(stcb); SCTP_INP_WUNLOCK(stcb->sctp_ep); SCTP_INP_INFO_WUNLOCK(); retval = sctp_process_init(init_cp, stcb, net); if (retval < 0) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 13; return (NULL); } /* * since we did not send a HB make sure we don't double * things */ net->hb_responded = 1; if (sctp_load_addresses_from_init(stcb, m, iphlen, init_offset + sizeof(struct sctp_init_chunk), initack_offset, sh, init_src)) { if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 14; return (NULL); } /* respond with a COOKIE-ACK */ sctp_stop_all_cookie_timers(stcb); sctp_toss_old_cookies(stcb, asoc); sctp_send_cookie_ack(stcb); if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 15; return (stcb); } if (how_indx < sizeof(asoc->cookie_how)) asoc->cookie_how[how_indx] = 16; /* all other cases... */ return (NULL); } /* * handle a state cookie for a new association m: input packet mbuf chain-- * assumes a pullup on IP/SCTP/COOKIE-ECHO chunk note: this is a "split" mbuf * and the cookie signature does not exist offset: offset into mbuf to the * cookie-echo chunk length: length of the cookie chunk to: where the init * was from returns a new TCB */ static struct sctp_tcb * sctp_process_cookie_new(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len, struct sctp_inpcb *inp, struct sctp_nets **netp, struct sockaddr *init_src, int *notification, int auth_skipped, uint32_t auth_offset, uint32_t auth_len, uint32_t vrf_id, uint32_t table_id) { struct sctp_tcb *stcb; struct sctp_init_chunk *init_cp, init_buf; struct sctp_init_ack_chunk *initack_cp, initack_buf; struct sockaddr_storage sa_store; struct sockaddr *initack_src = (struct sockaddr *)&sa_store; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sctp_association *asoc; int chk_length; int init_offset, initack_offset, initack_limit; int retval; int error = 0; uint32_t old_tag; uint8_t auth_chunk_buf[SCTP_PARAM_BUFFER_SIZE]; /* * find and validate the INIT chunk in the cookie (peer's info) the * INIT should start after the cookie-echo header struct (chunk * header, state cookie header struct) */ init_offset = offset + sizeof(struct sctp_cookie_echo_chunk); init_cp = (struct sctp_init_chunk *) sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk), (uint8_t *) & init_buf); if (init_cp == NULL) { /* could not pull a INIT chunk in cookie */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("process_cookie_new: could not pull INIT chunk hdr\n"); } #endif /* SCTP_DEBUG */ return (NULL); } chk_length = ntohs(init_cp->ch.chunk_length); if (init_cp->ch.chunk_type != SCTP_INITIATION) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("HUH? process_cookie_new: could not find INIT chunk!\n"); } #endif /* SCTP_DEBUG */ return (NULL); } initack_offset = init_offset + SCTP_SIZE32(chk_length); /* * find and validate the INIT-ACK chunk in the cookie (my info) the * INIT-ACK follows the INIT chunk */ initack_cp = (struct sctp_init_ack_chunk *) sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk), (uint8_t *) & initack_buf); if (initack_cp == NULL) { /* could not pull INIT-ACK chunk in cookie */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("process_cookie_new: could not pull INIT-ACK chunk hdr\n"); } #endif /* SCTP_DEBUG */ return (NULL); } chk_length = ntohs(initack_cp->ch.chunk_length); if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) { return (NULL); } /* * NOTE: We can't use the INIT_ACK's chk_length to determine the * "initack_limit" value. This is because the chk_length field * includes the length of the cookie, but the cookie is omitted when * the INIT and INIT_ACK are tacked onto the cookie... */ initack_limit = offset + cookie_len; /* * now that we know the INIT/INIT-ACK are in place, create a new TCB * and popluate */ stcb = sctp_aloc_assoc(inp, init_src, 0, &error, ntohl(initack_cp->init.initiate_tag), vrf_id); if (stcb == NULL) { struct mbuf *op_err; /* memory problem? */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("process_cookie_new: no room for another TCB!\n"); } #endif op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen, sh, op_err, vrf_id, table_id); return (NULL); } /* get the correct sctp_nets */ *netp = sctp_findnet(stcb, init_src); asoc = &stcb->asoc; /* save the table id (vrf_id is done in aloc_assoc) */ asoc->table_id = table_id; /* get scope variables out of cookie */ asoc->ipv4_local_scope = cookie->ipv4_scope; asoc->site_scope = cookie->site_scope; asoc->local_scope = cookie->local_scope; asoc->loopback_scope = cookie->loopback_scope; if ((asoc->ipv4_addr_legal != cookie->ipv4_addr_legal) || (asoc->ipv6_addr_legal != cookie->ipv6_addr_legal)) { struct mbuf *op_err; /* * Houston we have a problem. The EP changed while the * cookie was in flight. Only recourse is to abort the * association. */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen, sh, op_err, vrf_id, table_id); return (NULL); } /* process the INIT-ACK info (my info) */ old_tag = asoc->my_vtag; asoc->assoc_id = asoc->my_vtag = ntohl(initack_cp->init.initiate_tag); asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); asoc->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams); asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn); asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number; asoc->last_cwr_tsn = asoc->init_seq_number - 1; asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1; asoc->str_reset_seq_in = asoc->init_seq_number; asoc->advanced_peer_ack_point = asoc->last_acked_seq; /* process the INIT info (peer's info) */ retval = sctp_process_init(init_cp, stcb, *netp); if (retval < 0) { sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_16); return (NULL); } /* load all addresses */ if (sctp_load_addresses_from_init(stcb, m, iphlen, init_offset + sizeof(struct sctp_init_chunk), initack_offset, sh, init_src)) { sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_17); return (NULL); } /* * verify any preceding AUTH chunk that was skipped */ /* pull the local authentication parameters from the cookie/init-ack */ sctp_auth_get_cookie_params(stcb, m, initack_offset + sizeof(struct sctp_init_ack_chunk), initack_limit - (initack_offset + sizeof(struct sctp_init_ack_chunk))); if (auth_skipped) { struct sctp_auth_chunk *auth; auth = (struct sctp_auth_chunk *) sctp_m_getptr(m, auth_offset, auth_len, auth_chunk_buf); if (sctp_handle_auth(stcb, auth, m, auth_offset)) { /* auth HMAC failed, dump the assoc and packet */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("COOKIE-ECHO: AUTH failed\n"); #endif /* SCTP_DEBUG */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_18); return (NULL); } else { /* remaining chunks checked... good to go */ stcb->asoc.authenticated = 1; } } /* update current state */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("moving to OPEN state\n"); } #endif if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } else { asoc->state = SCTP_STATE_OPEN; } sctp_stop_all_cookie_timers(stcb); SCTP_STAT_INCR_COUNTER32(sctps_passiveestab); SCTP_STAT_INCR_GAUGE32(sctps_currestab); /* * if we're doing ASCONFs, check to see if we have any new local * addresses that need to get added to the peer (eg. addresses * changed while cookie echo in flight). This needs to be done * after we go to the OPEN state to do the correct asconf * processing. else, make sure we have the correct addresses in our * lists */ /* warning, we re-use sin, sin6, sa_store here! */ /* pull in local_address (our "from" address) */ if (cookie->laddr_type == SCTP_IPV4_ADDRESS) { /* source addr is IPv4 */ sin = (struct sockaddr_in *)initack_src; memset(sin, 0, sizeof(*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(struct sockaddr_in); sin->sin_addr.s_addr = cookie->laddress[0]; } else if (cookie->laddr_type == SCTP_IPV6_ADDRESS) { /* source addr is IPv6 */ sin6 = (struct sockaddr_in6 *)initack_src; memset(sin6, 0, sizeof(*sin6)); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_scope_id = cookie->scope_id; memcpy(&sin6->sin6_addr, cookie->laddress, sizeof(sin6->sin6_addr)); } else { sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_19); return (NULL); } sctp_check_address_list(stcb, m, initack_offset + sizeof(struct sctp_init_ack_chunk), initack_limit - (initack_offset + sizeof(struct sctp_init_ack_chunk)), initack_src, cookie->local_scope, cookie->site_scope, cookie->ipv4_scope, cookie->loopback_scope); /* set up to notify upper layer */ *notification = SCTP_NOTIFY_ASSOC_UP; if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && (inp->sctp_socket->so_qlimit == 0)) { /* * This is an endpoint that called connect() how it got a * cookie that is NEW is a bit of a mystery. It must be that * the INIT was sent, but before it got there.. a complete * INIT/INIT-ACK/COOKIE arrived. But of course then it * should have went to the other code.. not here.. oh well.. * a bit of protection is worth having.. */ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; soisconnected(stcb->sctp_ep->sctp_socket); } else if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_socket->so_qlimit)) { /* * We don't want to do anything with this one. Since it is * the listening guy. The timer will get started for * accepted connections in the caller. */ ; } /* since we did not send a HB make sure we don't double things */ (*netp)->hb_responded = 1; if (stcb->asoc.sctp_autoclose_ticks && sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) { sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL); } /* respond with a COOKIE-ACK */ /* calculate the RTT */ (*netp)->RTO = sctp_calculate_rto(stcb, asoc, *netp, &cookie->time_entered); sctp_send_cookie_ack(stcb); return (stcb); } /* * handles a COOKIE-ECHO message stcb: modified to either a new or left as * existing (non-NULL) TCB */ static struct mbuf * sctp_handle_cookie_echo(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_cookie_echo_chunk *cp, struct sctp_inpcb **inp_p, struct sctp_tcb **stcb, struct sctp_nets **netp, int auth_skipped, uint32_t auth_offset, uint32_t auth_len, struct sctp_tcb **locked_tcb, uint32_t vrf_id, uint32_t table_id) { struct sctp_state_cookie *cookie; struct sockaddr_in6 sin6; struct sockaddr_in sin; struct sctp_tcb *l_stcb = *stcb; struct sctp_inpcb *l_inp; struct sockaddr *to; sctp_assoc_t sac_restart_id; struct sctp_pcb *ep; struct mbuf *m_sig; uint8_t calc_sig[SCTP_SIGNATURE_SIZE], tmp_sig[SCTP_SIGNATURE_SIZE]; uint8_t *sig; uint8_t cookie_ok = 0; unsigned int size_of_pkt, sig_offset, cookie_offset; unsigned int cookie_len; struct timeval now; struct timeval time_expires; struct sockaddr_storage dest_store; struct sockaddr *localep_sa = (struct sockaddr *)&dest_store; struct ip *iph; int notification = 0; struct sctp_nets *netl; int had_a_existing_tcb = 0; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_cookie: handling COOKIE-ECHO\n"); } #endif if (inp_p == NULL) { return (NULL); } /* First get the destination address setup too. */ iph = mtod(m, struct ip *); if (iph->ip_v == IPVERSION) { /* its IPv4 */ struct sockaddr_in *sin; sin = (struct sockaddr_in *)(localep_sa); memset(sin, 0, sizeof(*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = sh->dest_port; sin->sin_addr.s_addr = iph->ip_dst.s_addr; size_of_pkt = SCTP_GET_IPV4_LENGTH(iph); } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* its IPv6 */ struct ip6_hdr *ip6; struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)(localep_sa); memset(sin6, 0, sizeof(*sin6)); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(struct sockaddr_in6); ip6 = mtod(m, struct ip6_hdr *); sin6->sin6_port = sh->dest_port; sin6->sin6_addr = ip6->ip6_dst; size_of_pkt = SCTP_GET_IPV6_LENGTH(ip6) + iphlen; } else { return (NULL); } cookie = &cp->cookie; cookie_offset = offset + sizeof(struct sctp_chunkhdr); cookie_len = ntohs(cp->ch.chunk_length); if ((cookie->peerport != sh->src_port) && (cookie->myport != sh->dest_port) && (cookie->my_vtag != sh->v_tag)) { /* * invalid ports or bad tag. Note that we always leave the * v_tag in the header in network order and when we stored * it in the my_vtag slot we also left it in network order. * This maintains the match even though it may be in the * opposite byte order of the machine :-> */ return (NULL); } if (cookie_len > size_of_pkt || cookie_len < sizeof(struct sctp_cookie_echo_chunk) + sizeof(struct sctp_init_chunk) + sizeof(struct sctp_init_ack_chunk) + SCTP_SIGNATURE_SIZE) { /* cookie too long! or too small */ return (NULL); } /* * split off the signature into its own mbuf (since it should not be * calculated in the sctp_hmac_m() call). */ sig_offset = offset + cookie_len - SCTP_SIGNATURE_SIZE; if (sig_offset > size_of_pkt) { /* packet not correct size! */ /* XXX this may already be accounted for earlier... */ return (NULL); } m_sig = m_split(m, sig_offset, M_DONTWAIT); if (m_sig == NULL) { /* out of memory or ?? */ return (NULL); } /* * compute the signature/digest for the cookie */ ep = &(*inp_p)->sctp_ep; l_inp = *inp_p; if (l_stcb) { SCTP_TCB_UNLOCK(l_stcb); } SCTP_INP_RLOCK(l_inp); if (l_stcb) { SCTP_TCB_LOCK(l_stcb); } /* which cookie is it? */ if ((cookie->time_entered.tv_sec < (long)ep->time_of_secret_change) && (ep->current_secret_number != ep->last_secret_number)) { /* it's the old cookie */ - sctp_hmac_m(SCTP_HMAC, + (void)sctp_hmac_m(SCTP_HMAC, (uint8_t *) ep->secret_key[(int)ep->last_secret_number], SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); } else { /* it's the current cookie */ - sctp_hmac_m(SCTP_HMAC, + (void)sctp_hmac_m(SCTP_HMAC, (uint8_t *) ep->secret_key[(int)ep->current_secret_number], SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); } /* get the signature */ SCTP_INP_RUNLOCK(l_inp); sig = (uint8_t *) sctp_m_getptr(m_sig, 0, SCTP_SIGNATURE_SIZE, (uint8_t *) & tmp_sig); if (sig == NULL) { /* couldn't find signature */ sctp_m_freem(m_sig); return (NULL); } /* compare the received digest with the computed digest */ if (memcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) != 0) { /* try the old cookie? */ if ((cookie->time_entered.tv_sec == (long)ep->time_of_secret_change) && (ep->current_secret_number != ep->last_secret_number)) { /* compute digest with old */ - sctp_hmac_m(SCTP_HMAC, + (void)sctp_hmac_m(SCTP_HMAC, (uint8_t *) ep->secret_key[(int)ep->last_secret_number], SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); /* compare */ if (memcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) == 0) cookie_ok = 1; } } else { cookie_ok = 1; } /* * Now before we continue we must reconstruct our mbuf so that * normal processing of any other chunks will work. */ { struct mbuf *m_at; m_at = m; while (SCTP_BUF_NEXT(m_at) != NULL) { m_at = SCTP_BUF_NEXT(m_at); } SCTP_BUF_NEXT(m_at) = m_sig; } if (cookie_ok == 0) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("handle_cookie_echo: cookie signature validation failed!\n"); printf("offset = %u, cookie_offset = %u, sig_offset = %u\n", (uint32_t) offset, cookie_offset, sig_offset); } #endif return (NULL); } /* * check the cookie timestamps to be sure it's not stale */ - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); /* Expire time is in Ticks, so we convert to seconds */ time_expires.tv_sec = cookie->time_entered.tv_sec + cookie->cookie_life; time_expires.tv_usec = cookie->time_entered.tv_usec; if (timevalcmp(&now, &time_expires, >)) { /* cookie is stale! */ struct mbuf *op_err; struct sctp_stale_cookie_msg *scm; uint32_t tim; op_err = sctp_get_mbuf_for_msg(sizeof(struct sctp_stale_cookie_msg), 0, M_DONTWAIT, 1, MT_DATA); if (op_err == NULL) { /* FOOBAR */ return (NULL); } /* pre-reserve some space */ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr)); /* Set the len */ SCTP_BUF_LEN(op_err) = sizeof(struct sctp_stale_cookie_msg); scm = mtod(op_err, struct sctp_stale_cookie_msg *); scm->ph.param_type = htons(SCTP_CAUSE_STALE_COOKIE); scm->ph.param_length = htons((sizeof(struct sctp_paramhdr) + (sizeof(uint32_t)))); /* seconds to usec */ tim = (now.tv_sec - time_expires.tv_sec) * 1000000; /* add in usec */ if (tim == 0) tim = now.tv_usec - cookie->time_entered.tv_usec; scm->time_usec = htonl(tim); sctp_send_operr_to(m, iphlen, op_err, cookie->peers_vtag, vrf_id, table_id); return (NULL); } /* * Now we must see with the lookup address if we have an existing * asoc. This will only happen if we were in the COOKIE-WAIT state * and a INIT collided with us and somewhere the peer sent the * cookie on another address besides the single address our assoc * had for him. In this case we will have one of the tie-tags set at * least AND the address field in the cookie can be used to look it * up. */ to = NULL; if (cookie->addr_type == SCTP_IPV6_ADDRESS) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(sin6); sin6.sin6_port = sh->src_port; sin6.sin6_scope_id = cookie->scope_id; memcpy(&sin6.sin6_addr.s6_addr, cookie->address, sizeof(sin6.sin6_addr.s6_addr)); to = (struct sockaddr *)&sin6; } else if (cookie->addr_type == SCTP_IPV4_ADDRESS) { memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_len = sizeof(sin); sin.sin_port = sh->src_port; sin.sin_addr.s_addr = cookie->address[0]; to = (struct sockaddr *)&sin; } if ((*stcb == NULL) && to) { /* Yep, lets check */ *stcb = sctp_findassociation_ep_addr(inp_p, to, netp, localep_sa, NULL); if (*stcb == NULL) { /* * We should have only got back the same inp. If we * got back a different ep we have a problem. The * original findep got back l_inp and now */ if (l_inp != *inp_p) { printf("Bad problem find_ep got a diff inp then special_locate?\n"); } } else { if (*locked_tcb == NULL) { /* * In this case we found the assoc only * after we locked the create lock. This * means we are in a colliding case and we * must make sure that we unlock the tcb if * its one of the cases where we throw away * the incoming packets. */ *locked_tcb = *stcb; /* * We must also increment the inp ref count * since the ref_count flags was set when we * did not find the TCB, now we found it * which reduces the refcount.. we must * raise it back out to balance it all :-) */ SCTP_INP_INCR_REF((*stcb)->sctp_ep); if ((*stcb)->sctp_ep != l_inp) { printf("Huh? ep:%p diff then l_inp:%p?\n", (*stcb)->sctp_ep, l_inp); } } } } cookie_len -= SCTP_SIGNATURE_SIZE; if (*stcb == NULL) { /* this is the "normal" case... get a new TCB */ *stcb = sctp_process_cookie_new(m, iphlen, offset, sh, cookie, cookie_len, *inp_p, netp, to, ¬ification, auth_skipped, auth_offset, auth_len, vrf_id, table_id); } else { /* this is abnormal... cookie-echo on existing TCB */ had_a_existing_tcb = 1; *stcb = sctp_process_cookie_existing(m, iphlen, offset, sh, cookie, cookie_len, *inp_p, *stcb, *netp, to, ¬ification, &sac_restart_id, vrf_id, table_id); } if (*stcb == NULL) { /* still no TCB... must be bad cookie-echo */ return (NULL); } /* * Ok, we built an association so confirm the address we sent the * INIT-ACK to. */ netl = sctp_findnet(*stcb, to); /* * This code should in theory NOT run but */ if (netl == NULL) { /* TSNH! Huh, why do I need to add this address here? */ int ret; ret = sctp_add_remote_addr(*stcb, to, SCTP_DONOT_SETSCOPE, SCTP_IN_COOKIE_PROC); netl = sctp_findnet(*stcb, to); } if (netl) { if (netl->dest_state & SCTP_ADDR_UNCONFIRMED) { netl->dest_state &= ~SCTP_ADDR_UNCONFIRMED; sctp_set_primary_addr((*stcb), (struct sockaddr *)NULL, netl); sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, (*stcb), 0, (void *)netl); } } if (*stcb) { sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, *inp_p, *stcb, NULL); } if ((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { if (!had_a_existing_tcb || (((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { /* * If we have a NEW cookie or the connect never * reached the connected state during collision we * must do the TCP accept thing. */ struct socket *so, *oso; struct sctp_inpcb *inp; if (notification == SCTP_NOTIFY_ASSOC_RESTART) { /* * For a restart we will keep the same * socket, no need to do anything. I THINK!! */ sctp_ulp_notify(notification, *stcb, 0, (void *)&sac_restart_id); return (m); } oso = (*inp_p)->sctp_socket; /* * We do this to keep the sockets side happy durin * the sonewcon ONLY. */ NET_LOCK_GIANT(); SCTP_TCB_UNLOCK((*stcb)); so = sonewconn(oso, 0 ); NET_UNLOCK_GIANT(); SCTP_INP_WLOCK((*stcb)->sctp_ep); SCTP_TCB_LOCK((*stcb)); SCTP_INP_WUNLOCK((*stcb)->sctp_ep); if (so == NULL) { struct mbuf *op_err; /* Too many sockets */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("process_cookie_new: no room for another socket!\n"); } #endif /* SCTP_DEBUG */ op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); sctp_abort_association(*inp_p, NULL, m, iphlen, sh, op_err, vrf_id, table_id); sctp_free_assoc(*inp_p, *stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_20); return (NULL); } inp = (struct sctp_inpcb *)so->so_pcb; SCTP_INP_INCR_REF(inp); /* * We add the unbound flag here so that if we get an * soabort() before we get the move_pcb done, we * will properly cleanup. */ inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE | SCTP_PCB_FLAGS_CONNECTED | SCTP_PCB_FLAGS_IN_TCPPOOL | SCTP_PCB_FLAGS_UNBOUND | (SCTP_PCB_COPY_FLAGS & (*inp_p)->sctp_flags) | SCTP_PCB_FLAGS_DONT_WAKE); inp->sctp_features = (*inp_p)->sctp_features; inp->sctp_socket = so; inp->sctp_frag_point = (*inp_p)->sctp_frag_point; inp->partial_delivery_point = (*inp_p)->partial_delivery_point; inp->sctp_context = (*inp_p)->sctp_context; inp->inp_starting_point_for_iterator = NULL; /* * copy in the authentication parameters from the * original endpoint */ if (inp->sctp_ep.local_hmacs) sctp_free_hmaclist(inp->sctp_ep.local_hmacs); inp->sctp_ep.local_hmacs = sctp_copy_hmaclist((*inp_p)->sctp_ep.local_hmacs); if (inp->sctp_ep.local_auth_chunks) sctp_free_chunklist(inp->sctp_ep.local_auth_chunks); inp->sctp_ep.local_auth_chunks = sctp_copy_chunklist((*inp_p)->sctp_ep.local_auth_chunks); (void)sctp_copy_skeylist(&(*inp_p)->sctp_ep.shared_keys, &inp->sctp_ep.shared_keys); /* * Now we must move it from one hash table to * another and get the tcb in the right place. */ sctp_move_pcb_and_assoc(*inp_p, inp, *stcb); sctp_pull_off_control_to_new_inp((*inp_p), inp, *stcb, M_NOWAIT); /* * now we must check to see if we were aborted while * the move was going on and the lock/unlock * happened. */ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* * yep it was, we leave the assoc attached * to the socket since the sctp_inpcb_free() * call will send an abort for us. */ SCTP_INP_DECR_REF(inp); return (NULL); } SCTP_INP_DECR_REF(inp); /* Switch over to the new guy */ *inp_p = inp; sctp_ulp_notify(notification, *stcb, 0, NULL); /* * Pull it from the incomplete queue and wake the * guy */ soisconnected(so); return (m); } } if ((notification) && ((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) { sctp_ulp_notify(notification, *stcb, 0, NULL); } return (m); } static void sctp_handle_cookie_ack(struct sctp_cookie_ack_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { /* cp must not be used, others call this without a c-ack :-) */ struct sctp_association *asoc; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_cookie_ack: handling COOKIE-ACK\n"); } #endif if (stcb == NULL) return; asoc = &stcb->asoc; sctp_stop_all_cookie_timers(stcb); /* process according to association state */ if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) { /* state change only needed when I am in right state */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("moving to OPEN state\n"); } #endif if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } else { asoc->state = SCTP_STATE_OPEN; } /* update RTO */ SCTP_STAT_INCR_COUNTER32(sctps_activeestab); SCTP_STAT_INCR_GAUGE32(sctps_currestab); if (asoc->overall_error_count == 0) { net->RTO = sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered); } - SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered); sctp_ulp_notify(SCTP_NOTIFY_ASSOC_UP, stcb, 0, NULL); if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; soisconnected(stcb->sctp_ep->sctp_socket); } sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net); /* * since we did not send a HB make sure we don't double * things */ net->hb_responded = 1; if (stcb->asoc.sctp_autoclose_ticks && sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_AUTOCLOSE)) { sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, stcb->sctp_ep, stcb, NULL); } /* * set ASCONF timer if ASCONFs are pending and allowed (eg. * addresses changed when init/cookie echo in flight) */ if ((sctp_is_feature_on(stcb->sctp_ep, SCTP_PCB_FLAGS_DO_ASCONF)) && (stcb->asoc.peer_supports_asconf) && (!TAILQ_EMPTY(&stcb->asoc.asconf_queue))) { sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, stcb->asoc.primary_destination); } } /* Toss the cookie if I can */ sctp_toss_old_cookies(stcb, asoc); if (!TAILQ_EMPTY(&asoc->sent_queue)) { /* Restart the timer if we have pending data */ struct sctp_tmit_chunk *chk; chk = TAILQ_FIRST(&asoc->sent_queue); if (chk) { sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo); } } } static void sctp_handle_ecn_echo(struct sctp_ecne_chunk *cp, struct sctp_tcb *stcb) { struct sctp_nets *net; struct sctp_tmit_chunk *lchk; uint32_t tsn; if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_ecne_chunk)) { return; } SCTP_STAT_INCR(sctps_recvecne); tsn = ntohl(cp->tsn); /* ECN Nonce stuff: need a resync and disable the nonce sum check */ /* Also we make sure we disable the nonce_wait */ lchk = TAILQ_FIRST(&stcb->asoc.send_queue); if (lchk == NULL) { stcb->asoc.nonce_resync_tsn = stcb->asoc.sending_seq; } else { stcb->asoc.nonce_resync_tsn = lchk->rec.data.TSN_seq; } stcb->asoc.nonce_wait_for_ecne = 0; stcb->asoc.nonce_sum_check = 0; /* Find where it was sent, if possible */ net = NULL; lchk = TAILQ_FIRST(&stcb->asoc.sent_queue); while (lchk) { if (lchk->rec.data.TSN_seq == tsn) { net = lchk->whoTo; break; } if (compare_with_wrap(lchk->rec.data.TSN_seq, tsn, MAX_SEQ)) break; lchk = TAILQ_NEXT(lchk, sctp_next); } if (net == NULL) /* default is we use the primary */ net = stcb->asoc.primary_destination; if (compare_with_wrap(tsn, stcb->asoc.last_cwr_tsn, MAX_TSN)) { #ifdef SCTP_CWND_MONITOR int old_cwnd; old_cwnd = net->cwnd; #endif SCTP_STAT_INCR(sctps_ecnereducedcwnd); net->ssthresh = net->cwnd / 2; if (net->ssthresh < net->mtu) { net->ssthresh = net->mtu; /* here back off the timer as well, to slow us down */ net->RTO <<= 2; } net->cwnd = net->ssthresh; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT); #endif /* * we reduce once every RTT. So we will only lower cwnd at * the next sending seq i.e. the resync_tsn. */ stcb->asoc.last_cwr_tsn = stcb->asoc.nonce_resync_tsn; } /* * We always send a CWR this way if our previous one was lost our * peer will get an update, or if it is not time again to reduce we * still get the cwr to the peer. */ sctp_send_cwr(stcb, net, tsn); } static void sctp_handle_ecn_cwr(struct sctp_cwr_chunk *cp, struct sctp_tcb *stcb) { /* * Here we get a CWR from the peer. We must look in the outqueue and * make sure that we have a covered ECNE in teh control chunk part. * If so remove it. */ struct sctp_tmit_chunk *chk; struct sctp_ecne_chunk *ecne; TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { if (chk->rec.chunk_id.id != SCTP_ECN_ECHO) { continue; } /* * Look for and remove if it is the right TSN. Since there * is only ONE ECNE on the control queue at any one time we * don't need to worry about more than one! */ ecne = mtod(chk->data, struct sctp_ecne_chunk *); if (compare_with_wrap(ntohl(cp->tsn), ntohl(ecne->tsn), MAX_TSN) || (cp->tsn == ecne->tsn)) { /* this covers this ECNE, we can remove it */ stcb->asoc.ecn_echo_cnt_onq--; TAILQ_REMOVE(&stcb->asoc.control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } stcb->asoc.ctrl_queue_cnt--; sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); break; } } } static void sctp_handle_shutdown_complete(struct sctp_shutdown_complete_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_association *asoc; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_handle_shutdown_complete: handling SHUTDOWN-COMPLETE\n"); } #endif if (stcb == NULL) return; asoc = &stcb->asoc; /* process according to association state */ if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT) { /* unexpected SHUTDOWN-COMPLETE... so ignore... */ SCTP_TCB_UNLOCK(stcb); return; } /* notify upper layer protocol */ if (stcb->sctp_socket) { sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL); /* are the queues empty? they should be */ if (!TAILQ_EMPTY(&asoc->send_queue) || !TAILQ_EMPTY(&asoc->sent_queue) || !TAILQ_EMPTY(&asoc->out_wheel)) { sctp_report_all_outbound(stcb, 0); } } /* stop the timer */ sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_21); SCTP_STAT_INCR_COUNTER32(sctps_shutdown); /* free the TCB */ sctp_free_assoc(stcb->sctp_ep, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_22); return; } static int process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc, struct sctp_nets *net, uint8_t flg) { switch (desc->chunk_type) { case SCTP_DATA: /* find the tsn to resend (possibly */ { uint32_t tsn; struct sctp_tmit_chunk *tp1; tsn = ntohl(desc->tsn_ifany); tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue); while (tp1) { if (tp1->rec.data.TSN_seq == tsn) { /* found it */ break; } if (compare_with_wrap(tp1->rec.data.TSN_seq, tsn, MAX_TSN)) { /* not found */ tp1 = NULL; break; } tp1 = TAILQ_NEXT(tp1, sctp_next); } if (tp1 == NULL) { /* * Do it the other way , aka without paying * attention to queue seq order. */ SCTP_STAT_INCR(sctps_pdrpdnfnd); tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue); while (tp1) { if (tp1->rec.data.TSN_seq == tsn) { /* found it */ break; } tp1 = TAILQ_NEXT(tp1, sctp_next); } } if (tp1 == NULL) { SCTP_STAT_INCR(sctps_pdrptsnnf); } if ((tp1) && (tp1->sent < SCTP_DATAGRAM_ACKED)) { uint8_t *ddp; if ((stcb->asoc.peers_rwnd == 0) && ((flg & SCTP_FROM_MIDDLE_BOX) == 0)) { SCTP_STAT_INCR(sctps_pdrpdiwnp); return (0); } if (stcb->asoc.peers_rwnd == 0 && (flg & SCTP_FROM_MIDDLE_BOX)) { SCTP_STAT_INCR(sctps_pdrpdizrw); return (0); } ddp = (uint8_t *) (mtod(tp1->data, caddr_t)+ sizeof(struct sctp_data_chunk)); { unsigned int iii; for (iii = 0; iii < sizeof(desc->data_bytes); iii++) { if (ddp[iii] != desc->data_bytes[iii]) { SCTP_STAT_INCR(sctps_pdrpbadd); return (-1); } } } /* * We zero out the nonce so resync not * needed */ tp1->rec.data.ect_nonce = 0; if (tp1->do_rtt) { /* * this guy had a RTO calculation * pending on it, cancel it */ tp1->do_rtt = 0; } SCTP_STAT_INCR(sctps_pdrpmark); if (tp1->sent != SCTP_DATAGRAM_RESEND) sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); tp1->sent = SCTP_DATAGRAM_RESEND; /* * mark it as if we were doing a FR, since * we will be getting gap ack reports behind * the info from the router. */ tp1->rec.data.doing_fast_retransmit = 1; /* * mark the tsn with what sequences can * cause a new FR. */ if (TAILQ_EMPTY(&stcb->asoc.send_queue)) { tp1->rec.data.fast_retran_tsn = stcb->asoc.sending_seq; } else { tp1->rec.data.fast_retran_tsn = (TAILQ_FIRST(&stcb->asoc.send_queue))->rec.data.TSN_seq; } /* restart the timer */ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, tp1->whoTo, SCTP_FROM_SCTP_INPUT + SCTP_LOC_23); sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, tp1->whoTo); /* fix counts and things */ #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_PDRP, tp1->whoTo->flight_size, tp1->book_size, (uintptr_t) stcb, tp1->rec.data.TSN_seq); #endif sctp_flight_size_decrease(tp1); sctp_total_flight_decrease(stcb, tp1); } { /* audit code */ unsigned int audit; audit = 0; TAILQ_FOREACH(tp1, &stcb->asoc.sent_queue, sctp_next) { if (tp1->sent == SCTP_DATAGRAM_RESEND) audit++; } TAILQ_FOREACH(tp1, &stcb->asoc.control_send_queue, sctp_next) { if (tp1->sent == SCTP_DATAGRAM_RESEND) audit++; } if (audit != stcb->asoc.sent_queue_retran_cnt) { printf("**Local Audit finds cnt:%d asoc cnt:%d\n", audit, stcb->asoc.sent_queue_retran_cnt); #ifndef SCTP_AUDITING_ENABLED stcb->asoc.sent_queue_retran_cnt = audit; #endif } } } break; case SCTP_ASCONF: { struct sctp_tmit_chunk *asconf; TAILQ_FOREACH(asconf, &stcb->asoc.control_send_queue, sctp_next) { if (asconf->rec.chunk_id.id == SCTP_ASCONF) { break; } } if (asconf) { if (asconf->sent != SCTP_DATAGRAM_RESEND) sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); asconf->sent = SCTP_DATAGRAM_RESEND; asconf->snd_count--; } } break; case SCTP_INITIATION: /* resend the INIT */ stcb->asoc.dropped_special_cnt++; if (stcb->asoc.dropped_special_cnt < SCTP_RETRY_DROPPED_THRESH) { /* * If we can get it in, in a few attempts we do * this, otherwise we let the timer fire. */ sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, stcb, net, SCTP_FROM_SCTP_INPUT + SCTP_LOC_24); sctp_send_initiate(stcb->sctp_ep, stcb); } break; case SCTP_SELECTIVE_ACK: /* resend the sack */ sctp_send_sack(stcb); break; case SCTP_HEARTBEAT_REQUEST: /* resend a demand HB */ - sctp_send_hb(stcb, 1, net); + (void)sctp_send_hb(stcb, 1, net); break; case SCTP_SHUTDOWN: sctp_send_shutdown(stcb, net); break; case SCTP_SHUTDOWN_ACK: sctp_send_shutdown_ack(stcb, net); break; case SCTP_COOKIE_ECHO: { struct sctp_tmit_chunk *cookie; cookie = NULL; TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue, sctp_next) { if (cookie->rec.chunk_id.id == SCTP_COOKIE_ECHO) { break; } } if (cookie) { if (cookie->sent != SCTP_DATAGRAM_RESEND) sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); cookie->sent = SCTP_DATAGRAM_RESEND; sctp_stop_all_cookie_timers(stcb); } } break; case SCTP_COOKIE_ACK: sctp_send_cookie_ack(stcb); break; case SCTP_ASCONF_ACK: /* resend last asconf ack */ sctp_send_asconf_ack(stcb, 1); break; case SCTP_FORWARD_CUM_TSN: send_forward_tsn(stcb, &stcb->asoc); break; /* can't do anything with these */ case SCTP_PACKET_DROPPED: case SCTP_INITIATION_ACK: /* this should not happen */ case SCTP_HEARTBEAT_ACK: case SCTP_ABORT_ASSOCIATION: case SCTP_OPERATION_ERROR: case SCTP_SHUTDOWN_COMPLETE: case SCTP_ECN_ECHO: case SCTP_ECN_CWR: default: break; } return (0); } void sctp_reset_in_stream(struct sctp_tcb *stcb, int number_entries, uint16_t * list) { int i; uint16_t temp; /* * We set things to 0xffff since this is the last delivered sequence * and we will be sending in 0 after the reset. */ if (number_entries) { for (i = 0; i < number_entries; i++) { temp = ntohs(list[i]); if (temp >= stcb->asoc.streamincnt) { continue; } stcb->asoc.strmin[temp].last_sequence_delivered = 0xffff; } } else { list = NULL; for (i = 0; i < stcb->asoc.streamincnt; i++) { stcb->asoc.strmin[i].last_sequence_delivered = 0xffff; } } sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_RECV, stcb, number_entries, (void *)list); } static void sctp_reset_out_streams(struct sctp_tcb *stcb, int number_entries, uint16_t * list) { int i; if (number_entries == 0) { for (i = 0; i < stcb->asoc.streamoutcnt; i++) { stcb->asoc.strmout[i].next_sequence_sent = 0; } } else if (number_entries) { for (i = 0; i < number_entries; i++) { uint16_t temp; temp = ntohs(list[i]); if (temp >= stcb->asoc.streamoutcnt) { /* no such stream */ continue; } stcb->asoc.strmout[temp].next_sequence_sent = 0; } } sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_SEND, stcb, number_entries, (void *)list); } struct sctp_stream_reset_out_request * sctp_find_stream_reset(struct sctp_tcb *stcb, uint32_t seq, struct sctp_tmit_chunk **bchk) { struct sctp_association *asoc; struct sctp_stream_reset_out_req *req; struct sctp_stream_reset_out_request *r; struct sctp_tmit_chunk *chk; int len, clen; asoc = &stcb->asoc; if (TAILQ_EMPTY(&stcb->asoc.control_send_queue)) { asoc->stream_reset_outstanding = 0; return (NULL); } if (stcb->asoc.str_reset == NULL) { asoc->stream_reset_outstanding = 0; return (NULL); } chk = stcb->asoc.str_reset; if (chk->data == NULL) { return (NULL); } if (bchk) { /* he wants a copy of the chk pointer */ *bchk = chk; } clen = chk->send_size; req = mtod(chk->data, struct sctp_stream_reset_out_req *); r = &req->sr_req; if (ntohl(r->request_seq) == seq) { /* found it */ return (r); } len = SCTP_SIZE32(ntohs(r->ph.param_length)); if (clen > (len + (int)sizeof(struct sctp_chunkhdr))) { /* move to the next one, there can only be a max of two */ r = (struct sctp_stream_reset_out_request *)((caddr_t)r + len); if (ntohl(r->request_seq) == seq) { return (r); } } /* that seq is not here */ return (NULL); } static void sctp_clean_up_stream_reset(struct sctp_tcb *stcb) { struct sctp_association *asoc; struct sctp_tmit_chunk *chk = stcb->asoc.str_reset; if (stcb->asoc.str_reset == NULL) { return; } asoc = &stcb->asoc; sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo, SCTP_FROM_SCTP_INPUT + SCTP_LOC_25); TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } asoc->ctrl_queue_cnt--; sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); stcb->asoc.str_reset = NULL; } static int sctp_handle_stream_reset_response(struct sctp_tcb *stcb, uint32_t seq, uint32_t action, struct sctp_stream_reset_response *respin) { uint16_t type; int lparm_len; struct sctp_association *asoc = &stcb->asoc; struct sctp_tmit_chunk *chk; struct sctp_stream_reset_out_request *srparam; int number_entries; if (asoc->stream_reset_outstanding == 0) { /* duplicate */ return (0); } if (seq == stcb->asoc.str_reset_seq_out) { srparam = sctp_find_stream_reset(stcb, seq, &chk); if (srparam) { stcb->asoc.str_reset_seq_out++; type = ntohs(srparam->ph.param_type); lparm_len = ntohs(srparam->ph.param_length); if (type == SCTP_STR_RESET_OUT_REQUEST) { number_entries = (lparm_len - sizeof(struct sctp_stream_reset_out_request)) / sizeof(uint16_t); asoc->stream_reset_out_is_outstanding = 0; if (asoc->stream_reset_outstanding) asoc->stream_reset_outstanding--; if (action == SCTP_STREAM_RESET_PERFORMED) { /* do it */ sctp_reset_out_streams(stcb, number_entries, srparam->list_of_streams); } else { sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_FAILED_OUT, stcb, number_entries, srparam->list_of_streams); } } else if (type == SCTP_STR_RESET_IN_REQUEST) { /* Answered my request */ number_entries = (lparm_len - sizeof(struct sctp_stream_reset_in_request)) / sizeof(uint16_t); if (asoc->stream_reset_outstanding) asoc->stream_reset_outstanding--; if (action != SCTP_STREAM_RESET_PERFORMED) { sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_FAILED_IN, stcb, number_entries, srparam->list_of_streams); } } else if (type == SCTP_STR_RESET_TSN_REQUEST) { /** * a) Adopt the new in tsn. * b) reset the map * c) Adopt the new out-tsn */ struct sctp_stream_reset_response_tsn *resp; struct sctp_forward_tsn_chunk fwdtsn; int abort_flag = 0; if (respin == NULL) { /* huh ? */ return (0); } if (action == SCTP_STREAM_RESET_PERFORMED) { resp = (struct sctp_stream_reset_response_tsn *)respin; asoc->stream_reset_outstanding--; fwdtsn.ch.chunk_length = htons(sizeof(struct sctp_forward_tsn_chunk)); fwdtsn.ch.chunk_type = SCTP_FORWARD_CUM_TSN; fwdtsn.new_cumulative_tsn = htonl(ntohl(resp->senders_next_tsn) - 1); sctp_handle_forward_tsn(stcb, &fwdtsn, &abort_flag); if (abort_flag) { return (1); } stcb->asoc.highest_tsn_inside_map = (ntohl(resp->senders_next_tsn) - 1); stcb->asoc.cumulative_tsn = stcb->asoc.highest_tsn_inside_map; stcb->asoc.mapping_array_base_tsn = ntohl(resp->senders_next_tsn); memset(stcb->asoc.mapping_array, 0, stcb->asoc.mapping_array_size); stcb->asoc.sending_seq = ntohl(resp->receivers_next_tsn); stcb->asoc.last_acked_seq = stcb->asoc.cumulative_tsn; sctp_reset_out_streams(stcb, 0, (uint16_t *) NULL); sctp_reset_in_stream(stcb, 0, (uint16_t *) NULL); } } /* get rid of the request and get the request flags */ if (asoc->stream_reset_outstanding == 0) { sctp_clean_up_stream_reset(stcb); } } } return (0); } static void sctp_handle_str_reset_request_in(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk, struct sctp_stream_reset_in_request *req) { uint32_t seq; int len, i; int number_entries; uint16_t temp; /* * peer wants me to send a str-reset to him for my outgoing seq's if * seq_in is right. */ struct sctp_association *asoc = &stcb->asoc; seq = ntohl(req->request_seq); if (asoc->str_reset_seq_in == seq) { if (stcb->asoc.stream_reset_out_is_outstanding == 0) { len = ntohs(req->ph.param_length); number_entries = ((len - sizeof(struct sctp_stream_reset_in_request)) / sizeof(uint16_t)); for (i = 0; i < number_entries; i++) { temp = ntohs(req->list_of_streams[i]); req->list_of_streams[i] = temp; } /* move the reset action back one */ asoc->last_reset_action[1] = asoc->last_reset_action[0]; asoc->last_reset_action[0] = SCTP_STREAM_RESET_PERFORMED; sctp_add_stream_reset_out(chk, number_entries, req->list_of_streams, asoc->str_reset_seq_out, seq, (asoc->sending_seq - 1)); asoc->stream_reset_out_is_outstanding = 1; asoc->str_reset = chk; sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo); stcb->asoc.stream_reset_outstanding++; } else { /* Can't do it, since we have sent one out */ asoc->last_reset_action[1] = asoc->last_reset_action[0]; asoc->last_reset_action[0] = SCTP_STREAM_RESET_TRY_LATER; sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]); } asoc->str_reset_seq_in++; } else if (asoc->str_reset_seq_in - 1 == seq) { sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]); } else if (asoc->str_reset_seq_in - 2 == seq) { sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]); } else { sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_BAD_SEQNO); } } static int sctp_handle_str_reset_request_tsn(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk, struct sctp_stream_reset_tsn_request *req) { /* reset all in and out and update the tsn */ /* * A) reset my str-seq's on in and out. B) Select a receive next, * and set cum-ack to it. Also process this selected number as a * fwd-tsn as well. C) set in the response my next sending seq. */ struct sctp_forward_tsn_chunk fwdtsn; struct sctp_association *asoc = &stcb->asoc; int abort_flag = 0; uint32_t seq; seq = ntohl(req->request_seq); if (asoc->str_reset_seq_in == seq) { fwdtsn.ch.chunk_length = htons(sizeof(struct sctp_forward_tsn_chunk)); fwdtsn.ch.chunk_type = SCTP_FORWARD_CUM_TSN; fwdtsn.ch.chunk_flags = 0; fwdtsn.new_cumulative_tsn = htonl(stcb->asoc.highest_tsn_inside_map + 1); sctp_handle_forward_tsn(stcb, &fwdtsn, &abort_flag); if (abort_flag) { return (1); } stcb->asoc.highest_tsn_inside_map += SCTP_STREAM_RESET_TSN_DELTA; stcb->asoc.cumulative_tsn = stcb->asoc.highest_tsn_inside_map; stcb->asoc.mapping_array_base_tsn = stcb->asoc.highest_tsn_inside_map + 1; memset(stcb->asoc.mapping_array, 0, stcb->asoc.mapping_array_size); atomic_add_int(&stcb->asoc.sending_seq, 1); /* save off historical data for retrans */ stcb->asoc.last_sending_seq[1] = stcb->asoc.last_sending_seq[0]; stcb->asoc.last_sending_seq[0] = stcb->asoc.sending_seq; stcb->asoc.last_base_tsnsent[1] = stcb->asoc.last_base_tsnsent[0]; stcb->asoc.last_base_tsnsent[0] = stcb->asoc.mapping_array_base_tsn; sctp_add_stream_reset_result_tsn(chk, ntohl(req->request_seq), SCTP_STREAM_RESET_PERFORMED, stcb->asoc.sending_seq, stcb->asoc.mapping_array_base_tsn); sctp_reset_out_streams(stcb, 0, (uint16_t *) NULL); sctp_reset_in_stream(stcb, 0, (uint16_t *) NULL); stcb->asoc.last_reset_action[1] = stcb->asoc.last_reset_action[0]; stcb->asoc.last_reset_action[0] = SCTP_STREAM_RESET_PERFORMED; asoc->str_reset_seq_in++; } else if (asoc->str_reset_seq_in - 1 == seq) { sctp_add_stream_reset_result_tsn(chk, seq, asoc->last_reset_action[0], stcb->asoc.last_sending_seq[0], stcb->asoc.last_base_tsnsent[0] ); } else if (asoc->str_reset_seq_in - 2 == seq) { sctp_add_stream_reset_result_tsn(chk, seq, asoc->last_reset_action[1], stcb->asoc.last_sending_seq[1], stcb->asoc.last_base_tsnsent[1] ); } else { sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_BAD_SEQNO); } return (0); } static void sctp_handle_str_reset_request_out(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk, struct sctp_stream_reset_out_request *req) { uint32_t seq, tsn; int number_entries, len; struct sctp_association *asoc = &stcb->asoc; seq = ntohl(req->request_seq); /* now if its not a duplicate we process it */ if (asoc->str_reset_seq_in == seq) { len = ntohs(req->ph.param_length); number_entries = ((len - sizeof(struct sctp_stream_reset_out_request)) / sizeof(uint16_t)); /* * the sender is resetting, handle the list issue.. we must * a) verify if we can do the reset, if so no problem b) If * we can't do the reset we must copy the request. c) queue * it, and setup the data in processor to trigger it off * when needed and dequeue all the queued data. */ tsn = ntohl(req->send_reset_at_tsn); /* move the reset action back one */ asoc->last_reset_action[1] = asoc->last_reset_action[0]; if ((tsn == asoc->cumulative_tsn) || (compare_with_wrap(asoc->cumulative_tsn, tsn, MAX_TSN))) { /* we can do it now */ sctp_reset_in_stream(stcb, number_entries, req->list_of_streams); sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_PERFORMED); asoc->last_reset_action[0] = SCTP_STREAM_RESET_PERFORMED; } else { /* * we must queue it up and thus wait for the TSN's * to arrive that are at or before tsn */ struct sctp_stream_reset_list *liste; int siz; siz = sizeof(struct sctp_stream_reset_list) + (number_entries * sizeof(uint16_t)); SCTP_MALLOC(liste, struct sctp_stream_reset_list *, siz, "StrRstList"); if (liste == NULL) { /* gak out of memory */ sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_DENIED); asoc->last_reset_action[0] = SCTP_STREAM_RESET_DENIED; return; } liste->tsn = tsn; liste->number_entries = number_entries; memcpy(&liste->req, req, (sizeof(struct sctp_stream_reset_out_request) + (number_entries * sizeof(uint16_t)))); TAILQ_INSERT_TAIL(&asoc->resetHead, liste, next_resp); sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_PERFORMED); asoc->last_reset_action[0] = SCTP_STREAM_RESET_PERFORMED; } asoc->str_reset_seq_in++; } else if ((asoc->str_reset_seq_in - 1) == seq) { /* * one seq back, just echo back last action since my * response was lost. */ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[0]); } else if ((asoc->str_reset_seq_in - 2) == seq) { /* * two seq back, just echo back last action since my * response was lost. */ sctp_add_stream_reset_result(chk, seq, asoc->last_reset_action[1]); } else { sctp_add_stream_reset_result(chk, seq, SCTP_STREAM_RESET_BAD_SEQNO); } } static int sctp_handle_stream_reset(struct sctp_tcb *stcb, struct sctp_stream_reset_out_req *sr_req) { int chk_length, param_len, ptype; uint32_t seq; int num_req = 0; struct sctp_tmit_chunk *chk; struct sctp_chunkhdr *ch; struct sctp_paramhdr *ph; int ret_code = 0; int num_param = 0; /* now it may be a reset or a reset-response */ chk_length = ntohs(sr_req->ch.chunk_length); /* setup for adding the response */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return (ret_code); } chk->rec.chunk_id.id = SCTP_STREAM_RESET; chk->rec.chunk_id.can_take_data = 0; chk->asoc = &stcb->asoc; chk->no_fr_allowed = 0; chk->book_size = chk->send_size = sizeof(struct sctp_chunkhdr); chk->book_size_scale = 0; chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (chk->data == NULL) { strres_nochunk: if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_a_chunk(stcb, chk); return (ret_code); } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); /* setup chunk parameters */ chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = stcb->asoc.primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); ch = mtod(chk->data, struct sctp_chunkhdr *); ch->chunk_type = SCTP_STREAM_RESET; ch->chunk_flags = 0; ch->chunk_length = htons(chk->send_size); SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size); ph = (struct sctp_paramhdr *)&sr_req->sr_req; while ((size_t)chk_length >= sizeof(struct sctp_stream_reset_tsn_request)) { param_len = ntohs(ph->param_length); if (param_len < (int)sizeof(struct sctp_stream_reset_tsn_request)) { /* bad param */ break; } ptype = ntohs(ph->param_type); num_param++; if (num_param > SCTP_MAX_RESET_PARAMS) { /* hit the max of parameters already sorry.. */ break; } if (ptype == SCTP_STR_RESET_OUT_REQUEST) { struct sctp_stream_reset_out_request *req_out; req_out = (struct sctp_stream_reset_out_request *)ph; num_req++; if (stcb->asoc.stream_reset_outstanding) { seq = ntohl(req_out->response_seq); if (seq == stcb->asoc.str_reset_seq_out) { /* implicit ack */ sctp_handle_stream_reset_response(stcb, seq, SCTP_STREAM_RESET_PERFORMED, NULL); } } sctp_handle_str_reset_request_out(stcb, chk, req_out); } else if (ptype == SCTP_STR_RESET_IN_REQUEST) { struct sctp_stream_reset_in_request *req_in; num_req++; req_in = (struct sctp_stream_reset_in_request *)ph; sctp_handle_str_reset_request_in(stcb, chk, req_in); } else if (ptype == SCTP_STR_RESET_TSN_REQUEST) { struct sctp_stream_reset_tsn_request *req_tsn; num_req++; req_tsn = (struct sctp_stream_reset_tsn_request *)ph; if (sctp_handle_str_reset_request_tsn(stcb, chk, req_tsn)) { ret_code = 1; goto strres_nochunk; } /* no more */ break; } else if (ptype == SCTP_STR_RESET_RESPONSE) { struct sctp_stream_reset_response *resp; uint32_t result; resp = (struct sctp_stream_reset_response *)ph; seq = ntohl(resp->response_seq); result = ntohl(resp->result); if (sctp_handle_stream_reset_response(stcb, seq, result, resp)) { ret_code = 1; goto strres_nochunk; } } else { break; } ph = (struct sctp_paramhdr *)((caddr_t)ph + SCTP_SIZE32(param_len)); chk_length -= SCTP_SIZE32(param_len); } if (num_req == 0) { /* we have no response free the stuff */ goto strres_nochunk; } /* ok we have a chunk to link in */ TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); stcb->asoc.ctrl_queue_cnt++; return (ret_code); } /* * Handle a router or endpoints report of a packet loss, there are two ways * to handle this, either we get the whole packet and must disect it * ourselves (possibly with truncation and or corruption) or it is a summary * from a middle box that did the disectting for us. */ static void sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp, struct sctp_tcb *stcb, struct sctp_nets *net) { uint32_t bottle_bw, on_queue; uint16_t trunc_len; unsigned int chlen; unsigned int at; struct sctp_chunk_desc desc; struct sctp_chunkhdr *ch; chlen = ntohs(cp->ch.chunk_length); chlen -= sizeof(struct sctp_pktdrop_chunk); /* XXX possible chlen underflow */ if (chlen == 0) { ch = NULL; if (cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) SCTP_STAT_INCR(sctps_pdrpbwrpt); } else { ch = (struct sctp_chunkhdr *)(cp->data + sizeof(struct sctphdr)); chlen -= sizeof(struct sctphdr); /* XXX possible chlen underflow */ memset(&desc, 0, sizeof(desc)); } trunc_len = (uint16_t) ntohs(cp->trunc_len); /* now the chunks themselves */ while ((ch != NULL) && (chlen >= sizeof(struct sctp_chunkhdr))) { desc.chunk_type = ch->chunk_type; /* get amount we need to move */ at = ntohs(ch->chunk_length); if (at < sizeof(struct sctp_chunkhdr)) { /* corrupt chunk, maybe at the end? */ SCTP_STAT_INCR(sctps_pdrpcrupt); break; } if (trunc_len == 0) { /* we are supposed to have all of it */ if (at > chlen) { /* corrupt skip it */ SCTP_STAT_INCR(sctps_pdrpcrupt); break; } } else { /* is there enough of it left ? */ if (desc.chunk_type == SCTP_DATA) { if (chlen < (sizeof(struct sctp_data_chunk) + sizeof(desc.data_bytes))) { break; } } else { if (chlen < sizeof(struct sctp_chunkhdr)) { break; } } } if (desc.chunk_type == SCTP_DATA) { /* can we get out the tsn? */ if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)) SCTP_STAT_INCR(sctps_pdrpmbda); if (chlen >= (sizeof(struct sctp_data_chunk) + sizeof(uint32_t))) { /* yep */ struct sctp_data_chunk *dcp; uint8_t *ddp; unsigned int iii; dcp = (struct sctp_data_chunk *)ch; ddp = (uint8_t *) (dcp + 1); for (iii = 0; iii < sizeof(desc.data_bytes); iii++) { desc.data_bytes[iii] = ddp[iii]; } desc.tsn_ifany = dcp->dp.tsn; } else { /* nope we are done. */ SCTP_STAT_INCR(sctps_pdrpnedat); break; } } else { if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)) SCTP_STAT_INCR(sctps_pdrpmbct); } if (process_chunk_drop(stcb, &desc, net, cp->ch.chunk_flags)) { SCTP_STAT_INCR(sctps_pdrppdbrk); break; } if (SCTP_SIZE32(at) > chlen) { break; } chlen -= SCTP_SIZE32(at); if (chlen < sizeof(struct sctp_chunkhdr)) { /* done, none left */ break; } ch = (struct sctp_chunkhdr *)((caddr_t)ch + SCTP_SIZE32(at)); } /* Now update any rwnd --- possibly */ if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) == 0) { /* From a peer, we get a rwnd report */ uint32_t a_rwnd; SCTP_STAT_INCR(sctps_pdrpfehos); bottle_bw = ntohl(cp->bottle_bw); on_queue = ntohl(cp->current_onq); if (bottle_bw && on_queue) { /* a rwnd report is in here */ if (bottle_bw > on_queue) a_rwnd = bottle_bw - on_queue; else a_rwnd = 0; if (a_rwnd == 0) stcb->asoc.peers_rwnd = 0; else { if (a_rwnd > stcb->asoc.total_flight) { stcb->asoc.peers_rwnd = a_rwnd - stcb->asoc.total_flight; } else { stcb->asoc.peers_rwnd = 0; } if (stcb->asoc.peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ stcb->asoc.peers_rwnd = 0; } } } } else { SCTP_STAT_INCR(sctps_pdrpfmbox); } /* now middle boxes in sat networks get a cwnd bump */ if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) && (stcb->asoc.sat_t3_loss_recovery == 0) && (stcb->asoc.sat_network)) { /* * This is debateable but for sat networks it makes sense * Note if a T3 timer has went off, we will prohibit any * changes to cwnd until we exit the t3 loss recovery. */ uint32_t bw_avail; int rtt, incr; #ifdef SCTP_CWND_MONITOR int old_cwnd = net->cwnd; #endif /* need real RTT for this calc */ rtt = ((net->lastsa >> 2) + net->lastsv) >> 1; /* get bottle neck bw */ bottle_bw = ntohl(cp->bottle_bw); /* and whats on queue */ on_queue = ntohl(cp->current_onq); /* * adjust the on-queue if our flight is more it could be * that the router has not yet gotten data "in-flight" to it */ if (on_queue < net->flight_size) on_queue = net->flight_size; /* calculate the available space */ bw_avail = (bottle_bw * rtt) / 1000; if (bw_avail > bottle_bw) { /* * Cap the growth to no more than the bottle neck. * This can happen as RTT slides up due to queues. * It also means if you have more than a 1 second * RTT with a empty queue you will be limited to the * bottle_bw per second no matter if other points * have 1/2 the RTT and you could get more out... */ bw_avail = bottle_bw; } if (on_queue > bw_avail) { /* * No room for anything else don't allow anything * else to be "added to the fire". */ int seg_inflight, seg_onqueue, my_portion; net->partial_bytes_acked = 0; /* how much are we over queue size? */ incr = on_queue - bw_avail; if (stcb->asoc.seen_a_sack_this_pkt) { /* * undo any cwnd adjustment that the sack * might have made */ net->cwnd = net->prev_cwnd; } /* Now how much of that is mine? */ seg_inflight = net->flight_size / net->mtu; seg_onqueue = on_queue / net->mtu; my_portion = (incr * seg_inflight) / seg_onqueue; /* Have I made an adjustment already */ if (net->cwnd > net->flight_size) { /* * for this flight I made an adjustment we * need to decrease the portion by a share * our previous adjustment. */ int diff_adj; diff_adj = net->cwnd - net->flight_size; if (diff_adj > my_portion) my_portion = 0; else my_portion -= diff_adj; } /* * back down to the previous cwnd (assume we have * had a sack before this packet). minus what ever * portion of the overage is my fault. */ net->cwnd -= my_portion; /* we will NOT back down more than 1 MTU */ if (net->cwnd <= net->mtu) { net->cwnd = net->mtu; } /* force into CA */ net->ssthresh = net->cwnd - 1; } else { /* * Take 1/4 of the space left or max burst up .. * whichever is less. */ incr = min((bw_avail - on_queue) >> 2, (int)stcb->asoc.max_burst * (int)net->mtu); net->cwnd += incr; } if (net->cwnd > bw_avail) { /* We can't exceed the pipe size */ net->cwnd = bw_avail; } if (net->cwnd < net->mtu) { /* We always have 1 MTU */ net->cwnd = net->mtu; } #ifdef SCTP_CWND_MONITOR if (net->cwnd - old_cwnd != 0) { /* log only changes */ sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_SAT); } #endif } } /* * handles all control chunks in a packet inputs: - m: mbuf chain, assumed to * still contain IP/SCTP header - stcb: is the tcb found for this packet - * offset: offset into the mbuf chain to first chunkhdr - length: is the * length of the complete packet outputs: - length: modified to remaining * length after control processing - netp: modified to new sctp_nets after * cookie-echo processing - return NULL to discard the packet (ie. no asoc, * bad packet,...) otherwise return the tcb for this packet */ static struct sctp_tcb * sctp_process_control(struct mbuf *m, int iphlen, int *offset, int length, struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets **netp, int *fwd_tsn_seen, uint32_t vrf_id, uint32_t table_id) { struct sctp_association *asoc; uint32_t vtag_in; int num_chunks = 0; /* number of control chunks processed */ int chk_length; int ret; int abort_no_unlock = 0; /* * How big should this be, and should it be alloc'd? Lets try the * d-mtu-ceiling for now (2k) and that should hopefully work ... * until we get into jumbo grams and such.. */ uint8_t chunk_buf[SCTP_CHUNK_BUFFER_SIZE]; struct sctp_tcb *locked_tcb = stcb; int got_auth = 0; uint32_t auth_offset = 0, auth_len = 0; int auth_skipped = 0; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("sctp_process_control: iphlen=%u, offset=%u, length=%u stcb:%p\n", iphlen, *offset, length, stcb); } #endif /* SCTP_DEBUG */ /* validate chunk header length... */ if (ntohs(ch->chunk_length) < sizeof(*ch)) { return (NULL); } /* * validate the verification tag */ vtag_in = ntohl(sh->v_tag); if (locked_tcb) { SCTP_TCB_LOCK_ASSERT(locked_tcb); } if (ch->chunk_type == SCTP_INITIATION) { if (vtag_in != 0) { /* protocol error- silently discard... */ SCTP_STAT_INCR(sctps_badvtag); - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } } else if (ch->chunk_type != SCTP_COOKIE_ECHO) { /* * If there is no stcb, skip the AUTH chunk and process * later after a stcb is found (to validate the lookup was * valid. */ if ((ch->chunk_type == SCTP_AUTHENTICATION) && (stcb == NULL) && !sctp_auth_disable) { /* save this chunk for later processing */ auth_skipped = 1; auth_offset = *offset; auth_len = ntohs(ch->chunk_length); /* (temporarily) move past this chunk */ *offset += SCTP_SIZE32(auth_len); if (*offset >= length) { /* no more data left in the mbuf chain */ *offset = length; return (NULL); } ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, sizeof(struct sctp_chunkhdr), chunk_buf); } if (ch->chunk_type == SCTP_COOKIE_ECHO) { goto process_control_chunks; } /* * first check if it's an ASCONF with an unknown src addr we * need to look inside to find the association */ if (ch->chunk_type == SCTP_ASCONF && stcb == NULL) { /* inp's refcount may be reduced */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_asconf(m, iphlen, *offset, sh, &inp, netp); if (stcb == NULL) { /* * reduce inp's refcount if not reduced in * sctp_findassociation_ep_asconf(). */ SCTP_INP_DECR_REF(inp); } /* now go back and verify any auth chunk to be sure */ if (auth_skipped && (stcb != NULL)) { struct sctp_auth_chunk *auth; auth = (struct sctp_auth_chunk *) sctp_m_getptr(m, auth_offset, auth_len, chunk_buf); got_auth = 1; auth_skipped = 0; if (sctp_handle_auth(stcb, auth, m, auth_offset)) { /* auth HMAC failed so dump it */ *offset = length; return (NULL); } else { /* remaining chunks are HMAC checked */ stcb->asoc.authenticated = 1; } } } if (stcb == NULL) { /* no association, so it's out of the blue... */ sctp_handle_ootb(m, iphlen, *offset, sh, inp, NULL, vrf_id, table_id); *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } asoc = &stcb->asoc; /* ABORT and SHUTDOWN can use either v_tag... */ if ((ch->chunk_type == SCTP_ABORT_ASSOCIATION) || (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) || (ch->chunk_type == SCTP_PACKET_DROPPED)) { if ((vtag_in == asoc->my_vtag) || ((ch->chunk_flags & SCTP_HAD_NO_TCB) && (vtag_in == asoc->peer_vtag))) { /* this is valid */ } else { /* drop this packet... */ SCTP_STAT_INCR(sctps_badvtag); - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } } else if (ch->chunk_type == SCTP_SHUTDOWN_ACK) { if (vtag_in != asoc->my_vtag) { /* * this could be a stale SHUTDOWN-ACK or the * peer never got the SHUTDOWN-COMPLETE and * is still hung; we have started a new asoc * but it won't complete until the shutdown * is completed */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } sctp_handle_ootb(m, iphlen, *offset, sh, inp, NULL, vrf_id, table_id); return (NULL); } } else { /* for all other chunks, vtag must match */ if (vtag_in != asoc->my_vtag) { /* invalid vtag... */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("invalid vtag: %xh, expect %xh\n", vtag_in, asoc->my_vtag); } #endif /* SCTP_DEBUG */ SCTP_STAT_INCR(sctps_badvtag); - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } } } /* end if !SCTP_COOKIE_ECHO */ /* * process all control chunks... */ if (((ch->chunk_type == SCTP_SELECTIVE_ACK) || (ch->chunk_type == SCTP_HEARTBEAT_REQUEST)) && (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED)) { /* implied cookie-ack.. we must have lost the ack */ stcb->asoc.overall_error_count = 0; sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, *netp); } process_control_chunks: while (IS_SCTP_CONTROL(ch)) { /* validate chunk length */ chk_length = ntohs(ch->chunk_length); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT2) { printf("sctp_process_control: processing a chunk type=%u, len=%u\n", ch->chunk_type, chk_length); } #endif /* SCTP_DEBUG */ if ((size_t)chk_length < sizeof(*ch) || (*offset + chk_length) > length) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } SCTP_STAT_INCR_COUNTER64(sctps_incontrolchunks); /* * INIT-ACK only gets the init ack "header" portion only * because we don't have to process the peer's COOKIE. All * others get a complete chunk. */ if ((ch->chunk_type == SCTP_INITIATION_ACK) || (ch->chunk_type == SCTP_INITIATION)) { /* get an init-ack chunk */ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, sizeof(struct sctp_init_ack_chunk), chunk_buf); if (ch == NULL) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } } else if (ch->chunk_type == SCTP_COOKIE_ECHO) { if (chk_length > sizeof(chunk_buf)) { /* * use just the size of the chunk buffer so * the front part of our cookie is intact. * The rest of cookie processing should use * the sctp_m_getptr() function to access * the other parts. */ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, (sizeof(chunk_buf) - 4), chunk_buf); if (ch == NULL) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } } else { /* We can fit it all */ goto all_fits; } } else { /* get a complete chunk... */ if ((size_t)chk_length > sizeof(chunk_buf)) { struct mbuf *oper; struct sctp_paramhdr *phdr; oper = NULL; if (stcb) { oper = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { /* pre-reserve some space */ SCTP_BUF_RESV_UF(oper, sizeof(struct sctp_chunkhdr)); SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr); phdr = mtod(oper, struct sctp_paramhdr *); phdr->param_type = htons(SCTP_CAUSE_OUT_OF_RESC); phdr->param_length = htons(sizeof(struct sctp_paramhdr)); sctp_queue_op_err(stcb, oper); } } - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } all_fits: ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, chk_length, chunk_buf); if (ch == NULL) { printf("sctp_process_control: Can't get the all data....\n"); *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } } num_chunks++; /* Save off the last place we got a control from */ if (stcb != NULL) { if ((*netp != NULL) || (ch->chunk_type == SCTP_ASCONF)) { /* * allow last_control to be NULL if * ASCONF... ASCONF processing will find the * right net later */ stcb->asoc.last_control_chunk_from = *netp; } } #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xB0, ch->chunk_type); #endif /* check to see if this chunk required auth, but isn't */ if ((stcb != NULL) && !sctp_auth_disable && sctp_auth_is_required_chunk(ch->chunk_type, stcb->asoc.local_auth_chunks) && !stcb->asoc.authenticated) { /* "silently" ignore */ SCTP_STAT_INCR(sctps_recvauthmissing); goto next_chunk; } switch (ch->chunk_type) { case SCTP_INITIATION: /* must be first and only chunk */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_INIT\n"); } #endif /* SCTP_DEBUG */ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* We are not interested anymore? */ if ((stcb) && (stcb->asoc.total_output_queue_size)) { /* * collision case where we are * sending to them too */ ; } else { - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } } if ((num_chunks > 1) || (sctp_strict_init && (length - *offset > SCTP_SIZE32(chk_length)))) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } if ((stcb != NULL) && (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT)) { sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); *offset = length; sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC); - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } sctp_handle_init(m, iphlen, *offset, sh, (struct sctp_init_chunk *)ch, inp, stcb, *netp, &abort_no_unlock, vrf_id, table_id); if (abort_no_unlock) return (NULL); *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); break; case SCTP_INITIATION_ACK: /* must be first and only chunk */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_INIT-ACK\n"); } #endif /* SCTP_DEBUG */ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* We are not interested anymore */ if ((stcb) && (stcb->asoc.total_output_queue_size)) { ; } else { - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; if (stcb) { sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_26); } return (NULL); } } if ((num_chunks > 1) || (sctp_strict_init && (length - *offset > SCTP_SIZE32(chk_length)))) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } ret = sctp_handle_init_ack(m, iphlen, *offset, sh, (struct sctp_init_ack_chunk *)ch, stcb, *netp, &abort_no_unlock, vrf_id, table_id); /* * Special case, I must call the output routine to * get the cookie echoed */ if (abort_no_unlock) return (NULL); if ((stcb) && ret == 0) sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC); *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); break; case SCTP_SELECTIVE_ACK: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_SACK\n"); } #endif /* SCTP_DEBUG */ SCTP_STAT_INCR(sctps_recvsacks); { struct sctp_sack_chunk *sack; int abort_now = 0; uint32_t a_rwnd, cum_ack; uint16_t num_seg; int nonce_sum_flag; if (chk_length < sizeof(struct sctp_sack_chunk)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INDATA1) { printf("Bad size on sack chunk .. to small\n"); } #endif *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); - + } return (NULL); } sack = (struct sctp_sack_chunk *)ch; nonce_sum_flag = ch->chunk_flags & SCTP_SACK_NONCE_SUM; cum_ack = ntohl(sack->sack.cum_tsn_ack); num_seg = ntohs(sack->sack.num_gap_ack_blks); a_rwnd = (uint32_t) ntohl(sack->sack.a_rwnd); stcb->asoc.seen_a_sack_this_pkt = 1; if ((stcb->asoc.pr_sctp_cnt == 0) && (num_seg == 0) && ((compare_with_wrap(cum_ack, stcb->asoc.last_acked_seq, MAX_TSN)) || (cum_ack == stcb->asoc.last_acked_seq)) && (stcb->asoc.saw_sack_with_frags == 0) && (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) ) { /* * We have a SIMPLE sack having no * prior segments and data on sent * queue to be acked.. Use the * faster path sack processing. We * also allow window update sacks * with no missing segments to go * this way too. */ sctp_express_handle_sack(stcb, cum_ack, a_rwnd, nonce_sum_flag, &abort_now); } else { sctp_handle_sack(sack, stcb, *netp, &abort_now, chk_length, a_rwnd); } if (abort_now) { /* ABORT signal from sack processing */ *offset = length; return (NULL); } } break; case SCTP_HEARTBEAT_REQUEST: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_HEARTBEAT\n"); } #endif /* SCTP_DEBUG */ SCTP_STAT_INCR(sctps_recvheartbeat); sctp_send_heartbeat_ack(stcb, m, *offset, chk_length, *netp); /* He's alive so give him credit */ stcb->asoc.overall_error_count = 0; break; case SCTP_HEARTBEAT_ACK: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_HEARTBEAT-ACK\n"); } #endif /* SCTP_DEBUG */ if (chk_length != sizeof(struct sctp_heartbeat_chunk)) { /* Its not ours */ *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } /* He's alive so give him credit */ stcb->asoc.overall_error_count = 0; SCTP_STAT_INCR(sctps_recvheartbeatack); sctp_handle_heartbeat_ack((struct sctp_heartbeat_chunk *)ch, stcb, *netp); break; case SCTP_ABORT_ASSOCIATION: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_ABORT\n"); } #endif /* SCTP_DEBUG */ sctp_handle_abort((struct sctp_abort_chunk *)ch, stcb, *netp); *offset = length; return (NULL); break; case SCTP_SHUTDOWN: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_SHUTDOWN\n"); } #endif /* SCTP_DEBUG */ if (chk_length != sizeof(struct sctp_shutdown_chunk)) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } { int abort_flag = 0; sctp_handle_shutdown((struct sctp_shutdown_chunk *)ch, stcb, *netp, &abort_flag); if (abort_flag) { *offset = length; return (NULL); } } break; case SCTP_SHUTDOWN_ACK: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_SHUTDOWN-ACK\n"); } #endif /* SCTP_DEBUG */ sctp_handle_shutdown_ack((struct sctp_shutdown_ack_chunk *)ch, stcb, *netp); *offset = length; return (NULL); break; case SCTP_OPERATION_ERROR: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_OP-ERR\n"); } #endif /* SCTP_DEBUG */ - if (sctp_handle_error(ch, stcb, *netp) < 0) { + if ((stcb) && sctp_handle_error(ch, stcb, *netp) < 0) { *offset = length; return (NULL); } break; case SCTP_COOKIE_ECHO: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_COOKIE-ECHO stcb is %p\n", stcb); } #endif /* SCTP_DEBUG */ if ((stcb) && (stcb->asoc.total_output_queue_size)) { ; } else { if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) && (stcb == NULL)) { /* We are not interested anymore */ *offset = length; return (NULL); } } /* * First are we accepting? We do this again here * since it is possible that a previous endpoint WAS * listening responded to a INIT-ACK and then * closed. We opened and bound.. and are now no * longer listening. */ if (inp->sctp_socket->so_qlimit == 0) { if ((stcb) && (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* * special case, is this a retran'd * COOKIE-ECHO or a restarting assoc * that is a peeled off or * one-to-one style socket. */ goto process_cookie_anyway; } sctp_abort_association(inp, stcb, m, iphlen, sh, NULL, vrf_id, table_id); *offset = length; return (NULL); } else if (inp->sctp_socket->so_qlimit) { /* we are accepting so check limits like TCP */ if (inp->sctp_socket->so_qlen > inp->sctp_socket->so_qlimit) { /* no space */ struct mbuf *oper; struct sctp_paramhdr *phdr; if (sctp_abort_if_one_2_one_hits_limit) { oper = NULL; oper = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr); phdr = mtod(oper, struct sctp_paramhdr *); phdr->param_type = htons(SCTP_CAUSE_OUT_OF_RESC); phdr->param_length = htons(sizeof(struct sctp_paramhdr)); } sctp_abort_association(inp, stcb, m, iphlen, sh, oper, vrf_id, table_id); } *offset = length; return (NULL); } } process_cookie_anyway: { struct mbuf *ret_buf; struct sctp_inpcb *linp; if (stcb) linp = NULL; else linp = inp; if (linp) SCTP_ASOC_CREATE_LOCK(linp); ret_buf = sctp_handle_cookie_echo(m, iphlen, *offset, sh, (struct sctp_cookie_echo_chunk *)ch, &inp, &stcb, netp, auth_skipped, auth_offset, auth_len, &locked_tcb, vrf_id, table_id); if (linp) SCTP_ASOC_CREATE_UNLOCK(linp); if (ret_buf == NULL) { if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("GAK, null buffer\n"); } #endif /* SCTP_DEBUG */ auth_skipped = 0; *offset = length; return (NULL); } /* if AUTH skipped, see if it verified... */ if (auth_skipped) { got_auth = 1; auth_skipped = 0; } if (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) { /* * Restart the timer if we have * pending data */ struct sctp_tmit_chunk *chk; chk = TAILQ_FIRST(&stcb->asoc.sent_queue); if (chk) { sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo); } } } break; case SCTP_COOKIE_ACK: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_COOKIE-ACK\n"); } #endif /* SCTP_DEBUG */ if (chk_length != sizeof(struct sctp_cookie_ack_chunk)) { - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } return (NULL); } if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* We are not interested anymore */ if ((stcb) && (stcb->asoc.total_output_queue_size)) { ; } else { sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_27); *offset = length; return (NULL); } } /* He's alive so give him credit */ - stcb->asoc.overall_error_count = 0; - sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, *netp); + if (stcb) { + stcb->asoc.overall_error_count = 0; + sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, *netp); + } break; case SCTP_ECN_ECHO: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_ECN-ECHO\n"); } #endif /* SCTP_DEBUG */ /* He's alive so give him credit */ if (chk_length != sizeof(struct sctp_ecne_chunk)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } - stcb->asoc.overall_error_count = 0; - sctp_handle_ecn_echo((struct sctp_ecne_chunk *)ch, - stcb); + if (stcb) { + stcb->asoc.overall_error_count = 0; + sctp_handle_ecn_echo((struct sctp_ecne_chunk *)ch, + stcb); + } break; case SCTP_ECN_CWR: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_ECN-CWR\n"); } #endif /* SCTP_DEBUG */ /* He's alive so give him credit */ if (chk_length != sizeof(struct sctp_cwr_chunk)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } - stcb->asoc.overall_error_count = 0; - - sctp_handle_ecn_cwr((struct sctp_cwr_chunk *)ch, stcb); + if (stcb) { + stcb->asoc.overall_error_count = 0; + sctp_handle_ecn_cwr((struct sctp_cwr_chunk *)ch, stcb); + } break; case SCTP_SHUTDOWN_COMPLETE: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_SHUTDOWN-COMPLETE\n"); } #endif /* SCTP_DEBUG */ /* must be first and only chunk */ if ((num_chunks > 1) || (length - *offset > SCTP_SIZE32(chk_length))) { *offset = length; - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); - + } return (NULL); } - sctp_handle_shutdown_complete((struct sctp_shutdown_complete_chunk *)ch, - stcb, *netp); + if (stcb) { + sctp_handle_shutdown_complete((struct sctp_shutdown_complete_chunk *)ch, + stcb, *netp); + } *offset = length; return (NULL); break; case SCTP_ASCONF: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_ASCONF\n"); } #endif /* SCTP_DEBUG */ /* He's alive so give him credit */ - stcb->asoc.overall_error_count = 0; - - sctp_handle_asconf(m, *offset, - (struct sctp_asconf_chunk *)ch, stcb); + if (stcb) { + stcb->asoc.overall_error_count = 0; + sctp_handle_asconf(m, *offset, + (struct sctp_asconf_chunk *)ch, stcb); + } break; case SCTP_ASCONF_ACK: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_ASCONF-ACK\n"); } #endif /* SCTP_DEBUG */ if (chk_length < sizeof(struct sctp_asconf_ack_chunk)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } - /* He's alive so give him credit */ - stcb->asoc.overall_error_count = 0; - - sctp_handle_asconf_ack(m, *offset, - (struct sctp_asconf_ack_chunk *)ch, stcb, *netp); + if (stcb) { + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + sctp_handle_asconf_ack(m, *offset, + (struct sctp_asconf_ack_chunk *)ch, stcb, *netp); + } break; case SCTP_FORWARD_CUM_TSN: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_FWD-TSN\n"); } #endif /* SCTP_DEBUG */ if (chk_length < sizeof(struct sctp_forward_tsn_chunk)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } /* He's alive so give him credit */ - { + if (stcb) { int abort_flag = 0; stcb->asoc.overall_error_count = 0; *fwd_tsn_seen = 1; if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* We are not interested anymore */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_28); *offset = length; return (NULL); } sctp_handle_forward_tsn(stcb, (struct sctp_forward_tsn_chunk *)ch, &abort_flag); if (abort_flag) { *offset = length; return (NULL); } else { stcb->asoc.overall_error_count = 0; } } break; case SCTP_STREAM_RESET: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_STREAM_RESET\n"); } #endif /* SCTP_DEBUG */ ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, chk_length, chunk_buf); if (chk_length < sizeof(struct sctp_stream_reset_tsn_req)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* We are not interested anymore */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_INPUT + SCTP_LOC_29); *offset = length; return (NULL); } - if (stcb->asoc.peer_supports_strreset == 0) { - /* - * hmm, peer should have announced this, but - * we will turn it on since he is sending us - * a stream reset. - */ - stcb->asoc.peer_supports_strreset = 1; - } - if (sctp_handle_stream_reset(stcb, (struct sctp_stream_reset_out_req *)ch)) { - /* stop processing */ - *offset = length; - return (NULL); + if (stcb) { + if (stcb->asoc.peer_supports_strreset == 0) { + /* + * hmm, peer should have announced + * this, but we will turn it on + * since he is sending us a stream + * reset. + */ + stcb->asoc.peer_supports_strreset = 1; + } + if (sctp_handle_stream_reset(stcb, (struct sctp_stream_reset_out_req *)ch)) { + /* stop processing */ + *offset = length; + return (NULL); + } } break; case SCTP_PACKET_DROPPED: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_PACKET_DROPPED\n"); } #endif /* SCTP_DEBUG */ /* re-get it all please */ if (chk_length < sizeof(struct sctp_pktdrop_chunk)) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, chk_length, chunk_buf); - sctp_handle_packet_dropped((struct sctp_pktdrop_chunk *)ch, - stcb, *netp); - + if ((stcb) && (*netp)) { + sctp_handle_packet_dropped((struct sctp_pktdrop_chunk *)ch, + stcb, *netp); + } break; case SCTP_AUTHENTICATION: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("SCTP_AUTHENTICATION\n"); } #endif /* SCTP_DEBUG */ if (sctp_auth_disable) goto unknown_chunk; if (stcb == NULL) { /* save the first AUTH for later processing */ if (auth_skipped == 0) { auth_offset = *offset; auth_len = chk_length; auth_skipped = 1; } /* skip this chunk (temporarily) */ goto next_chunk; } if ((chk_length < (sizeof(struct sctp_auth_chunk))) || (chk_length > (sizeof(struct sctp_auth_chunk) + SCTP_AUTH_DIGEST_LEN_MAX))) { /* Its not ours */ - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } if (got_auth == 1) { /* skip this chunk... it's already auth'd */ goto next_chunk; } ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, chk_length, chunk_buf); got_auth = 1; if (sctp_handle_auth(stcb, (struct sctp_auth_chunk *)ch, m, *offset)) { /* auth HMAC failed so dump the packet */ *offset = length; return (stcb); } else { /* remaining chunks are HMAC checked */ stcb->asoc.authenticated = 1; } break; default: unknown_chunk: /* it's an unknown chunk! */ if ((ch->chunk_type & 0x40) && (stcb != NULL)) { struct mbuf *mm; struct sctp_paramhdr *phd; mm = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (mm) { phd = mtod(mm, struct sctp_paramhdr *); /* * We cheat and use param type since * we did not bother to define a * error cause struct. They are the * same basic format with different * names. */ phd->param_type = htons(SCTP_CAUSE_UNRECOG_CHUNK); phd->param_length = htons(chk_length + sizeof(*phd)); SCTP_BUF_LEN(mm) = sizeof(*phd); SCTP_BUF_NEXT(mm) = SCTP_M_COPYM(m, *offset, SCTP_SIZE32(chk_length), M_DONTWAIT); if (SCTP_BUF_NEXT(mm)) { sctp_queue_op_err(stcb, mm); } else { sctp_m_freem(mm); } } } if ((ch->chunk_type & 0x80) == 0) { /* discard this packet */ *offset = length; return (stcb); } /* else skip this bad chunk and continue... */ break; } /* switch (ch->chunk_type) */ next_chunk: /* get the next chunk */ *offset += SCTP_SIZE32(chk_length); if (*offset >= length) { /* no more data left in the mbuf chain */ break; } ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, sizeof(struct sctp_chunkhdr), chunk_buf); if (ch == NULL) { - if (locked_tcb) + if (locked_tcb) { SCTP_TCB_UNLOCK(locked_tcb); + } *offset = length; return (NULL); } } /* while */ return (stcb); } /* * Process the ECN bits we have something set so we must look to see if it is * ECN(0) or ECN(1) or CE */ static __inline void sctp_process_ecn_marked_a(struct sctp_tcb *stcb, struct sctp_nets *net, uint8_t ecn_bits) { if ((ecn_bits & SCTP_CE_BITS) == SCTP_CE_BITS) { ; } else if ((ecn_bits & SCTP_ECT1_BIT) == SCTP_ECT1_BIT) { /* * we only add to the nonce sum for ECT1, ECT0 does not * change the NS bit (that we have yet to find a way to send * it yet). */ /* ECN Nonce stuff */ stcb->asoc.receiver_nonce_sum++; stcb->asoc.receiver_nonce_sum &= SCTP_SACK_NONCE_SUM; /* * Drag up the last_echo point if cumack is larger since we * don't want the point falling way behind by more than * 2^^31 and then having it be incorrect. */ if (compare_with_wrap(stcb->asoc.cumulative_tsn, stcb->asoc.last_echo_tsn, MAX_TSN)) { stcb->asoc.last_echo_tsn = stcb->asoc.cumulative_tsn; } } else if ((ecn_bits & SCTP_ECT0_BIT) == SCTP_ECT0_BIT) { /* * Drag up the last_echo point if cumack is larger since we * don't want the point falling way behind by more than * 2^^31 and then having it be incorrect. */ if (compare_with_wrap(stcb->asoc.cumulative_tsn, stcb->asoc.last_echo_tsn, MAX_TSN)) { stcb->asoc.last_echo_tsn = stcb->asoc.cumulative_tsn; } } } static __inline void sctp_process_ecn_marked_b(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t high_tsn, uint8_t ecn_bits) { if ((ecn_bits & SCTP_CE_BITS) == SCTP_CE_BITS) { /* * we possibly must notify the sender that a congestion * window reduction is in order. We do this by adding a ECNE * chunk to the output chunk queue. The incoming CWR will * remove this chunk. */ if (compare_with_wrap(high_tsn, stcb->asoc.last_echo_tsn, MAX_TSN)) { /* Yep, we need to add a ECNE */ sctp_send_ecn_echo(stcb, net, high_tsn); stcb->asoc.last_echo_tsn = high_tsn; } } } /* * common input chunk processing (v4 and v6) */ -int +void sctp_common_input_processing(struct mbuf **mm, int iphlen, int offset, int length, struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, uint8_t ecn_bits, uint32_t vrf_id, uint32_t table_id) { /* * Control chunk processing */ uint32_t high_tsn; int fwd_tsn_seen = 0, data_processed = 0; struct mbuf *m = *mm; int abort_flag = 0; int un_sent; SCTP_STAT_INCR(sctps_recvdatagrams); #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xE0, 1); sctp_auditing(0, inp, stcb, net); #endif #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Ok, Common input processing called, m:%p iphlen:%d offset:%d\n", m, iphlen, offset); } #endif /* SCTP_DEBUG */ if (stcb) { /* always clear this before beginning a packet */ stcb->asoc.authenticated = 0; stcb->asoc.seen_a_sack_this_pkt = 0; } if (IS_SCTP_CONTROL(ch)) { /* process the control portion of the SCTP packet */ stcb = sctp_process_control(m, iphlen, &offset, length, sh, ch, inp, stcb, &net, &fwd_tsn_seen, vrf_id, table_id); if (stcb) { /* * This covers us if the cookie-echo was there and * it changes our INP. */ inp = stcb->sctp_ep; } } else { /* * no control chunks, so pre-process DATA chunks (these * checks are taken care of by control processing) */ /* * if DATA only packet, and auth is required, then punt... * can't have authenticated without any AUTH (control) * chunks */ if ((stcb != NULL) && !sctp_auth_disable && sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.local_auth_chunks)) { /* "silently" ignore */ SCTP_STAT_INCR(sctps_recvauthmissing); SCTP_TCB_UNLOCK(stcb); - return (1); + return; } if (stcb == NULL) { /* out of the blue DATA chunk */ sctp_handle_ootb(m, iphlen, offset, sh, inp, NULL, vrf_id, table_id); - return (1); + return; } if (stcb->asoc.my_vtag != ntohl(sh->v_tag)) { /* v_tag mismatch! */ SCTP_STAT_INCR(sctps_badvtag); SCTP_TCB_UNLOCK(stcb); - return (1); + return; } } if (stcb == NULL) { /* * no valid TCB for this packet, or we found it's a bad * packet while processing control, or we're done with this * packet (done or skip rest of data), so we drop it... */ - return (1); + return; } /* * DATA chunk processing */ /* plow through the data chunks while length > offset */ /* * Rest should be DATA only. Check authentication state if AUTH for * DATA is required. */ if ((length > offset) && (stcb != NULL) && !sctp_auth_disable && sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.local_auth_chunks) && !stcb->asoc.authenticated) { /* "silently" ignore */ SCTP_STAT_INCR(sctps_recvauthmissing); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("Data chunk requires AUTH, skipped\n"); #endif goto trigger_send; } if (length > offset) { int retval; /* * First check to make sure our state is correct. We would * not get here unless we really did have a tag, so we don't * abort if this happens, just dump the chunk silently. */ switch (SCTP_GET_STATE(&stcb->asoc)) { case SCTP_STATE_COOKIE_ECHOED: /* * we consider data with valid tags in this state * shows us the cookie-ack was lost. Imply it was * there. */ stcb->asoc.overall_error_count = 0; sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, net); break; case SCTP_STATE_COOKIE_WAIT: /* * We consider OOTB any data sent during asoc setup. */ sctp_handle_ootb(m, iphlen, offset, sh, inp, NULL, vrf_id, table_id); SCTP_TCB_UNLOCK(stcb); - return (1); + return; break; case SCTP_STATE_EMPTY: /* should not happen */ case SCTP_STATE_INUSE: /* should not happen */ case SCTP_STATE_SHUTDOWN_RECEIVED: /* This is a peer error */ case SCTP_STATE_SHUTDOWN_ACK_SENT: default: SCTP_TCB_UNLOCK(stcb); - return (1); + return; break; case SCTP_STATE_OPEN: case SCTP_STATE_SHUTDOWN_SENT: break; } /* take care of ECN, part 1. */ if (stcb->asoc.ecn_allowed && (ecn_bits & (SCTP_ECT0_BIT | SCTP_ECT1_BIT))) { sctp_process_ecn_marked_a(stcb, net, ecn_bits); } /* plow through the data chunks while length > offset */ retval = sctp_process_data(mm, iphlen, &offset, length, sh, inp, stcb, net, &high_tsn); if (retval == 2) { /* * The association aborted, NO UNLOCK needed since * the association is destroyed. */ - return (0); + return; } data_processed = 1; if (retval == 0) { /* take care of ecn part 2. */ if (stcb->asoc.ecn_allowed && (ecn_bits & (SCTP_ECT0_BIT | SCTP_ECT1_BIT))) { sctp_process_ecn_marked_b(stcb, net, high_tsn, ecn_bits); } } /* * Anything important needs to have been m_copy'ed in * process_data */ } if ((data_processed == 0) && (fwd_tsn_seen)) { int was_a_gap = 0; if (compare_with_wrap(stcb->asoc.highest_tsn_inside_map, stcb->asoc.cumulative_tsn, MAX_TSN)) { /* there was a gap before this data was processed */ was_a_gap = 1; } sctp_sack_check(stcb, 1, was_a_gap, &abort_flag); if (abort_flag) { /* Again, we aborted so NO UNLOCK needed */ - return (0); + return; } } /* trigger send of any chunks in queue... */ trigger_send: #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xE0, 2); sctp_auditing(1, inp, stcb, net); #endif #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Check for chunk output prw:%d tqe:%d tf=%d\n", stcb->asoc.peers_rwnd, TAILQ_EMPTY(&stcb->asoc.control_send_queue), stcb->asoc.total_flight); } #endif un_sent = (stcb->asoc.total_output_queue_size - stcb->asoc.total_flight); if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue) || ((un_sent) && (stcb->asoc.peers_rwnd > 0 || (stcb->asoc.peers_rwnd <= 0 && stcb->asoc.total_flight == 0)))) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("Calling chunk OUTPUT\n"); } #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CONTROL_PROC); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("chunk OUTPUT returns\n"); } #endif } #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xE0, 3); sctp_auditing(2, inp, stcb, net); #endif SCTP_TCB_UNLOCK(stcb); - return (0); + return; } void sctp_input(i_pak, off) struct mbuf *i_pak; int off; { #ifdef SCTP_MBUF_LOGGING struct mbuf *mat; #endif struct mbuf *m; int iphlen; uint32_t vrf_id = 0, table_id = 0; uint8_t ecn_bits; struct ip *ip; struct sctphdr *sh; struct sctp_inpcb *inp = NULL; uint32_t check, calc_check; struct sctp_nets *net; struct sctp_tcb *stcb = NULL; struct sctp_chunkhdr *ch; int refcount_up = 0; int length, mlen, offset; if (SCTP_GET_PKT_VRFID(i_pak, vrf_id)) { SCTP_RELEASE_PKT(i_pak); return; } if (SCTP_GET_PKT_TABLEID(i_pak, table_id)) { SCTP_RELEASE_PKT(i_pak); return; } mlen = SCTP_HEADER_LEN(i_pak); iphlen = off; m = SCTP_HEADER_TO_CHAIN(i_pak); net = NULL; SCTP_STAT_INCR(sctps_recvpackets); SCTP_STAT_INCR_COUNTER64(sctps_inpackets); #ifdef SCTP_MBUF_LOGGING /* Log in any input mbufs */ mat = m; while (mat) { if (SCTP_BUF_IS_EXTENDED(mat)) { sctp_log_mb(mat, SCTP_MBUF_INPUT); } mat = SCTP_BUF_NEXT(mat); } #endif /* * Get IP, SCTP, and first chunk header together in first mbuf. */ ip = mtod(m, struct ip *); offset = iphlen + sizeof(*sh) + sizeof(*ch); if (SCTP_BUF_LEN(m) < offset) { if ((m = m_pullup(m, offset)) == 0) { SCTP_STAT_INCR(sctps_hdrops); return; } ip = mtod(m, struct ip *); } sh = (struct sctphdr *)((caddr_t)ip + iphlen); ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(*sh)); /* SCTP does not allow broadcasts or multicasts */ if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { goto bad; } if (SCTP_IS_IT_BROADCAST(ip->ip_dst, m)) { /* * We only look at broadcast if its a front state, All * others we will not have a tcb for anyway. */ goto bad; } /* validate SCTP checksum */ if ((sctp_no_csum_on_loopback == 0) || !SCTP_IS_IT_LOOPBACK(m)) { /* * we do NOT validate things from the loopback if the sysctl * is set to 1. */ check = sh->checksum; /* save incoming checksum */ if ((check == 0) && (sctp_no_csum_on_loopback)) { /* * special hook for where we got a local address * somehow routed across a non IFT_LOOP type * interface */ if (ip->ip_src.s_addr == ip->ip_dst.s_addr) goto sctp_skip_csum_4; } sh->checksum = 0; /* prepare for calc */ calc_check = sctp_calculate_sum(m, &mlen, iphlen); if (calc_check != check) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Bad CSUM on SCTP packet calc_check:%x check:%x m:%p mlen:%d iphlen:%d\n", calc_check, check, m, mlen, iphlen); } #endif stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), sh, ch, &inp, &net, vrf_id); if ((inp) && (stcb)) { sctp_send_packet_dropped(stcb, net, m, iphlen, 1); sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_INPUT_ERROR); } else if ((inp != NULL) && (stcb == NULL)) { refcount_up = 1; } SCTP_STAT_INCR(sctps_badsum); SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors); goto bad; } sh->checksum = calc_check; } sctp_skip_csum_4: /* destination port of 0 is illegal, based on RFC2960. */ if (sh->dest_port == 0) { SCTP_STAT_INCR(sctps_hdrops); goto bad; } /* validate mbuf chain length with IP payload length */ if (mlen < (ip->ip_len - iphlen)) { SCTP_STAT_INCR(sctps_hdrops); goto bad; } /* * Locate pcb and tcb for datagram sctp_findassociation_addr() wants * IP/SCTP/first chunk header... */ stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), sh, ch, &inp, &net, vrf_id); /* inp's ref-count increased && stcb locked */ if (inp == NULL) { struct sctp_init_chunk *init_chk, chunk_buf; SCTP_STAT_INCR(sctps_noport); #ifdef ICMP_BANDLIM /* * we use the bandwidth limiting to protect against sending * too many ABORTS all at once. In this case these count the * same as an ICMP message. */ if (badport_bandlim(0) < 0) goto bad; #endif /* ICMP_BANDLIM */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Sending a ABORT from packet entry!\n"); } #endif if (ch->chunk_type == SCTP_INITIATION) { /* * we do a trick here to get the INIT tag, dig in * and get the tag from the INIT and put it in the * common header. */ init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, iphlen + sizeof(*sh), sizeof(*init_chk), (uint8_t *) & chunk_buf); if (init_chk != NULL) sh->v_tag = init_chk->init.initiate_tag; } if (ch->chunk_type == SCTP_SHUTDOWN_ACK) { sctp_send_shutdown_complete2(m, iphlen, sh, vrf_id, table_id); goto bad; } if (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) { goto bad; } if (ch->chunk_type != SCTP_ABORT_ASSOCIATION) sctp_send_abort(m, iphlen, sh, 0, NULL, vrf_id, table_id); goto bad; } else if (stcb == NULL) { refcount_up = 1; } #ifdef IPSEC /* * I very much doubt any of the IPSEC stuff will work but I have no * idea, so I will leave it in place. */ if (inp && ipsec4_in_reject(m, &inp->ip_inp.inp)) { ipsecstat.in_polvio++; SCTP_STAT_INCR(sctps_hdrops); goto bad; } #endif /* IPSEC */ /* * common chunk processing */ length = ip->ip_len + iphlen; offset -= sizeof(struct sctp_chunkhdr); ecn_bits = ip->ip_tos; sctp_common_input_processing(&m, iphlen, offset, length, sh, ch, inp, stcb, net, ecn_bits, vrf_id, table_id); /* inp's ref-count reduced && stcb unlocked */ if (m) { sctp_m_freem(m); } if ((inp) && (refcount_up)) { /* reduce ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } return; bad: if (stcb) SCTP_TCB_UNLOCK(stcb); if ((inp) && (refcount_up)) { /* reduce ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } if (m) { sctp_m_freem(m); } /* For BSD/MAC this does nothing */ SCTP_DETACH_HEADER_FROM_CHAIN(i_pak); SCTP_RELEASE_HEADER(i_pak); return; } diff --git a/sys/netinet/sctp_input.h b/sys/netinet/sctp_input.h index 047cab480239..192e3b183014 100644 --- a/sys/netinet/sctp_input.h +++ b/sys/netinet/sctp_input.h @@ -1,56 +1,56 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_input.h,v 1.6 2005/03/06 16:04:17 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #ifndef __sctp_input_h__ #define __sctp_input_h__ #if defined(_KERNEL) -int +void sctp_common_input_processing(struct mbuf **, int, int, int, struct sctphdr *, struct sctp_chunkhdr *, struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *, uint8_t, uint32_t, uint32_t); struct sctp_stream_reset_out_request * sctp_find_stream_reset(struct sctp_tcb *stcb, uint32_t seq, struct sctp_tmit_chunk **bchk); void sctp_reset_in_stream(struct sctp_tcb *stcb, int number_entries, uint16_t * list); #endif #endif diff --git a/sys/netinet/sctp_output.c b/sys/netinet/sctp_output.c index b13e9c4e8e6d..80fab0b0bcad 100644 --- a/sys/netinet/sctp_output.c +++ b/sys/netinet/sctp_output.c @@ -1,11956 +1,11957 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_output.c,v 1.46 2005/03/06 16:04:17 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SCTP_MAX_GAPS_INARRAY 4 struct sack_track { uint8_t right_edge; /* mergable on the right edge */ uint8_t left_edge; /* mergable on the left edge */ uint8_t num_entries; uint8_t spare; struct sctp_gap_ack_block gaps[SCTP_MAX_GAPS_INARRAY]; }; struct sack_track sack_array[256] = { {0, 0, 0, 0, /* 0x00 */ {{0, 0}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x01 */ {{0, 0}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x02 */ {{1, 1}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x03 */ {{0, 1}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x04 */ {{2, 2}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x05 */ {{0, 0}, {2, 2}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x06 */ {{1, 2}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x07 */ {{0, 2}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x08 */ {{3, 3}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x09 */ {{0, 0}, {3, 3}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x0a */ {{1, 1}, {3, 3}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x0b */ {{0, 1}, {3, 3}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x0c */ {{2, 3}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x0d */ {{0, 0}, {2, 3}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x0e */ {{1, 3}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x0f */ {{0, 3}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x10 */ {{4, 4}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x11 */ {{0, 0}, {4, 4}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x12 */ {{1, 1}, {4, 4}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x13 */ {{0, 1}, {4, 4}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x14 */ {{2, 2}, {4, 4}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x15 */ {{0, 0}, {2, 2}, {4, 4}, {0, 0} } }, {0, 0, 2, 0, /* 0x16 */ {{1, 2}, {4, 4}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x17 */ {{0, 2}, {4, 4}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x18 */ {{3, 4}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x19 */ {{0, 0}, {3, 4}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x1a */ {{1, 1}, {3, 4}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x1b */ {{0, 1}, {3, 4}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x1c */ {{2, 4}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x1d */ {{0, 0}, {2, 4}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x1e */ {{1, 4}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x1f */ {{0, 4}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x20 */ {{5, 5}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x21 */ {{0, 0}, {5, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x22 */ {{1, 1}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x23 */ {{0, 1}, {5, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x24 */ {{2, 2}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x25 */ {{0, 0}, {2, 2}, {5, 5}, {0, 0} } }, {0, 0, 2, 0, /* 0x26 */ {{1, 2}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x27 */ {{0, 2}, {5, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x28 */ {{3, 3}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x29 */ {{0, 0}, {3, 3}, {5, 5}, {0, 0} } }, {0, 0, 3, 0, /* 0x2a */ {{1, 1}, {3, 3}, {5, 5}, {0, 0} } }, {1, 0, 3, 0, /* 0x2b */ {{0, 1}, {3, 3}, {5, 5}, {0, 0} } }, {0, 0, 2, 0, /* 0x2c */ {{2, 3}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x2d */ {{0, 0}, {2, 3}, {5, 5}, {0, 0} } }, {0, 0, 2, 0, /* 0x2e */ {{1, 3}, {5, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x2f */ {{0, 3}, {5, 5}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x30 */ {{4, 5}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x31 */ {{0, 0}, {4, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x32 */ {{1, 1}, {4, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x33 */ {{0, 1}, {4, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x34 */ {{2, 2}, {4, 5}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x35 */ {{0, 0}, {2, 2}, {4, 5}, {0, 0} } }, {0, 0, 2, 0, /* 0x36 */ {{1, 2}, {4, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x37 */ {{0, 2}, {4, 5}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x38 */ {{3, 5}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x39 */ {{0, 0}, {3, 5}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x3a */ {{1, 1}, {3, 5}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x3b */ {{0, 1}, {3, 5}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x3c */ {{2, 5}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x3d */ {{0, 0}, {2, 5}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x3e */ {{1, 5}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x3f */ {{0, 5}, {0, 0}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x40 */ {{6, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x41 */ {{0, 0}, {6, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x42 */ {{1, 1}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x43 */ {{0, 1}, {6, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x44 */ {{2, 2}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x45 */ {{0, 0}, {2, 2}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x46 */ {{1, 2}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x47 */ {{0, 2}, {6, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x48 */ {{3, 3}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x49 */ {{0, 0}, {3, 3}, {6, 6}, {0, 0} } }, {0, 0, 3, 0, /* 0x4a */ {{1, 1}, {3, 3}, {6, 6}, {0, 0} } }, {1, 0, 3, 0, /* 0x4b */ {{0, 1}, {3, 3}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x4c */ {{2, 3}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x4d */ {{0, 0}, {2, 3}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x4e */ {{1, 3}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x4f */ {{0, 3}, {6, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x50 */ {{4, 4}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x51 */ {{0, 0}, {4, 4}, {6, 6}, {0, 0} } }, {0, 0, 3, 0, /* 0x52 */ {{1, 1}, {4, 4}, {6, 6}, {0, 0} } }, {1, 0, 3, 0, /* 0x53 */ {{0, 1}, {4, 4}, {6, 6}, {0, 0} } }, {0, 0, 3, 0, /* 0x54 */ {{2, 2}, {4, 4}, {6, 6}, {0, 0} } }, {1, 0, 4, 0, /* 0x55 */ {{0, 0}, {2, 2}, {4, 4}, {6, 6} } }, {0, 0, 3, 0, /* 0x56 */ {{1, 2}, {4, 4}, {6, 6}, {0, 0} } }, {1, 0, 3, 0, /* 0x57 */ {{0, 2}, {4, 4}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x58 */ {{3, 4}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x59 */ {{0, 0}, {3, 4}, {6, 6}, {0, 0} } }, {0, 0, 3, 0, /* 0x5a */ {{1, 1}, {3, 4}, {6, 6}, {0, 0} } }, {1, 0, 3, 0, /* 0x5b */ {{0, 1}, {3, 4}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x5c */ {{2, 4}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x5d */ {{0, 0}, {2, 4}, {6, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x5e */ {{1, 4}, {6, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x5f */ {{0, 4}, {6, 6}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x60 */ {{5, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x61 */ {{0, 0}, {5, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x62 */ {{1, 1}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x63 */ {{0, 1}, {5, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x64 */ {{2, 2}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x65 */ {{0, 0}, {2, 2}, {5, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x66 */ {{1, 2}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x67 */ {{0, 2}, {5, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x68 */ {{3, 3}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x69 */ {{0, 0}, {3, 3}, {5, 6}, {0, 0} } }, {0, 0, 3, 0, /* 0x6a */ {{1, 1}, {3, 3}, {5, 6}, {0, 0} } }, {1, 0, 3, 0, /* 0x6b */ {{0, 1}, {3, 3}, {5, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x6c */ {{2, 3}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x6d */ {{0, 0}, {2, 3}, {5, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x6e */ {{1, 3}, {5, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x6f */ {{0, 3}, {5, 6}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x70 */ {{4, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x71 */ {{0, 0}, {4, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x72 */ {{1, 1}, {4, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x73 */ {{0, 1}, {4, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x74 */ {{2, 2}, {4, 6}, {0, 0}, {0, 0} } }, {1, 0, 3, 0, /* 0x75 */ {{0, 0}, {2, 2}, {4, 6}, {0, 0} } }, {0, 0, 2, 0, /* 0x76 */ {{1, 2}, {4, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x77 */ {{0, 2}, {4, 6}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x78 */ {{3, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x79 */ {{0, 0}, {3, 6}, {0, 0}, {0, 0} } }, {0, 0, 2, 0, /* 0x7a */ {{1, 1}, {3, 6}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x7b */ {{0, 1}, {3, 6}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x7c */ {{2, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 2, 0, /* 0x7d */ {{0, 0}, {2, 6}, {0, 0}, {0, 0} } }, {0, 0, 1, 0, /* 0x7e */ {{1, 6}, {0, 0}, {0, 0}, {0, 0} } }, {1, 0, 1, 0, /* 0x7f */ {{0, 6}, {0, 0}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0x80 */ {{7, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0x81 */ {{0, 0}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0x82 */ {{1, 1}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0x83 */ {{0, 1}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0x84 */ {{2, 2}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x85 */ {{0, 0}, {2, 2}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x86 */ {{1, 2}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0x87 */ {{0, 2}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0x88 */ {{3, 3}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x89 */ {{0, 0}, {3, 3}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0x8a */ {{1, 1}, {3, 3}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0x8b */ {{0, 1}, {3, 3}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x8c */ {{2, 3}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x8d */ {{0, 0}, {2, 3}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x8e */ {{1, 3}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0x8f */ {{0, 3}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0x90 */ {{4, 4}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x91 */ {{0, 0}, {4, 4}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0x92 */ {{1, 1}, {4, 4}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0x93 */ {{0, 1}, {4, 4}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0x94 */ {{2, 2}, {4, 4}, {7, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0x95 */ {{0, 0}, {2, 2}, {4, 4}, {7, 7} } }, {0, 1, 3, 0, /* 0x96 */ {{1, 2}, {4, 4}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0x97 */ {{0, 2}, {4, 4}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x98 */ {{3, 4}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x99 */ {{0, 0}, {3, 4}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0x9a */ {{1, 1}, {3, 4}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0x9b */ {{0, 1}, {3, 4}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x9c */ {{2, 4}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0x9d */ {{0, 0}, {2, 4}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0x9e */ {{1, 4}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0x9f */ {{0, 4}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xa0 */ {{5, 5}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xa1 */ {{0, 0}, {5, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xa2 */ {{1, 1}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xa3 */ {{0, 1}, {5, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xa4 */ {{2, 2}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0xa5 */ {{0, 0}, {2, 2}, {5, 5}, {7, 7} } }, {0, 1, 3, 0, /* 0xa6 */ {{1, 2}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xa7 */ {{0, 2}, {5, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xa8 */ {{3, 3}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0xa9 */ {{0, 0}, {3, 3}, {5, 5}, {7, 7} } }, {0, 1, 4, 0, /* 0xaa */ {{1, 1}, {3, 3}, {5, 5}, {7, 7} } }, {1, 1, 4, 0, /* 0xab */ {{0, 1}, {3, 3}, {5, 5}, {7, 7} } }, {0, 1, 3, 0, /* 0xac */ {{2, 3}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0xad */ {{0, 0}, {2, 3}, {5, 5}, {7, 7} } }, {0, 1, 3, 0, /* 0xae */ {{1, 3}, {5, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xaf */ {{0, 3}, {5, 5}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xb0 */ {{4, 5}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xb1 */ {{0, 0}, {4, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xb2 */ {{1, 1}, {4, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xb3 */ {{0, 1}, {4, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xb4 */ {{2, 2}, {4, 5}, {7, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0xb5 */ {{0, 0}, {2, 2}, {4, 5}, {7, 7} } }, {0, 1, 3, 0, /* 0xb6 */ {{1, 2}, {4, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xb7 */ {{0, 2}, {4, 5}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xb8 */ {{3, 5}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xb9 */ {{0, 0}, {3, 5}, {7, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xba */ {{1, 1}, {3, 5}, {7, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xbb */ {{0, 1}, {3, 5}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xbc */ {{2, 5}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xbd */ {{0, 0}, {2, 5}, {7, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xbe */ {{1, 5}, {7, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xbf */ {{0, 5}, {7, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xc0 */ {{6, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xc1 */ {{0, 0}, {6, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xc2 */ {{1, 1}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xc3 */ {{0, 1}, {6, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xc4 */ {{2, 2}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xc5 */ {{0, 0}, {2, 2}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xc6 */ {{1, 2}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xc7 */ {{0, 2}, {6, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xc8 */ {{3, 3}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xc9 */ {{0, 0}, {3, 3}, {6, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xca */ {{1, 1}, {3, 3}, {6, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xcb */ {{0, 1}, {3, 3}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xcc */ {{2, 3}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xcd */ {{0, 0}, {2, 3}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xce */ {{1, 3}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xcf */ {{0, 3}, {6, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xd0 */ {{4, 4}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xd1 */ {{0, 0}, {4, 4}, {6, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xd2 */ {{1, 1}, {4, 4}, {6, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xd3 */ {{0, 1}, {4, 4}, {6, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xd4 */ {{2, 2}, {4, 4}, {6, 7}, {0, 0} } }, {1, 1, 4, 0, /* 0xd5 */ {{0, 0}, {2, 2}, {4, 4}, {6, 7} } }, {0, 1, 3, 0, /* 0xd6 */ {{1, 2}, {4, 4}, {6, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xd7 */ {{0, 2}, {4, 4}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xd8 */ {{3, 4}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xd9 */ {{0, 0}, {3, 4}, {6, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xda */ {{1, 1}, {3, 4}, {6, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xdb */ {{0, 1}, {3, 4}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xdc */ {{2, 4}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xdd */ {{0, 0}, {2, 4}, {6, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xde */ {{1, 4}, {6, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xdf */ {{0, 4}, {6, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xe0 */ {{5, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xe1 */ {{0, 0}, {5, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xe2 */ {{1, 1}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xe3 */ {{0, 1}, {5, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xe4 */ {{2, 2}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xe5 */ {{0, 0}, {2, 2}, {5, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xe6 */ {{1, 2}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xe7 */ {{0, 2}, {5, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xe8 */ {{3, 3}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xe9 */ {{0, 0}, {3, 3}, {5, 7}, {0, 0} } }, {0, 1, 3, 0, /* 0xea */ {{1, 1}, {3, 3}, {5, 7}, {0, 0} } }, {1, 1, 3, 0, /* 0xeb */ {{0, 1}, {3, 3}, {5, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xec */ {{2, 3}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xed */ {{0, 0}, {2, 3}, {5, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xee */ {{1, 3}, {5, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xef */ {{0, 3}, {5, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xf0 */ {{4, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xf1 */ {{0, 0}, {4, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xf2 */ {{1, 1}, {4, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xf3 */ {{0, 1}, {4, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xf4 */ {{2, 2}, {4, 7}, {0, 0}, {0, 0} } }, {1, 1, 3, 0, /* 0xf5 */ {{0, 0}, {2, 2}, {4, 7}, {0, 0} } }, {0, 1, 2, 0, /* 0xf6 */ {{1, 2}, {4, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xf7 */ {{0, 2}, {4, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xf8 */ {{3, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xf9 */ {{0, 0}, {3, 7}, {0, 0}, {0, 0} } }, {0, 1, 2, 0, /* 0xfa */ {{1, 1}, {3, 7}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xfb */ {{0, 1}, {3, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xfc */ {{2, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 2, 0, /* 0xfd */ {{0, 0}, {2, 7}, {0, 0}, {0, 0} } }, {0, 1, 1, 0, /* 0xfe */ {{1, 7}, {0, 0}, {0, 0}, {0, 0} } }, {1, 1, 1, 0, /* 0xff */ {{0, 7}, {0, 0}, {0, 0}, {0, 0} } } }; int sctp_is_address_in_scope(struct sctp_ifa *ifa, int ipv4_addr_legal, int ipv6_addr_legal, int loopback_scope, int ipv4_local_scope, int local_scope, int site_scope, int do_update) { if ((loopback_scope == 0) && (ifa->ifn_p) && SCTP_IFN_IS_IFT_LOOP(ifa->ifn_p)) { /* * skip loopback if not in scope * */ return (0); } if ((ifa->address.sa.sa_family == AF_INET) && ipv4_addr_legal) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)&ifa->address.sin; if (sin->sin_addr.s_addr == 0) { /* not in scope , unspecified */ return (0); } if ((ipv4_local_scope == 0) && (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { /* private address not in scope */ return (0); } } else if ((ifa->address.sa.sa_family == AF_INET6) && ipv6_addr_legal) { struct sockaddr_in6 *sin6; /* * Must update the flags, bummer, which means any IFA locks * must now be applied HERE <-> */ if (do_update) { sctp_gather_internal_ifa_flags(ifa); } if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { return (0); } /* ok to use deprecated addresses? */ sin6 = (struct sockaddr_in6 *)&ifa->address.sin6; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* skip unspecifed addresses */ return (0); } if ( /* (local_scope == 0) && */ (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) { return (0); } if ((site_scope == 0) && (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) { return (0); } } else { return (0); } return (1); } static struct mbuf * sctp_add_addr_to_mbuf(struct mbuf *m, struct sctp_ifa *ifa) { struct sctp_paramhdr *parmh; struct mbuf *mret; int len; if (ifa->address.sa.sa_family == AF_INET) { len = sizeof(struct sctp_ipv4addr_param); } else if (ifa->address.sa.sa_family == AF_INET6) { len = sizeof(struct sctp_ipv6addr_param); } else { /* unknown type */ return (m); } if (M_TRAILINGSPACE(m) >= len) { /* easy side we just drop it on the end */ parmh = (struct sctp_paramhdr *)(SCTP_BUF_AT(m, SCTP_BUF_LEN(m))); mret = m; } else { /* Need more space */ mret = m; while (SCTP_BUF_NEXT(mret) != NULL) { mret = SCTP_BUF_NEXT(mret); } SCTP_BUF_NEXT(mret) = sctp_get_mbuf_for_msg(len, 0, M_DONTWAIT, 1, MT_DATA); if (SCTP_BUF_NEXT(mret) == NULL) { /* We are hosed, can't add more addresses */ return (m); } mret = SCTP_BUF_NEXT(mret); parmh = mtod(mret, struct sctp_paramhdr *); } /* now add the parameter */ if (ifa->address.sa.sa_family == AF_INET) { struct sctp_ipv4addr_param *ipv4p; struct sockaddr_in *sin; sin = (struct sockaddr_in *)&ifa->address.sin; ipv4p = (struct sctp_ipv4addr_param *)parmh; parmh->param_type = htons(SCTP_IPV4_ADDRESS); parmh->param_length = htons(len); ipv4p->addr = sin->sin_addr.s_addr; SCTP_BUF_LEN(mret) += len; } else if (ifa->address.sa.sa_family == AF_INET6) { struct sctp_ipv6addr_param *ipv6p; struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&ifa->address.sin6; ipv6p = (struct sctp_ipv6addr_param *)parmh; parmh->param_type = htons(SCTP_IPV6_ADDRESS); parmh->param_length = htons(len); memcpy(ipv6p->addr, &sin6->sin6_addr, sizeof(ipv6p->addr)); /* clear embedded scope in the address */ in6_clearscope((struct in6_addr *)ipv6p->addr); SCTP_BUF_LEN(mret) += len; } else { return (m); } return (mret); } struct mbuf * sctp_add_addresses_to_i_ia(struct sctp_inpcb *inp, struct sctp_scoping *scope, struct mbuf *m_at, int cnt_inits_to) { struct sctp_vrf *vrf = NULL; int cnt, limit_out = 0, total_count; uint32_t vrf_id; vrf_id = inp->def_vrf_id; SCTP_IPI_ADDR_LOCK(); vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { SCTP_IPI_ADDR_UNLOCK(); return (m_at); } if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { struct sctp_ifa *sctp_ifap; struct sctp_ifn *sctp_ifnp; cnt = cnt_inits_to; if (vrf->total_ifa_count > SCTP_COUNT_LIMIT) { limit_out = 1; cnt = SCTP_ADDRESS_LIMIT; goto skip_count; } LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) { if ((scope->loopback_scope == 0) && SCTP_IFN_IS_IFT_LOOP(sctp_ifnp)) { /* * Skip loopback devices if loopback_scope * not set */ continue; } LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) { if (sctp_is_address_in_scope(sctp_ifap, scope->ipv4_addr_legal, scope->ipv6_addr_legal, scope->loopback_scope, scope->ipv4_local_scope, scope->local_scope, scope->site_scope, 1) == 0) { continue; } cnt++; if (cnt > SCTP_ADDRESS_LIMIT) { break; } } if (cnt > SCTP_ADDRESS_LIMIT) { break; } } skip_count: if (cnt > 1) { total_count = 0; LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) { cnt = 0; if ((scope->loopback_scope == 0) && SCTP_IFN_IS_IFT_LOOP(sctp_ifnp)) { /* * Skip loopback devices if * loopback_scope not set */ continue; } LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) { if (sctp_is_address_in_scope(sctp_ifap, scope->ipv4_addr_legal, scope->ipv6_addr_legal, scope->loopback_scope, scope->ipv4_local_scope, scope->local_scope, scope->site_scope, 0) == 0) { continue; } m_at = sctp_add_addr_to_mbuf(m_at, sctp_ifap); if (limit_out) { cnt++; total_count++; if (cnt >= 2) { /* * two from each * address */ break; } if (total_count > SCTP_ADDRESS_LIMIT) { /* No more addresses */ break; } } } } } } else { struct sctp_laddr *laddr; cnt = cnt_inits_to; /* First, how many ? */ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) { continue; } if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) /* * Address being deleted by the system, dont * list. */ continue; if (laddr->action == SCTP_DEL_IP_ADDRESS) { /* * Address being deleted on this ep don't * list. */ continue; } if (sctp_is_address_in_scope(laddr->ifa, scope->ipv4_addr_legal, scope->ipv6_addr_legal, scope->loopback_scope, scope->ipv4_local_scope, scope->local_scope, scope->site_scope, 1) == 0) { continue; } cnt++; } if (cnt > SCTP_ADDRESS_LIMIT) { limit_out = 1; } /* * To get through a NAT we only list addresses if we have * more than one. That way if you just bind a single address * we let the source of the init dictate our address. */ if (cnt > 1) { LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { cnt = 0; if (laddr->ifa == NULL) { continue; } if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) continue; if (sctp_is_address_in_scope(laddr->ifa, scope->ipv4_addr_legal, scope->ipv6_addr_legal, scope->loopback_scope, scope->ipv4_local_scope, scope->local_scope, scope->site_scope, 0) == 0) { continue; } m_at = sctp_add_addr_to_mbuf(m_at, laddr->ifa); cnt++; if (cnt >= SCTP_ADDRESS_LIMIT) { break; } } } } SCTP_IPI_ADDR_UNLOCK(); return (m_at); } static struct sctp_ifa * sctp_is_ifa_addr_preferred(struct sctp_ifa *ifa, uint8_t dest_is_loop, uint8_t dest_is_priv, sa_family_t fam) { uint8_t dest_is_global = 0; /* dest_is_priv is true if destination is a private address */ /* dest_is_loop is true if destination is a loopback addresses */ /* * Here we determine if its a preferred address. A preferred address * means it is the same scope or higher scope then the destination. * L = loopback, P = private, G = global * ----------------------------------------- src | dest | result * ---------------------------------------- L | L | yes * ----------------------------------------- P | L | * yes-v4 no-v6 ----------------------------------------- G | * L | yes-v4 no-v6 ----------------------------------------- L * | P | no ----------------------------------------- P | * P | yes ----------------------------------------- G | * P | no ----------------------------------------- L | G * | no ----------------------------------------- P | G | * no ----------------------------------------- G | G | * yes ----------------------------------------- */ if (ifa->address.sa.sa_family != fam) { /* forget mis-matched family */ return (NULL); } if ((dest_is_priv == 0) && (dest_is_loop == 0)) { dest_is_global = 1; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Is destination preferred:"); sctp_print_address(&ifa->address.sa); } #endif /* Ok the address may be ok */ if (fam == AF_INET6) { /* ok to use deprecated addresses? */ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:1\n"); } #endif return (NULL); } if (ifa->src_is_priv) { if (dest_is_loop) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:2\n"); } #endif return (NULL); } } if (ifa->src_is_glob) { if (dest_is_loop) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:3\n"); } #endif return (NULL); } } } /* * Now that we know what is what, implement or table this could in * theory be done slicker (it used to be), but this is * straightforward and easier to validate :-) */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("src_loop:%d src_priv:%d src_glob:%d\n", ifa->src_is_loop, ifa->src_is_priv, ifa->src_is_glob); printf("dest_loop:%d dest_priv:%d dest_glob:%d\n", dest_is_loop, dest_is_priv, dest_is_global); } #endif if ((ifa->src_is_loop) && (dest_is_priv)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:4\n"); } #endif return (NULL); } if ((ifa->src_is_glob) && (dest_is_priv)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:5\n"); } #endif return (NULL); } if ((ifa->src_is_loop) && (dest_is_global)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:6\n"); } #endif return (NULL); } if ((ifa->src_is_priv) && (dest_is_global)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("NO:7\n"); } #endif return (NULL); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("YES\n"); } #endif /* its a preferred address */ return (ifa); } static struct sctp_ifa * sctp_is_ifa_addr_acceptable(struct sctp_ifa *ifa, uint8_t dest_is_loop, uint8_t dest_is_priv, sa_family_t fam) { uint8_t dest_is_global = 0; /* * Here we determine if its a acceptable address. A acceptable * address means it is the same scope or higher scope but we can * allow for NAT which means its ok to have a global dest and a * private src. * * L = loopback, P = private, G = global * ----------------------------------------- src | dest | result * ----------------------------------------- L | L | yes * ----------------------------------------- P | L | * yes-v4 no-v6 ----------------------------------------- G | * L | yes ----------------------------------------- L | * P | no ----------------------------------------- P | P * | yes ----------------------------------------- G | P * | yes - May not work ----------------------------------------- * L | G | no ----------------------------------------- P * | G | yes - May not work * ----------------------------------------- G | G | yes * ----------------------------------------- */ if (ifa->address.sa.sa_family != fam) { /* forget non matching family */ return (NULL); } /* Ok the address may be ok */ if ((dest_is_loop == 0) && (dest_is_priv == 0)) { dest_is_global = 1; } if (fam == AF_INET6) { /* ok to use deprecated addresses? */ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { return (NULL); } if (ifa->src_is_priv) { /* Special case, linklocal to loop */ if (dest_is_loop) return (NULL); } } /* * Now that we know what is what, implement our table. This could in * theory be done slicker (it used to be), but this is * straightforward and easier to validate :-) */ if ((ifa->src_is_loop == 1) && (dest_is_priv)) { return (NULL); } if ((ifa->src_is_loop == 1) && (dest_is_global)) { return (NULL); } /* its an acceptable address */ return (ifa); } int sctp_is_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa) { struct sctp_laddr *laddr; if (stcb == NULL) { /* There are no restrictions, no TCB :-) */ return (0); } LIST_FOREACH(laddr, &stcb->asoc.sctp_restricted_addrs, sctp_nxt_addr) { if (laddr->ifa == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Help I have fallen and I can't get up!\n"); } #endif continue; } if (laddr->ifa == ifa) { /* Yes it is on the list */ return (1); } } return (0); } int sctp_is_addr_in_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa) { struct sctp_laddr *laddr; if (ifa == NULL) return (0); LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Help I have fallen and I can't get up!\n"); } #endif continue; } if ((laddr->ifa == ifa) && laddr->action == 0) /* same pointer */ return (1); } return (0); } static struct sctp_ifa * sctp_choose_boundspecific_inp(struct sctp_inpcb *inp, sctp_route_t * ro, uint32_t vrf_id, int non_asoc_addr_ok, uint8_t dest_is_priv, uint8_t dest_is_loop, sa_family_t fam) { struct sctp_laddr *laddr, *starting_point; void *ifn; int resettotop = 0; struct sctp_ifn *sctp_ifn; struct sctp_ifa *sctp_ifa, *sifa; struct sctp_vrf *vrf; uint32_t ifn_index; vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) return (NULL); ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro); ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro); sctp_ifn = sctp_find_ifn(vrf, ifn, ifn_index); /* * first question, is the ifn we will emit on in our list, if so, we * want such an address. Note that we first looked for a preferred * address. */ if (sctp_ifn) { /* is a preferred one on the interface we route out? */ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; sifa = sctp_is_ifa_addr_preferred(sctp_ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if (sctp_is_addr_in_ep(inp, sifa)) { atomic_add_int(&sifa->refcount, 1); return (sifa); } } } /* * ok, now we now need to find one on the list of the addresses. We * can't get one on the emitting interface so let's find first a * preferred one. If not that an acceptable one otherwise... we * return NULL. */ starting_point = inp->next_addr_touse; once_again: if (inp->next_addr_touse == NULL) { inp->next_addr_touse = LIST_FIRST(&inp->sctp_addr_list); resettotop = 1; } for (laddr = inp->next_addr_touse; laddr; laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { if (laddr->ifa == NULL) { /* address has been removed */ continue; } sifa = sctp_is_ifa_addr_preferred(laddr->ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; atomic_add_int(&sifa->refcount, 1); return (sifa); } if (resettotop == 0) { inp->next_addr_touse = NULL; goto once_again; } inp->next_addr_touse = starting_point; resettotop = 0; once_again_too: if (inp->next_addr_touse == NULL) { inp->next_addr_touse = LIST_FIRST(&inp->sctp_addr_list); resettotop = 1; } /* ok, what about an acceptable address in the inp */ for (laddr = inp->next_addr_touse; laddr; laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { if (laddr->ifa == NULL) { /* address has been removed */ continue; } sifa = sctp_is_ifa_addr_acceptable(laddr->ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; atomic_add_int(&sifa->refcount, 1); return (sifa); } if (resettotop == 0) { inp->next_addr_touse = NULL; goto once_again_too; } /* * no address bound can be a source for the destination we are in * trouble */ return (NULL); } static struct sctp_ifa * sctp_choose_boundspecific_stcb(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, sctp_route_t * ro, uint32_t vrf_id, uint8_t dest_is_priv, uint8_t dest_is_loop, int non_asoc_addr_ok, sa_family_t fam) { struct sctp_laddr *laddr, *starting_point; void *ifn; struct sctp_ifn *sctp_ifn; struct sctp_ifa *sctp_ifa, *sifa; uint8_t start_at_beginning = 0; struct sctp_vrf *vrf; uint32_t ifn_index; /* * first question, is the ifn we will emit on in our list, if so, we * want that one. */ vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) return (NULL); ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro); ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro); sctp_ifn = sctp_find_ifn(vrf, ifn, ifn_index); /* * first question, is the ifn we will emit on in our list? If so, * we want that one. First we look for a preferred. Second, we go * for an acceptable. */ if (sctp_ifn) { /* first try for a preferred address on the ep */ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; if (sctp_is_addr_in_ep(inp, sctp_ifa)) { sifa = sctp_is_ifa_addr_preferred(sctp_ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, sifa))) { /* on the no-no list */ continue; } atomic_add_int(&sifa->refcount, 1); return (sifa); } } /* next try for an acceptable address on the ep */ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; if (sctp_is_addr_in_ep(inp, sctp_ifa)) { sifa = sctp_is_ifa_addr_acceptable(sctp_ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, sifa))) { /* on the no-no list */ continue; } atomic_add_int(&sifa->refcount, 1); return (sifa); } } } /* * if we can't find one like that then we must look at all addresses * bound to pick one at first preferable then secondly acceptable. */ starting_point = stcb->asoc.last_used_address; sctp_from_the_top: if (stcb->asoc.last_used_address == NULL) { start_at_beginning = 1; stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); } /* search beginning with the last used address */ for (laddr = stcb->asoc.last_used_address; laddr; laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { if (laddr->ifa == NULL) { /* address has been removed */ continue; } sifa = sctp_is_ifa_addr_preferred(laddr->ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, sifa))) { /* on the no-no list */ continue; } stcb->asoc.last_used_address = laddr; atomic_add_int(&sifa->refcount, 1); return (sifa); } if (start_at_beginning == 0) { stcb->asoc.last_used_address = NULL; goto sctp_from_the_top; } /* now try for any higher scope than the destination */ stcb->asoc.last_used_address = starting_point; start_at_beginning = 0; sctp_from_the_top2: if (stcb->asoc.last_used_address == NULL) { start_at_beginning = 1; stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); } /* search beginning with the last used address */ for (laddr = stcb->asoc.last_used_address; laddr; laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { if (laddr->ifa == NULL) { /* address has been removed */ continue; } sifa = sctp_is_ifa_addr_acceptable(laddr->ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, sifa))) { /* on the no-no list */ continue; } stcb->asoc.last_used_address = laddr; atomic_add_int(&sifa->refcount, 1); return (sifa); } if (start_at_beginning == 0) { stcb->asoc.last_used_address = NULL; goto sctp_from_the_top2; } return (NULL); } static struct sctp_ifa * sctp_select_nth_preferred_addr_from_ifn_boundall(struct sctp_ifn *ifn, struct sctp_tcb *stcb, int non_asoc_addr_ok, uint8_t dest_is_loop, uint8_t dest_is_priv, int addr_wanted, sa_family_t fam) { struct sctp_ifa *ifa, *sifa; int num_eligible_addr = 0; LIST_FOREACH(ifa, &ifn->ifalist, next_ifa) { if ((ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; sifa = sctp_is_ifa_addr_preferred(ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if (stcb) { if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, sifa)) { /* * It is restricted for some reason.. * probably not yet added. */ continue; } } if (num_eligible_addr >= addr_wanted) { return (sifa); } num_eligible_addr++; } return (NULL); } static int sctp_count_num_preferred_boundall(struct sctp_ifn *ifn, struct sctp_tcb *stcb, int non_asoc_addr_ok, uint8_t dest_is_loop, uint8_t dest_is_priv, sa_family_t fam) { struct sctp_ifa *ifa, *sifa; int num_eligible_addr = 0; LIST_FOREACH(ifa, &ifn->ifalist, next_ifa) { if ((ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) { continue; } sifa = sctp_is_ifa_addr_preferred(ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) { continue; } if (stcb) { if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, sifa)) { /* * It is restricted for some reason.. * probably not yet added. */ continue; } } num_eligible_addr++; } return (num_eligible_addr); } static struct sctp_ifa * sctp_choose_boundall(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, sctp_route_t * ro, uint32_t vrf_id, uint8_t dest_is_priv, uint8_t dest_is_loop, int non_asoc_addr_ok, sa_family_t fam) { int cur_addr_num = 0, num_preferred = 0; void *ifn; struct sctp_ifn *sctp_ifn, *looked_at = NULL, *emit_ifn; struct sctp_ifa *sctp_ifa, *sifa; uint32_t ifn_index; struct sctp_vrf *vrf; /* * For boundall we can use any address in the association. If * non_asoc_addr_ok is set we can use any address (at least in * theory). So we look for preferred addresses first. If we find * one, we use it. Otherwise we next try to get an address on the * interface, which we should be able to do (unless non_asoc_addr_ok * is false and we are routed out that way). In these cases where we * can't use the address of the interface we go through all the * ifn's looking for an address we can use and fill that in. Punting * means we send back address 0, which will probably cause problems * actually since then IP will fill in the address of the route ifn, * which means we probably already rejected it.. i.e. here comes an * abort :-<. */ vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) return (NULL); ifn = SCTP_GET_IFN_VOID_FROM_ROUTE(ro); ifn_index = SCTP_GET_IF_INDEX_FROM_ROUTE(ro); emit_ifn = looked_at = sctp_ifn = sctp_find_ifn(vrf, ifn, ifn_index); if (sctp_ifn == NULL) { /* ?? We don't have this guy ?? */ goto bound_all_plan_b; } if (net) { cur_addr_num = net->indx_of_eligible_next_to_use; } num_preferred = sctp_count_num_preferred_boundall(sctp_ifn, stcb, non_asoc_addr_ok, dest_is_loop, dest_is_priv, fam); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Found %d preferred source addresses\n", num_preferred); } #endif if (num_preferred == 0) { /* * no eligible addresses, we must use some other interface * address if we can find one. */ goto bound_all_plan_b; } /* * Ok we have num_eligible_addr set with how many we can use, this * may vary from call to call due to addresses being deprecated * etc.. */ if (cur_addr_num >= num_preferred) { cur_addr_num = 0; } /* * select the nth address from the list (where cur_addr_num is the * nth) and 0 is the first one, 1 is the second one etc... */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("cur_addr_num:%d\n", cur_addr_num); } #endif sctp_ifa = sctp_select_nth_preferred_addr_from_ifn_boundall(sctp_ifn, stcb, non_asoc_addr_ok, dest_is_loop, dest_is_priv, cur_addr_num, fam); /* if sctp_ifa is NULL something changed??, fall to plan b. */ if (sctp_ifa) { atomic_add_int(&sctp_ifa->refcount, 1); if (net) { /* save off where the next one we will want */ net->indx_of_eligible_next_to_use = cur_addr_num + 1; } return (sctp_ifa); } /* * plan_b: Look at all interfaces and find a preferred address. If * no preferred fall through to plan_c. */ bound_all_plan_b: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Plan B?\n"); } #endif LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) { if (dest_is_loop == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) { /* wrong base scope */ continue; } if ((sctp_ifn == looked_at) && looked_at) /* already looked at this guy */ continue; num_preferred = sctp_count_num_preferred_boundall(sctp_ifn, stcb, non_asoc_addr_ok, dest_is_loop, dest_is_priv, fam); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Found ifn:%p %d preferred source addresses\n", ifn, num_preferred); } #endif if (num_preferred == 0) { /* * None on this interface. */ continue; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("num preferred:%d on interface:%p cur_addr_num:%d\n", num_preferred, sctp_ifn, cur_addr_num); } #endif /* * Ok we have num_eligible_addr set with how many we can * use, this may vary from call to call due to addresses * being deprecated etc.. */ if (cur_addr_num >= num_preferred) { cur_addr_num = 0; } sifa = sctp_select_nth_preferred_addr_from_ifn_boundall(sctp_ifn, stcb, non_asoc_addr_ok, dest_is_loop, dest_is_priv, cur_addr_num, fam); if (sifa == NULL) continue; if (net) { net->indx_of_eligible_next_to_use = cur_addr_num + 1; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("we selected %d\n", cur_addr_num); printf("Source:"); sctp_print_address(&sifa->address.sa); printf("Dest:"); sctp_print_address(&net->ro._l_addr.sa); } #endif } atomic_add_int(&sifa->refcount, 1); return (sifa); } /* * plan_c: See if we have an acceptable address on the emit * interface */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Plan C no preferred for Dest, acceptable for?\n"); } #endif if (emit_ifn == NULL) { goto plan_d; } LIST_FOREACH(sctp_ifa, &emit_ifn->ifalist, next_ifa) { if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; sifa = sctp_is_ifa_addr_acceptable(sctp_ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if (stcb) { if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, sifa)) { /* * It is restricted for some reason.. * probably not yet added. */ continue; } } atomic_add_int(&sifa->refcount, 1); return (sifa); } plan_d: /* * plan_d: We are in trouble. No preferred address on the emit * interface. And not even a perfered address on all interfaces. Go * out and see if we can find an acceptable address somewhere * amongst all interfaces. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Plan C fails plan D?\n"); } #endif LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) { if (dest_is_loop == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) { /* wrong base scope */ continue; } if ((sctp_ifn == looked_at) && looked_at) /* already looked at this guy */ continue; LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if ((sctp_ifa->localifa_flags & SCTP_ADDR_DEFER_USE) && (non_asoc_addr_ok == 0)) continue; sifa = sctp_is_ifa_addr_acceptable(sctp_ifa, dest_is_loop, dest_is_priv, fam); if (sifa == NULL) continue; if (stcb) { if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, sifa)) { /* * It is restricted for some * reason.. probably not yet added. */ continue; } } atomic_add_int(&sifa->refcount, 1); return (sifa); } } /* * Ok we can find NO address to source from that is not on our * negative list and non_asoc_address is NOT ok, or its on our * negative list. We cant source to it :-( */ return (NULL); } /* tcb may be NULL */ struct sctp_ifa * sctp_source_address_selection(struct sctp_inpcb *inp, struct sctp_tcb *stcb, sctp_route_t * ro, struct sctp_nets *net, int non_asoc_addr_ok, uint32_t vrf_id) { struct sockaddr_in *to = (struct sockaddr_in *)&ro->ro_dst; struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&ro->ro_dst; struct sctp_ifa *answer; uint8_t dest_is_priv, dest_is_loop; sa_family_t fam; /* * Rules: - Find the route if needed, cache if I can. - Look at * interface address in route, Is it in the bound list. If so we * have the best source. - If not we must rotate amongst the * addresses. * * Cavets and issues * * Do we need to pay attention to scope. We can have a private address * or a global address we are sourcing or sending to. So if we draw * it out zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz * For V4 ------------------------------------------ source * * dest * result ----------------------------------------- * Private * Global * NAT * ----------------------------------------- Private * * Private * No problem ----------------------------------------- * Global * Private * Huh, How will this work? * ----------------------------------------- Global * * Global * No Problem ------------------------------------------ * zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz For V6 * ------------------------------------------ source * dest * * result ----------------------------------------- Linklocal * * Global * ----------------------------------------- * Linklocal * Linklocal * No problem * ----------------------------------------- Global * * Linklocal * Huh, How will this work? * ----------------------------------------- Global * * Global * No Problem ------------------------------------------ * zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz * * And then we add to that what happens if there are multiple addresses * assigned to an interface. Remember the ifa on a ifn is a linked * list of addresses. So one interface can have more than one IP * address. What happens if we have both a private and a global * address? Do we then use context of destination to sort out which * one is best? And what about NAT's sending P->G may get you a NAT * translation, or should you select the G thats on the interface in * preference. * * Decisions: * * - count the number of addresses on the interface. - if its one, no * problem except case . For we will assume a NAT out there. * - if there are more than one, then we need to worry about scope P * or G. We should prefer G -> G and P -> P if possible. Then as a * secondary fall back to mixed types G->P being a last ditch one. - * The above all works for bound all, but bound specific we need to * use the same concept but instead only consider the bound * addresses. If the bound set is NOT assigned to the interface then * we must use rotation amongst the bound addresses.. */ if (ro->ro_rt == NULL) { uint32_t table_id = 0; /* * Need a route to cache. */ if (stcb) { table_id = stcb->asoc.table_id; } else { table_id = SCTP_VRF_DEFAULT_TABLEID(vrf_id); } SCTP_RTALLOC(ro, vrf_id, table_id); } if (ro->ro_rt == NULL) { return (NULL); } fam = to->sin_family; dest_is_priv = dest_is_loop = 0; /* Setup our scopes for the destination */ if (fam == AF_INET) { /* Scope based on outbound address */ if ((IN4_ISPRIVATE_ADDRESS(&to->sin_addr))) { dest_is_priv = 1; } else if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) { dest_is_loop = 1; if (net != NULL) { /* mark it as local */ net->addr_is_local = 1; } } } else if (fam == AF_INET6) { /* Scope based on outbound address */ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) { /* * If the route goes to the loopback address OR the * address is a loopback address, we are loopback * scope. But we don't use dest_is_priv (link local * addresses). */ dest_is_loop = 1; if (net != NULL) { /* mark it as local */ net->addr_is_local = 1; } } else if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) { dest_is_priv = 1; } } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("Select source for:"); sctp_print_address((struct sockaddr *)to); } #endif if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* * When bound to all if the address list is set it is a * negative list. Addresses being added by asconf. */ answer = sctp_choose_boundall(inp, stcb, net, ro, vrf_id, dest_is_priv, dest_is_loop, non_asoc_addr_ok, fam); return (answer); } /* * Three possiblities here: * * a) stcb is NULL, which means we operate only from the list of * addresses (ifa's) bound to the endpoint and we care not about the * list. b) stcb is NOT-NULL, which means we have an assoc structure * and auto-asconf is on. This means that the list of addresses is a * NOT list. We use the list from the inp, but any listed address in * our list is NOT yet added. However if the non_asoc_addr_ok is set * we CAN use an address NOT available (i.e. being added). Its a * negative list. c) stcb is NOT-NULL, which means we have an assoc * structure and auto-asconf is off. This means that the list of * addresses is the ONLY addresses I can use.. its positive. * * Note we collapse b & c into the same function just like in the v6 * address selection. */ if (stcb) { answer = sctp_choose_boundspecific_stcb(inp, stcb, net, ro, vrf_id, dest_is_priv, dest_is_loop, non_asoc_addr_ok, fam); } else { answer = sctp_choose_boundspecific_inp(inp, ro, vrf_id, non_asoc_addr_ok, dest_is_priv, dest_is_loop, fam); } return (answer); } static int sctp_find_cmsg(int c_type, void *data, struct mbuf *control, int cpsize) { struct cmsghdr cmh; int tlen, at; tlen = SCTP_BUF_LEN(control); at = 0; /* * Independent of how many mbufs, find the c_type inside the control * structure and copy out the data. */ while (at < tlen) { if ((tlen - at) < (int)CMSG_ALIGN(sizeof(cmh))) { /* not enough room for one more we are done. */ return (0); } m_copydata(control, at, sizeof(cmh), (caddr_t)&cmh); if ((cmh.cmsg_len + at) > tlen) { /* * this is real messed up since there is not enough * data here to cover the cmsg header. We are done. */ return (0); } if ((cmh.cmsg_level == IPPROTO_SCTP) && (c_type == cmh.cmsg_type)) { /* found the one we want, copy it out */ at += CMSG_ALIGN(sizeof(struct cmsghdr)); if ((int)(cmh.cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr))) < cpsize) { /* * space of cmsg_len after header not big * enough */ return (0); } m_copydata(control, at, cpsize, data); return (1); } else { at += CMSG_ALIGN(cmh.cmsg_len); if (cmh.cmsg_len == 0) { break; } } } /* not found */ return (0); } static struct mbuf * sctp_add_cookie(struct sctp_inpcb *inp, struct mbuf *init, int init_offset, struct mbuf *initack, int initack_offset, struct sctp_state_cookie *stc_in) { struct mbuf *copy_init, *copy_initack, *m_at, *sig, *mret; struct sctp_state_cookie *stc; struct sctp_paramhdr *ph; uint8_t *signature; int sig_offset; uint16_t cookie_sz; mret = NULL; mret = sctp_get_mbuf_for_msg((sizeof(struct sctp_state_cookie) + sizeof(struct sctp_paramhdr)), 0, M_DONTWAIT, 1, MT_DATA); if (mret == NULL) { return (NULL); } copy_init = SCTP_M_COPYM(init, init_offset, M_COPYALL, M_DONTWAIT); if (copy_init == NULL) { sctp_m_freem(mret); return (NULL); } copy_initack = SCTP_M_COPYM(initack, initack_offset, M_COPYALL, M_DONTWAIT); if (copy_initack == NULL) { sctp_m_freem(mret); sctp_m_freem(copy_init); return (NULL); } /* easy side we just drop it on the end */ ph = mtod(mret, struct sctp_paramhdr *); SCTP_BUF_LEN(mret) = sizeof(struct sctp_state_cookie) + sizeof(struct sctp_paramhdr); stc = (struct sctp_state_cookie *)((caddr_t)ph + sizeof(struct sctp_paramhdr)); ph->param_type = htons(SCTP_STATE_COOKIE); ph->param_length = 0; /* fill in at the end */ /* Fill in the stc cookie data */ *stc = *stc_in; /* tack the INIT and then the INIT-ACK onto the chain */ cookie_sz = 0; m_at = mret; for (m_at = mret; m_at; m_at = SCTP_BUF_NEXT(m_at)) { cookie_sz += SCTP_BUF_LEN(m_at); if (SCTP_BUF_NEXT(m_at) == NULL) { SCTP_BUF_NEXT(m_at) = copy_init; break; } } for (m_at = copy_init; m_at; m_at = SCTP_BUF_NEXT(m_at)) { cookie_sz += SCTP_BUF_LEN(m_at); if (SCTP_BUF_NEXT(m_at) == NULL) { SCTP_BUF_NEXT(m_at) = copy_initack; break; } } for (m_at = copy_initack; m_at; m_at = SCTP_BUF_NEXT(m_at)) { cookie_sz += SCTP_BUF_LEN(m_at); if (SCTP_BUF_NEXT(m_at) == NULL) { break; } } sig = sctp_get_mbuf_for_msg(SCTP_SECRET_SIZE, 0, M_DONTWAIT, 1, MT_DATA); if (sig == NULL) { /* no space, so free the entire chain */ sctp_m_freem(mret); return (NULL); } SCTP_BUF_LEN(sig) = 0; SCTP_BUF_NEXT(m_at) = sig; sig_offset = 0; signature = (uint8_t *) (mtod(sig, caddr_t)+sig_offset); /* Time to sign the cookie */ - sctp_hmac_m(SCTP_HMAC, + (void)sctp_hmac_m(SCTP_HMAC, (uint8_t *) inp->sctp_ep.secret_key[(int)(inp->sctp_ep.current_secret_number)], SCTP_SECRET_SIZE, mret, sizeof(struct sctp_paramhdr), (uint8_t *) signature); SCTP_BUF_LEN(sig) += SCTP_SIGNATURE_SIZE; cookie_sz += SCTP_SIGNATURE_SIZE; ph->param_length = htons(cookie_sz); return (mret); } static __inline uint8_t sctp_get_ect(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk) { uint8_t this_random; /* Huh? */ if (sctp_ecn_enable == 0) return (0); if (sctp_ecn_nonce == 0) /* no nonce, always return ECT0 */ return (SCTP_ECT0_BIT); if (stcb->asoc.peer_supports_ecn_nonce == 0) { /* Peer does NOT support it, so we send a ECT0 only */ return (SCTP_ECT0_BIT); } if (chk == NULL) return (SCTP_ECT0_BIT); if (((stcb->asoc.hb_random_idx == 3) && (stcb->asoc.hb_ect_randombit > 7)) || (stcb->asoc.hb_random_idx > 3)) { uint32_t rndval; rndval = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); memcpy(stcb->asoc.hb_random_values, &rndval, sizeof(stcb->asoc.hb_random_values)); this_random = stcb->asoc.hb_random_values[0]; stcb->asoc.hb_random_idx = 0; stcb->asoc.hb_ect_randombit = 0; } else { if (stcb->asoc.hb_ect_randombit > 7) { stcb->asoc.hb_ect_randombit = 0; stcb->asoc.hb_random_idx++; } this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx]; } if ((this_random >> stcb->asoc.hb_ect_randombit) & 0x01) { if (chk != NULL) /* ECN Nonce stuff */ chk->rec.data.ect_nonce = SCTP_ECT1_BIT; stcb->asoc.hb_ect_randombit++; return (SCTP_ECT1_BIT); } else { stcb->asoc.hb_ect_randombit++; return (SCTP_ECT0_BIT); } } static int sctp_lowlevel_chunk_output(struct sctp_inpcb *inp, struct sctp_tcb *stcb, /* may be NULL */ struct sctp_nets *net, struct sockaddr *to, struct mbuf *m, uint32_t auth_offset, struct sctp_auth_chunk *auth, int nofragment_flag, int ecn_ok, struct sctp_tmit_chunk *chk, int out_of_asoc_ok) /* nofragment_flag to tell if IP_DF should be set (IPv4 only) */ { /* * Given a mbuf chain (via SCTP_BUF_NEXT()) that holds a packet * header WITH an SCTPHDR but no IP header, endpoint inp and sa * structure: - fill in the HMAC digest of any AUTH chunk in the * packet. - calculate and fill in the SCTP checksum. - prepend an * IP address header. - if boundall use INADDR_ANY. - if * boundspecific do source address selection. - set fragmentation * option for ipV4. - On return from IP output, check/adjust mtu * size of output interface and smallest_mtu size as well. */ /* Will need ifdefs around this */ struct mbuf *o_pak; struct mbuf *newm; struct sctphdr *sctphdr; int packet_length; uint32_t csum; int ret; uint32_t vrf_id; sctp_route_t *ro = NULL; if ((net) && (net->dest_state & SCTP_ADDR_OUT_OF_SCOPE)) { sctp_m_freem(m); return (EFAULT); } if (stcb) { vrf_id = stcb->asoc.vrf_id; } else { vrf_id = inp->def_vrf_id; } /* fill in the HMAC digest for any AUTH chunk in the packet */ if ((auth != NULL) && (stcb != NULL)) { sctp_fill_hmac_digest_m(m, auth_offset, auth, stcb); } /* Calculate the csum and fill in the length of the packet */ sctphdr = mtod(m, struct sctphdr *); if (sctp_no_csum_on_loopback && (stcb) && (stcb->asoc.loopback_scope)) { sctphdr->checksum = 0; /* * This can probably now be taken out since my audit shows * no more bad pktlen's coming in. But we will wait a while * yet. */ packet_length = sctp_calculate_len(m); } else { sctphdr->checksum = 0; csum = sctp_calculate_sum(m, &packet_length, 0); sctphdr->checksum = csum; } if (to->sa_family == AF_INET) { struct ip *ip = NULL; sctp_route_t iproute; uint8_t tos_value; newm = sctp_get_mbuf_for_msg(sizeof(struct ip), 1, M_DONTWAIT, 1, MT_DATA); if (newm == NULL) { sctp_m_freem(m); return (ENOMEM); } SCTP_ALIGN_TO_END(newm, sizeof(struct ip)); SCTP_BUF_LEN(newm) = sizeof(struct ip); packet_length += sizeof(struct ip); SCTP_BUF_NEXT(newm) = m; m = newm; ip = mtod(m, struct ip *); ip->ip_v = IPVERSION; ip->ip_hl = (sizeof(struct ip) >> 2); if (net) { tos_value = net->tos_flowlabel & 0x000000ff; } else { tos_value = inp->ip_inp.inp.inp_ip_tos; } if (nofragment_flag) { #if defined(WITH_CONVERT_IP_OFF) || defined(__FreeBSD__) || defined(__APPLE__) ip->ip_off = IP_DF; #else ip->ip_off = htons(IP_DF); #endif } else ip->ip_off = 0; /* FreeBSD has a function for ip_id's */ ip->ip_id = ip_newid(); ip->ip_ttl = inp->ip_inp.inp.inp_ip_ttl; ip->ip_len = packet_length; if (stcb) { if ((stcb->asoc.ecn_allowed) && ecn_ok) { /* Enable ECN */ ip->ip_tos = ((u_char)(tos_value & 0xfc) | sctp_get_ect(stcb, chk)); } else { /* No ECN */ ip->ip_tos = (u_char)(tos_value & 0xfc); } } else { /* no association at all */ ip->ip_tos = (tos_value & 0xfc); } ip->ip_p = IPPROTO_SCTP; ip->ip_sum = 0; if (net == NULL) { ro = &iproute; memset(&iproute, 0, sizeof(iproute)); memcpy(&ro->ro_dst, to, to->sa_len); } else { ro = (sctp_route_t *) & net->ro; } /* Now the address selection part */ ip->ip_dst.s_addr = ((struct sockaddr_in *)to)->sin_addr.s_addr; /* call the routine to select the src address */ if (net) { if (net->ro._s_addr && (net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED)) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } if (net->src_addr_selected == 0) { /* Cache the source address */ net->ro._s_addr = sctp_source_address_selection(inp, stcb, ro, net, out_of_asoc_ok, vrf_id); if (net->ro._s_addr == NULL) { /* No route to host */ goto no_route; } net->src_addr_selected = 1; } ip->ip_src = net->ro._s_addr->address.sin.sin_addr; } else { struct sctp_ifa *_lsrc; _lsrc = sctp_source_address_selection(inp, stcb, ro, net, out_of_asoc_ok, vrf_id); if (_lsrc == NULL) { goto no_route; } ip->ip_src = _lsrc->address.sin.sin_addr; sctp_free_ifa(_lsrc); } /* * If source address selection fails and we find no route * then the ip_output should fail as well with a * NO_ROUTE_TO_HOST type error. We probably should catch * that somewhere and abort the association right away * (assuming this is an INIT being sent). */ if ((ro->ro_rt == NULL)) { /* * src addr selection failed to find a route (or * valid source addr), so we can't get there from * here (yet)! */ no_route: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("low_level_output: dropped packet - no valid source addr\n"); if (net) { printf("Destination was "); sctp_print_address(&net->ro._l_addr.sa); } } #endif /* SCTP_DEBUG */ if (net) { if (net->dest_state & SCTP_ADDR_CONFIRMED) { if ((net->dest_state & SCTP_ADDR_REACHABLE) && stcb) { printf("no route takes interface %p down\n", net); sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, SCTP_FAILED_THRESHOLD, (void *)net); net->dest_state &= ~SCTP_ADDR_REACHABLE; net->dest_state |= SCTP_ADDR_NOT_REACHABLE; } } if (stcb) { if (net == stcb->asoc.primary_destination) { /* need a new primary */ struct sctp_nets *alt; alt = sctp_find_alternate_net(stcb, net, 0); if (alt != net) { if (sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, alt) == 0) { net->dest_state |= SCTP_ADDR_WAS_PRIMARY; if (net->ro._s_addr) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; } net->src_addr_selected = 0; } } } } } return (EHOSTUNREACH); } if (ro != &iproute) { memcpy(&iproute, ro, sizeof(*ro)); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("Calling ipv4 output routine from low level src addr:%x\n", (uint32_t) (ntohl(ip->ip_src.s_addr))); printf("Destination is %x\n", (uint32_t) (ntohl(ip->ip_dst.s_addr))); printf("RTP route is %p through\n", ro->ro_rt); } #endif if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) { /* failed to prepend data, give up */ sctp_m_freem(m); return (ENOMEM); } SCTP_ATTACH_CHAIN(o_pak, m, packet_length); /* send it out. table id is taken from stcb */ SCTP_IP_OUTPUT(ret, o_pak, ro, stcb, vrf_id, 0); SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); if (ret) SCTP_STAT_INCR(sctps_senderrors); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("Ip output returns %d\n", ret); } #endif if (net == NULL) { /* free tempy routes */ if (ro->ro_rt) { RTFREE(ro->ro_rt); ro->ro_rt = NULL; } } else { /* PMTU check versus smallest asoc MTU goes here */ if (ro->ro_rt != NULL) { uint32_t mtu; mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt); if (mtu && (stcb->asoc.smallest_mtu > mtu)) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("sctp_mtu_size_reset called after ip_output mtu-change:%d\n", mtu); #endif sctp_mtu_size_reset(inp, &stcb->asoc, mtu); net->mtu = mtu; } } else { /* route was freed */ if (net->ro._s_addr && net->src_addr_selected) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; } net->src_addr_selected = 0; } } return (ret); } #ifdef INET6 else if (to->sa_family == AF_INET6) { uint32_t flowlabel; struct ip6_hdr *ip6h; struct route_in6 ip6route; struct ifnet *ifp; u_char flowTop; uint16_t flowBottom; u_char tosBottom, tosTop; struct sockaddr_in6 *sin6, tmp, *lsa6, lsa6_tmp; int prev_scope = 0; struct sockaddr_in6 lsa6_storage; int error; u_short prev_port = 0; if (net != NULL) { flowlabel = net->tos_flowlabel; } else { flowlabel = ((struct in6pcb *)inp)->in6p_flowinfo; } newm = sctp_get_mbuf_for_msg(sizeof(struct ip6_hdr), 1, M_DONTWAIT, 1, MT_DATA); if (newm == NULL) { sctp_m_freem(m); return (ENOMEM); } SCTP_ALIGN_TO_END(newm, sizeof(struct ip6_hdr)); SCTP_BUF_LEN(newm) = sizeof(struct ip6_hdr); packet_length += sizeof(struct ip6_hdr); SCTP_BUF_NEXT(newm) = m; m = newm; ip6h = mtod(m, struct ip6_hdr *); /* * We assume here that inp_flow is in host byte order within * the TCB! */ flowBottom = flowlabel & 0x0000ffff; flowTop = ((flowlabel & 0x000f0000) >> 16); tosTop = (((flowlabel & 0xf0) >> 4) | IPV6_VERSION); /* protect *sin6 from overwrite */ sin6 = (struct sockaddr_in6 *)to; tmp = *sin6; sin6 = &tmp; /* KAME hack: embed scopeid */ if (sa6_embedscope(sin6, ip6_use_defzone) != 0) return (EINVAL); if (net == NULL) { memset(&ip6route, 0, sizeof(ip6route)); ro = (sctp_route_t *) & ip6route; memcpy(&ro->ro_dst, sin6, sin6->sin6_len); } else { ro = (sctp_route_t *) & net->ro; } if (stcb != NULL) { if ((stcb->asoc.ecn_allowed) && ecn_ok) { /* Enable ECN */ tosBottom = (((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) | sctp_get_ect(stcb, chk)) << 4); } else { /* No ECN */ tosBottom = ((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) << 4); } } else { /* we could get no asoc if it is a O-O-T-B packet */ tosBottom = ((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) << 4); } ip6h->ip6_flow = htonl(((tosTop << 24) | ((tosBottom | flowTop) << 16) | flowBottom)); ip6h->ip6_nxt = IPPROTO_SCTP; ip6h->ip6_plen = (packet_length - sizeof(struct ip6_hdr)); ip6h->ip6_dst = sin6->sin6_addr; /* * Add SRC address selection here: we can only reuse to a * limited degree the kame src-addr-sel, since we can try * their selection but it may not be bound. */ bzero(&lsa6_tmp, sizeof(lsa6_tmp)); lsa6_tmp.sin6_family = AF_INET6; lsa6_tmp.sin6_len = sizeof(lsa6_tmp); lsa6 = &lsa6_tmp; if (net) { if (net->ro._s_addr && net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } if (net->src_addr_selected == 0) { /* Cache the source address */ net->ro._s_addr = sctp_source_address_selection(inp, stcb, ro, net, out_of_asoc_ok, vrf_id); if (net->ro._s_addr == NULL) { #ifdef SCTP_DEBUG printf("V6:No route to host\n"); #endif goto no_route; } net->src_addr_selected = 1; } lsa6->sin6_addr = net->ro._s_addr->address.sin6.sin6_addr; } else { struct sctp_ifa *_lsrc; _lsrc = sctp_source_address_selection(inp, stcb, ro, net, out_of_asoc_ok, vrf_id); if (_lsrc == NULL) { goto no_route; } lsa6->sin6_addr = _lsrc->address.sin6.sin6_addr; sctp_free_ifa(_lsrc); } lsa6->sin6_port = inp->sctp_lport; if ((ro->ro_rt == NULL)) { /* * src addr selection failed to find a route (or * valid source addr), so we can't get there from * here! */ goto no_route; } /* * XXX: sa6 may not have a valid sin6_scope_id in the * non-SCOPEDROUTING case. */ bzero(&lsa6_storage, sizeof(lsa6_storage)); lsa6_storage.sin6_family = AF_INET6; lsa6_storage.sin6_len = sizeof(lsa6_storage); if ((error = sa6_recoverscope(&lsa6_storage)) != 0) { sctp_m_freem(m); return (error); } /* XXX */ lsa6_storage.sin6_addr = lsa6->sin6_addr; lsa6_storage.sin6_port = inp->sctp_lport; lsa6 = &lsa6_storage; ip6h->ip6_src = lsa6->sin6_addr; /* * We set the hop limit now since there is a good chance * that our ro pointer is now filled */ ip6h->ip6_hlim = SCTP_GET_HLIM(inp, ro); ifp = SCTP_GET_IFN_VOID_FROM_ROUTE(ro); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { /* Copy to be sure something bad is not happening */ sin6->sin6_addr = ip6h->ip6_dst; lsa6->sin6_addr = ip6h->ip6_src; printf("Calling ipv6 output routine from low level\n"); printf("src: "); sctp_print_address((struct sockaddr *)lsa6); printf("dst: "); sctp_print_address((struct sockaddr *)sin6); } #endif if (net) { sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; /* preserve the port and scope for link local send */ prev_scope = sin6->sin6_scope_id; prev_port = sin6->sin6_port; } if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) { /* failed to prepend data, give up */ sctp_m_freem(m); return (ENOMEM); } SCTP_ATTACH_CHAIN(o_pak, m, packet_length); /* send it out. table id is taken from stcb */ SCTP_IP6_OUTPUT(ret, o_pak, (struct route_in6 *)ro, &ifp, stcb, vrf_id, 0); if (net) { /* for link local this must be done */ sin6->sin6_scope_id = prev_scope; sin6->sin6_port = prev_port; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("return from send is %d\n", ret); } #endif SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); if (ret) { SCTP_STAT_INCR(sctps_senderrors); } if (net == NULL) { /* Now if we had a temp route free it */ if (ro->ro_rt) { RTFREE(ro->ro_rt); } } else { /* PMTU check versus smallest asoc MTU goes here */ if (ro->ro_rt == NULL) { /* Route was freed */ if (net->ro._s_addr && net->src_addr_selected) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; } net->src_addr_selected = 0; } if (ro->ro_rt != NULL) { uint32_t mtu; mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, ro->ro_rt); if (mtu && (stcb->asoc.smallest_mtu > mtu)) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("sctp_mtu_size_reset called after ip6_output mtu-change:%d\n", mtu); #endif sctp_mtu_size_reset(inp, &stcb->asoc, mtu); net->mtu = mtu; } } else if (ifp) { if (ND_IFINFO(ifp)->linkmtu && (stcb->asoc.smallest_mtu > ND_IFINFO(ifp)->linkmtu)) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("sctp_mtu_size_reset called via ifp ND_IFINFO() linkmtu:%d\n", ND_IFINFO(ifp)->linkmtu); #endif sctp_mtu_size_reset(inp, &stcb->asoc, ND_IFINFO(ifp)->linkmtu); } } } return (ret); } #endif else { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Unknown protocol (TSNH) type %d\n", ((struct sockaddr *)to)->sa_family); } #endif sctp_m_freem(m); return (EFAULT); } } void sctp_send_initiate(struct sctp_inpcb *inp, struct sctp_tcb *stcb) { struct mbuf *m, *m_at, *mp_last; struct sctp_nets *net; struct sctp_init_msg *initm; struct sctp_supported_addr_param *sup_addr; struct sctp_ecn_supported_param *ecn; struct sctp_prsctp_supported_param *prsctp; struct sctp_ecn_nonce_supported_param *ecn_nonce; struct sctp_supported_chunk_types_param *pr_supported; int cnt_inits_to = 0; int padval, ret; int num_ext; int p_len; /* INIT's always go to the primary (and usually ONLY address) */ mp_last = NULL; net = stcb->asoc.primary_destination; if (net == NULL) { net = TAILQ_FIRST(&stcb->asoc.nets); if (net == NULL) { /* TSNH */ return; } /* we confirm any address we send an INIT to */ net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; sctp_set_primary_addr(stcb, NULL, net); } else { /* we confirm any address we send an INIT to */ net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { printf("Sending INIT\n"); } #endif if (((struct sockaddr *)&(net->ro._l_addr))->sa_family == AF_INET6) { /* * special hook, if we are sending to link local it will not * show up in our private address count. */ struct sockaddr_in6 *sin6l; sin6l = &net->ro._l_addr.sin6; if (IN6_IS_ADDR_LINKLOCAL(&sin6l->sin6_addr)) cnt_inits_to = 1; } if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { /* This case should not happen */ return; } /* start the INIT timer */ if (sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, net)) { /* we are hosed since I can't start the INIT timer? */ return; } m = sctp_get_mbuf_for_msg(MCLBYTES, 1, M_DONTWAIT, 1, MT_DATA); if (m == NULL) { /* No memory, INIT timer will re-attempt. */ return; } SCTP_BUF_LEN(m) = sizeof(struct sctp_init_msg); /* Now lets put the SCTP header in place */ initm = mtod(m, struct sctp_init_msg *); initm->sh.src_port = inp->sctp_lport; initm->sh.dest_port = stcb->rport; initm->sh.v_tag = 0; initm->sh.checksum = 0; /* calculate later */ /* now the chunk header */ initm->msg.ch.chunk_type = SCTP_INITIATION; initm->msg.ch.chunk_flags = 0; /* fill in later from mbuf we build */ initm->msg.ch.chunk_length = 0; /* place in my tag */ initm->msg.init.initiate_tag = htonl(stcb->asoc.my_vtag); /* set up some of the credits. */ initm->msg.init.a_rwnd = htonl(max(SCTP_SB_LIMIT_RCV(inp->sctp_socket), SCTP_MINIMAL_RWND)); initm->msg.init.num_outbound_streams = htons(stcb->asoc.pre_open_streams); initm->msg.init.num_inbound_streams = htons(stcb->asoc.max_inbound_streams); initm->msg.init.initial_tsn = htonl(stcb->asoc.init_seq_number); /* now the address restriction */ sup_addr = (struct sctp_supported_addr_param *)((caddr_t)initm + sizeof(*initm)); sup_addr->ph.param_type = htons(SCTP_SUPPORTED_ADDRTYPE); /* we support 2 types IPv6/IPv4 */ sup_addr->ph.param_length = htons(sizeof(*sup_addr) + sizeof(uint16_t)); sup_addr->addr_type[0] = htons(SCTP_IPV4_ADDRESS); sup_addr->addr_type[1] = htons(SCTP_IPV6_ADDRESS); SCTP_BUF_LEN(m) += sizeof(*sup_addr) + sizeof(uint16_t); if (inp->sctp_ep.adaptation_layer_indicator) { struct sctp_adaptation_layer_indication *ali; ali = (struct sctp_adaptation_layer_indication *)( (caddr_t)sup_addr + sizeof(*sup_addr) + sizeof(uint16_t)); ali->ph.param_type = htons(SCTP_ULP_ADAPTATION); ali->ph.param_length = htons(sizeof(*ali)); ali->indication = ntohl(inp->sctp_ep.adaptation_layer_indicator); SCTP_BUF_LEN(m) += sizeof(*ali); ecn = (struct sctp_ecn_supported_param *)((caddr_t)ali + sizeof(*ali)); } else { ecn = (struct sctp_ecn_supported_param *)((caddr_t)sup_addr + sizeof(*sup_addr) + sizeof(uint16_t)); } /* now any cookie time extensions */ if (stcb->asoc.cookie_preserve_req) { struct sctp_cookie_perserve_param *cookie_preserve; cookie_preserve = (struct sctp_cookie_perserve_param *)(ecn); cookie_preserve->ph.param_type = htons(SCTP_COOKIE_PRESERVE); cookie_preserve->ph.param_length = htons( sizeof(*cookie_preserve)); cookie_preserve->time = htonl(stcb->asoc.cookie_preserve_req); SCTP_BUF_LEN(m) += sizeof(*cookie_preserve); ecn = (struct sctp_ecn_supported_param *)( (caddr_t)cookie_preserve + sizeof(*cookie_preserve)); stcb->asoc.cookie_preserve_req = 0; } /* ECN parameter */ if (sctp_ecn_enable == 1) { ecn->ph.param_type = htons(SCTP_ECN_CAPABLE); ecn->ph.param_length = htons(sizeof(*ecn)); SCTP_BUF_LEN(m) += sizeof(*ecn); prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn + sizeof(*ecn)); } else { prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn); } /* And now tell the peer we do pr-sctp */ prsctp->ph.param_type = htons(SCTP_PRSCTP_SUPPORTED); prsctp->ph.param_length = htons(sizeof(*prsctp)); SCTP_BUF_LEN(m) += sizeof(*prsctp); /* And now tell the peer we do all the extensions */ pr_supported = (struct sctp_supported_chunk_types_param *) ((caddr_t)prsctp + sizeof(*prsctp)); pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT); num_ext = 0; pr_supported->chunk_types[num_ext++] = SCTP_ASCONF; pr_supported->chunk_types[num_ext++] = SCTP_ASCONF_ACK; pr_supported->chunk_types[num_ext++] = SCTP_FORWARD_CUM_TSN; pr_supported->chunk_types[num_ext++] = SCTP_PACKET_DROPPED; pr_supported->chunk_types[num_ext++] = SCTP_STREAM_RESET; if (!sctp_auth_disable) pr_supported->chunk_types[num_ext++] = SCTP_AUTHENTICATION; p_len = sizeof(*pr_supported) + num_ext; pr_supported->ph.param_length = htons(p_len); bzero((caddr_t)pr_supported + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); /* ECN nonce: And now tell the peer we support ECN nonce */ if (sctp_ecn_nonce) { ecn_nonce = (struct sctp_ecn_nonce_supported_param *) ((caddr_t)pr_supported + SCTP_SIZE32(p_len)); ecn_nonce->ph.param_type = htons(SCTP_ECN_NONCE_SUPPORTED); ecn_nonce->ph.param_length = htons(sizeof(*ecn_nonce)); SCTP_BUF_LEN(m) += sizeof(*ecn_nonce); } /* add authentication parameters */ if (!sctp_auth_disable) { struct sctp_auth_random *random; struct sctp_auth_hmac_algo *hmacs; struct sctp_auth_chunk_list *chunks; /* attach RANDOM parameter, if available */ if (stcb->asoc.authinfo.random != NULL) { random = (struct sctp_auth_random *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); p_len = sizeof(*random) + stcb->asoc.authinfo.random_len; #ifdef SCTP_AUTH_DRAFT_04 random->ph.param_type = htons(SCTP_RANDOM); random->ph.param_length = htons(p_len); bcopy(stcb->asoc.authinfo.random->key, random->random_data, stcb->asoc.authinfo.random_len); #else /* random key already contains the header */ bcopy(stcb->asoc.authinfo.random->key, random, p_len); #endif /* zero out any padding required */ bzero((caddr_t)random + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); } /* add HMAC_ALGO parameter */ hmacs = (struct sctp_auth_hmac_algo *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); p_len = sctp_serialize_hmaclist(stcb->asoc.local_hmacs, (uint8_t *) hmacs->hmac_ids); if (p_len > 0) { p_len += sizeof(*hmacs); hmacs->ph.param_type = htons(SCTP_HMAC_LIST); hmacs->ph.param_length = htons(p_len); /* zero out any padding required */ bzero((caddr_t)hmacs + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); } /* add CHUNKS parameter */ chunks = (struct sctp_auth_chunk_list *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); p_len = sctp_serialize_auth_chunks(stcb->asoc.local_auth_chunks, chunks->chunk_types); if (p_len > 0) { p_len += sizeof(*chunks); chunks->ph.param_type = htons(SCTP_CHUNK_LIST); chunks->ph.param_length = htons(p_len); /* zero out any padding required */ bzero((caddr_t)chunks + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); } } m_at = m; /* now the addresses */ { struct sctp_scoping scp; /* * To optimize this we could put the scoping stuff into a * structure and remove the individual uint8's from the * assoc structure. Then we could just sifa in the address * within the stcb.. but for now this is a quick hack to get * the address stuff teased apart. */ scp.ipv4_addr_legal = stcb->asoc.ipv4_addr_legal; scp.ipv6_addr_legal = stcb->asoc.ipv6_addr_legal; scp.loopback_scope = stcb->asoc.loopback_scope; scp.ipv4_local_scope = stcb->asoc.ipv4_local_scope; scp.local_scope = stcb->asoc.local_scope; scp.site_scope = stcb->asoc.site_scope; m_at = sctp_add_addresses_to_i_ia(inp, &scp, m_at, cnt_inits_to); } /* calulate the size and update pkt header and chunk header */ p_len = 0; for (m_at = m; m_at; m_at = SCTP_BUF_NEXT(m_at)) { if (SCTP_BUF_NEXT(m_at) == NULL) mp_last = m_at; p_len += SCTP_BUF_LEN(m_at); } initm->msg.ch.chunk_length = htons((p_len - sizeof(struct sctphdr))); /* * We sifa 0 here to NOT set IP_DF if its IPv4, we ignore the return * here since the timer will drive a retranmission. */ /* I don't expect this to execute but we will be safe here */ padval = p_len % 4; if ((padval) && (mp_last)) { /* * The compiler worries that mp_last may not be set even * though I think it is impossible :-> however we add * mp_last here just in case. */ int ret; ret = sctp_add_pad_tombuf(mp_last, (4 - padval)); if (ret) { /* Houston we have a problem, no space */ sctp_m_freem(m); return; } p_len += padval; } ret = sctp_lowlevel_chunk_output(inp, stcb, net, (struct sockaddr *)&net->ro._l_addr, m, 0, NULL, 0, 0, NULL, 0); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, net); - SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); } struct mbuf * sctp_arethere_unrecognized_parameters(struct mbuf *in_initpkt, int param_offset, int *abort_processing, struct sctp_chunkhdr *cp) { /* * Given a mbuf containing an INIT or INIT-ACK with the param_offset * being equal to the beginning of the params i.e. (iphlen + * sizeof(struct sctp_init_msg) parse through the parameters to the * end of the mbuf verifying that all parameters are known. * * For unknown parameters build and return a mbuf with * UNRECOGNIZED_PARAMETER errors. If the flags indicate to stop * processing this chunk stop, and set *abort_processing to 1. * * By having param_offset be pre-set to where parameters begin it is * hoped that this routine may be reused in the future by new * features. */ struct sctp_paramhdr *phdr, params; struct mbuf *mat, *op_err; char tempbuf[SCTP_PARAM_BUFFER_SIZE]; int at, limit, pad_needed; uint16_t ptype, plen, padded_size; int err_at; *abort_processing = 0; mat = in_initpkt; err_at = 0; limit = ntohs(cp->chunk_length) - sizeof(struct sctp_init_chunk); at = param_offset; op_err = NULL; phdr = sctp_get_next_param(mat, at, ¶ms, sizeof(params)); while ((phdr != NULL) && ((size_t)limit >= sizeof(struct sctp_paramhdr))) { ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if ((plen > limit) || (plen < sizeof(struct sctp_paramhdr))) { /* wacked parameter */ goto invalid_size; } limit -= SCTP_SIZE32(plen); /*- * All parameters for all chunks that we know/understand are * listed here. We process them other places and make * appropriate stop actions per the upper bits. However this * is the generic routine processor's can call to get back * an operr.. to either incorporate (init-ack) or send. */ padded_size = SCTP_SIZE32(plen); switch (ptype) { /* Param's with variable size */ case SCTP_HEARTBEAT_INFO: case SCTP_STATE_COOKIE: case SCTP_UNRECOG_PARAM: case SCTP_ERROR_CAUSE_IND: /* ok skip fwd */ at += padded_size; break; /* Param's with variable size within a range */ case SCTP_CHUNK_LIST: case SCTP_SUPPORTED_CHUNK_EXT: if (padded_size > (sizeof(struct sctp_supported_chunk_types_param) + (sizeof(uint8_t) * SCTP_MAX_SUPPORTED_EXT))) { goto invalid_size; } at += padded_size; break; case SCTP_SUPPORTED_ADDRTYPE: if (padded_size > SCTP_MAX_ADDR_PARAMS_SIZE) { goto invalid_size; } at += padded_size; break; case SCTP_RANDOM: if (padded_size > (sizeof(struct sctp_auth_random) + SCTP_RANDOM_MAX_SIZE)) { goto invalid_size; } at += padded_size; break; case SCTP_SET_PRIM_ADDR: case SCTP_DEL_IP_ADDRESS: case SCTP_ADD_IP_ADDRESS: if ((padded_size != sizeof(struct sctp_asconf_addrv4_param)) && (padded_size != sizeof(struct sctp_asconf_addr_param))) { goto invalid_size; } at += padded_size; break; /* Param's with a fixed size */ case SCTP_IPV4_ADDRESS: if (padded_size != sizeof(struct sctp_ipv4addr_param)) { goto invalid_size; } at += padded_size; break; case SCTP_IPV6_ADDRESS: if (padded_size != sizeof(struct sctp_ipv6addr_param)) { goto invalid_size; } at += padded_size; break; case SCTP_COOKIE_PRESERVE: if (padded_size != sizeof(struct sctp_cookie_perserve_param)) { goto invalid_size; } at += padded_size; break; case SCTP_ECN_NONCE_SUPPORTED: case SCTP_PRSCTP_SUPPORTED: if (padded_size != sizeof(struct sctp_paramhdr)) { goto invalid_size; } at += padded_size; break; case SCTP_ECN_CAPABLE: if (padded_size != sizeof(struct sctp_ecn_supported_param)) { goto invalid_size; } at += padded_size; break; case SCTP_ULP_ADAPTATION: if (padded_size != sizeof(struct sctp_adaptation_layer_indication)) { goto invalid_size; } at += padded_size; break; case SCTP_SUCCESS_REPORT: if (padded_size != sizeof(struct sctp_asconf_paramhdr)) { goto invalid_size; } at += padded_size; break; case SCTP_HOSTNAME_ADDRESS: { /* We can NOT handle HOST NAME addresses!! */ int l_len; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { printf("Can't handle hostname addresses.. abort processing\n"); } #endif *abort_processing = 1; if (op_err == NULL) { /* Ok need to try to get a mbuf */ l_len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr); l_len += plen; l_len += sizeof(struct sctp_paramhdr); op_err = sctp_get_mbuf_for_msg(l_len, 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { SCTP_BUF_LEN(op_err) = 0; /* * pre-reserve space for ip * and sctp header and * chunk hdr */ SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr)); } } if (op_err) { /* If we have space */ struct sctp_paramhdr s; if (err_at % 4) { uint32_t cpthis = 0; pad_needed = 4 - (err_at % 4); m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis); err_at += pad_needed; } s.param_type = htons(SCTP_CAUSE_UNRESOLVABLE_ADDR); s.param_length = htons(sizeof(s) + plen); m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s); err_at += sizeof(s); phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, min(sizeof(tempbuf), plen)); if (phdr == NULL) { sctp_m_freem(op_err); /* * we are out of memory but * we still need to have a * look at what to do (the * system is in trouble * though). */ return (NULL); } m_copyback(op_err, err_at, plen, (caddr_t)phdr); err_at += plen; } return (op_err); break; } default: /* * we do not recognize the parameter figure out what * we do. */ if ((ptype & 0x4000) == 0x4000) { /* Report bit is set?? */ if (op_err == NULL) { int l_len; /* Ok need to try to get an mbuf */ l_len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr); l_len += plen; l_len += sizeof(struct sctp_paramhdr); op_err = sctp_get_mbuf_for_msg(l_len, 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { SCTP_BUF_LEN(op_err) = 0; SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr)); } } if (op_err) { /* If we have space */ struct sctp_paramhdr s; if (err_at % 4) { uint32_t cpthis = 0; pad_needed = 4 - (err_at % 4); m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis); err_at += pad_needed; } s.param_type = htons(SCTP_UNRECOG_PARAM); s.param_length = htons(sizeof(s) + plen); m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s); err_at += sizeof(s); if (plen > sizeof(tempbuf)) { plen = sizeof(tempbuf); } phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, min(sizeof(tempbuf), plen)); if (phdr == NULL) { sctp_m_freem(op_err); /* * we are out of memory but * we still need to have a * look at what to do (the * system is in trouble * though). */ op_err = NULL; goto more_processing; } m_copyback(op_err, err_at, plen, (caddr_t)phdr); err_at += plen; } } more_processing: if ((ptype & 0x8000) == 0x0000) { return (op_err); } else { /* skip this chunk and continue processing */ at += SCTP_SIZE32(plen); } break; } phdr = sctp_get_next_param(mat, at, ¶ms, sizeof(params)); } return (op_err); invalid_size: *abort_processing = 1; if ((op_err == NULL) && phdr) { int l_len; l_len = sizeof(struct ip6_hdr) + sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr); l_len += (2 * sizeof(struct sctp_paramhdr)); op_err = sctp_get_mbuf_for_msg(l_len, 0, M_DONTWAIT, 1, MT_DATA); SCTP_BUF_LEN(op_err) = 0; SCTP_BUF_RESV_UF(op_err, sizeof(struct ip6_hdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctphdr)); SCTP_BUF_RESV_UF(op_err, sizeof(struct sctp_chunkhdr)); } if ((op_err) && phdr) { struct sctp_paramhdr s; if (err_at % 4) { uint32_t cpthis = 0; pad_needed = 4 - (err_at % 4); m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis); err_at += pad_needed; } s.param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); s.param_length = htons(sizeof(s) + sizeof(struct sctp_paramhdr)); m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s); err_at += sizeof(s); /* Only copy back the p-hdr that caused the issue */ m_copyback(op_err, err_at, sizeof(struct sctp_paramhdr), (caddr_t)phdr); } return (op_err); } static int sctp_are_there_new_addresses(struct sctp_association *asoc, struct mbuf *in_initpkt, int iphlen, int offset) { /* * Given a INIT packet, look through the packet to verify that there * are NO new addresses. As we go through the parameters add reports * of any un-understood parameters that require an error. Also we * must return (1) to drop the packet if we see a un-understood * parameter that tells us to drop the chunk. */ struct sockaddr_in sin4, *sa4; struct sockaddr_in6 sin6, *sa6; struct sockaddr *sa_touse; struct sockaddr *sa; struct sctp_paramhdr *phdr, params; struct ip *iph; struct mbuf *mat; uint16_t ptype, plen; int err_at; uint8_t fnd; struct sctp_nets *net; memset(&sin4, 0, sizeof(sin4)); memset(&sin6, 0, sizeof(sin6)); sin4.sin_family = AF_INET; sin4.sin_len = sizeof(sin4); sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(sin6); sa_touse = NULL; /* First what about the src address of the pkt ? */ iph = mtod(in_initpkt, struct ip *); if (iph->ip_v == IPVERSION) { /* source addr is IPv4 */ sin4.sin_addr = iph->ip_src; sa_touse = (struct sockaddr *)&sin4; } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* source addr is IPv6 */ struct ip6_hdr *ip6h; ip6h = mtod(in_initpkt, struct ip6_hdr *); sin6.sin6_addr = ip6h->ip6_src; sa_touse = (struct sockaddr *)&sin6; } else { return (1); } fnd = 0; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { sa = (struct sockaddr *)&net->ro._l_addr; if (sa->sa_family == sa_touse->sa_family) { if (sa->sa_family == AF_INET) { sa4 = (struct sockaddr_in *)sa; if (sa4->sin_addr.s_addr == sin4.sin_addr.s_addr) { fnd = 1; break; } } else if (sa->sa_family == AF_INET6) { sa6 = (struct sockaddr_in6 *)sa; if (SCTP6_ARE_ADDR_EQUAL(&sa6->sin6_addr, &sin6.sin6_addr)) { fnd = 1; break; } } } } if (fnd == 0) { /* New address added! no need to look futher. */ return (1); } /* Ok so far lets munge through the rest of the packet */ mat = in_initpkt; err_at = 0; sa_touse = NULL; offset += sizeof(struct sctp_init_chunk); phdr = sctp_get_next_param(mat, offset, ¶ms, sizeof(params)); while (phdr) { ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if (ptype == SCTP_IPV4_ADDRESS) { struct sctp_ipv4addr_param *p4, p4_buf; phdr = sctp_get_next_param(mat, offset, (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf)); if (plen != sizeof(struct sctp_ipv4addr_param) || phdr == NULL) { return (1); } p4 = (struct sctp_ipv4addr_param *)phdr; sin4.sin_addr.s_addr = p4->addr; sa_touse = (struct sockaddr *)&sin4; } else if (ptype == SCTP_IPV6_ADDRESS) { struct sctp_ipv6addr_param *p6, p6_buf; phdr = sctp_get_next_param(mat, offset, (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf)); if (plen != sizeof(struct sctp_ipv6addr_param) || phdr == NULL) { return (1); } p6 = (struct sctp_ipv6addr_param *)phdr; memcpy((caddr_t)&sin6.sin6_addr, p6->addr, sizeof(p6->addr)); sa_touse = (struct sockaddr *)&sin4; } if (sa_touse) { /* ok, sa_touse points to one to check */ fnd = 0; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { sa = (struct sockaddr *)&net->ro._l_addr; if (sa->sa_family != sa_touse->sa_family) { continue; } if (sa->sa_family == AF_INET) { sa4 = (struct sockaddr_in *)sa; if (sa4->sin_addr.s_addr == sin4.sin_addr.s_addr) { fnd = 1; break; } } else if (sa->sa_family == AF_INET6) { sa6 = (struct sockaddr_in6 *)sa; if (SCTP6_ARE_ADDR_EQUAL( &sa6->sin6_addr, &sin6.sin6_addr)) { fnd = 1; break; } } } if (!fnd) { /* New addr added! no need to look further */ return (1); } } offset += SCTP_SIZE32(plen); phdr = sctp_get_next_param(mat, offset, ¶ms, sizeof(params)); } return (0); } /* * Given a MBUF chain that was sent into us containing an INIT. Build a * INIT-ACK with COOKIE and send back. We assume that the in_initpkt has done * a pullup to include IPv6/4header, SCTP header and initial part of INIT * message (i.e. the struct sctp_init_msg). */ void sctp_send_initiate_ack(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct mbuf *init_pkt, int iphlen, int offset, struct sctphdr *sh, struct sctp_init_chunk *init_chk, uint32_t vrf_id, uint32_t table_id) { struct sctp_association *asoc; struct mbuf *m, *m_at, *m_tmp, *m_cookie, *op_err, *mp_last; struct sctp_init_msg *initackm_out; struct sctp_ecn_supported_param *ecn; struct sctp_prsctp_supported_param *prsctp; struct sctp_ecn_nonce_supported_param *ecn_nonce; struct sctp_supported_chunk_types_param *pr_supported; struct sockaddr_storage store; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; sctp_route_t *ro; struct ip *iph; struct ip6_hdr *ip6; struct sockaddr *to; struct sctp_state_cookie stc; struct sctp_nets *net = NULL; int cnt_inits_to = 0; uint16_t his_limit, i_want; int abort_flag, padval, sz_of; int num_ext; int p_len; if (stcb) asoc = &stcb->asoc; else asoc = NULL; mp_last = NULL; if ((asoc != NULL) && (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) && (sctp_are_there_new_addresses(asoc, init_pkt, iphlen, offset))) { /* new addresses, out of here in non-cookie-wait states */ /* * Send a ABORT, we don't add the new address error clause * though we even set the T bit and copy in the 0 tag.. this * looks no different than if no listener was present. */ sctp_send_abort(init_pkt, iphlen, sh, 0, NULL, vrf_id, table_id); return; } abort_flag = 0; op_err = sctp_arethere_unrecognized_parameters(init_pkt, (offset + sizeof(struct sctp_init_chunk)), &abort_flag, (struct sctp_chunkhdr *)init_chk); if (abort_flag) { sctp_send_abort(init_pkt, iphlen, sh, init_chk->init.initiate_tag, op_err, vrf_id, table_id); return; } m = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (m == NULL) { /* No memory, INIT timer will re-attempt. */ if (op_err) sctp_m_freem(op_err); return; } SCTP_BUF_LEN(m) = sizeof(struct sctp_init_msg); /* the time I built cookie */ - SCTP_GETTIME_TIMEVAL(&stc.time_entered); + (void)SCTP_GETTIME_TIMEVAL(&stc.time_entered); /* populate any tie tags */ if (asoc != NULL) { /* unlock before tag selections */ stc.tie_tag_my_vtag = asoc->my_vtag_nonce; stc.tie_tag_peer_vtag = asoc->peer_vtag_nonce; stc.cookie_life = asoc->cookie_life; net = asoc->primary_destination; } else { stc.tie_tag_my_vtag = 0; stc.tie_tag_peer_vtag = 0; /* life I will award this cookie */ stc.cookie_life = inp->sctp_ep.def_cookie_life; } /* copy in the ports for later check */ stc.myport = sh->dest_port; stc.peerport = sh->src_port; /* * If we wanted to honor cookie life extentions, we would add to * stc.cookie_life. For now we should NOT honor any extension */ stc.site_scope = stc.local_scope = stc.loopback_scope = 0; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { struct inpcb *in_inp; /* Its a V6 socket */ in_inp = (struct inpcb *)inp; stc.ipv6_addr_legal = 1; /* Now look at the binding flag to see if V4 will be legal */ if (SCTP_IPV6_V6ONLY(in_inp) == 0) { stc.ipv4_addr_legal = 1; } else { /* V4 addresses are NOT legal on the association */ stc.ipv4_addr_legal = 0; } } else { /* Its a V4 socket, no - V6 */ stc.ipv4_addr_legal = 1; stc.ipv6_addr_legal = 0; } #ifdef SCTP_DONT_DO_PRIVADDR_SCOPE stc.ipv4_scope = 1; #else stc.ipv4_scope = 0; #endif /* now for scope setup */ memset((caddr_t)&store, 0, sizeof(store)); sin = (struct sockaddr_in *)&store; sin6 = (struct sockaddr_in6 *)&store; if (net == NULL) { to = (struct sockaddr *)&store; iph = mtod(init_pkt, struct ip *); if (iph->ip_v == IPVERSION) { struct sctp_ifa *addr; sctp_route_t iproute; sin->sin_family = AF_INET; sin->sin_len = sizeof(struct sockaddr_in); sin->sin_port = sh->src_port; sin->sin_addr = iph->ip_src; /* lookup address */ stc.address[0] = sin->sin_addr.s_addr; stc.address[1] = 0; stc.address[2] = 0; stc.address[3] = 0; stc.addr_type = SCTP_IPV4_ADDRESS; /* local from address */ memset(&iproute, 0, sizeof(iproute)); ro = &iproute; memcpy(&ro->ro_dst, sin, sizeof(*sin)); addr = sctp_source_address_selection(inp, NULL, ro, NULL, 0, vrf_id); if (addr == NULL) return; if (ro->ro_rt) { RTFREE(ro->ro_rt); ro->ro_rt = NULL; } stc.laddress[0] = addr->address.sin.sin_addr.s_addr; stc.laddress[1] = 0; stc.laddress[2] = 0; stc.laddress[3] = 0; stc.laddr_type = SCTP_IPV4_ADDRESS; /* scope_id is only for v6 */ stc.scope_id = 0; #ifndef SCTP_DONT_DO_PRIVADDR_SCOPE if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { stc.ipv4_scope = 1; } #else stc.ipv4_scope = 1; #endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */ /* Must use the address in this case */ if (sctp_is_address_on_local_host((struct sockaddr *)sin, vrf_id)) { stc.loopback_scope = 1; stc.ipv4_scope = 1; stc.site_scope = 1; stc.local_scope = 0; } sctp_free_ifa(addr); } else if (iph->ip_v == (IPV6_VERSION >> 4)) { struct sctp_ifa *addr; struct route_in6 iproute6; ip6 = mtod(init_pkt, struct ip6_hdr *); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_port = sh->src_port; sin6->sin6_addr = ip6->ip6_src; /* lookup address */ memcpy(&stc.address, &sin6->sin6_addr, sizeof(struct in6_addr)); sin6->sin6_scope_id = 0; stc.addr_type = SCTP_IPV6_ADDRESS; stc.scope_id = 0; if (sctp_is_address_on_local_host((struct sockaddr *)sin6, vrf_id)) { stc.loopback_scope = 1; stc.local_scope = 0; stc.site_scope = 1; stc.ipv4_scope = 1; } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { /* * If the new destination is a LINK_LOCAL we * must have common both site and local * scope. Don't set local scope though since * we must depend on the source to be added * implicitly. We cannot assure just because * we share one link that all links are * common. */ stc.local_scope = 0; stc.site_scope = 1; stc.ipv4_scope = 1; /* * we start counting for the private address * stuff at 1. since the link local we * source from won't show up in our scoped * count. */ cnt_inits_to = 1; /* pull out the scope_id from incoming pkt */ /* FIX ME: does this have scope from rcvif? */ (void)sa6_recoverscope(sin6); sa6_embedscope(sin6, ip6_use_defzone); stc.scope_id = sin6->sin6_scope_id; } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { /* * If the new destination is SITE_LOCAL then * we must have site scope in common. */ stc.site_scope = 1; } /* local from address */ memset(&iproute6, 0, sizeof(iproute6)); ro = (sctp_route_t *) & iproute6; memcpy(&ro->ro_dst, sin6, sizeof(*sin6)); addr = sctp_source_address_selection(inp, NULL, ro, NULL, 0, vrf_id); if (addr == NULL) return; if (ro->ro_rt) { RTFREE(ro->ro_rt); ro->ro_rt = NULL; } memcpy(&stc.laddress, &addr->address.sin6.sin6_addr, sizeof(struct in6_addr)); stc.laddr_type = SCTP_IPV6_ADDRESS; sctp_free_ifa(addr); } } else { /* set the scope per the existing tcb */ struct sctp_nets *lnet; stc.loopback_scope = asoc->loopback_scope; stc.ipv4_scope = asoc->ipv4_local_scope; stc.site_scope = asoc->site_scope; stc.local_scope = asoc->local_scope; TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) { if (lnet->ro._l_addr.sin6.sin6_family == AF_INET6) { if (IN6_IS_ADDR_LINKLOCAL(&lnet->ro._l_addr.sin6.sin6_addr)) { /* * if we have a LL address, start * counting at 1. */ cnt_inits_to = 1; } } } /* use the net pointer */ to = (struct sockaddr *)&net->ro._l_addr; if (to->sa_family == AF_INET) { sin = (struct sockaddr_in *)to; stc.address[0] = sin->sin_addr.s_addr; stc.address[1] = 0; stc.address[2] = 0; stc.address[3] = 0; stc.addr_type = SCTP_IPV4_ADDRESS; if (net->src_addr_selected == 0) { /* * strange case here, the INIT should have * did the selection. */ net->ro._s_addr = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, vrf_id); if (net->ro._s_addr == NULL) return; net->src_addr_selected = 1; } stc.laddress[0] = net->ro._s_addr->address.sin.sin_addr.s_addr; stc.laddress[1] = 0; stc.laddress[2] = 0; stc.laddress[3] = 0; stc.laddr_type = SCTP_IPV4_ADDRESS; } else if (to->sa_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)to; memcpy(&stc.address, &sin6->sin6_addr, sizeof(struct in6_addr)); stc.addr_type = SCTP_IPV6_ADDRESS; if (net->src_addr_selected == 0) { /* * strange case here, the INIT should have * did the selection. */ net->ro._s_addr = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, vrf_id); if (net->ro._s_addr == NULL) return; net->src_addr_selected = 1; } memcpy(&stc.laddress, &net->ro._s_addr->address.sin6.sin6_addr, sizeof(struct in6_addr)); stc.laddr_type = SCTP_IPV6_ADDRESS; } } /* Now lets put the SCTP header in place */ initackm_out = mtod(m, struct sctp_init_msg *); initackm_out->sh.src_port = inp->sctp_lport; initackm_out->sh.dest_port = sh->src_port; initackm_out->sh.v_tag = init_chk->init.initiate_tag; /* Save it off for quick ref */ stc.peers_vtag = init_chk->init.initiate_tag; initackm_out->sh.checksum = 0; /* calculate later */ /* who are we */ memcpy(stc.identification, SCTP_VERSION_STRING, min(strlen(SCTP_VERSION_STRING), sizeof(stc.identification))); /* now the chunk header */ initackm_out->msg.ch.chunk_type = SCTP_INITIATION_ACK; initackm_out->msg.ch.chunk_flags = 0; /* fill in later from mbuf we build */ initackm_out->msg.ch.chunk_length = 0; /* place in my tag */ if ((asoc != NULL) && ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_INUSE) || (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED))) { /* re-use the v-tags and init-seq here */ initackm_out->msg.init.initiate_tag = htonl(asoc->my_vtag); initackm_out->msg.init.initial_tsn = htonl(asoc->init_seq_number); } else { uint32_t vtag; if (asoc) { atomic_add_int(&asoc->refcnt, 1); SCTP_TCB_UNLOCK(stcb); vtag = sctp_select_a_tag(inp); initackm_out->msg.init.initiate_tag = htonl(vtag); /* get a TSN to use too */ initackm_out->msg.init.initial_tsn = htonl(sctp_select_initial_TSN(&inp->sctp_ep)); SCTP_TCB_LOCK(stcb); atomic_add_int(&asoc->refcnt, -1); } else { vtag = sctp_select_a_tag(inp); initackm_out->msg.init.initiate_tag = htonl(vtag); /* get a TSN to use too */ initackm_out->msg.init.initial_tsn = htonl(sctp_select_initial_TSN(&inp->sctp_ep)); } } /* save away my tag to */ stc.my_vtag = initackm_out->msg.init.initiate_tag; /* set up some of the credits. */ initackm_out->msg.init.a_rwnd = htonl(max(SCTP_SB_LIMIT_RCV(inp->sctp_socket), SCTP_MINIMAL_RWND)); /* set what I want */ his_limit = ntohs(init_chk->init.num_inbound_streams); /* choose what I want */ if (asoc != NULL) { if (asoc->streamoutcnt > inp->sctp_ep.pre_open_stream_count) { i_want = asoc->streamoutcnt; } else { i_want = inp->sctp_ep.pre_open_stream_count; } } else { i_want = inp->sctp_ep.pre_open_stream_count; } if (his_limit < i_want) { /* I Want more :< */ initackm_out->msg.init.num_outbound_streams = init_chk->init.num_inbound_streams; } else { /* I can have what I want :> */ initackm_out->msg.init.num_outbound_streams = htons(i_want); } /* tell him his limt. */ initackm_out->msg.init.num_inbound_streams = htons(inp->sctp_ep.max_open_streams_intome); /* setup the ECN pointer */ if (inp->sctp_ep.adaptation_layer_indicator) { struct sctp_adaptation_layer_indication *ali; ali = (struct sctp_adaptation_layer_indication *)( (caddr_t)initackm_out + sizeof(*initackm_out)); ali->ph.param_type = htons(SCTP_ULP_ADAPTATION); ali->ph.param_length = htons(sizeof(*ali)); ali->indication = ntohl(inp->sctp_ep.adaptation_layer_indicator); SCTP_BUF_LEN(m) += sizeof(*ali); ecn = (struct sctp_ecn_supported_param *)((caddr_t)ali + sizeof(*ali)); } else { ecn = (struct sctp_ecn_supported_param *)( (caddr_t)initackm_out + sizeof(*initackm_out)); } /* ECN parameter */ if (sctp_ecn_enable == 1) { ecn->ph.param_type = htons(SCTP_ECN_CAPABLE); ecn->ph.param_length = htons(sizeof(*ecn)); SCTP_BUF_LEN(m) += sizeof(*ecn); prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn + sizeof(*ecn)); } else { prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn); } /* And now tell the peer we do pr-sctp */ prsctp->ph.param_type = htons(SCTP_PRSCTP_SUPPORTED); prsctp->ph.param_length = htons(sizeof(*prsctp)); SCTP_BUF_LEN(m) += sizeof(*prsctp); /* And now tell the peer we do all the extensions */ pr_supported = (struct sctp_supported_chunk_types_param *) ((caddr_t)prsctp + sizeof(*prsctp)); pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT); num_ext = 0; pr_supported->chunk_types[num_ext++] = SCTP_ASCONF; pr_supported->chunk_types[num_ext++] = SCTP_ASCONF_ACK; pr_supported->chunk_types[num_ext++] = SCTP_FORWARD_CUM_TSN; pr_supported->chunk_types[num_ext++] = SCTP_PACKET_DROPPED; pr_supported->chunk_types[num_ext++] = SCTP_STREAM_RESET; if (!sctp_auth_disable) pr_supported->chunk_types[num_ext++] = SCTP_AUTHENTICATION; p_len = sizeof(*pr_supported) + num_ext; pr_supported->ph.param_length = htons(p_len); bzero((caddr_t)pr_supported + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); /* ECN nonce: And now tell the peer we support ECN nonce */ if (sctp_ecn_nonce) { ecn_nonce = (struct sctp_ecn_nonce_supported_param *) ((caddr_t)pr_supported + SCTP_SIZE32(p_len)); ecn_nonce->ph.param_type = htons(SCTP_ECN_NONCE_SUPPORTED); ecn_nonce->ph.param_length = htons(sizeof(*ecn_nonce)); SCTP_BUF_LEN(m) += sizeof(*ecn_nonce); } /* add authentication parameters */ if (!sctp_auth_disable) { struct sctp_auth_random *random; struct sctp_auth_hmac_algo *hmacs; struct sctp_auth_chunk_list *chunks; uint16_t random_len; /* generate and add RANDOM parameter */ random_len = SCTP_AUTH_RANDOM_SIZE_DEFAULT; random = (struct sctp_auth_random *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); random->ph.param_type = htons(SCTP_RANDOM); p_len = sizeof(*random) + random_len; random->ph.param_length = htons(p_len); SCTP_READ_RANDOM(random->random_data, random_len); /* zero out any padding required */ bzero((caddr_t)random + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); /* add HMAC_ALGO parameter */ hmacs = (struct sctp_auth_hmac_algo *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); p_len = sctp_serialize_hmaclist(inp->sctp_ep.local_hmacs, (uint8_t *) hmacs->hmac_ids); if (p_len > 0) { p_len += sizeof(*hmacs); hmacs->ph.param_type = htons(SCTP_HMAC_LIST); hmacs->ph.param_length = htons(p_len); /* zero out any padding required */ bzero((caddr_t)hmacs + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); } /* add CHUNKS parameter */ chunks = (struct sctp_auth_chunk_list *)(mtod(m, caddr_t)+SCTP_BUF_LEN(m)); p_len = sctp_serialize_auth_chunks(inp->sctp_ep.local_auth_chunks, chunks->chunk_types); if (p_len > 0) { p_len += sizeof(*chunks); chunks->ph.param_type = htons(SCTP_CHUNK_LIST); chunks->ph.param_length = htons(p_len); /* zero out any padding required */ bzero((caddr_t)chunks + p_len, SCTP_SIZE32(p_len) - p_len); SCTP_BUF_LEN(m) += SCTP_SIZE32(p_len); } } m_at = m; /* now the addresses */ { struct sctp_scoping scp; /* * To optimize this we could put the scoping stuff into a * structure and remove the individual uint8's from the stc * structure. Then we could just sifa in the address within * the stc.. but for now this is a quick hack to get the * address stuff teased apart. */ scp.ipv4_addr_legal = stc.ipv4_addr_legal; scp.ipv6_addr_legal = stc.ipv6_addr_legal; scp.loopback_scope = stc.loopback_scope; scp.ipv4_local_scope = stc.ipv4_scope; scp.local_scope = stc.local_scope; scp.site_scope = stc.site_scope; m_at = sctp_add_addresses_to_i_ia(inp, &scp, m_at, cnt_inits_to); } /* tack on the operational error if present */ if (op_err) { struct mbuf *ol; int llen; llen = 0; ol = op_err; while (ol) { llen += SCTP_BUF_LEN(ol); ol = SCTP_BUF_NEXT(ol); } if (llen % 4) { /* must add a pad to the param */ uint32_t cpthis = 0; int padlen; padlen = 4 - (llen % 4); m_copyback(op_err, llen, padlen, (caddr_t)&cpthis); } while (SCTP_BUF_NEXT(m_at) != NULL) { m_at = SCTP_BUF_NEXT(m_at); } SCTP_BUF_NEXT(m_at) = op_err; while (SCTP_BUF_NEXT(m_at) != NULL) { m_at = SCTP_BUF_NEXT(m_at); } } /* Get total size of init packet */ sz_of = SCTP_SIZE32(ntohs(init_chk->ch.chunk_length)); /* pre-calulate the size and update pkt header and chunk header */ p_len = 0; for (m_tmp = m; m_tmp; m_tmp = SCTP_BUF_NEXT(m_tmp)) { p_len += SCTP_BUF_LEN(m_tmp); if (SCTP_BUF_NEXT(m_tmp) == NULL) { /* m_tmp should now point to last one */ break; } } /* * Figure now the size of the cookie. We know the size of the * INIT-ACK. The Cookie is going to be the size of INIT, INIT-ACK, * COOKIE-STRUCTURE and SIGNATURE. */ /* * take our earlier INIT calc and add in the sz we just calculated * minus the size of the sctphdr (its not included in chunk size */ /* add once for the INIT-ACK */ sz_of += (p_len - sizeof(struct sctphdr)); /* add a second time for the INIT-ACK in the cookie */ sz_of += (p_len - sizeof(struct sctphdr)); /* Now add the cookie header and cookie message struct */ sz_of += sizeof(struct sctp_state_cookie_param); /* ...and add the size of our signature */ sz_of += SCTP_SIGNATURE_SIZE; initackm_out->msg.ch.chunk_length = htons(sz_of); /* Now we must build a cookie */ m_cookie = sctp_add_cookie(inp, init_pkt, offset, m, sizeof(struct sctphdr), &stc); if (m_cookie == NULL) { /* memory problem */ sctp_m_freem(m); return; } /* Now append the cookie to the end and update the space/size */ SCTP_BUF_NEXT(m_tmp) = m_cookie; for (; m_tmp; m_tmp = SCTP_BUF_NEXT(m_tmp)) { p_len += SCTP_BUF_LEN(m_tmp); if (SCTP_BUF_NEXT(m_tmp) == NULL) { /* m_tmp should now point to last one */ mp_last = m_tmp; break; } } /* * We sifa 0 here to NOT set IP_DF if its IPv4, we ignore the return * here since the timer will drive a retranmission. */ padval = p_len % 4; if ((padval) && (mp_last)) { /* see my previous comments on mp_last */ int ret; ret = sctp_add_pad_tombuf(mp_last, (4 - padval)); if (ret) { /* Houston we have a problem, no space */ sctp_m_freem(m); return; } p_len += padval; } sctp_lowlevel_chunk_output(inp, NULL, NULL, to, m, 0, NULL, 0, 0, NULL, 0); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); } void sctp_insert_on_wheel(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_stream_out *strq, int holds_lock) { struct sctp_stream_out *stre, *strn; if (holds_lock == 0) SCTP_TCB_SEND_LOCK(stcb); if ((strq->next_spoke.tqe_next) || (strq->next_spoke.tqe_prev)) { /* already on wheel */ goto outof_here; } stre = TAILQ_FIRST(&asoc->out_wheel); if (stre == NULL) { /* only one on wheel */ TAILQ_INSERT_HEAD(&asoc->out_wheel, strq, next_spoke); goto outof_here; } for (; stre; stre = strn) { strn = TAILQ_NEXT(stre, next_spoke); if (stre->stream_no > strq->stream_no) { TAILQ_INSERT_BEFORE(stre, strq, next_spoke); goto outof_here; } else if (stre->stream_no == strq->stream_no) { /* huh, should not happen */ goto outof_here; } else if (strn == NULL) { /* next one is null */ TAILQ_INSERT_AFTER(&asoc->out_wheel, stre, strq, next_spoke); } } outof_here: if (holds_lock == 0) SCTP_TCB_SEND_UNLOCK(stcb); } static void sctp_remove_from_wheel(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_stream_out *strq) { /* take off and then setup so we know it is not on the wheel */ SCTP_TCB_SEND_LOCK(stcb); if (TAILQ_FIRST(&strq->outqueue)) { /* more was added */ SCTP_TCB_SEND_UNLOCK(stcb); return; } TAILQ_REMOVE(&asoc->out_wheel, strq, next_spoke); strq->next_spoke.tqe_next = NULL; strq->next_spoke.tqe_prev = NULL; SCTP_TCB_SEND_UNLOCK(stcb); } static void sctp_prune_prsctp(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_sndrcvinfo *srcv, int dataout) { int freed_spc = 0; struct sctp_tmit_chunk *chk, *nchk; SCTP_TCB_LOCK_ASSERT(stcb); if ((asoc->peer_supports_prsctp) && (asoc->sent_queue_cnt_removeable > 0)) { TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { /* * Look for chunks marked with the PR_SCTP flag AND * the buffer space flag. If the one being sent is * equal or greater priority then purge the old one * and free some space. */ if (PR_SCTP_BUF_ENABLED(chk->flags)) { /* * This one is PR-SCTP AND buffer space * limited type */ if (chk->rec.data.timetodrop.tv_sec >= (long)srcv->sinfo_timetolive) { /* * Lower numbers equates to higher * priority so if the one we are * looking at has a larger or equal * priority we want to drop the data * and NOT retransmit it. */ if (chk->data) { /* * We release the book_size * if the mbuf is here */ int ret_spc; int cause; if (chk->sent > SCTP_DATAGRAM_UNSENT) cause = SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT; else cause = SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_UNSENT; ret_spc = sctp_release_pr_sctp_chunk(stcb, chk, cause, &asoc->sent_queue); freed_spc += ret_spc; if (freed_spc >= dataout) { return; } } /* if chunk was present */ } /* if of sufficent priority */ } /* if chunk has enabled */ } /* tailqforeach */ chk = TAILQ_FIRST(&asoc->send_queue); while (chk) { nchk = TAILQ_NEXT(chk, sctp_next); /* Here we must move to the sent queue and mark */ if (PR_SCTP_TTL_ENABLED(chk->flags)) { if (chk->rec.data.timetodrop.tv_sec >= (long)srcv->sinfo_timetolive) { if (chk->data) { /* * We release the book_size * if the mbuf is here */ int ret_spc; ret_spc = sctp_release_pr_sctp_chunk(stcb, chk, SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_UNSENT, &asoc->send_queue); freed_spc += ret_spc; if (freed_spc >= dataout) { return; } } /* end if chk->data */ } /* end if right class */ } /* end if chk pr-sctp */ chk = nchk; } /* end while (chk) */ } /* if enabled in asoc */ } __inline int sctp_get_frag_point(struct sctp_tcb *stcb, struct sctp_association *asoc) { int siz, ovh; /* * For endpoints that have both v6 and v4 addresses we must reserve * room for the ipv6 header, for those that are only dealing with V4 * we use a larger frag point. */ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ovh = SCTP_MED_OVERHEAD; } else { ovh = SCTP_MED_V4_OVERHEAD; } if (stcb->sctp_ep->sctp_frag_point > asoc->smallest_mtu) siz = asoc->smallest_mtu - ovh; else siz = (stcb->sctp_ep->sctp_frag_point - ovh); /* * if (siz > (MCLBYTES-sizeof(struct sctp_data_chunk))) { */ /* A data chunk MUST fit in a cluster */ /* siz = (MCLBYTES - sizeof(struct sctp_data_chunk)); */ /* } */ /* adjust for an AUTH chunk if DATA requires auth */ if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) siz -= sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id); if (siz % 4) { /* make it an even word boundary please */ siz -= (siz % 4); } return (siz); } static void sctp_set_prsctp_policy(struct sctp_tcb *stcb, struct sctp_stream_queue_pending *sp) { sp->pr_sctp_on = 0; if (stcb->asoc.peer_supports_prsctp) { /* * We assume that the user wants PR_SCTP_TTL if the user * provides a positive lifetime but does not specify any * PR_SCTP policy. This is a BAD assumption and causes * problems at least with the U-Vancovers MPI folks. I will * change this to be no policy means NO PR-SCTP. */ if (PR_SCTP_ENABLED(sp->sinfo_flags)) { sp->act_flags |= PR_SCTP_POLICY(sp->sinfo_flags); sp->pr_sctp_on = 1; } else { return; } switch (PR_SCTP_POLICY(sp->sinfo_flags)) { case CHUNK_FLAGS_PR_SCTP_BUF: /* * Time to live is a priority stored in tv_sec when * doing the buffer drop thing. */ sp->ts.tv_sec = sp->timetolive; sp->ts.tv_usec = 0; break; case CHUNK_FLAGS_PR_SCTP_TTL: { struct timeval tv; - SCTP_GETTIME_TIMEVAL(&sp->ts); + (void)SCTP_GETTIME_TIMEVAL(&sp->ts); tv.tv_sec = sp->timetolive / 1000; tv.tv_usec = (sp->timetolive * 1000) % 1000000; timevaladd(&sp->ts, &tv); } break; case CHUNK_FLAGS_PR_SCTP_RTX: /* * Time to live is a the number or retransmissions * stored in tv_sec. */ sp->ts.tv_sec = sp->timetolive; sp->ts.tv_usec = 0; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { printf("Unknown PR_SCTP policy %u.\n", PR_SCTP_POLICY(sp->sinfo_flags)); } #endif break; } } } static int sctp_msg_append(struct sctp_tcb *stcb, struct sctp_nets *net, struct mbuf *m, struct sctp_sndrcvinfo *srcv, int hold_stcb_lock) { int error = 0, holds_lock; struct mbuf *at; struct sctp_stream_queue_pending *sp = NULL; struct sctp_stream_out *strm; /* * Given an mbuf chain, put it into the association send queue and * place it on the wheel */ holds_lock = hold_stcb_lock; if (srcv->sinfo_stream >= stcb->asoc.streamoutcnt) { /* Invalid stream number */ error = EINVAL; goto out_now; } if ((stcb->asoc.stream_locked) && (stcb->asoc.stream_locked_on != srcv->sinfo_stream)) { error = EAGAIN; goto out_now; } strm = &stcb->asoc.strmout[srcv->sinfo_stream]; /* Now can we send this? */ if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_SENT) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || (stcb->asoc.state & SCTP_STATE_SHUTDOWN_PENDING)) { /* got data while shutting down */ error = ECONNRESET; goto out_now; } sctp_alloc_a_strmoq(stcb, sp); if (sp == NULL) { error = ENOMEM; goto out_now; } sp->sinfo_flags = srcv->sinfo_flags; sp->timetolive = srcv->sinfo_timetolive; sp->ppid = srcv->sinfo_ppid; sp->context = srcv->sinfo_context; sp->strseq = 0; if (sp->sinfo_flags & SCTP_ADDR_OVER) { sp->net = net; sp->addr_over = 1; } else { sp->net = stcb->asoc.primary_destination; sp->addr_over = 0; } atomic_add_int(&sp->net->ref_count, 1); - SCTP_GETTIME_TIMEVAL(&sp->ts); + (void)SCTP_GETTIME_TIMEVAL(&sp->ts); sp->stream = srcv->sinfo_stream; sp->msg_is_complete = 1; sp->sender_all_done = 1; sp->some_taken = 0; sp->data = m; sp->tail_mbuf = NULL; sp->length = 0; at = m; sctp_set_prsctp_policy(stcb, sp); /* * We could in theory (for sendall) sifa the length in, but we would * still have to hunt through the chain since we need to setup the * tail_mbuf */ while (at) { if (SCTP_BUF_NEXT(at) == NULL) sp->tail_mbuf = at; sp->length += SCTP_BUF_LEN(at); at = SCTP_BUF_NEXT(at); } SCTP_TCB_SEND_LOCK(stcb); sctp_snd_sb_alloc(stcb, sp->length); atomic_add_int(&stcb->asoc.stream_queue_cnt, 1); TAILQ_INSERT_TAIL(&strm->outqueue, sp, next); if ((srcv->sinfo_flags & SCTP_UNORDERED) == 0) { sp->strseq = strm->next_sequence_sent; strm->next_sequence_sent++; } if ((strm->next_spoke.tqe_next == NULL) && (strm->next_spoke.tqe_prev == NULL)) { /* Not on wheel, insert */ sctp_insert_on_wheel(stcb, &stcb->asoc, strm, 1); } m = NULL; SCTP_TCB_SEND_UNLOCK(stcb); out_now: if (m) { sctp_m_freem(m); } return (error); } static struct mbuf * sctp_copy_mbufchain(struct mbuf *clonechain, struct mbuf *outchain, struct mbuf **endofchain, int can_take_mbuf, int sizeofcpy, uint8_t copy_by_ref) { struct mbuf *m; struct mbuf *appendchain; caddr_t cp; int len; if (endofchain == NULL) { /* error */ error_out: if (outchain) sctp_m_freem(outchain); return (NULL); } if (can_take_mbuf) { appendchain = clonechain; } else { if (!copy_by_ref && (sizeofcpy <= ((((sctp_mbuf_threshold_count - 1) * MLEN) + MHLEN))) ) { /* Its not in a cluster */ if (*endofchain == NULL) { /* lets get a mbuf cluster */ if (outchain == NULL) { /* This is the general case */ new_mbuf: outchain = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_HEADER); if (outchain == NULL) { goto error_out; } SCTP_BUF_LEN(outchain) = 0; *endofchain = outchain; /* get the prepend space */ SCTP_BUF_RESV_UF(outchain, (SCTP_FIRST_MBUF_RESV + 4)); } else { /* * We really should not get a NULL * in endofchain */ /* find end */ m = outchain; while (m) { if (SCTP_BUF_NEXT(m) == NULL) { *endofchain = m; break; } m = SCTP_BUF_NEXT(m); } /* sanity */ if (*endofchain == NULL) { /* * huh, TSNH XXX maybe we * should panic */ sctp_m_freem(outchain); goto new_mbuf; } } /* get the new end of length */ len = M_TRAILINGSPACE(*endofchain); } else { /* how much is left at the end? */ len = M_TRAILINGSPACE(*endofchain); } /* Find the end of the data, for appending */ cp = (mtod((*endofchain), caddr_t)+SCTP_BUF_LEN((*endofchain))); /* Now lets copy it out */ if (len >= sizeofcpy) { /* It all fits, copy it in */ m_copydata(clonechain, 0, sizeofcpy, cp); SCTP_BUF_LEN((*endofchain)) += sizeofcpy; } else { /* fill up the end of the chain */ if (len > 0) { m_copydata(clonechain, 0, len, cp); SCTP_BUF_LEN((*endofchain)) += len; /* now we need another one */ sizeofcpy -= len; } m = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_HEADER); if (m == NULL) { /* We failed */ goto error_out; } SCTP_BUF_NEXT((*endofchain)) = m; *endofchain = m; cp = mtod((*endofchain), caddr_t); m_copydata(clonechain, len, sizeofcpy, cp); SCTP_BUF_LEN((*endofchain)) += sizeofcpy; } return (outchain); } else { /* copy the old fashion way */ appendchain = SCTP_M_COPYM(clonechain, 0, M_COPYALL, M_DONTWAIT); } } if (appendchain == NULL) { /* error */ if (outchain) sctp_m_freem(outchain); return (NULL); } if (outchain) { /* tack on to the end */ if (*endofchain != NULL) { SCTP_BUF_NEXT(((*endofchain))) = appendchain; } else { m = outchain; while (m) { if (SCTP_BUF_NEXT(m) == NULL) { SCTP_BUF_NEXT(m) = appendchain; break; } m = SCTP_BUF_NEXT(m); } } /* * save off the end and update the end-chain postion */ m = appendchain; while (m) { if (SCTP_BUF_NEXT(m) == NULL) { *endofchain = m; break; } m = SCTP_BUF_NEXT(m); } return (outchain); } else { /* save off the end and update the end-chain postion */ m = appendchain; while (m) { if (SCTP_BUF_NEXT(m) == NULL) { *endofchain = m; break; } m = SCTP_BUF_NEXT(m); } return (appendchain); } } int sctp_med_chunk_output(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_association *asoc, int *num_out, int *reason_code, int control_only, int *cwnd_full, int from_where, struct timeval *now, int *now_filled, int frag_point); static void sctp_sendall_iterator(struct sctp_inpcb *inp, struct sctp_tcb *stcb, void *ptr, uint32_t val) { struct sctp_copy_all *ca; struct mbuf *m; int ret = 0; int added_control = 0; int un_sent, do_chunk_output = 1; struct sctp_association *asoc; ca = (struct sctp_copy_all *)ptr; if (ca->m == NULL) { return; } if (ca->inp != inp) { /* TSNH */ return; } if ((ca->m) && ca->sndlen) { m = SCTP_M_COPYM(ca->m, 0, M_COPYALL, M_DONTWAIT); if (m == NULL) { /* can't copy so we are done */ ca->cnt_failed++; return; } } else { m = NULL; } SCTP_TCB_LOCK_ASSERT(stcb); if (ca->sndrcv.sinfo_flags & SCTP_ABORT) { /* Abort this assoc with m as the user defined reason */ if (m) { struct sctp_paramhdr *ph; SCTP_BUF_PREPEND(m, sizeof(struct sctp_paramhdr), M_DONTWAIT); if (m) { ph = mtod(m, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(ca->sndlen); } /* * We add one here to keep the assoc from * dis-appearing on us. */ atomic_add_int(&stcb->asoc.refcnt, 1); sctp_abort_an_association(inp, stcb, SCTP_RESPONSE_TO_USER_REQ, m); /* * sctp_abort_an_association calls sctp_free_asoc() * free association will NOT free it since we * incremented the refcnt .. we do this to prevent * it being freed and things getting tricky since we * could end up (from free_asoc) calling inpcb_free * which would get a recursive lock call to the * iterator lock.. But as a consequence of that the * stcb will return to us un-locked.. since * free_asoc returns with either no TCB or the TCB * unlocked, we must relock.. to unlock in the * iterator timer :-0 */ SCTP_TCB_LOCK(stcb); atomic_add_int(&stcb->asoc.refcnt, -1); goto no_chunk_output; } } else { if (m) { ret = sctp_msg_append(stcb, stcb->asoc.primary_destination, m, &ca->sndrcv, 1); } asoc = &stcb->asoc; if (ca->sndrcv.sinfo_flags & SCTP_EOF) { /* shutdown this assoc */ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->stream_queue_cnt == 0)) { if (asoc->locked_on_sending) { goto abort_anyway; } /* * there is nothing queued to send, so I'm * done... */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* * only send SHUTDOWN the first time * through */ sctp_send_shutdown(stcb, stcb->asoc.primary_destination); if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); added_control = 1; do_chunk_output = 0; } } else { /* * we still got (or just got) data to send, * so set SHUTDOWN_PENDING */ /* * XXX sockets draft says that SCTP_EOF * should be sent with no data. currently, * we will allow user data to be sent first * and move to SHUTDOWN-PENDING */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { if (asoc->locked_on_sending) { /* * Locked to send out the * data */ struct sctp_stream_queue_pending *sp; sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead); if (sp) { if ((sp->length == 0) && (sp->msg_is_complete == 0)) asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; } } asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) { abort_anyway: atomic_add_int(&stcb->asoc.refcnt, 1); sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, NULL); atomic_add_int(&stcb->asoc.refcnt, -1); goto no_chunk_output; } sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } } } un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) && (stcb->asoc.total_flight > 0) && (un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) ) { do_chunk_output = 0; } if (do_chunk_output) sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); else if (added_control) { int num_out = 0, reason = 0, cwnd_full = 0, now_filled = 0; struct timeval now; int frag_point; frag_point = sctp_get_frag_point(stcb, &stcb->asoc); sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out, &reason, 1, &cwnd_full, 1, &now, &now_filled, frag_point); } no_chunk_output: if (ret) { ca->cnt_failed++; } else { ca->cnt_sent++; } } static void sctp_sendall_completes(void *ptr, uint32_t val) { struct sctp_copy_all *ca; ca = (struct sctp_copy_all *)ptr; /* * Do a notify here? Kacheong suggests that the notify be done at * the send time.. so you would push up a notification if any send * failed. Don't know if this is feasable since the only failures we * have is "memory" related and if you cannot get an mbuf to send * the data you surely can't get an mbuf to send up to notify the * user you can't send the data :-> */ /* now free everything */ sctp_m_freem(ca->m); SCTP_FREE(ca); } #define MC_ALIGN(m, len) do { \ SCTP_BUF_RESV_UF(m, ((MCLBYTES - (len)) & ~(sizeof(long) - 1)); \ } while (0) static struct mbuf * sctp_copy_out_all(struct uio *uio, int len) { struct mbuf *ret, *at; int left, willcpy, cancpy, error; ret = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_WAIT, 1, MT_DATA); if (ret == NULL) { /* TSNH */ return (NULL); } left = len; SCTP_BUF_LEN(ret) = 0; /* save space for the data chunk header */ cancpy = M_TRAILINGSPACE(ret); willcpy = min(cancpy, left); at = ret; while (left > 0) { /* Align data to the end */ error = uiomove(mtod(at, caddr_t), willcpy, uio); if (error) { err_out_now: sctp_m_freem(at); return (NULL); } SCTP_BUF_LEN(at) = willcpy; SCTP_BUF_NEXT_PKT(at) = SCTP_BUF_NEXT(at) = 0; left -= willcpy; if (left > 0) { SCTP_BUF_NEXT(at) = sctp_get_mbuf_for_msg(left, 0, M_WAIT, 1, MT_DATA); if (SCTP_BUF_NEXT(at) == NULL) { goto err_out_now; } at = SCTP_BUF_NEXT(at); SCTP_BUF_LEN(at) = 0; cancpy = M_TRAILINGSPACE(at); willcpy = min(cancpy, left); } } return (ret); } static int sctp_sendall(struct sctp_inpcb *inp, struct uio *uio, struct mbuf *m, struct sctp_sndrcvinfo *srcv) { int ret; struct sctp_copy_all *ca; SCTP_MALLOC(ca, struct sctp_copy_all *, sizeof(struct sctp_copy_all), "CopyAll"); if (ca == NULL) { sctp_m_freem(m); return (ENOMEM); } memset(ca, 0, sizeof(struct sctp_copy_all)); ca->inp = inp; ca->sndrcv = *srcv; /* * take off the sendall flag, it would be bad if we failed to do * this :-0 */ ca->sndrcv.sinfo_flags &= ~SCTP_SENDALL; /* get length and mbuf chain */ if (uio) { ca->sndlen = uio->uio_resid; ca->m = sctp_copy_out_all(uio, ca->sndlen); if (ca->m == NULL) { SCTP_FREE(ca); return (ENOMEM); } } else { /* Gather the length of the send */ struct mbuf *mat; mat = m; ca->sndlen = 0; while (m) { ca->sndlen += SCTP_BUF_LEN(m); m = SCTP_BUF_NEXT(m); } ca->m = m; } ret = sctp_initiate_iterator(NULL, sctp_sendall_iterator, NULL, SCTP_PCB_ANY_FLAGS, SCTP_PCB_ANY_FEATURES, SCTP_ASOC_ANY_STATE, (void *)ca, 0, sctp_sendall_completes, inp, 1); if (ret) { #ifdef SCTP_DEBUG printf("Failed to initiate iterator for sendall\n"); #endif SCTP_FREE(ca); return (EFAULT); } return (0); } void sctp_toss_old_cookies(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk, *nchk; chk = TAILQ_FIRST(&asoc->control_send_queue); while (chk) { nchk = TAILQ_NEXT(chk, sctp_next); if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) { TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } asoc->ctrl_queue_cnt--; if (chk->whoTo) sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); } chk = nchk; } } void sctp_toss_old_asconf(struct sctp_tcb *stcb) { struct sctp_association *asoc; struct sctp_tmit_chunk *chk, *chk_tmp; asoc = &stcb->asoc; for (chk = TAILQ_FIRST(&asoc->control_send_queue); chk != NULL; chk = chk_tmp) { /* get next chk */ chk_tmp = TAILQ_NEXT(chk, sctp_next); /* find SCTP_ASCONF chunk in queue (only one ever in queue) */ if (chk->rec.chunk_id.id == SCTP_ASCONF) { TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } asoc->ctrl_queue_cnt--; if (chk->whoTo) sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); } } } static __inline void sctp_clean_up_datalist(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_tmit_chunk **data_list, int bundle_at, struct sctp_nets *net) { int i; struct sctp_tmit_chunk *tp1; for (i = 0; i < bundle_at; i++) { /* off of the send queue */ if (i) { /* * Any chunk NOT 0 you zap the time chunk 0 gets * zapped or set based on if a RTO measurment is * needed. */ data_list[i]->do_rtt = 0; } /* record time */ data_list[i]->sent_rcv_time = net->last_sent_time; data_list[i]->rec.data.fast_retran_tsn = data_list[i]->rec.data.TSN_seq; TAILQ_REMOVE(&asoc->send_queue, data_list[i], sctp_next); /* on to the sent queue */ tp1 = TAILQ_LAST(&asoc->sent_queue, sctpchunk_listhead); if ((tp1) && (compare_with_wrap(tp1->rec.data.TSN_seq, data_list[i]->rec.data.TSN_seq, MAX_TSN))) { struct sctp_tmit_chunk *tpp; /* need to move back */ back_up_more: tpp = TAILQ_PREV(tp1, sctpchunk_listhead, sctp_next); if (tpp == NULL) { TAILQ_INSERT_BEFORE(tp1, data_list[i], sctp_next); goto all_done; } tp1 = tpp; if (compare_with_wrap(tp1->rec.data.TSN_seq, data_list[i]->rec.data.TSN_seq, MAX_TSN)) { goto back_up_more; } TAILQ_INSERT_AFTER(&asoc->sent_queue, tp1, data_list[i], sctp_next); } else { TAILQ_INSERT_TAIL(&asoc->sent_queue, data_list[i], sctp_next); } all_done: /* This does not lower until the cum-ack passes it */ asoc->sent_queue_cnt++; asoc->send_queue_cnt--; if ((asoc->peers_rwnd <= 0) && (asoc->total_flight == 0) && (bundle_at == 1)) { /* Mark the chunk as being a window probe */ SCTP_STAT_INCR(sctps_windowprobed); } #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xC2, 3); #endif data_list[i]->sent = SCTP_DATAGRAM_SENT; data_list[i]->snd_count = 1; data_list[i]->rec.data.chunk_was_revoked = 0; #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_UP, data_list[i]->whoTo->flight_size, data_list[i]->book_size, (uintptr_t) data_list[i]->whoTo, data_list[i]->rec.data.TSN_seq); #endif sctp_flight_size_increase(data_list[i]); sctp_total_flight_increase(stcb, data_list[i]); #ifdef SCTP_LOG_RWND sctp_log_rwnd(SCTP_DECREASE_PEER_RWND, asoc->peers_rwnd, data_list[i]->send_size, sctp_peer_chunk_oh); #endif asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd, (uint32_t) (data_list[i]->send_size + sctp_peer_chunk_oh)); if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } } } static __inline void sctp_clean_up_ctl(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk, *nchk; for (chk = TAILQ_FIRST(&asoc->control_send_queue); chk; chk = nchk) { nchk = TAILQ_NEXT(chk, sctp_next); if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) || (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) || (chk->rec.chunk_id.id == SCTP_HEARTBEAT_ACK) || (chk->rec.chunk_id.id == SCTP_SHUTDOWN) || (chk->rec.chunk_id.id == SCTP_SHUTDOWN_ACK) || (chk->rec.chunk_id.id == SCTP_OPERATION_ERROR) || (chk->rec.chunk_id.id == SCTP_PACKET_DROPPED) || (chk->rec.chunk_id.id == SCTP_COOKIE_ACK) || (chk->rec.chunk_id.id == SCTP_ECN_CWR) || (chk->rec.chunk_id.id == SCTP_ASCONF_ACK)) { /* Stray chunks must be cleaned up */ clean_up_anyway: TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } asoc->ctrl_queue_cnt--; sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); } else if (chk->rec.chunk_id.id == SCTP_STREAM_RESET) { /* special handling, we must look into the param */ if (chk != asoc->str_reset) { goto clean_up_anyway; } } } } static __inline int sctp_can_we_split_this(struct sctp_tcb *stcb, struct sctp_stream_queue_pending *sp, int goal_mtu, int frag_point, int eeor_on) { /* * Make a decision on if I should split a msg into multiple parts. * This is only asked of incomplete messages. */ if (eeor_on) { /* * If we are doing EEOR we need to always send it if its the * entire thing, since it might be all the guy is putting in * the hopper. */ if (goal_mtu >= sp->length) { /*- * If we have data outstanding, * we get another chance when the sack * arrives to transmit - wait for more data */ if (stcb->asoc.total_flight == 0) { /* * If nothing is in flight, we zero the * packet counter. */ return (sp->length); } return (0); } else { /* You can fill the rest */ return (goal_mtu); } } if ((sp->length <= goal_mtu) || ((sp->length - goal_mtu) < sctp_min_residual)) { /* Sub-optimial residual don't split in non-eeor mode. */ return (0); } /* * If we reach here sp->length is larger than the goal_mtu. Do we * wish to split it for the sake of packet putting together? */ if (goal_mtu >= min(sctp_min_split_point, frag_point)) { /* Its ok to split it */ return (min(goal_mtu, frag_point)); } /* Nope, can't split */ return (0); } static int sctp_move_to_outqueue(struct sctp_tcb *stcb, struct sctp_nets *net, struct sctp_stream_out *strq, int goal_mtu, int frag_point, int *locked, int *giveup, int eeor_mode) { /* Move from the stream to the send_queue keeping track of the total */ struct sctp_association *asoc; struct sctp_stream_queue_pending *sp; struct sctp_tmit_chunk *chk; struct sctp_data_chunk *dchkh; int to_move; uint8_t rcv_flags = 0; uint8_t some_taken; uint8_t send_lock_up = 0; SCTP_TCB_LOCK_ASSERT(stcb); asoc = &stcb->asoc; one_more_time: sp = TAILQ_FIRST(&strq->outqueue); if (sp == NULL) { *locked = 0; SCTP_TCB_SEND_LOCK(stcb); sp = TAILQ_FIRST(&strq->outqueue); if (sp) { SCTP_TCB_SEND_UNLOCK(stcb); goto one_more_time; } if (strq->last_msg_incomplete) { printf("Huh? Stream:%d lm_in_c=%d but queue is NULL\n", strq->stream_no, strq->last_msg_incomplete); strq->last_msg_incomplete = 0; } SCTP_TCB_SEND_UNLOCK(stcb); return (0); } if (sp->msg_is_complete) { if (sp->length == 0) { if (sp->sender_all_done) { /* * We are doing differed cleanup. Last time * through when we took all the data the * sender_all_done was not set. */ if (sp->put_last_out == 0) { printf("Gak, put out entire msg with NO end!-1\n"); printf("sender_done:%d len:%d msg_comp:%d put_last_out:%d send_lock:%d\n", sp->sender_all_done, sp->length, sp->msg_is_complete, sp->put_last_out, send_lock_up); } if (TAILQ_NEXT(sp, next) == NULL) { SCTP_TCB_SEND_LOCK(stcb); send_lock_up = 1; } atomic_subtract_int(&asoc->stream_queue_cnt, 1); TAILQ_REMOVE(&strq->outqueue, sp, next); sctp_free_remote_addr(sp->net); if (sp->data) { sctp_m_freem(sp->data); sp->data = NULL; } sctp_free_a_strmoq(stcb, sp); /* we can't be locked to it */ *locked = 0; stcb->asoc.locked_on_sending = NULL; if (send_lock_up) { SCTP_TCB_SEND_UNLOCK(stcb); send_lock_up = 0; } /* back to get the next msg */ goto one_more_time; } else { /* * sender just finished this but still holds * a reference */ *locked = 1; *giveup = 1; return (0); } } } else { /* is there some to get */ if (sp->length == 0) { /* no */ *locked = 1; *giveup = 1; return (0); } } some_taken = sp->some_taken; if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) { sp->msg_is_complete = 1; } re_look: if (sp->msg_is_complete) { /* The message is complete */ to_move = min(sp->length, frag_point); if (to_move == sp->length) { /* All of it fits in the MTU */ if (sp->some_taken) { rcv_flags |= SCTP_DATA_LAST_FRAG; sp->put_last_out = 1; } else { rcv_flags |= SCTP_DATA_NOT_FRAG; sp->put_last_out = 1; } } else { /* Not all of it fits, we fragment */ if (sp->some_taken == 0) { rcv_flags |= SCTP_DATA_FIRST_FRAG; } sp->some_taken = 1; } } else { to_move = sctp_can_we_split_this(stcb, sp, goal_mtu, frag_point, eeor_mode); if (to_move) { /*- * We use a snapshot of length in case it * is expanding during the compare. */ uint32_t llen; llen = sp->length; if (to_move >= llen) { to_move = llen; if (send_lock_up == 0) { /*- * We are taking all of an incomplete msg * thus we need a send lock. */ SCTP_TCB_SEND_LOCK(stcb); send_lock_up = 1; if (sp->msg_is_complete) { /* * the sender finished the * msg */ goto re_look; } } } if (sp->some_taken == 0) { rcv_flags |= SCTP_DATA_FIRST_FRAG; sp->some_taken = 1; } } else { /* Nothing to take. */ if (sp->some_taken) { *locked = 1; } *giveup = 1; return (0); } } /* If we reach here, we can copy out a chunk */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* No chunk memory */ out_gu: if (send_lock_up) { SCTP_TCB_SEND_UNLOCK(stcb); send_lock_up = 0; } *giveup = 1; return (0); } /* * Setup for unordered if needed by looking at the user sent info * flags. */ if (sp->sinfo_flags & SCTP_UNORDERED) { rcv_flags |= SCTP_DATA_UNORDERED; } /* clear out the chunk before setting up */ memset(chk, sizeof(*chk), 0); chk->rec.data.rcv_flags = rcv_flags; if (SCTP_BUF_IS_EXTENDED(sp->data)) { chk->copy_by_ref = 1; } else { chk->copy_by_ref = 0; } if (to_move >= sp->length) { /* we can steal the whole thing */ chk->data = sp->data; chk->last_mbuf = sp->tail_mbuf; /* register the stealing */ sp->data = sp->tail_mbuf = NULL; } else { struct mbuf *m; chk->data = SCTP_M_COPYM(sp->data, 0, to_move, M_DONTWAIT); chk->last_mbuf = NULL; if (chk->data == NULL) { sp->some_taken = some_taken; sctp_free_a_chunk(stcb, chk); goto out_gu; } /* Pull off the data */ m_adj(sp->data, to_move); /* Now lets work our way down and compact it */ m = sp->data; while (m && (SCTP_BUF_LEN(m) == 0)) { sp->data = SCTP_BUF_NEXT(m); SCTP_BUF_NEXT(m) = NULL; if (sp->tail_mbuf == m) { /*- * Freeing tail? TSNH since * we supposedly were taking less * than the sp->length. */ #ifdef INVARIANTS panic("Huh, freing tail? - TSNH"); #else printf("Huh, freeing tail? - TSNH\n"); sp->tail_mbuf = sp->data = NULL; sp->length = 0; #endif } sctp_m_free(m); m = sp->data; } } if (to_move > sp->length) { /*- This should not happen either * since we always lower to_move to the size * of sp->length if its larger. */ #ifdef INVARIANTS panic("Huh, how can to_move be larger?"); #else printf("Huh, how can to_move be larger?\n"); sp->length = 0; #endif } else { atomic_subtract_int(&sp->length, to_move); } if (M_LEADINGSPACE(chk->data) < sizeof(struct sctp_data_chunk)) { /* Not enough room for a chunk header, get some */ struct mbuf *m; m = sctp_get_mbuf_for_msg(1, 0, M_DONTWAIT, 0, MT_DATA); if (m == NULL) { /* * we're in trouble here. _PREPEND below will free * all the data if there is no leading space, so we * must put the data back and restore. */ if (send_lock_up == 0) { SCTP_TCB_SEND_LOCK(stcb); send_lock_up = 1; } if (chk->data == NULL) { /* unsteal the data */ sp->data = chk->data; sp->tail_mbuf = chk->last_mbuf; } else { struct mbuf *m; /* reassemble the data */ m = sp->data; sp->data = chk->data; SCTP_BUF_NEXT(sp->data) = m; } sp->some_taken = some_taken; atomic_add_int(&sp->length, to_move); chk->data = NULL; sctp_free_a_chunk(stcb, chk); goto out_gu; } else { SCTP_BUF_LEN(m) = 0; SCTP_BUF_NEXT(m) = chk->data; chk->data = m; M_ALIGN(chk->data, 4); } } SCTP_BUF_PREPEND(chk->data, sizeof(struct sctp_data_chunk), M_DONTWAIT); if (chk->data == NULL) { /* HELP, TSNH since we assured it would not above? */ #ifdef INVARIANTS panic("prepend failes HELP?"); #else printf("prepend fails HELP?\n"); sctp_free_a_chunk(stcb, chk); #endif goto out_gu; } sctp_snd_sb_alloc(stcb, sizeof(struct sctp_data_chunk)); chk->book_size = chk->send_size = (to_move + sizeof(struct sctp_data_chunk)); chk->book_size_scale = 0; chk->sent = SCTP_DATAGRAM_UNSENT; /* * get last_mbuf and counts of mb useage This is ugly but hopefully * its only one mbuf. */ if (chk->last_mbuf == NULL) { chk->last_mbuf = chk->data; while (SCTP_BUF_NEXT(chk->last_mbuf) != NULL) { chk->last_mbuf = SCTP_BUF_NEXT(chk->last_mbuf); } } chk->flags = 0; chk->asoc = &stcb->asoc; chk->pad_inplace = 0; chk->no_fr_allowed = 0; chk->rec.data.stream_seq = sp->strseq; chk->rec.data.stream_number = sp->stream; chk->rec.data.payloadtype = sp->ppid; chk->rec.data.context = sp->context; chk->rec.data.doing_fast_retransmit = 0; chk->rec.data.ect_nonce = 0; /* ECN Nonce */ chk->rec.data.timetodrop = sp->ts; chk->flags = sp->act_flags; chk->addr_over = sp->addr_over; chk->whoTo = net; atomic_add_int(&chk->whoTo->ref_count, 1); chk->rec.data.TSN_seq = atomic_fetchadd_int(&asoc->sending_seq, 1); #ifdef SCTP_LOG_SENDING_STR sctp_misc_ints(SCTP_STRMOUT_LOG_SEND, (uintptr_t) stcb, (uintptr_t) sp, (uint32_t) ((chk->rec.data.stream_number << 16) | chk->rec.data.stream_seq), chk->rec.data.TSN_seq); #endif dchkh = mtod(chk->data, struct sctp_data_chunk *); /* * Put the rest of the things in place now. Size was done earlier in * previous loop prior to padding. */ #ifdef SCTP_ASOCLOG_OF_TSNS asoc->out_tsnlog[asoc->tsn_out_at].tsn = chk->rec.data.TSN_seq; asoc->out_tsnlog[asoc->tsn_out_at].strm = chk->rec.data.stream_number; asoc->out_tsnlog[asoc->tsn_out_at].seq = chk->rec.data.stream_seq; asoc->out_tsnlog[asoc->tsn_out_at].sz = chk->send_size; asoc->out_tsnlog[asoc->tsn_out_at].flgs = chk->rec.data.rcv_flags; asoc->tsn_out_at++; if (asoc->tsn_out_at >= SCTP_TSN_LOG_SIZE) { asoc->tsn_out_at = 0; asoc->tsn_out_wrapped = 1; } #endif dchkh->ch.chunk_type = SCTP_DATA; dchkh->ch.chunk_flags = chk->rec.data.rcv_flags; dchkh->dp.tsn = htonl(chk->rec.data.TSN_seq); dchkh->dp.stream_id = htons(strq->stream_no); dchkh->dp.stream_sequence = htons(chk->rec.data.stream_seq); dchkh->dp.protocol_id = chk->rec.data.payloadtype; dchkh->ch.chunk_length = htons(chk->send_size); /* Now advance the chk->send_size by the actual pad needed. */ if (chk->send_size < SCTP_SIZE32(chk->book_size)) { /* need a pad */ struct mbuf *lm; int pads; pads = SCTP_SIZE32(chk->book_size) - chk->send_size; if (sctp_pad_lastmbuf(chk->data, pads, chk->last_mbuf) == 0) { chk->pad_inplace = 1; } if ((lm = SCTP_BUF_NEXT(chk->last_mbuf)) != NULL) { /* pad added an mbuf */ chk->last_mbuf = lm; } chk->send_size += pads; } /* We only re-set the policy if it is on */ if (sp->pr_sctp_on) { sctp_set_prsctp_policy(stcb, sp); } if (sp->msg_is_complete && (sp->length == 0) && (sp->sender_all_done)) { /* All done pull and kill the message */ atomic_subtract_int(&asoc->stream_queue_cnt, 1); if (sp->put_last_out == 0) { printf("Gak, put out entire msg with NO end!-2\n"); printf("sender_done:%d len:%d msg_comp:%d put_last_out:%d send_lock:%d\n", sp->sender_all_done, sp->length, sp->msg_is_complete, sp->put_last_out, send_lock_up); } if ((send_lock_up == 0) && (TAILQ_NEXT(sp, next) == NULL)) { SCTP_TCB_SEND_LOCK(stcb); send_lock_up = 1; } TAILQ_REMOVE(&strq->outqueue, sp, next); sctp_free_remote_addr(sp->net); if (sp->data) { sctp_m_freem(sp->data); sp->data = NULL; } sctp_free_a_strmoq(stcb, sp); /* we can't be locked to it */ *locked = 0; stcb->asoc.locked_on_sending = NULL; } else { /* more to go, we are locked */ *locked = 1; } asoc->chunks_on_out_queue++; if (sp->pr_sctp_on) { asoc->pr_sctp_cnt++; chk->pr_sctp_on = 1; } else { chk->pr_sctp_on = 0; } TAILQ_INSERT_TAIL(&asoc->send_queue, chk, sctp_next); asoc->send_queue_cnt++; if (send_lock_up) { SCTP_TCB_SEND_UNLOCK(stcb); send_lock_up = 0; } return (to_move); } static struct sctp_stream_out * sctp_select_a_stream(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_stream_out *strq; /* Find the next stream to use */ if (asoc->last_out_stream == NULL) { strq = asoc->last_out_stream = TAILQ_FIRST(&asoc->out_wheel); if (asoc->last_out_stream == NULL) { /* huh nothing on the wheel, TSNH */ return (NULL); } goto done_it; } strq = TAILQ_NEXT(asoc->last_out_stream, next_spoke); done_it: if (strq == NULL) { strq = asoc->last_out_stream = TAILQ_FIRST(&asoc->out_wheel); } return (strq); } static void sctp_fill_outqueue(struct sctp_tcb *stcb, struct sctp_nets *net, int frag_point, int eeor_mode) { struct sctp_association *asoc; struct sctp_stream_out *strq, *strqn, *strqt; int goal_mtu, moved_how_much, total_moved = 0; int locked, giveup; struct sctp_stream_queue_pending *sp; SCTP_TCB_LOCK_ASSERT(stcb); asoc = &stcb->asoc; #ifdef INET6 if (net->ro._l_addr.sin6.sin6_family == AF_INET6) { goal_mtu = net->mtu - SCTP_MIN_OVERHEAD; } else { /* ?? not sure what else to do */ goal_mtu = net->mtu - SCTP_MIN_V4_OVERHEAD; } #else goal_mtu = net->mtu - SCTP_MIN_OVERHEAD; mtu_fromwheel = 0; #endif /* Need an allowance for the data chunk header too */ goal_mtu -= sizeof(struct sctp_data_chunk); /* must make even word boundary */ goal_mtu &= 0xfffffffc; if (asoc->locked_on_sending) { /* We are stuck on one stream until the message completes. */ strqn = strq = asoc->locked_on_sending; locked = 1; } else { strqn = strq = sctp_select_a_stream(stcb, asoc); locked = 0; } while ((goal_mtu > 0) && strq) { sp = TAILQ_FIRST(&strq->outqueue); /* * If CMT is off, we must validate that the stream in * question has the first item pointed towards are network * destionation requested by the caller. Note that if we * turn out to be locked to a stream (assigning TSN's then * we must stop, since we cannot look for another stream * with data to send to that destination). In CMT's case, by * skipping this check, we will send one data packet towards * the requested net. */ if (sp == NULL) { break; } if ((sp->net != net) && (sctp_cmt_on_off == 0)) { /* none for this network */ if (locked) { break; } else { strq = sctp_select_a_stream(stcb, asoc); if (strq == NULL) /* none left */ break; if (strqn == strq) { /* I have circled */ break; } continue; } } giveup = 0; moved_how_much = sctp_move_to_outqueue(stcb, net, strq, goal_mtu, frag_point, &locked, &giveup, eeor_mode); asoc->last_out_stream = strq; if (locked) { asoc->locked_on_sending = strq; if ((moved_how_much == 0) || (giveup)) /* no more to move for now */ break; } else { asoc->locked_on_sending = NULL; strqt = sctp_select_a_stream(stcb, asoc); if (TAILQ_FIRST(&strq->outqueue) == NULL) { sctp_remove_from_wheel(stcb, asoc, strq); } if (giveup) { break; } strq = strqt; if (strq == NULL) { break; } } total_moved += moved_how_much; goal_mtu -= (moved_how_much + sizeof(struct sctp_data_chunk)); goal_mtu &= 0xfffffffc; } if (total_moved == 0) { if ((sctp_cmt_on_off == 0) && (net == stcb->asoc.primary_destination)) { /* ran dry for primary network net */ SCTP_STAT_INCR(sctps_primary_randry); } else if (sctp_cmt_on_off) { /* ran dry with CMT on */ SCTP_STAT_INCR(sctps_cmt_randry); } } } __inline void sctp_fix_ecn_echo(struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->rec.chunk_id.id == SCTP_ECN_ECHO) { chk->sent = SCTP_DATAGRAM_UNSENT; } } } static void sctp_move_to_an_alt(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_nets *net) { struct sctp_tmit_chunk *chk; struct sctp_nets *a_net; SCTP_TCB_LOCK_ASSERT(stcb); a_net = sctp_find_alternate_net(stcb, net, 0); if ((a_net != net) && ((a_net->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE)) { /* * We only proceed if a valid alternate is found that is not * this one and is reachable. Here we must move all chunks * queued in the send queue off of the destination address * to our alternate. */ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { if (chk->whoTo == net) { /* Move the chunk to our alternate */ sctp_free_remote_addr(chk->whoTo); chk->whoTo = a_net; atomic_add_int(&a_net->ref_count, 1); } } } } int sctp_med_chunk_output(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_association *asoc, int *num_out, int *reason_code, int control_only, int *cwnd_full, int from_where, struct timeval *now, int *now_filled, int frag_point) { /* * Ok this is the generic chunk service queue. we must do the * following: - Service the stream queue that is next, moving any * message (note I must get a complete message i.e. FIRST/MIDDLE and * LAST to the out queue in one pass) and assigning TSN's - Check to * see if the cwnd/rwnd allows any output, if so we go ahead and * fomulate and send the low level chunks. Making sure to combine * any control in the control chunk queue also. */ struct sctp_nets *net; struct mbuf *outchain, *endoutchain; struct sctp_tmit_chunk *chk, *nchk; struct sctphdr *shdr; /* temp arrays for unlinking */ struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING]; int no_fragmentflg, error; int one_chunk, hbflag, skip_data_for_this_net; int asconf, cookie, no_out_cnt; int bundle_at, ctl_cnt, no_data_chunks, cwnd_full_ind, eeor_mode; unsigned int mtu, r_mtu, omtu, mx_mtu, to_out; struct sctp_nets *start_at, *old_startat = NULL, *send_start_at; int tsns_sent = 0; uint32_t auth_offset = 0; struct sctp_auth_chunk *auth = NULL; *num_out = 0; cwnd_full_ind = 0; if ((asoc->state & SCTP_STATE_SHUTDOWN_PENDING) || (asoc->state & SCTP_STATE_SHUTDOWN_RECEIVED) || (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR))) { eeor_mode = 1; } else { eeor_mode = 0; } ctl_cnt = no_out_cnt = asconf = cookie = 0; /* * First lets prime the pump. For each destination, if there is room * in the flight size, attempt to pull an MTU's worth out of the * stream queues into the general send_queue */ #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xC2, 2); #endif SCTP_TCB_LOCK_ASSERT(stcb); hbflag = 0; if ((control_only) || (asoc->stream_reset_outstanding)) no_data_chunks = 1; else no_data_chunks = 0; /* Nothing to possible to send? */ if (TAILQ_EMPTY(&asoc->control_send_queue) && TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->out_wheel)) { *reason_code = 9; return (0); } if (asoc->peers_rwnd == 0) { /* No room in peers rwnd */ *cwnd_full = 1; *reason_code = 1; if (asoc->total_flight > 0) { /* we are allowed one chunk in flight */ no_data_chunks = 1; } } if ((no_data_chunks == 0) && (!TAILQ_EMPTY(&asoc->out_wheel))) { if (sctp_cmt_on_off) { /* * for CMT we start at the next one past the one we * last added data to. */ if (TAILQ_FIRST(&asoc->send_queue) != NULL) { goto skip_the_fill_from_streams; } if (asoc->last_net_data_came_from) { net = TAILQ_NEXT(asoc->last_net_data_came_from, sctp_next); if (net == NULL) { net = TAILQ_FIRST(&asoc->nets); } } else { /* back to start */ net = TAILQ_FIRST(&asoc->nets); } } else { net = asoc->primary_destination; if (net == NULL) { /* TSNH */ net = TAILQ_FIRST(&asoc->nets); } } start_at = net; one_more_time: for (; net != NULL; net = TAILQ_NEXT(net, sctp_next)) { net->window_probe = 0; if (old_startat && (old_startat == net)) { break; } if ((sctp_cmt_on_off == 0) && (net->ref_count < 2)) { /* nothing can be in queue for this guy */ continue; } if (net->flight_size >= net->cwnd) { /* skip this network, no room */ cwnd_full_ind++; continue; } /* * @@@ JRI : this for loop we are in takes in each * net, if its's got space in cwnd and has data sent * to it (when CMT is off) then it calls * sctp_fill_outqueue for the net. This gets data on * the send queue for that network. * * In sctp_fill_outqueue TSN's are assigned and data is * copied out of the stream buffers. Note mostly * copy by reference (we hope). */ #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, 0, SCTP_CWND_LOG_FILL_OUTQ_CALLED); #endif sctp_fill_outqueue(stcb, net, frag_point, eeor_mode); } if (start_at != TAILQ_FIRST(&asoc->nets)) { /* got to pick up the beginning stuff. */ old_startat = start_at; start_at = net = TAILQ_FIRST(&asoc->nets); goto one_more_time; } } skip_the_fill_from_streams: *cwnd_full = cwnd_full_ind; /* now service each destination and send out what we can for it */ /* Nothing to send? */ if ((TAILQ_FIRST(&asoc->control_send_queue) == NULL) && (TAILQ_FIRST(&asoc->send_queue) == NULL)) { *reason_code = 8; return (0); } chk = TAILQ_FIRST(&asoc->send_queue); if (chk) { send_start_at = chk->whoTo; } else { send_start_at = TAILQ_FIRST(&asoc->nets); } old_startat = NULL; again_one_more_time: for (net = send_start_at; net != NULL; net = TAILQ_NEXT(net, sctp_next)) { /* how much can we send? */ /* printf("Examine for sending net:%x\n", (uint32_t)net); */ if (old_startat && (old_startat == net)) { /* through list ocmpletely. */ break; } tsns_sent = 0; if (net->ref_count < 2) { /* * Ref-count of 1 so we cannot have data or control * queued to this address. Skip it. */ continue; } ctl_cnt = bundle_at = 0; endoutchain = outchain = NULL; no_fragmentflg = 1; one_chunk = 0; if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { skip_data_for_this_net = 1; } else { skip_data_for_this_net = 0; } if ((net->ro.ro_rt) && (net->ro.ro_rt->rt_ifp)) { /* * if we have a route and an ifp check to see if we * have room to send to this guy */ struct ifnet *ifp; ifp = net->ro.ro_rt->rt_ifp; if ((ifp->if_snd.ifq_len + 2) >= ifp->if_snd.ifq_maxlen) { SCTP_STAT_INCR(sctps_ifnomemqueued); #ifdef SCTP_LOG_MAXBURST sctp_log_maxburst(stcb, net, ifp->if_snd.ifq_len, ifp->if_snd.ifq_maxlen, SCTP_MAX_IFP_APPLIED); #endif continue; } } if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { mtu = net->mtu - (sizeof(struct ip) + sizeof(struct sctphdr)); } else { mtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr)); } mx_mtu = mtu; to_out = 0; if (mtu > asoc->peers_rwnd) { if (asoc->total_flight > 0) { /* We have a packet in flight somewhere */ r_mtu = asoc->peers_rwnd; } else { /* We are always allowed to send one MTU out */ one_chunk = 1; r_mtu = mtu; } } else { r_mtu = mtu; } /************************/ /* Control transmission */ /************************/ /* Now first lets go through the control queue */ for (chk = TAILQ_FIRST(&asoc->control_send_queue); chk; chk = nchk) { nchk = TAILQ_NEXT(chk, sctp_next); if (chk->whoTo != net) { /* * No, not sent to the network we are * looking at */ continue; } if (chk->data == NULL) { continue; } if (chk->sent != SCTP_DATAGRAM_UNSENT) { /* * It must be unsent. Cookies and ASCONF's * hang around but there timers will force * when marked for resend. */ continue; } /* * if no AUTH is yet included and this chunk * requires it, make sure to account for it. We * don't apply the size until the AUTH chunk is * actually added below in case there is no room for * this chunk. NOTE: we overload the use of "omtu" * here */ if ((auth == NULL) && sctp_auth_is_required_chunk(chk->rec.chunk_id.id, stcb->asoc.peer_auth_chunks)) { omtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id); } else omtu = 0; /* Here we do NOT factor the r_mtu */ if ((chk->send_size < (int)(mtu - omtu)) || (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) { /* * We probably should glom the mbuf chain * from the chk->data for control but the * problem is it becomes yet one more level * of tracking to do if for some reason * output fails. Then I have got to * reconstruct the merged control chain.. el * yucko.. for now we take the easy way and * do the copy */ /* * Add an AUTH chunk, if chunk requires it * save the offset into the chain for AUTH */ if ((auth == NULL) && (sctp_auth_is_required_chunk(chk->rec.chunk_id.id, stcb->asoc.peer_auth_chunks))) { outchain = sctp_add_auth_chunk(outchain, &endoutchain, &auth, &auth_offset, stcb, chk->rec.chunk_id.id); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); } outchain = sctp_copy_mbufchain(chk->data, outchain, &endoutchain, (int)chk->rec.chunk_id.can_take_data, chk->send_size, chk->copy_by_ref); if (outchain == NULL) { *reason_code = 8; return (ENOMEM); } SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); /* update our MTU size */ if (mtu > (chk->send_size + omtu)) mtu -= (chk->send_size + omtu); else mtu = 0; to_out += (chk->send_size + omtu); /* Do clear IP_DF ? */ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { no_fragmentflg = 0; } if (chk->rec.chunk_id.can_take_data) chk->data = NULL; /* Mark things to be removed, if needed */ if ((chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) || (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) || (chk->rec.chunk_id.id == SCTP_HEARTBEAT_ACK) || (chk->rec.chunk_id.id == SCTP_SHUTDOWN) || (chk->rec.chunk_id.id == SCTP_SHUTDOWN_ACK) || (chk->rec.chunk_id.id == SCTP_OPERATION_ERROR) || (chk->rec.chunk_id.id == SCTP_COOKIE_ACK) || (chk->rec.chunk_id.id == SCTP_ECN_CWR) || (chk->rec.chunk_id.id == SCTP_PACKET_DROPPED) || (chk->rec.chunk_id.id == SCTP_ASCONF_ACK)) { if (chk->rec.chunk_id.id == SCTP_HEARTBEAT_REQUEST) hbflag = 1; /* remove these chunks at the end */ if (chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) { /* turn off the timer */ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_RECV, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_1); } } ctl_cnt++; } else { /* * Other chunks, since they have * timers running (i.e. COOKIE or * ASCONF) we just "trust" that it * gets sent or retransmitted. */ ctl_cnt++; if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) { cookie = 1; no_out_cnt = 1; } else if (chk->rec.chunk_id.id == SCTP_ASCONF) { /* * set hb flag since we can * use these for RTO */ hbflag = 1; asconf = 1; } chk->sent = SCTP_DATAGRAM_SENT; chk->snd_count++; } if (mtu == 0) { /* * Ok we are out of room but we can * output without effecting the * flight size since this little guy * is a control only packet. */ if (asconf) { sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net); asconf = 0; } if (cookie) { sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); cookie = 0; } SCTP_BUF_PREPEND(outchain, sizeof(struct sctphdr), M_DONTWAIT); if (outchain == NULL) { /* no memory */ error = ENOBUFS; goto error_out_again; } shdr = mtod(outchain, struct sctphdr *); shdr->src_port = inp->sctp_lport; shdr->dest_port = stcb->rport; shdr->v_tag = htonl(stcb->asoc.peer_vtag); shdr->checksum = 0; auth_offset += sizeof(struct sctphdr); if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, (struct sockaddr *)&net->ro._l_addr, outchain, auth_offset, auth, no_fragmentflg, 0, NULL, asconf))) { if (error == ENOBUFS) { asoc->ifp_had_enobuf = 1; SCTP_STAT_INCR(sctps_lowlevelerr); } if (from_where == 0) { SCTP_STAT_INCR(sctps_lowlevelerrusr); } error_out_again: /* error, could not output */ if (hbflag) { if (*now_filled == 0) { - SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); *now_filled = 1; *now = net->last_sent_time; } else { net->last_sent_time = *now; } hbflag = 0; } if (error == EHOSTUNREACH) { /* * Destination went * unreachable * during this send */ sctp_move_to_an_alt(stcb, asoc, net); } *reason_code = 7; continue; } else asoc->ifp_had_enobuf = 0; /* Only HB or ASCONF advances time */ if (hbflag) { if (*now_filled == 0) { - SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); *now_filled = 1; *now = net->last_sent_time; } else { net->last_sent_time = *now; } hbflag = 0; } /* * increase the number we sent, if a * cookie is sent we don't tell them * any was sent out. */ outchain = endoutchain = NULL; auth = NULL; auth_offset = 0; if (!no_out_cnt) *num_out += ctl_cnt; /* recalc a clean slate and setup */ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { mtu = (net->mtu - SCTP_MIN_OVERHEAD); } else { mtu = (net->mtu - SCTP_MIN_V4_OVERHEAD); } to_out = 0; no_fragmentflg = 1; } } } /*********************/ /* Data transmission */ /*********************/ /* * if AUTH for DATA is required and no AUTH has been added * yet, account for this in the mtu now... if no data can be * bundled, this adjustment won't matter anyways since the * packet will be going out... */ if ((auth == NULL) && sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) { mtu -= sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id); } /* now lets add any data within the MTU constraints */ if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { if (net->mtu > (sizeof(struct ip) + sizeof(struct sctphdr))) omtu = net->mtu - (sizeof(struct ip) + sizeof(struct sctphdr)); else omtu = 0; } else { if (net->mtu > (sizeof(struct ip6_hdr) + sizeof(struct sctphdr))) omtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr)); else omtu = 0; } if ((((asoc->state & SCTP_STATE_OPEN) == SCTP_STATE_OPEN) && (skip_data_for_this_net == 0)) || (cookie)) { for (chk = TAILQ_FIRST(&asoc->send_queue); chk; chk = nchk) { if (no_data_chunks) { /* let only control go out */ *reason_code = 1; break; } if (net->flight_size >= net->cwnd) { /* skip this net, no room for data */ *reason_code = 2; break; } nchk = TAILQ_NEXT(chk, sctp_next); if (chk->whoTo != net) { /* No, not sent to this net */ continue; } if ((chk->send_size > omtu) && ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) == 0)) { /*- * strange, we have a chunk that is * to big for its destination and * yet no fragment ok flag. * Something went wrong when the * PMTU changed...we did not mark * this chunk for some reason?? I * will fix it here by letting IP * fragment it for now and printing * a warning. This really should not * happen ... */ #ifdef SCTP_DEBUG printf("Warning chunk of %d bytes > mtu:%d and yet PMTU disc missed\n", chk->send_size, mtu); #endif chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; } if (((chk->send_size <= mtu) && (chk->send_size <= r_mtu)) || ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) && (chk->send_size <= asoc->peers_rwnd))) { /* ok we will add this one */ /* * Add an AUTH chunk, if chunk * requires it, save the offset into * the chain for AUTH */ if ((auth == NULL) && (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks))) { outchain = sctp_add_auth_chunk(outchain, &endoutchain, &auth, &auth_offset, stcb, SCTP_DATA); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); } outchain = sctp_copy_mbufchain(chk->data, outchain, &endoutchain, 0, chk->send_size, chk->copy_by_ref); if (outchain == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("No memory?\n"); } #endif if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); } *reason_code = 3; return (ENOMEM); } /* upate our MTU size */ /* Do clear IP_DF ? */ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { no_fragmentflg = 0; } /* unsigned subtraction of mtu */ if (mtu > chk->send_size) mtu -= chk->send_size; else mtu = 0; /* unsigned subtraction of r_mtu */ if (r_mtu > chk->send_size) r_mtu -= chk->send_size; else r_mtu = 0; to_out += chk->send_size; if (to_out > mx_mtu) { #ifdef INVARIANTS panic("Exceeding mtu of %d out size is %d", mx_mtu, to_out); #else printf("Exceeding mtu of %d out size is %d\n", mx_mtu, to_out); #endif } chk->window_probe = 0; data_list[bundle_at++] = chk; if (bundle_at >= SCTP_MAX_DATA_BUNDLING) { mtu = 0; break; } if (chk->sent == SCTP_DATAGRAM_UNSENT) { if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) { SCTP_STAT_INCR_COUNTER64(sctps_outorderchunks); } else { SCTP_STAT_INCR_COUNTER64(sctps_outunorderchunks); } if (((chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) == SCTP_DATA_LAST_FRAG) && ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0)) /* * Count number of * user msg's that * were fragmented * we do this by * counting when we * see a LAST * fragment only. */ SCTP_STAT_INCR_COUNTER64(sctps_fragusrmsgs); } if ((mtu == 0) || (r_mtu == 0) || (one_chunk)) { if (one_chunk) { data_list[0]->window_probe = 1; net->window_probe = 1; } break; } } else { /* * Must be sent in order of the * TSN's (on a network) */ break; } } /* for (chunk gather loop for this net) */ } /* if asoc.state OPEN */ /* Is there something to send for this destination? */ if (outchain) { /* We may need to start a control timer or two */ if (asconf) { sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net); asconf = 0; } if (cookie) { sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); cookie = 0; } /* must start a send timer if data is being sent */ if (bundle_at && (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer))) { /* * no timer running on this destination * restart it. */ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); } /* Now send it, if there is anything to send :> */ SCTP_BUF_PREPEND(outchain, sizeof(struct sctphdr), M_DONTWAIT); if (outchain == NULL) { /* out of mbufs */ error = ENOBUFS; goto errored_send; } shdr = mtod(outchain, struct sctphdr *); shdr->src_port = inp->sctp_lport; shdr->dest_port = stcb->rport; shdr->v_tag = htonl(stcb->asoc.peer_vtag); shdr->checksum = 0; auth_offset += sizeof(struct sctphdr); if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, (struct sockaddr *)&net->ro._l_addr, outchain, auth_offset, auth, no_fragmentflg, bundle_at, data_list[0], asconf))) { /* error, we could not output */ if (error == ENOBUFS) { SCTP_STAT_INCR(sctps_lowlevelerr); asoc->ifp_had_enobuf = 1; } if (from_where == 0) { SCTP_STAT_INCR(sctps_lowlevelerrusr); } errored_send: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("Gak send error %d\n", error); } #endif if (hbflag) { if (*now_filled == 0) { - SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); *now_filled = 1; *now = net->last_sent_time; } else { net->last_sent_time = *now; } hbflag = 0; } if (error == EHOSTUNREACH) { /* * Destination went unreachable * during this send */ sctp_move_to_an_alt(stcb, asoc, net); } *reason_code = 6; /*- * I add this line to be paranoid. As far as * I can tell the continue, takes us back to * the top of the for, but just to make sure * I will reset these again here. */ ctl_cnt = bundle_at = 0; continue; /* This takes us back to the * for() for the nets. */ } else { asoc->ifp_had_enobuf = 0; } outchain = endoutchain = NULL; auth = NULL; auth_offset = 0; if (bundle_at || hbflag) { /* For data/asconf and hb set time */ if (*now_filled == 0) { - SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); *now_filled = 1; *now = net->last_sent_time; } else { net->last_sent_time = *now; } } if (!no_out_cnt) { *num_out += (ctl_cnt + bundle_at); } if (bundle_at) { /* setup for a RTO measurement */ tsns_sent = data_list[0]->rec.data.TSN_seq; data_list[0]->do_rtt = 1; SCTP_STAT_INCR_BY(sctps_senddata, bundle_at); sctp_clean_up_datalist(stcb, asoc, data_list, bundle_at, net); if (sctp_early_fr) { if (net->flight_size < net->cwnd) { /* start or restart it */ if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_2); } SCTP_STAT_INCR(sctps_earlyfrstrout); sctp_timer_start(SCTP_TIMER_TYPE_EARLYFR, inp, stcb, net); } else { /* stop it if its running */ if (SCTP_OS_TIMER_PENDING(&net->fr_timer.timer)) { SCTP_STAT_INCR(sctps_earlyfrstpout); sctp_timer_stop(SCTP_TIMER_TYPE_EARLYFR, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_3); } } } } if (one_chunk) { break; } } #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, tsns_sent, SCTP_CWND_LOG_FROM_SEND); #endif } if (old_startat == NULL) { old_startat = send_start_at; send_start_at = TAILQ_FIRST(&asoc->nets); goto again_one_more_time; } /* * At the end there should be no NON timed chunks hanging on this * queue. */ #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, *num_out, SCTP_CWND_LOG_FROM_SEND); #endif if ((*num_out == 0) && (*reason_code == 0)) { *reason_code = 4; } else { *reason_code = 5; } sctp_clean_up_ctl(stcb, asoc); return (0); } void sctp_queue_op_err(struct sctp_tcb *stcb, struct mbuf *op_err) { /*- * Prepend a OPERATIONAL_ERROR chunk header and put on the end of * the control chunk queue. */ struct sctp_chunkhdr *hdr; struct sctp_tmit_chunk *chk; struct mbuf *mat; SCTP_TCB_LOCK_ASSERT(stcb); sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(op_err); return; } chk->copy_by_ref = 0; SCTP_BUF_PREPEND(op_err, sizeof(struct sctp_chunkhdr), M_DONTWAIT); if (op_err == NULL) { sctp_free_a_chunk(stcb, chk); return; } chk->send_size = 0; mat = op_err; while (mat != NULL) { chk->send_size += SCTP_BUF_LEN(mat); mat = SCTP_BUF_NEXT(mat); } chk->rec.chunk_id.id = SCTP_OPERATION_ERROR; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = op_err; chk->whoTo = chk->asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); hdr = mtod(op_err, struct sctp_chunkhdr *); hdr->chunk_type = SCTP_OPERATION_ERROR; hdr->chunk_flags = 0; hdr->chunk_length = htons(chk->send_size); TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; } int sctp_send_cookie_echo(struct mbuf *m, int offset, struct sctp_tcb *stcb, struct sctp_nets *net) { /*- * pull out the cookie and put it at the front of the control chunk * queue. */ int at; struct mbuf *cookie; struct sctp_paramhdr parm, *phdr; struct sctp_chunkhdr *hdr; struct sctp_tmit_chunk *chk; uint16_t ptype, plen; /* First find the cookie in the param area */ cookie = NULL; at = offset + sizeof(struct sctp_init_chunk); SCTP_TCB_LOCK_ASSERT(stcb); do { phdr = sctp_get_next_param(m, at, &parm, sizeof(parm)); if (phdr == NULL) { return (-3); } ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if (ptype == SCTP_STATE_COOKIE) { int pad; /* found the cookie */ if ((pad = (plen % 4))) { plen += 4 - pad; } cookie = SCTP_M_COPYM(m, at, plen, M_DONTWAIT); if (cookie == NULL) { /* No memory */ return (-2); } break; } at += SCTP_SIZE32(plen); } while (phdr); if (cookie == NULL) { /* Did not find the cookie */ return (-3); } /* ok, we got the cookie lets change it into a cookie echo chunk */ /* first the change from param to cookie */ hdr = mtod(cookie, struct sctp_chunkhdr *); hdr->chunk_type = SCTP_COOKIE_ECHO; hdr->chunk_flags = 0; /* get the chunk stuff now and place it in the FRONT of the queue */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(cookie); return (-5); } chk->copy_by_ref = 0; chk->send_size = plen; chk->rec.chunk_id.id = SCTP_COOKIE_ECHO; chk->rec.chunk_id.can_take_data = 0; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = cookie; chk->whoTo = chk->asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); TAILQ_INSERT_HEAD(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; return (0); } void sctp_send_heartbeat_ack(struct sctp_tcb *stcb, struct mbuf *m, int offset, int chk_length, struct sctp_nets *net) { /* * take a HB request and make it into a HB ack and send it. */ struct mbuf *outchain; struct sctp_chunkhdr *chdr; struct sctp_tmit_chunk *chk; if (net == NULL) /* must have a net pointer */ return; outchain = SCTP_M_COPYM(m, offset, chk_length, M_DONTWAIT); if (outchain == NULL) { /* gak out of memory */ return; } chdr = mtod(outchain, struct sctp_chunkhdr *); chdr->chunk_type = SCTP_HEARTBEAT_ACK; chdr->chunk_flags = 0; if (chk_length % 4) { /* need pad */ uint32_t cpthis = 0; int padlen; padlen = 4 - (chk_length % 4); m_copyback(outchain, chk_length, padlen, (caddr_t)&cpthis); } sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(outchain); return; } chk->copy_by_ref = 0; chk->send_size = chk_length; chk->rec.chunk_id.id = SCTP_HEARTBEAT_ACK; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = outchain; chk->whoTo = net; atomic_add_int(&chk->whoTo->ref_count, 1); TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; } -int +void sctp_send_cookie_ack(struct sctp_tcb *stcb) { /* formulate and queue a cookie-ack back to sender */ struct mbuf *cookie_ack; struct sctp_chunkhdr *hdr; struct sctp_tmit_chunk *chk; cookie_ack = NULL; SCTP_TCB_LOCK_ASSERT(stcb); cookie_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_chunkhdr), 0, M_DONTWAIT, 1, MT_HEADER); if (cookie_ack == NULL) { /* no mbuf's */ - return (-1); + return; } SCTP_BUF_RESV_UF(cookie_ack, SCTP_MIN_OVERHEAD); sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(cookie_ack); - return (-1); + return; } chk->copy_by_ref = 0; chk->send_size = sizeof(struct sctp_chunkhdr); chk->rec.chunk_id.id = SCTP_COOKIE_ACK; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = cookie_ack; if (chk->asoc->last_control_chunk_from != NULL) { chk->whoTo = chk->asoc->last_control_chunk_from; } else { chk->whoTo = chk->asoc->primary_destination; } atomic_add_int(&chk->whoTo->ref_count, 1); hdr = mtod(cookie_ack, struct sctp_chunkhdr *); hdr->chunk_type = SCTP_COOKIE_ACK; hdr->chunk_flags = 0; hdr->chunk_length = htons(chk->send_size); SCTP_BUF_LEN(cookie_ack) = chk->send_size; TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; - return (0); + return; } -int +void sctp_send_shutdown_ack(struct sctp_tcb *stcb, struct sctp_nets *net) { /* formulate and queue a SHUTDOWN-ACK back to the sender */ struct mbuf *m_shutdown_ack; struct sctp_shutdown_ack_chunk *ack_cp; struct sctp_tmit_chunk *chk; m_shutdown_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_ack_chunk), 0, M_DONTWAIT, 1, MT_HEADER); if (m_shutdown_ack == NULL) { /* no mbuf's */ - return (-1); + return; } SCTP_BUF_RESV_UF(m_shutdown_ack, SCTP_MIN_OVERHEAD); sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(m_shutdown_ack); - return (-1); + return; } chk->copy_by_ref = 0; chk->send_size = sizeof(struct sctp_chunkhdr); chk->rec.chunk_id.id = SCTP_SHUTDOWN_ACK; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = m_shutdown_ack; chk->whoTo = net; atomic_add_int(&net->ref_count, 1); ack_cp = mtod(m_shutdown_ack, struct sctp_shutdown_ack_chunk *); ack_cp->ch.chunk_type = SCTP_SHUTDOWN_ACK; ack_cp->ch.chunk_flags = 0; ack_cp->ch.chunk_length = htons(chk->send_size); SCTP_BUF_LEN(m_shutdown_ack) = chk->send_size; TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; - return (0); + return; } -int +void sctp_send_shutdown(struct sctp_tcb *stcb, struct sctp_nets *net) { /* formulate and queue a SHUTDOWN to the sender */ struct mbuf *m_shutdown; struct sctp_shutdown_chunk *shutdown_cp; struct sctp_tmit_chunk *chk; m_shutdown = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_chunk), 0, M_DONTWAIT, 1, MT_HEADER); if (m_shutdown == NULL) { /* no mbuf's */ - return (-1); + return; } SCTP_BUF_RESV_UF(m_shutdown, SCTP_MIN_OVERHEAD); sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(m_shutdown); - return (-1); + return; } chk->copy_by_ref = 0; chk->send_size = sizeof(struct sctp_shutdown_chunk); chk->rec.chunk_id.id = SCTP_SHUTDOWN; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->data = m_shutdown; chk->whoTo = net; atomic_add_int(&net->ref_count, 1); shutdown_cp = mtod(m_shutdown, struct sctp_shutdown_chunk *); shutdown_cp->ch.chunk_type = SCTP_SHUTDOWN; shutdown_cp->ch.chunk_flags = 0; shutdown_cp->ch.chunk_length = htons(chk->send_size); shutdown_cp->cumulative_tsn_ack = htonl(stcb->asoc.cumulative_tsn); SCTP_BUF_LEN(m_shutdown) = chk->send_size; TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; - return (0); + return; } -int +void sctp_send_asconf(struct sctp_tcb *stcb, struct sctp_nets *net) { /* * formulate and queue an ASCONF to the peer ASCONF parameters * should be queued on the assoc queue */ struct sctp_tmit_chunk *chk; struct mbuf *m_asconf; struct sctp_asconf_chunk *acp; int len; SCTP_TCB_LOCK_ASSERT(stcb); /* compose an ASCONF chunk, maximum length is PMTU */ m_asconf = sctp_compose_asconf(stcb, &len); if (m_asconf == NULL) { - return (-1); + return; } acp = mtod(m_asconf, struct sctp_asconf_chunk *); sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ sctp_m_freem(m_asconf); - return (-1); + return; } chk->copy_by_ref = 0; chk->data = m_asconf; chk->send_size = len; chk->rec.chunk_id.id = SCTP_ASCONF; chk->rec.chunk_id.can_take_data = 0; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; chk->whoTo = chk->asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; - return (0); + return; } -int +void sctp_send_asconf_ack(struct sctp_tcb *stcb, uint32_t retrans) { /* * formulate and queue a asconf-ack back to sender the asconf-ack * must be stored in the tcb */ struct sctp_tmit_chunk *chk; struct mbuf *m_ack, *m; SCTP_TCB_LOCK_ASSERT(stcb); /* is there a asconf-ack mbuf chain to send? */ if (stcb->asoc.last_asconf_ack_sent == NULL) { - return (-1); + return; } /* copy the asconf_ack */ m_ack = SCTP_M_COPYM(stcb->asoc.last_asconf_ack_sent, 0, M_COPYALL, M_DONTWAIT); if (m_ack == NULL) { /* couldn't copy it */ - return (-1); + return; } sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { /* no memory */ if (m_ack) sctp_m_freem(m_ack); - return (-1); + return; } chk->copy_by_ref = 0; /* figure out where it goes to */ if (retrans) { /* we're doing a retransmission */ if (stcb->asoc.used_alt_asconfack > 2) { /* tried alternate nets already, go back */ chk->whoTo = NULL; } else { /* need to try and alternate net */ chk->whoTo = sctp_find_alternate_net(stcb, stcb->asoc.last_control_chunk_from, 0); stcb->asoc.used_alt_asconfack++; } if (chk->whoTo == NULL) { /* no alternate */ if (stcb->asoc.last_control_chunk_from == NULL) chk->whoTo = stcb->asoc.primary_destination; else chk->whoTo = stcb->asoc.last_control_chunk_from; stcb->asoc.used_alt_asconfack = 0; } } else { /* normal case */ if (stcb->asoc.last_control_chunk_from == NULL) chk->whoTo = stcb->asoc.primary_destination; else chk->whoTo = stcb->asoc.last_control_chunk_from; stcb->asoc.used_alt_asconfack = 0; } chk->data = m_ack; chk->send_size = 0; /* Get size */ m = m_ack; while (m) { chk->send_size += SCTP_BUF_LEN(m); m = SCTP_BUF_NEXT(m); } chk->rec.chunk_id.id = SCTP_ASCONF_ACK; chk->rec.chunk_id.can_take_data = 1; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->flags = 0; chk->asoc = &stcb->asoc; atomic_add_int(&chk->whoTo->ref_count, 1); TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); chk->asoc->ctrl_queue_cnt++; - return (0); + return; } static int sctp_chunk_retransmission(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_association *asoc, int *cnt_out, struct timeval *now, int *now_filled, int *fr_done) { /*- * send out one MTU of retransmission. If fast_retransmit is * happening we ignore the cwnd. Otherwise we obey the cwnd and * rwnd. For a Cookie or Asconf in the control chunk queue we * retransmit them by themselves. * * For data chunks we will pick out the lowest TSN's in the sent_queue * marked for resend and bundle them all together (up to a MTU of * destination). The address to send to should have been * selected/changed where the retransmission was marked (i.e. in FR * or t3-timeout routines). */ struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING]; struct sctp_tmit_chunk *chk, *fwd; struct mbuf *m, *endofchain; struct sctphdr *shdr; int asconf; struct sctp_nets *net; uint32_t tsns_sent = 0; int no_fragmentflg, bundle_at, cnt_thru; unsigned int mtu; int error, i, one_chunk, fwd_tsn, ctl_cnt, tmr_started; struct sctp_auth_chunk *auth = NULL; uint32_t auth_offset = 0; uint32_t dmtu = 0; SCTP_TCB_LOCK_ASSERT(stcb); tmr_started = ctl_cnt = bundle_at = error = 0; no_fragmentflg = 1; asconf = 0; fwd_tsn = 0; *cnt_out = 0; fwd = NULL; endofchain = m = NULL; #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xC3, 1); #endif if ((TAILQ_EMPTY(&asoc->sent_queue)) && (TAILQ_EMPTY(&asoc->control_send_queue))) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("SCTP hits empty queue with cnt set to %d?\n", asoc->sent_queue_retran_cnt); } #endif asoc->sent_queue_cnt = 0; asoc->sent_queue_cnt_removeable = 0; /* send back 0/0 so we enter normal transmission */ *cnt_out = 0; return (0); } TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if ((chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) || (chk->rec.chunk_id.id == SCTP_ASCONF) || (chk->rec.chunk_id.id == SCTP_STREAM_RESET) || (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN)) { if (chk->rec.chunk_id.id == SCTP_STREAM_RESET) { if (chk != asoc->str_reset) { /* * not eligible for retran if its * not ours */ continue; } } ctl_cnt++; if (chk->rec.chunk_id.id == SCTP_ASCONF) { no_fragmentflg = 1; asconf = 1; } if (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) { fwd_tsn = 1; fwd = chk; } /* * Add an AUTH chunk, if chunk requires it save the * offset into the chain for AUTH */ if ((auth == NULL) && (sctp_auth_is_required_chunk(chk->rec.chunk_id.id, stcb->asoc.peer_auth_chunks))) { m = sctp_add_auth_chunk(m, &endofchain, &auth, &auth_offset, stcb, chk->rec.chunk_id.id); } m = sctp_copy_mbufchain(chk->data, m, &endofchain, 0, chk->send_size, chk->copy_by_ref); break; } } one_chunk = 0; cnt_thru = 0; /* do we have control chunks to retransmit? */ if (m != NULL) { /* Start a timer no matter if we suceed or fail */ if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) { sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, chk->whoTo); } else if (chk->rec.chunk_id.id == SCTP_ASCONF) sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, chk->whoTo); SCTP_BUF_PREPEND(m, sizeof(struct sctphdr), M_DONTWAIT); if (m == NULL) { return (ENOBUFS); } shdr = mtod(m, struct sctphdr *); shdr->src_port = inp->sctp_lport; shdr->dest_port = stcb->rport; shdr->v_tag = htonl(stcb->asoc.peer_vtag); shdr->checksum = 0; auth_offset += sizeof(struct sctphdr); chk->snd_count++; /* update our count */ if ((error = sctp_lowlevel_chunk_output(inp, stcb, chk->whoTo, (struct sockaddr *)&chk->whoTo->ro._l_addr, m, auth_offset, auth, no_fragmentflg, 0, NULL, asconf))) { SCTP_STAT_INCR(sctps_lowlevelerr); return (error); } m = endofchain = NULL; auth = NULL; auth_offset = 0; /* * We don't want to mark the net->sent time here since this * we use this for HB and retrans cannot measure RTT */ - /* SCTP_GETTIME_TIMEVAL(&chk->whoTo->last_sent_time); */ + /* (void)SCTP_GETTIME_TIMEVAL(&chk->whoTo->last_sent_time); */ *cnt_out += 1; chk->sent = SCTP_DATAGRAM_SENT; sctp_ucount_decr(asoc->sent_queue_retran_cnt); if (fwd_tsn == 0) { return (0); } else { /* Clean up the fwd-tsn list */ sctp_clean_up_ctl(stcb, asoc); return (0); } } /* * Ok, it is just data retransmission we need to do or that and a * fwd-tsn with it all. */ if (TAILQ_EMPTY(&asoc->sent_queue)) { return (SCTP_RETRAN_DONE); } if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) || (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT)) { /* not yet open, resend the cookie and that is it */ return (1); } #ifdef SCTP_AUDITING_ENABLED sctp_auditing(20, inp, stcb, NULL); #endif TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { if (chk->sent != SCTP_DATAGRAM_RESEND) { /* No, not sent to this net or not ready for rtx */ continue; } if ((sctp_max_retran_chunk) && (chk->snd_count >= sctp_max_retran_chunk)) { /* Gak, we have exceeded max unlucky retran, abort! */ #ifdef SCTP_DEBUG printf("Gak, chk->snd_count:%d >= max:%d - send abort\n", chk->snd_count, sctp_max_retran_chunk); #endif sctp_send_abort_tcb(stcb, NULL); sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL); return (SCTP_RETRAN_EXIT); } /* pick up the net */ net = chk->whoTo; if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { mtu = (net->mtu - SCTP_MIN_OVERHEAD); } else { mtu = net->mtu - SCTP_MIN_V4_OVERHEAD; } if ((asoc->peers_rwnd < mtu) && (asoc->total_flight > 0)) { /* No room in peers rwnd */ uint32_t tsn; tsn = asoc->last_acked_seq + 1; if (tsn == chk->rec.data.TSN_seq) { /* * we make a special exception for this * case. The peer has no rwnd but is missing * the lowest chunk.. which is probably what * is holding up the rwnd. */ goto one_chunk_around; } return (1); } one_chunk_around: if (asoc->peers_rwnd < mtu) { one_chunk = 1; if ((asoc->peers_rwnd == 0) && (asoc->total_flight == 0)) { chk->window_probe = 1; chk->whoTo->window_probe = 1; } } #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xC3, 2); #endif bundle_at = 0; m = NULL; net->fast_retran_ip = 0; if (chk->rec.data.doing_fast_retransmit == 0) { /* * if no FR in progress skip destination that have * flight_size > cwnd. */ if (net->flight_size >= net->cwnd) { continue; } } else { /* * Mark the destination net to have FR recovery * limits put on it. */ *fr_done = 1; net->fast_retran_ip = 1; } /* * if no AUTH is yet included and this chunk requires it, * make sure to account for it. We don't apply the size * until the AUTH chunk is actually added below in case * there is no room for this chunk. */ if ((auth == NULL) && sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) { dmtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id); } else dmtu = 0; if ((chk->send_size <= (mtu - dmtu)) || (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) { /* ok we will add this one */ if ((auth == NULL) && (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks))) { m = sctp_add_auth_chunk(m, &endofchain, &auth, &auth_offset, stcb, SCTP_DATA); } m = sctp_copy_mbufchain(chk->data, m, &endofchain, 0, chk->send_size, chk->copy_by_ref); if (m == NULL) { return (ENOMEM); } /* Do clear IP_DF ? */ if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { no_fragmentflg = 0; } /* upate our MTU size */ if (mtu > (chk->send_size + dmtu)) mtu -= (chk->send_size + dmtu); else mtu = 0; data_list[bundle_at++] = chk; if (one_chunk && (asoc->total_flight <= 0)) { SCTP_STAT_INCR(sctps_windowprobed); } } if (one_chunk == 0) { /* * now are there anymore forward from chk to pick * up? */ fwd = TAILQ_NEXT(chk, sctp_next); while (fwd) { if (fwd->sent != SCTP_DATAGRAM_RESEND) { /* Nope, not for retran */ fwd = TAILQ_NEXT(fwd, sctp_next); continue; } if (fwd->whoTo != net) { /* Nope, not the net in question */ fwd = TAILQ_NEXT(fwd, sctp_next); continue; } if ((auth == NULL) && sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) { dmtu = sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id); } else dmtu = 0; if (fwd->send_size <= (mtu - dmtu)) { if ((auth == NULL) && (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks))) { m = sctp_add_auth_chunk(m, &endofchain, &auth, &auth_offset, stcb, SCTP_DATA); } m = sctp_copy_mbufchain(fwd->data, m, &endofchain, 0, fwd->send_size, fwd->copy_by_ref); if (m == NULL) { return (ENOMEM); } /* Do clear IP_DF ? */ if (fwd->flags & CHUNK_FLAGS_FRAGMENT_OK) { no_fragmentflg = 0; } /* upate our MTU size */ if (mtu > (fwd->send_size + dmtu)) mtu -= (fwd->send_size + dmtu); else mtu = 0; data_list[bundle_at++] = fwd; if (bundle_at >= SCTP_MAX_DATA_BUNDLING) { break; } fwd = TAILQ_NEXT(fwd, sctp_next); } else { /* can't fit so we are done */ break; } } } /* Is there something to send for this destination? */ if (m) { /* * No matter if we fail/or suceed we should start a * timer. A failure is like a lost IP packet :-) */ if (!SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { /* * no timer running on this destination * restart it. */ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); tmr_started = 1; } SCTP_BUF_PREPEND(m, sizeof(struct sctphdr), M_DONTWAIT); if (m == NULL) { return (ENOBUFS); } shdr = mtod(m, struct sctphdr *); shdr->src_port = inp->sctp_lport; shdr->dest_port = stcb->rport; shdr->v_tag = htonl(stcb->asoc.peer_vtag); shdr->checksum = 0; auth_offset += sizeof(struct sctphdr); /* Now lets send it, if there is anything to send :> */ if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, (struct sockaddr *)&net->ro._l_addr, m, auth_offset, auth, no_fragmentflg, 0, NULL, asconf))) { /* error, we could not output */ SCTP_STAT_INCR(sctps_lowlevelerr); return (error); } m = endofchain = NULL; auth = NULL; auth_offset = 0; /* For HB's */ /* * We don't want to mark the net->sent time here * since this we use this for HB and retrans cannot * measure RTT */ - /* SCTP_GETTIME_TIMEVAL(&net->last_sent_time); */ + /* (void)SCTP_GETTIME_TIMEVAL(&net->last_sent_time); */ /* For auto-close */ cnt_thru++; if (*now_filled == 0) { - SCTP_GETTIME_TIMEVAL(&asoc->time_last_sent); + (void)SCTP_GETTIME_TIMEVAL(&asoc->time_last_sent); *now = asoc->time_last_sent; *now_filled = 1; } else { asoc->time_last_sent = *now; } *cnt_out += bundle_at; #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xC4, bundle_at); #endif if (bundle_at) { tsns_sent = data_list[0]->rec.data.TSN_seq; } for (i = 0; i < bundle_at; i++) { SCTP_STAT_INCR(sctps_sendretransdata); data_list[i]->sent = SCTP_DATAGRAM_SENT; /* * When we have a revoked data, and we * retransmit it, then we clear the revoked * flag since this flag dictates if we * subtracted from the fs */ if (data_list[i]->rec.data.chunk_was_revoked) { /* Deflate the cwnd */ data_list[i]->whoTo->cwnd -= data_list[i]->book_size; data_list[i]->rec.data.chunk_was_revoked = 0; } data_list[i]->snd_count++; sctp_ucount_decr(asoc->sent_queue_retran_cnt); /* record the time */ data_list[i]->sent_rcv_time = asoc->time_last_sent; if (data_list[i]->book_size_scale) { /* * need to double the book size on * this one */ data_list[i]->book_size_scale = 0; /* * Since we double the booksize, we * must also double the output queue * size, since this get shrunk when * we free by this amount. */ atomic_add_int(&((asoc)->total_output_queue_size), data_list[i]->book_size); data_list[i]->book_size *= 2; } else { #ifdef SCTP_LOG_RWND sctp_log_rwnd(SCTP_DECREASE_PEER_RWND, asoc->peers_rwnd, data_list[i]->send_size, sctp_peer_chunk_oh); #endif asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd, (uint32_t) (data_list[i]->send_size + sctp_peer_chunk_oh)); } #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_UP_RSND, data_list[i]->whoTo->flight_size, data_list[i]->book_size, (uintptr_t) data_list[i]->whoTo, data_list[i]->rec.data.TSN_seq); #endif sctp_flight_size_increase(data_list[i]); sctp_total_flight_increase(stcb, data_list[i]); if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { /* SWS sender side engages */ asoc->peers_rwnd = 0; } if ((i == 0) && (data_list[i]->rec.data.doing_fast_retransmit)) { SCTP_STAT_INCR(sctps_sendfastretrans); if ((data_list[i] == TAILQ_FIRST(&asoc->sent_queue)) && (tmr_started == 0)) { /*- * ok we just fast-retrans'd * the lowest TSN, i.e the * first on the list. In * this case we want to give * some more time to get a * SACK back without a * t3-expiring. */ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, inp, stcb, net, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_4); sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); } } } #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, net, tsns_sent, SCTP_CWND_LOG_FROM_RESEND); #endif #ifdef SCTP_AUDITING_ENABLED sctp_auditing(21, inp, stcb, NULL); #endif } else { /* None will fit */ return (1); } if (asoc->sent_queue_retran_cnt <= 0) { /* all done we have no more to retran */ asoc->sent_queue_retran_cnt = 0; break; } if (one_chunk) { /* No more room in rwnd */ return (1); } /* stop the for loop here. we sent out a packet */ break; } return (0); } static int sctp_timer_validation(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_association *asoc, int ret) { struct sctp_nets *net; /* Validate that a timer is running somewhere */ TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if (SCTP_OS_TIMER_PENDING(&net->rxt_timer.timer)) { /* Here is a timer */ return (ret); } } SCTP_TCB_LOCK_ASSERT(stcb); /* Gak, we did not have a timer somewhere */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("Deadlock avoided starting timer on a dest at retran\n"); } #endif sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, asoc->primary_destination); return (ret); } -int +void sctp_chunk_output(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int from_where) { /*- * Ok this is the generic chunk service queue. we must do the * following: * - See if there are retransmits pending, if so we must * do these first. * - Service the stream queue that is next, moving any * message (note I must get a complete message i.e. * FIRST/MIDDLE and LAST to the out queue in one pass) and assigning * TSN's * - Check to see if the cwnd/rwnd allows any output, if so we * go ahead and fomulate and send the low level chunks. Making sure * to combine any control in the control chunk queue also. */ struct sctp_association *asoc; struct sctp_nets *net; int error = 0, num_out = 0, tot_out = 0, ret = 0, reason_code = 0, burst_cnt = 0, burst_limit = 0; struct timeval now; int now_filled = 0; int cwnd_full = 0; int nagle_on = 0; int frag_point = sctp_get_frag_point(stcb, &stcb->asoc); int un_sent = 0; int fr_done, tot_frs = 0; asoc = &stcb->asoc; if (from_where == SCTP_OUTPUT_FROM_USR_SEND) { if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NODELAY)) { nagle_on = 0; } else { nagle_on = 1; } } SCTP_TCB_LOCK_ASSERT(stcb); un_sent = (stcb->asoc.total_output_queue_size - stcb->asoc.total_flight); if ((un_sent <= 0) && (TAILQ_EMPTY(&asoc->control_send_queue)) && (asoc->sent_queue_retran_cnt == 0)) { /* Nothing to do unless there is something to be sent left */ - return (error); + return; } /* * Do we have something to send, data or control AND a sack timer * running, if so piggy-back the sack. */ if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { sctp_send_sack(stcb); SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer); } while (asoc->sent_queue_retran_cnt) { /*- * Ok, it is retransmission time only, we send out only ONE * packet with a single call off to the retran code. */ if (from_where == SCTP_OUTPUT_FROM_COOKIE_ACK) { /*- * Special hook for handling cookiess discarded * by peer that carried data. Send cookie-ack only * and then the next call with get the retran's. */ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, &cwnd_full, from_where, &now, &now_filled, frag_point); - return (0); + return; } else if (from_where != SCTP_OUTPUT_FROM_HB_TMR) { /* if its not from a HB then do it */ fr_done = 0; ret = sctp_chunk_retransmission(inp, stcb, asoc, &num_out, &now, &now_filled, &fr_done); if (fr_done) { tot_frs++; } } else { /* * its from any other place, we don't allow retran * output (only control) */ ret = 1; } if (ret > 0) { /* Can't send anymore */ /*- * now lets push out control by calling med-level * output once. this assures that we WILL send HB's * if queued too. */ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, &cwnd_full, from_where, &now, &now_filled, frag_point); #ifdef SCTP_AUDITING_ENABLED sctp_auditing(8, inp, stcb, NULL); #endif - return (sctp_timer_validation(inp, stcb, asoc, ret)); + sctp_timer_validation(inp, stcb, asoc, ret); + return; } if (ret < 0) { /*- * The count was off.. retran is not happening so do * the normal retransmission. */ #ifdef SCTP_AUDITING_ENABLED sctp_auditing(9, inp, stcb, NULL); #endif if (ret == SCTP_RETRAN_EXIT) { - return (-1); + return; } break; } if (from_where == SCTP_OUTPUT_FROM_T3) { /* Only one transmission allowed out of a timeout */ #ifdef SCTP_AUDITING_ENABLED sctp_auditing(10, inp, stcb, NULL); #endif /* Push out any control */ (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, &cwnd_full, from_where, &now, &now_filled, frag_point); - return (ret); + return; } if (tot_frs > asoc->max_burst) { /* Hit FR burst limit */ - return (0); + return; } if ((num_out == 0) && (ret == 0)) { /* No more retrans to send */ break; } } #ifdef SCTP_AUDITING_ENABLED sctp_auditing(12, inp, stcb, NULL); #endif /* Check for bad destinations, if they exist move chunks around. */ burst_limit = asoc->max_burst; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { if ((net->dest_state & SCTP_ADDR_NOT_REACHABLE) == SCTP_ADDR_NOT_REACHABLE) { /*- * if possible move things off of this address we * still may send below due to the dormant state but * we try to find an alternate address to send to * and if we have one we move all queued data on the * out wheel to this alternate address. */ if (net->ref_count > 1) sctp_move_to_an_alt(stcb, asoc, net); } else { /*- * if ((asoc->sat_network) || (net->addr_is_local)) * { burst_limit = asoc->max_burst * * SCTP_SAT_NETWORK_BURST_INCR; } */ if (sctp_use_cwnd_based_maxburst) { if ((net->flight_size + (burst_limit * net->mtu)) < net->cwnd) { int old_cwnd; if (net->ssthresh < net->cwnd) net->ssthresh = net->cwnd; old_cwnd = net->cwnd; net->cwnd = (net->flight_size + (burst_limit * net->mtu)); #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, (net->cwnd - old_cwnd), SCTP_CWND_LOG_FROM_BRST); #endif #ifdef SCTP_LOG_MAXBURST sctp_log_maxburst(stcb, net, 0, burst_limit, SCTP_MAX_BURST_APPLIED); #endif SCTP_STAT_INCR(sctps_maxburstqueued); } net->fast_retran_ip = 0; } else { if (net->flight_size == 0) { /* Should be decaying the cwnd here */ ; } } } } burst_cnt = 0; cwnd_full = 0; do { error = sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 0, &cwnd_full, from_where, &now, &now_filled, frag_point); if (error) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Error %d was returned from med-c-op\n", error); } #endif #ifdef SCTP_LOG_MAXBURST sctp_log_maxburst(stcb, asoc->primary_destination, error, burst_cnt, SCTP_MAX_BURST_ERROR_STOP); #endif #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, NULL, error, SCTP_SEND_NOW_COMPLETES); sctp_log_cwnd(stcb, NULL, 0xdeadbeef, SCTP_SEND_NOW_COMPLETES); #endif break; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { printf("m-c-o put out %d\n", num_out); } #endif tot_out += num_out; burst_cnt++; #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, NULL, num_out, SCTP_SEND_NOW_COMPLETES); if (num_out == 0) { sctp_log_cwnd(stcb, NULL, reason_code, SCTP_SEND_NOW_COMPLETES); } #endif if (nagle_on) { /*- * When nagle is on, we look at how much is un_sent, then * if its smaller than an MTU and we have data in * flight we stop. */ un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); if ((un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) && (stcb->asoc.total_flight > 0)) { break; } } if (TAILQ_EMPTY(&asoc->control_send_queue) && TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->out_wheel)) { /* Nothing left to send */ break; } if ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) <= 0) { /* Nothing left to send */ break; } } while (num_out && (sctp_use_cwnd_based_maxburst || (burst_cnt < burst_limit))); if (sctp_use_cwnd_based_maxburst == 0) { if (burst_cnt >= burst_limit) { SCTP_STAT_INCR(sctps_maxburstqueued); asoc->burst_limit_applied = 1; #ifdef SCTP_LOG_MAXBURST sctp_log_maxburst(stcb, asoc->primary_destination, 0, burst_cnt, SCTP_MAX_BURST_APPLIED); #endif } else { asoc->burst_limit_applied = 0; } } #ifdef SCTP_CWND_LOGGING sctp_log_cwnd(stcb, NULL, tot_out, SCTP_SEND_NOW_COMPLETES); #endif #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Ok, we have put out %d chunks\n", tot_out); } #endif /*- * Now we need to clean up the control chunk chain if a ECNE is on * it. It must be marked as UNSENT again so next call will continue * to send it until such time that we get a CWR, to remove it. */ if (stcb->asoc.ecn_echo_cnt_onq) sctp_fix_ecn_echo(asoc); - return (error); + return; } int sctp_output(inp, m, addr, control, p, flags) struct sctp_inpcb *inp; struct mbuf *m; struct sockaddr *addr; struct mbuf *control; struct thread *p; int flags; { if (inp == NULL) { return (EINVAL); } if (inp->sctp_socket == NULL) { return (EINVAL); } return (sctp_sosend(inp->sctp_socket, addr, (struct uio *)NULL, m, control, flags, p)); } void send_forward_tsn(struct sctp_tcb *stcb, struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; struct sctp_forward_tsn_chunk *fwdtsn; SCTP_TCB_LOCK_ASSERT(stcb); TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->rec.chunk_id.id == SCTP_FORWARD_CUM_TSN) { /* mark it to unsent */ chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; /* Do we correct its output location? */ if (chk->whoTo != asoc->primary_destination) { sctp_free_remote_addr(chk->whoTo); chk->whoTo = asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); } goto sctp_fill_in_rest; } } /* Ok if we reach here we must build one */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return; } chk->copy_by_ref = 0; chk->rec.chunk_id.id = SCTP_FORWARD_CUM_TSN; chk->rec.chunk_id.can_take_data = 0; chk->asoc = asoc; chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (chk->data == NULL) { atomic_subtract_int(&chk->whoTo->ref_count, 1); sctp_free_a_chunk(stcb, chk); return; } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); TAILQ_INSERT_TAIL(&asoc->control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; sctp_fill_in_rest: /*- * Here we go through and fill out the part that deals with * stream/seq of the ones we skip. */ SCTP_BUF_LEN(chk->data) = 0; { struct sctp_tmit_chunk *at, *tp1, *last; struct sctp_strseq *strseq; unsigned int cnt_of_space, i, ovh; unsigned int space_needed; unsigned int cnt_of_skipped = 0; TAILQ_FOREACH(at, &asoc->sent_queue, sctp_next) { if (at->sent != SCTP_FORWARD_TSN_SKIP) { /* no more to look at */ break; } if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { /* We don't report these */ continue; } cnt_of_skipped++; } space_needed = (sizeof(struct sctp_forward_tsn_chunk) + (cnt_of_skipped * sizeof(struct sctp_strseq))); cnt_of_space = M_TRAILINGSPACE(chk->data); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ovh = SCTP_MIN_OVERHEAD; } else { ovh = SCTP_MIN_V4_OVERHEAD; } if (cnt_of_space > (asoc->smallest_mtu - ovh)) { /* trim to a mtu size */ cnt_of_space = asoc->smallest_mtu - ovh; } if (cnt_of_space < space_needed) { /*- * ok we must trim down the chunk by lowering the * advance peer ack point. */ cnt_of_skipped = (cnt_of_space - ((sizeof(struct sctp_forward_tsn_chunk)) / sizeof(struct sctp_strseq))); /*- * Go through and find the TSN that will be the one * we report. */ at = TAILQ_FIRST(&asoc->sent_queue); for (i = 0; i < cnt_of_skipped; i++) { tp1 = TAILQ_NEXT(at, sctp_next); at = tp1; } last = at; /*- * last now points to last one I can report, update * peer ack point */ asoc->advanced_peer_ack_point = last->rec.data.TSN_seq; space_needed -= (cnt_of_skipped * sizeof(struct sctp_strseq)); } chk->send_size = space_needed; /* Setup the chunk */ fwdtsn = mtod(chk->data, struct sctp_forward_tsn_chunk *); fwdtsn->ch.chunk_length = htons(chk->send_size); fwdtsn->ch.chunk_flags = 0; fwdtsn->ch.chunk_type = SCTP_FORWARD_CUM_TSN; fwdtsn->new_cumulative_tsn = htonl(asoc->advanced_peer_ack_point); chk->send_size = (sizeof(struct sctp_forward_tsn_chunk) + (cnt_of_skipped * sizeof(struct sctp_strseq))); SCTP_BUF_LEN(chk->data) = chk->send_size; fwdtsn++; /*- * Move pointer to after the fwdtsn and transfer to the * strseq pointer. */ strseq = (struct sctp_strseq *)fwdtsn; /*- * Now populate the strseq list. This is done blindly * without pulling out duplicate stream info. This is * inefficent but won't harm the process since the peer will * look at these in sequence and will thus release anything. * It could mean we exceed the PMTU and chop off some that * we could have included.. but this is unlikely (aka 1432/4 * would mean 300+ stream seq's would have to be reported in * one FWD-TSN. With a bit of work we can later FIX this to * optimize and pull out duplcates.. but it does add more * overhead. So for now... not! */ at = TAILQ_FIRST(&asoc->sent_queue); for (i = 0; i < cnt_of_skipped; i++) { tp1 = TAILQ_NEXT(at, sctp_next); if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { /* We don't report these */ i--; at = tp1; continue; } strseq->stream = ntohs(at->rec.data.stream_number); strseq->sequence = ntohs(at->rec.data.stream_seq); strseq++; at = tp1; } } return; } void sctp_send_sack(struct sctp_tcb *stcb) { /*- * Queue up a SACK in the control queue. We must first check to see * if a SACK is somehow on the control queue. If so, we will take * and and remove the old one. */ struct sctp_association *asoc; struct sctp_tmit_chunk *chk, *a_chk; struct sctp_sack_chunk *sack; struct sctp_gap_ack_block *gap_descriptor; struct sack_track *selector; int mergeable = 0; int offset; caddr_t limit; uint32_t *dup; int limit_reached = 0; unsigned int i, jstart, siz, j; unsigned int num_gap_blocks = 0, space; int num_dups = 0; int space_req; a_chk = NULL; asoc = &stcb->asoc; SCTP_TCB_LOCK_ASSERT(stcb); if (asoc->last_data_chunk_from == NULL) { /* Hmm we never received anything */ return; } sctp_set_rwnd(stcb, asoc); TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->rec.chunk_id.id == SCTP_SELECTIVE_ACK) { /* Hmm, found a sack already on queue, remove it */ TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; a_chk = chk; if (a_chk->data) { sctp_m_freem(a_chk->data); a_chk->data = NULL; } sctp_free_remote_addr(a_chk->whoTo); a_chk->whoTo = NULL; break; } } if (a_chk == NULL) { sctp_alloc_a_chunk(stcb, a_chk); if (a_chk == NULL) { /* No memory so we drop the idea, and set a timer */ if (stcb->asoc.delayed_ack) { sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_5); sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL); } else { stcb->asoc.send_sack = 1; } return; } a_chk->copy_by_ref = 0; /* a_chk->rec.chunk_id.id = SCTP_SELECTIVE_ACK; */ a_chk->rec.chunk_id.id = SCTP_SELECTIVE_ACK; a_chk->rec.chunk_id.can_take_data = 1; } /* Clear our pkt counts */ asoc->data_pkts_seen = 0; a_chk->asoc = asoc; a_chk->snd_count = 0; a_chk->send_size = 0; /* fill in later */ a_chk->sent = SCTP_DATAGRAM_UNSENT; if ((asoc->numduptsns) || (asoc->last_data_chunk_from->dest_state & SCTP_ADDR_NOT_REACHABLE) ) { /*- * Ok, we have some duplicates or the destination for the * sack is unreachable, lets see if we can select an * alternate than asoc->last_data_chunk_from */ if ((!(asoc->last_data_chunk_from->dest_state & SCTP_ADDR_NOT_REACHABLE)) && (asoc->used_alt_onsack > asoc->numnets)) { /* We used an alt last time, don't this time */ a_chk->whoTo = NULL; } else { asoc->used_alt_onsack++; a_chk->whoTo = sctp_find_alternate_net(stcb, asoc->last_data_chunk_from, 0); } if (a_chk->whoTo == NULL) { /* Nope, no alternate */ a_chk->whoTo = asoc->last_data_chunk_from; asoc->used_alt_onsack = 0; } } else { /* * No duplicates so we use the last place we received data * from. */ asoc->used_alt_onsack = 0; a_chk->whoTo = asoc->last_data_chunk_from; } if (a_chk->whoTo) { atomic_add_int(&a_chk->whoTo->ref_count, 1); } if (asoc->highest_tsn_inside_map == asoc->cumulative_tsn) { /* no gaps */ space_req = sizeof(struct sctp_sack_chunk); } else { /* gaps get a cluster */ space_req = MCLBYTES; } /* Ok now lets formulate a MBUF with our sack */ a_chk->data = sctp_get_mbuf_for_msg(space_req, 0, M_DONTWAIT, 1, MT_DATA); if ((a_chk->data == NULL) || (a_chk->whoTo == NULL)) { /* rats, no mbuf memory */ if (a_chk->data) { /* was a problem with the destination */ sctp_m_freem(a_chk->data); a_chk->data = NULL; } if (a_chk->whoTo) atomic_subtract_int(&a_chk->whoTo->ref_count, 1); sctp_free_a_chunk(stcb, a_chk); if (stcb->asoc.delayed_ack) { sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL, SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_6); sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL); } else { stcb->asoc.send_sack = 1; } return; } /* ok, lets go through and fill it in */ SCTP_BUF_RESV_UF(a_chk->data, SCTP_MIN_OVERHEAD); space = M_TRAILINGSPACE(a_chk->data); if (space > (a_chk->whoTo->mtu - SCTP_MIN_OVERHEAD)) { space = (a_chk->whoTo->mtu - SCTP_MIN_OVERHEAD); } limit = mtod(a_chk->data, caddr_t); limit += space; sack = mtod(a_chk->data, struct sctp_sack_chunk *); sack->ch.chunk_type = SCTP_SELECTIVE_ACK; /* 0x01 is used by nonce for ecn */ if ((sctp_ecn_enable) && (sctp_ecn_nonce) && (asoc->peer_supports_ecn_nonce)) sack->ch.chunk_flags = (asoc->receiver_nonce_sum & SCTP_SACK_NONCE_SUM); else sack->ch.chunk_flags = 0; if (sctp_cmt_on_off && sctp_cmt_use_dac) { /*- * CMT DAC algorithm: If 2 (i.e., 0x10) packets have been * received, then set high bit to 1, else 0. Reset * pkts_rcvd. */ sack->ch.chunk_flags |= (asoc->cmt_dac_pkts_rcvd << 6); asoc->cmt_dac_pkts_rcvd = 0; } sack->sack.cum_tsn_ack = htonl(asoc->cumulative_tsn); sack->sack.a_rwnd = htonl(asoc->my_rwnd); asoc->my_last_reported_rwnd = asoc->my_rwnd; /* reset the readers interpretation */ stcb->freed_by_sorcv_sincelast = 0; gap_descriptor = (struct sctp_gap_ack_block *)((caddr_t)sack + sizeof(struct sctp_sack_chunk)); siz = (((asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn) + 1) + 7) / 8; if (compare_with_wrap(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, MAX_TSN)) { offset = 1; /*- * cum-ack behind the mapping array, so we start and use all * entries. */ jstart = 0; } else { offset = asoc->mapping_array_base_tsn - asoc->cumulative_tsn; /*- * we skip the first one when the cum-ack is at or above the * mapping array base. Note this only works if */ jstart = 1; } if (compare_with_wrap(asoc->highest_tsn_inside_map, asoc->cumulative_tsn, MAX_TSN)) { /* we have a gap .. maybe */ for (i = 0; i < siz; i++) { selector = &sack_array[asoc->mapping_array[i]]; if (mergeable && selector->right_edge) { /* * Backup, left and right edges were ok to * merge. */ num_gap_blocks--; gap_descriptor--; } if (selector->num_entries == 0) mergeable = 0; else { for (j = jstart; j < selector->num_entries; j++) { if (mergeable && selector->right_edge) { /* * do a merge by NOT setting * the left side */ mergeable = 0; } else { /* * no merge, set the left * side */ mergeable = 0; gap_descriptor->start = htons((selector->gaps[j].start + offset)); } gap_descriptor->end = htons((selector->gaps[j].end + offset)); num_gap_blocks++; gap_descriptor++; if (((caddr_t)gap_descriptor + sizeof(struct sctp_gap_ack_block)) > limit) { /* no more room */ limit_reached = 1; break; } } if (selector->left_edge) { mergeable = 1; } } if (limit_reached) { /* Reached the limit stop */ break; } jstart = 0; offset += 8; } if (num_gap_blocks == 0) { /* reneged all chunks */ asoc->highest_tsn_inside_map = asoc->cumulative_tsn; } } /* now we must add any dups we are going to report. */ if ((limit_reached == 0) && (asoc->numduptsns)) { dup = (uint32_t *) gap_descriptor; for (i = 0; i < asoc->numduptsns; i++) { *dup = htonl(asoc->dup_tsns[i]); dup++; num_dups++; if (((caddr_t)dup + sizeof(uint32_t)) > limit) { /* no more room */ break; } } asoc->numduptsns = 0; } /* * now that the chunk is prepared queue it to the control chunk * queue. */ a_chk->send_size = (sizeof(struct sctp_sack_chunk) + (num_gap_blocks * sizeof(struct sctp_gap_ack_block)) + (num_dups * sizeof(int32_t))); SCTP_BUF_LEN(a_chk->data) = a_chk->send_size; sack->sack.num_gap_ack_blks = htons(num_gap_blocks); sack->sack.num_dup_tsns = htons(num_dups); sack->ch.chunk_length = htons(a_chk->send_size); TAILQ_INSERT_TAIL(&asoc->control_send_queue, a_chk, sctp_next); asoc->ctrl_queue_cnt++; asoc->send_sack = 0; SCTP_STAT_INCR(sctps_sendsacks); return; } void sctp_send_abort_tcb(struct sctp_tcb *stcb, struct mbuf *operr) { struct mbuf *m_abort; struct mbuf *m_out = NULL, *m_end = NULL; struct sctp_abort_chunk *abort = NULL; int sz; uint32_t auth_offset = 0; struct sctp_auth_chunk *auth = NULL; struct sctphdr *shdr; /*- * Add an AUTH chunk, if chunk requires it and save the offset into * the chain for AUTH */ if (sctp_auth_is_required_chunk(SCTP_ABORT_ASSOCIATION, stcb->asoc.peer_auth_chunks)) { m_out = sctp_add_auth_chunk(m_out, &m_end, &auth, &auth_offset, stcb, SCTP_ABORT_ASSOCIATION); } SCTP_TCB_LOCK_ASSERT(stcb); m_abort = sctp_get_mbuf_for_msg(sizeof(struct sctp_abort_chunk), 0, M_DONTWAIT, 1, MT_HEADER); if (m_abort == NULL) { /* no mbuf's */ if (m_out) sctp_m_freem(m_out); return; } /* link in any error */ SCTP_BUF_NEXT(m_abort) = operr; sz = 0; if (operr) { struct mbuf *n; n = operr; while (n) { sz += SCTP_BUF_LEN(n); n = SCTP_BUF_NEXT(n); } } SCTP_BUF_LEN(m_abort) = sizeof(*abort); if (m_out == NULL) { /* NO Auth chunk prepended, so reserve space in front */ SCTP_BUF_RESV_UF(m_abort, SCTP_MIN_OVERHEAD); m_out = m_abort; } else { /* Put AUTH chunk at the front of the chain */ SCTP_BUF_NEXT(m_end) = m_abort; } /* fill in the ABORT chunk */ abort = mtod(m_abort, struct sctp_abort_chunk *); abort->ch.chunk_type = SCTP_ABORT_ASSOCIATION; abort->ch.chunk_flags = 0; abort->ch.chunk_length = htons(sizeof(*abort) + sz); /* prepend and fill in the SCTP header */ SCTP_BUF_PREPEND(m_out, sizeof(struct sctphdr), M_DONTWAIT); if (m_out == NULL) { /* TSNH: no memory */ return; } shdr = mtod(m_out, struct sctphdr *); shdr->src_port = stcb->sctp_ep->sctp_lport; shdr->dest_port = stcb->rport; shdr->v_tag = htonl(stcb->asoc.peer_vtag); shdr->checksum = 0; auth_offset += sizeof(struct sctphdr); sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, stcb->asoc.primary_destination, (struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr, m_out, auth_offset, auth, 1, 0, NULL, 0); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); } -int +void sctp_send_shutdown_complete(struct sctp_tcb *stcb, struct sctp_nets *net) { /* formulate and SEND a SHUTDOWN-COMPLETE */ struct mbuf *m_shutdown_comp; struct sctp_shutdown_complete_msg *comp_cp; m_shutdown_comp = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_complete_msg), 0, M_DONTWAIT, 1, MT_HEADER); if (m_shutdown_comp == NULL) { /* no mbuf's */ - return (-1); + return; } comp_cp = mtod(m_shutdown_comp, struct sctp_shutdown_complete_msg *); comp_cp->shut_cmp.ch.chunk_type = SCTP_SHUTDOWN_COMPLETE; comp_cp->shut_cmp.ch.chunk_flags = 0; comp_cp->shut_cmp.ch.chunk_length = htons(sizeof(struct sctp_shutdown_complete_chunk)); comp_cp->sh.src_port = stcb->sctp_ep->sctp_lport; comp_cp->sh.dest_port = stcb->rport; comp_cp->sh.v_tag = htonl(stcb->asoc.peer_vtag); comp_cp->sh.checksum = 0; SCTP_BUF_LEN(m_shutdown_comp) = sizeof(struct sctp_shutdown_complete_msg); sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, net, (struct sockaddr *)&net->ro._l_addr, m_shutdown_comp, 0, NULL, 1, 0, NULL, 0); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); - return (0); + return; } -int +void sctp_send_shutdown_complete2(struct mbuf *m, int iphlen, struct sctphdr *sh, uint32_t vrf_id, uint32_t table_id) { /* formulate and SEND a SHUTDOWN-COMPLETE */ struct mbuf *o_pak; struct mbuf *mout; struct ip *iph, *iph_out; struct ip6_hdr *ip6, *ip6_out; int offset_out, len, mlen; struct sctp_shutdown_complete_msg *comp_cp; /* Get room for the largest message */ len = (sizeof(struct ip6_hdr) + sizeof(struct sctp_shutdown_complete_msg)); mout = sctp_get_mbuf_for_msg(len, 1, M_DONTWAIT, 1, MT_DATA); if (mout == NULL) { - return (-1); + return; } SCTP_BUF_LEN(mout) = len; iph = mtod(m, struct ip *); iph_out = NULL; ip6_out = NULL; offset_out = 0; if (iph->ip_v == IPVERSION) { SCTP_BUF_LEN(mout) = sizeof(struct ip) + sizeof(struct sctp_shutdown_complete_msg); SCTP_BUF_NEXT(mout) = NULL; iph_out = mtod(mout, struct ip *); /* Fill in the IP header for the ABORT */ iph_out->ip_v = IPVERSION; iph_out->ip_hl = (sizeof(struct ip) / 4); iph_out->ip_tos = (u_char)0; iph_out->ip_id = 0; iph_out->ip_off = 0; iph_out->ip_ttl = MAXTTL; iph_out->ip_p = IPPROTO_SCTP; iph_out->ip_src.s_addr = iph->ip_dst.s_addr; iph_out->ip_dst.s_addr = iph->ip_src.s_addr; /* let IP layer calculate this */ iph_out->ip_sum = 0; offset_out += sizeof(*iph_out); comp_cp = (struct sctp_shutdown_complete_msg *)( (caddr_t)iph_out + offset_out); } else if (iph->ip_v == (IPV6_VERSION >> 4)) { ip6 = (struct ip6_hdr *)iph; SCTP_BUF_LEN(mout) = sizeof(struct ip6_hdr) + sizeof(struct sctp_shutdown_complete_msg); SCTP_BUF_NEXT(mout) = NULL; ip6_out = mtod(mout, struct ip6_hdr *); /* Fill in the IPv6 header for the ABORT */ ip6_out->ip6_flow = ip6->ip6_flow; ip6_out->ip6_hlim = ip6_defhlim; ip6_out->ip6_nxt = IPPROTO_SCTP; ip6_out->ip6_src = ip6->ip6_dst; ip6_out->ip6_dst = ip6->ip6_src; /* * ?? The old code had both the iph len + payload, I think * this is wrong and would never have worked */ ip6_out->ip6_plen = sizeof(struct sctp_shutdown_complete_msg); offset_out += sizeof(*ip6_out); comp_cp = (struct sctp_shutdown_complete_msg *)( (caddr_t)ip6_out + offset_out); } else { /* Currently not supported. */ - return (-1); + return; } if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) { /* no mbuf's */ sctp_m_freem(mout); - return (-1); + return; } /* Now copy in and fill in the ABORT tags etc. */ comp_cp->sh.src_port = sh->dest_port; comp_cp->sh.dest_port = sh->src_port; comp_cp->sh.checksum = 0; comp_cp->sh.v_tag = sh->v_tag; comp_cp->shut_cmp.ch.chunk_flags = SCTP_HAD_NO_TCB; comp_cp->shut_cmp.ch.chunk_type = SCTP_SHUTDOWN_COMPLETE; comp_cp->shut_cmp.ch.chunk_length = htons(sizeof(struct sctp_shutdown_complete_chunk)); /* add checksum */ if ((sctp_no_csum_on_loopback) && SCTP_IS_IT_LOOPBACK(mout)) { comp_cp->sh.checksum = 0; } else { comp_cp->sh.checksum = sctp_calculate_sum(mout, NULL, offset_out); } if (iph_out != NULL) { sctp_route_t ro; int ret; struct sctp_tcb *stcb = NULL; mlen = SCTP_BUF_LEN(mout); bzero(&ro, sizeof ro); /* set IPv4 length */ iph_out->ip_len = mlen; SCTP_ATTACH_CHAIN(o_pak, mout, mlen); /* out it goes */ SCTP_IP_OUTPUT(ret, o_pak, &ro, stcb, vrf_id, table_id); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } else if (ip6_out != NULL) { struct route_in6 ro; int ret; struct sctp_tcb *stcb = NULL; struct ifnet *ifp = NULL; bzero(&ro, sizeof(ro)); mlen = SCTP_BUF_LEN(mout); SCTP_ATTACH_CHAIN(o_pak, mout, mlen); SCTP_IP6_OUTPUT(ret, o_pak, &ro, &ifp, stcb, vrf_id, table_id); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); SCTP_STAT_INCR_COUNTER64(sctps_outcontrolchunks); - return (0); + return; } static struct sctp_nets * sctp_select_hb_destination(struct sctp_tcb *stcb, struct timeval *now) { struct sctp_nets *net, *hnet; int ms_goneby, highest_ms, state_overide = 0; - SCTP_GETTIME_TIMEVAL(now); + (void)SCTP_GETTIME_TIMEVAL(now); highest_ms = 0; hnet = NULL; SCTP_TCB_LOCK_ASSERT(stcb); TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if ( ((net->dest_state & SCTP_ADDR_NOHB) && ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) || (net->dest_state & SCTP_ADDR_OUT_OF_SCOPE) ) { /* * Skip this guy from consideration if HB is off AND * its confirmed */ continue; } if (sctp_destination_is_reachable(stcb, (struct sockaddr *)&net->ro._l_addr) == 0) { /* skip this dest net from consideration */ continue; } if (net->last_sent_time.tv_sec) { /* Sent to so we subtract */ ms_goneby = (now->tv_sec - net->last_sent_time.tv_sec) * 1000; } else /* Never been sent to */ ms_goneby = 0x7fffffff; /*- * When the address state is unconfirmed but still * considered reachable, we HB at a higher rate. Once it * goes confirmed OR reaches the "unreachable" state, thenw * we cut it back to HB at a more normal pace. */ if ((net->dest_state & (SCTP_ADDR_UNCONFIRMED | SCTP_ADDR_NOT_REACHABLE)) == SCTP_ADDR_UNCONFIRMED) { state_overide = 1; } else { state_overide = 0; } if ((((unsigned int)ms_goneby >= net->RTO) || (state_overide)) && (ms_goneby > highest_ms)) { highest_ms = ms_goneby; hnet = net; } } if (hnet && ((hnet->dest_state & (SCTP_ADDR_UNCONFIRMED | SCTP_ADDR_NOT_REACHABLE)) == SCTP_ADDR_UNCONFIRMED)) { state_overide = 1; } else { state_overide = 0; } if (highest_ms && (((unsigned int)highest_ms >= hnet->RTO) || state_overide)) { /*- * Found the one with longest delay bounds OR it is * unconfirmed and still not marked unreachable. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { printf("net:%p is the hb winner -", hnet); if (hnet) sctp_print_address((struct sockaddr *)&hnet->ro._l_addr); else printf(" none\n"); } #endif /* update the timer now */ hnet->last_sent_time = *now; return (hnet); } /* Nothing to HB */ return (NULL); } int sctp_send_hb(struct sctp_tcb *stcb, int user_req, struct sctp_nets *u_net) { struct sctp_tmit_chunk *chk; struct sctp_nets *net; struct sctp_heartbeat_chunk *hb; struct timeval now; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; SCTP_TCB_LOCK_ASSERT(stcb); if (user_req == 0) { net = sctp_select_hb_destination(stcb, &now); if (net == NULL) { /*- * All our busy none to send to, just start the * timer again. */ if (stcb->asoc.state == 0) { return (0); } sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net); return (0); } } else { net = u_net; if (net == NULL) { return (0); } - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); } sin = (struct sockaddr_in *)&net->ro._l_addr; if (sin->sin_family != AF_INET) { if (sin->sin_family != AF_INET6) { /* huh */ return (0); } } sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { printf("Gak, can't get a chunk for hb\n"); } #endif return (0); } chk->copy_by_ref = 0; chk->rec.chunk_id.id = SCTP_HEARTBEAT_REQUEST; chk->rec.chunk_id.can_take_data = 1; chk->asoc = &stcb->asoc; chk->send_size = sizeof(struct sctp_heartbeat_chunk); chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_DONTWAIT, 1, MT_HEADER); if (chk->data == NULL) { sctp_free_a_chunk(stcb, chk); return (0); } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); SCTP_BUF_LEN(chk->data) = chk->send_size; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = net; atomic_add_int(&chk->whoTo->ref_count, 1); /* Now we have a mbuf that we can fill in with the details */ hb = mtod(chk->data, struct sctp_heartbeat_chunk *); /* fill out chunk header */ hb->ch.chunk_type = SCTP_HEARTBEAT_REQUEST; hb->ch.chunk_flags = 0; hb->ch.chunk_length = htons(chk->send_size); /* Fill out hb parameter */ hb->heartbeat.hb_info.ph.param_type = htons(SCTP_HEARTBEAT_INFO); hb->heartbeat.hb_info.ph.param_length = htons(sizeof(struct sctp_heartbeat_info_param)); hb->heartbeat.hb_info.time_value_1 = now.tv_sec; hb->heartbeat.hb_info.time_value_2 = now.tv_usec; /* Did our user request this one, put it in */ hb->heartbeat.hb_info.user_req = user_req; hb->heartbeat.hb_info.addr_family = sin->sin_family; hb->heartbeat.hb_info.addr_len = sin->sin_len; if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { /* * we only take from the entropy pool if the address is not * confirmed. */ net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); } else { net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = 0; net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = 0; } if (sin->sin_family == AF_INET) { memcpy(hb->heartbeat.hb_info.address, &sin->sin_addr, sizeof(sin->sin_addr)); } else if (sin->sin_family == AF_INET6) { /* We leave the scope the way it is in our lookup table. */ sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; memcpy(hb->heartbeat.hb_info.address, &sin6->sin6_addr, sizeof(sin6->sin6_addr)); } else { /* huh compiler bug */ return (0); } /* ok we have a destination that needs a beat */ /* lets do the theshold management Qiaobing style */ if (sctp_threshold_management(stcb->sctp_ep, stcb, net, stcb->asoc.max_send_times)) { /*- * we have lost the association, in a way this is * quite bad since we really are one less time since * we really did not send yet. This is the down side * to the Q's style as defined in the RFC and not my * alternate style defined in the RFC. */ atomic_subtract_int(&chk->whoTo->ref_count, 1); if (chk->data != NULL) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_a_chunk(stcb, chk); return (-1); } net->hb_responded = 0; TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); stcb->asoc.ctrl_queue_cnt++; SCTP_STAT_INCR(sctps_sendheartbeat); /*- * Call directly med level routine to put out the chunk. It will * always tumble out control chunks aka HB but it may even tumble * out data too. */ return (1); } void sctp_send_ecn_echo(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t high_tsn) { struct sctp_association *asoc; struct sctp_ecne_chunk *ecne; struct sctp_tmit_chunk *chk; asoc = &stcb->asoc; SCTP_TCB_LOCK_ASSERT(stcb); TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->rec.chunk_id.id == SCTP_ECN_ECHO) { /* found a previous ECN_ECHO update it if needed */ ecne = mtod(chk->data, struct sctp_ecne_chunk *); ecne->tsn = htonl(high_tsn); return; } } /* nope could not find one to update so we must build one */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return; } chk->copy_by_ref = 0; SCTP_STAT_INCR(sctps_sendecne); chk->rec.chunk_id.id = SCTP_ECN_ECHO; chk->rec.chunk_id.can_take_data = 0; chk->asoc = &stcb->asoc; chk->send_size = sizeof(struct sctp_ecne_chunk); chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_DONTWAIT, 1, MT_HEADER); if (chk->data == NULL) { sctp_free_a_chunk(stcb, chk); return; } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); SCTP_BUF_LEN(chk->data) = chk->send_size; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = net; atomic_add_int(&chk->whoTo->ref_count, 1); stcb->asoc.ecn_echo_cnt_onq++; ecne = mtod(chk->data, struct sctp_ecne_chunk *); ecne->ch.chunk_type = SCTP_ECN_ECHO; ecne->ch.chunk_flags = 0; ecne->ch.chunk_length = htons(sizeof(struct sctp_ecne_chunk)); ecne->tsn = htonl(high_tsn); TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; } void sctp_send_packet_dropped(struct sctp_tcb *stcb, struct sctp_nets *net, struct mbuf *m, int iphlen, int bad_crc) { struct sctp_association *asoc; struct sctp_pktdrop_chunk *drp; struct sctp_tmit_chunk *chk; uint8_t *datap; int len; unsigned int small_one; struct ip *iph; long spc; asoc = &stcb->asoc; SCTP_TCB_LOCK_ASSERT(stcb); if (asoc->peer_supports_pktdrop == 0) { /*- * peer must declare support before I send one. */ return; } if (stcb->sctp_socket == NULL) { return; } sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return; } chk->copy_by_ref = 0; iph = mtod(m, struct ip *); if (iph == NULL) { return; } if (iph->ip_v == IPVERSION) { /* IPv4 */ len = chk->send_size = iph->ip_len; } else { struct ip6_hdr *ip6h; /* IPv6 */ ip6h = mtod(m, struct ip6_hdr *); len = chk->send_size = htons(ip6h->ip6_plen); } chk->asoc = &stcb->asoc; chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (chk->data == NULL) { jump_out: sctp_free_a_chunk(stcb, chk); return; } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); drp = mtod(chk->data, struct sctp_pktdrop_chunk *); if (drp == NULL) { sctp_m_freem(chk->data); chk->data = NULL; goto jump_out; } small_one = asoc->smallest_mtu; if (small_one > MCLBYTES) { /* Only one cluster worth of data MAX */ small_one = MCLBYTES; } chk->book_size = SCTP_SIZE32((chk->send_size + sizeof(struct sctp_pktdrop_chunk) + sizeof(struct sctphdr) + SCTP_MED_OVERHEAD)); chk->book_size_scale = 0; if (chk->book_size > small_one) { drp->ch.chunk_flags = SCTP_PACKET_TRUNCATED; drp->trunc_len = htons(chk->send_size); chk->send_size = small_one - (SCTP_MED_OVERHEAD + sizeof(struct sctp_pktdrop_chunk) + sizeof(struct sctphdr)); len = chk->send_size; } else { /* no truncation needed */ drp->ch.chunk_flags = 0; drp->trunc_len = htons(0); } if (bad_crc) { drp->ch.chunk_flags |= SCTP_BADCRC; } chk->send_size += sizeof(struct sctp_pktdrop_chunk); SCTP_BUF_LEN(chk->data) = chk->send_size; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; if (net) { /* we should hit here */ chk->whoTo = net; } else { chk->whoTo = asoc->primary_destination; } atomic_add_int(&chk->whoTo->ref_count, 1); chk->rec.chunk_id.id = SCTP_PACKET_DROPPED; chk->rec.chunk_id.can_take_data = 1; drp->ch.chunk_type = SCTP_PACKET_DROPPED; drp->ch.chunk_length = htons(chk->send_size); spc = SCTP_SB_LIMIT_RCV(stcb->sctp_socket); if (spc < 0) { spc = 0; } drp->bottle_bw = htonl(spc); if (asoc->my_rwnd) { drp->current_onq = htonl(asoc->size_on_reasm_queue + asoc->size_on_all_streams + asoc->my_rwnd_control_len + stcb->sctp_socket->so_rcv.sb_cc); } else { /*- * If my rwnd is 0, possibly from mbuf depletion as well as * space used, tell the peer there is NO space aka onq == bw */ drp->current_onq = htonl(spc); } drp->reserved = 0; datap = drp->data; m_copydata(m, iphlen, len, (caddr_t)datap); TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; } void sctp_send_cwr(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t high_tsn) { struct sctp_association *asoc; struct sctp_cwr_chunk *cwr; struct sctp_tmit_chunk *chk; asoc = &stcb->asoc; SCTP_TCB_LOCK_ASSERT(stcb); TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->rec.chunk_id.id == SCTP_ECN_CWR) { /* found a previous ECN_CWR update it if needed */ cwr = mtod(chk->data, struct sctp_cwr_chunk *); if (compare_with_wrap(high_tsn, ntohl(cwr->tsn), MAX_TSN)) { cwr->tsn = htonl(high_tsn); } return; } } /* nope could not find one to update so we must build one */ sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return; } chk->copy_by_ref = 0; chk->rec.chunk_id.id = SCTP_ECN_CWR; chk->rec.chunk_id.can_take_data = 1; chk->asoc = &stcb->asoc; chk->send_size = sizeof(struct sctp_cwr_chunk); chk->data = sctp_get_mbuf_for_msg(chk->send_size, 0, M_DONTWAIT, 1, MT_HEADER); if (chk->data == NULL) { sctp_free_a_chunk(stcb, chk); return; } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); SCTP_BUF_LEN(chk->data) = chk->send_size; chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = net; atomic_add_int(&chk->whoTo->ref_count, 1); cwr = mtod(chk->data, struct sctp_cwr_chunk *); cwr->ch.chunk_type = SCTP_ECN_CWR; cwr->ch.chunk_flags = 0; cwr->ch.chunk_length = htons(sizeof(struct sctp_cwr_chunk)); cwr->tsn = htonl(high_tsn); TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; } void sctp_add_stream_reset_out(struct sctp_tmit_chunk *chk, int number_entries, uint16_t * list, uint32_t seq, uint32_t resp_seq, uint32_t last_sent) { int len, old_len, i; struct sctp_stream_reset_out_request *req_out; struct sctp_chunkhdr *ch; ch = mtod(chk->data, struct sctp_chunkhdr *); old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length)); /* get to new offset for the param. */ req_out = (struct sctp_stream_reset_out_request *)((caddr_t)ch + len); /* now how long will this param be? */ len = (sizeof(struct sctp_stream_reset_out_request) + (sizeof(uint16_t) * number_entries)); req_out->ph.param_type = htons(SCTP_STR_RESET_OUT_REQUEST); req_out->ph.param_length = htons(len); req_out->request_seq = htonl(seq); req_out->response_seq = htonl(resp_seq); req_out->send_reset_at_tsn = htonl(last_sent); if (number_entries) { for (i = 0; i < number_entries; i++) { req_out->list_of_streams[i] = htons(list[i]); } } if (SCTP_SIZE32(len) > len) { /*- * Need to worry about the pad we may end up adding to the * end. This is easy since the struct is either aligned to 4 * bytes or 2 bytes off. */ req_out->list_of_streams[number_entries] = 0; } /* now fix the chunk length */ ch->chunk_length = htons(len + old_len); chk->book_size = len + old_len; chk->book_size_scale = 0; chk->send_size = SCTP_SIZE32(chk->book_size); SCTP_BUF_LEN(chk->data) = chk->send_size; return; } void sctp_add_stream_reset_in(struct sctp_tmit_chunk *chk, int number_entries, uint16_t * list, uint32_t seq) { int len, old_len, i; struct sctp_stream_reset_in_request *req_in; struct sctp_chunkhdr *ch; ch = mtod(chk->data, struct sctp_chunkhdr *); old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length)); /* get to new offset for the param. */ req_in = (struct sctp_stream_reset_in_request *)((caddr_t)ch + len); /* now how long will this param be? */ len = (sizeof(struct sctp_stream_reset_in_request) + (sizeof(uint16_t) * number_entries)); req_in->ph.param_type = htons(SCTP_STR_RESET_IN_REQUEST); req_in->ph.param_length = htons(len); req_in->request_seq = htonl(seq); if (number_entries) { for (i = 0; i < number_entries; i++) { req_in->list_of_streams[i] = htons(list[i]); } } if (SCTP_SIZE32(len) > len) { /*- * Need to worry about the pad we may end up adding to the * end. This is easy since the struct is either aligned to 4 * bytes or 2 bytes off. */ req_in->list_of_streams[number_entries] = 0; } /* now fix the chunk length */ ch->chunk_length = htons(len + old_len); chk->book_size = len + old_len; chk->book_size_scale = 0; chk->send_size = SCTP_SIZE32(chk->book_size); SCTP_BUF_LEN(chk->data) = chk->send_size; return; } void sctp_add_stream_reset_tsn(struct sctp_tmit_chunk *chk, uint32_t seq) { int len, old_len; struct sctp_stream_reset_tsn_request *req_tsn; struct sctp_chunkhdr *ch; ch = mtod(chk->data, struct sctp_chunkhdr *); old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length)); /* get to new offset for the param. */ req_tsn = (struct sctp_stream_reset_tsn_request *)((caddr_t)ch + len); /* now how long will this param be? */ len = sizeof(struct sctp_stream_reset_tsn_request); req_tsn->ph.param_type = htons(SCTP_STR_RESET_TSN_REQUEST); req_tsn->ph.param_length = htons(len); req_tsn->request_seq = htonl(seq); /* now fix the chunk length */ ch->chunk_length = htons(len + old_len); chk->send_size = len + old_len; chk->book_size = SCTP_SIZE32(chk->send_size); chk->book_size_scale = 0; SCTP_BUF_LEN(chk->data) = SCTP_SIZE32(chk->send_size); return; } void sctp_add_stream_reset_result(struct sctp_tmit_chunk *chk, uint32_t resp_seq, uint32_t result) { int len, old_len; struct sctp_stream_reset_response *resp; struct sctp_chunkhdr *ch; ch = mtod(chk->data, struct sctp_chunkhdr *); old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length)); /* get to new offset for the param. */ resp = (struct sctp_stream_reset_response *)((caddr_t)ch + len); /* now how long will this param be? */ len = sizeof(struct sctp_stream_reset_response); resp->ph.param_type = htons(SCTP_STR_RESET_RESPONSE); resp->ph.param_length = htons(len); resp->response_seq = htonl(resp_seq); resp->result = ntohl(result); /* now fix the chunk length */ ch->chunk_length = htons(len + old_len); chk->book_size = len + old_len; chk->book_size_scale = 0; chk->send_size = SCTP_SIZE32(chk->book_size); SCTP_BUF_LEN(chk->data) = chk->send_size; return; } void sctp_add_stream_reset_result_tsn(struct sctp_tmit_chunk *chk, uint32_t resp_seq, uint32_t result, uint32_t send_una, uint32_t recv_next) { int len, old_len; struct sctp_stream_reset_response_tsn *resp; struct sctp_chunkhdr *ch; ch = mtod(chk->data, struct sctp_chunkhdr *); old_len = len = SCTP_SIZE32(ntohs(ch->chunk_length)); /* get to new offset for the param. */ resp = (struct sctp_stream_reset_response_tsn *)((caddr_t)ch + len); /* now how long will this param be? */ len = sizeof(struct sctp_stream_reset_response_tsn); resp->ph.param_type = htons(SCTP_STR_RESET_RESPONSE); resp->ph.param_length = htons(len); resp->response_seq = htonl(resp_seq); resp->result = htonl(result); resp->senders_next_tsn = htonl(send_una); resp->receivers_next_tsn = htonl(recv_next); /* now fix the chunk length */ ch->chunk_length = htons(len + old_len); chk->book_size = len + old_len; chk->send_size = SCTP_SIZE32(chk->book_size); chk->book_size_scale = 0; SCTP_BUF_LEN(chk->data) = chk->send_size; return; } int sctp_send_str_reset_req(struct sctp_tcb *stcb, int number_entries, uint16_t * list, uint8_t send_out_req, uint32_t resp_seq, uint8_t send_in_req, uint8_t send_tsn_req) { struct sctp_association *asoc; struct sctp_tmit_chunk *chk; struct sctp_chunkhdr *ch; uint32_t seq; asoc = &stcb->asoc; if (asoc->stream_reset_outstanding) { /*- * Already one pending, must get ACK back to clear the flag. */ return (EBUSY); } if ((send_out_req == 0) && (send_in_req == 0) && (send_tsn_req == 0)) { /* nothing to do */ return (EINVAL); } if (send_tsn_req && (send_out_req || send_in_req)) { /* error, can't do that */ return (EINVAL); } sctp_alloc_a_chunk(stcb, chk); if (chk == NULL) { return (ENOMEM); } chk->copy_by_ref = 0; chk->rec.chunk_id.id = SCTP_STREAM_RESET; chk->rec.chunk_id.can_take_data = 0; chk->asoc = &stcb->asoc; chk->book_size = sizeof(struct sctp_chunkhdr); chk->send_size = SCTP_SIZE32(chk->book_size); chk->book_size_scale = 0; chk->data = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (chk->data == NULL) { sctp_free_a_chunk(stcb, chk); return (ENOMEM); } SCTP_BUF_RESV_UF(chk->data, SCTP_MIN_OVERHEAD); /* setup chunk parameters */ chk->sent = SCTP_DATAGRAM_UNSENT; chk->snd_count = 0; chk->whoTo = asoc->primary_destination; atomic_add_int(&chk->whoTo->ref_count, 1); ch = mtod(chk->data, struct sctp_chunkhdr *); ch->chunk_type = SCTP_STREAM_RESET; ch->chunk_flags = 0; ch->chunk_length = htons(chk->book_size); SCTP_BUF_LEN(chk->data) = chk->send_size; seq = stcb->asoc.str_reset_seq_out; if (send_out_req) { sctp_add_stream_reset_out(chk, number_entries, list, seq, resp_seq, (stcb->asoc.sending_seq - 1)); asoc->stream_reset_out_is_outstanding = 1; seq++; asoc->stream_reset_outstanding++; } if (send_in_req) { sctp_add_stream_reset_in(chk, number_entries, list, seq); asoc->stream_reset_outstanding++; } if (send_tsn_req) { sctp_add_stream_reset_tsn(chk, seq); asoc->stream_reset_outstanding++; } asoc->str_reset = chk; /* insert the chunk for sending */ TAILQ_INSERT_TAIL(&asoc->control_send_queue, chk, sctp_next); asoc->ctrl_queue_cnt++; sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo); return (0); } void sctp_send_abort(struct mbuf *m, int iphlen, struct sctphdr *sh, uint32_t vtag, struct mbuf *err_cause, uint32_t vrf_id, uint32_t table_id) { /*- * Formulate the abort message, and send it back down. */ struct mbuf *o_pak; struct mbuf *mout; struct sctp_abort_msg *abm; struct ip *iph, *iph_out; struct ip6_hdr *ip6, *ip6_out; int iphlen_out, len; /* don't respond to ABORT with ABORT */ if (sctp_is_there_an_abort_here(m, iphlen, &vtag)) { if (err_cause) sctp_m_freem(err_cause); return; } len = (sizeof(struct ip6_hdr) + sizeof(struct sctp_abort_msg)); mout = sctp_get_mbuf_for_msg(len, 1, M_DONTWAIT, 1, MT_DATA); if (mout == NULL) { if (err_cause) sctp_m_freem(err_cause); return; } iph = mtod(m, struct ip *); iph_out = NULL; ip6_out = NULL; if (iph->ip_v == IPVERSION) { iph_out = mtod(mout, struct ip *); SCTP_BUF_LEN(mout) = sizeof(*iph_out) + sizeof(*abm); SCTP_BUF_NEXT(mout) = err_cause; /* Fill in the IP header for the ABORT */ iph_out->ip_v = IPVERSION; iph_out->ip_hl = (sizeof(struct ip) / 4); iph_out->ip_tos = (u_char)0; iph_out->ip_id = 0; iph_out->ip_off = 0; iph_out->ip_ttl = MAXTTL; iph_out->ip_p = IPPROTO_SCTP; iph_out->ip_src.s_addr = iph->ip_dst.s_addr; iph_out->ip_dst.s_addr = iph->ip_src.s_addr; /* let IP layer calculate this */ iph_out->ip_sum = 0; iphlen_out = sizeof(*iph_out); abm = (struct sctp_abort_msg *)((caddr_t)iph_out + iphlen_out); } else if (iph->ip_v == (IPV6_VERSION >> 4)) { ip6 = (struct ip6_hdr *)iph; ip6_out = mtod(mout, struct ip6_hdr *); SCTP_BUF_LEN(mout) = sizeof(*ip6_out) + sizeof(*abm); SCTP_BUF_NEXT(mout) = err_cause; /* Fill in the IP6 header for the ABORT */ ip6_out->ip6_flow = ip6->ip6_flow; ip6_out->ip6_hlim = ip6_defhlim; ip6_out->ip6_nxt = IPPROTO_SCTP; ip6_out->ip6_src = ip6->ip6_dst; ip6_out->ip6_dst = ip6->ip6_src; iphlen_out = sizeof(*ip6_out); abm = (struct sctp_abort_msg *)((caddr_t)ip6_out + iphlen_out); } else { /* Currently not supported */ return; } abm->sh.src_port = sh->dest_port; abm->sh.dest_port = sh->src_port; abm->sh.checksum = 0; if (vtag == 0) { abm->sh.v_tag = sh->v_tag; abm->msg.ch.chunk_flags = SCTP_HAD_NO_TCB; } else { abm->sh.v_tag = htonl(vtag); abm->msg.ch.chunk_flags = 0; } abm->msg.ch.chunk_type = SCTP_ABORT_ASSOCIATION; if (err_cause) { struct mbuf *m_tmp = err_cause; int err_len = 0; /* get length of the err_cause chain */ while (m_tmp != NULL) { err_len += SCTP_BUF_LEN(m_tmp); m_tmp = SCTP_BUF_NEXT(m_tmp); } len = SCTP_BUF_LEN(mout) + err_len; if (err_len % 4) { /* need pad at end of chunk */ uint32_t cpthis = 0; int padlen; padlen = 4 - (len % 4); m_copyback(mout, len, padlen, (caddr_t)&cpthis); len += padlen; } abm->msg.ch.chunk_length = htons(sizeof(abm->msg.ch) + err_len); } else { len = SCTP_BUF_LEN(mout); abm->msg.ch.chunk_length = htons(sizeof(abm->msg.ch)); } /* add checksum */ if ((sctp_no_csum_on_loopback) && SCTP_IS_IT_LOOPBACK(m)) { abm->sh.checksum = 0; } else { abm->sh.checksum = sctp_calculate_sum(mout, NULL, iphlen_out); } if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) { /* no mbuf's */ sctp_m_freem(mout); return; } if (iph_out != NULL) { sctp_route_t ro; struct sctp_tcb *stcb = NULL; int ret; /* zap the stack pointer to the route */ bzero(&ro, sizeof ro); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("sctp_send_abort calling ip_output:\n"); sctp_print_address_pkt(iph_out, &abm->sh); } #endif /* set IPv4 length */ iph_out->ip_len = len; /* out it goes */ SCTP_ATTACH_CHAIN(o_pak, mout, len); SCTP_IP_OUTPUT(ret, o_pak, &ro, stcb, vrf_id, table_id); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } else if (ip6_out != NULL) { struct route_in6 ro; int ret; struct sctp_tcb *stcb = NULL; struct ifnet *ifp = NULL; /* zap the stack pointer to the route */ bzero(&ro, sizeof(ro)); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("sctp_send_abort calling ip6_output:\n"); sctp_print_address_pkt((struct ip *)ip6_out, &abm->sh); } #endif ip6_out->ip6_plen = len - sizeof(*ip6_out); SCTP_ATTACH_CHAIN(o_pak, mout, len); SCTP_IP6_OUTPUT(ret, o_pak, &ro, &ifp, stcb, vrf_id, table_id); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); } void sctp_send_operr_to(struct mbuf *m, int iphlen, struct mbuf *scm, uint32_t vtag, uint32_t vrf_id, uint32_t table_id) { struct mbuf *o_pak; struct sctphdr *ihdr; int retcode; struct sctphdr *ohdr; struct sctp_chunkhdr *ophdr; struct ip *iph; struct mbuf *mout; #ifdef SCTP_DEBUG struct sockaddr_in6 lsa6, fsa6; #endif uint32_t val; struct mbuf *at; int len; iph = mtod(m, struct ip *); ihdr = (struct sctphdr *)((caddr_t)iph + iphlen); SCTP_BUF_PREPEND(scm, (sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr)), M_DONTWAIT); if (scm == NULL) { /* can't send because we can't add a mbuf */ return; } ohdr = mtod(scm, struct sctphdr *); ohdr->src_port = ihdr->dest_port; ohdr->dest_port = ihdr->src_port; ohdr->v_tag = vtag; ohdr->checksum = 0; ophdr = (struct sctp_chunkhdr *)(ohdr + 1); ophdr->chunk_type = SCTP_OPERATION_ERROR; ophdr->chunk_flags = 0; len = 0; at = scm; while (at) { len += SCTP_BUF_LEN(at); at = SCTP_BUF_NEXT(at); } ophdr->chunk_length = htons(len - sizeof(struct sctphdr)); if (len % 4) { /* need padding */ uint32_t cpthis = 0; int padlen; padlen = 4 - (len % 4); m_copyback(scm, len, padlen, (caddr_t)&cpthis); len += padlen; } if ((sctp_no_csum_on_loopback) && SCTP_IS_IT_LOOPBACK(m)) { val = 0; } else { val = sctp_calculate_sum(scm, NULL, 0); } mout = sctp_get_mbuf_for_msg(sizeof(struct ip6_hdr), 1, M_DONTWAIT, 1, MT_DATA); if (mout == NULL) { sctp_m_freem(scm); return; } SCTP_BUF_NEXT(mout) = scm; if (SCTP_GET_HEADER_FOR_OUTPUT(o_pak)) { sctp_m_freem(mout); return; } ohdr->checksum = val; if (iph->ip_v == IPVERSION) { /* V4 */ struct ip *out; sctp_route_t ro; struct sctp_tcb *stcb = NULL; SCTP_BUF_LEN(mout) = sizeof(struct ip); len += sizeof(struct ip); bzero(&ro, sizeof ro); out = mtod(mout, struct ip *); out->ip_v = iph->ip_v; out->ip_hl = (sizeof(struct ip) / 4); out->ip_tos = iph->ip_tos; out->ip_id = iph->ip_id; out->ip_off = 0; out->ip_ttl = MAXTTL; out->ip_p = IPPROTO_SCTP; out->ip_sum = 0; out->ip_src = iph->ip_dst; out->ip_dst = iph->ip_src; out->ip_len = len; SCTP_ATTACH_CHAIN(o_pak, mout, len); SCTP_IP_OUTPUT(retcode, o_pak, &ro, stcb, vrf_id, table_id); SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } else { /* V6 */ struct route_in6 ro; int ret; struct sctp_tcb *stcb = NULL; struct ifnet *ifp = NULL; struct ip6_hdr *out6, *in6; SCTP_BUF_LEN(mout) = sizeof(struct ip6_hdr); len += sizeof(struct ip6_hdr); bzero(&ro, sizeof ro); in6 = mtod(m, struct ip6_hdr *); out6 = mtod(mout, struct ip6_hdr *); out6->ip6_flow = in6->ip6_flow; out6->ip6_hlim = ip6_defhlim; out6->ip6_nxt = IPPROTO_SCTP; out6->ip6_src = in6->ip6_dst; out6->ip6_dst = in6->ip6_src; out6->ip6_plen = len - sizeof(struct ip6_hdr); #ifdef SCTP_DEBUG bzero(&lsa6, sizeof(lsa6)); lsa6.sin6_len = sizeof(lsa6); lsa6.sin6_family = AF_INET6; lsa6.sin6_addr = out6->ip6_src; bzero(&fsa6, sizeof(fsa6)); fsa6.sin6_len = sizeof(fsa6); fsa6.sin6_family = AF_INET6; fsa6.sin6_addr = out6->ip6_dst; if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { printf("sctp_operr_to calling ipv6 output:\n"); printf("src: "); sctp_print_address((struct sockaddr *)&lsa6); printf("dst "); sctp_print_address((struct sockaddr *)&fsa6); } #endif /* SCTP_DEBUG */ SCTP_ATTACH_CHAIN(o_pak, mout, len); SCTP_IP6_OUTPUT(ret, o_pak, &ro, &ifp, stcb, vrf_id, table_id); SCTP_STAT_INCR(sctps_sendpackets); SCTP_STAT_INCR_COUNTER64(sctps_outpackets); /* Free the route if we got one back */ if (ro.ro_rt) RTFREE(ro.ro_rt); } } static struct mbuf * sctp_copy_resume(struct sctp_stream_queue_pending *sp, struct uio *uio, struct sctp_sndrcvinfo *srcv, int max_send_len, int user_marks_eor, int *error, uint32_t * sndout, struct mbuf **new_tail) { struct mbuf *m; m = m_uiotombuf(uio, M_WAITOK, max_send_len, 0, (M_PKTHDR | (user_marks_eor ? M_EOR : 0))); if (m == NULL) *error = ENOMEM; else *sndout = m_length(m, NULL); *new_tail = m_last(m); return (m); } static int sctp_copy_one(struct sctp_stream_queue_pending *sp, struct uio *uio, int resv_upfront) { int left; left = sp->length; sp->data = m_uiotombuf(uio, M_WAITOK, sp->length, resv_upfront, 0); if (sp->data == NULL) return (ENOMEM); sp->tail_mbuf = m_last(sp->data); return (0); } static struct sctp_stream_queue_pending * sctp_copy_it_in(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_sndrcvinfo *srcv, struct uio *uio, struct sctp_nets *net, int max_send_len, int user_marks_eor, int *error, int non_blocking) { /*- * This routine must be very careful in its work. Protocol * processing is up and running so care must be taken to spl...() * when you need to do something that may effect the stcb/asoc. The * sb is locked however. When data is copied the protocol processing * should be enabled since this is a slower operation... */ struct sctp_stream_queue_pending *sp = NULL; int resv_in_first; *error = 0; /* Unless E_EOR mode is on, we must make a send FIT in one call. */ if (((user_marks_eor == 0) && non_blocking) && (uio->uio_resid > SCTP_SB_LIMIT_SND(stcb->sctp_socket))) { /* It will NEVER fit */ *error = EMSGSIZE; goto out_now; } /* Now can we send this? */ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { /* got data while shutting down */ *error = ECONNRESET; goto out_now; } sctp_alloc_a_strmoq(stcb, sp); if (sp == NULL) { *error = ENOMEM; goto out_now; } sp->act_flags = 0; sp->sender_all_done = 0; sp->sinfo_flags = srcv->sinfo_flags; sp->timetolive = srcv->sinfo_timetolive; sp->ppid = srcv->sinfo_ppid; sp->context = srcv->sinfo_context; sp->strseq = 0; - SCTP_GETTIME_TIMEVAL(&sp->ts); + (void)SCTP_GETTIME_TIMEVAL(&sp->ts); sp->stream = srcv->sinfo_stream; sp->length = min(uio->uio_resid, max_send_len); if ((sp->length == uio->uio_resid) && ((user_marks_eor == 0) || (srcv->sinfo_flags & SCTP_EOF) || (user_marks_eor && (srcv->sinfo_flags & SCTP_EOR))) ) { sp->msg_is_complete = 1; } else { sp->msg_is_complete = 0; } sp->sender_all_done = 0; sp->some_taken = 0; sp->put_last_out = 0; resv_in_first = sizeof(struct sctp_data_chunk); sp->data = sp->tail_mbuf = NULL; *error = sctp_copy_one(sp, uio, resv_in_first); if (*error) { sctp_free_a_strmoq(stcb, sp); sp->data = NULL; sp->net = NULL; sp = NULL; } else { if (sp->sinfo_flags & SCTP_ADDR_OVER) { sp->net = net; sp->addr_over = 1; } else { sp->net = asoc->primary_destination; sp->addr_over = 0; } atomic_add_int(&sp->net->ref_count, 1); sctp_set_prsctp_policy(stcb, sp); } out_now: return (sp); } int sctp_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags , struct thread *p ) { struct sctp_inpcb *inp; int error, use_rcvinfo = 0; struct sctp_sndrcvinfo srcv; inp = (struct sctp_inpcb *)so->so_pcb; if (control) { /* process cmsg snd/rcv info (maybe a assoc-id) */ if (sctp_find_cmsg(SCTP_SNDRCV, (void *)&srcv, control, sizeof(srcv))) { /* got one */ use_rcvinfo = 1; } } error = sctp_lower_sosend(so, addr, uio, top, control, flags, use_rcvinfo, &srcv ,p ); return (error); } int sctp_lower_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *i_pak, struct mbuf *control, int flags, int use_rcvinfo, struct sctp_sndrcvinfo *srcv , struct thread *p ) { unsigned int sndlen, max_len; int error, len; struct mbuf *top = NULL; #if defined(__NetBSD__) || defined(__OpenBSD_) int s; #endif int queue_only = 0, queue_only_for_init = 0; int free_cnt_applied = 0; int un_sent = 0; int now_filled = 0; struct sctp_block_entry be; struct sctp_inpcb *inp; struct sctp_tcb *stcb = NULL; struct timeval now; struct sctp_nets *net; struct sctp_association *asoc; struct sctp_inpcb *t_inp; int create_lock_applied = 0; int nagle_applies = 0; int some_on_control = 0; int got_all_of_the_send = 0; int hold_tcblock = 0; int non_blocking = 0; int temp_flags = 0; error = 0; net = NULL; stcb = NULL; asoc = NULL; t_inp = inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { error = EFAULT; goto out_unlocked; } atomic_add_int(&inp->total_sends, 1); if (uio) sndlen = uio->uio_resid; else { sndlen = SCTP_HEADER_LEN(i_pak); top = SCTP_HEADER_TO_CHAIN(i_pak); } hold_tcblock = 0; if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_socket->so_qlimit)) { /* The listener can NOT send */ error = EFAULT; goto out_unlocked; } if ((use_rcvinfo) && srcv) { if (INVALID_SINFO_FLAG(srcv->sinfo_flags) || PR_SCTP_INVALID_POLICY(srcv->sinfo_flags)) { error = EINVAL; goto out_unlocked; } if (srcv->sinfo_flags) SCTP_STAT_INCR(sctps_sends_with_flags); if (srcv->sinfo_flags & SCTP_SENDALL) { /* its a sendall */ error = sctp_sendall(inp, uio, top, srcv); top = NULL; goto out_unlocked; } } /* now we must find the assoc */ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { SCTP_INP_RUNLOCK(inp); error = ENOTCONN; goto out_unlocked; } hold_tcblock = 0; SCTP_INP_RUNLOCK(inp); if (addr) { /* Must locate the net structure if addr given */ net = sctp_findnet(stcb, addr); if (net) { /* validate port was 0 or correct */ struct sockaddr_in *sin; sin = (struct sockaddr_in *)addr; if ((sin->sin_port != 0) && (sin->sin_port != stcb->rport)) { net = NULL; } } temp_flags |= SCTP_ADDR_OVER; } else net = stcb->asoc.primary_destination; if (addr && (net == NULL)) { /* Could not find address, was it legal */ if (addr->sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)addr; if (sin->sin_addr.s_addr == 0) { if ((sin->sin_port == 0) || (sin->sin_port == stcb->rport)) { net = stcb->asoc.primary_destination; } } } else { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { if ((sin6->sin6_port == 0) || (sin6->sin6_port == stcb->rport)) { net = stcb->asoc.primary_destination; } } } } if (net == NULL) { error = EINVAL; goto out_unlocked; } } else if (use_rcvinfo && srcv && srcv->sinfo_assoc_id) { stcb = sctp_findassociation_ep_asocid(inp, srcv->sinfo_assoc_id, 0); if (stcb) { if (addr) /* * Must locate the net structure if addr * given */ net = sctp_findnet(stcb, addr); else net = stcb->asoc.primary_destination; if ((srcv->sinfo_flags & SCTP_ADDR_OVER) && ((net == NULL) || (addr == NULL))) { struct sockaddr_in *sin; if (addr == NULL) { error = EINVAL; goto out_unlocked; } sin = (struct sockaddr_in *)addr; /* Validate port is 0 or correct */ if ((sin->sin_port != 0) && (sin->sin_port != stcb->rport)) { net = NULL; } } } hold_tcblock = 0; } else if (addr) { /*- * Since we did not use findep we must * increment it, and if we don't find a tcb * decrement it. */ SCTP_INP_WLOCK(inp); SCTP_INP_INCR_REF(inp); SCTP_INP_WUNLOCK(inp); stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } else { hold_tcblock = 1; } } if ((stcb == NULL) && (addr)) { /* Possible implicit send? */ SCTP_ASOC_CREATE_LOCK(inp); create_lock_applied = 1; if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { /* Should I really unlock ? */ error = EFAULT; goto out_unlocked; } if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && (addr->sa_family == AF_INET6)) { error = EINVAL; goto out_unlocked; } SCTP_INP_WLOCK(inp); SCTP_INP_INCR_REF(inp); SCTP_INP_WUNLOCK(inp); /* With the lock applied look again */ stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } else { hold_tcblock = 1; } } if (stcb == NULL) { if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { error = ENOTCONN; goto out_unlocked; } else if (addr == NULL) { error = ENOENT; goto out_unlocked; } else { /* * UDP style, we must go ahead and start the INIT * process */ uint32_t vrf_id; if ((use_rcvinfo) && (srcv) && ((srcv->sinfo_flags & SCTP_ABORT) || ((srcv->sinfo_flags & SCTP_EOF) && (uio->uio_resid == 0)))) { /*- * User asks to abort a non-existant assoc, * or EOF a non-existant assoc with no data */ error = ENOENT; goto out_unlocked; } /* get an asoc/stcb struct */ vrf_id = inp->def_vrf_id; stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0, vrf_id); if (stcb == NULL) { /* Error is setup for us in the call */ goto out_unlocked; } if (create_lock_applied) { SCTP_ASOC_CREATE_UNLOCK(inp); create_lock_applied = 0; } else { printf("Huh-3? create lock should have been on??\n"); } /* * Turn on queue only flag to prevent data from * being sent */ queue_only = 1; asoc = &stcb->asoc; asoc->state = SCTP_STATE_COOKIE_WAIT; - SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered); /* initialize authentication params for the assoc */ sctp_initialize_auth_params(inp, stcb); if (control) { /* * see if a init structure exists in cmsg * headers */ struct sctp_initmsg initm; int i; if (sctp_find_cmsg(SCTP_INIT, (void *)&initm, control, sizeof(initm))) { /* * we have an INIT override of the * default */ if (initm.sinit_max_attempts) asoc->max_init_times = initm.sinit_max_attempts; if (initm.sinit_num_ostreams) asoc->pre_open_streams = initm.sinit_num_ostreams; if (initm.sinit_max_instreams) asoc->max_inbound_streams = initm.sinit_max_instreams; if (initm.sinit_max_init_timeo) asoc->initial_init_rto_max = initm.sinit_max_init_timeo; if (asoc->streamoutcnt < asoc->pre_open_streams) { /* Default is NOT correct */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("Ok, defout:%d pre_open:%d\n", asoc->streamoutcnt, asoc->pre_open_streams); } #endif SCTP_FREE(asoc->strmout); asoc->strmout = NULL; asoc->streamoutcnt = asoc->pre_open_streams; /* * What happens if this * fails? .. we panic ... */ { struct sctp_stream_out *tmp_str; int had_lock = 0; if (hold_tcblock) { had_lock = 1; SCTP_TCB_UNLOCK(stcb); } SCTP_MALLOC(tmp_str, struct sctp_stream_out *, asoc->streamoutcnt * sizeof(struct sctp_stream_out), "StreamsOut"); if (had_lock) { SCTP_TCB_LOCK(stcb); } if (asoc->strmout == NULL) { asoc->strmout = tmp_str; } else { SCTP_FREE(asoc->strmout); asoc->strmout = tmp_str; } } for (i = 0; i < asoc->streamoutcnt; i++) { /*- * inbound side must be set * to 0xffff, also NOTE when * we get the INIT-ACK back * (for INIT sender) we MUST * reduce the count * (streamoutcnt) but first * check if we sent to any * of the upper streams that * were dropped (if some * were). Those that were * dropped must be notified * to the upper layer as * failed to send. */ asoc->strmout[i].next_sequence_sent = 0x0; TAILQ_INIT(&asoc->strmout[i].outqueue); asoc->strmout[i].stream_no = i; asoc->strmout[i].last_msg_incomplete = 0; asoc->strmout[i].next_spoke.tqe_next = 0; asoc->strmout[i].next_spoke.tqe_prev = 0; } } } } hold_tcblock = 1; /* out with the INIT */ queue_only_for_init = 1; /*- * we may want to dig in after this call and adjust the MTU * value. It defaulted to 1500 (constant) but the ro * structure may now have an update and thus we may need to * change it BEFORE we append the message. */ net = stcb->asoc.primary_destination; asoc = &stcb->asoc; } } if ((SCTP_SO_IS_NBIO(so) || (flags & MSG_NBIO) )) { non_blocking = 1; } asoc = &stcb->asoc; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT)) { if (sndlen > asoc->smallest_mtu) { error = EMSGSIZE; goto out_unlocked; } } /* would we block? */ if (non_blocking) { if ((SCTP_SB_LIMIT_SND(so) < (sndlen + stcb->asoc.total_output_queue_size)) || (stcb->asoc.chunks_on_out_queue > sctp_max_chunks_on_queue)) { error = EWOULDBLOCK; atomic_add_int(&stcb->sctp_ep->total_nospaces, 1); goto out_unlocked; } } /* Keep the stcb from being freed under our feet */ atomic_add_int(&stcb->asoc.refcnt, 1); free_cnt_applied = 1; if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { error = ECONNRESET; goto out_unlocked; } if (create_lock_applied) { SCTP_ASOC_CREATE_UNLOCK(inp); create_lock_applied = 0; } if (asoc->stream_reset_outstanding) { /* * Can't queue any data while stream reset is underway. */ error = EAGAIN; goto out_unlocked; } if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) { queue_only = 1; } if ((use_rcvinfo == 0) || (srcv == NULL)) { /* Grab the default stuff from the asoc */ srcv = &stcb->asoc.def_send; } /* we are now done with all control */ if (control) { sctp_m_freem(control); control = NULL; } if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { if ((use_rcvinfo) && (srcv->sinfo_flags & SCTP_ABORT)) { ; } else { error = ECONNRESET; goto out_unlocked; } } /* Ok, we will attempt a msgsnd :> */ if (p) { p->td_proc->p_stats->p_ru.ru_msgsnd++; } if (stcb) { if (((srcv->sinfo_flags | temp_flags) & SCTP_ADDR_OVER) == 0) { net = stcb->asoc.primary_destination; } } if (net == NULL) { error = EINVAL; goto out_unlocked; } if ((net->flight_size > net->cwnd) && (sctp_cmt_on_off == 0)) { /*- * CMT: Added check for CMT above. net above is the primary * dest. If CMT is ON, sender should always attempt to send * with the output routine sctp_fill_outqueue() that loops * through all destination addresses. Therefore, if CMT is * ON, queue_only is NOT set to 1 here, so that * sctp_chunk_output() can be called below. */ queue_only = 1; } else if (asoc->ifp_had_enobuf) { SCTP_STAT_INCR(sctps_ifnomemqueued); if (net->flight_size > (net->mtu * 2)) queue_only = 1; asoc->ifp_had_enobuf = 0; } else { un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); } /* Are we aborting? */ if (srcv->sinfo_flags & SCTP_ABORT) { struct mbuf *mm; int tot_demand, tot_out, max; SCTP_STAT_INCR(sctps_sends_with_abort); if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) { /* It has to be up before we abort */ /* how big is the user initiated abort? */ error = EINVAL; goto out; } if (hold_tcblock) { SCTP_TCB_UNLOCK(stcb); hold_tcblock = 0; } if (top) { struct mbuf *cntm; mm = sctp_get_mbuf_for_msg(1, 0, M_WAIT, 1, MT_DATA); tot_out = 0; cntm = top; while (cntm) { tot_out += SCTP_BUF_LEN(cntm); cntm = SCTP_BUF_NEXT(cntm); } tot_demand = (tot_out + sizeof(struct sctp_paramhdr)); } else { /* Must fit in a MTU */ tot_out = uio->uio_resid; tot_demand = (tot_out + sizeof(struct sctp_paramhdr)); mm = sctp_get_mbuf_for_msg(tot_demand, 0, M_WAIT, 1, MT_DATA); } if (mm == NULL) { error = ENOMEM; goto out; } max = asoc->smallest_mtu - sizeof(struct sctp_paramhdr); max -= sizeof(struct sctp_abort_msg); if (tot_out > max) { tot_out = max; } if (mm) { struct sctp_paramhdr *ph; /* now move forward the data pointer */ ph = mtod(mm, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons((sizeof(struct sctp_paramhdr) + tot_out)); ph++; SCTP_BUF_LEN(mm) = tot_out + sizeof(struct sctp_paramhdr); if (top == NULL) { error = uiomove((caddr_t)ph, (int)tot_out, uio); if (error) { /*- * Here if we can't get his data we * still abort we just don't get to * send the users note :-0 */ sctp_m_freem(mm); mm = NULL; } } else { SCTP_BUF_NEXT(mm) = top; } } if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } atomic_add_int(&stcb->asoc.refcnt, -1); free_cnt_applied = 0; /* release this lock, otherwise we hang on ourselves */ sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, mm); /* now relock the stcb so everything is sane */ hold_tcblock = 0; stcb = NULL; goto out_unlocked; } /* Calculate the maximum we can send */ if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size) { max_len = SCTP_SB_LIMIT_SND(so) - stcb->asoc.total_output_queue_size; } else { max_len = 0; } if (hold_tcblock) { SCTP_TCB_UNLOCK(stcb); hold_tcblock = 0; } /* Is the stream no. valid? */ if (srcv->sinfo_stream >= asoc->streamoutcnt) { /* Invalid stream number */ error = EINVAL; goto out_unlocked; } if (asoc->strmout == NULL) { /* huh? software error */ error = EFAULT; goto out_unlocked; } len = 0; if (max_len < sctp_add_more_threshold) { /* No room right no ! */ SOCKBUF_LOCK(&so->so_snd); while (SCTP_SB_LIMIT_SND(so) < (stcb->asoc.total_output_queue_size + sctp_add_more_threshold)) { #ifdef SCTP_BLK_LOGGING sctp_log_block(SCTP_BLOCK_LOG_INTO_BLKA, so, asoc, uio->uio_resid); #endif be.error = 0; stcb->block_entry = &be; error = sbwait(&so->so_snd); stcb->block_entry = NULL; if (error || so->so_error || be.error) { if (error == 0) { if (so->so_error) error = so->so_error; if (be.error) { error = be.error; } } SOCKBUF_UNLOCK(&so->so_snd); goto out_unlocked; } #ifdef SCTP_BLK_LOGGING sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK, so, asoc, stcb->asoc.total_output_queue_size); #endif if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { goto out_unlocked; } } if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size) { max_len = SCTP_SB_LIMIT_SND(so) - stcb->asoc.total_output_queue_size; } else { max_len = 0; } SOCKBUF_UNLOCK(&so->so_snd); } if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { goto out_unlocked; } atomic_add_int(&stcb->total_sends, 1); if (top == NULL) { struct sctp_stream_queue_pending *sp; #ifdef INVARIANTS struct sctp_stream_queue_pending *msp; #endif struct sctp_stream_out *strm; uint32_t sndout, initial_out; int user_marks_eor; if (uio->uio_resid == 0) { if (srcv->sinfo_flags & SCTP_EOF) { got_all_of_the_send = 1; goto dataless_eof; } else { error = EINVAL; goto out; } } initial_out = uio->uio_resid; SCTP_TCB_SEND_LOCK(stcb); if ((asoc->stream_locked) && (asoc->stream_locked_on != srcv->sinfo_stream)) { SCTP_TCB_SEND_UNLOCK(stcb); error = EAGAIN; goto out; } SCTP_TCB_SEND_UNLOCK(stcb); strm = &stcb->asoc.strmout[srcv->sinfo_stream]; user_marks_eor = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR); if (strm->last_msg_incomplete == 0) { do_a_copy_in: sp = sctp_copy_it_in(stcb, asoc, srcv, uio, net, max_len, user_marks_eor, &error, non_blocking); if ((sp == NULL) || (error)) { goto out; } SCTP_TCB_SEND_LOCK(stcb); #ifdef INVARIANTS msp = TAILQ_LAST(&strm->outqueue, sctp_streamhead); if (msp && (msp->msg_is_complete == 0)) panic("Huh, new mesg and old not done?"); #endif if (sp->msg_is_complete) { strm->last_msg_incomplete = 0; asoc->stream_locked = 0; } else { /* * Just got locked to this guy in case of an * interupt. */ strm->last_msg_incomplete = 1; asoc->stream_locked = 1; asoc->stream_locked_on = srcv->sinfo_stream; sp->sender_all_done = 0; } sctp_snd_sb_alloc(stcb, sp->length); atomic_add_int(&asoc->stream_queue_cnt, 1); if ((srcv->sinfo_flags & SCTP_UNORDERED) == 0) { sp->strseq = strm->next_sequence_sent; #ifdef SCTP_LOG_SENDING_STR sctp_misc_ints(SCTP_STRMOUT_LOG_ASSIGN, (uintptr_t) stcb, (uintptr_t) sp, (uint32_t) ((srcv->sinfo_stream << 16) | sp->strseq), 0); #endif strm->next_sequence_sent++; } else { SCTP_STAT_INCR(sctps_sends_with_unord); } TAILQ_INSERT_TAIL(&strm->outqueue, sp, next); if ((strm->next_spoke.tqe_next == NULL) && (strm->next_spoke.tqe_prev == NULL)) { /* Not on wheel, insert */ sctp_insert_on_wheel(stcb, asoc, strm, 1); } SCTP_TCB_SEND_UNLOCK(stcb); } else { SCTP_TCB_SEND_LOCK(stcb); sp = TAILQ_LAST(&strm->outqueue, sctp_streamhead); SCTP_TCB_SEND_UNLOCK(stcb); if (sp == NULL) { /* ???? Huh ??? last msg is gone */ #ifdef INVARIANTS panic("Warning: Last msg marked incomplete, yet nothing left?"); #else printf("Warning: Last msg marked incomplete, yet nothing left?\n"); strm->last_msg_incomplete = 0; #endif goto do_a_copy_in; } } while (uio->uio_resid > 0) { /* How much room do we have? */ struct mbuf *new_tail, *mm; if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size) max_len = SCTP_SB_LIMIT_SND(so) - stcb->asoc.total_output_queue_size; else max_len = 0; if ((max_len > sctp_add_more_threshold) || (uio->uio_resid && (uio->uio_resid < max_len))) { sndout = 0; new_tail = NULL; if (hold_tcblock) { SCTP_TCB_UNLOCK(stcb); hold_tcblock = 0; } mm = sctp_copy_resume(sp, uio, srcv, max_len, user_marks_eor, &error, &sndout, &new_tail); if ((mm == NULL) || error) { if (mm) { sctp_m_freem(mm); } goto out; } /* Update the mbuf and count */ SCTP_TCB_SEND_LOCK(stcb); if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* * we need to get out. Peer probably * aborted. */ sctp_m_freem(mm); if (stcb->asoc.state & SCTP_PCB_FLAGS_WAS_ABORTED) error = ECONNRESET; SCTP_TCB_SEND_UNLOCK(stcb); goto out; } if (sp->tail_mbuf) { /* tack it to the end */ SCTP_BUF_NEXT(sp->tail_mbuf) = mm; sp->tail_mbuf = new_tail; } else { /* A stolen mbuf */ sp->data = mm; sp->tail_mbuf = new_tail; } sctp_snd_sb_alloc(stcb, sndout); atomic_add_int(&sp->length, sndout); len += sndout; /* Did we reach EOR? */ if ((uio->uio_resid == 0) && ((user_marks_eor == 0) || (srcv->sinfo_flags & SCTP_EOF) || (user_marks_eor && (srcv->sinfo_flags & SCTP_EOR))) ) { sp->msg_is_complete = 1; } else { sp->msg_is_complete = 0; } SCTP_TCB_SEND_UNLOCK(stcb); } if (uio->uio_resid == 0) { /* got it all? */ continue; } /* PR-SCTP? */ if ((asoc->peer_supports_prsctp) && (asoc->sent_queue_cnt_removeable > 0)) { /* * This is ugly but we must assure locking * order */ if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } sctp_prune_prsctp(stcb, asoc, srcv, sndlen); if (SCTP_SB_LIMIT_SND(so) > stcb->asoc.total_output_queue_size) max_len = SCTP_SB_LIMIT_SND(so) - stcb->asoc.total_output_queue_size; else max_len = 0; if (max_len > 0) { continue; } SCTP_TCB_UNLOCK(stcb); hold_tcblock = 0; } /* wait for space now */ if (non_blocking) { /* Non-blocking io in place out */ goto skip_out_eof; } if ((net->flight_size > net->cwnd) && (sctp_cmt_on_off == 0)) { queue_only = 1; } else if (asoc->ifp_had_enobuf) { SCTP_STAT_INCR(sctps_ifnomemqueued); if (net->flight_size > (net->mtu * 2)) { queue_only = 1; } else { queue_only = 0; } asoc->ifp_had_enobuf = 0; un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); } else { un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); if (net->flight_size > (net->mtu * stcb->asoc.max_burst)) { queue_only = 1; SCTP_STAT_INCR(sctps_send_burst_avoid); } else if (net->flight_size > net->cwnd) { queue_only = 1; SCTP_STAT_INCR(sctps_send_cwnd_avoid); } else { queue_only = 0; } } if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) && (stcb->asoc.total_flight > 0) && (un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) ) { /*- * Ok, Nagle is set on and we have data outstanding. * Don't send anything and let SACKs drive out the * data unless wen have a "full" segment to send. */ #ifdef SCTP_NAGLE_LOGGING sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED); #endif SCTP_STAT_INCR(sctps_naglequeued); nagle_applies = 1; } else { #ifdef SCTP_NAGLE_LOGGING if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED); #endif SCTP_STAT_INCR(sctps_naglesent); nagle_applies = 0; } /* What about the INIT, send it maybe */ #ifdef SCTP_BLK_LOGGING sctp_misc_ints(SCTP_CWNDLOG_PRESEND, queue_only_for_init, queue_only, nagle_applies, un_sent); sctp_misc_ints(SCTP_CWNDLOG_PRESEND, stcb->asoc.total_output_queue_size, stcb->asoc.total_flight, stcb->asoc.chunks_on_out_queue, stcb->asoc.total_flight_count); #endif if (queue_only_for_init) { if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) { /* a collision took us forward? */ queue_only_for_init = 0; queue_only = 0; } else { sctp_send_initiate(inp, stcb); stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; queue_only_for_init = 0; queue_only = 1; } } if ((queue_only == 0) && (nagle_applies == 0) ) { /*- * need to start chunk output * before blocking.. note that if * a lock is already applied, then * the input via the net is happening * and I don't need to start output :-D */ if (hold_tcblock == 0) { if (SCTP_TCB_TRYLOCK(stcb)) { hold_tcblock = 1; sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); } } else { sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); } if (hold_tcblock == 1) { SCTP_TCB_UNLOCK(stcb); hold_tcblock = 0; } } SOCKBUF_LOCK(&so->so_snd); /*- * This is a bit strange, but I think it will * work. The total_output_queue_size is locked and * protected by the TCB_LOCK, which we just released. * There is a race that can occur between releasing it * above, and me getting the socket lock, where sacks * come in but we have not put the SB_WAIT on the * so_snd buffer to get the wakeup. After the LOCK * is applied the sack_processing will also need to * LOCK the so->so_snd to do the actual sowwakeup(). So * once we have the socket buffer lock if we recheck the * size we KNOW we will get to sleep safely with the * wakeup flag in place. */ if (SCTP_SB_LIMIT_SND(so) < (stcb->asoc.total_output_queue_size + sctp_add_more_threshold)) { #ifdef SCTP_BLK_LOGGING sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK, so, asoc, uio->uio_resid); #endif be.error = 0; stcb->block_entry = &be; error = sbwait(&so->so_snd); stcb->block_entry = NULL; if (error || so->so_error || be.error) { if (error == 0) { if (so->so_error) error = so->so_error; if (be.error) { error = be.error; } } SOCKBUF_UNLOCK(&so->so_snd); goto out_unlocked; } #ifdef SCTP_BLK_LOGGING sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK, so, asoc, stcb->asoc.total_output_queue_size); #endif } SOCKBUF_UNLOCK(&so->so_snd); if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { goto out_unlocked; } } SCTP_TCB_SEND_LOCK(stcb); if (sp) { if (sp->msg_is_complete == 0) { strm->last_msg_incomplete = 1; asoc->stream_locked = 1; asoc->stream_locked_on = srcv->sinfo_stream; } else { sp->sender_all_done = 1; strm->last_msg_incomplete = 0; asoc->stream_locked = 0; } } else { printf("Huh no sp TSNH?\n"); strm->last_msg_incomplete = 0; asoc->stream_locked = 0; } SCTP_TCB_SEND_UNLOCK(stcb); if (uio->uio_resid == 0) { got_all_of_the_send = 1; } } else if (top) { /* We send in a 0, since we do NOT have any locks */ error = sctp_msg_append(stcb, net, top, srcv, 0); top = NULL; } if (error) { goto out; } dataless_eof: /* EOF thing ? */ if ((srcv->sinfo_flags & SCTP_EOF) && (got_all_of_the_send == 1) && (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) ) { SCTP_STAT_INCR(sctps_sends_with_eof); error = 0; if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->stream_queue_cnt == 0)) { if (asoc->locked_on_sending) { goto abort_anyway; } /* there is nothing queued to send, so I'm done... */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* only send SHUTDOWN the first time through */ sctp_send_shutdown(stcb, stcb->asoc.primary_destination); if (SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } else { /*- * we still got (or just got) data to send, so set * SHUTDOWN_PENDING */ /*- * XXX sockets draft says that SCTP_EOF should be * sent with no data. currently, we will allow user * data to be sent first and move to * SHUTDOWN-PENDING */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } if (asoc->locked_on_sending) { /* Locked to send out the data */ struct sctp_stream_queue_pending *sp; sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead); if (sp) { if ((sp->length == 0) && (sp->msg_is_complete == 0)) asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; } } asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) { abort_anyway: if (free_cnt_applied) { atomic_add_int(&stcb->asoc.refcnt, -1); free_cnt_applied = 0; } sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, NULL); /* * now relock the stcb so everything * is sane */ hold_tcblock = 0; stcb = NULL; goto out; } sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } } skip_out_eof: if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue)) { some_on_control = 1; } if ((net->flight_size > net->cwnd) && (sctp_cmt_on_off == 0)) { queue_only = 1; } else if (asoc->ifp_had_enobuf) { SCTP_STAT_INCR(sctps_ifnomemqueued); if (net->flight_size > (net->mtu * 2)) { queue_only = 1; } else { queue_only = 0; } asoc->ifp_had_enobuf = 0; un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); } else { un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk))); if (net->flight_size > (net->mtu * stcb->asoc.max_burst)) { queue_only = 1; SCTP_STAT_INCR(sctps_send_burst_avoid); } else if (net->flight_size > net->cwnd) { queue_only = 1; SCTP_STAT_INCR(sctps_send_cwnd_avoid); } else { queue_only = 0; } } if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) && (stcb->asoc.total_flight > 0) && (un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD)) ) { /*- * Ok, Nagle is set on and we have data outstanding. * Don't send anything and let SACKs drive out the * data unless wen have a "full" segment to send. */ #ifdef SCTP_NAGLE_LOGGING sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED); #endif SCTP_STAT_INCR(sctps_naglequeued); nagle_applies = 1; } else { #ifdef SCTP_NAGLE_LOGGING if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED); #endif SCTP_STAT_INCR(sctps_naglesent); nagle_applies = 0; } if (queue_only_for_init) { if (hold_tcblock == 0) { SCTP_TCB_LOCK(stcb); hold_tcblock = 1; } if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) { /* a collision took us forward? */ queue_only_for_init = 0; queue_only = 0; } else { sctp_send_initiate(inp, stcb); if (stcb->asoc.state & SCTP_STATE_SHUTDOWN_PENDING) stcb->asoc.state = SCTP_STATE_COOKIE_WAIT | SCTP_STATE_SHUTDOWN_PENDING; else stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; queue_only_for_init = 0; queue_only = 1; } } if ((queue_only == 0) && (nagle_applies == 0) && (stcb->asoc.peers_rwnd && un_sent)) { /* we can attempt to send too. */ if (hold_tcblock == 0) { /* * If there is activity recv'ing sacks no need to * send */ if (SCTP_TCB_TRYLOCK(stcb)) { sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); hold_tcblock = 1; } } else { sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); } } else if ((queue_only == 0) && (stcb->asoc.peers_rwnd == 0) && (stcb->asoc.total_flight == 0)) { /* We get to have a probe outstanding */ if (hold_tcblock == 0) { hold_tcblock = 1; SCTP_TCB_LOCK(stcb); } sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND); } else if (some_on_control) { int num_out, reason, cwnd_full, frag_point; /* Here we do control only */ if (hold_tcblock == 0) { hold_tcblock = 1; SCTP_TCB_LOCK(stcb); } frag_point = sctp_get_frag_point(stcb, &stcb->asoc); sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out, &reason, 1, &cwnd_full, 1, &now, &now_filled, frag_point); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { printf("USR Send complete qo:%d prw:%d unsent:%d tf:%d cooq:%d toqs:%d \n", queue_only, stcb->asoc.peers_rwnd, un_sent, stcb->asoc.total_flight, stcb->asoc.chunks_on_out_queue, stcb->asoc.total_output_queue_size); } #endif out: out_unlocked: if (create_lock_applied) { SCTP_ASOC_CREATE_UNLOCK(inp); create_lock_applied = 0; } if ((stcb) && hold_tcblock) { SCTP_TCB_UNLOCK(stcb); } if (stcb && free_cnt_applied) { atomic_add_int(&stcb->asoc.refcnt, -1); } #ifdef INVARIANTS if (stcb) { if (mtx_owned(&stcb->tcb_mtx)) { panic("Leaving with tcb mtx owned?"); } if (mtx_owned(&stcb->tcb_send_mtx)) { panic("Leaving with tcb send mtx owned?"); } } #endif if (top) { sctp_m_freem(top); } if (control) { sctp_m_freem(control); } return (error); } /* * generate an AUTHentication chunk, if required */ struct mbuf * sctp_add_auth_chunk(struct mbuf *m, struct mbuf **m_end, struct sctp_auth_chunk **auth_ret, uint32_t * offset, struct sctp_tcb *stcb, uint8_t chunk) { struct mbuf *m_auth; struct sctp_auth_chunk *auth; int chunk_len; if ((m_end == NULL) || (auth_ret == NULL) || (offset == NULL) || (stcb == NULL)) return (m); /* sysctl disabled auth? */ if (sctp_auth_disable) return (m); /* peer doesn't do auth... */ if (!stcb->asoc.peer_supports_auth) { return (m); } /* does the requested chunk require auth? */ if (!sctp_auth_is_required_chunk(chunk, stcb->asoc.peer_auth_chunks)) { return (m); } m_auth = sctp_get_mbuf_for_msg(sizeof(*auth), 0, M_DONTWAIT, 1, MT_HEADER); if (m_auth == NULL) { /* no mbuf's */ return (m); } /* reserve some space if this will be the first mbuf */ if (m == NULL) SCTP_BUF_RESV_UF(m_auth, SCTP_MIN_OVERHEAD); /* fill in the AUTH chunk details */ auth = mtod(m_auth, struct sctp_auth_chunk *); bzero(auth, sizeof(*auth)); auth->ch.chunk_type = SCTP_AUTHENTICATION; auth->ch.chunk_flags = 0; chunk_len = sizeof(*auth) + sctp_get_hmac_digest_len(stcb->asoc.peer_hmac_id); auth->ch.chunk_length = htons(chunk_len); auth->hmac_id = htons(stcb->asoc.peer_hmac_id); /* key id and hmac digest will be computed and filled in upon send */ /* save the offset where the auth was inserted into the chain */ if (m != NULL) { struct mbuf *cn; *offset = 0; cn = m; while (cn) { *offset += SCTP_BUF_LEN(cn); cn = SCTP_BUF_NEXT(cn); } } else *offset = 0; /* update length and return pointer to the auth chunk */ SCTP_BUF_LEN(m_auth) = chunk_len; m = sctp_copy_mbufchain(m_auth, m, m_end, 1, chunk_len, 0); if (auth_ret != NULL) *auth_ret = auth; return (m); } diff --git a/sys/netinet/sctp_output.h b/sys/netinet/sctp_output.h index 2f9b3441c482..27033a50ab0b 100644 --- a/sys/netinet/sctp_output.h +++ b/sys/netinet/sctp_output.h @@ -1,198 +1,199 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_output.h,v 1.14 2005/03/06 16:04:18 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #ifndef __sctp_output_h__ #define __sctp_output_h__ #include #if defined(_KERNEL) struct mbuf * sctp_add_addresses_to_i_ia(struct sctp_inpcb *inp, struct sctp_scoping *scope, struct mbuf *m_at, int cnt_inits_to); int sctp_is_addr_restricted(struct sctp_tcb *, struct sctp_ifa *); int sctp_is_address_in_scope(struct sctp_ifa *ifa, int ipv4_addr_legal, int ipv6_addr_legal, int loopback_scope, int ipv4_local_scope, int local_scope, int site_scope, int do_update); int sctp_is_addr_in_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa); struct sctp_ifa * sctp_source_address_selection(struct sctp_inpcb *inp, struct sctp_tcb *stcb, sctp_route_t * ro, struct sctp_nets *net, int non_asoc_addr_ok, uint32_t vrf_id); void sctp_send_initiate(struct sctp_inpcb *, struct sctp_tcb *); void sctp_send_initiate_ack(struct sctp_inpcb *, struct sctp_tcb *, struct mbuf *, int, int, struct sctphdr *, struct sctp_init_chunk *, uint32_t, uint32_t); struct mbuf * sctp_arethere_unrecognized_parameters(struct mbuf *, int, int *, struct sctp_chunkhdr *); void sctp_queue_op_err(struct sctp_tcb *, struct mbuf *); int sctp_send_cookie_echo(struct mbuf *, int, struct sctp_tcb *, struct sctp_nets *); -int sctp_send_cookie_ack(struct sctp_tcb *); + +void sctp_send_cookie_ack(struct sctp_tcb *); void sctp_send_heartbeat_ack(struct sctp_tcb *, struct mbuf *, int, int, struct sctp_nets *); -int sctp_send_shutdown(struct sctp_tcb *, struct sctp_nets *); +void sctp_send_shutdown(struct sctp_tcb *, struct sctp_nets *); -int sctp_send_shutdown_ack(struct sctp_tcb *, struct sctp_nets *); +void sctp_send_shutdown_ack(struct sctp_tcb *, struct sctp_nets *); -int sctp_send_shutdown_complete(struct sctp_tcb *, struct sctp_nets *); +void sctp_send_shutdown_complete(struct sctp_tcb *, struct sctp_nets *); -int +void sctp_send_shutdown_complete2(struct mbuf *, int, struct sctphdr *, uint32_t, uint32_t); -int sctp_send_asconf(struct sctp_tcb *, struct sctp_nets *); +void sctp_send_asconf(struct sctp_tcb *, struct sctp_nets *); -int sctp_send_asconf_ack(struct sctp_tcb *, uint32_t); +void sctp_send_asconf_ack(struct sctp_tcb *, uint32_t); int sctp_get_frag_point(struct sctp_tcb *, struct sctp_association *); void sctp_toss_old_cookies(struct sctp_tcb *, struct sctp_association *); void sctp_toss_old_asconf(struct sctp_tcb *); void sctp_fix_ecn_echo(struct sctp_association *); int sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *, int); void sctp_insert_on_wheel(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_stream_out *strq, int holdslock); -int sctp_chunk_output(struct sctp_inpcb *, struct sctp_tcb *, int); +void sctp_chunk_output(struct sctp_inpcb *, struct sctp_tcb *, int); void sctp_send_abort_tcb(struct sctp_tcb *, struct mbuf *); void send_forward_tsn(struct sctp_tcb *, struct sctp_association *); void sctp_send_sack(struct sctp_tcb *); int sctp_send_hb(struct sctp_tcb *, int, struct sctp_nets *); void sctp_send_ecn_echo(struct sctp_tcb *, struct sctp_nets *, uint32_t); void sctp_send_packet_dropped(struct sctp_tcb *, struct sctp_nets *, struct mbuf *, int, int); void sctp_send_cwr(struct sctp_tcb *, struct sctp_nets *, uint32_t); void sctp_add_stream_reset_out(struct sctp_tmit_chunk *chk, int number_entries, uint16_t * list, uint32_t seq, uint32_t resp_seq, uint32_t last_sent); void sctp_add_stream_reset_in(struct sctp_tmit_chunk *chk, int number_entries, uint16_t * list, uint32_t seq); void sctp_add_stream_reset_tsn(struct sctp_tmit_chunk *chk, uint32_t seq); void sctp_add_stream_reset_result(struct sctp_tmit_chunk *chk, uint32_t resp_seq, uint32_t result); void sctp_add_stream_reset_result_tsn(struct sctp_tmit_chunk *chk, uint32_t resp_seq, uint32_t result, uint32_t send_una, uint32_t recv_next); int sctp_send_str_reset_req(struct sctp_tcb *stcb, int number_entries, uint16_t * list, uint8_t send_out_req, uint32_t resp_seq, uint8_t send_in_req, uint8_t send_tsn_req); void sctp_send_abort(struct mbuf *, int, struct sctphdr *, uint32_t, struct mbuf *, uint32_t, uint32_t); void sctp_send_operr_to(struct mbuf *, int, struct mbuf *, uint32_t, uint32_t, uint32_t); int sctp_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *p ); #endif #endif diff --git a/sys/netinet/sctp_pcb.c b/sys/netinet/sctp_pcb.c index 75af8b30e19d..c43342bfdb93 100644 --- a/sys/netinet/sctp_pcb.c +++ b/sys/netinet/sctp_pcb.c @@ -1,5676 +1,5681 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_pcb.c,v 1.38 2005/03/06 16:04:18 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include struct sctp_epinfo sctppcbinfo; /* FIX: we don't handle multiple link local scopes */ /* "scopeless" replacement IN6_ARE_ADDR_EQUAL */ int SCTP6_ARE_ADDR_EQUAL(struct in6_addr *a, struct in6_addr *b) { struct in6_addr tmp_a, tmp_b; /* use a copy of a and b */ tmp_a = *a; tmp_b = *b; in6_clearscope(&tmp_a); in6_clearscope(&tmp_b); return (IN6_ARE_ADDR_EQUAL(&tmp_a, &tmp_b)); } void sctp_fill_pcbinfo(struct sctp_pcbinfo *spcb) { /* * We really don't need to lock this, but I will just because it * does not hurt. */ SCTP_INP_INFO_RLOCK(); spcb->ep_count = sctppcbinfo.ipi_count_ep; spcb->asoc_count = sctppcbinfo.ipi_count_asoc; spcb->laddr_count = sctppcbinfo.ipi_count_laddr; spcb->raddr_count = sctppcbinfo.ipi_count_raddr; spcb->chk_count = sctppcbinfo.ipi_count_chunk; spcb->readq_count = sctppcbinfo.ipi_count_readq; spcb->stream_oque = sctppcbinfo.ipi_count_strmoq; spcb->free_chunks = sctppcbinfo.ipi_free_chunks; SCTP_INP_INFO_RUNLOCK(); } /* * Addresses are added to VRF's (Virtual Router's). For BSD we * have only the default VRF 0. We maintain a hash list of * VRF's. Each VRF has its own list of sctp_ifn's. Each of * these has a list of addresses. When we add a new address * to a VRF we lookup the ifn/ifn_index, if the ifn does * not exist we create it and add it to the list of IFN's * within the VRF. Once we have the sctp_ifn, we add the * address to the list. So we look something like: * * hash-vrf-table * vrf-> ifn-> ifn -> ifn * vrf | * ... +--ifa-> ifa -> ifa * vrf * * We keep these seperate lists since the SCTP subsystem will * point to these from its source address selection nets structure. * When an address is deleted it does not happen right away on * the SCTP side, it gets scheduled. What we do when a * delete happens is immediately remove the address from * the master list and decrement the refcount. As our * addip iterator works through and frees the src address * selection pointing to the sctp_ifa, eventually the refcount * will reach 0 and we will delete it. Note that it is assumed * that any locking on system level ifn/ifa is done at the * caller of these functions and these routines will only * lock the SCTP structures as they add or delete things. * * Other notes on VRF concepts. * - An endpoint can be in multiple VRF's * - An association lives within a VRF and only one VRF. * - Any incoming packet we can deduce the VRF for by * looking at the mbuf/pak inbound (for BSD its VRF=0 :D) * - Any downward send call or connect call must supply the * VRF via ancillary data or via some sort of set default * VRF socket option call (again for BSD no brainer since * the VRF is always 0). * - An endpoint may add multiple VRF's to it. * - Listening sockets can accept associations in any * of the VRF's they are in but the assoc will end up * in only one VRF (gotten from the packet or connect/send). * */ struct sctp_vrf * sctp_allocate_vrf(int vrf_id) { struct sctp_vrf *vrf = NULL; struct sctp_vrflist *bucket; /* First allocate the VRF structure */ vrf = sctp_find_vrf(vrf_id); if (vrf) { /* Already allocated */ return (vrf); } SCTP_MALLOC(vrf, struct sctp_vrf *, sizeof(struct sctp_vrf), "SCTP_VRF"); if (vrf == NULL) { /* No memory */ #ifdef INVARIANTS panic("No memory for VRF:%d", vrf_id); #endif return (NULL); } /* setup the VRF */ memset(vrf, 0, sizeof(struct sctp_vrf)); vrf->vrf_id = vrf_id; LIST_INIT(&vrf->ifnlist); vrf->total_ifa_count = 0; /* Init the HASH of addresses */ vrf->vrf_addr_hash = SCTP_HASH_INIT(SCTP_VRF_ADDR_HASH_SIZE, &vrf->vrf_addr_hashmark); if (vrf->vrf_addr_hash == NULL) { /* No memory */ #ifdef INVARIANTS panic("No memory for VRF:%d", vrf_id); #endif + SCTP_FREE(vrf); return (NULL); } vrf->vrf_ifn_hash = SCTP_HASH_INIT(SCTP_VRF_IFN_HASH_SIZE, &vrf->vrf_ifn_hashmark); if (vrf->vrf_ifn_hash == NULL) { /* No memory */ #ifdef INVARIANTS panic("No memory for VRF:%d", vrf_id); #endif + SCTP_HASH_FREE(vrf->vrf_addr_hash, vrf->vrf_addr_hashmark); + SCTP_FREE(vrf); return (NULL); } /* Add it to the hash table */ bucket = &sctppcbinfo.sctp_vrfhash[(vrf_id & sctppcbinfo.hashvrfmark)]; LIST_INSERT_HEAD(bucket, vrf, next_vrf); atomic_add_int(&sctppcbinfo.ipi_count_vrfs, 1); return (vrf); } struct sctp_ifn * sctp_find_ifn(struct sctp_vrf *vrf, void *ifn, uint32_t ifn_index) { struct sctp_ifn *sctp_ifnp; struct sctp_ifnlist *hash_ifn_head; /* * We assume the lock is held for the addresses if thats wrong * problems could occur :-) */ hash_ifn_head = &vrf->vrf_ifn_hash[(ifn_index & vrf->vrf_ifn_hashmark)]; LIST_FOREACH(sctp_ifnp, hash_ifn_head, next_bucket) { if (sctp_ifnp->ifn_index == ifn_index) { return (sctp_ifnp); } if (sctp_ifnp->ifn_p && ifn && (sctp_ifnp->ifn_p == ifn)) { return (sctp_ifnp); } } return (NULL); } struct sctp_vrf * sctp_find_vrf(uint32_t vrf_id) { struct sctp_vrflist *bucket; struct sctp_vrf *liste; bucket = &sctppcbinfo.sctp_vrfhash[(vrf_id & sctppcbinfo.hashvrfmark)]; LIST_FOREACH(liste, bucket, next_vrf) { if (vrf_id == liste->vrf_id) { return (liste); } } return (NULL); } void sctp_free_ifn(struct sctp_ifn *sctp_ifnp) { int ret; ret = atomic_fetchadd_int(&sctp_ifnp->refcount, -1); if (ret == 1) { /* We zero'd the count */ SCTP_FREE(sctp_ifnp); atomic_subtract_int(&sctppcbinfo.ipi_count_ifns, 1); } } void sctp_update_ifn_mtu(uint32_t vrf_id, uint32_t ifn_index, uint32_t mtu) { struct sctp_ifn *sctp_ifnp; struct sctp_vrf *vrf; vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) return; sctp_ifnp = sctp_find_ifn(vrf, (void *)NULL, ifn_index); if (sctp_ifnp != NULL) { sctp_ifnp->ifn_mtu = mtu; } } void sctp_free_ifa(struct sctp_ifa *sctp_ifap) { int ret; ret = atomic_fetchadd_int(&sctp_ifap->refcount, -1); if (ret == 1) { /* We zero'd the count */ SCTP_FREE(sctp_ifap); atomic_subtract_int(&sctppcbinfo.ipi_count_ifas, 1); } } static void sctp_delete_ifn(struct sctp_ifn *sctp_ifnp, int hold_addr_lock) { struct sctp_ifn *found; found = sctp_find_ifn(sctp_ifnp->vrf, sctp_ifnp->ifn_p, sctp_ifnp->ifn_index); if (found == NULL) { /* Not in the list.. sorry */ return; } if (hold_addr_lock == 0) SCTP_IPI_ADDR_LOCK(); LIST_REMOVE(sctp_ifnp, next_bucket); LIST_REMOVE(sctp_ifnp, next_ifn); if (hold_addr_lock == 0) SCTP_IPI_ADDR_UNLOCK(); /* Take away the reference, and possibly free it */ sctp_free_ifn(sctp_ifnp); } struct sctp_ifa * sctp_add_addr_to_vrf(uint32_t vrf_id, void *ifn, uint32_t ifn_index, uint32_t ifn_type, const char *if_name, void *ifa, struct sockaddr *addr, uint32_t ifa_flags, int dynamic_add) { struct sctp_vrf *vrf; struct sctp_ifn *sctp_ifnp = NULL; struct sctp_ifa *sctp_ifap = NULL; struct sctp_ifalist *hash_addr_head; struct sctp_ifnlist *hash_ifn_head; uint32_t hash_of_addr; /* How granular do we need the locks to be here? */ SCTP_IPI_ADDR_LOCK(); vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { vrf = sctp_allocate_vrf(vrf_id); if (vrf == NULL) { SCTP_IPI_ADDR_UNLOCK(); return (NULL); } } sctp_ifnp = sctp_find_ifn(vrf, ifn, ifn_index); if (sctp_ifnp == NULL) { /* * build one and add it, can't hold lock until after malloc * done though. */ SCTP_IPI_ADDR_UNLOCK(); SCTP_MALLOC(sctp_ifnp, struct sctp_ifn *, sizeof(struct sctp_ifn), "SCTP_IFN"); if (sctp_ifnp == NULL) { #ifdef INVARIANTS panic("No memory for IFN:%u", sctp_ifnp->ifn_index); #endif return (NULL); } sctp_ifnp->ifn_index = ifn_index; sctp_ifnp->ifn_p = ifn; sctp_ifnp->ifn_type = ifn_type; sctp_ifnp->ifa_count = 0; sctp_ifnp->refcount = 1; sctp_ifnp->vrf = vrf; sctp_ifnp->ifn_mtu = SCTP_GATHER_MTU_FROM_IFN_INFO(ifn, ifn_index); if (if_name != NULL) { memcpy(sctp_ifnp->ifn_name, if_name, SCTP_IFNAMSIZ); } else { memcpy(sctp_ifnp->ifn_name, "unknown", min(7, SCTP_IFNAMSIZ)); } hash_ifn_head = &vrf->vrf_ifn_hash[(ifn_index & vrf->vrf_ifn_hashmark)]; LIST_INIT(&sctp_ifnp->ifalist); SCTP_IPI_ADDR_LOCK(); LIST_INSERT_HEAD(hash_ifn_head, sctp_ifnp, next_bucket); LIST_INSERT_HEAD(&vrf->ifnlist, sctp_ifnp, next_ifn); atomic_add_int(&sctppcbinfo.ipi_count_ifns, 1); } sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, 1); if (sctp_ifap) { /* Hmm, it already exists? */ if ((sctp_ifap->ifn_p) && (sctp_ifap->ifn_p->ifn_index == ifn_index)) { if (sctp_ifap->localifa_flags & SCTP_BEING_DELETED) { /* easy to solve, just switch back to active */ sctp_ifap->localifa_flags = SCTP_ADDR_VALID; sctp_ifap->ifn_p = sctp_ifnp; exit_stage_left: SCTP_IPI_ADDR_UNLOCK(); return (sctp_ifap); } else { goto exit_stage_left; } } else { if (sctp_ifap->ifn_p) { /* * The first IFN gets the address, * duplicates are ignored. */ goto exit_stage_left; } else { /* repair ifnp which was NULL ? */ sctp_ifap->localifa_flags = SCTP_ADDR_VALID; sctp_ifap->ifn_p = sctp_ifnp; atomic_add_int(&sctp_ifap->ifn_p->refcount, 1); } goto exit_stage_left; } } SCTP_IPI_ADDR_UNLOCK(); SCTP_MALLOC(sctp_ifap, struct sctp_ifa *, sizeof(struct sctp_ifa), "SCTP_IFA"); if (sctp_ifap == NULL) { #ifdef INVARIANTS panic("No memory for IFA"); #endif return (NULL); } memset(sctp_ifap, 0, sizeof(struct sctp_ifa)); sctp_ifap->ifn_p = sctp_ifnp; atomic_add_int(&sctp_ifnp->refcount, 1); sctp_ifap->ifa = ifa; memcpy(&sctp_ifap->address, addr, addr->sa_len); sctp_ifap->localifa_flags = SCTP_ADDR_VALID | SCTP_ADDR_DEFER_USE; sctp_ifap->flags = ifa_flags; /* Set scope */ if (sctp_ifap->address.sa.sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)&sctp_ifap->address.sin; if (SCTP_IFN_IS_IFT_LOOP(sctp_ifap->ifn_p) || (IN4_ISLOOPBACK_ADDRESS(&sin->sin_addr))) { sctp_ifap->src_is_loop = 1; } if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { sctp_ifap->src_is_priv = 1; } } else if (sctp_ifap->address.sa.sa_family == AF_INET6) { /* ok to use deprecated addresses? */ struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&sctp_ifap->address.sin6; if (SCTP_IFN_IS_IFT_LOOP(sctp_ifap->ifn_p) || (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))) { sctp_ifap->src_is_loop = 1; } if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { sctp_ifap->src_is_priv = 1; } } hash_of_addr = sctp_get_ifa_hash_val(&sctp_ifap->address.sa); if ((sctp_ifap->src_is_priv == 0) && (sctp_ifap->src_is_loop == 0)) { sctp_ifap->src_is_glob = 1; } SCTP_IPI_ADDR_LOCK(); hash_addr_head = &vrf->vrf_addr_hash[(hash_of_addr & vrf->vrf_addr_hashmark)]; LIST_INSERT_HEAD(hash_addr_head, sctp_ifap, next_bucket); sctp_ifap->refcount = 1; LIST_INSERT_HEAD(&sctp_ifnp->ifalist, sctp_ifap, next_ifa); sctp_ifnp->ifa_count++; vrf->total_ifa_count++; atomic_add_int(&sctppcbinfo.ipi_count_ifas, 1); SCTP_IPI_ADDR_UNLOCK(); if (dynamic_add) { /* * Bump up the refcount so that when the timer completes it * will drop back down. */ struct sctp_laddr *wi; atomic_add_int(&sctp_ifap->refcount, 1); wi = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr, struct sctp_laddr); if (wi == NULL) { /* * Gak, what can we do? We have lost an address * change can you say HOSED? */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Lost and address change ???\n"); } #endif /* SCTP_DEBUG */ /* Opps, must decrement the count */ sctp_del_addr_from_vrf(vrf_id, addr, ifn_index); return (NULL); } SCTP_INCR_LADDR_COUNT(); bzero(wi, sizeof(*wi)); wi->ifa = sctp_ifap; wi->action = SCTP_ADD_IP_ADDRESS; SCTP_IPI_ITERATOR_WQ_LOCK(); /* * Should this really be a tailq? As it is we will process * the newest first :-0 */ LIST_INSERT_HEAD(&sctppcbinfo.addr_wq, wi, sctp_nxt_addr); sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ, (struct sctp_inpcb *)NULL, (struct sctp_tcb *)NULL, (struct sctp_nets *)NULL); SCTP_IPI_ITERATOR_WQ_UNLOCK(); } else { /* it's ready for use */ sctp_ifap->localifa_flags &= ~SCTP_ADDR_DEFER_USE; } return (sctp_ifap); } void sctp_del_addr_from_vrf(uint32_t vrf_id, struct sockaddr *addr, uint32_t ifn_index) { struct sctp_vrf *vrf; struct sctp_ifa *sctp_ifap = NULL; SCTP_IPI_ADDR_LOCK(); vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { #ifdef SCTP_DEBUG printf("Can't find vrf_id:%d\n", vrf_id); #endif goto out_now; } sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, 1); if (sctp_ifap) { sctp_ifap->localifa_flags &= SCTP_ADDR_VALID; sctp_ifap->localifa_flags |= SCTP_BEING_DELETED; - sctp_ifap->ifn_p->ifa_count--; vrf->total_ifa_count--; LIST_REMOVE(sctp_ifap, next_bucket); LIST_REMOVE(sctp_ifap, next_ifa); if (sctp_ifap->ifn_p) { + sctp_ifap->ifn_p->ifa_count--; if (SCTP_LIST_EMPTY(&sctp_ifap->ifn_p->ifalist)) { sctp_delete_ifn(sctp_ifap->ifn_p, 1); } sctp_free_ifn(sctp_ifap->ifn_p); sctp_ifap->ifn_p = NULL; } } #ifdef SCTP_DEBUG else { printf("Del Addr-ifn:%d Could not find address:", ifn_index); sctp_print_address(addr); } #endif out_now: SCTP_IPI_ADDR_UNLOCK(); if (sctp_ifap) { struct sctp_laddr *wi; wi = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr, struct sctp_laddr); if (wi == NULL) { /* * Gak, what can we do? We have lost an address * change can you say HOSED? */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Lost and address change ???\n"); } #endif /* SCTP_DEBUG */ /* Opps, must decrement the count */ sctp_free_ifa(sctp_ifap); return; } SCTP_INCR_LADDR_COUNT(); bzero(wi, sizeof(*wi)); wi->ifa = sctp_ifap; wi->action = SCTP_DEL_IP_ADDRESS; SCTP_IPI_ITERATOR_WQ_LOCK(); /* * Should this really be a tailq? As it is we will process * the newest first :-0 */ LIST_INSERT_HEAD(&sctppcbinfo.addr_wq, wi, sctp_nxt_addr); sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ, (struct sctp_inpcb *)NULL, (struct sctp_tcb *)NULL, (struct sctp_nets *)NULL); SCTP_IPI_ITERATOR_WQ_UNLOCK(); } return; } static struct sctp_tcb * sctp_tcb_special_locate(struct sctp_inpcb **inp_p, struct sockaddr *from, struct sockaddr *to, struct sctp_nets **netp, uint32_t vrf_id) { /**** ASSUMSES THE CALLER holds the INP_INFO_RLOCK */ /* * If we support the TCP model, then we must now dig through to see * if we can find our endpoint in the list of tcp ep's. */ uint16_t lport, rport; struct sctppcbhead *ephead; struct sctp_inpcb *inp; struct sctp_laddr *laddr; struct sctp_tcb *stcb; struct sctp_nets *net; if ((to == NULL) || (from == NULL)) { return (NULL); } if (to->sa_family == AF_INET && from->sa_family == AF_INET) { lport = ((struct sockaddr_in *)to)->sin_port; rport = ((struct sockaddr_in *)from)->sin_port; } else if (to->sa_family == AF_INET6 && from->sa_family == AF_INET6) { lport = ((struct sockaddr_in6 *)to)->sin6_port; rport = ((struct sockaddr_in6 *)from)->sin6_port; } else { return NULL; } ephead = &sctppcbinfo.sctp_tcpephash[SCTP_PCBHASH_ALLADDR( (lport + rport), sctppcbinfo.hashtcpmark)]; /* * Ok now for each of the guys in this bucket we must look and see: * - Does the remote port match. - Does there single association's * addresses match this address (to). If so we update p_ep to point * to this ep and return the tcb from it. */ LIST_FOREACH(inp, ephead, sctp_hash) { SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(inp); continue; } if (lport != inp->sctp_lport) { SCTP_INP_RUNLOCK(inp); continue; } if (inp->def_vrf_id != vrf_id) { SCTP_INP_RUNLOCK(inp); continue; } /* check to see if the ep has one of the addresses */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { /* We are NOT bound all, so look further */ int match = 0; LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("An ounce of prevention is worth a pound of cure\n"); } #endif continue; } if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("ifa being deleted\n"); } #endif continue; } if (laddr->ifa->address.sa.sa_family == to->sa_family) { /* see if it matches */ struct sockaddr_in *intf_addr, *sin; intf_addr = &laddr->ifa->address.sin; sin = (struct sockaddr_in *)to; if (from->sa_family == AF_INET) { if (sin->sin_addr.s_addr == intf_addr->sin_addr.s_addr) { match = 1; break; } } else { struct sockaddr_in6 *intf_addr6; struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *) to; intf_addr6 = &laddr->ifa->address.sin6; if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &intf_addr6->sin6_addr)) { match = 1; break; } } } } if (match == 0) { /* This endpoint does not have this address */ SCTP_INP_RUNLOCK(inp); continue; } } /* * Ok if we hit here the ep has the address, does it hold * the tcb? */ stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { SCTP_INP_RUNLOCK(inp); continue; } SCTP_TCB_LOCK(stcb); if (stcb->rport != rport) { /* remote port does not match. */ SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); continue; } /* Does this TCB have a matching address? */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->ro._l_addr.sa.sa_family != from->sa_family) { /* not the same family, can't be a match */ continue; } if (from->sa_family == AF_INET) { struct sockaddr_in *sin, *rsin; sin = (struct sockaddr_in *)&net->ro._l_addr; rsin = (struct sockaddr_in *)from; if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) { *netp = net; } /* Update the endpoint pointer */ *inp_p = inp; SCTP_INP_RUNLOCK(inp); return (stcb); } } else { struct sockaddr_in6 *sin6, *rsin6; sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; rsin6 = (struct sockaddr_in6 *)from; if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &rsin6->sin6_addr)) { /* found it */ if (netp != NULL) { *netp = net; } /* Update the endpoint pointer */ *inp_p = inp; SCTP_INP_RUNLOCK(inp); return (stcb); } } } SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); } return (NULL); } /* * rules for use * * 1) If I return a NULL you must decrement any INP ref cnt. 2) If I find an * stcb, both will be locked (locked_tcb and stcb) but decrement will be done * (if locked == NULL). 3) Decrement happens on return ONLY if locked == * NULL. */ struct sctp_tcb * sctp_findassociation_ep_addr(struct sctp_inpcb **inp_p, struct sockaddr *remote, struct sctp_nets **netp, struct sockaddr *local, struct sctp_tcb *locked_tcb) { struct sctpasochead *head; struct sctp_inpcb *inp; struct sctp_tcb *stcb = NULL; struct sctp_nets *net; uint16_t rport; inp = *inp_p; if (remote->sa_family == AF_INET) { rport = (((struct sockaddr_in *)remote)->sin_port); } else if (remote->sa_family == AF_INET6) { rport = (((struct sockaddr_in6 *)remote)->sin6_port); } else { return (NULL); } if (locked_tcb) { /* * UN-lock so we can do proper locking here this occurs when * called from load_addresses_from_init. */ SCTP_TCB_UNLOCK(locked_tcb); } SCTP_INP_INFO_RLOCK(); if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { /*- * Now either this guy is our listener or it's the * connector. If it is the one that issued the connect, then * it's only chance is to be the first TCB in the list. If * it is the acceptor, then do the special_lookup to hash * and find the real inp. */ if ((inp->sctp_socket) && (inp->sctp_socket->so_qlimit)) { /* to is peer addr, from is my addr */ stcb = sctp_tcb_special_locate(inp_p, remote, local, netp, inp->def_vrf_id); if ((stcb != NULL) && (locked_tcb == NULL)) { /* we have a locked tcb, lower refcount */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } if ((locked_tcb != NULL) && (locked_tcb != stcb)) { SCTP_INP_RLOCK(locked_tcb->sctp_ep); SCTP_TCB_LOCK(locked_tcb); SCTP_INP_RUNLOCK(locked_tcb->sctp_ep); } SCTP_INP_INFO_RUNLOCK(); return (stcb); } else { SCTP_INP_WLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { goto null_return; } stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { goto null_return; } SCTP_TCB_LOCK(stcb); if (stcb->rport != rport) { /* remote port does not match. */ SCTP_TCB_UNLOCK(stcb); goto null_return; } /* now look at the list of remote addresses */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { #ifdef INVARIANTS if (net == (TAILQ_NEXT(net, sctp_next))) { panic("Corrupt net list"); } #endif if (net->ro._l_addr.sa.sa_family != remote->sa_family) { /* not the same family */ continue; } if (remote->sa_family == AF_INET) { struct sockaddr_in *sin, *rsin; sin = (struct sockaddr_in *) &net->ro._l_addr; rsin = (struct sockaddr_in *)remote; if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) { *netp = net; } if (locked_tcb == NULL) { SCTP_INP_DECR_REF(inp); } else if (locked_tcb != stcb) { SCTP_TCB_LOCK(locked_tcb); } SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_RUNLOCK(); return (stcb); } } else if (remote->sa_family == AF_INET6) { struct sockaddr_in6 *sin6, *rsin6; sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; rsin6 = (struct sockaddr_in6 *)remote; if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &rsin6->sin6_addr)) { /* found it */ if (netp != NULL) { *netp = net; } if (locked_tcb == NULL) { SCTP_INP_DECR_REF(inp); } else if (locked_tcb != stcb) { SCTP_TCB_LOCK(locked_tcb); } SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_RUNLOCK(); return (stcb); } } } SCTP_TCB_UNLOCK(stcb); } } else { SCTP_INP_WLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { goto null_return; } head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(rport, inp->sctp_hashmark)]; if (head == NULL) { goto null_return; } LIST_FOREACH(stcb, head, sctp_tcbhash) { if (stcb->rport != rport) { /* remote port does not match */ continue; } /* now look at the list of remote addresses */ SCTP_TCB_LOCK(stcb); TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { #ifdef INVARIANTS if (net == (TAILQ_NEXT(net, sctp_next))) { panic("Corrupt net list"); } #endif if (net->ro._l_addr.sa.sa_family != remote->sa_family) { /* not the same family */ continue; } if (remote->sa_family == AF_INET) { struct sockaddr_in *sin, *rsin; sin = (struct sockaddr_in *) &net->ro._l_addr; rsin = (struct sockaddr_in *)remote; if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) { *netp = net; } if (locked_tcb == NULL) { SCTP_INP_DECR_REF(inp); } else if (locked_tcb != stcb) { SCTP_TCB_LOCK(locked_tcb); } SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_RUNLOCK(); return (stcb); } } else if (remote->sa_family == AF_INET6) { struct sockaddr_in6 *sin6, *rsin6; sin6 = (struct sockaddr_in6 *) &net->ro._l_addr; rsin6 = (struct sockaddr_in6 *)remote; if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &rsin6->sin6_addr)) { /* found it */ if (netp != NULL) { *netp = net; } if (locked_tcb == NULL) { SCTP_INP_DECR_REF(inp); } else if (locked_tcb != stcb) { SCTP_TCB_LOCK(locked_tcb); } SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_RUNLOCK(); return (stcb); } } } SCTP_TCB_UNLOCK(stcb); } } null_return: /* clean up for returning null */ if (locked_tcb) { SCTP_TCB_LOCK(locked_tcb); } SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_RUNLOCK(); /* not found */ return (NULL); } /* * Find an association for a specific endpoint using the association id given * out in the COMM_UP notification */ struct sctp_tcb * sctp_findassociation_ep_asocid(struct sctp_inpcb *inp, sctp_assoc_t asoc_id, int want_lock) { /* * Use my the assoc_id to find a endpoint */ struct sctpasochead *head; struct sctp_tcb *stcb; uint32_t id; if (asoc_id == 0 || inp == NULL) { return (NULL); } SCTP_INP_INFO_RLOCK(); id = (uint32_t) asoc_id; head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(id, sctppcbinfo.hashasocmark)]; if (head == NULL) { /* invalid id TSNH */ SCTP_INP_INFO_RUNLOCK(); return (NULL); } LIST_FOREACH(stcb, head, sctp_asocs) { SCTP_INP_RLOCK(stcb->sctp_ep); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(stcb->sctp_ep); SCTP_INP_INFO_RUNLOCK(); return (NULL); } if (stcb->asoc.assoc_id == id) { /* candidate */ if (inp != stcb->sctp_ep) { /* * some other guy has the same id active (id * collision ??). */ SCTP_INP_RUNLOCK(stcb->sctp_ep); continue; } if (want_lock) { SCTP_TCB_LOCK(stcb); } SCTP_INP_RUNLOCK(stcb->sctp_ep); SCTP_INP_INFO_RUNLOCK(); return (stcb); } SCTP_INP_RUNLOCK(stcb->sctp_ep); } /* Ok if we missed here, lets try the restart hash */ head = &sctppcbinfo.sctp_restarthash[SCTP_PCBHASH_ASOC(id, sctppcbinfo.hashrestartmark)]; if (head == NULL) { /* invalid id TSNH */ SCTP_INP_INFO_RUNLOCK(); return (NULL); } LIST_FOREACH(stcb, head, sctp_tcbrestarhash) { SCTP_INP_RLOCK(stcb->sctp_ep); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(stcb->sctp_ep); SCTP_INP_INFO_RUNLOCK(); return (NULL); } SCTP_TCB_LOCK(stcb); SCTP_INP_RUNLOCK(stcb->sctp_ep); if (stcb->asoc.assoc_id == id) { /* candidate */ if (inp != stcb->sctp_ep) { /* * some other guy has the same id active (id * collision ??). */ SCTP_TCB_UNLOCK(stcb); continue; } SCTP_INP_INFO_RUNLOCK(); return (stcb); } SCTP_TCB_UNLOCK(stcb); } SCTP_INP_INFO_RUNLOCK(); return (NULL); } static struct sctp_inpcb * sctp_endpoint_probe(struct sockaddr *nam, struct sctppcbhead *head, uint16_t lport, uint32_t vrf_id) { struct sctp_inpcb *inp; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sctp_laddr *laddr; int fnd; /* * Endpoing probe expects that the INP_INFO is locked. */ if (nam->sa_family == AF_INET) { sin = (struct sockaddr_in *)nam; sin6 = NULL; } else if (nam->sa_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)nam; sin = NULL; } else { /* unsupported family */ return (NULL); } if (head == NULL) return (NULL); LIST_FOREACH(inp, head, sctp_hash) { SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(inp); continue; } if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) && (inp->sctp_lport == lport)) { /* got it */ if ((nam->sa_family == AF_INET) && (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && SCTP_IPV6_V6ONLY(inp)) { /* IPv4 on a IPv6 socket with ONLY IPv6 set */ SCTP_INP_RUNLOCK(inp); continue; } /* A V6 address and the endpoint is NOT bound V6 */ if (nam->sa_family == AF_INET6 && (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) { SCTP_INP_RUNLOCK(inp); continue; } /* does a VRF id match? */ fnd = 0; if (inp->def_vrf_id == vrf_id) fnd = 1; SCTP_INP_RUNLOCK(inp); if (!fnd) continue; return (inp); } SCTP_INP_RUNLOCK(inp); } if ((nam->sa_family == AF_INET) && (sin->sin_addr.s_addr == INADDR_ANY)) { /* Can't hunt for one that has no address specified */ return (NULL); } else if ((nam->sa_family == AF_INET6) && (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) { /* Can't hunt for one that has no address specified */ return (NULL); } /* * ok, not bound to all so see if we can find a EP bound to this * address. */ LIST_FOREACH(inp, head, sctp_hash) { SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(inp); continue; } if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)) { SCTP_INP_RUNLOCK(inp); continue; } /* * Ok this could be a likely candidate, look at all of its * addresses */ if (inp->sctp_lport != lport) { SCTP_INP_RUNLOCK(inp); continue; } /* does a VRF id match? */ fnd = 0; if (inp->def_vrf_id == vrf_id) fnd = 1; if (!fnd) { SCTP_INP_RUNLOCK(inp); continue; } LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("An ounce of prevention is worth a pound of cure\n"); } #endif continue; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Ok laddr->ifa:%p is possible, ", laddr->ifa); } #endif if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Huh IFA being deleted\n"); } #endif continue; } if (laddr->ifa->address.sa.sa_family == nam->sa_family) { /* possible, see if it matches */ struct sockaddr_in *intf_addr; intf_addr = &laddr->ifa->address.sin; if (nam->sa_family == AF_INET) { if (sin->sin_addr.s_addr == intf_addr->sin_addr.s_addr) { SCTP_INP_RUNLOCK(inp); return (inp); } } else if (nam->sa_family == AF_INET6) { struct sockaddr_in6 *intf_addr6; intf_addr6 = &laddr->ifa->address.sin6; if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &intf_addr6->sin6_addr)) { SCTP_INP_RUNLOCK(inp); return (inp); } } } } SCTP_INP_RUNLOCK(inp); } return (NULL); } struct sctp_inpcb * sctp_pcb_findep(struct sockaddr *nam, int find_tcp_pool, int have_lock, uint32_t vrf_id) { /* * First we check the hash table to see if someone has this port * bound with just the port. */ struct sctp_inpcb *inp; struct sctppcbhead *head; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int lport; if (nam->sa_family == AF_INET) { sin = (struct sockaddr_in *)nam; lport = ((struct sockaddr_in *)nam)->sin_port; } else if (nam->sa_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)nam; lport = ((struct sockaddr_in6 *)nam)->sin6_port; } else { /* unsupported family */ return (NULL); } /* * I could cheat here and just cast to one of the types but we will * do it right. It also provides the check against an Unsupported * type too. */ /* Find the head of the ALLADDR chain */ if (have_lock == 0) { SCTP_INP_INFO_RLOCK(); } head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, sctppcbinfo.hashmark)]; inp = sctp_endpoint_probe(nam, head, lport, vrf_id); /* * If the TCP model exists it could be that the main listening * endpoint is gone but there exists a connected socket for this guy * yet. If so we can return the first one that we find. This may NOT * be the correct one but the sctp_findassociation_ep_addr has * further code to look at all TCP models. */ if (inp == NULL && find_tcp_pool) { unsigned int i; for (i = 0; i < sctppcbinfo.hashtblsize; i++) { /* * This is real gross, but we do NOT have a remote * port at this point depending on who is calling. * We must therefore look for ANY one that matches * our local port :/ */ head = &sctppcbinfo.sctp_tcpephash[i]; if (LIST_FIRST(head)) { inp = sctp_endpoint_probe(nam, head, lport, vrf_id); if (inp) { /* Found one */ break; } } } } if (inp) { SCTP_INP_INCR_REF(inp); } if (have_lock == 0) { SCTP_INP_INFO_RUNLOCK(); } return (inp); } /* * Find an association for an endpoint with the pointer to whom you want to * send to and the endpoint pointer. The address can be IPv4 or IPv6. We may * need to change the *to to some other struct like a mbuf... */ struct sctp_tcb * sctp_findassociation_addr_sa(struct sockaddr *to, struct sockaddr *from, struct sctp_inpcb **inp_p, struct sctp_nets **netp, int find_tcp_pool, uint32_t vrf_id) { struct sctp_inpcb *inp = NULL; struct sctp_tcb *retval; SCTP_INP_INFO_RLOCK(); if (find_tcp_pool) { if (inp_p != NULL) { retval = sctp_tcb_special_locate(inp_p, from, to, netp, vrf_id); } else { retval = sctp_tcb_special_locate(&inp, from, to, netp, vrf_id); } if (retval != NULL) { SCTP_INP_INFO_RUNLOCK(); return (retval); } } inp = sctp_pcb_findep(to, 0, 1, vrf_id); if (inp_p != NULL) { *inp_p = inp; } SCTP_INP_INFO_RUNLOCK(); if (inp == NULL) { return (NULL); } /* * ok, we have an endpoint, now lets find the assoc for it (if any) * we now place the source address or from in the to of the find * endpoint call. Since in reality this chain is used from the * inbound packet side. */ if (inp_p != NULL) { retval = sctp_findassociation_ep_addr(inp_p, from, netp, to, NULL); } else { retval = sctp_findassociation_ep_addr(&inp, from, netp, to, NULL); } return retval; } /* * This routine will grub through the mbuf that is a INIT or INIT-ACK and * find all addresses that the sender has specified in any address list. Each * address will be used to lookup the TCB and see if one exits. */ static struct sctp_tcb * sctp_findassociation_special_addr(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp, struct sockaddr *dest) { struct sockaddr_in sin4; struct sockaddr_in6 sin6; struct sctp_paramhdr *phdr, parm_buf; struct sctp_tcb *retval; uint32_t ptype, plen; memset(&sin4, 0, sizeof(sin4)); memset(&sin6, 0, sizeof(sin6)); sin4.sin_len = sizeof(sin4); sin4.sin_family = AF_INET; sin4.sin_port = sh->src_port; sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; sin6.sin6_port = sh->src_port; retval = NULL; offset += sizeof(struct sctp_init_chunk); phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); while (phdr != NULL) { /* now we must see if we want the parameter */ ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); if (plen == 0) { break; } if (ptype == SCTP_IPV4_ADDRESS && plen == sizeof(struct sctp_ipv4addr_param)) { /* Get the rest of the address */ struct sctp_ipv4addr_param ip4_parm, *p4; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&ip4_parm, min(plen, sizeof(ip4_parm))); if (phdr == NULL) { return (NULL); } p4 = (struct sctp_ipv4addr_param *)phdr; memcpy(&sin4.sin_addr, &p4->addr, sizeof(p4->addr)); /* look it up */ retval = sctp_findassociation_ep_addr(inp_p, (struct sockaddr *)&sin4, netp, dest, NULL); if (retval != NULL) { return (retval); } } else if (ptype == SCTP_IPV6_ADDRESS && plen == sizeof(struct sctp_ipv6addr_param)) { /* Get the rest of the address */ struct sctp_ipv6addr_param ip6_parm, *p6; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&ip6_parm, min(plen, sizeof(ip6_parm))); if (phdr == NULL) { return (NULL); } p6 = (struct sctp_ipv6addr_param *)phdr; memcpy(&sin6.sin6_addr, &p6->addr, sizeof(p6->addr)); /* look it up */ retval = sctp_findassociation_ep_addr(inp_p, (struct sockaddr *)&sin6, netp, dest, NULL); if (retval != NULL) { return (retval); } } offset += SCTP_SIZE32(plen); phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); } return (NULL); } static struct sctp_tcb * sctp_findassoc_by_vtag(struct sockaddr *from, uint32_t vtag, struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint16_t rport, uint16_t lport, int skip_src_check) { /* * Use my vtag to hash. If we find it we then verify the source addr * is in the assoc. If all goes well we save a bit on rec of a * packet. */ struct sctpasochead *head; struct sctp_nets *net; struct sctp_tcb *stcb; *netp = NULL; *inp_p = NULL; SCTP_INP_INFO_RLOCK(); head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(vtag, sctppcbinfo.hashasocmark)]; if (head == NULL) { /* invalid vtag */ SCTP_INP_INFO_RUNLOCK(); return (NULL); } LIST_FOREACH(stcb, head, sctp_asocs) { SCTP_INP_RLOCK(stcb->sctp_ep); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { SCTP_INP_RUNLOCK(stcb->sctp_ep); continue; } SCTP_TCB_LOCK(stcb); SCTP_INP_RUNLOCK(stcb->sctp_ep); if (stcb->asoc.my_vtag == vtag) { /* candidate */ if (stcb->rport != rport) { /* * we could remove this if vtags are unique * across the system. */ SCTP_TCB_UNLOCK(stcb); continue; } if (stcb->sctp_ep->sctp_lport != lport) { /* * we could remove this if vtags are unique * across the system. */ SCTP_TCB_UNLOCK(stcb); continue; } if (skip_src_check) { *netp = NULL; /* unknown */ *inp_p = stcb->sctp_ep; SCTP_INP_INFO_RUNLOCK(); return (stcb); } net = sctp_findnet(stcb, from); if (net) { /* yep its him. */ *netp = net; SCTP_STAT_INCR(sctps_vtagexpress); *inp_p = stcb->sctp_ep; SCTP_INP_INFO_RUNLOCK(); return (stcb); } else { /* * not him, this should only happen in rare * cases so I peg it. */ SCTP_STAT_INCR(sctps_vtagbogus); } } SCTP_TCB_UNLOCK(stcb); } SCTP_INP_INFO_RUNLOCK(); return (NULL); } /* * Find an association with the pointer to the inbound IP packet. This can be * a IPv4 or IPv6 packet. */ struct sctp_tcb * sctp_findassociation_addr(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint32_t vrf_id) { int find_tcp_pool; struct ip *iph; struct sctp_tcb *retval; struct sockaddr_storage to_store, from_store; struct sockaddr *to = (struct sockaddr *)&to_store; struct sockaddr *from = (struct sockaddr *)&from_store; struct sctp_inpcb *inp; iph = mtod(m, struct ip *); if (iph->ip_v == IPVERSION) { /* its IPv4 */ struct sockaddr_in *from4; from4 = (struct sockaddr_in *)&from_store; bzero(from4, sizeof(*from4)); from4->sin_family = AF_INET; from4->sin_len = sizeof(struct sockaddr_in); from4->sin_addr.s_addr = iph->ip_src.s_addr; from4->sin_port = sh->src_port; } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* its IPv6 */ struct ip6_hdr *ip6; struct sockaddr_in6 *from6; ip6 = mtod(m, struct ip6_hdr *); from6 = (struct sockaddr_in6 *)&from_store; bzero(from6, sizeof(*from6)); from6->sin6_family = AF_INET6; from6->sin6_len = sizeof(struct sockaddr_in6); from6->sin6_addr = ip6->ip6_src; from6->sin6_port = sh->src_port; /* Get the scopes in properly to the sin6 addr's */ /* we probably don't need these operations */ (void)sa6_recoverscope(from6); sa6_embedscope(from6, ip6_use_defzone); } else { /* Currently not supported. */ return (NULL); } if (sh->v_tag) { /* we only go down this path if vtag is non-zero */ retval = sctp_findassoc_by_vtag(from, ntohl(sh->v_tag), inp_p, netp, sh->src_port, sh->dest_port, 0); if (retval) { return (retval); } } if (iph->ip_v == IPVERSION) { /* its IPv4 */ struct sockaddr_in *to4; to4 = (struct sockaddr_in *)&to_store; bzero(to4, sizeof(*to4)); to4->sin_family = AF_INET; to4->sin_len = sizeof(struct sockaddr_in); to4->sin_addr.s_addr = iph->ip_dst.s_addr; to4->sin_port = sh->dest_port; } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* its IPv6 */ struct ip6_hdr *ip6; struct sockaddr_in6 *to6; ip6 = mtod(m, struct ip6_hdr *); to6 = (struct sockaddr_in6 *)&to_store; bzero(to6, sizeof(*to6)); to6->sin6_family = AF_INET6; to6->sin6_len = sizeof(struct sockaddr_in6); to6->sin6_addr = ip6->ip6_dst; to6->sin6_port = sh->dest_port; /* Get the scopes in properly to the sin6 addr's */ /* we probably don't need these operations */ (void)sa6_recoverscope(to6); sa6_embedscope(to6, ip6_use_defzone); } find_tcp_pool = 0; if ((ch->chunk_type != SCTP_INITIATION) && (ch->chunk_type != SCTP_INITIATION_ACK) && (ch->chunk_type != SCTP_COOKIE_ACK) && (ch->chunk_type != SCTP_COOKIE_ECHO)) { /* Other chunk types go to the tcp pool. */ find_tcp_pool = 1; } if (inp_p) { retval = sctp_findassociation_addr_sa(to, from, inp_p, netp, find_tcp_pool, vrf_id); inp = *inp_p; } else { retval = sctp_findassociation_addr_sa(to, from, &inp, netp, find_tcp_pool, vrf_id); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("retval:%p inp:%p\n", retval, inp); } #endif if (retval == NULL && inp) { /* Found a EP but not this address */ if ((ch->chunk_type == SCTP_INITIATION) || (ch->chunk_type == SCTP_INITIATION_ACK)) { /*- * special hook, we do NOT return linp or an * association that is linked to an existing * association that is under the TCP pool (i.e. no * listener exists). The endpoint finding routine * will always find a listner before examining the * TCP pool. */ if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { if (inp_p) { *inp_p = NULL; } return (NULL); } retval = sctp_findassociation_special_addr(m, iphlen, offset, sh, &inp, netp, to); if (inp_p != NULL) { *inp_p = inp; } } } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("retval is %p\n", retval); } #endif return (retval); } /* * lookup an association by an ASCONF lookup address. * if the lookup address is 0.0.0.0 or ::0, use the vtag to do the lookup */ struct sctp_tcb * sctp_findassociation_ep_asconf(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp) { struct sctp_tcb *stcb; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sockaddr_storage local_store, remote_store; struct ip *iph; struct sctp_paramhdr parm_buf, *phdr; int ptype; int zero_address = 0; memset(&local_store, 0, sizeof(local_store)); memset(&remote_store, 0, sizeof(remote_store)); /* First get the destination address setup too. */ iph = mtod(m, struct ip *); if (iph->ip_v == IPVERSION) { /* its IPv4 */ sin = (struct sockaddr_in *)&local_store; sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = sh->dest_port; sin->sin_addr.s_addr = iph->ip_dst.s_addr; } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* its IPv6 */ struct ip6_hdr *ip6; ip6 = mtod(m, struct ip6_hdr *); sin6 = (struct sockaddr_in6 *)&local_store; sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); sin6->sin6_port = sh->dest_port; sin6->sin6_addr = ip6->ip6_dst; } else { return NULL; } phdr = sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk), &parm_buf, sizeof(struct sctp_paramhdr)); if (phdr == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("findassociation_ep_asconf: failed to get asconf lookup addr\n"); } #endif /* SCTP_DEBUG */ return NULL; } ptype = (int)((uint32_t) ntohs(phdr->param_type)); /* get the correlation address */ if (ptype == SCTP_IPV6_ADDRESS) { /* ipv6 address param */ struct sctp_ipv6addr_param *p6, p6_buf; if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv6addr_param)) { return NULL; } p6 = (struct sctp_ipv6addr_param *)sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk), &p6_buf.ph, sizeof(*p6)); if (p6 == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("findassociation_ep_asconf: failed to get asconf v6 lookup addr\n"); } #endif /* SCTP_DEBUG */ return (NULL); } sin6 = (struct sockaddr_in6 *)&remote_store; sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); sin6->sin6_port = sh->src_port; memcpy(&sin6->sin6_addr, &p6->addr, sizeof(struct in6_addr)); if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) zero_address = 1; } else if (ptype == SCTP_IPV4_ADDRESS) { /* ipv4 address param */ struct sctp_ipv4addr_param *p4, p4_buf; if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv4addr_param)) { return NULL; } p4 = (struct sctp_ipv4addr_param *)sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk), &p4_buf.ph, sizeof(*p4)); if (p4 == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT3) { printf("findassociation_ep_asconf: failed to get asconf v4 lookup addr\n"); } #endif /* SCTP_DEBUG */ return (NULL); } sin = (struct sockaddr_in *)&remote_store; sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = sh->src_port; memcpy(&sin->sin_addr, &p4->addr, sizeof(struct in_addr)); if (sin->sin_addr.s_addr == INADDR_ANY) zero_address = 1; } else { /* invalid address param type */ return NULL; } if (zero_address) { stcb = sctp_findassoc_by_vtag(NULL, ntohl(sh->v_tag), inp_p, netp, sh->src_port, sh->dest_port, 1); /* * printf("findassociation_ep_asconf: zero lookup address * finds stcb 0x%x\n", (uint32_t)stcb); */ } else { stcb = sctp_findassociation_ep_addr(inp_p, (struct sockaddr *)&remote_store, netp, (struct sockaddr *)&local_store, NULL); } return (stcb); } /* * allocate a sctp_inpcb and setup a temporary binding to a port/all * addresses. This way if we don't get a bind we by default pick a ephemeral * port with all addresses bound. */ int sctp_inpcb_alloc(struct socket *so) { /* * we get called when a new endpoint starts up. We need to allocate * the sctp_inpcb structure from the zone and init it. Mark it as * unbound and find a port that we can use as an ephemeral with * INADDR_ANY. If the user binds later no problem we can then add in * the specific addresses. And setup the default parameters for the * EP. */ int i, error; struct sctp_inpcb *inp; struct sctp_pcb *m; struct timeval time; sctp_sharedkey_t *null_key; error = 0; SCTP_INP_INFO_WLOCK(); inp = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_ep, struct sctp_inpcb); if (inp == NULL) { printf("Out of SCTP-INPCB structures - no resources\n"); SCTP_INP_INFO_WUNLOCK(); return (ENOBUFS); } /* zap it */ bzero(inp, sizeof(*inp)); /* bump generations */ /* setup socket pointers */ inp->sctp_socket = so; inp->ip_inp.inp.inp_socket = so; inp->partial_delivery_point = SCTP_SB_LIMIT_RCV(so) >> SCTP_PARTIAL_DELIVERY_SHIFT; inp->sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT; #ifdef IPSEC { struct inpcbpolicy *pcb_sp = NULL; error = ipsec_init_pcbpolicy(so, &pcb_sp); /* Arrange to share the policy */ inp->ip_inp.inp.inp_sp = pcb_sp; ((struct in6pcb *)(&inp->ip_inp.inp))->in6p_sp = pcb_sp; } if (error != 0) { SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); SCTP_INP_INFO_WUNLOCK(); return error; } #endif /* IPSEC */ SCTP_INCR_EP_COUNT(); inp->ip_inp.inp.inp_ip_ttl = ip_defttl; SCTP_INP_INFO_WUNLOCK(); so->so_pcb = (caddr_t)inp; if ((SCTP_SO_TYPE(so) == SOCK_DGRAM) || (SCTP_SO_TYPE(so) == SOCK_SEQPACKET)) { /* UDP style socket */ inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE | SCTP_PCB_FLAGS_UNBOUND); /* Be sure it is NON-BLOCKING IO for UDP */ /* SCTP_SET_SO_NBIO(so); */ } else if (SCTP_SO_TYPE(so) == SOCK_STREAM) { /* TCP style socket */ inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE | SCTP_PCB_FLAGS_UNBOUND); /* Be sure we have blocking IO by default */ SCTP_CLEAR_SO_NBIO(so); } else { /* * unsupported socket type (RAW, etc)- in case we missed it * in protosw */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); return (EOPNOTSUPP); } inp->sctp_tcbhash = SCTP_HASH_INIT(sctp_pcbtblsize, &inp->sctp_hashmark); if (inp->sctp_tcbhash == NULL) { printf("Out of SCTP-INPCB->hashinit - no resources\n"); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); return (ENOBUFS); } inp->def_vrf_id = SCTP_DEFAULT_VRFID; inp->def_table_id = SCTP_DEFAULT_TABLEID; SCTP_INP_INFO_WLOCK(); SCTP_INP_LOCK_INIT(inp); SCTP_INP_READ_INIT(inp); SCTP_ASOC_CREATE_LOCK_INIT(inp); /* lock the new ep */ SCTP_INP_WLOCK(inp); /* add it to the info area */ LIST_INSERT_HEAD(&sctppcbinfo.listhead, inp, sctp_list); SCTP_INP_INFO_WUNLOCK(); TAILQ_INIT(&inp->read_queue); LIST_INIT(&inp->sctp_addr_list); LIST_INIT(&inp->sctp_asoc_list); #ifdef SCTP_TRACK_FREED_ASOCS /* TEMP CODE */ LIST_INIT(&inp->sctp_asoc_free_list); #endif /* Init the timer structure for signature change */ SCTP_OS_TIMER_INIT(&inp->sctp_ep.signature_change.timer); inp->sctp_ep.signature_change.type = SCTP_TIMER_TYPE_NEWCOOKIE; /* now init the actual endpoint default data */ m = &inp->sctp_ep; /* setup the base timeout information */ m->sctp_timeoutticks[SCTP_TIMER_SEND] = SEC_TO_TICKS(SCTP_SEND_SEC); /* needed ? */ m->sctp_timeoutticks[SCTP_TIMER_INIT] = SEC_TO_TICKS(SCTP_INIT_SEC); /* needed ? */ m->sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(sctp_delayed_sack_time_default); m->sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = MSEC_TO_TICKS(sctp_heartbeat_interval_default); m->sctp_timeoutticks[SCTP_TIMER_PMTU] = SEC_TO_TICKS(sctp_pmtu_raise_time_default); m->sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN] = SEC_TO_TICKS(sctp_shutdown_guard_time_default); m->sctp_timeoutticks[SCTP_TIMER_SIGNATURE] = SEC_TO_TICKS(sctp_secret_lifetime_default); /* all max/min max are in ms */ m->sctp_maxrto = sctp_rto_max_default; m->sctp_minrto = sctp_rto_min_default; m->initial_rto = sctp_rto_initial_default; m->initial_init_rto_max = sctp_init_rto_max_default; m->sctp_sack_freq = sctp_sack_freq_default; m->max_open_streams_intome = MAX_SCTP_STREAMS; m->max_init_times = sctp_init_rtx_max_default; m->max_send_times = sctp_assoc_rtx_max_default; m->def_net_failure = sctp_path_rtx_max_default; m->sctp_sws_sender = SCTP_SWS_SENDER_DEF; m->sctp_sws_receiver = SCTP_SWS_RECEIVER_DEF; m->max_burst = sctp_max_burst_default; /* number of streams to pre-open on a association */ m->pre_open_stream_count = sctp_nr_outgoing_streams_default; /* Add adaptation cookie */ m->adaptation_layer_indicator = 0x504C5253; /* seed random number generator */ m->random_counter = 1; m->store_at = SCTP_SIGNATURE_SIZE; SCTP_READ_RANDOM(m->random_numbers, sizeof(m->random_numbers)); sctp_fill_random_store(m); /* Minimum cookie size */ m->size_of_a_cookie = (sizeof(struct sctp_init_msg) * 2) + sizeof(struct sctp_state_cookie); m->size_of_a_cookie += SCTP_SIGNATURE_SIZE; /* Setup the initial secret */ - SCTP_GETTIME_TIMEVAL(&time); + (void)SCTP_GETTIME_TIMEVAL(&time); m->time_of_secret_change = time.tv_sec; for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) { m->secret_key[0][i] = sctp_select_initial_TSN(m); } sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL); /* How long is a cookie good for ? */ m->def_cookie_life = sctp_valid_cookie_life_default; /* * Initialize authentication parameters */ m->local_hmacs = sctp_default_supported_hmaclist(); m->local_auth_chunks = sctp_alloc_chunklist(); sctp_auth_set_default_chunks(m->local_auth_chunks); LIST_INIT(&m->shared_keys); /* add default NULL key as key id 0 */ null_key = sctp_alloc_sharedkey(); sctp_insert_sharedkey(&m->shared_keys, null_key); SCTP_INP_WUNLOCK(inp); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 12); #endif return (error); } void sctp_move_pcb_and_assoc(struct sctp_inpcb *old_inp, struct sctp_inpcb *new_inp, struct sctp_tcb *stcb) { struct sctp_nets *net; uint16_t lport, rport; struct sctppcbhead *head; struct sctp_laddr *laddr, *oladdr; SCTP_TCB_UNLOCK(stcb); SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(old_inp); SCTP_INP_WLOCK(new_inp); SCTP_TCB_LOCK(stcb); new_inp->sctp_ep.time_of_secret_change = old_inp->sctp_ep.time_of_secret_change; memcpy(new_inp->sctp_ep.secret_key, old_inp->sctp_ep.secret_key, sizeof(old_inp->sctp_ep.secret_key)); new_inp->sctp_ep.current_secret_number = old_inp->sctp_ep.current_secret_number; new_inp->sctp_ep.last_secret_number = old_inp->sctp_ep.last_secret_number; new_inp->sctp_ep.size_of_a_cookie = old_inp->sctp_ep.size_of_a_cookie; /* make it so new data pours into the new socket */ stcb->sctp_socket = new_inp->sctp_socket; stcb->sctp_ep = new_inp; /* Copy the port across */ lport = new_inp->sctp_lport = old_inp->sctp_lport; rport = stcb->rport; /* Pull the tcb from the old association */ LIST_REMOVE(stcb, sctp_tcbhash); LIST_REMOVE(stcb, sctp_tcblist); /* Now insert the new_inp into the TCP connected hash */ head = &sctppcbinfo.sctp_tcpephash[SCTP_PCBHASH_ALLADDR((lport + rport), sctppcbinfo.hashtcpmark)]; LIST_INSERT_HEAD(head, new_inp, sctp_hash); /* Its safe to access */ new_inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND; /* Now move the tcb into the endpoint list */ LIST_INSERT_HEAD(&new_inp->sctp_asoc_list, stcb, sctp_tcblist); /* * Question, do we even need to worry about the ep-hash since we * only have one connection? Probably not :> so lets get rid of it * and not suck up any kernel memory in that. */ /* Ok. Let's restart timer. */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, new_inp, stcb, net); } SCTP_INP_INFO_WUNLOCK(); if (new_inp->sctp_tcbhash != NULL) { SCTP_HASH_FREE(new_inp->sctp_tcbhash, new_inp->sctp_hashmark); new_inp->sctp_tcbhash = NULL; } if ((new_inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { /* Subset bound, so copy in the laddr list from the old_inp */ LIST_FOREACH(oladdr, &old_inp->sctp_addr_list, sctp_nxt_addr) { laddr = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr, struct sctp_laddr); if (laddr == NULL) { /* * Gak, what can we do? This assoc is really * HOSED. We probably should send an abort * here. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Association hosed in TCP model, out of laddr memory\n"); } #endif /* SCTP_DEBUG */ continue; } SCTP_INCR_LADDR_COUNT(); bzero(laddr, sizeof(*laddr)); laddr->ifa = oladdr->ifa; atomic_add_int(&laddr->ifa->refcount, 1); LIST_INSERT_HEAD(&new_inp->sctp_addr_list, laddr, sctp_nxt_addr); new_inp->laddr_count++; } } /* * Now any running timers need to be adjusted since we really don't * care if they are running or not just blast in the new_inp into * all of them. */ stcb->asoc.hb_timer.ep = (void *)new_inp; stcb->asoc.dack_timer.ep = (void *)new_inp; stcb->asoc.asconf_timer.ep = (void *)new_inp; stcb->asoc.strreset_timer.ep = (void *)new_inp; stcb->asoc.shut_guard_timer.ep = (void *)new_inp; stcb->asoc.autoclose_timer.ep = (void *)new_inp; stcb->asoc.delayed_event_timer.ep = (void *)new_inp; /* now what about the nets? */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { net->pmtu_timer.ep = (void *)new_inp; net->rxt_timer.ep = (void *)new_inp; net->fr_timer.ep = (void *)new_inp; } SCTP_INP_WUNLOCK(new_inp); SCTP_INP_WUNLOCK(old_inp); } static int sctp_isport_inuse(struct sctp_inpcb *inp, uint16_t lport, uint32_t vrf_id) { struct sctppcbhead *head; struct sctp_inpcb *t_inp; int fnd; head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, sctppcbinfo.hashmark)]; LIST_FOREACH(t_inp, head, sctp_hash) { if (t_inp->sctp_lport != lport) { continue; } /* is it in the VRF in question */ fnd = 0; if (t_inp->def_vrf_id == vrf_id) fnd = 1; if (!fnd) continue; /* This one is in use. */ /* check the v6/v4 binding issue */ if ((t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && SCTP_IPV6_V6ONLY(t_inp)) { if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { /* collision in V6 space */ return (1); } else { /* inp is BOUND_V4 no conflict */ continue; } } else if (t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { /* t_inp is bound v4 and v6, conflict always */ return (1); } else { /* t_inp is bound only V4 */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && SCTP_IPV6_V6ONLY(inp)) { /* no conflict */ continue; } /* else fall through to conflict */ } return (1); } return (0); } int sctp_inpcb_bind(struct socket *so, struct sockaddr *addr, struct thread *p) { /* bind a ep to a socket address */ struct sctppcbhead *head; struct sctp_inpcb *inp, *inp_tmp; struct inpcb *ip_inp; int bindall; uint16_t lport; int error; uint32_t vrf_id; lport = 0; error = 0; bindall = 1; inp = (struct sctp_inpcb *)so->so_pcb; ip_inp = (struct inpcb *)so->so_pcb; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { if (addr) { printf("Bind called port:%d\n", ntohs(((struct sockaddr_in *)addr)->sin_port)); printf("Addr :"); sctp_print_address(addr); } } #endif /* SCTP_DEBUG */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) { /* already did a bind, subsequent binds NOT allowed ! */ return (EINVAL); } if (addr != NULL) { if (addr->sa_family == AF_INET) { struct sockaddr_in *sin; /* IPV6_V6ONLY socket? */ if (SCTP_IPV6_V6ONLY(ip_inp)) { return (EINVAL); } if (addr->sa_len != sizeof(*sin)) return (EINVAL); sin = (struct sockaddr_in *)addr; lport = sin->sin_port; if (sin->sin_addr.s_addr != INADDR_ANY) { bindall = 0; } } else if (addr->sa_family == AF_INET6) { /* Only for pure IPv6 Address. (No IPv4 Mapped!) */ struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)addr; if (addr->sa_len != sizeof(*sin6)) return (EINVAL); lport = sin6->sin6_port; if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { bindall = 0; /* KAME hack: embed scopeid */ if (sa6_embedscope(sin6, ip6_use_defzone) != 0) return (EINVAL); } /* this must be cleared for ifa_ifwithaddr() */ sin6->sin6_scope_id = 0; } else { return (EAFNOSUPPORT); } } /* * Setup a vrf_id to be the default for the non-bind-all case. */ vrf_id = inp->def_vrf_id; SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(inp); /* increase our count due to the unlock we do */ SCTP_INP_INCR_REF(inp); if (lport) { /* * Did the caller specify a port? if so we must see if a ep * already has this one bound. */ /* got to be root to get at low ports */ if (ntohs(lport) < IPPORT_RESERVED) { if (p && (error = priv_check_cred(p->td_ucred, PRIV_NETINET_RESERVEDPORT, SUSER_ALLOWJAIL ) )) { SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (error); } } if (p == NULL) { SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (error); } SCTP_INP_WUNLOCK(inp); if (bindall) { vrf_id = inp->def_vrf_id; inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id); if (inp_tmp != NULL) { /* * lock guy returned and lower count note * that we are not bound so inp_tmp should * NEVER be inp. And it is this inp * (inp_tmp) that gets the reference bump, * so we must lower it. */ SCTP_INP_DECR_REF(inp_tmp); SCTP_INP_DECR_REF(inp); /* unlock info */ SCTP_INP_INFO_WUNLOCK(); return (EADDRNOTAVAIL); } } else { inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id); if (inp_tmp != NULL) { /* * lock guy returned and lower count note * that we are not bound so inp_tmp should * NEVER be inp. And it is this inp * (inp_tmp) that gets the reference bump, * so we must lower it. */ SCTP_INP_DECR_REF(inp_tmp); SCTP_INP_DECR_REF(inp); /* unlock info */ SCTP_INP_INFO_WUNLOCK(); return (EADDRNOTAVAIL); } } SCTP_INP_WLOCK(inp); if (bindall) { /* verify that no lport is not used by a singleton */ if (sctp_isport_inuse(inp, lport, vrf_id)) { /* Sorry someone already has this one bound */ SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (EADDRNOTAVAIL); } } } else { /* * get any port but lets make sure no one has any address * with this port bound */ /* * setup the inp to the top (I could use the union but this * is just as easy */ uint32_t port_guess; uint16_t port_attempt; int not_done = 1; int not_found = 1; while (not_done) { port_guess = sctp_select_initial_TSN(&inp->sctp_ep); port_attempt = (port_guess & 0x0000ffff); if (port_attempt == 0) { goto next_half; } if (port_attempt < IPPORT_RESERVED) { port_attempt += IPPORT_RESERVED; } vrf_id = inp->def_vrf_id; if (sctp_isport_inuse(inp, htons(port_attempt), vrf_id) == 1) { /* got a port we can use */ not_found = 0; break; } if (not_found == 1) { /* We can use this port */ not_done = 0; continue; } /* try upper half */ next_half: port_attempt = ((port_guess >> 16) & 0x0000ffff); if (port_attempt == 0) { goto last_try; } if (port_attempt < IPPORT_RESERVED) { port_attempt += IPPORT_RESERVED; } vrf_id = inp->def_vrf_id; if (sctp_isport_inuse(inp, htons(port_attempt), vrf_id) == 1) { /* got a port we can use */ not_found = 0; break; } if (not_found == 1) { /* We can use this port */ not_done = 0; continue; } /* try two half's added together */ last_try: port_attempt = (((port_guess >> 16) & 0x0000ffff) + (port_guess & 0x0000ffff)); if (port_attempt == 0) { /* get a new random number */ continue; } if (port_attempt < IPPORT_RESERVED) { port_attempt += IPPORT_RESERVED; } vrf_id = inp->def_vrf_id; if (sctp_isport_inuse(inp, htons(port_attempt), vrf_id) == 1) { /* got a port we can use */ not_found = 0; break; } if (not_found == 1) { /* We can use this port */ not_done = 0; continue; } } /* we don't get out of the loop until we have a port */ lport = htons(port_attempt); } SCTP_INP_DECR_REF(inp); if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { /* * this really should not happen. The guy did a non-blocking * bind and then did a close at the same time. */ SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (EINVAL); } /* ok we look clear to give out this port, so lets setup the binding */ if (bindall) { /* binding to all addresses, so just set in the proper flags */ inp->sctp_flags |= SCTP_PCB_FLAGS_BOUNDALL; sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF); /* set the automatic addr changes from kernel flag */ if (sctp_auto_asconf == 0) { sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF); } else { sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF); } } else { /* * bind specific, make sure flags is off and add a new * address structure to the sctp_addr_list inside the ep * structure. * * We will need to allocate one and insert it at the head. The * socketopt call can just insert new addresses in there as * well. It will also have to do the embed scope kame hack * too (before adding). */ struct sctp_ifa *ifa; struct sockaddr_storage store_sa; memset(&store_sa, 0, sizeof(store_sa)); if (addr->sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)&store_sa; memcpy(sin, addr, sizeof(struct sockaddr_in)); sin->sin_port = 0; } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&store_sa; memcpy(sin6, addr, sizeof(struct sockaddr_in6)); sin6->sin6_port = 0; } /* * first find the interface with the bound address need to * zero out the port to find the address! yuck! can't do * this earlier since need port for sctp_pcb_findep() */ ifa = sctp_find_ifa_by_addr((struct sockaddr *)&store_sa, vrf_id, 0); if (ifa == NULL) { /* Can't find an interface with that address */ SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (EADDRNOTAVAIL); } if (addr->sa_family == AF_INET6) { /* GAK, more FIXME IFA lock? */ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-existent addr. */ SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (EINVAL); } } /* we're not bound all */ inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUNDALL; /* set the automatic addr changes from kernel flag */ sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF); if (sctp_auto_asconf == 0) { sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF); } else { /* * allow bindx() to send ASCONF's for binding * changes */ sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF); } /* add this address to the endpoint list */ error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, 0); if (error != 0) { SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (error); } inp->laddr_count++; } /* find the bucket */ head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, sctppcbinfo.hashmark)]; /* put it in the bucket */ LIST_INSERT_HEAD(head, inp, sctp_hash); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Main hash to bind at head:%p, bound port:%d\n", head, ntohs(lport)); } #endif /* set in the port */ inp->sctp_lport = lport; /* turn off just the unbound flag */ inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND; SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); return (0); } static void sctp_iterator_inp_being_freed(struct sctp_inpcb *inp, struct sctp_inpcb *inp_next) { struct sctp_iterator *it; /* * We enter with the only the ITERATOR_LOCK in place and a write * lock on the inp_info stuff. */ /* * Go through all iterators, we must do this since it is possible * that some iterator does NOT have the lock, but is waiting for it. * And the one that had the lock has either moved in the last * iteration or we just cleared it above. We need to find all of * those guys. The list of iterators should never be very big * though. */ TAILQ_FOREACH(it, &sctppcbinfo.iteratorhead, sctp_nxt_itr) { if (it == inp->inp_starting_point_for_iterator) /* skip this guy, he's special */ continue; if (it->inp == inp) { /* * This is tricky and we DON'T lock the iterator. * Reason is he's running but waiting for me since * inp->inp_starting_point_for_iterator has the lock * on me (the guy above we skipped). This tells us * its is not running but waiting for * inp->inp_starting_point_for_iterator to be * released by the guy that does have our INP in a * lock. */ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { it->inp = NULL; it->stcb = NULL; } else { /* set him up to do the next guy not me */ it->inp = inp_next; it->stcb = NULL; } } } it = inp->inp_starting_point_for_iterator; if (it) { if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { it->inp = NULL; } else { it->inp = inp_next; } it->stcb = NULL; } } /* release sctp_inpcb unbind the port */ void sctp_inpcb_free(struct sctp_inpcb *inp, int immediate, int from) { /* * Here we free a endpoint. We must find it (if it is in the Hash * table) and remove it from there. Then we must also find it in the * overall list and remove it from there. After all removals are * complete then any timer has to be stopped. Then start the actual * freeing. a) Any local lists. b) Any associations. c) The hash of * all associations. d) finally the ep itself. */ struct sctp_pcb *m; struct sctp_inpcb *inp_save; struct sctp_tcb *asoc, *nasoc; struct sctp_laddr *laddr, *nladdr; struct inpcb *ip_pcb; struct socket *so; struct sctp_queued_to_read *sq; int cnt; sctp_sharedkey_t *shared_key; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 0); #endif SCTP_ITERATOR_LOCK(); so = inp->sctp_socket; if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { /* been here before.. eeks.. get out of here */ printf("This conflict in free SHOULD not be happening!\n"); SCTP_ITERATOR_UNLOCK(); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 1); #endif return; } SCTP_ASOC_CREATE_LOCK(inp); SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(inp); /* * First time through we have the socket lock, after that no more. */ if (from == 1) { /* * Once we are in we can remove the flag from = 1 is only * passed from the actual closing routines that are called * via the sockets layer. */ inp->sctp_flags &= ~SCTP_PCB_FLAGS_CLOSE_IP; } sctp_timer_stop(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL, SCTP_FROM_SCTP_PCB + SCTP_LOC_1); if (inp->control) { sctp_m_freem(inp->control); inp->control = NULL; } if (inp->pkt) { sctp_m_freem(inp->pkt); inp->pkt = NULL; } m = &inp->sctp_ep; ip_pcb = &inp->ip_inp.inp; /* we could just cast the main pointer * here but I will be nice :> (i.e. * ip_pcb = ep;) */ if (immediate == 0) { int cnt_in_sd; cnt_in_sd = 0; for ((asoc = LIST_FIRST(&inp->sctp_asoc_list)); asoc != NULL; asoc = nasoc) { nasoc = LIST_NEXT(asoc, sctp_tcblist); if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* Skip guys being freed */ asoc->sctp_socket = NULL; cnt_in_sd++; continue; } if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_COOKIE_WAIT) || (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_COOKIE_ECHOED)) { /* Just abandon things in the front states */ if (asoc->asoc.total_output_queue_size == 0) { sctp_free_assoc(inp, asoc, SCTP_PCBFREE_NOFORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_2); continue; } } SCTP_TCB_LOCK(asoc); /* Disconnect the socket please */ asoc->sctp_socket = NULL; asoc->asoc.state |= SCTP_STATE_CLOSED_SOCKET; if ((asoc->asoc.size_on_reasm_queue > 0) || (asoc->asoc.control_pdapi) || (asoc->asoc.size_on_all_streams > 0) || (so && (so->so_rcv.sb_cc > 0)) ) { /* Left with Data unread */ struct mbuf *op_err; op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { /* Fill in the user initiated abort */ struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(op_err) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons( SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_PCB + SCTP_LOC_3); } asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_3; sctp_send_abort_tcb(asoc, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } sctp_free_assoc(inp, asoc, SCTP_PCBFREE_NOFORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_4); continue; } else if (TAILQ_EMPTY(&asoc->asoc.send_queue) && TAILQ_EMPTY(&asoc->asoc.sent_queue) && (asoc->asoc.stream_queue_cnt == 0) ) { if (asoc->asoc.locked_on_sending) { goto abort_anyway; } if ((SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* * there is nothing queued to send, * so I send shutdown */ sctp_send_shutdown(asoc, asoc->asoc.primary_destination); if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->asoc.state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, asoc->sctp_ep, asoc, asoc->asoc.primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc, asoc->asoc.primary_destination); sctp_chunk_output(inp, asoc, SCTP_OUTPUT_FROM_SHUT_TMR); } } else { /* mark into shutdown pending */ struct sctp_stream_queue_pending *sp; asoc->asoc.state |= SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc, asoc->asoc.primary_destination); if (asoc->asoc.locked_on_sending) { sp = TAILQ_LAST(&((asoc->asoc.locked_on_sending)->outqueue), sctp_streamhead); if (sp == NULL) { printf("Error, sp is NULL, locked on sending is %p strm:%d\n", asoc->asoc.locked_on_sending, asoc->asoc.locked_on_sending->stream_no); } else { if ((sp->length == 0) && (sp->msg_is_complete == 0)) asoc->asoc.state |= SCTP_STATE_PARTIAL_MSG_LEFT; } } if (TAILQ_EMPTY(&asoc->asoc.send_queue) && TAILQ_EMPTY(&asoc->asoc.sent_queue) && (asoc->asoc.state & SCTP_STATE_PARTIAL_MSG_LEFT)) { struct mbuf *op_err; abort_anyway: op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { /* * Fill in the user * initiated abort */ struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(op_err) = (sizeof(struct sctp_paramhdr) + sizeof(uint32_t)); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons( SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_PCB + SCTP_LOC_5); } asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_5; sctp_send_abort_tcb(asoc, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } sctp_free_assoc(inp, asoc, SCTP_PCBFREE_NOFORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_6); continue; } } cnt_in_sd++; SCTP_TCB_UNLOCK(asoc); } /* now is there some left in our SHUTDOWN state? */ if (cnt_in_sd) { SCTP_INP_WUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); SCTP_ITERATOR_UNLOCK(); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 2); #endif return; } } inp->sctp_socket = NULL; if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) != SCTP_PCB_FLAGS_UNBOUND) { /* * ok, this guy has been bound. It's port is somewhere in * the sctppcbinfo hash table. Remove it! */ LIST_REMOVE(inp, sctp_hash); inp->sctp_flags |= SCTP_PCB_FLAGS_UNBOUND; } /* * If there is a timer running to kill us, forget it, since it may * have a contest on the INP lock.. which would cause us to die ... */ cnt = 0; for ((asoc = LIST_FIRST(&inp->sctp_asoc_list)); asoc != NULL; asoc = nasoc) { nasoc = LIST_NEXT(asoc, sctp_tcblist); if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { cnt++; continue; } /* Free associations that are NOT killing us */ SCTP_TCB_LOCK(asoc); if ((SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_COOKIE_WAIT) && ((asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0)) { struct mbuf *op_err; uint32_t *ippp; op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { /* Fill in the user initiated abort */ struct sctp_paramhdr *ph; SCTP_BUF_LEN(op_err) = (sizeof(struct sctp_paramhdr) + sizeof(uint32_t)); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons( SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_PCB + SCTP_LOC_7); } asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_7; sctp_send_abort_tcb(asoc, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); } else if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { cnt++; SCTP_TCB_UNLOCK(asoc); continue; } if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } sctp_free_assoc(inp, asoc, SCTP_PCBFREE_FORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_8); } if (cnt) { /* Ok we have someone out there that will kill us */ - SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); + (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); SCTP_INP_WUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); SCTP_ITERATOR_UNLOCK(); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 3); #endif return; } if ((inp->refcount) || (inp->sctp_flags & SCTP_PCB_FLAGS_CLOSE_IP)) { - SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); + (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); sctp_timer_start(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL); SCTP_INP_WUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); SCTP_ITERATOR_UNLOCK(); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 4); #endif return; } - SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); + (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); inp->sctp_ep.signature_change.type = 0; inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_ALLGONE; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 5); #endif - SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); + (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer); inp->sctp_ep.signature_change.type = SCTP_TIMER_TYPE_NONE; /* Clear the read queue */ while ((sq = TAILQ_FIRST(&inp->read_queue)) != NULL) { /* Its only abandoned if it had data left */ if (sq->length) SCTP_STAT_INCR(sctps_left_abandon); TAILQ_REMOVE(&inp->read_queue, sq, next); sctp_free_remote_addr(sq->whoFrom); if (so) so->so_rcv.sb_cc -= sq->length; if (sq->data) { sctp_m_freem(sq->data); sq->data = NULL; } /* * no need to free the net count, since at this point all * assoc's are gone. */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, sq); SCTP_DECR_READQ_COUNT(); } /* Now the sctp_pcb things */ /* * free each asoc if it is not already closed/free. we can't use the * macro here since le_next will get freed as part of the * sctp_free_assoc() call. */ cnt = 0; if (so) { #ifdef IPSEC ipsec4_delete_pcbpolicy(ip_pcb); #endif /* IPSEC */ /* Unlocks not needed since the socket is gone now */ } if (ip_pcb->inp_options) { (void)sctp_m_free(ip_pcb->inp_options); ip_pcb->inp_options = 0; } if (ip_pcb->inp_moptions) { ip_freemoptions(ip_pcb->inp_moptions); ip_pcb->inp_moptions = 0; } #ifdef INET6 if (ip_pcb->inp_vflag & INP_IPV6) { struct in6pcb *in6p; in6p = (struct in6pcb *)inp; ip6_freepcbopts(in6p->in6p_outputopts); } #endif /* INET6 */ ip_pcb->inp_vflag = 0; /* free up authentication fields */ if (inp->sctp_ep.local_auth_chunks != NULL) sctp_free_chunklist(inp->sctp_ep.local_auth_chunks); if (inp->sctp_ep.local_hmacs != NULL) sctp_free_hmaclist(inp->sctp_ep.local_hmacs); shared_key = LIST_FIRST(&inp->sctp_ep.shared_keys); while (shared_key) { LIST_REMOVE(shared_key, next); sctp_free_sharedkey(shared_key); shared_key = LIST_FIRST(&inp->sctp_ep.shared_keys); } inp_save = LIST_NEXT(inp, sctp_list); LIST_REMOVE(inp, sctp_list); /* fix any iterators only after out of the list */ sctp_iterator_inp_being_freed(inp, inp_save); /* * if we have an address list the following will free the list of * ifaddr's that are set into this ep. Again macro limitations here, * since the LIST_FOREACH could be a bad idea. */ for ((laddr = LIST_FIRST(&inp->sctp_addr_list)); laddr != NULL; laddr = nladdr) { nladdr = LIST_NEXT(laddr, sctp_nxt_addr); sctp_remove_laddr(laddr); } #ifdef SCTP_TRACK_FREED_ASOCS /* TEMP CODE */ for ((asoc = LIST_FIRST(&inp->sctp_asoc_free_list)); asoc != NULL; asoc = nasoc) { nasoc = LIST_NEXT(asoc, sctp_tcblist); LIST_REMOVE(asoc, sctp_tcblist); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, asoc); SCTP_DECR_ASOC_COUNT(); } /* *** END TEMP CODE *** */ #endif /* Now lets see about freeing the EP hash table. */ if (inp->sctp_tcbhash != NULL) { SCTP_HASH_FREE(inp->sctp_tcbhash, inp->sctp_hashmark); inp->sctp_tcbhash = NULL; } /* Now we must put the ep memory back into the zone pool */ SCTP_INP_LOCK_DESTROY(inp); SCTP_INP_READ_DESTROY(inp); SCTP_ASOC_CREATE_LOCK_DESTROY(inp); SCTP_INP_INFO_WUNLOCK(); SCTP_ITERATOR_UNLOCK(); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); SCTP_DECR_EP_COUNT(); } struct sctp_nets * sctp_findnet(struct sctp_tcb *stcb, struct sockaddr *addr) { struct sctp_nets *net; /* locate the address */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (sctp_cmpaddr(addr, (struct sockaddr *)&net->ro._l_addr)) return (net); } return (NULL); } /* * add's a remote endpoint address, done with the INIT/INIT-ACK as well as * when a ASCONF arrives that adds it. It will also initialize all the cwnd * stats of stuff. */ int sctp_is_address_on_local_host(struct sockaddr *addr, uint32_t vrf_id) { struct sctp_ifa *sctp_ifa; sctp_ifa = sctp_find_ifa_by_addr(addr, vrf_id, 0); if (sctp_ifa) { return (1); } else { return (0); } } void sctp_set_initial_cc_param(struct sctp_tcb *stcb, struct sctp_nets *net) { net->cwnd = min((net->mtu * 4), max((2 * net->mtu), SCTP_INITIAL_CWND)); /* we always get at LEAST 2 MTU's */ if (net->cwnd < (2 * net->mtu)) { net->cwnd = 2 * net->mtu; } net->ssthresh = stcb->asoc.peers_rwnd; } int sctp_add_remote_addr(struct sctp_tcb *stcb, struct sockaddr *newaddr, int set_scope, int from) { /* * The following is redundant to the same lines in the * sctp_aloc_assoc() but is needed since other's call the add * address function */ struct sctp_nets *net, *netfirst; int addr_inscope; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Adding an address (from:%d) to the peer: ", from); sctp_print_address(newaddr); } #endif netfirst = sctp_findnet(stcb, newaddr); if (netfirst) { /* * Lie and return ok, we don't want to make the association * go away for this behavior. It will happen in the TCP * model in a connected socket. It does not reach the hash * table until after the association is built so it can't be * found. Mark as reachable, since the initial creation will * have been cleared and the NOT_IN_ASSOC flag will have * been added... and we don't want to end up removing it * back out. */ if (netfirst->dest_state & SCTP_ADDR_UNCONFIRMED) { netfirst->dest_state = (SCTP_ADDR_REACHABLE | SCTP_ADDR_UNCONFIRMED); } else { netfirst->dest_state = SCTP_ADDR_REACHABLE; } return (0); } addr_inscope = 1; if (newaddr->sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)newaddr; if (sin->sin_addr.s_addr == 0) { /* Invalid address */ return (-1); } /* zero out the bzero area */ memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); /* assure len is set */ sin->sin_len = sizeof(struct sockaddr_in); if (set_scope) { #ifdef SCTP_DONT_DO_PRIVADDR_SCOPE stcb->ipv4_local_scope = 1; #else if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { stcb->asoc.ipv4_local_scope = 1; } #endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */ } else { /* Validate the address is in scope */ if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) && (stcb->asoc.ipv4_local_scope == 0)) { addr_inscope = 0; } } } else if (newaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)newaddr; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* Invalid address */ return (-1); } /* assure len is set */ sin6->sin6_len = sizeof(struct sockaddr_in6); if (set_scope) { if (sctp_is_address_on_local_host(newaddr, stcb->asoc.vrf_id)) { stcb->asoc.loopback_scope = 1; stcb->asoc.local_scope = 0; stcb->asoc.ipv4_local_scope = 1; stcb->asoc.site_scope = 1; } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { /* * If the new destination is a LINK_LOCAL we * must have common site scope. Don't set * the local scope since we may not share * all links, only loopback can do this. * Links on the local network would also be * on our private network for v4 too. */ stcb->asoc.ipv4_local_scope = 1; stcb->asoc.site_scope = 1; } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { /* * If the new destination is SITE_LOCAL then * we must have site scope in common. */ stcb->asoc.site_scope = 1; } } else { /* Validate the address is in scope */ if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) && (stcb->asoc.loopback_scope == 0)) { addr_inscope = 0; } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && (stcb->asoc.local_scope == 0)) { addr_inscope = 0; } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && (stcb->asoc.site_scope == 0)) { addr_inscope = 0; } } } else { /* not supported family type */ return (-1); } net = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_net, struct sctp_nets); if (net == NULL) { return (-1); } SCTP_INCR_RADDR_COUNT(); bzero(net, sizeof(*net)); - SCTP_GETTIME_TIMEVAL(&net->start_time); + (void)SCTP_GETTIME_TIMEVAL(&net->start_time); memcpy(&net->ro._l_addr, newaddr, newaddr->sa_len); if (newaddr->sa_family == AF_INET) { ((struct sockaddr_in *)&net->ro._l_addr)->sin_port = stcb->rport; } else if (newaddr->sa_family == AF_INET6) { ((struct sockaddr_in6 *)&net->ro._l_addr)->sin6_port = stcb->rport; } net->addr_is_local = sctp_is_address_on_local_host(newaddr, stcb->asoc.vrf_id); if (net->addr_is_local && ((set_scope || (from == SCTP_ADDR_IS_CONFIRMED)))) { stcb->asoc.loopback_scope = 1; stcb->asoc.ipv4_local_scope = 1; stcb->asoc.local_scope = 0; stcb->asoc.site_scope = 1; addr_inscope = 1; } net->failure_threshold = stcb->asoc.def_net_failure; if (addr_inscope == 0) { net->dest_state = (SCTP_ADDR_REACHABLE | SCTP_ADDR_OUT_OF_SCOPE); } else { if (from == SCTP_ADDR_IS_CONFIRMED) /* SCTP_ADDR_IS_CONFIRMED is passed by connect_x */ net->dest_state = SCTP_ADDR_REACHABLE; else net->dest_state = SCTP_ADDR_REACHABLE | SCTP_ADDR_UNCONFIRMED; } net->RTO = stcb->asoc.initial_rto; stcb->asoc.numnets++; *(&net->ref_count) = 1; net->tos_flowlabel = 0; #ifdef INET if (newaddr->sa_family == AF_INET) net->tos_flowlabel = stcb->asoc.default_tos; #endif #ifdef INET6 if (newaddr->sa_family == AF_INET6) net->tos_flowlabel = stcb->asoc.default_flowlabel; #endif /* Init the timer structure */ SCTP_OS_TIMER_INIT(&net->rxt_timer.timer); SCTP_OS_TIMER_INIT(&net->fr_timer.timer); SCTP_OS_TIMER_INIT(&net->pmtu_timer.timer); /* Now generate a route for this guy */ /* KAME hack: embed scopeid */ if (newaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; (void)sa6_embedscope(sin6, ip6_use_defzone); sin6->sin6_scope_id = 0; } SCTP_RTALLOC((sctp_route_t *) & net->ro, stcb->asoc.vrf_id, stcb->asoc.table_id); if (newaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; (void)sa6_recoverscope(sin6); } if (SCTP_ROUTE_HAS_VALID_IFN(&net->ro)) { /* Get source address */ net->ro._s_addr = sctp_source_address_selection(stcb->sctp_ep, stcb, (sctp_route_t *) & net->ro, net, 0, stcb->asoc.vrf_id); /* Now get the interface MTU */ if (net->ro._s_addr && net->ro._s_addr->ifn_p) { net->mtu = SCTP_GATHER_MTU_FROM_INTFC(net->ro._s_addr->ifn_p); } else { net->mtu = 0; } #ifdef SCTP_PRINT_FOR_B_AND_M printf("We have found an interface mtu of %d\n", net->mtu); #endif if (net->mtu == 0) { /* Huh ?? */ net->mtu = SCTP_DEFAULT_MTU; } else { uint32_t rmtu; rmtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._l_addr.sa, net->ro.ro_rt); #ifdef SCTP_PRINT_FOR_B_AND_M printf("The route mtu is %d\n", rmtu); #endif if (rmtu == 0) { /* * Start things off to match mtu of * interface please. */ SCTP_SET_MTU_OF_ROUTE(&net->ro._l_addr.sa, net->ro.ro_rt, net->mtu); } else { /* * we take the route mtu over the interface, * since the route may be leading out the * loopback, or a different interface. */ net->mtu = rmtu; } } if (from == SCTP_ALLOC_ASOC) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("New assoc sets mtu to :%d\n", net->mtu); #endif stcb->asoc.smallest_mtu = net->mtu; } } else { net->mtu = stcb->asoc.smallest_mtu; } if (stcb->asoc.smallest_mtu > net->mtu) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("new address mtu:%d smaller than smallest:%d\n", net->mtu, stcb->asoc.smallest_mtu); #endif stcb->asoc.smallest_mtu = net->mtu; } /* * We take the max of the burst limit times a MTU or the * INITIAL_CWND. We then limit this to 4 MTU's of sending. */ sctp_set_initial_cc_param(stcb, net); #if defined(SCTP_CWND_MONITOR) || defined(SCTP_CWND_LOGGING) sctp_log_cwnd(stcb, net, 0, SCTP_CWND_INITIALIZATION); #endif /* * CMT: CUC algo - set find_pseudo_cumack to TRUE (1) at beginning * of assoc (2005/06/27, iyengar@cis.udel.edu) */ net->find_pseudo_cumack = 1; net->find_rtx_pseudo_cumack = 1; net->src_addr_selected = 0; netfirst = TAILQ_FIRST(&stcb->asoc.nets); if (net->ro.ro_rt == NULL) { /* Since we have no route put it at the back */ TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next); } else if (netfirst == NULL) { /* We are the first one in the pool. */ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); } else if (netfirst->ro.ro_rt == NULL) { /* * First one has NO route. Place this one ahead of the first * one. */ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); } else if (net->ro.ro_rt->rt_ifp != netfirst->ro.ro_rt->rt_ifp) { /* * This one has a different interface than the one at the * top of the list. Place it ahead. */ TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); } else { /* * Ok we have the same interface as the first one. Move * forward until we find either a) one with a NULL route... * insert ahead of that b) one with a different ifp.. insert * after that. c) end of the list.. insert at the tail. */ struct sctp_nets *netlook; do { netlook = TAILQ_NEXT(netfirst, sctp_next); if (netlook == NULL) { /* End of the list */ TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next); break; } else if (netlook->ro.ro_rt == NULL) { /* next one has NO route */ TAILQ_INSERT_BEFORE(netfirst, net, sctp_next); break; } else if (netlook->ro.ro_rt->rt_ifp != net->ro.ro_rt->rt_ifp) { TAILQ_INSERT_AFTER(&stcb->asoc.nets, netlook, net, sctp_next); break; } /* Shift forward */ netfirst = netlook; } while (netlook != NULL); } /* got to have a primary set */ if (stcb->asoc.primary_destination == 0) { stcb->asoc.primary_destination = net; } else if ((stcb->asoc.primary_destination->ro.ro_rt == NULL) && (net->ro.ro_rt) && ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) { /* No route to current primary adopt new primary */ stcb->asoc.primary_destination = net; } sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, stcb->sctp_ep, stcb, net); /* Validate primary is first */ net = TAILQ_FIRST(&stcb->asoc.nets); if ((net != stcb->asoc.primary_destination) && (stcb->asoc.primary_destination)) { /* * first one on the list is NOT the primary sctp_cmpaddr() * is much more efficent if the primary is the first on the * list, make it so. */ TAILQ_REMOVE(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); TAILQ_INSERT_HEAD(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); } return (0); } /* * allocate an association and add it to the endpoint. The caller must be * careful to add all additional addresses once they are know right away or * else the assoc will be may experience a blackout scenario. */ struct sctp_tcb * sctp_aloc_assoc(struct sctp_inpcb *inp, struct sockaddr *firstaddr, int for_a_init, int *error, uint32_t override_tag, uint32_t vrf_id) { struct sctp_tcb *stcb; struct sctp_association *asoc; struct sctpasochead *head; uint16_t rport; int err; /* * Assumption made here: Caller has done a * sctp_findassociation_ep_addr(ep, addr's); to make sure the * address does not exist already. */ if (sctppcbinfo.ipi_count_asoc >= SCTP_MAX_NUM_OF_ASOC) { /* Hit max assoc, sorry no more */ *error = ENOBUFS; return (NULL); } SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { /* * If its in the TCP pool, its NOT allowed to create an * association. The parent listener needs to call * sctp_aloc_assoc.. or the one-2-many socket. If a peeled * off, or connected one does this.. its an error. */ SCTP_INP_RUNLOCK(inp); *error = EINVAL; return (NULL); } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB3) { printf("Allocate an association for peer:"); - if (firstaddr) + if (firstaddr) { sctp_print_address(firstaddr); - else + printf("Port:%d\n", + ntohs(((struct sockaddr_in *)firstaddr)->sin_port)); + } else printf("None\n"); - printf("Port:%d\n", - ntohs(((struct sockaddr_in *)firstaddr)->sin_port)); } #endif /* SCTP_DEBUG */ if (firstaddr->sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)firstaddr; if ((sin->sin_port == 0) || (sin->sin_addr.s_addr == 0)) { /* Invalid address */ SCTP_INP_RUNLOCK(inp); *error = EINVAL; return (NULL); } rport = sin->sin_port; } else if (firstaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)firstaddr; if ((sin6->sin6_port == 0) || (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) { /* Invalid address */ SCTP_INP_RUNLOCK(inp); *error = EINVAL; return (NULL); } rport = sin6->sin6_port; } else { /* not supported family type */ SCTP_INP_RUNLOCK(inp); *error = EINVAL; return (NULL); } SCTP_INP_RUNLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { /* * If you have not performed a bind, then we need to do the * ephemerial bind for you. */ if ((err = sctp_inpcb_bind(inp->sctp_socket, (struct sockaddr *)NULL, (struct thread *)NULL ))) { /* bind error, probably perm */ *error = err; return (NULL); } } stcb = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_asoc, struct sctp_tcb); if (stcb == NULL) { /* out of memory? */ *error = ENOMEM; return (NULL); } SCTP_INCR_ASOC_COUNT(); bzero(stcb, sizeof(*stcb)); asoc = &stcb->asoc; SCTP_TCB_LOCK_INIT(stcb); SCTP_TCB_SEND_LOCK_INIT(stcb); /* setup back pointer's */ stcb->sctp_ep = inp; stcb->sctp_socket = inp->sctp_socket; if ((err = sctp_init_asoc(inp, asoc, for_a_init, override_tag, vrf_id))) { /* failed */ SCTP_TCB_LOCK_DESTROY(stcb); SCTP_TCB_SEND_LOCK_DESTROY(stcb); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); SCTP_DECR_ASOC_COUNT(); *error = err; return (NULL); } /* and the port */ stcb->rport = rport; SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(inp); if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { /* inpcb freed while alloc going on */ SCTP_TCB_LOCK_DESTROY(stcb); SCTP_TCB_SEND_LOCK_DESTROY(stcb); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); SCTP_INP_WUNLOCK(inp); SCTP_INP_INFO_WUNLOCK(); SCTP_DECR_ASOC_COUNT(); *error = EINVAL; return (NULL); } SCTP_TCB_LOCK(stcb); /* now that my_vtag is set, add it to the hash */ head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, sctppcbinfo.hashasocmark)]; /* put it in the bucket in the vtag hash of assoc's for the system */ LIST_INSERT_HEAD(head, stcb, sctp_asocs); SCTP_INP_INFO_WUNLOCK(); if ((err = sctp_add_remote_addr(stcb, firstaddr, SCTP_DO_SETSCOPE, SCTP_ALLOC_ASOC))) { /* failure.. memory error? */ if (asoc->strmout) SCTP_FREE(asoc->strmout); if (asoc->mapping_array) SCTP_FREE(asoc->mapping_array); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); SCTP_DECR_ASOC_COUNT(); SCTP_TCB_LOCK_DESTROY(stcb); SCTP_TCB_SEND_LOCK_DESTROY(stcb); SCTP_INP_WUNLOCK(inp); *error = ENOBUFS; return (NULL); } /* Init all the timers */ SCTP_OS_TIMER_INIT(&asoc->hb_timer.timer); SCTP_OS_TIMER_INIT(&asoc->dack_timer.timer); SCTP_OS_TIMER_INIT(&asoc->strreset_timer.timer); SCTP_OS_TIMER_INIT(&asoc->asconf_timer.timer); SCTP_OS_TIMER_INIT(&asoc->shut_guard_timer.timer); SCTP_OS_TIMER_INIT(&asoc->autoclose_timer.timer); SCTP_OS_TIMER_INIT(&asoc->delayed_event_timer.timer); LIST_INSERT_HEAD(&inp->sctp_asoc_list, stcb, sctp_tcblist); /* now file the port under the hash as well */ if (inp->sctp_tcbhash != NULL) { head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(stcb->rport, inp->sctp_hashmark)]; LIST_INSERT_HEAD(head, stcb, sctp_tcbhash); } SCTP_INP_WUNLOCK(inp); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Association %p now allocated\n", stcb); } #endif return (stcb); } void sctp_remove_net(struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_association *asoc; asoc = &stcb->asoc; asoc->numnets--; TAILQ_REMOVE(&asoc->nets, net, sctp_next); if (net == asoc->primary_destination) { /* Reset primary */ struct sctp_nets *lnet; lnet = TAILQ_FIRST(&asoc->nets); /* Try to find a confirmed primary */ asoc->primary_destination = sctp_find_alternate_net(stcb, lnet, 0); } if (net == asoc->last_data_chunk_from) { /* Reset primary */ asoc->last_data_chunk_from = TAILQ_FIRST(&asoc->nets); } if (net == asoc->last_control_chunk_from) { /* Clear net */ asoc->last_control_chunk_from = NULL; } sctp_free_remote_addr(net); } /* * remove a remote endpoint address from an association, it will fail if the * address does not exist. */ int sctp_del_remote_addr(struct sctp_tcb *stcb, struct sockaddr *remaddr) { /* * Here we need to remove a remote address. This is quite simple, we * first find it in the list of address for the association * (tasoc->asoc.nets) and then if it is there, we do a LIST_REMOVE * on that item. Note we do not allow it to be removed if there are * no other addresses. */ struct sctp_association *asoc; struct sctp_nets *net, *net_tmp; asoc = &stcb->asoc; /* locate the address */ for (net = TAILQ_FIRST(&asoc->nets); net != NULL; net = net_tmp) { net_tmp = TAILQ_NEXT(net, sctp_next); if (net->ro._l_addr.sa.sa_family != remaddr->sa_family) { continue; } if (sctp_cmpaddr((struct sockaddr *)&net->ro._l_addr, remaddr)) { /* we found the guy */ if (asoc->numnets < 2) { /* Must have at LEAST two remote addresses */ return (-1); } else { sctp_remove_net(stcb, net); return (0); } } } /* not found. */ return (-2); } void sctp_add_vtag_to_timewait(struct sctp_inpcb *inp, uint32_t tag, uint32_t time) { struct sctpvtaghead *chain; struct sctp_tagblock *twait_block; struct timeval now; int set, i; - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); chain = &sctppcbinfo.vtag_timewait[(tag % SCTP_STACK_VTAG_HASH_SIZE)]; set = 0; if (!SCTP_LIST_EMPTY(chain)) { /* Block(s) present, lets find space, and expire on the fly */ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) { if ((twait_block->vtag_block[i].v_tag == 0) && !set) { twait_block->vtag_block[i].tv_sec_at_expire = now.tv_sec + time; twait_block->vtag_block[i].v_tag = tag; set = 1; } else if ((twait_block->vtag_block[i].v_tag) && ((long)twait_block->vtag_block[i].tv_sec_at_expire > now.tv_sec)) { /* Audit expires this guy */ twait_block->vtag_block[i].tv_sec_at_expire = 0; twait_block->vtag_block[i].v_tag = 0; if (set == 0) { /* Reuse it for my new tag */ twait_block->vtag_block[0].tv_sec_at_expire = now.tv_sec + SCTP_TIME_WAIT; twait_block->vtag_block[0].v_tag = tag; set = 1; } } } if (set) { /* * We only do up to the block where we can * place our tag for audits */ break; } } } /* Need to add a new block to chain */ if (!set) { SCTP_MALLOC(twait_block, struct sctp_tagblock *, sizeof(struct sctp_tagblock), "TimeWait"); if (twait_block == NULL) { return; } memset(twait_block, 0, sizeof(struct sctp_tagblock)); LIST_INSERT_HEAD(chain, twait_block, sctp_nxt_tagblock); twait_block->vtag_block[0].tv_sec_at_expire = now.tv_sec + SCTP_TIME_WAIT; twait_block->vtag_block[0].v_tag = tag; } } static void sctp_iterator_asoc_being_freed(struct sctp_inpcb *inp, struct sctp_tcb *stcb) { struct sctp_iterator *it; /* * Unlock the tcb lock we do this so we avoid a dead lock scenario * where the iterator is waiting on the TCB lock and the TCB lock is * waiting on the iterator lock. */ it = stcb->asoc.stcb_starting_point_for_iterator; if (it == NULL) { return; } if (it->inp != stcb->sctp_ep) { /* hmm, focused on the wrong one? */ return; } if (it->stcb != stcb) { return; } it->stcb = LIST_NEXT(stcb, sctp_tcblist); if (it->stcb == NULL) { /* done with all asoc's in this assoc */ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { it->inp = NULL; } else { it->inp = LIST_NEXT(inp, sctp_list); } } } /* * Free the association after un-hashing the remote port. */ int sctp_free_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int from_inpcbfree, int from_location) { int i; struct sctp_association *asoc; struct sctp_nets *net, *prev; struct sctp_laddr *laddr; struct sctp_tmit_chunk *chk; struct sctp_asconf_addr *aparam; struct sctp_stream_reset_list *liste; struct sctp_queued_to_read *sq; struct sctp_stream_queue_pending *sp; sctp_sharedkey_t *shared_key; struct socket *so; int ccnt = 0; int cnt = 0; /* first, lets purge the entry from the hash table. */ #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, stcb, 6); #endif if (stcb->asoc.state == 0) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 7); #endif /* there is no asoc, really TSNH :-0 */ return (1); } /* TEMP CODE */ if (stcb->freed_from_where == 0) { /* Only record the first place free happened from */ stcb->freed_from_where = from_location; } /* TEMP CODE */ asoc = &stcb->asoc; if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */ so = NULL; else so = inp->sctp_socket; /* * We used timer based freeing if a reader or writer is in the way. * So we first check if we are actually being called from a timer, * if so we abort early if a reader or writer is still in the way. */ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) && (from_inpcbfree == SCTP_NORMAL_PROC)) { /* * is it the timer driving us? if so are the reader/writers * gone? */ if (stcb->asoc.refcnt) { /* nope, reader or writer in the way */ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL); /* no asoc destroyed */ SCTP_TCB_UNLOCK(stcb); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, stcb, 8); #endif return (0); } } /* now clean up any other timers */ - SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); asoc->hb_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); asoc->dack_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); /*- * For stream reset we don't blast this unless * it is a str-reset timer, it might be the * free-asoc timer which we DON'T want to * disturb. */ if (asoc->strreset_timer.type == SCTP_TIMER_TYPE_STRRESET) asoc->strreset_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); asoc->asconf_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); asoc->autoclose_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer); asoc->shut_guard_timer.self = NULL; - SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); asoc->delayed_event_timer.self = NULL; TAILQ_FOREACH(net, &asoc->nets, sctp_next) { - SCTP_OS_TIMER_STOP(&net->fr_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->fr_timer.timer); net->fr_timer.self = NULL; - SCTP_OS_TIMER_STOP(&net->rxt_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->rxt_timer.timer); net->rxt_timer.self = NULL; - SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); net->pmtu_timer.self = NULL; } /* Now the read queue needs to be cleaned up (only once) */ cnt = 0; if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) { stcb->asoc.state |= SCTP_STATE_ABOUT_TO_BE_FREED; SCTP_INP_READ_LOCK(inp); TAILQ_FOREACH(sq, &inp->read_queue, next) { if (sq->stcb == stcb) { sq->do_not_ref_stcb = 1; sq->sinfo_cumtsn = stcb->asoc.cumulative_tsn; /* * If there is no end, there never will be * now. */ if (sq->end_added == 0) { /* Held for PD-API clear that. */ sq->pdapi_aborted = 1; sq->held_length = 0; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PDAPIEVNT)) { /* * Need to add a PD-API * aborted indication. * Setting the control_pdapi * assures that it will be * added right after this * msg. */ uint32_t strseq; stcb->asoc.control_pdapi = sq; strseq = (sq->sinfo_stream << 16) | sq->sinfo_ssn; sctp_notify_partial_delivery_indication(stcb, SCTP_PARTIAL_DELIVERY_ABORTED, 1, strseq); stcb->asoc.control_pdapi = NULL; } } /* Add an end to wake them */ sq->end_added = 1; cnt++; } } SCTP_INP_READ_UNLOCK(inp); if (stcb->block_entry) { cnt++; stcb->block_entry->error = ECONNRESET; stcb->block_entry = NULL; } } if ((from_inpcbfree != SCTP_PCBFREE_FORCE) && (stcb->asoc.refcnt)) { /* * reader or writer in the way, we have hopefully given him * something to chew on above. */ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL); SCTP_TCB_UNLOCK(stcb); if (so) { SCTP_INP_RLOCK(inp); if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */ so = NULL; if (so) { /* Wake any reader/writers */ sctp_sorwakeup(inp, so); sctp_sowwakeup(inp, so); } SCTP_INP_RUNLOCK(inp); } #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, stcb, 9); #endif /* no asoc destroyed */ return (0); } #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, stcb, 10); #endif /* * When I reach here, no others want to kill the assoc yet.. and I * own the lock. Now its possible an abort comes in when I do the * lock exchange below to grab all the locks to do the final take * out. to prevent this we increment the count, which will start a * timer and blow out above thus assuring us that we hold exclusive * killing of the asoc. Note that after getting back the TCB lock we * will go ahead and increment the counter back up and stop any * timer a passing stranger may have started :-S */ if (from_inpcbfree == SCTP_NORMAL_PROC) { atomic_add_int(&stcb->asoc.refcnt, 1); SCTP_TCB_UNLOCK(stcb); SCTP_ITERATOR_LOCK(); SCTP_INP_INFO_WLOCK(); SCTP_INP_WLOCK(inp); SCTP_TCB_LOCK(stcb); } /* Double check the GONE flag */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */ so = NULL; if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* * For TCP type we need special handling when we are * connected. We also include the peel'ed off ones to. */ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { inp->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED; inp->sctp_flags |= SCTP_PCB_FLAGS_WAS_CONNECTED; if (so) { SOCK_LOCK(so); if (so->so_rcv.sb_cc == 0) { so->so_state &= ~(SS_ISCONNECTING | SS_ISDISCONNECTING | SS_ISCONFIRMING | SS_ISCONNECTED); } SOCK_UNLOCK(so); sctp_sowwakeup(inp, so); sctp_sorwakeup(inp, so); SCTP_SOWAKEUP(so); } } } /* * Make it invalid too, that way if its about to run it will abort * and return. */ sctp_iterator_asoc_being_freed(inp, stcb); /* re-increment the lock */ if (from_inpcbfree == SCTP_NORMAL_PROC) { atomic_add_int(&stcb->asoc.refcnt, -1); } asoc->state = 0; if (inp->sctp_tcbhash) { LIST_REMOVE(stcb, sctp_tcbhash); } if (stcb->asoc.in_restart_hash) { LIST_REMOVE(stcb, sctp_tcbrestarhash); } /* Now lets remove it from the list of ALL associations in the EP */ LIST_REMOVE(stcb, sctp_tcblist); if (from_inpcbfree == SCTP_NORMAL_PROC) { SCTP_INP_INCR_REF(inp); SCTP_INP_WUNLOCK(inp); SCTP_ITERATOR_UNLOCK(); } /* pull from vtag hash */ LIST_REMOVE(stcb, sctp_asocs); sctp_add_vtag_to_timewait(inp, asoc->my_vtag, SCTP_TIME_WAIT); /* * Now restop the timers to be sure - this is paranoia at is finest! */ - SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->shut_guard_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); TAILQ_FOREACH(net, &asoc->nets, sctp_next) { - SCTP_OS_TIMER_STOP(&net->fr_timer.timer); - SCTP_OS_TIMER_STOP(&net->rxt_timer.timer); - SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->fr_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->rxt_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); } asoc->strreset_timer.type = SCTP_TIMER_TYPE_NONE; prev = NULL; /* * The chunk lists and such SHOULD be empty but we check them just * in case. */ /* anything on the wheel needs to be removed */ for (i = 0; i < asoc->streamoutcnt; i++) { struct sctp_stream_out *outs; outs = &asoc->strmout[i]; /* now clean up any chunks here */ sp = TAILQ_FIRST(&outs->outqueue); while (sp) { TAILQ_REMOVE(&outs->outqueue, sp, next); if (sp->data) { sctp_m_freem(sp->data); sp->data = NULL; sp->tail_mbuf = NULL; } sctp_free_remote_addr(sp->net); sctp_free_spbufspace(stcb, asoc, sp); /* Free the zone stuff */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_strmoq, sp); SCTP_DECR_STRMOQ_COUNT(); sp = TAILQ_FIRST(&outs->outqueue); } } while ((liste = TAILQ_FIRST(&asoc->resetHead)) != NULL) { TAILQ_REMOVE(&asoc->resetHead, liste, next_resp); SCTP_FREE(liste); } sq = TAILQ_FIRST(&asoc->pending_reply_queue); while (sq) { TAILQ_REMOVE(&asoc->pending_reply_queue, sq, next); if (sq->data) { sctp_m_freem(sq->data); sq->data = NULL; } sctp_free_remote_addr(sq->whoFrom); sq->whoFrom = NULL; sq->stcb = NULL; /* Free the ctl entry */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, sq); SCTP_DECR_READQ_COUNT(); sq = TAILQ_FIRST(&asoc->pending_reply_queue); } chk = TAILQ_FIRST(&asoc->free_chunks); while (chk) { TAILQ_REMOVE(&asoc->free_chunks, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } ccnt++; SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); SCTP_DECR_CHK_COUNT(); atomic_subtract_int(&sctppcbinfo.ipi_free_chunks, 1); asoc->free_chunk_cnt--; chk = TAILQ_FIRST(&asoc->free_chunks); } /* pending send queue SHOULD be empty */ if (!TAILQ_EMPTY(&asoc->send_queue)) { chk = TAILQ_FIRST(&asoc->send_queue); while (chk) { TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } ccnt++; sctp_free_remote_addr(chk->whoTo); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); SCTP_DECR_CHK_COUNT(); chk = TAILQ_FIRST(&asoc->send_queue); } } /* if(ccnt) { printf("Freed %d from send_queue\n", ccnt); ccnt = 0; } */ /* sent queue SHOULD be empty */ if (!TAILQ_EMPTY(&asoc->sent_queue)) { chk = TAILQ_FIRST(&asoc->sent_queue); while (chk) { TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } ccnt++; sctp_free_remote_addr(chk->whoTo); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); SCTP_DECR_CHK_COUNT(); chk = TAILQ_FIRST(&asoc->sent_queue); } } /* if(ccnt) { printf("Freed %d from sent_queue\n", ccnt); ccnt = 0; } */ /* control queue MAY not be empty */ if (!TAILQ_EMPTY(&asoc->control_send_queue)) { chk = TAILQ_FIRST(&asoc->control_send_queue); while (chk) { TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } ccnt++; sctp_free_remote_addr(chk->whoTo); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); SCTP_DECR_CHK_COUNT(); chk = TAILQ_FIRST(&asoc->control_send_queue); } } /* if(ccnt) { printf("Freed %d from ctrl_queue\n", ccnt); ccnt = 0; } */ if (!TAILQ_EMPTY(&asoc->reasmqueue)) { chk = TAILQ_FIRST(&asoc->reasmqueue); while (chk) { TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_remote_addr(chk->whoTo); ccnt++; SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); SCTP_DECR_CHK_COUNT(); chk = TAILQ_FIRST(&asoc->reasmqueue); } } /* if(ccnt) { printf("Freed %d from reasm_queue\n", ccnt); ccnt = 0; } */ if (asoc->mapping_array) { SCTP_FREE(asoc->mapping_array); asoc->mapping_array = NULL; } /* the stream outs */ if (asoc->strmout) { SCTP_FREE(asoc->strmout); asoc->strmout = NULL; } asoc->streamoutcnt = 0; if (asoc->strmin) { struct sctp_queued_to_read *ctl; for (i = 0; i < asoc->streamincnt; i++) { if (!TAILQ_EMPTY(&asoc->strmin[i].inqueue)) { /* We have somethings on the streamin queue */ ctl = TAILQ_FIRST(&asoc->strmin[i].inqueue); while (ctl) { TAILQ_REMOVE(&asoc->strmin[i].inqueue, ctl, next); sctp_free_remote_addr(ctl->whoFrom); if (ctl->data) { sctp_m_freem(ctl->data); ctl->data = NULL; } /* * We don't free the address here * since all the net's were freed * above. */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, ctl); SCTP_DECR_READQ_COUNT(); ctl = TAILQ_FIRST(&asoc->strmin[i].inqueue); } } } SCTP_FREE(asoc->strmin); asoc->strmin = NULL; } asoc->streamincnt = 0; while (!TAILQ_EMPTY(&asoc->nets)) { net = TAILQ_FIRST(&asoc->nets); /* pull from list */ if ((sctppcbinfo.ipi_count_raddr == 0) || (prev == net)) { #ifdef INVARIANTS panic("no net's left alloc'ed, or list points to itself"); #endif break; } prev = net; TAILQ_REMOVE(&asoc->nets, net, sctp_next); sctp_free_remote_addr(net); } while (!SCTP_LIST_EMPTY(&asoc->sctp_restricted_addrs)) { laddr = LIST_FIRST(&asoc->sctp_restricted_addrs); sctp_remove_laddr(laddr); } /* pending asconf (address) parameters */ while (!TAILQ_EMPTY(&asoc->asconf_queue)) { aparam = TAILQ_FIRST(&asoc->asconf_queue); TAILQ_REMOVE(&asoc->asconf_queue, aparam, next); SCTP_FREE(aparam); } if (asoc->last_asconf_ack_sent != NULL) { sctp_m_freem(asoc->last_asconf_ack_sent); asoc->last_asconf_ack_sent = NULL; } /* clean up auth stuff */ if (asoc->local_hmacs) sctp_free_hmaclist(asoc->local_hmacs); if (asoc->peer_hmacs) sctp_free_hmaclist(asoc->peer_hmacs); if (asoc->local_auth_chunks) sctp_free_chunklist(asoc->local_auth_chunks); if (asoc->peer_auth_chunks) sctp_free_chunklist(asoc->peer_auth_chunks); sctp_free_authinfo(&asoc->authinfo); shared_key = LIST_FIRST(&asoc->shared_keys); while (shared_key) { LIST_REMOVE(shared_key, next); sctp_free_sharedkey(shared_key); shared_key = LIST_FIRST(&asoc->shared_keys); } /* Insert new items here :> */ /* Get rid of LOCK */ SCTP_TCB_LOCK_DESTROY(stcb); SCTP_TCB_SEND_LOCK_DESTROY(stcb); if (from_inpcbfree == SCTP_NORMAL_PROC) { SCTP_INP_INFO_WUNLOCK(); SCTP_INP_RLOCK(inp); } #ifdef SCTP_TRACK_FREED_ASOCS if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* now clean up the tasoc itself */ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); SCTP_DECR_ASOC_COUNT(); } else { LIST_INSERT_HEAD(&inp->sctp_asoc_free_list, stcb, sctp_tcblist); } #else SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); SCTP_DECR_ASOC_COUNT(); #endif if (from_inpcbfree == SCTP_NORMAL_PROC) { if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* * If its NOT the inp_free calling us AND sctp_close * as been called, we call back... */ SCTP_INP_RUNLOCK(inp); /* * This will start the kill timer (if we are the * lastone) since we hold an increment yet. But this * is the only safe way to do this since otherwise * if the socket closes at the same time we are here * we might collide in the cleanup. */ sctp_inpcb_free(inp, 0, 0); SCTP_INP_DECR_REF(inp); goto out_of; } else { /* The socket is still open. */ SCTP_INP_DECR_REF(inp); } } if (from_inpcbfree == SCTP_NORMAL_PROC) { SCTP_INP_RUNLOCK(inp); } out_of: /* destroyed the asoc */ #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 11); #endif return (1); } /* * determine if a destination is "reachable" based upon the addresses bound * to the current endpoint (e.g. only v4 or v6 currently bound) */ /* * FIX: if we allow assoc-level bindx(), then this needs to be fixed to use * assoc level v4/v6 flags, as the assoc *may* not have the same address * types bound as its endpoint */ int sctp_destination_is_reachable(struct sctp_tcb *stcb, struct sockaddr *destaddr) { struct sctp_inpcb *inp; int answer; /* * No locks here, the TCB, in all cases is already locked and an * assoc is up. There is either a INP lock by the caller applied (in * asconf case when deleting an address) or NOT in the HB case, * however if HB then the INP increment is up and the INP will not * be removed (on top of the fact that we have a TCB lock). So we * only want to read the sctp_flags, which is either bound-all or * not.. no protection needed since once an assoc is up you can't be * changing your binding. */ inp = stcb->sctp_ep; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* if bound all, destination is not restricted */ /* * RRS: Question during lock work: Is this correct? If you * are bound-all you still might need to obey the V4--V6 * flags??? IMO this bound-all stuff needs to be removed! */ return (1); } /* NOTE: all "scope" checks are done when local addresses are added */ if (destaddr->sa_family == AF_INET6) { answer = inp->ip_inp.inp.inp_vflag & INP_IPV6; } else if (destaddr->sa_family == AF_INET) { answer = inp->ip_inp.inp.inp_vflag & INP_IPV4; } else { /* invalid family, so it's unreachable */ answer = 0; } return (answer); } /* * update the inp_vflags on an endpoint */ static void sctp_update_ep_vflag(struct sctp_inpcb *inp) { struct sctp_laddr *laddr; /* first clear the flag */ inp->ip_inp.inp.inp_vflag = 0; /* set the flag based on addresses on the ep list */ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("An ounce of prevention is worth a pound of cure\n"); } #endif /* SCTP_DEBUG */ continue; } if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) { continue; } if (laddr->ifa->address.sa.sa_family == AF_INET6) { inp->ip_inp.inp.inp_vflag |= INP_IPV6; } else if (laddr->ifa->address.sa.sa_family == AF_INET) { inp->ip_inp.inp.inp_vflag |= INP_IPV4; } } } /* * Add the address to the endpoint local address list There is nothing to be * done if we are bound to all addresses */ int sctp_add_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa, uint32_t action) { struct sctp_laddr *laddr; int fnd, error; fnd = 0; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* You are already bound to all. You have it already */ return (0); } if (ifa->address.sa.sa_family == AF_INET6) { if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-useable addr. */ return (-1); } } /* first, is it already present? */ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == ifa) { fnd = 1; break; } } if (fnd == 0) { /* Not in the ep list */ error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, action); if (error != 0) return (error); inp->laddr_count++; /* update inp_vflag flags */ if (ifa->address.sa.sa_family == AF_INET6) { inp->ip_inp.inp.inp_vflag |= INP_IPV6; } else if (ifa->address.sa.sa_family == AF_INET) { inp->ip_inp.inp.inp_vflag |= INP_IPV4; } } return (0); } /* * select a new (hopefully reachable) destination net (should only be used * when we deleted an ep addr that is the only usable source address to reach * the destination net) */ static void sctp_select_primary_destination(struct sctp_tcb *stcb) { struct sctp_nets *net; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { /* for now, we'll just pick the first reachable one we find */ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) continue; if (sctp_destination_is_reachable(stcb, (struct sockaddr *)&net->ro._l_addr)) { /* found a reachable destination */ stcb->asoc.primary_destination = net; } } /* I can't there from here! ...we're gonna die shortly... */ } /* * Delete the address from the endpoint local address list There is nothing * to be done if we are bound to all addresses */ int sctp_del_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa) { struct sctp_laddr *laddr; int fnd; fnd = 0; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* You are already bound to all. You have it already */ return (EINVAL); } LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == ifa) { fnd = 1; break; } } if (fnd && (inp->laddr_count < 2)) { /* can't delete unless there are at LEAST 2 addresses */ return (-1); } if (fnd) { /* * clean up any use of this address go through our * associations and clear any last_used_address that match * this one for each assoc, see if a new primary_destination * is needed */ struct sctp_tcb *stcb; /* clean up "next_addr_touse" */ if (inp->next_addr_touse == laddr) /* delete this address */ inp->next_addr_touse = NULL; /* clean up "last_used_address" */ LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { struct sctp_nets *net; SCTP_TCB_LOCK(stcb); if (stcb->asoc.last_used_address == laddr) /* delete this address */ stcb->asoc.last_used_address = NULL; /* * Now spin through all the nets and purge any ref * to laddr */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->ro._s_addr && (net->ro._s_addr->ifa == laddr->ifa)) { /* Yep, purge src address selected */ sctp_rtentry_t *rt; /* delete this address if cached */ rt = net->ro.ro_rt; if (rt != NULL) { RTFREE(rt); net->ro.ro_rt = NULL; } sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } } SCTP_TCB_UNLOCK(stcb); } /* for each tcb */ /* remove it from the ep list */ sctp_remove_laddr(laddr); inp->laddr_count--; /* update inp_vflag flags */ sctp_update_ep_vflag(inp); } return (0); } /* * Add the addr to the TCB local address list For the BOUNDALL or dynamic * case, this is a "pending" address list (eg. addresses waiting for an * ASCONF-ACK response) For the subset binding, static case, this is a * "valid" address list */ int sctp_add_local_addr_assoc(struct sctp_tcb *stcb, struct sctp_ifa *ifa, int restricted_list) { struct sctp_inpcb *inp; struct sctp_laddr *laddr; struct sctpladdr *list; int error; /* * Assumes TCB is locked.. and possibly the INP. May need to * confirm/fix that if we need it and is not the case. */ list = &stcb->asoc.sctp_restricted_addrs; inp = stcb->sctp_ep; if (ifa->address.sa.sa_family == AF_INET6) { if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-existent addr. */ return (-1); } } /* does the address already exist? */ LIST_FOREACH(laddr, list, sctp_nxt_addr) { if (laddr->ifa == ifa) { return (-1); } } /* add to the list */ error = sctp_insert_laddr(list, ifa, 0); if (error != 0) return (error); return (0); } /* * insert an laddr entry with the given ifa for the desired list */ int sctp_insert_laddr(struct sctpladdr *list, struct sctp_ifa *ifa, uint32_t act) { struct sctp_laddr *laddr; laddr = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr, struct sctp_laddr); if (laddr == NULL) { /* out of memory? */ return (EINVAL); } SCTP_INCR_LADDR_COUNT(); bzero(laddr, sizeof(*laddr)); laddr->ifa = ifa; laddr->action = act; atomic_add_int(&ifa->refcount, 1); /* insert it */ LIST_INSERT_HEAD(list, laddr, sctp_nxt_addr); return (0); } /* * Remove an laddr entry from the local address list (on an assoc) */ void sctp_remove_laddr(struct sctp_laddr *laddr) { /* remove from the list */ LIST_REMOVE(laddr, sctp_nxt_addr); sctp_free_ifa(laddr->ifa); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_laddr, laddr); SCTP_DECR_LADDR_COUNT(); } /* * Remove an address from the TCB local address list */ int sctp_del_local_addr_assoc(struct sctp_tcb *stcb, struct sctp_ifa *ifa) { struct sctp_inpcb *inp; struct sctp_laddr *laddr; /* * This is called by asconf work. It is assumed that a) The TCB is * locked and b) The INP is locked. This is true in as much as I can * trace through the entry asconf code where I did these locks. * Again, the ASCONF code is a bit different in that it does lock * the INP during its work often times. This must be since we don't * want other proc's looking up things while what they are looking * up is changing :-D */ inp = stcb->sctp_ep; /* if subset bound and don't allow ASCONF's, can't delete last */ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) && (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF) == 0)) { if (stcb->asoc.numnets < 2) { /* can't delete last address */ return (-1); } } LIST_FOREACH(laddr, &stcb->asoc.sctp_restricted_addrs, sctp_nxt_addr) { /* remove the address if it exists */ if (laddr->ifa == NULL) continue; if (laddr->ifa == ifa) { sctp_remove_laddr(laddr); return (0); } } /* address not found! */ return (-1); } static char sctp_pcb_initialized = 0; /* * Temporarily remove for __APPLE__ until we use the Tiger equivalents */ /* sysctl */ static int sctp_max_number_of_assoc = SCTP_MAX_NUM_OF_ASOC; static int sctp_scale_up_for_address = SCTP_SCALE_FOR_ADDR; void sctp_pcb_init() { /* * SCTP initialization for the PCB structures should be called by * the sctp_init() funciton. */ int i; if (sctp_pcb_initialized != 0) { /* error I was called twice */ return; } sctp_pcb_initialized = 1; bzero(&sctpstat, sizeof(struct sctpstat)); - SCTP_GETTIME_TIMEVAL(&sctpstat.sctps_discontinuitytime); + (void)SCTP_GETTIME_TIMEVAL(&sctpstat.sctps_discontinuitytime); /* init the empty list of (All) Endpoints */ LIST_INIT(&sctppcbinfo.listhead); /* init the iterator head */ TAILQ_INIT(&sctppcbinfo.iteratorhead); /* init the hash table of endpoints */ TUNABLE_INT_FETCH("net.inet.sctp.tcbhashsize", &sctp_hashtblsize); TUNABLE_INT_FETCH("net.inet.sctp.pcbhashsize", &sctp_pcbtblsize); TUNABLE_INT_FETCH("net.inet.sctp.chunkscale", &sctp_chunkscale); sctppcbinfo.sctp_asochash = SCTP_HASH_INIT((sctp_hashtblsize * 31), &sctppcbinfo.hashasocmark); sctppcbinfo.sctp_ephash = SCTP_HASH_INIT(sctp_hashtblsize, &sctppcbinfo.hashmark); sctppcbinfo.sctp_tcpephash = SCTP_HASH_INIT(sctp_hashtblsize, &sctppcbinfo.hashtcpmark); sctppcbinfo.hashtblsize = sctp_hashtblsize; /* init the small hash table we use to track restarted asoc's */ sctppcbinfo.sctp_restarthash = SCTP_HASH_INIT(SCTP_STACK_VTAG_HASH_SIZE, &sctppcbinfo.hashrestartmark); sctppcbinfo.sctp_vrfhash = SCTP_HASH_INIT(SCTP_SIZE_OF_VRF_HASH, &sctppcbinfo.hashvrfmark); /* init the zones */ /* * FIX ME: Should check for NULL returns, but if it does fail we are * doomed to panic anyways... add later maybe. */ SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_ep, "sctp_ep", sizeof(struct sctp_inpcb), maxsockets); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_asoc, "sctp_asoc", sizeof(struct sctp_tcb), sctp_max_number_of_assoc); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_laddr, "sctp_laddr", sizeof(struct sctp_laddr), (sctp_max_number_of_assoc * sctp_scale_up_for_address)); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_net, "sctp_raddr", sizeof(struct sctp_nets), (sctp_max_number_of_assoc * sctp_scale_up_for_address)); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_chunk, "sctp_chunk", sizeof(struct sctp_tmit_chunk), (sctp_max_number_of_assoc * sctp_chunkscale)); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_readq, "sctp_readq", sizeof(struct sctp_queued_to_read), (sctp_max_number_of_assoc * sctp_chunkscale)); SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_strmoq, "sctp_stream_msg_out", sizeof(struct sctp_stream_queue_pending), (sctp_max_number_of_assoc * sctp_chunkscale)); /* Master Lock INIT for info structure */ SCTP_INP_INFO_LOCK_INIT(); SCTP_STATLOG_INIT_LOCK(); SCTP_ITERATOR_LOCK_INIT(); SCTP_IPI_COUNT_INIT(); SCTP_IPI_ADDR_INIT(); SCTP_IPI_ITERATOR_WQ_INIT(); LIST_INIT(&sctppcbinfo.addr_wq); /* not sure if we need all the counts */ sctppcbinfo.ipi_count_ep = 0; /* assoc/tcb zone info */ sctppcbinfo.ipi_count_asoc = 0; /* local addrlist zone info */ sctppcbinfo.ipi_count_laddr = 0; /* remote addrlist zone info */ sctppcbinfo.ipi_count_raddr = 0; /* chunk info */ sctppcbinfo.ipi_count_chunk = 0; /* socket queue zone info */ sctppcbinfo.ipi_count_readq = 0; /* stream out queue cont */ sctppcbinfo.ipi_count_strmoq = 0; sctppcbinfo.ipi_free_strmoq = 0; sctppcbinfo.ipi_free_chunks = 0; SCTP_OS_TIMER_INIT(&sctppcbinfo.addr_wq_timer.timer); /* Init the TIMEWAIT list */ for (i = 0; i < SCTP_STACK_VTAG_HASH_SIZE; i++) { LIST_INIT(&sctppcbinfo.vtag_timewait[i]); } #if defined(SCTP_USE_THREAD_BASED_ITERATOR) sctppcbinfo.iterator_running = 0; sctp_startup_iterator(); #endif /* * INIT the default VRF which for BSD is the only one, other O/S's * may have more. But initially they must start with one and then * add the VRF's as addresses are added. */ sctp_init_vrf_list(SCTP_DEFAULT_VRF); } int sctp_load_addresses_from_init(struct sctp_tcb *stcb, struct mbuf *m, int iphlen, int offset, int limit, struct sctphdr *sh, struct sockaddr *altsa) { /* * grub through the INIT pulling addresses and loading them to the * nets structure in the asoc. The from address in the mbuf should * also be loaded (if it is not already). This routine can be called * with either INIT or INIT-ACK's as long as the m points to the IP * packet and the offset points to the beginning of the parameters. */ struct sctp_inpcb *inp, *l_inp; struct sctp_nets *net, *net_tmp; struct ip *iph; struct sctp_paramhdr *phdr, parm_buf; struct sctp_tcb *stcb_tmp; uint16_t ptype, plen; struct sockaddr *sa; struct sockaddr_storage dest_store; struct sockaddr *local_sa = (struct sockaddr *)&dest_store; struct sockaddr_in sin; struct sockaddr_in6 sin6; uint8_t random_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_random *p_random = NULL; uint16_t random_len = 0; uint8_t hmacs_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_hmac_algo *hmacs = NULL; uint16_t hmacs_len = 0; uint8_t chunks_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_chunk_list *chunks = NULL; uint16_t num_chunks = 0; sctp_key_t *new_key; uint32_t keylen; int got_random = 0, got_hmacs = 0, got_chklist = 0; /* First get the destination address setup too. */ memset(&sin, 0, sizeof(sin)); memset(&sin6, 0, sizeof(sin6)); sin.sin_family = AF_INET; sin.sin_len = sizeof(sin); sin.sin_port = stcb->rport; sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(struct sockaddr_in6); sin6.sin6_port = stcb->rport; if (altsa == NULL) { iph = mtod(m, struct ip *); if (iph->ip_v == IPVERSION) { /* its IPv4 */ struct sockaddr_in *sin_2; sin_2 = (struct sockaddr_in *)(local_sa); memset(sin_2, 0, sizeof(sin)); sin_2->sin_family = AF_INET; sin_2->sin_len = sizeof(sin); sin_2->sin_port = sh->dest_port; sin_2->sin_addr.s_addr = iph->ip_dst.s_addr; sin.sin_addr = iph->ip_src; sa = (struct sockaddr *)&sin; } else if (iph->ip_v == (IPV6_VERSION >> 4)) { /* its IPv6 */ struct ip6_hdr *ip6; struct sockaddr_in6 *sin6_2; ip6 = mtod(m, struct ip6_hdr *); sin6_2 = (struct sockaddr_in6 *)(local_sa); memset(sin6_2, 0, sizeof(sin6)); sin6_2->sin6_family = AF_INET6; sin6_2->sin6_len = sizeof(struct sockaddr_in6); sin6_2->sin6_port = sh->dest_port; sin6.sin6_addr = ip6->ip6_src; sa = (struct sockaddr *)&sin6; } else { sa = NULL; } } else { /* * For cookies we use the src address NOT from the packet * but from the original INIT */ sa = altsa; } /* Turn off ECN until we get through all params */ stcb->asoc.ecn_allowed = 0; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { /* mark all addresses that we have currently on the list */ net->dest_state |= SCTP_ADDR_NOT_IN_ASSOC; } /* does the source address already exist? if so skip it */ l_inp = inp = stcb->sctp_ep; atomic_add_int(&stcb->asoc.refcnt, 1); stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net_tmp, local_sa, stcb); atomic_add_int(&stcb->asoc.refcnt, -1); if ((stcb_tmp == NULL && inp == stcb->sctp_ep) || inp == NULL) { /* we must add the source address */ /* no scope set here since we have a tcb already. */ if ((sa->sa_family == AF_INET) && (stcb->asoc.ipv4_addr_legal)) { if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_2)) { return (-1); } } else if ((sa->sa_family == AF_INET6) && (stcb->asoc.ipv6_addr_legal)) { if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_3)) { return (-2); } } } else { if (net_tmp != NULL && stcb_tmp == stcb) { net_tmp->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC; } else if (stcb_tmp != stcb) { /* It belongs to another association? */ SCTP_TCB_UNLOCK(stcb_tmp); return (-3); } } if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-4); } /* now we must go through each of the params. */ phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); while (phdr) { ptype = ntohs(phdr->param_type); plen = ntohs(phdr->param_length); /* * printf("ptype => %0x, plen => %d\n", (uint32_t)ptype, * (int)plen); */ if (offset + plen > limit) { break; } if (plen == 0) { break; } if (ptype == SCTP_IPV4_ADDRESS) { if (stcb->asoc.ipv4_addr_legal) { struct sctp_ipv4addr_param *p4, p4_buf; /* ok get the v4 address and check/add */ phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf)); if (plen != sizeof(struct sctp_ipv4addr_param) || phdr == NULL) { return (-5); } p4 = (struct sctp_ipv4addr_param *)phdr; sin.sin_addr.s_addr = p4->addr; if (IN_MULTICAST(sin.sin_addr.s_addr)) { /* Skip multi-cast addresses */ goto next_param; } if ((sin.sin_addr.s_addr == INADDR_BROADCAST) || (sin.sin_addr.s_addr == INADDR_ANY)) { goto next_param; } sa = (struct sockaddr *)&sin; inp = stcb->sctp_ep; atomic_add_int(&stcb->asoc.refcnt, 1); stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net, local_sa, stcb); atomic_add_int(&stcb->asoc.refcnt, -1); if ((stcb_tmp == NULL && inp == stcb->sctp_ep) || inp == NULL) { /* we must add the source address */ /* * no scope set since we have a tcb * already */ /* * we must validate the state again * here */ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-7); } if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_4)) { return (-8); } } else if (stcb_tmp == stcb) { if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-10); } if (net != NULL) { /* clear flag */ net->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC; } } else { /* * strange, address is in another * assoc? straighten out locks. */ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-12); } return (-13); } } } else if (ptype == SCTP_IPV6_ADDRESS) { if (stcb->asoc.ipv6_addr_legal) { /* ok get the v6 address and check/add */ struct sctp_ipv6addr_param *p6, p6_buf; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf)); if (plen != sizeof(struct sctp_ipv6addr_param) || phdr == NULL) { return (-14); } p6 = (struct sctp_ipv6addr_param *)phdr; memcpy((caddr_t)&sin6.sin6_addr, p6->addr, sizeof(p6->addr)); if (IN6_IS_ADDR_MULTICAST(&sin6.sin6_addr)) { /* Skip multi-cast addresses */ goto next_param; } if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) { /* * Link local make no sense without * scope */ goto next_param; } sa = (struct sockaddr *)&sin6; inp = stcb->sctp_ep; atomic_add_int(&stcb->asoc.refcnt, 1); stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net, local_sa, stcb); atomic_add_int(&stcb->asoc.refcnt, -1); if (stcb_tmp == NULL && (inp == stcb->sctp_ep || inp == NULL)) { /* * we must validate the state again * here */ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-16); } /* * we must add the address, no scope * set */ if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_5)) { return (-17); } } else if (stcb_tmp == stcb) { /* * we must validate the state again * here */ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-19); } if (net != NULL) { /* clear flag */ net->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC; } } else { /* * strange, address is in another * assoc? straighten out locks. */ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-21); } return (-22); } } } else if (ptype == SCTP_ECN_CAPABLE) { stcb->asoc.ecn_allowed = 1; } else if (ptype == SCTP_ULP_ADAPTATION) { if (stcb->asoc.state != SCTP_STATE_OPEN) { struct sctp_adaptation_layer_indication ai, *aip; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&ai, sizeof(ai)); aip = (struct sctp_adaptation_layer_indication *)phdr; - sctp_ulp_notify(SCTP_NOTIFY_ADAPTATION_INDICATION, - stcb, ntohl(aip->indication), NULL); + if (aip) { + sctp_ulp_notify(SCTP_NOTIFY_ADAPTATION_INDICATION, + stcb, ntohl(aip->indication), NULL); + } } } else if (ptype == SCTP_SET_PRIM_ADDR) { struct sctp_asconf_addr_param lstore, *fee; struct sctp_asconf_addrv4_param *fii; int lptype; struct sockaddr *lsa = NULL; stcb->asoc.peer_supports_asconf = 1; if (plen > sizeof(lstore)) { return (-23); } phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&lstore, min(plen, sizeof(lstore))); if (phdr == NULL) { return (-24); } fee = (struct sctp_asconf_addr_param *)phdr; lptype = ntohs(fee->addrp.ph.param_type); if (lptype == SCTP_IPV4_ADDRESS) { if (plen != sizeof(struct sctp_asconf_addrv4_param)) { printf("Sizeof setprim in init/init ack not %d but %d - ignored\n", (int)sizeof(struct sctp_asconf_addrv4_param), plen); } else { fii = (struct sctp_asconf_addrv4_param *)fee; sin.sin_addr.s_addr = fii->addrp.addr; lsa = (struct sockaddr *)&sin; } } else if (lptype == SCTP_IPV6_ADDRESS) { if (plen != sizeof(struct sctp_asconf_addr_param)) { printf("Sizeof setprim (v6) in init/init ack not %d but %d - ignored\n", (int)sizeof(struct sctp_asconf_addr_param), plen); } else { memcpy(sin6.sin6_addr.s6_addr, fee->addrp.addr, sizeof(fee->addrp.addr)); lsa = (struct sockaddr *)&sin6; } } if (lsa) { sctp_set_primary_addr(stcb, sa, NULL); } } else if (ptype == SCTP_PRSCTP_SUPPORTED) { /* Peer supports pr-sctp */ stcb->asoc.peer_supports_prsctp = 1; } else if (ptype == SCTP_SUPPORTED_CHUNK_EXT) { /* A supported extension chunk */ struct sctp_supported_chunk_types_param *pr_supported; uint8_t local_store[SCTP_PARAM_BUFFER_SIZE]; int num_ent, i; phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)&local_store, min(sizeof(local_store), plen)); if (phdr == NULL) { return (-25); } stcb->asoc.peer_supports_asconf = 0; stcb->asoc.peer_supports_prsctp = 0; stcb->asoc.peer_supports_pktdrop = 0; stcb->asoc.peer_supports_strreset = 0; stcb->asoc.peer_supports_auth = 0; pr_supported = (struct sctp_supported_chunk_types_param *)phdr; num_ent = plen - sizeof(struct sctp_paramhdr); for (i = 0; i < num_ent; i++) { switch (pr_supported->chunk_types[i]) { case SCTP_ASCONF: case SCTP_ASCONF_ACK: stcb->asoc.peer_supports_asconf = 1; break; case SCTP_FORWARD_CUM_TSN: stcb->asoc.peer_supports_prsctp = 1; break; case SCTP_PACKET_DROPPED: stcb->asoc.peer_supports_pktdrop = 1; break; case SCTP_STREAM_RESET: stcb->asoc.peer_supports_strreset = 1; break; case SCTP_AUTHENTICATION: stcb->asoc.peer_supports_auth = 1; break; default: /* one I have not learned yet */ break; } } } else if (ptype == SCTP_ECN_NONCE_SUPPORTED) { /* Peer supports ECN-nonce */ stcb->asoc.peer_supports_ecn_nonce = 1; stcb->asoc.ecn_nonce_allowed = 1; } else if (ptype == SCTP_RANDOM) { if (plen > sizeof(random_store)) break; if (got_random) { /* already processed a RANDOM */ goto next_param; } phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)random_store, min(sizeof(random_store), plen)); if (phdr == NULL) return (-26); p_random = (struct sctp_auth_random *)phdr; random_len = plen - sizeof(*p_random); /* enforce the random length */ if (random_len != SCTP_AUTH_RANDOM_SIZE_REQUIRED) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_AUTH1) printf("SCTP: invalid RANDOM len\n"); #endif return (-27); } got_random = 1; } else if (ptype == SCTP_HMAC_LIST) { int num_hmacs; int i; if (plen > sizeof(hmacs_store)) break; if (got_hmacs) { /* already processed a HMAC list */ goto next_param; } phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)hmacs_store, min(plen, sizeof(hmacs_store))); if (phdr == NULL) return (-28); hmacs = (struct sctp_auth_hmac_algo *)phdr; hmacs_len = plen - sizeof(*hmacs); num_hmacs = hmacs_len / sizeof(hmacs->hmac_ids[0]); /* validate the hmac list */ if (sctp_verify_hmac_param(hmacs, num_hmacs)) { return (-29); } if (stcb->asoc.peer_hmacs != NULL) sctp_free_hmaclist(stcb->asoc.peer_hmacs); stcb->asoc.peer_hmacs = sctp_alloc_hmaclist(num_hmacs); if (stcb->asoc.peer_hmacs != NULL) { for (i = 0; i < num_hmacs; i++) { - sctp_auth_add_hmacid(stcb->asoc.peer_hmacs, + (void)sctp_auth_add_hmacid(stcb->asoc.peer_hmacs, ntohs(hmacs->hmac_ids[i])); } } got_hmacs = 1; } else if (ptype == SCTP_CHUNK_LIST) { int i; if (plen > sizeof(chunks_store)) break; if (got_chklist) { /* already processed a Chunks list */ goto next_param; } phdr = sctp_get_next_param(m, offset, (struct sctp_paramhdr *)chunks_store, min(plen, sizeof(chunks_store))); if (phdr == NULL) return (-30); chunks = (struct sctp_auth_chunk_list *)phdr; num_chunks = plen - sizeof(*chunks); if (stcb->asoc.peer_auth_chunks != NULL) sctp_clear_chunklist(stcb->asoc.peer_auth_chunks); else stcb->asoc.peer_auth_chunks = sctp_alloc_chunklist(); for (i = 0; i < num_chunks; i++) { - sctp_auth_add_chunk(chunks->chunk_types[i], + (void)sctp_auth_add_chunk(chunks->chunk_types[i], stcb->asoc.peer_auth_chunks); } got_chklist = 1; } else if ((ptype == SCTP_HEARTBEAT_INFO) || (ptype == SCTP_STATE_COOKIE) || (ptype == SCTP_UNRECOG_PARAM) || (ptype == SCTP_COOKIE_PRESERVE) || (ptype == SCTP_SUPPORTED_ADDRTYPE) || (ptype == SCTP_ADD_IP_ADDRESS) || (ptype == SCTP_DEL_IP_ADDRESS) || (ptype == SCTP_ERROR_CAUSE_IND) || (ptype == SCTP_SUCCESS_REPORT)) { /* don't care */ ; } else { if ((ptype & 0x8000) == 0x0000) { /* * must stop processing the rest of the * param's. Any report bits were handled * with the call to * sctp_arethere_unrecognized_parameters() * when the INIT or INIT-ACK was first seen. */ break; } } next_param: offset += SCTP_SIZE32(plen); if (offset >= limit) { break; } phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); } /* Now check to see if we need to purge any addresses */ for (net = TAILQ_FIRST(&stcb->asoc.nets); net != NULL; net = net_tmp) { net_tmp = TAILQ_NEXT(net, sctp_next); if ((net->dest_state & SCTP_ADDR_NOT_IN_ASSOC) == SCTP_ADDR_NOT_IN_ASSOC) { /* This address has been removed from the asoc */ /* remove and free it */ stcb->asoc.numnets--; TAILQ_REMOVE(&stcb->asoc.nets, net, sctp_next); sctp_free_remote_addr(net); if (net == stcb->asoc.primary_destination) { stcb->asoc.primary_destination = NULL; sctp_select_primary_destination(stcb); } } } /* validate authentication required parameters */ if (got_random && got_hmacs) { stcb->asoc.peer_supports_auth = 1; } else { stcb->asoc.peer_supports_auth = 0; } if (!stcb->asoc.peer_supports_auth && got_chklist) { /* peer does not support auth but sent a chunks list? */ return (-31); } if (!sctp_asconf_auth_nochk && stcb->asoc.peer_supports_asconf && !stcb->asoc.peer_supports_auth) { /* peer supports asconf but not auth? */ return (-32); } /* concatenate the full random key */ #ifdef SCTP_AUTH_DRAFT_04 keylen = random_len; new_key = sctp_alloc_key(keylen); if (new_key != NULL) { /* copy in the RANDOM */ if (p_random != NULL) bcopy(p_random->random_data, new_key->key, random_len); } #else keylen = sizeof(*p_random) + random_len + sizeof(*chunks) + num_chunks + sizeof(*hmacs) + hmacs_len; new_key = sctp_alloc_key(keylen); if (new_key != NULL) { /* copy in the RANDOM */ if (p_random != NULL) { keylen = sizeof(*p_random) + random_len; bcopy(p_random, new_key->key, keylen); } /* append in the AUTH chunks */ if (chunks != NULL) { bcopy(chunks, new_key->key + keylen, sizeof(*chunks) + num_chunks); keylen += sizeof(*chunks) + num_chunks; } /* append in the HMACs */ if (hmacs != NULL) { bcopy(hmacs, new_key->key + keylen, sizeof(*hmacs) + hmacs_len); } } #endif else { /* failed to get memory for the key */ return (-33); } if (stcb->asoc.authinfo.peer_random != NULL) sctp_free_key(stcb->asoc.authinfo.peer_random); stcb->asoc.authinfo.peer_random = new_key; #ifdef SCTP_AUTH_DRAFT_04 /* don't include the chunks and hmacs for draft -04 */ stcb->asoc.authinfo.peer_random->keylen = random_len; #endif sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.assoc_keyid); sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.recv_keyid); return (0); } int sctp_set_primary_addr(struct sctp_tcb *stcb, struct sockaddr *sa, struct sctp_nets *net) { /* make sure the requested primary address exists in the assoc */ if (net == NULL && sa) net = sctp_findnet(stcb, sa); if (net == NULL) { /* didn't find the requested primary address! */ return (-1); } else { /* set the primary address */ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { /* Must be confirmed, so queue to set */ net->dest_state |= SCTP_ADDR_REQ_PRIMARY; return (0); } stcb->asoc.primary_destination = net; net->dest_state &= ~SCTP_ADDR_WAS_PRIMARY; net = TAILQ_FIRST(&stcb->asoc.nets); if (net != stcb->asoc.primary_destination) { /* * first one on the list is NOT the primary * sctp_cmpaddr() is much more efficent if the * primary is the first on the list, make it so. */ TAILQ_REMOVE(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); TAILQ_INSERT_HEAD(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next); } return (0); } } int sctp_is_vtag_good(struct sctp_inpcb *inp, uint32_t tag, struct timeval *now) { /* * This function serves two purposes. It will see if a TAG can be * re-used and return 1 for yes it is ok and 0 for don't use that * tag. A secondary function it will do is purge out old tags that * can be removed. */ struct sctpasochead *head; struct sctpvtaghead *chain; struct sctp_tagblock *twait_block; struct sctp_tcb *stcb; int i; SCTP_INP_INFO_WLOCK(); chain = &sctppcbinfo.vtag_timewait[(tag % SCTP_STACK_VTAG_HASH_SIZE)]; /* First is the vtag in use ? */ head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(tag, sctppcbinfo.hashasocmark)]; if (head == NULL) { goto check_restart; } LIST_FOREACH(stcb, head, sctp_asocs) { if (stcb->asoc.my_vtag == tag) { /* * We should remove this if and return 0 always if * we want vtags unique across all endpoints. For * now within a endpoint is ok. */ if (inp == stcb->sctp_ep) { /* bad tag, in use */ SCTP_INP_INFO_WUNLOCK(); return (0); } } } check_restart: /* Now lets check the restart hash */ head = &sctppcbinfo.sctp_restarthash[SCTP_PCBHASH_ASOC(tag, sctppcbinfo.hashrestartmark)]; if (head == NULL) { goto check_time_wait; } LIST_FOREACH(stcb, head, sctp_tcbrestarhash) { if (stcb->asoc.assoc_id == tag) { /* candidate */ if (inp == stcb->sctp_ep) { /* bad tag, in use */ SCTP_INP_INFO_WUNLOCK(); return (0); } } } check_time_wait: /* Now what about timed wait ? */ if (!SCTP_LIST_EMPTY(chain)) { /* * Block(s) are present, lets see if we have this tag in the * list */ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) { if (twait_block->vtag_block[i].v_tag == 0) { /* not used */ continue; } else if ((long)twait_block->vtag_block[i].tv_sec_at_expire > now->tv_sec) { /* Audit expires this guy */ twait_block->vtag_block[i].tv_sec_at_expire = 0; twait_block->vtag_block[i].v_tag = 0; } else if (twait_block->vtag_block[i].v_tag == tag) { /* Bad tag, sorry :< */ SCTP_INP_INFO_WUNLOCK(); return (0); } } } } /* Not found, ok to use the tag */ SCTP_INP_INFO_WUNLOCK(); return (1); } static sctp_assoc_t reneged_asoc_ids[256]; static uint8_t reneged_at = 0; static void sctp_drain_mbufs(struct sctp_inpcb *inp, struct sctp_tcb *stcb) { /* * We must hunt this association for MBUF's past the cumack (i.e. * out of order data that we can renege on). */ struct sctp_association *asoc; struct sctp_tmit_chunk *chk, *nchk; uint32_t cumulative_tsn_p1, tsn; struct sctp_queued_to_read *ctl, *nctl; int cnt, strmat, gap; /* We look for anything larger than the cum-ack + 1 */ SCTP_STAT_INCR(sctps_protocol_drain_calls); if (sctp_do_drain == 0) { return; } asoc = &stcb->asoc; if (asoc->cumulative_tsn == asoc->highest_tsn_inside_map) { /* none we can reneg on. */ return; } SCTP_STAT_INCR(sctps_protocol_drains_done); cumulative_tsn_p1 = asoc->cumulative_tsn + 1; cnt = 0; /* First look in the re-assembly queue */ chk = TAILQ_FIRST(&asoc->reasmqueue); while (chk) { /* Get the next one */ nchk = TAILQ_NEXT(chk, sctp_next); if (compare_with_wrap(chk->rec.data.TSN_seq, cumulative_tsn_p1, MAX_TSN)) { /* Yep it is above cum-ack */ cnt++; tsn = chk->rec.data.TSN_seq; if (tsn >= asoc->mapping_array_base_tsn) { gap = tsn - asoc->mapping_array_base_tsn; } else { gap = (MAX_TSN - asoc->mapping_array_base_tsn) + tsn + 1; } asoc->size_on_reasm_queue = sctp_sbspace_sub(asoc->size_on_reasm_queue, chk->send_size); sctp_ucount_decr(asoc->cnt_on_reasm_queue); SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap); TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } sctp_free_remote_addr(chk->whoTo); sctp_free_a_chunk(stcb, chk); } chk = nchk; } /* Ok that was fun, now we will drain all the inbound streams? */ for (strmat = 0; strmat < asoc->streamincnt; strmat++) { ctl = TAILQ_FIRST(&asoc->strmin[strmat].inqueue); while (ctl) { nctl = TAILQ_NEXT(ctl, next); if (compare_with_wrap(ctl->sinfo_tsn, cumulative_tsn_p1, MAX_TSN)) { /* Yep it is above cum-ack */ cnt++; tsn = ctl->sinfo_tsn; if (tsn >= asoc->mapping_array_base_tsn) { gap = tsn - asoc->mapping_array_base_tsn; } else { gap = (MAX_TSN - asoc->mapping_array_base_tsn) + tsn + 1; } asoc->size_on_all_streams = sctp_sbspace_sub(asoc->size_on_all_streams, ctl->length); sctp_ucount_decr(asoc->cnt_on_all_streams); SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap); TAILQ_REMOVE(&asoc->strmin[strmat].inqueue, ctl, next); if (ctl->data) { sctp_m_freem(ctl->data); ctl->data = NULL; } sctp_free_remote_addr(ctl->whoFrom); SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, ctl); SCTP_DECR_READQ_COUNT(); } ctl = nctl; } } /* * Question, should we go through the delivery queue? The only * reason things are on here is the app not reading OR a p-d-api up. * An attacker COULD send enough in to initiate the PD-API and then * send a bunch of stuff to other streams... these would wind up on * the delivery queue.. and then we would not get to them. But in * order to do this I then have to back-track and un-deliver * sequence numbers in streams.. el-yucko. I think for now we will * NOT look at the delivery queue and leave it to be something to * consider later. An alternative would be to abort the P-D-API with * a notification and then deliver the data.... Or another method * might be to keep track of how many times the situation occurs and * if we see a possible attack underway just abort the association. */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { if (cnt) { printf("Freed %d chunks from reneg harvest\n", cnt); } } #endif /* SCTP_DEBUG */ if (cnt) { /* * Now do we need to find a new * asoc->highest_tsn_inside_map? */ if (asoc->highest_tsn_inside_map >= asoc->mapping_array_base_tsn) { gap = asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn; } else { gap = (MAX_TSN - asoc->mapping_array_base_tsn) + asoc->highest_tsn_inside_map + 1; } if (gap >= (asoc->mapping_array_size << 3)) { /* * Something bad happened or cum-ack and high were * behind the base, but if so earlier checks should * have found NO data... wierd... we will start at * end of mapping array. */ printf("Gap was larger than array?? %d set to max:%d maparraymax:%x\n", (int)gap, (int)(asoc->mapping_array_size << 3), (int)asoc->highest_tsn_inside_map); gap = asoc->mapping_array_size << 3; } while (gap > 0) { if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) { /* found the new highest */ asoc->highest_tsn_inside_map = asoc->mapping_array_base_tsn + gap; break; } gap--; } if (gap == 0) { /* Nothing left in map */ memset(asoc->mapping_array, 0, asoc->mapping_array_size); asoc->mapping_array_base_tsn = asoc->cumulative_tsn + 1; asoc->highest_tsn_inside_map = asoc->cumulative_tsn; } asoc->last_revoke_count = cnt; - SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer); + (void)SCTP_OS_TIMER_STOP(&stcb->asoc.dack_timer.timer); sctp_send_sack(stcb); sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_DRAIN); reneged_asoc_ids[reneged_at] = sctp_get_associd(stcb); reneged_at++; } /* * Another issue, in un-setting the TSN's in the mapping array we * DID NOT adjust the higest_tsn marker. This will cause one of two * things to occur. It may cause us to do extra work in checking for * our mapping array movement. More importantly it may cause us to * SACK every datagram. This may not be a bad thing though since we * will recover once we get our cum-ack above and all this stuff we * dumped recovered. */ } void sctp_drain() { /* * We must walk the PCB lists for ALL associations here. The system * is LOW on MBUF's and needs help. This is where reneging will * occur. We really hope this does NOT happen! */ struct sctp_inpcb *inp; struct sctp_tcb *stcb; SCTP_INP_INFO_RLOCK(); LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { /* For each endpoint */ SCTP_INP_RLOCK(inp); LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { /* For each association */ SCTP_TCB_LOCK(stcb); sctp_drain_mbufs(inp, stcb); SCTP_TCB_UNLOCK(stcb); } SCTP_INP_RUNLOCK(inp); } SCTP_INP_INFO_RUNLOCK(); } /* * start a new iterator * iterates through all endpoints and associations based on the pcb_state * flags and asoc_state. "af" (mandatory) is executed for all matching * assocs and "ef" (optional) is executed when the iterator completes. * "inpf" (optional) is executed for each new endpoint as it is being * iterated through. inpe (optional) is called when the inp completes * its way through all the stcbs. */ int sctp_initiate_iterator(inp_func inpf, asoc_func af, inp_func inpe, uint32_t pcb_state, uint32_t pcb_features, uint32_t asoc_state, void *argp, uint32_t argi, end_func ef, struct sctp_inpcb *s_inp, uint8_t chunk_output_off) { struct sctp_iterator *it = NULL; if (af == NULL) { return (-1); } SCTP_MALLOC(it, struct sctp_iterator *, sizeof(struct sctp_iterator), "Iterator"); if (it == NULL) { return (ENOMEM); } memset(it, 0, sizeof(*it)); it->function_assoc = af; it->function_inp = inpf; if (inpf) it->done_current_ep = 0; else it->done_current_ep = 1; it->function_atend = ef; it->pointer = argp; it->val = argi; it->pcb_flags = pcb_state; it->pcb_features = pcb_features; it->asoc_state = asoc_state; it->function_inp_end = inpe; it->no_chunk_output = chunk_output_off; if (s_inp) { it->inp = s_inp; it->iterator_flags = SCTP_ITERATOR_DO_SINGLE_INP; } else { SCTP_INP_INFO_RLOCK(); it->inp = LIST_FIRST(&sctppcbinfo.listhead); SCTP_INP_INFO_RUNLOCK(); it->iterator_flags = SCTP_ITERATOR_DO_ALL_INP; } SCTP_IPI_ITERATOR_WQ_LOCK(); if (it->inp) SCTP_INP_INCR_REF(it->inp); TAILQ_INSERT_TAIL(&sctppcbinfo.iteratorhead, it, sctp_nxt_itr); #if defined(SCTP_USE_THREAD_BASED_ITERATOR) if (sctppcbinfo.iterator_running == 0) { sctp_wakeup_iterator(); } SCTP_IPI_ITERATOR_WQ_UNLOCK(); #else if (it->inp) SCTP_INP_DECR_REF(it->inp); SCTP_IPI_ITERATOR_WQ_UNLOCK(); /* Init the timer */ SCTP_OS_TIMER_INIT(&it->tmr.timer); /* add to the list of all iterators */ sctp_timer_start(SCTP_TIMER_TYPE_ITERATOR, (struct sctp_inpcb *)it, NULL, NULL); #endif return (0); } diff --git a/sys/netinet/sctp_timer.c b/sys/netinet/sctp_timer.c index 3e72cc000c0f..62c34bde0728 100644 --- a/sys/netinet/sctp_timer.c +++ b/sys/netinet/sctp_timer.c @@ -1,1781 +1,1781 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_timer.c,v 1.29 2005/03/06 16:04:18 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #define _IP_VHL #include #include #ifdef INET6 #include #endif #include #include #include #include #include #include #include #include #include #include #include void sctp_early_fr_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_tmit_chunk *chk, *tp2; struct timeval now, min_wait, tv; unsigned int cur_rtt, cnt = 0, cnt_resend = 0; /* an early FR is occuring. */ - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); /* get cur rto in micro-seconds */ if (net->lastsa == 0) { /* Hmm no rtt estimate yet? */ cur_rtt = stcb->asoc.initial_rto >> 2; } else { cur_rtt = ((net->lastsa >> 2) + net->lastsv) >> 1; } if (cur_rtt < sctp_early_fr_msec) { cur_rtt = sctp_early_fr_msec; } cur_rtt *= 1000; tv.tv_sec = cur_rtt / 1000000; tv.tv_usec = cur_rtt % 1000000; min_wait = now; timevalsub(&min_wait, &tv); if (min_wait.tv_sec < 0 || min_wait.tv_usec < 0) { /* * if we hit here, we don't have enough seconds on the clock * to account for the RTO. We just let the lower seconds be * the bounds and don't worry about it. This may mean we * will mark a lot more than we should. */ min_wait.tv_sec = min_wait.tv_usec = 0; } chk = TAILQ_LAST(&stcb->asoc.sent_queue, sctpchunk_listhead); for (; chk != NULL; chk = tp2) { tp2 = TAILQ_PREV(chk, sctpchunk_listhead, sctp_next); if (chk->whoTo != net) { continue; } if (chk->sent == SCTP_DATAGRAM_RESEND) cnt_resend++; else if ((chk->sent > SCTP_DATAGRAM_UNSENT) && (chk->sent < SCTP_DATAGRAM_RESEND)) { /* pending, may need retran */ if (chk->sent_rcv_time.tv_sec > min_wait.tv_sec) { /* * we have reached a chunk that was sent * some seconds past our min.. forget it we * will find no more to send. */ continue; } else if (chk->sent_rcv_time.tv_sec == min_wait.tv_sec) { /* * we must look at the micro seconds to * know. */ if (chk->sent_rcv_time.tv_usec >= min_wait.tv_usec) { /* * ok it was sent after our boundary * time. */ continue; } } #ifdef SCTP_EARLYFR_LOGGING sctp_log_fr(chk->rec.data.TSN_seq, chk->snd_count, 4, SCTP_FR_MARKED_EARLY); #endif SCTP_STAT_INCR(sctps_earlyfrmrkretrans); chk->sent = SCTP_DATAGRAM_RESEND; sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); /* double book size since we are doing an early FR */ chk->book_size_scale++; cnt += chk->send_size; if ((cnt + net->flight_size) > net->cwnd) { /* Mark all we could possibly resend */ break; } } } if (cnt) { #ifdef SCTP_CWND_MONITOR int old_cwnd; old_cwnd = net->cwnd; #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_EARLY_FR_TMR); /* * make a small adjustment to cwnd and force to CA. */ if (net->cwnd > net->mtu) /* drop down one MTU after sending */ net->cwnd -= net->mtu; if (net->cwnd < net->ssthresh) /* still in SS move to CA */ net->ssthresh = net->cwnd - 1; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, (old_cwnd - net->cwnd), SCTP_CWND_LOG_FROM_FR); #endif } else if (cnt_resend) { sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_EARLY_FR_TMR); } /* Restart it? */ if (net->flight_size < net->cwnd) { SCTP_STAT_INCR(sctps_earlyfrstrtmr); sctp_timer_start(SCTP_TIMER_TYPE_EARLYFR, stcb->sctp_ep, stcb, net); } } void sctp_audit_retranmission_queue(struct sctp_association *asoc) { struct sctp_tmit_chunk *chk; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Audit invoked on send queue cnt:%d onqueue:%d\n", asoc->sent_queue_retran_cnt, asoc->sent_queue_cnt); } #endif /* SCTP_DEBUG */ asoc->sent_queue_retran_cnt = 0; asoc->sent_queue_cnt = 0; TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { if (chk->sent == SCTP_DATAGRAM_RESEND) { sctp_ucount_incr(asoc->sent_queue_retran_cnt); } asoc->sent_queue_cnt++; } TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { if (chk->sent == SCTP_DATAGRAM_RESEND) { sctp_ucount_incr(asoc->sent_queue_retran_cnt); } } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Audit completes retran:%d onqueue:%d\n", asoc->sent_queue_retran_cnt, asoc->sent_queue_cnt); } #endif /* SCTP_DEBUG */ } int sctp_threshold_management(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, uint16_t threshold) { if (net) { net->error_count++; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Error count for %p now %d thresh:%d\n", net, net->error_count, net->failure_threshold); } #endif /* SCTP_DEBUG */ if (net->error_count > net->failure_threshold) { /* We had a threshold failure */ if (net->dest_state & SCTP_ADDR_REACHABLE) { net->dest_state &= ~SCTP_ADDR_REACHABLE; net->dest_state |= SCTP_ADDR_NOT_REACHABLE; net->dest_state &= ~SCTP_ADDR_REQ_PRIMARY; if (net == stcb->asoc.primary_destination) { net->dest_state |= SCTP_ADDR_WAS_PRIMARY; } sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, SCTP_FAILED_THRESHOLD, (void *)net); } } /*********HOLD THIS COMMENT FOR PATCH OF ALTERNATE *********ROUTING CODE */ /*********HOLD THIS COMMENT FOR END OF PATCH OF ALTERNATE *********ROUTING CODE */ } if (stcb == NULL) return (0); if (net) { if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) { stcb->asoc.overall_error_count++; } } else { stcb->asoc.overall_error_count++; } #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Overall error count for %p now %d thresh:%u state:%x\n", &stcb->asoc, stcb->asoc.overall_error_count, (uint32_t) threshold, ((net == NULL) ? (uint32_t) 0 : (uint32_t) net->dest_state)); } #endif /* SCTP_DEBUG */ /* * We specifically do not do >= to give the assoc one more change * before we fail it. */ if (stcb->asoc.overall_error_count > threshold) { /* Abort notification sends a ULP notify */ struct mbuf *oper; oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_TIMER + SCTP_LOC_1); } inp->last_abort_code = SCTP_FROM_SCTP_TIMER + SCTP_LOC_1; sctp_abort_an_association(inp, stcb, SCTP_FAILED_THRESHOLD, oper); return (1); } return (0); } struct sctp_nets * sctp_find_alternate_net(struct sctp_tcb *stcb, struct sctp_nets *net, int highest_ssthresh) { /* Find and return an alternate network if possible */ struct sctp_nets *alt, *mnet, *hthresh = NULL; int once; uint32_t val = 0; if (stcb->asoc.numnets == 1) { /* No others but net */ return (TAILQ_FIRST(&stcb->asoc.nets)); } if (highest_ssthresh) { TAILQ_FOREACH(mnet, &stcb->asoc.nets, sctp_next) { if (((mnet->dest_state & SCTP_ADDR_REACHABLE) != SCTP_ADDR_REACHABLE) || (mnet->dest_state & SCTP_ADDR_UNCONFIRMED) ) { /* * will skip ones that are not-reachable or * unconfirmed */ continue; } if (val > mnet->ssthresh) { hthresh = mnet; val = mnet->ssthresh; } else if (val == mnet->ssthresh) { uint32_t rndval; uint8_t this_random; if (stcb->asoc.hb_random_idx > 3) { rndval = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); memcpy(stcb->asoc.hb_random_values, &rndval, sizeof(stcb->asoc.hb_random_values)); this_random = stcb->asoc.hb_random_values[0]; stcb->asoc.hb_random_idx = 0; stcb->asoc.hb_ect_randombit = 0; } else { this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx]; stcb->asoc.hb_random_idx++; stcb->asoc.hb_ect_randombit = 0; } if (this_random % 2) { hthresh = mnet; val = mnet->ssthresh; } } } if (hthresh) { return (hthresh); } } mnet = net; once = 0; if (mnet == NULL) { mnet = TAILQ_FIRST(&stcb->asoc.nets); } do { alt = TAILQ_NEXT(mnet, sctp_next); if (alt == NULL) { once++; if (once > 1) { break; } alt = TAILQ_FIRST(&stcb->asoc.nets); } if (alt->ro.ro_rt == NULL) { if (alt->ro._s_addr) { sctp_free_ifa(alt->ro._s_addr); alt->ro._s_addr = NULL; } alt->src_addr_selected = 0; } if ( ((alt->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE) && (alt->ro.ro_rt != NULL) && (!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) ) { /* Found a reachable address */ break; } mnet = alt; } while (alt != NULL); if (alt == NULL) { /* Case where NO insv network exists (dormant state) */ /* we rotate destinations */ once = 0; mnet = net; do { alt = TAILQ_NEXT(mnet, sctp_next); if (alt == NULL) { once++; if (once > 1) { break; } alt = TAILQ_FIRST(&stcb->asoc.nets); } if ((!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) && (alt != net)) { /* Found an alternate address */ break; } mnet = alt; } while (alt != NULL); } if (alt == NULL) { return (net); } return (alt); } static void sctp_backoff_on_timeout(struct sctp_tcb *stcb, struct sctp_nets *net, int win_probe, int num_marked) { net->RTO <<= 1; if (net->RTO > stcb->asoc.maxrto) { net->RTO = stcb->asoc.maxrto; } if ((win_probe == 0) && num_marked) { /* We don't apply penalty to window probe scenarios */ #ifdef SCTP_CWND_MONITOR int old_cwnd = net->cwnd; #endif net->ssthresh = net->cwnd >> 1; if (net->ssthresh < (net->mtu << 1)) { net->ssthresh = (net->mtu << 1); } net->cwnd = net->mtu; /* floor of 1 mtu */ if (net->cwnd < net->mtu) net->cwnd = net->mtu; #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->cwnd - old_cwnd, SCTP_CWND_LOG_FROM_RTX); #endif net->partial_bytes_acked = 0; } } static int sctp_mark_all_for_resend(struct sctp_tcb *stcb, struct sctp_nets *net, struct sctp_nets *alt, int window_probe, int *num_marked) { /* * Mark all chunks (well not all) that were sent to *net for * retransmission. Move them to alt for there destination as well... * We only mark chunks that have been outstanding long enough to * have received feed-back. */ struct sctp_tmit_chunk *chk, *tp2, *could_be_sent = NULL; struct sctp_nets *lnets; struct timeval now, min_wait, tv; int cur_rtt; int audit_tf, num_mk, fir; unsigned int cnt_mk; uint32_t orig_flight, orig_tf; uint32_t tsnlast, tsnfirst; /* * CMT: Using RTX_SSTHRESH policy for CMT. If CMT is being used, * then pick dest with largest ssthresh for any retransmission. * (iyengar@cis.udel.edu, 2005/08/12) */ if (sctp_cmt_on_off) { alt = sctp_find_alternate_net(stcb, net, 1); /* * CUCv2: If a different dest is picked for the * retransmission, then new (rtx-)pseudo_cumack needs to be * tracked for orig dest. Let CUCv2 track new (rtx-) * pseudo-cumack always. */ net->find_pseudo_cumack = 1; net->find_rtx_pseudo_cumack = 1; } /* none in flight now */ audit_tf = 0; fir = 0; /* * figure out how long a data chunk must be pending before we can * mark it .. */ - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); /* get cur rto in micro-seconds */ cur_rtt = (((net->lastsa >> 2) + net->lastsv) >> 1); cur_rtt *= 1000; #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(cur_rtt, stcb->asoc.peers_rwnd, window_probe, SCTP_FR_T3_MARK_TIME); sctp_log_fr(net->flight_size, SCTP_OS_TIMER_PENDING(&net->fr_timer.timer), SCTP_OS_TIMER_ACTIVE(&net->fr_timer.timer), SCTP_FR_CWND_REPORT); sctp_log_fr(net->flight_size, net->cwnd, stcb->asoc.total_flight, SCTP_FR_CWND_REPORT); #endif tv.tv_sec = cur_rtt / 1000000; tv.tv_usec = cur_rtt % 1000000; min_wait = now; timevalsub(&min_wait, &tv); if (min_wait.tv_sec < 0 || min_wait.tv_usec < 0) { /* * if we hit here, we don't have enough seconds on the clock * to account for the RTO. We just let the lower seconds be * the bounds and don't worry about it. This may mean we * will mark a lot more than we should. */ min_wait.tv_sec = min_wait.tv_usec = 0; } #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(cur_rtt, now.tv_sec, now.tv_usec, SCTP_FR_T3_MARK_TIME); sctp_log_fr(0, min_wait.tv_sec, min_wait.tv_usec, SCTP_FR_T3_MARK_TIME); #endif /* * Our rwnd will be incorrect here since we are not adding back the * cnt * mbuf but we will fix that down below. */ orig_flight = net->flight_size; orig_tf = stcb->asoc.total_flight; net->fast_retran_ip = 0; /* Now on to each chunk */ num_mk = cnt_mk = 0; tsnfirst = tsnlast = 0; chk = TAILQ_FIRST(&stcb->asoc.sent_queue); for (; chk != NULL; chk = tp2) { tp2 = TAILQ_NEXT(chk, sctp_next); if ((compare_with_wrap(stcb->asoc.last_acked_seq, chk->rec.data.TSN_seq, MAX_TSN)) || (stcb->asoc.last_acked_seq == chk->rec.data.TSN_seq)) { /* Strange case our list got out of order? */ printf("Our list is out of order?\n"); panic("Out of order list"); } if ((chk->whoTo == net) && (chk->sent < SCTP_DATAGRAM_ACKED)) { /* * found one to mark: If it is less than * DATAGRAM_ACKED it MUST not be a skipped or marked * TSN but instead one that is either already set * for retransmission OR one that needs * retransmission. */ /* validate its been outstanding long enough */ #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(chk->rec.data.TSN_seq, chk->sent_rcv_time.tv_sec, chk->sent_rcv_time.tv_usec, SCTP_FR_T3_MARK_TIME); #endif if ((chk->sent_rcv_time.tv_sec > min_wait.tv_sec) && (window_probe == 0)) { /* * we have reached a chunk that was sent * some seconds past our min.. forget it we * will find no more to send. */ #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(0, chk->sent_rcv_time.tv_sec, chk->sent_rcv_time.tv_usec, SCTP_FR_T3_STOPPED); #endif continue; } else if ((chk->sent_rcv_time.tv_sec == min_wait.tv_sec) && (window_probe == 0)) { /* * we must look at the micro seconds to * know. */ if (chk->sent_rcv_time.tv_usec >= min_wait.tv_usec) { /* * ok it was sent after our boundary * time. */ #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(0, chk->sent_rcv_time.tv_sec, chk->sent_rcv_time.tv_usec, SCTP_FR_T3_STOPPED); #endif continue; } } if (PR_SCTP_TTL_ENABLED(chk->flags)) { /* Is it expired? */ if ((now.tv_sec > chk->rec.data.timetodrop.tv_sec) || ((chk->rec.data.timetodrop.tv_sec == now.tv_sec) && (now.tv_usec > chk->rec.data.timetodrop.tv_usec))) { /* Yes so drop it */ if (chk->data) { sctp_release_pr_sctp_chunk(stcb, chk, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), &stcb->asoc.sent_queue); } } continue; } if (PR_SCTP_RTX_ENABLED(chk->flags)) { /* Has it been retransmitted tv_sec times? */ if (chk->snd_count > chk->rec.data.timetodrop.tv_sec) { if (chk->data) { sctp_release_pr_sctp_chunk(stcb, chk, (SCTP_RESPONSE_TO_USER_REQ | SCTP_NOTIFY_DATAGRAM_SENT), &stcb->asoc.sent_queue); } } continue; } if (chk->sent < SCTP_DATAGRAM_RESEND) { sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); num_mk++; if (fir == 0) { fir = 1; tsnfirst = chk->rec.data.TSN_seq; } tsnlast = chk->rec.data.TSN_seq; #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(chk->rec.data.TSN_seq, chk->snd_count, 0, SCTP_FR_T3_MARKED); #endif if (chk->rec.data.chunk_was_revoked) { /* deflate the cwnd */ chk->whoTo->cwnd -= chk->book_size; chk->rec.data.chunk_was_revoked = 0; } net->marked_retrans++; stcb->asoc.marked_retrans++; #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_RSND_TO, chk->whoTo->flight_size, chk->book_size, (uintptr_t) chk->whoTo, chk->rec.data.TSN_seq); #endif sctp_flight_size_decrease(chk); sctp_total_flight_decrease(stcb, chk); stcb->asoc.peers_rwnd += chk->send_size; stcb->asoc.peers_rwnd += sctp_peer_chunk_oh; } chk->sent = SCTP_DATAGRAM_RESEND; SCTP_STAT_INCR(sctps_markedretrans); /* reset the TSN for striking and other FR stuff */ chk->rec.data.doing_fast_retransmit = 0; /* Clear any time so NO RTT is being done */ chk->do_rtt = 0; if (alt != net) { sctp_free_remote_addr(chk->whoTo); chk->no_fr_allowed = 1; chk->whoTo = alt; atomic_add_int(&alt->ref_count, 1); } else { chk->no_fr_allowed = 0; if (TAILQ_EMPTY(&stcb->asoc.send_queue)) { chk->rec.data.fast_retran_tsn = stcb->asoc.sending_seq; } else { chk->rec.data.fast_retran_tsn = (TAILQ_FIRST(&stcb->asoc.send_queue))->rec.data.TSN_seq; } } if (sctp_cmt_on_off == 1) { chk->no_fr_allowed = 1; } } else if (chk->sent == SCTP_DATAGRAM_ACKED) { /* remember highest acked one */ could_be_sent = chk; } if (chk->sent == SCTP_DATAGRAM_RESEND) { cnt_mk++; } } if ((orig_flight - net->flight_size) != (orig_tf - stcb->asoc.total_flight)) { /* we did not subtract the same things? */ audit_tf = 1; } #if defined(SCTP_FR_LOGGING) || defined(SCTP_EARLYFR_LOGGING) sctp_log_fr(tsnfirst, tsnlast, num_mk, SCTP_FR_T3_TIMEOUT); #endif #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { if (num_mk) { printf("LAST TSN marked was %x\n", tsnlast); printf("Num marked for retransmission was %d peer-rwd:%ld\n", num_mk, (u_long)stcb->asoc.peers_rwnd); printf("LAST TSN marked was %x\n", tsnlast); printf("Num marked for retransmission was %d peer-rwd:%d\n", num_mk, (int)stcb->asoc.peers_rwnd ); } } #endif *num_marked = num_mk; if ((stcb->asoc.sent_queue_retran_cnt == 0) && (could_be_sent)) { /* fix it so we retransmit the highest acked anyway */ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); cnt_mk++; could_be_sent->sent = SCTP_DATAGRAM_RESEND; } if (stcb->asoc.sent_queue_retran_cnt != cnt_mk) { #ifdef INVARIANTS printf("Local Audit says there are %d for retran asoc cnt:%d\n", cnt_mk, stcb->asoc.sent_queue_retran_cnt); #endif #ifndef SCTP_AUDITING_ENABLED stcb->asoc.sent_queue_retran_cnt = cnt_mk; #endif } /* Now check for a ECN Echo that may be stranded */ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { if ((chk->whoTo == net) && (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) { sctp_free_remote_addr(chk->whoTo); chk->whoTo = alt; if (chk->sent != SCTP_DATAGRAM_RESEND) { chk->sent = SCTP_DATAGRAM_RESEND; sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); } atomic_add_int(&alt->ref_count, 1); } } if (audit_tf) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Audit total flight due to negative value net:%p\n", net); } #endif /* SCTP_DEBUG */ stcb->asoc.total_flight = 0; stcb->asoc.total_flight_count = 0; /* Clear all networks flight size */ TAILQ_FOREACH(lnets, &stcb->asoc.nets, sctp_next) { lnets->flight_size = 0; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER4) { printf("Net:%p c-f cwnd:%d ssthresh:%d\n", lnets, lnets->cwnd, lnets->ssthresh); } #endif /* SCTP_DEBUG */ } TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if (chk->sent < SCTP_DATAGRAM_RESEND) { #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_UP, chk->whoTo->flight_size, chk->book_size, (uintptr_t) chk->whoTo, chk->rec.data.TSN_seq); #endif sctp_flight_size_increase(chk); sctp_total_flight_increase(stcb, chk); } } } /* * Setup the ecn nonce re-sync point. We do this since * retranmissions are NOT setup for ECN. This means that do to * Karn's rule, we don't know the total of the peers ecn bits. */ chk = TAILQ_FIRST(&stcb->asoc.send_queue); if (chk == NULL) { stcb->asoc.nonce_resync_tsn = stcb->asoc.sending_seq; } else { stcb->asoc.nonce_resync_tsn = chk->rec.data.TSN_seq; } stcb->asoc.nonce_wait_for_ecne = 0; stcb->asoc.nonce_sum_check = 0; /* We return 1 if we only have a window probe outstanding */ return (0); } static void sctp_move_all_chunks_to_alt(struct sctp_tcb *stcb, struct sctp_nets *net, struct sctp_nets *alt) { struct sctp_association *asoc; struct sctp_stream_out *outs; struct sctp_tmit_chunk *chk; struct sctp_stream_queue_pending *sp; if (net == alt) /* nothing to do */ return; asoc = &stcb->asoc; /* * now through all the streams checking for chunks sent to our bad * network. */ TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { /* now clean up any chunks here */ TAILQ_FOREACH(sp, &outs->outqueue, next) { if (sp->net == net) { sctp_free_remote_addr(sp->net); sp->net = alt; atomic_add_int(&alt->ref_count, 1); } } } /* Now check the pending queue */ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { if (chk->whoTo == net) { sctp_free_remote_addr(chk->whoTo); chk->whoTo = alt; atomic_add_int(&alt->ref_count, 1); } } } int sctp_t3rxt_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; int win_probe, num_mk; #ifdef SCTP_FR_LOGGING sctp_log_fr(0, 0, 0, SCTP_FR_T3_TIMEOUT); #ifdef SCTP_CWND_LOGGING { struct sctp_nets *lnet; TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { if (net == lnet) { sctp_log_cwnd(stcb, lnet, 1, SCTP_CWND_LOG_FROM_T3); } else { sctp_log_cwnd(stcb, lnet, 0, SCTP_CWND_LOG_FROM_T3); } } } #endif #endif /* Find an alternate and mark those for retransmission */ if ((stcb->asoc.peers_rwnd == 0) && (stcb->asoc.total_flight < net->mtu)) { SCTP_STAT_INCR(sctps_timowindowprobe); win_probe = 1; } else { win_probe = 0; } if (sctp_cmt_on_off) { /* * CMT: Using RTX_SSTHRESH policy for CMT. If CMT is being * used, then pick dest with largest ssthresh for any * retransmission. */ alt = net; alt = sctp_find_alternate_net(stcb, alt, 1); /* * CUCv2: If a different dest is picked for the * retransmission, then new (rtx-)pseudo_cumack needs to be * tracked for orig dest. Let CUCv2 track new (rtx-) * pseudo-cumack always. */ net->find_pseudo_cumack = 1; net->find_rtx_pseudo_cumack = 1; } else { /* CMT is OFF */ alt = sctp_find_alternate_net(stcb, net, 0); } sctp_mark_all_for_resend(stcb, net, alt, win_probe, &num_mk); /* FR Loss recovery just ended with the T3. */ stcb->asoc.fast_retran_loss_recovery = 0; /* CMT FR loss recovery ended with the T3 */ net->fast_retran_loss_recovery = 0; /* * setup the sat loss recovery that prevents satellite cwnd advance. */ stcb->asoc.sat_t3_loss_recovery = 1; stcb->asoc.sat_t3_recovery_tsn = stcb->asoc.sending_seq; /* Backoff the timer and cwnd */ sctp_backoff_on_timeout(stcb, net, win_probe, num_mk); if (win_probe == 0) { /* We don't do normal threshold management on window probes */ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) { /* Association was destroyed */ return (1); } else { if (net != stcb->asoc.primary_destination) { /* send a immediate HB if our RTO is stale */ struct timeval now; unsigned int ms_goneby; - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); if (net->last_sent_time.tv_sec) { ms_goneby = (now.tv_sec - net->last_sent_time.tv_sec) * 1000; } else { ms_goneby = 0; } if ((ms_goneby > net->RTO) || (net->RTO == 0)) { /* * no recent feed back in an RTO or * more, request a RTT update */ - sctp_send_hb(stcb, 1, net); + (void)sctp_send_hb(stcb, 1, net); } } } } else { /* * For a window probe we don't penalize the net's but only * the association. This may fail it if SACKs are not coming * back. If sack's are coming with rwnd locked at 0, we will * continue to hold things waiting for rwnd to raise */ if (sctp_threshold_management(inp, stcb, NULL, stcb->asoc.max_send_times)) { /* Association was destroyed */ return (1); } } if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { /* Move all pending over too */ sctp_move_all_chunks_to_alt(stcb, net, alt); /* * Get the address that failed, to force a new src address * selecton and a route allocation. */ if (net->ro._s_addr) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; } net->src_addr_selected = 0; /* Force a route allocation too */ if (net->ro.ro_rt) { RTFREE(net->ro.ro_rt); net->ro.ro_rt = NULL; } /* Was it our primary? */ if ((stcb->asoc.primary_destination == net) && (alt != net)) { /* * Yes, note it as such and find an alternate note: * this means HB code must use this to resent the * primary if it goes active AND if someone does a * change-primary then this flag must be cleared * from any net structures. */ if (sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, alt) == 0) { net->dest_state |= SCTP_ADDR_WAS_PRIMARY; } } } /* * Special case for cookie-echo'ed case, we don't do output but must * await the COOKIE-ACK before retransmission */ if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED) { /* * Here we just reset the timer and start again since we * have not established the asoc */ sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); return (0); } if (stcb->asoc.peer_supports_prsctp) { struct sctp_tmit_chunk *lchk; lchk = sctp_try_advance_peer_ack_point(stcb, &stcb->asoc); /* C3. See if we need to send a Fwd-TSN */ if (compare_with_wrap(stcb->asoc.advanced_peer_ack_point, stcb->asoc.last_acked_seq, MAX_TSN)) { /* * ISSUE with ECN, see FWD-TSN processing for notes * on issues that will occur when the ECN NONCE * stuff is put into SCTP for cross checking. */ send_forward_tsn(stcb, &stcb->asoc); if (lchk) { /* Assure a timer is up */ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, lchk->whoTo); } } } #ifdef SCTP_CWND_MONITOR sctp_log_cwnd(stcb, net, net->cwnd, SCTP_CWND_LOG_FROM_RTX); #endif return (0); } int sctp_t1init_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { /* bump the thresholds */ if (stcb->asoc.delayed_connection) { /* * special hook for delayed connection. The library did NOT * complete the rest of its sends. */ stcb->asoc.delayed_connection = 0; sctp_send_initiate(inp, stcb); return (0); } if (SCTP_GET_STATE((&stcb->asoc)) != SCTP_STATE_COOKIE_WAIT) { return (0); } if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_init_times)) { /* Association was destroyed */ return (1); } stcb->asoc.dropped_special_cnt = 0; sctp_backoff_on_timeout(stcb, stcb->asoc.primary_destination, 1, 0); if (stcb->asoc.initial_init_rto_max < net->RTO) { net->RTO = stcb->asoc.initial_init_rto_max; } if (stcb->asoc.numnets > 1) { /* If we have more than one addr use it */ struct sctp_nets *alt; alt = sctp_find_alternate_net(stcb, stcb->asoc.primary_destination, 0); if ((alt != NULL) && (alt != stcb->asoc.primary_destination)) { sctp_move_all_chunks_to_alt(stcb, stcb->asoc.primary_destination, alt); stcb->asoc.primary_destination = alt; } } /* Send out a new init */ sctp_send_initiate(inp, stcb); return (0); } /* * For cookie and asconf we actually need to find and mark for resend, then * increment the resend counter (after all the threshold management stuff of * course). */ int sctp_cookie_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; struct sctp_tmit_chunk *cookie; /* first before all else we must find the cookie */ TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue, sctp_next) { if (cookie->rec.chunk_id.id == SCTP_COOKIE_ECHO) { break; } } if (cookie == NULL) { if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED) { /* FOOBAR! */ struct mbuf *oper; oper = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (oper) { struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(oper) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(oper, struct sctp_paramhdr *); ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); ph->param_length = htons(SCTP_BUF_LEN(oper)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_TIMER + SCTP_LOC_2); } inp->last_abort_code = SCTP_FROM_SCTP_TIMER + SCTP_LOC_3; sctp_abort_an_association(inp, stcb, SCTP_INTERNAL_ERROR, oper); } else { #ifdef INVARIANTS panic("Cookie timer expires in wrong state?"); #else printf("Strange in state %d not cookie-echoed yet c-e timer expires?\n", SCTP_GET_STATE(&stcb->asoc)); return (0); #endif } return (0); } /* Ok we found the cookie, threshold management next */ if (sctp_threshold_management(inp, stcb, cookie->whoTo, stcb->asoc.max_init_times)) { /* Assoc is over */ return (1); } /* * cleared theshold management now lets backoff the address & select * an alternate */ stcb->asoc.dropped_special_cnt = 0; sctp_backoff_on_timeout(stcb, cookie->whoTo, 1, 0); alt = sctp_find_alternate_net(stcb, cookie->whoTo, 0); if (alt != cookie->whoTo) { sctp_free_remote_addr(cookie->whoTo); cookie->whoTo = alt; atomic_add_int(&alt->ref_count, 1); } /* Now mark the retran info */ if (cookie->sent != SCTP_DATAGRAM_RESEND) { sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); } cookie->sent = SCTP_DATAGRAM_RESEND; /* * Now call the output routine to kick out the cookie again, Note we * don't mark any chunks for retran so that FR will need to kick in * to move these (or a send timer). */ return (0); } int sctp_strreset_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; struct sctp_tmit_chunk *strrst = NULL, *chk = NULL; if (stcb->asoc.stream_reset_outstanding == 0) { return (0); } /* find the existing STRRESET, we use the seq number we sent out on */ sctp_find_stream_reset(stcb, stcb->asoc.str_reset_seq_out, &strrst); if (strrst == NULL) { return (0); } /* do threshold management */ if (sctp_threshold_management(inp, stcb, strrst->whoTo, stcb->asoc.max_send_times)) { /* Assoc is over */ return (1); } /* * cleared theshold management now lets backoff the address & select * an alternate */ sctp_backoff_on_timeout(stcb, strrst->whoTo, 1, 0); alt = sctp_find_alternate_net(stcb, strrst->whoTo, 0); sctp_free_remote_addr(strrst->whoTo); strrst->whoTo = alt; atomic_add_int(&alt->ref_count, 1); /* See if a ECN Echo is also stranded */ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { if ((chk->whoTo == net) && (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) { sctp_free_remote_addr(chk->whoTo); if (chk->sent != SCTP_DATAGRAM_RESEND) { chk->sent = SCTP_DATAGRAM_RESEND; sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); } chk->whoTo = alt; atomic_add_int(&alt->ref_count, 1); } } if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { /* * If the address went un-reachable, we need to move to * alternates for ALL chk's in queue */ sctp_move_all_chunks_to_alt(stcb, net, alt); } /* mark the retran info */ if (strrst->sent != SCTP_DATAGRAM_RESEND) sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); strrst->sent = SCTP_DATAGRAM_RESEND; /* restart the timer */ sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, inp, stcb, strrst->whoTo); return (0); } int sctp_asconf_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; struct sctp_tmit_chunk *asconf, *chk; /* is this the first send, or a retransmission? */ if (stcb->asoc.asconf_sent == 0) { /* compose a new ASCONF chunk and send it */ sctp_send_asconf(stcb, net); } else { /* Retransmission of the existing ASCONF needed... */ /* find the existing ASCONF */ TAILQ_FOREACH(asconf, &stcb->asoc.control_send_queue, sctp_next) { if (asconf->rec.chunk_id.id == SCTP_ASCONF) { break; } } if (asconf == NULL) { return (0); } /* do threshold management */ if (sctp_threshold_management(inp, stcb, asconf->whoTo, stcb->asoc.max_send_times)) { /* Assoc is over */ return (1); } /* * PETER? FIX? How will the following code ever run? If the * max_send_times is hit, threshold managment will blow away * the association? */ if (asconf->snd_count > stcb->asoc.max_send_times) { /* * Something is rotten, peer is not responding to * ASCONFs but maybe is to data etc. e.g. it is not * properly handling the chunk type upper bits Mark * this peer as ASCONF incapable and cleanup */ #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("asconf_timer: Peer has not responded to our repeated ASCONFs\n"); } #endif /* SCTP_DEBUG */ sctp_asconf_cleanup(stcb, net); return (0); } /* * cleared theshold management now lets backoff the address * & select an alternate */ sctp_backoff_on_timeout(stcb, asconf->whoTo, 1, 0); alt = sctp_find_alternate_net(stcb, asconf->whoTo, 0); sctp_free_remote_addr(asconf->whoTo); asconf->whoTo = alt; atomic_add_int(&alt->ref_count, 1); /* See if a ECN Echo is also stranded */ TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { if ((chk->whoTo == net) && (chk->rec.chunk_id.id == SCTP_ECN_ECHO)) { sctp_free_remote_addr(chk->whoTo); chk->whoTo = alt; if (chk->sent != SCTP_DATAGRAM_RESEND) { chk->sent = SCTP_DATAGRAM_RESEND; sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); } atomic_add_int(&alt->ref_count, 1); } } if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { /* * If the address went un-reachable, we need to move * to alternates for ALL chk's in queue */ sctp_move_all_chunks_to_alt(stcb, net, alt); } /* mark the retran info */ if (asconf->sent != SCTP_DATAGRAM_RESEND) sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); asconf->sent = SCTP_DATAGRAM_RESEND; } return (0); } /* * For the shutdown and shutdown-ack, we do not keep one around on the * control queue. This means we must generate a new one and call the general * chunk output routine, AFTER having done threshold management. */ int sctp_shutdown_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; /* first threshold managment */ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) { /* Assoc is over */ return (1); } /* second select an alternative */ alt = sctp_find_alternate_net(stcb, net, 0); /* third generate a shutdown into the queue for out net */ if (alt) { sctp_send_shutdown(stcb, alt); } else { /* * if alt is NULL, there is no dest to send to?? */ return (0); } /* fourth restart timer */ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, inp, stcb, alt); return (0); } int sctp_shutdownack_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct sctp_nets *alt; /* first threshold managment */ if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) { /* Assoc is over */ return (1); } /* second select an alternative */ alt = sctp_find_alternate_net(stcb, net, 0); /* third generate a shutdown into the queue for out net */ sctp_send_shutdown_ack(stcb, alt); /* fourth restart timer */ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, inp, stcb, alt); return (0); } static void sctp_audit_stream_queues_for_size(struct sctp_inpcb *inp, struct sctp_tcb *stcb) { struct sctp_stream_out *outs; struct sctp_stream_queue_pending *sp; unsigned int chks_in_queue = 0; int being_filled = 0; /* * This function is ONLY called when the send/sent queues are empty. */ if ((stcb == NULL) || (inp == NULL)) return; if (stcb->asoc.sent_queue_retran_cnt) { printf("Hmm, sent_queue_retran_cnt is non-zero %d\n", stcb->asoc.sent_queue_retran_cnt); stcb->asoc.sent_queue_retran_cnt = 0; } SCTP_TCB_SEND_LOCK(stcb); if (TAILQ_EMPTY(&stcb->asoc.out_wheel)) { int i, cnt = 0; /* Check to see if a spoke fell off the wheel */ for (i = 0; i < stcb->asoc.streamoutcnt; i++) { if (!TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) { sctp_insert_on_wheel(stcb, &stcb->asoc, &stcb->asoc.strmout[i], 1); cnt++; } } if (cnt) { /* yep, we lost a spoke or two */ printf("Found an additional %d streams NOT on outwheel, corrected\n", cnt); } else { /* no spokes lost, */ stcb->asoc.total_output_queue_size = 0; } SCTP_TCB_SEND_UNLOCK(stcb); return; } SCTP_TCB_SEND_UNLOCK(stcb); /* Check to see if some data queued, if so report it */ TAILQ_FOREACH(outs, &stcb->asoc.out_wheel, next_spoke) { if (!TAILQ_EMPTY(&outs->outqueue)) { TAILQ_FOREACH(sp, &outs->outqueue, next) { if (sp->msg_is_complete) being_filled++; chks_in_queue++; } } } if (chks_in_queue != stcb->asoc.stream_queue_cnt) { printf("Hmm, stream queue cnt at %d I counted %d in stream out wheel\n", stcb->asoc.stream_queue_cnt, chks_in_queue); } if (chks_in_queue) { /* call the output queue function */ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3); if ((TAILQ_EMPTY(&stcb->asoc.send_queue)) && (TAILQ_EMPTY(&stcb->asoc.sent_queue))) { /* * Probably should go in and make it go back through * and add fragments allowed */ if (being_filled == 0) { printf("Still nothing moved %d chunks are stuck\n", chks_in_queue); } } } else { printf("Found no chunks on any queue tot:%lu\n", (u_long)stcb->asoc.total_output_queue_size); stcb->asoc.total_output_queue_size = 0; } } int sctp_heartbeat_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, int cnt_of_unconf) { if (net) { if (net->hb_responded == 0) { if (net->ro._s_addr) { /* * Invalidate the src address if we did not * get a response last time. */ sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } sctp_backoff_on_timeout(stcb, net, 1, 0); } /* Zero PBA, if it needs it */ if (net->partial_bytes_acked) { net->partial_bytes_acked = 0; } } if ((stcb->asoc.total_output_queue_size > 0) && (TAILQ_EMPTY(&stcb->asoc.send_queue)) && (TAILQ_EMPTY(&stcb->asoc.sent_queue))) { sctp_audit_stream_queues_for_size(inp, stcb); } /* Send a new HB, this will do threshold managment, pick a new dest */ if (cnt_of_unconf == 0) { if (sctp_send_hb(stcb, 0, NULL) < 0) { return (1); } } else { /* * this will send out extra hb's up to maxburst if there are * any unconfirmed addresses. */ int cnt_sent = 0; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) && (net->dest_state & SCTP_ADDR_REACHABLE)) { cnt_sent++; if (net->hb_responded == 0) { /* Did we respond last time? */ if (net->ro._s_addr) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } } if (sctp_send_hb(stcb, 1, net) == 0) { break; } if (cnt_sent >= sctp_hb_maxburst) break; } } } return (0); } int sctp_is_hb_timer_running(struct sctp_tcb *stcb) { if (SCTP_OS_TIMER_PENDING(&stcb->asoc.hb_timer.timer)) { /* its running */ return (1); } else { /* nope */ return (0); } } int sctp_is_sack_timer_running(struct sctp_tcb *stcb) { if (SCTP_OS_TIMER_PENDING(&stcb->asoc.dack_timer.timer)) { /* its running */ return (1); } else { /* nope */ return (0); } } #define SCTP_NUMBER_OF_MTU_SIZES 18 static uint32_t mtu_sizes[] = { 68, 296, 508, 512, 544, 576, 1006, 1492, 1500, 1536, 2002, 2048, 4352, 4464, 8166, 17914, 32000, 65535 }; static uint32_t sctp_getnext_mtu(struct sctp_inpcb *inp, uint32_t cur_mtu) { /* select another MTU that is just bigger than this one */ int i; for (i = 0; i < SCTP_NUMBER_OF_MTU_SIZES; i++) { if (cur_mtu < mtu_sizes[i]) { /* no max_mtu is bigger than this one */ return (mtu_sizes[i]); } } /* here return the highest allowable */ return (cur_mtu); } void sctp_pathmtu_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { uint32_t next_mtu; /* restart the timer in any case */ next_mtu = sctp_getnext_mtu(inp, net->mtu); if (next_mtu <= net->mtu) { /* nothing to do */ return; } { uint32_t mtu; if ((net->src_addr_selected == 0) || (net->ro._s_addr == NULL) || (net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED)) { if ((net->ro._s_addr == NULL) && (net->ro._s_addr->localifa_flags & SCTP_BEING_DELETED)) { sctp_free_ifa(net->ro._s_addr); net->ro._s_addr = NULL; net->src_addr_selected = 0; } net->ro._s_addr = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, stcb->asoc.vrf_id); if (net->ro._s_addr) net->src_addr_selected = 1; } if (net->ro._s_addr) { mtu = SCTP_GATHER_MTU_FROM_ROUTE(net->ro._s_addr, &net->ro._s_addr.sa, net->ro.ro_rt); if (mtu > next_mtu) { net->mtu = next_mtu; } } } /* restart the timer */ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net); } void sctp_autoclose_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { struct timeval tn, *tim_touse; struct sctp_association *asoc; int ticks_gone_by; - SCTP_GETTIME_TIMEVAL(&tn); + (void)SCTP_GETTIME_TIMEVAL(&tn); if (stcb->asoc.sctp_autoclose_ticks && sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) { /* Auto close is on */ asoc = &stcb->asoc; /* pick the time to use */ if (asoc->time_last_rcvd.tv_sec > asoc->time_last_sent.tv_sec) { tim_touse = &asoc->time_last_rcvd; } else { tim_touse = &asoc->time_last_sent; } /* Now has long enough transpired to autoclose? */ ticks_gone_by = SEC_TO_TICKS(tn.tv_sec - tim_touse->tv_sec); if ((ticks_gone_by > 0) && (ticks_gone_by >= (int)asoc->sctp_autoclose_ticks)) { /* * autoclose time has hit, call the output routine, * which should do nothing just to be SURE we don't * have hanging data. We can then safely check the * queues and know that we are clear to send * shutdown */ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR); /* Are we clean? */ if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue)) { /* * there is nothing queued to send, so I'm * done... */ if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) { /* only send SHUTDOWN 1st time thru */ sctp_send_shutdown(stcb, stcb->asoc.primary_destination); if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } } else { /* * No auto close at this time, reset t-o to check * later */ int tmp; /* fool the timer startup to use the time left */ tmp = asoc->sctp_autoclose_ticks; asoc->sctp_autoclose_ticks -= ticks_gone_by; sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, net); /* restore the real tick value */ asoc->sctp_autoclose_ticks = tmp; } } } void sctp_iterator_timer(struct sctp_iterator *it) { int iteration_count = 0; int inp_skip = 0; /* * only one iterator can run at a time. This is the only way we can * cleanly pull ep's from underneath all the running interators when * a ep is freed. */ SCTP_ITERATOR_LOCK(); if (it->inp == NULL) { /* iterator is complete */ done_with_iterator: SCTP_ITERATOR_UNLOCK(); SCTP_INP_INFO_WLOCK(); TAILQ_REMOVE(&sctppcbinfo.iteratorhead, it, sctp_nxt_itr); /* stopping the callout is not needed, in theory */ SCTP_INP_INFO_WUNLOCK(); - SCTP_OS_TIMER_STOP(&it->tmr.timer); + (void)SCTP_OS_TIMER_STOP(&it->tmr.timer); if (it->function_atend != NULL) { (*it->function_atend) (it->pointer, it->val); } SCTP_FREE(it); return; } select_a_new_ep: SCTP_INP_WLOCK(it->inp); while (((it->pcb_flags) && ((it->inp->sctp_flags & it->pcb_flags) != it->pcb_flags)) || ((it->pcb_features) && ((it->inp->sctp_features & it->pcb_features) != it->pcb_features))) { /* endpoint flags or features don't match, so keep looking */ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { SCTP_INP_WUNLOCK(it->inp); goto done_with_iterator; } SCTP_INP_WUNLOCK(it->inp); it->inp = LIST_NEXT(it->inp, sctp_list); if (it->inp == NULL) { goto done_with_iterator; } SCTP_INP_WLOCK(it->inp); } if ((it->inp->inp_starting_point_for_iterator != NULL) && (it->inp->inp_starting_point_for_iterator != it)) { printf("Iterator collision, waiting for one at %p\n", it->inp); SCTP_INP_WUNLOCK(it->inp); goto start_timer_return; } /* mark the current iterator on the endpoint */ it->inp->inp_starting_point_for_iterator = it; SCTP_INP_WUNLOCK(it->inp); SCTP_INP_RLOCK(it->inp); /* now go through each assoc which is in the desired state */ if (it->done_current_ep == 0) { if (it->function_inp != NULL) inp_skip = (*it->function_inp) (it->inp, it->pointer, it->val); it->done_current_ep = 1; } if (it->stcb == NULL) { /* run the per instance function */ it->stcb = LIST_FIRST(&it->inp->sctp_asoc_list); } SCTP_INP_RUNLOCK(it->inp); if ((inp_skip) || it->stcb == NULL) { if (it->function_inp_end != NULL) { inp_skip = (*it->function_inp_end) (it->inp, it->pointer, it->val); } goto no_stcb; } if ((it->stcb) && (it->stcb->asoc.stcb_starting_point_for_iterator == it)) { it->stcb->asoc.stcb_starting_point_for_iterator = NULL; } while (it->stcb) { SCTP_TCB_LOCK(it->stcb); if (it->asoc_state && ((it->stcb->asoc.state & it->asoc_state) != it->asoc_state)) { /* not in the right state... keep looking */ SCTP_TCB_UNLOCK(it->stcb); goto next_assoc; } /* mark the current iterator on the assoc */ it->stcb->asoc.stcb_starting_point_for_iterator = it; /* see if we have limited out the iterator loop */ iteration_count++; if (iteration_count > SCTP_ITERATOR_MAX_AT_ONCE) { start_timer_return: /* set a timer to continue this later */ SCTP_TCB_UNLOCK(it->stcb); sctp_timer_start(SCTP_TIMER_TYPE_ITERATOR, (struct sctp_inpcb *)it, NULL, NULL); SCTP_ITERATOR_UNLOCK(); return; } /* run function on this one */ (*it->function_assoc) (it->inp, it->stcb, it->pointer, it->val); /* * we lie here, it really needs to have its own type but * first I must verify that this won't effect things :-0 */ if (it->no_chunk_output == 0) sctp_chunk_output(it->inp, it->stcb, SCTP_OUTPUT_FROM_T3); SCTP_TCB_UNLOCK(it->stcb); next_assoc: it->stcb = LIST_NEXT(it->stcb, sctp_tcblist); if (it->stcb == NULL) { if (it->function_inp_end != NULL) { inp_skip = (*it->function_inp_end) (it->inp, it->pointer, it->val); } } } no_stcb: /* done with all assocs on this endpoint, move on to next endpoint */ it->done_current_ep = 0; SCTP_INP_WLOCK(it->inp); it->inp->inp_starting_point_for_iterator = NULL; SCTP_INP_WUNLOCK(it->inp); if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { it->inp = NULL; } else { SCTP_INP_INFO_RLOCK(); it->inp = LIST_NEXT(it->inp, sctp_list); SCTP_INP_INFO_RUNLOCK(); } if (it->inp == NULL) { goto done_with_iterator; } goto select_a_new_ep; } diff --git a/sys/netinet/sctp_uio.h b/sys/netinet/sctp_uio.h index 240db62d139c..03df51307b0d 100644 --- a/sys/netinet/sctp_uio.h +++ b/sys/netinet/sctp_uio.h @@ -1,1059 +1,1059 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_uio.h,v 1.11 2005/03/06 16:04:18 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #ifndef __sctp_uio_h__ #define __sctp_uio_h__ #if ! defined(_KERNEL) #include #endif #include #include #include #include typedef uint32_t sctp_assoc_t; /* On/Off setup for subscription to events */ struct sctp_event_subscribe { uint8_t sctp_data_io_event; uint8_t sctp_association_event; uint8_t sctp_address_event; uint8_t sctp_send_failure_event; uint8_t sctp_peer_error_event; uint8_t sctp_shutdown_event; uint8_t sctp_partial_delivery_event; uint8_t sctp_adaptation_layer_event; uint8_t sctp_authentication_event; uint8_t sctp_stream_reset_events; }; /* ancillary data types */ #define SCTP_INIT 0x0001 #define SCTP_SNDRCV 0x0002 #define SCTP_EXTRCV 0x0003 /* * ancillary data structures */ struct sctp_initmsg { uint32_t sinit_num_ostreams; uint32_t sinit_max_instreams; uint16_t sinit_max_attempts; uint16_t sinit_max_init_timeo; }; /* We add 96 bytes to the size of sctp_sndrcvinfo. * This makes the current structure 128 bytes long * which is nicely 64 bit aligned but also has room * for us to add more and keep ABI compatability. * For example, already we have the sctp_extrcvinfo * when enabled which is 48 bytes. */ /* * The assoc up needs a verfid * all sendrcvinfo's need a verfid for SENDING only. */ #define SCTP_ALIGN_RESV_PAD 96 #define SCTP_ALIGN_RESV_PAD_SHORT 80 struct sctp_sndrcvinfo { uint16_t sinfo_stream; uint16_t sinfo_ssn; uint16_t sinfo_flags; uint32_t sinfo_ppid; uint32_t sinfo_context; uint32_t sinfo_timetolive; uint32_t sinfo_tsn; uint32_t sinfo_cumtsn; sctp_assoc_t sinfo_assoc_id; uint8_t __reserve_pad[SCTP_ALIGN_RESV_PAD]; }; struct sctp_extrcvinfo { struct sctp_sndrcvinfo sreinfo_sinfo; uint16_t sreinfo_next_flags; uint16_t sreinfo_next_stream; uint32_t sreinfo_next_aid; uint32_t sreinfo_next_length; uint32_t sreinfo_next_ppid; uint8_t __reserve_pad[SCTP_ALIGN_RESV_PAD_SHORT]; }; #define SCTP_NO_NEXT_MSG 0x0000 #define SCTP_NEXT_MSG_AVAIL 0x0001 #define SCTP_NEXT_MSG_ISCOMPLETE 0x0002 #define SCTP_NEXT_MSG_IS_UNORDERED 0x0004 #define SCTP_NEXT_MSG_IS_NOTIFICATION 0x0008 struct sctp_snd_all_completes { uint16_t sall_stream; uint16_t sall_flags; uint32_t sall_ppid; uint32_t sall_context; uint32_t sall_num_sent; uint32_t sall_num_failed; }; /* Flags that go into the sinfo->sinfo_flags field */ #define SCTP_EOF 0x0100/* Start shutdown procedures */ #define SCTP_ABORT 0x0200/* Send an ABORT to peer */ #define SCTP_UNORDERED 0x0400/* Message is un-ordered */ #define SCTP_ADDR_OVER 0x0800/* Override the primary-address */ #define SCTP_SENDALL 0x1000/* Send this on all associations */ #define SCTP_EOR 0x2000/* end of message signal */ #define INVALID_SINFO_FLAG(x) (((x) & 0xffffff00 \ & ~(SCTP_EOF | SCTP_ABORT | SCTP_UNORDERED |\ SCTP_ADDR_OVER | SCTP_SENDALL | SCTP_EOR)) != 0) /* for the endpoint */ /* The lower byte is an enumeration of PR-SCTP policies */ #define SCTP_PR_SCTP_TTL 0x0001/* Time based PR-SCTP */ #define SCTP_PR_SCTP_BUF 0x0002/* Buffer based PR-SCTP */ #define SCTP_PR_SCTP_RTX 0x0003/* Number of retransmissions based PR-SCTP */ #define PR_SCTP_POLICY(x) ((x) & 0xff) #define PR_SCTP_ENABLED(x) (PR_SCTP_POLICY(x) != 0) #define PR_SCTP_TTL_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_TTL) #define PR_SCTP_BUF_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_BUF) #define PR_SCTP_RTX_ENABLED(x) (PR_SCTP_POLICY(x) == SCTP_PR_SCTP_RTX) #define PR_SCTP_INVALID_POLICY(x) (PR_SCTP_POLICY(x) > SCTP_PR_SCTP_RTX) /* Stat's */ struct sctp_pcbinfo { uint32_t ep_count; uint32_t asoc_count; uint32_t laddr_count; uint32_t raddr_count; uint32_t chk_count; uint32_t readq_count; uint32_t free_chunks; uint32_t stream_oque; }; struct sctp_sockstat { sctp_assoc_t ss_assoc_id; uint32_t ss_total_sndbuf; uint32_t ss_total_recv_buf; }; /* * notification event structures */ /* * association change event */ struct sctp_assoc_change { uint16_t sac_type; uint16_t sac_flags; uint32_t sac_length; uint16_t sac_state; uint16_t sac_error; uint16_t sac_outbound_streams; uint16_t sac_inbound_streams; sctp_assoc_t sac_assoc_id; }; /* sac_state values */ #define SCTP_COMM_UP 0x0001 #define SCTP_COMM_LOST 0x0002 #define SCTP_RESTART 0x0003 #define SCTP_SHUTDOWN_COMP 0x0004 #define SCTP_CANT_STR_ASSOC 0x0005 /* * Address event */ struct sctp_paddr_change { uint16_t spc_type; uint16_t spc_flags; uint32_t spc_length; struct sockaddr_storage spc_aaddr; uint32_t spc_state; uint32_t spc_error; sctp_assoc_t spc_assoc_id; }; /* paddr state values */ #define SCTP_ADDR_AVAILABLE 0x0001 #define SCTP_ADDR_UNREACHABLE 0x0002 #define SCTP_ADDR_REMOVED 0x0003 #define SCTP_ADDR_ADDED 0x0004 #define SCTP_ADDR_MADE_PRIM 0x0005 #define SCTP_ADDR_CONFIRMED 0x0006 /* * CAUTION: these are user exposed SCTP addr reachability states must be * compatible with SCTP_ADDR states in sctp_constants.h */ #ifdef SCTP_ACTIVE #undef SCTP_ACTIVE #endif #define SCTP_ACTIVE 0x0001 /* SCTP_ADDR_REACHABLE */ #ifdef SCTP_INACTIVE #undef SCTP_INACTIVE #endif #define SCTP_INACTIVE 0x0002 /* SCTP_ADDR_NOT_REACHABLE */ #ifdef SCTP_UNCONFIRMED #undef SCTP_UNCONFIRMED #endif #define SCTP_UNCONFIRMED 0x0200 /* SCTP_ADDR_UNCONFIRMED */ #ifdef SCTP_NOHEARTBEAT #undef SCTP_NOHEARTBEAT #endif #define SCTP_NOHEARTBEAT 0x0040 /* SCTP_ADDR_NOHB */ /* remote error events */ struct sctp_remote_error { uint16_t sre_type; uint16_t sre_flags; uint32_t sre_length; uint16_t sre_error; sctp_assoc_t sre_assoc_id; uint8_t sre_data[4]; }; /* data send failure event */ struct sctp_send_failed { uint16_t ssf_type; uint16_t ssf_flags; uint32_t ssf_length; uint32_t ssf_error; struct sctp_sndrcvinfo ssf_info; sctp_assoc_t ssf_assoc_id; uint8_t ssf_data[4]; }; /* flag that indicates state of data */ #define SCTP_DATA_UNSENT 0x0001 /* inqueue never on wire */ #define SCTP_DATA_SENT 0x0002 /* on wire at failure */ /* shutdown event */ struct sctp_shutdown_event { uint16_t sse_type; uint16_t sse_flags; uint32_t sse_length; sctp_assoc_t sse_assoc_id; }; /* Adaptation layer indication stuff */ struct sctp_adaptation_event { uint16_t sai_type; uint16_t sai_flags; uint32_t sai_length; uint32_t sai_adaptation_ind; sctp_assoc_t sai_assoc_id; }; struct sctp_setadaptation { uint32_t ssb_adaptation_ind; }; /* compatable old spelling */ struct sctp_adaption_event { uint16_t sai_type; uint16_t sai_flags; uint32_t sai_length; uint32_t sai_adaption_ind; sctp_assoc_t sai_assoc_id; }; struct sctp_setadaption { uint32_t ssb_adaption_ind; }; /* * Partial Delivery API event */ struct sctp_pdapi_event { uint16_t pdapi_type; uint16_t pdapi_flags; uint32_t pdapi_length; uint32_t pdapi_indication; uint16_t pdapi_stream; uint16_t pdapi_seq; sctp_assoc_t pdapi_assoc_id; }; /* indication values */ #define SCTP_PARTIAL_DELIVERY_ABORTED 0x0001 /* * authentication key event */ struct sctp_authkey_event { uint16_t auth_type; uint16_t auth_flags; uint32_t auth_length; uint16_t auth_keynumber; uint16_t auth_altkeynumber; uint32_t auth_indication; sctp_assoc_t auth_assoc_id; }; /* indication values */ #define SCTP_AUTH_NEWKEY 0x0001 /* * stream reset event */ struct sctp_stream_reset_event { uint16_t strreset_type; uint16_t strreset_flags; uint32_t strreset_length; sctp_assoc_t strreset_assoc_id; uint16_t strreset_list[0]; }; /* flags in strreset_flags field */ #define SCTP_STRRESET_INBOUND_STR 0x0001 #define SCTP_STRRESET_OUTBOUND_STR 0x0002 #define SCTP_STRRESET_ALL_STREAMS 0x0004 #define SCTP_STRRESET_STREAM_LIST 0x0008 #define SCTP_STRRESET_FAILED 0x0010 /* SCTP notification event */ struct sctp_tlv { uint16_t sn_type; uint16_t sn_flags; uint32_t sn_length; }; union sctp_notification { struct sctp_tlv sn_header; struct sctp_assoc_change sn_assoc_change; struct sctp_paddr_change sn_paddr_change; struct sctp_remote_error sn_remote_error; struct sctp_send_failed sn_send_failed; struct sctp_shutdown_event sn_shutdown_event; struct sctp_adaptation_event sn_adaptation_event; /* compatability same as above */ struct sctp_adaption_event sn_adaption_event; struct sctp_pdapi_event sn_pdapi_event; struct sctp_authkey_event sn_auth_event; struct sctp_stream_reset_event sn_strreset_event; }; /* notification types */ #define SCTP_ASSOC_CHANGE 0x0001 #define SCTP_PEER_ADDR_CHANGE 0x0002 #define SCTP_REMOTE_ERROR 0x0003 #define SCTP_SEND_FAILED 0x0004 #define SCTP_SHUTDOWN_EVENT 0x0005 #define SCTP_ADAPTATION_INDICATION 0x0006 /* same as above */ #define SCTP_ADAPTION_INDICATION 0x0006 #define SCTP_PARTIAL_DELIVERY_EVENT 0x0007 #define SCTP_AUTHENTICATION_EVENT 0x0008 #define SCTP_STREAM_RESET_EVENT 0x0009 /* * socket option structs */ struct sctp_paddrparams { sctp_assoc_t spp_assoc_id; struct sockaddr_storage spp_address; uint32_t spp_hbinterval; uint16_t spp_pathmaxrxt; uint32_t spp_pathmtu; uint32_t spp_flags; uint32_t spp_ipv6_flowlabel; uint8_t spp_ipv4_tos; }; #define SPP_HB_ENABLE 0x00000001 #define SPP_HB_DISABLE 0x00000002 #define SPP_HB_DEMAND 0x00000004 #define SPP_PMTUD_ENABLE 0x00000008 #define SPP_PMTUD_DISABLE 0x00000010 #define SPP_SACKDELAY_ENABLE 0x00000020 #define SPP_SACKDELAY_DISABLE 0x00000040 #define SPP_HB_TIME_IS_ZERO 0x00000080 #define SPP_IPV6_FLOWLABEL 0x00000100 #define SPP_IPV4_TOS 0x00000200 struct sctp_paddrinfo { sctp_assoc_t spinfo_assoc_id; struct sockaddr_storage spinfo_address; int32_t spinfo_state; uint32_t spinfo_cwnd; uint32_t spinfo_srtt; uint32_t spinfo_rto; uint32_t spinfo_mtu; }; struct sctp_rtoinfo { sctp_assoc_t srto_assoc_id; uint32_t srto_initial; uint32_t srto_max; uint32_t srto_min; }; struct sctp_assocparams { sctp_assoc_t sasoc_assoc_id; uint16_t sasoc_asocmaxrxt; uint16_t sasoc_number_peer_destinations; uint32_t sasoc_peer_rwnd; uint32_t sasoc_local_rwnd; uint32_t sasoc_cookie_life; uint32_t sasoc_sack_delay; uint32_t sasoc_sack_freq; }; struct sctp_setprim { sctp_assoc_t ssp_assoc_id; struct sockaddr_storage ssp_addr; }; struct sctp_setpeerprim { sctp_assoc_t sspp_assoc_id; struct sockaddr_storage sspp_addr; }; struct sctp_getaddresses { sctp_assoc_t sget_assoc_id; /* addr is filled in for N * sockaddr_storage */ struct sockaddr addr[1]; }; struct sctp_setstrm_timeout { sctp_assoc_t ssto_assoc_id; uint32_t ssto_timeout; uint32_t ssto_streamid_start; uint32_t ssto_streamid_end; }; struct sctp_status { sctp_assoc_t sstat_assoc_id; int32_t sstat_state; uint32_t sstat_rwnd; uint16_t sstat_unackdata; uint16_t sstat_penddata; uint16_t sstat_instrms; uint16_t sstat_outstrms; uint32_t sstat_fragmentation_point; struct sctp_paddrinfo sstat_primary; }; /* * AUTHENTICATION support */ /* SCTP_AUTH_CHUNK */ struct sctp_authchunk { uint8_t sauth_chunk; }; /* SCTP_AUTH_KEY */ struct sctp_authkey { sctp_assoc_t sca_assoc_id; uint16_t sca_keynumber; uint8_t sca_key[0]; }; /* SCTP_HMAC_IDENT */ struct sctp_hmacalgo { uint16_t shmac_idents[0]; }; /* AUTH hmac_id */ #define SCTP_AUTH_HMAC_ID_RSVD 0x0000 #define SCTP_AUTH_HMAC_ID_SHA1 0x0001 /* default, mandatory */ #define SCTP_AUTH_HMAC_ID_MD5 0x0002 /* deprecated */ #define SCTP_AUTH_HMAC_ID_SHA256 0x0003 #define SCTP_AUTH_HMAC_ID_SHA224 0x8001 #define SCTP_AUTH_HMAC_ID_SHA384 0x8002 #define SCTP_AUTH_HMAC_ID_SHA512 0x8003 /* SCTP_AUTH_ACTIVE_KEY / SCTP_AUTH_DELETE_KEY */ struct sctp_authkeyid { sctp_assoc_t scact_assoc_id; uint16_t scact_keynumber; }; /* SCTP_PEER_AUTH_CHUNKS / SCTP_LOCAL_AUTH_CHUNKS */ struct sctp_authchunks { sctp_assoc_t gauth_assoc_id; uint8_t gauth_chunks[0]; }; struct sctp_assoc_value { sctp_assoc_t assoc_id; uint32_t assoc_value; }; #define MAX_ASOC_IDS_RET 255 struct sctp_assoc_ids { uint16_t asls_assoc_start; /* array of index's start at 0 */ uint8_t asls_numb_present; uint8_t asls_more_to_get; sctp_assoc_t asls_assoc_id[MAX_ASOC_IDS_RET]; }; struct sctp_cwnd_args { struct sctp_nets *net; /* network to */ uint32_t cwnd_new_value;/* cwnd in k */ uint32_t inflight; /* flightsize in k */ uint32_t pseudo_cumack; int cwnd_augment; /* increment to it */ uint8_t meets_pseudo_cumack; uint8_t need_new_pseudo_cumack; uint8_t cnt_in_send; uint8_t cnt_in_str; }; struct sctp_blk_args { uint32_t onsb; /* in 1k bytes */ uint32_t sndlen; /* len of send being attempted */ uint32_t peer_rwnd; /* rwnd of peer */ uint16_t send_sent_qcnt;/* chnk cnt */ uint16_t stream_qcnt; /* chnk cnt */ uint16_t chunks_on_oque;/* chunks out */ uint16_t flight_size; /* flight size in k */ }; /* * Max we can reset in one setting, note this is dictated not by the define * but the size of a mbuf cluster so don't change this define and think you * can specify more. You must do multiple resets if you want to reset more * than SCTP_MAX_EXPLICIT_STR_RESET. */ #define SCTP_MAX_EXPLICT_STR_RESET 1000 #define SCTP_RESET_LOCAL_RECV 0x0001 #define SCTP_RESET_LOCAL_SEND 0x0002 #define SCTP_RESET_BOTH 0x0003 #define SCTP_RESET_TSN 0x0004 struct sctp_stream_reset { sctp_assoc_t strrst_assoc_id; uint16_t strrst_flags; uint16_t strrst_num_streams; /* 0 == ALL */ uint16_t strrst_list[0];/* list if strrst_num_streams is not 0 */ }; struct sctp_get_nonce_values { sctp_assoc_t gn_assoc_id; uint32_t gn_peers_tag; uint32_t gn_local_tag; }; /* Debugging logs */ struct sctp_str_log { void *stcb; uint32_t n_tsn; uint32_t e_tsn; uint16_t n_sseq; uint16_t e_sseq; uint16_t strm; }; struct sctp_sb_log { void *stcb; uint32_t so_sbcc; uint32_t stcb_sbcc; uint32_t incr; }; struct sctp_fr_log { uint32_t largest_tsn; uint32_t largest_new_tsn; uint32_t tsn; }; struct sctp_fr_map { uint32_t base; uint32_t cum; uint32_t high; }; struct sctp_rwnd_log { uint32_t rwnd; uint32_t send_size; uint32_t overhead; uint32_t new_rwnd; }; struct sctp_mbcnt_log { uint32_t total_queue_size; uint32_t size_change; uint32_t total_queue_mb_size; uint32_t mbcnt_change; }; struct sctp_sack_log { uint32_t cumack; uint32_t oldcumack; uint32_t tsn; uint16_t numGaps; uint16_t numDups; }; struct sctp_lock_log { void *sock; void *inp; uint8_t tcb_lock; uint8_t inp_lock; uint8_t info_lock; uint8_t sock_lock; uint8_t sockrcvbuf_lock; uint8_t socksndbuf_lock; uint8_t create_lock; uint8_t resv; }; struct sctp_rto_log { void *net; uint32_t rtt; }; struct sctp_nagle_log { void *stcb; uint32_t total_flight; uint32_t total_in_queue; uint16_t count_in_queue; uint16_t count_in_flight; }; struct sctp_sbwake_log { void *stcb; uint16_t send_q; uint16_t sent_q; uint16_t flight; uint16_t wake_cnt; uint8_t stream_qcnt; /* chnk cnt */ uint8_t chunks_on_oque; /* chunks out */ uint8_t sbflags; uint8_t sctpflags; }; struct sctp_misc_info { uint32_t log1; uint32_t log2; uint32_t log3; uint32_t log4; }; struct sctp_log_closing { void *inp; void *stcb; uint32_t sctp_flags; uint16_t state; int16_t loc; }; struct sctp_mbuf_log { struct mbuf *mp; caddr_t ext; caddr_t data; uint16_t size; uint8_t refcnt; uint8_t mbuf_flags; }; struct sctp_cwnd_log { uint32_t time_event; uint8_t from; uint8_t event_type; uint8_t resv[2]; union { struct sctp_log_closing close; struct sctp_blk_args blk; struct sctp_cwnd_args cwnd; struct sctp_str_log strlog; struct sctp_fr_log fr; struct sctp_fr_map map; struct sctp_rwnd_log rwnd; struct sctp_mbcnt_log mbcnt; struct sctp_sack_log sack; struct sctp_lock_log lock; struct sctp_rto_log rto; struct sctp_sb_log sb; struct sctp_nagle_log nagle; struct sctp_sbwake_log wake; struct sctp_mbuf_log mb; struct sctp_misc_info misc; } x; }; struct sctp_cwnd_log_req { int num_in_log; /* Number in log */ int num_ret; /* Number returned */ int start_at; /* start at this one */ int end_at; /* end at this one */ struct sctp_cwnd_log log[0]; }; struct sctpstat { /* MIB according to RFC 3873 */ u_long sctps_currestab; /* sctpStats 1 (Gauge32) */ u_long sctps_activeestab; /* sctpStats 2 (Counter32) */ u_long sctps_restartestab; u_long sctps_collisionestab; u_long sctps_passiveestab; /* sctpStats 3 (Counter32) */ u_long sctps_aborted; /* sctpStats 4 (Counter32) */ u_long sctps_shutdown; /* sctpStats 5 (Counter32) */ u_long sctps_outoftheblue; /* sctpStats 6 (Counter32) */ u_long sctps_checksumerrors; /* sctpStats 7 (Counter32) */ u_long sctps_outcontrolchunks; /* sctpStats 8 (Counter64) */ u_long sctps_outorderchunks; /* sctpStats 9 (Counter64) */ u_long sctps_outunorderchunks; /* sctpStats 10 (Counter64) */ u_long sctps_incontrolchunks; /* sctpStats 11 (Counter64) */ u_long sctps_inorderchunks; /* sctpStats 12 (Counter64) */ u_long sctps_inunorderchunks; /* sctpStats 13 (Counter64) */ u_long sctps_fragusrmsgs; /* sctpStats 14 (Counter64) */ u_long sctps_reasmusrmsgs; /* sctpStats 15 (Counter64) */ u_long sctps_outpackets;/* sctpStats 16 (Counter64) */ u_long sctps_inpackets; /* sctpStats 17 (Counter64) */ struct timeval sctps_discontinuitytime; /* sctpStats 18 (TimeStamp) */ /* input statistics: */ u_long sctps_recvpackets; /* total input packets */ u_long sctps_recvdatagrams; /* total input datagrams */ u_long sctps_recvpktwithdata; /* total packets that had data */ u_long sctps_recvsacks; /* total input SACK chunks */ u_long sctps_recvdata; /* total input DATA chunks */ u_long sctps_recvdupdata; /* total input duplicate DATA chunks */ u_long sctps_recvheartbeat; /* total input HB chunks */ u_long sctps_recvheartbeatack; /* total input HB-ACK chunks */ u_long sctps_recvecne; /* total input ECNE chunks */ u_long sctps_recvauth; /* total input AUTH chunks */ u_long sctps_recvauthmissing; /* total input chunks missing AUTH */ u_long sctps_recvivalhmacid; /* total number of invalid HMAC ids * received */ u_long sctps_recvivalkeyid; /* total number of invalid secret ids * received */ u_long sctps_recvauthfailed; /* total number of auth failed */ u_long sctps_recvexpress; /* total fast path receives all one * chunk */ u_long sctps_recvexpressm; /* total fast path multi-part data */ /* output statistics: */ u_long sctps_sendpackets; /* total output packets */ u_long sctps_sendsacks; /* total output SACKs */ u_long sctps_senddata; /* total output DATA chunks */ u_long sctps_sendretransdata; /* total output retransmitted DATA * chunks */ u_long sctps_sendfastretrans; /* total output fast retransmitted * DATA chunks */ u_long sctps_sendmultfastretrans; /* total FR's that happened * more than once to same * chunk (u-del multi-fr * algo). */ u_long sctps_sendheartbeat; /* total output HB chunks */ u_long sctps_sendecne; /* total output ECNE chunks */ u_long sctps_sendauth; /* total output AUTH chunks FIXME */ u_long sctps_senderrors;/* ip_output error counter */ /* PCKDROPREP statistics: */ u_long sctps_pdrpfmbox; /* Packet drop from middle box */ u_long sctps_pdrpfehos; /* P-drop from end host */ u_long sctps_pdrpmbda; /* P-drops with data */ u_long sctps_pdrpmbct; /* P-drops, non-data, non-endhost */ u_long sctps_pdrpbwrpt; /* P-drop, non-endhost, bandwidth rep only */ u_long sctps_pdrpcrupt; /* P-drop, not enough for chunk header */ u_long sctps_pdrpnedat; /* P-drop, not enough data to confirm */ u_long sctps_pdrppdbrk; /* P-drop, where process_chunk_drop said break */ u_long sctps_pdrptsnnf; /* P-drop, could not find TSN */ u_long sctps_pdrpdnfnd; /* P-drop, attempt reverse TSN lookup */ u_long sctps_pdrpdiwnp; /* P-drop, e-host confirms zero-rwnd */ u_long sctps_pdrpdizrw; /* P-drop, midbox confirms no space */ u_long sctps_pdrpbadd; /* P-drop, data did not match TSN */ u_long sctps_pdrpmark; /* P-drop, TSN's marked for Fast Retran */ /* timeouts */ u_long sctps_timoiterator; /* Number of iterator timers that * fired */ u_long sctps_timodata; /* Number of T3 data time outs */ u_long sctps_timowindowprobe; /* Number of window probe (T3) timers * that fired */ u_long sctps_timoinit; /* Number of INIT timers that fired */ u_long sctps_timosack; /* Number of sack timers that fired */ u_long sctps_timoshutdown; /* Number of shutdown timers that * fired */ u_long sctps_timoheartbeat; /* Number of heartbeat timers that * fired */ u_long sctps_timocookie;/* Number of times a cookie timeout fired */ u_long sctps_timosecret;/* Number of times an endpoint changed its * cookie secret */ u_long sctps_timopathmtu; /* Number of PMTU timers that fired */ u_long sctps_timoshutdownack; /* Number of shutdown ack timers that * fired */ u_long sctps_timoshutdownguard; /* Number of shutdown guard timers * that fired */ u_long sctps_timostrmrst; /* Number of stream reset timers that * fired */ u_long sctps_timoearlyfr; /* Number of early FR timers that * fired */ u_long sctps_timoasconf;/* Number of times an asconf timer fired */ u_long sctps_timoautoclose; /* Number of times auto close timer * fired */ u_long sctps_timoassockill; /* Number of asoc free timers expired */ u_long sctps_timoinpkill; /* Number of inp free timers expired */ /* Early fast retransmission counters */ u_long sctps_earlyfrstart; u_long sctps_earlyfrstop; u_long sctps_earlyfrmrkretrans; u_long sctps_earlyfrstpout; u_long sctps_earlyfrstpidsck1; u_long sctps_earlyfrstpidsck2; u_long sctps_earlyfrstpidsck3; u_long sctps_earlyfrstpidsck4; u_long sctps_earlyfrstrid; u_long sctps_earlyfrstrout; u_long sctps_earlyfrstrtmr; /* otheres */ u_long sctps_hdrops; /* packet shorter than header */ u_long sctps_badsum; /* checksum error */ u_long sctps_noport; /* no endpoint for port */ u_long sctps_badvtag; /* bad v-tag */ u_long sctps_badsid; /* bad SID */ u_long sctps_nomem; /* no memory */ u_long sctps_fastretransinrtt; /* number of multiple FR in a RTT * window */ u_long sctps_markedretrans; u_long sctps_naglesent; /* nagle allowed sending */ u_long sctps_naglequeued; /* nagle does't allow sending */ u_long sctps_maxburstqueued; /* max burst dosn't allow sending */ u_long sctps_ifnomemqueued; /* look ahead tells us no memory in * interface ring buffer OR we had a * send error and are queuing one * send. */ u_long sctps_windowprobed; /* total number of window probes sent */ u_long sctps_lowlevelerr; /* total times an output error causes * us to clamp down on next user send. */ u_long sctps_lowlevelerrusr; /* total times sctp_senderrors were * caused from a user send from a user * invoked send not a sack response */ u_long sctps_datadropchklmt; /* Number of in data drops due to * chunk limit reached */ u_long sctps_datadroprwnd; /* Number of in data drops due to rwnd * limit reached */ u_long sctps_ecnereducedcwnd; /* Number of times a ECN reduced the * cwnd */ u_long sctps_vtagexpress; /* Used express lookup via vtag */ u_long sctps_vtagbogus; /* Collision in express lookup. */ u_long sctps_primary_randry; /* Number of times the sender ran dry * of user data on primary */ u_long sctps_cmt_randry;/* Same for above */ u_long sctps_slowpath_sack; /* Sacks the slow way */ u_long sctps_wu_sacks_sent; /* Window Update only sacks sent */ u_long sctps_sends_with_flags; /* number of sends with sinfo_flags * !=0 */ u_long sctps_sends_with_unord /* number of undordered sends */ ; u_long sctps_sends_with_eof; /* number of sends with EOF flag set */ u_long sctps_sends_with_abort; /* number of sends with ABORT flag set */ u_long sctps_protocol_drain_calls; /* number of times protocol * drain called */ u_long sctps_protocol_drains_done; /* number of times we did a * protocol drain */ u_long sctps_read_peeks;/* Number of times recv was called with peek */ u_long sctps_cached_chk;/* Number of cached chunks used */ u_long sctps_cached_strmoq; /* Number of cached stream oq's used */ u_long sctps_left_abandon; /* Number of unread message abandonded * by close */ u_long sctps_send_burst_avoid; /* Send burst avoidance, already max * burst inflight to net */ u_long sctps_send_cwnd_avoid; /* Send cwnd full avoidance, already * max burst inflight to net */ }; #define SCTP_STAT_INCR(_x) SCTP_STAT_INCR_BY(_x,1) #define SCTP_STAT_DECR(_x) SCTP_STAT_DECR_BY(_x,1) #define SCTP_STAT_INCR_BY(_x,_d) atomic_add_long(&sctpstat._x, _d) -#define SCTP_STAT_DECR_BY(_x,_d) atomic_add_long(&sctpstat._x, -(_d)) +#define SCTP_STAT_DECR_BY(_x,_d) atomic_subtract_long(&sctpstat._x, _d) /* The following macros are for handling MIB values, */ #define SCTP_STAT_INCR_COUNTER32(_x) SCTP_STAT_INCR(_x) #define SCTP_STAT_INCR_COUNTER64(_x) SCTP_STAT_INCR(_x) #define SCTP_STAT_INCR_GAUGE32(_x) SCTP_STAT_INCR(_x) #define SCTP_STAT_DECR_COUNTER32(_x) SCTP_STAT_DECR(_x) #define SCTP_STAT_DECR_COUNTER64(_x) SCTP_STAT_DECR(_x) #define SCTP_STAT_DECR_GAUGE32(_x) SCTP_STAT_DECR(_x) union sctp_sockstore { #if defined(INET) || !defined(_KERNEL) struct sockaddr_in sin; #endif #if defined(INET6) || !defined(_KERNEL) struct sockaddr_in6 sin6; #endif struct sockaddr sa; }; struct xsctp_inpcb { uint32_t last; uint16_t local_port; uint16_t number_local_addresses; uint32_t number_associations; uint32_t flags; uint32_t features; uint32_t total_sends; uint32_t total_recvs; uint32_t total_nospaces; uint32_t fragmentation_point; /* add more endpoint specific data here */ }; struct xsctp_tcb { uint16_t LocalPort; /* sctpAssocEntry 3 */ uint16_t RemPort; /* sctpAssocEntry 4 */ union sctp_sockstore RemPrimAddr; /* sctpAssocEntry 5/6 */ uint32_t HeartBeatInterval; /* sctpAssocEntry 7 */ uint32_t State; /* sctpAssocEntry 8 */ uint32_t InStreams; /* sctpAssocEntry 9 */ uint32_t OutStreams; /* sctpAssocEntry 10 */ uint32_t MaxRetr; /* sctpAssocEntry 11 */ uint32_t PrimProcess; /* sctpAssocEntry 12 */ uint32_t T1expireds; /* sctpAssocEntry 13 */ uint32_t T2expireds; /* sctpAssocEntry 14 */ uint32_t RtxChunks; /* sctpAssocEntry 15 */ struct timeval StartTime; /* sctpAssocEntry 16 */ struct timeval DiscontinuityTime; /* sctpAssocEntry 17 */ uint32_t total_sends; uint32_t total_recvs; uint32_t local_tag; uint32_t remote_tag; uint32_t initial_tsn; uint32_t highest_tsn; uint32_t cumulative_tsn; uint32_t cumulative_tsn_ack; uint32_t mtu; /* add more association specific data here */ uint16_t number_local_addresses; uint16_t number_remote_addresses; }; struct xsctp_laddr { union sctp_sockstore LocalAddr; /* sctpAssocLocalAddrEntry 1/2 */ struct timeval LocalStartTime; /* sctpAssocLocalAddrEntry 3 */ /* add more local address specific data */ }; struct xsctp_raddr { union sctp_sockstore RemAddr; /* sctpAssocLocalRemEntry 1/2 */ uint8_t RemAddrActive; /* sctpAssocLocalRemEntry 3 */ uint8_t RemAddrConfirmed; /* */ uint8_t RemAddrHBActive;/* sctpAssocLocalRemEntry 4 */ uint32_t RemAddrRTO; /* sctpAssocLocalRemEntry 5 */ uint32_t RemAddrMaxPathRtx; /* sctpAssocLocalRemEntry 6 */ uint32_t RemAddrRtx; /* sctpAssocLocalRemEntry 7 */ uint32_t RemAddrErrorCounter; /* */ uint32_t RemAddrCwnd; /* */ uint32_t RemAddrFlightSize; /* */ uint32_t RemAddrMTU; /* */ struct timeval RemAddrStartTime; /* sctpAssocLocalRemEntry 8 */ /* add more remote address specific data */ }; /* * Kernel defined for sctp_send */ #if defined(_KERNEL) int sctp_lower_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *i_pak, struct mbuf *control, int flags, int use_rcvinfo, struct sctp_sndrcvinfo *srcv ,struct thread *p ); int sctp_sorecvmsg(struct socket *so, struct uio *uio, struct mbuf **mp, struct sockaddr *from, int fromlen, int *msg_flags, struct sctp_sndrcvinfo *sinfo, int filling_sinfo); #endif /* * API system calls */ #if !(defined(_KERNEL)) __BEGIN_DECLS int sctp_peeloff __P((int, sctp_assoc_t)); int sctp_bindx __P((int, struct sockaddr *, int, int)); int sctp_connectx __P((int, const struct sockaddr *, int, sctp_assoc_t *)); int sctp_getaddrlen __P((sa_family_t)); int sctp_getpaddrs __P((int, sctp_assoc_t, struct sockaddr **)); void sctp_freepaddrs __P((struct sockaddr *)); int sctp_getladdrs __P((int, sctp_assoc_t, struct sockaddr **)); void sctp_freeladdrs __P((struct sockaddr *)); int sctp_opt_info __P((int, sctp_assoc_t, int, void *, socklen_t *)); ssize_t sctp_sendmsg __P((int, const void *, size_t, const struct sockaddr *, socklen_t, uint32_t, uint32_t, uint16_t, uint32_t, uint32_t)); ssize_t sctp_send __P((int sd, const void *msg, size_t len, const struct sctp_sndrcvinfo *sinfo, int flags)); ssize_t sctp_sendx __P((int sd, const void *msg, size_t len, struct sockaddr *addrs, int addrcnt, struct sctp_sndrcvinfo *sinfo, int flags)); ssize_t sctp_sendmsgx __P((int sd, const void *, size_t, struct sockaddr *, int, uint32_t, uint32_t, uint16_t, uint32_t, uint32_t)); sctp_assoc_t sctp_getassocid __P((int sd, struct sockaddr *sa)); ssize_t sctp_recvmsg __P((int, void *, size_t, struct sockaddr *, socklen_t *, struct sctp_sndrcvinfo *, int *)); __END_DECLS #endif /* !_KERNEL */ #endif /* !__sctp_uio_h__ */ diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c index a37da5c2336d..e0aa9eb72bf4 100644 --- a/sys/netinet/sctp_usrreq.c +++ b/sys/netinet/sctp_usrreq.c @@ -1,3914 +1,3914 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_usrreq.c,v 1.48 2005/03/07 23:26:08 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #if defined(INET6) #include #endif #include #include #include #include #include #include #include #include void sctp_init(void) { /* Init the SCTP pcb in sctp_pcb.c */ u_long sb_max_adj; sctp_pcb_init(); if ((nmbclusters / 8) > SCTP_ASOC_MAX_CHUNKS_ON_QUEUE) sctp_max_chunks_on_queue = (nmbclusters / 8); /* * Allow a user to take no more than 1/2 the number of clusters or * the SB_MAX whichever is smaller for the send window. */ sb_max_adj = (u_long)((u_quad_t) (SB_MAX) * MCLBYTES / (MSIZE + MCLBYTES)); sctp_sendspace = min((min(SB_MAX, sb_max_adj)), ((nmbclusters / 2) * SCTP_DEFAULT_MAXSEGMENT)); /* * Now for the recv window, should we take the same amount? or * should I do 1/2 the SB_MAX instead in the SB_MAX min above. For * now I will just copy. */ sctp_recvspace = sctp_sendspace; } /* * cleanup of the sctppcbinfo structure. * Assumes that the sctppcbinfo lock is held. */ void sctp_pcbinfo_cleanup(void) { /* free the hash tables */ if (sctppcbinfo.sctp_asochash != NULL) SCTP_HASH_FREE(sctppcbinfo.sctp_asochash, sctppcbinfo.hashasocmark); if (sctppcbinfo.sctp_ephash != NULL) SCTP_HASH_FREE(sctppcbinfo.sctp_ephash, sctppcbinfo.hashmark); if (sctppcbinfo.sctp_tcpephash != NULL) SCTP_HASH_FREE(sctppcbinfo.sctp_tcpephash, sctppcbinfo.hashtcpmark); if (sctppcbinfo.sctp_restarthash != NULL) SCTP_HASH_FREE(sctppcbinfo.sctp_restarthash, sctppcbinfo.hashrestartmark); } static void sctp_pathmtu_adjustment(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, uint16_t nxtsz) { struct sctp_tmit_chunk *chk; /* Adjust that too */ stcb->asoc.smallest_mtu = nxtsz; /* now off to subtract IP_DF flag if needed */ #ifdef SCTP_PRINT_FOR_B_AND_M printf("sctp_pathmtu_adjust called inp:%p stcb:%p net:%p nxtsz:%d\n", inp, stcb, net, nxtsz); #endif TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) { if ((chk->send_size + IP_HDR_SIZE) > nxtsz) { chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; } } TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if ((chk->send_size + IP_HDR_SIZE) > nxtsz) { /* * For this guy we also mark for immediate resend * since we sent to big of chunk */ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; if (chk->sent != SCTP_DATAGRAM_RESEND) { sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt); } chk->sent = SCTP_DATAGRAM_RESEND; chk->rec.data.doing_fast_retransmit = 0; #ifdef SCTP_FLIGHT_LOGGING sctp_misc_ints(SCTP_FLIGHT_LOG_DOWN_PMTU, chk->whoTo->flight_size, chk->book_size, (uintptr_t) chk->whoTo, chk->rec.data.TSN_seq); #endif /* Clear any time so NO RTT is being done */ chk->do_rtt = 0; sctp_flight_size_decrease(chk); sctp_total_flight_decrease(stcb, chk); } } } static void sctp_notify_mbuf(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, struct ip *ip, struct sctphdr *sh) { struct icmp *icmph; int totsz, tmr_stopped = 0; uint16_t nxtsz; /* protection */ if ((inp == NULL) || (stcb == NULL) || (net == NULL) || (ip == NULL) || (sh == NULL)) { if (stcb != NULL) SCTP_TCB_UNLOCK(stcb); return; } /* First job is to verify the vtag matches what I would send */ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) { SCTP_TCB_UNLOCK(stcb); return; } icmph = (struct icmp *)((caddr_t)ip - (sizeof(struct icmp) - sizeof(struct ip))); if (icmph->icmp_type != ICMP_UNREACH) { /* We only care about unreachable */ SCTP_TCB_UNLOCK(stcb); return; } if (icmph->icmp_code != ICMP_UNREACH_NEEDFRAG) { /* not a unreachable message due to frag. */ SCTP_TCB_UNLOCK(stcb); return; } totsz = ip->ip_len; nxtsz = ntohs(icmph->icmp_seq); if (nxtsz == 0) { /* * old type router that does not tell us what the next size * mtu is. Rats we will have to guess (in a educated fashion * of course) */ nxtsz = find_next_best_mtu(totsz); } /* Stop any PMTU timer */ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) { tmr_stopped = 1; sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_1); } /* Adjust destination size limit */ if (net->mtu > nxtsz) { net->mtu = nxtsz; } /* now what about the ep? */ if (stcb->asoc.smallest_mtu > nxtsz) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("notify_mbuf (ICMP) calls sctp_pathmtu_adjust mtu:%d\n", nxtsz); #endif sctp_pathmtu_adjustment(inp, stcb, net, nxtsz); } if (tmr_stopped) sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net); SCTP_TCB_UNLOCK(stcb); } void sctp_notify(struct sctp_inpcb *inp, int error, struct sctphdr *sh, struct sockaddr *to, struct sctp_tcb *stcb, struct sctp_nets *net) { /* protection */ if ((inp == NULL) || (stcb == NULL) || (net == NULL) || (sh == NULL) || (to == NULL)) { return; } /* First job is to verify the vtag matches what I would send */ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) { return; } /* FIX ME FIX ME PROTOPT i.e. no SCTP should ALWAYS be an ABORT */ if ((error == EHOSTUNREACH) || /* Host is not reachable */ (error == EHOSTDOWN) || /* Host is down */ (error == ECONNREFUSED) || /* Host refused the connection, (not * an abort?) */ (error == ENOPROTOOPT) /* SCTP is not present on host */ ) { /* * Hmm reachablity problems we must examine closely. If its * not reachable, we may have lost a network. Or if there is * NO protocol at the other end named SCTP. well we consider * it a OOTB abort. */ if ((error == EHOSTUNREACH) || (error == EHOSTDOWN)) { if (net->dest_state & SCTP_ADDR_REACHABLE) { /* Ok that destination is NOT reachable */ printf("ICMP (thresh %d/%d) takes interface %p down\n", net->error_count, net->failure_threshold, net); net->dest_state &= ~SCTP_ADDR_REACHABLE; net->dest_state |= SCTP_ADDR_NOT_REACHABLE; net->error_count = net->failure_threshold + 1; sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, stcb, SCTP_FAILED_THRESHOLD, (void *)net); } if (stcb) SCTP_TCB_UNLOCK(stcb); } else { /* * Here the peer is either playing tricks on us, * including an address that belongs to someone who * does not support SCTP OR was a userland * implementation that shutdown and now is dead. In * either case treat it like a OOTB abort with no * TCB */ sctp_abort_notification(stcb, SCTP_PEER_FAULTY); sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_2); /* no need to unlock here, since the TCB is gone */ } } else { /* Send all others to the app */ if (stcb) SCTP_TCB_UNLOCK(stcb); if (inp->sctp_socket) { #ifdef SCTP_LOCK_LOGGING sctp_log_lock(inp, stcb, SCTP_LOG_LOCK_SOCK); #endif SOCK_LOCK(inp->sctp_socket); inp->sctp_socket->so_error = error; sctp_sowwakeup(inp, inp->sctp_socket); SOCK_UNLOCK(inp->sctp_socket); } } } void sctp_ctlinput(cmd, sa, vip) int cmd; struct sockaddr *sa; void *vip; { struct ip *ip = vip; struct sctphdr *sh; uint32_t vrf_id; /* FIX, for non-bsd is this right? */ vrf_id = SCTP_DEFAULT_VRFID; if (sa->sa_family != AF_INET || ((struct sockaddr_in *)sa)->sin_addr.s_addr == INADDR_ANY) { return; } if (PRC_IS_REDIRECT(cmd)) { ip = 0; } else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) { return; } if (ip) { struct sctp_inpcb *inp = NULL; struct sctp_tcb *stcb = NULL; struct sctp_nets *net = NULL; struct sockaddr_in to, from; sh = (struct sctphdr *)((caddr_t)ip + (ip->ip_hl << 2)); bzero(&to, sizeof(to)); bzero(&from, sizeof(from)); from.sin_family = to.sin_family = AF_INET; from.sin_len = to.sin_len = sizeof(to); from.sin_port = sh->src_port; from.sin_addr = ip->ip_src; to.sin_port = sh->dest_port; to.sin_addr = ip->ip_dst; /* * 'to' holds the dest of the packet that failed to be sent. * 'from' holds our local endpoint address. Thus we reverse * the to and the from in the lookup. */ stcb = sctp_findassociation_addr_sa((struct sockaddr *)&from, (struct sockaddr *)&to, &inp, &net, 1, vrf_id); if (stcb != NULL && inp && (inp->sctp_socket != NULL)) { if (cmd != PRC_MSGSIZE) { int cm; if (cmd == PRC_HOSTDEAD) { cm = EHOSTUNREACH; } else { cm = inetctlerrmap[cmd]; } sctp_notify(inp, cm, sh, (struct sockaddr *)&to, stcb, net); } else { /* handle possible ICMP size messages */ sctp_notify_mbuf(inp, stcb, net, ip, sh); } } else { if ((stcb == NULL) && (inp != NULL)) { /* reduce ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } } } return; } static int sctp_getcred(SYSCTL_HANDLER_ARGS) { struct xucred xuc; struct sockaddr_in addrs[2]; struct sctp_inpcb *inp; struct sctp_nets *net; struct sctp_tcb *stcb; int error; uint32_t vrf_id; /* FIX, for non-bsd is this right? */ vrf_id = SCTP_DEFAULT_VRFID; /* * XXXRW: Other instances of getcred use SUSER_ALLOWJAIL, as socket * visibility is scoped using cr_canseesocket(), which it is not * here. */ error = priv_check_cred(req->td->td_ucred, PRIV_NETINET_GETCRED, SUSER_ALLOWJAIL); if (error) return (error); error = SYSCTL_IN(req, addrs, sizeof(addrs)); if (error) return (error); stcb = sctp_findassociation_addr_sa(sintosa(&addrs[0]), sintosa(&addrs[1]), &inp, &net, 1, vrf_id); if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) { if ((inp != NULL) && (stcb == NULL)) { /* reduce ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); goto cred_can_cont; } error = ENOENT; goto out; } SCTP_TCB_UNLOCK(stcb); /* * We use the write lock here, only since in the error leg we need * it. If we used RLOCK, then we would have to * wlock/decr/unlock/rlock. Which in theory could create a hole. * Better to use higher wlock. */ SCTP_INP_WLOCK(inp); cred_can_cont: error = cr_canseesocket(req->td->td_ucred, inp->sctp_socket); if (error) { SCTP_INP_WUNLOCK(inp); goto out; } cru2x(inp->sctp_socket->so_cred, &xuc); SCTP_INP_WUNLOCK(inp); error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); out: return (error); } SYSCTL_PROC(_net_inet_sctp, OID_AUTO, getcred, CTLTYPE_OPAQUE | CTLFLAG_RW, 0, 0, sctp_getcred, "S,ucred", "Get the ucred of a SCTP connection"); static void sctp_abort(struct socket *so) { struct sctp_inpcb *inp; uint32_t flags; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return; sctp_must_try_again: flags = inp->sctp_flags; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 17); #endif if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) && (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 16); #endif - sctp_inpcb_free(inp, 1, 0); + sctp_inpcb_free(inp, 1, 1); SOCK_LOCK(so); SCTP_SB_CLEAR(so->so_snd); /* * same for the rcv ones, they are only here for the * accounting/select. */ SCTP_SB_CLEAR(so->so_rcv); /* Now null out the reference, we are completely detached. */ so->so_pcb = NULL; SOCK_UNLOCK(so); } else { flags = inp->sctp_flags; if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) { goto sctp_must_try_again; } } return; } static int sctp_attach(struct socket *so, int proto, struct thread *p) { struct sctp_inpcb *inp; struct inpcb *ip_inp; int error; #ifdef IPSEC uint32_t flags; #endif inp = (struct sctp_inpcb *)so->so_pcb; if (inp != 0) { return EINVAL; } error = SCTP_SORESERVE(so, sctp_sendspace, sctp_recvspace); if (error) { return error; } error = sctp_inpcb_alloc(so); if (error) { return error; } inp = (struct sctp_inpcb *)so->so_pcb; SCTP_INP_WLOCK(inp); inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUND_V6; /* I'm not v6! */ ip_inp = &inp->ip_inp.inp; ip_inp->inp_vflag |= INP_IPV4; ip_inp->inp_ip_ttl = ip_defttl; #ifdef IPSEC error = ipsec_init_pcbpolicy(so, &ip_inp->inp_sp); #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 17); #endif if (error != 0) { flags = inp->sctp_flags; if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) && (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 15); #endif SCTP_INP_WUNLOCK(inp); - sctp_inpcb_free(inp, 1, 0); + sctp_inpcb_free(inp, 1, 1); } else { SCTP_INP_WUNLOCK(inp); } return error; } #endif /* IPSEC */ SCTP_INP_WUNLOCK(inp); return 0; } static int sctp_bind(struct socket *so, struct sockaddr *addr, struct thread *p) { struct sctp_inpcb *inp; int error; #ifdef INET6 if (addr && addr->sa_family != AF_INET) /* must be a v4 address! */ return EINVAL; #endif /* INET6 */ inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return EINVAL; error = sctp_inpcb_bind(so, addr, p); return error; } static void sctp_close(struct socket *so) { struct sctp_inpcb *inp; uint32_t flags; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return; /* * Inform all the lower layer assoc that we are done. */ sctp_must_try_again: flags = inp->sctp_flags; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 17); #endif if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) && (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) { if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || (so->so_rcv.sb_cc > 0)) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 13); #endif sctp_inpcb_free(inp, 1, 1); } else { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 14); #endif sctp_inpcb_free(inp, 0, 1); } /* * The socket is now detached, no matter what the state of * the SCTP association. */ SOCK_LOCK(so); SCTP_SB_CLEAR(so->so_snd); /* * same for the rcv ones, they are only here for the * accounting/select. */ SCTP_SB_CLEAR(so->so_rcv); /* Now null out the reference, we are completely detached. */ so->so_pcb = NULL; SOCK_UNLOCK(so); } else { flags = inp->sctp_flags; if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) { goto sctp_must_try_again; } } return; } int sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *p); int sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *p) { struct sctp_inpcb *inp; int error; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { if (control) { sctp_m_freem(control); control = NULL; } sctp_m_freem(m); return EINVAL; } /* Got to have an to address if we are NOT a connected socket */ if ((addr == NULL) && ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) || (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) ) { goto connected_type; } else if (addr == NULL) { error = EDESTADDRREQ; sctp_m_freem(m); if (control) { sctp_m_freem(control); control = NULL; } return (error); } #ifdef INET6 if (addr->sa_family != AF_INET) { /* must be a v4 address! */ sctp_m_freem(m); if (control) { sctp_m_freem(control); control = NULL; } error = EDESTADDRREQ; return EINVAL; } #endif /* INET6 */ connected_type: /* now what about control */ if (control) { if (inp->control) { printf("huh? control set?\n"); sctp_m_freem(inp->control); inp->control = NULL; } inp->control = control; } /* Place the data */ if (inp->pkt) { SCTP_BUF_NEXT(inp->pkt_last) = m; inp->pkt_last = m; } else { inp->pkt_last = inp->pkt = m; } if ( /* FreeBSD uses a flag passed */ ((flags & PRUS_MORETOCOME) == 0) ) { /* * note with the current version this code will only be used * by OpenBSD-- NetBSD, FreeBSD, and MacOS have methods for * re-defining sosend to use the sctp_sosend. One can * optionally switch back to this code (by changing back the * definitions) but this is not advisable. This code is used * by FreeBSD when sending a file with sendfile() though. */ int ret; ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags); inp->pkt = NULL; inp->control = NULL; return (ret); } else { return (0); } } static int sctp_disconnect(struct socket *so) { struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { return (ENOTCONN); } SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { if (SCTP_LIST_EMPTY(&inp->sctp_asoc_list)) { /* No connection */ SCTP_INP_RUNLOCK(inp); return (0); } else { struct sctp_association *asoc; struct sctp_tcb *stcb; stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { SCTP_INP_RUNLOCK(inp); return (EINVAL); } SCTP_TCB_LOCK(stcb); asoc = &stcb->asoc; if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* We are about to be freed, out of here */ SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); return (0); } if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || (so->so_rcv.sb_cc > 0)) { if (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) { /* Left with Data unread */ struct mbuf *err; err = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (err) { /* * Fill in the user * initiated abort */ struct sctp_paramhdr *ph; ph = mtod(err, struct sctp_paramhdr *); SCTP_BUF_LEN(err) = sizeof(struct sctp_paramhdr); ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(err)); } sctp_send_abort_tcb(stcb, err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); } SCTP_INP_RUNLOCK(inp); if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_3); /* No unlock tcb assoc is gone */ return (0); } if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->stream_queue_cnt == 0)) { /* there is nothing queued to send, so done */ if (asoc->locked_on_sending) { goto abort_anyway; } if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* only send SHUTDOWN 1st time thru */ sctp_stop_timers_for_shutdown(stcb); sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3); if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } else { /* * we still got (or just got) data to send, * so set SHUTDOWN_PENDING */ /* * XXX sockets draft says that SCTP_EOF * should be sent with no data. currently, * we will allow user data to be sent first * and move to SHUTDOWN-PENDING */ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); if (asoc->locked_on_sending) { /* Locked to send out the data */ struct sctp_stream_queue_pending *sp; sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead); if (sp == NULL) { printf("Error, sp is NULL, locked on sending is non-null strm:%d\n", asoc->locked_on_sending->stream_no); } else { if ((sp->length == 0) && (sp->msg_is_complete == 0)) asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; } } if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) { struct mbuf *op_err; abort_anyway: op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { /* * Fill in the user * initiated abort */ struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(op_err) = (sizeof(struct sctp_paramhdr) + sizeof(uint32_t)); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons( SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_USRREQ + SCTP_LOC_4); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_4; sctp_send_abort_tcb(stcb, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } SCTP_INP_RUNLOCK(inp); sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_5); return (0); } } SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); return (0); } /* not reached */ printf("Not reached reached?\n"); } else { /* UDP model does not support this */ SCTP_INP_RUNLOCK(inp); return EOPNOTSUPP; } } int sctp_shutdown(struct socket *so) { struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { return EINVAL; } SCTP_INP_RLOCK(inp); /* For UDP model this is a invalid call */ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { /* Restore the flags that the soshutdown took away. */ so->so_rcv.sb_state &= ~SBS_CANTRCVMORE; /* This proc will wakeup for read and do nothing (I hope) */ SCTP_INP_RUNLOCK(inp); return (EOPNOTSUPP); } /* * Ok if we reach here its the TCP model and it is either a SHUT_WR * or SHUT_RDWR. This means we put the shutdown flag against it. */ { struct sctp_tcb *stcb; struct sctp_association *asoc; socantsendmore(so); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { /* * Ok we hit the case that the shutdown call was * made after an abort or something. Nothing to do * now. */ SCTP_INP_RUNLOCK(inp); return (0); } SCTP_TCB_LOCK(stcb); asoc = &stcb->asoc; if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->stream_queue_cnt == 0)) { if (asoc->locked_on_sending) { goto abort_anyway; } /* there is nothing queued to send, so I'm done... */ if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) { /* only send SHUTDOWN the first time through */ sctp_stop_timers_for_shutdown(stcb); sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3); if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } else { /* * we still got (or just got) data to send, so set * SHUTDOWN_PENDING */ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); if (asoc->locked_on_sending) { /* Locked to send out the data */ struct sctp_stream_queue_pending *sp; sp = TAILQ_LAST(&asoc->locked_on_sending->outqueue, sctp_streamhead); if (sp == NULL) { printf("Error, sp is NULL, locked on sending is non-null strm:%d\n", asoc->locked_on_sending->stream_no); } else { if ((sp->length == 0) && (sp->msg_is_complete == 0)) { asoc->state |= SCTP_STATE_PARTIAL_MSG_LEFT; } } } if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) { struct mbuf *op_err; abort_anyway: op_err = sctp_get_mbuf_for_msg((sizeof(struct sctp_paramhdr) + sizeof(uint32_t)), 0, M_DONTWAIT, 1, MT_DATA); if (op_err) { /* Fill in the user initiated abort */ struct sctp_paramhdr *ph; uint32_t *ippp; SCTP_BUF_LEN(op_err) = sizeof(struct sctp_paramhdr) + sizeof(uint32_t); ph = mtod(op_err, struct sctp_paramhdr *); ph->param_type = htons( SCTP_CAUSE_USER_INITIATED_ABT); ph->param_length = htons(SCTP_BUF_LEN(op_err)); ippp = (uint32_t *) (ph + 1); *ippp = htonl(SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6); } stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6; sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, op_err); goto skip_unlock; } } SCTP_TCB_UNLOCK(stcb); } skip_unlock: SCTP_INP_RUNLOCK(inp); return 0; } /* * copies a "user" presentable address and removes embedded scope, etc. * returns 0 on success, 1 on error */ static uint32_t sctp_fill_user_address(struct sockaddr_storage *ss, struct sockaddr *sa) { struct sockaddr_in6 lsa6; sa = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)sa, &lsa6); memcpy(ss, sa, sa->sa_len); return (0); } static size_t sctp_fill_up_addresses_vrf(struct sctp_inpcb *inp, struct sctp_tcb *stcb, size_t limit, struct sockaddr_storage *sas, uint32_t vrf_id) { struct sctp_ifn *sctp_ifn; struct sctp_ifa *sctp_ifa; int loopback_scope, ipv4_local_scope, local_scope, site_scope; size_t actual; int ipv4_addr_legal, ipv6_addr_legal; struct sctp_vrf *vrf; actual = 0; if (limit <= 0) return (actual); if (stcb) { /* Turn on all the appropriate scope */ loopback_scope = stcb->asoc.loopback_scope; ipv4_local_scope = stcb->asoc.ipv4_local_scope; local_scope = stcb->asoc.local_scope; site_scope = stcb->asoc.site_scope; } else { /* Turn on ALL scope, since we look at the EP */ loopback_scope = ipv4_local_scope = local_scope = site_scope = 1; } ipv4_addr_legal = ipv6_addr_legal = 0; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ipv6_addr_legal = 1; if (SCTP_IPV6_V6ONLY(inp) == 0) { ipv4_addr_legal = 1; } } else { ipv4_addr_legal = 1; } vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { return (0); } if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) { if ((loopback_scope == 0) && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) { /* Skip loopback if loopback_scope not set */ continue; } LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if (stcb) { /* * For the BOUND-ALL case, the list * associated with a TCB is Always * considered a reverse list.. i.e. * it lists addresses that are NOT * part of the association. If this * is one of those we must skip it. */ if (sctp_is_addr_restricted(stcb, sctp_ifa)) { continue; } } if ((sctp_ifa->address.sa.sa_family == AF_INET) && (ipv4_addr_legal)) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)&sctp_ifa->address.sa; if (sin->sin_addr.s_addr == 0) { /* * we skip unspecifed * addresses */ continue; } if ((ipv4_local_scope == 0) && (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { continue; } if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) { in6_sin_2_v4mapsin6(sin, (struct sockaddr_in6 *)sas); ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(struct sockaddr_in6)); actual += sizeof(sizeof(struct sockaddr_in6)); } else { memcpy(sas, sin, sizeof(*sin)); ((struct sockaddr_in *)sas)->sin_port = inp->sctp_lport; sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin)); actual += sizeof(*sin); } if (actual >= limit) { return (actual); } } else if ((sctp_ifa->address.sa.sa_family == AF_INET6) && (ipv6_addr_legal)) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)&sctp_ifa->address.sa; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* * we skip unspecifed * addresses */ continue; } if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { if (local_scope == 0) continue; if (sin6->sin6_scope_id == 0) { if (sa6_recoverscope(sin6) != 0) /* * bad link * local * address */ continue; } } if ((site_scope == 0) && (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) { continue; } memcpy(sas, sin6, sizeof(*sin6)); ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin6)); actual += sizeof(*sin6); if (actual >= limit) { return (actual); } } } } } else { struct sctp_laddr *laddr; /* The list is a NEGATIVE list */ LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (stcb) { if (sctp_is_addr_restricted(stcb, laddr->ifa)) { continue; } } if (sctp_fill_user_address(sas, &laddr->ifa->address.sa)) continue; ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; sas = (struct sockaddr_storage *)((caddr_t)sas + laddr->ifa->address.sa.sa_len); actual += laddr->ifa->address.sa.sa_len; if (actual >= limit) { return (actual); } } } return (actual); } static size_t sctp_fill_up_addresses(struct sctp_inpcb *inp, struct sctp_tcb *stcb, size_t limit, struct sockaddr_storage *sas) { size_t size = 0; /* fill up addresses for the endpoint's default vrf */ size = sctp_fill_up_addresses_vrf(inp, stcb, limit, sas, inp->def_vrf_id); return (size); } static int sctp_count_max_addresses_vrf(struct sctp_inpcb *inp, uint32_t vrf_id) { int cnt = 0; struct sctp_vrf *vrf = NULL; /* * In both sub-set bound an bound_all cases we return the MAXIMUM * number of addresses that you COULD get. In reality the sub-set * bound may have an exclusion list for a given TCB OR in the * bound-all case a TCB may NOT include the loopback or other * addresses as well. */ vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { return (0); } if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { struct sctp_ifn *sctp_ifn; struct sctp_ifa *sctp_ifa; LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) { LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { /* Count them if they are the right type */ if (sctp_ifa->address.sa.sa_family == AF_INET) { if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) cnt += sizeof(struct sockaddr_in6); else cnt += sizeof(struct sockaddr_in); } else if (sctp_ifa->address.sa.sa_family == AF_INET6) cnt += sizeof(struct sockaddr_in6); } } } else { struct sctp_laddr *laddr; LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa->address.sa.sa_family == AF_INET) { if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) cnt += sizeof(struct sockaddr_in6); else cnt += sizeof(struct sockaddr_in); } else if (laddr->ifa->address.sa.sa_family == AF_INET6) cnt += sizeof(struct sockaddr_in6); } } return (cnt); } static int sctp_count_max_addresses(struct sctp_inpcb *inp) { int cnt = 0; /* count addresses for the endpoint's default VRF */ cnt = sctp_count_max_addresses_vrf(inp, inp->def_vrf_id); return (cnt); } static int sctp_do_connect_x(struct socket *so, struct sctp_inpcb *inp, void *optval, size_t optsize, void *p, int delay) { int error = 0; int creat_lock_on = 0; struct sctp_tcb *stcb = NULL; struct sockaddr *sa; int num_v6 = 0, num_v4 = 0, *totaddrp, totaddr; int added = 0; uint32_t vrf_id; sctp_assoc_t *a_id; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_PCB1) { printf("Connectx called\n"); } #endif /* SCTP_DEBUG */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { /* We are already connected AND the TCP model */ return (EADDRINUSE); } if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { return (EINVAL); } if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); SCTP_INP_RUNLOCK(inp); } if (stcb) { return (EALREADY); } SCTP_INP_INCR_REF(inp); SCTP_ASOC_CREATE_LOCK(inp); creat_lock_on = 1; if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { error = EFAULT; goto out_now; } totaddrp = (int *)optval; totaddr = *totaddrp; sa = (struct sockaddr *)(totaddrp + 1); stcb = sctp_connectx_helper_find(inp, sa, &totaddr, &num_v4, &num_v6, &error, (optsize - sizeof(int))); if (stcb != NULL) { /* Already have or am bring up an association */ SCTP_ASOC_CREATE_UNLOCK(inp); creat_lock_on = 0; SCTP_TCB_UNLOCK(stcb); error = EALREADY; goto out_now; } #ifdef INET6 if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && (num_v6 > 0)) { error = EINVAL; goto out_now; } if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && (num_v4 > 0)) { struct in6pcb *inp6; inp6 = (struct in6pcb *)inp; if (SCTP_IPV6_V6ONLY(inp6)) { /* * if IPV6_V6ONLY flag, ignore connections destined * to a v4 addr or v4-mapped addr */ error = EINVAL; goto out_now; } } #endif /* INET6 */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == SCTP_PCB_FLAGS_UNBOUND) { /* Bind a ephemeral port */ error = sctp_inpcb_bind(so, NULL, p); if (error) { goto out_now; } } /* FIX ME: do we want to pass in a vrf on the connect call? */ vrf_id = inp->def_vrf_id; /* We are GOOD to go */ stcb = sctp_aloc_assoc(inp, sa, 1, &error, 0, vrf_id); if (stcb == NULL) { /* Gak! no memory */ goto out_now; } stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; /* move to second address */ if (sa->sa_family == AF_INET) sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in)); else sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6)); added = sctp_connectx_helper_add(stcb, sa, (totaddr - 1), &error); /* Fill in the return id */ a_id = (sctp_assoc_t *) optval; *a_id = sctp_get_associd(stcb); /* initialize authentication parameters for the assoc */ sctp_initialize_auth_params(inp, stcb); if (delay) { /* doing delayed connection */ stcb->asoc.delayed_connection = 1; sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, stcb->asoc.primary_destination); } else { - SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); sctp_send_initiate(inp, stcb); } SCTP_TCB_UNLOCK(stcb); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; /* Set the connected flag so we can queue data */ soisconnecting(so); } out_now: if (creat_lock_on) SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_INP_DECR_REF(inp); return error; } #define SCTP_FIND_STCB(inp, stcb, assoc_id) \ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { \ SCTP_INP_RLOCK(inp); \ stcb = LIST_FIRST(&inp->sctp_asoc_list); \ if (stcb) \ SCTP_TCB_LOCK(stcb); \ SCTP_INP_RUNLOCK(inp); \ } else if (assoc_id != 0) { \ stcb = sctp_findassociation_ep_asocid(inp, assoc_id, 1); \ if (stcb == NULL) { \ error = ENOENT; \ break; \ } \ } else { \ stcb = NULL; \ } #define SCTP_CHECK_AND_CAST(destp, srcp, type, size) \ if (size < sizeof(type)) { \ error = EINVAL; \ break; \ } else { \ destp = (type *)srcp; \ } static int sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize, void *p) { struct sctp_inpcb *inp; int error, val = 0; struct sctp_tcb *stcb = NULL; if (optval == NULL) { return (EINVAL); } inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return EINVAL; error = 0; switch (optname) { case SCTP_NODELAY: case SCTP_AUTOCLOSE: case SCTP_EXPLICIT_EOR: case SCTP_AUTO_ASCONF: case SCTP_DISABLE_FRAGMENTS: case SCTP_I_WANT_MAPPED_V4_ADDR: case SCTP_USE_EXT_RCVINFO: SCTP_INP_RLOCK(inp); switch (optname) { case SCTP_DISABLE_FRAGMENTS: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT); break; case SCTP_I_WANT_MAPPED_V4_ADDR: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NEEDS_MAPPED_V4); break; case SCTP_AUTO_ASCONF: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF); break; case SCTP_EXPLICIT_EOR: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR); break; case SCTP_NODELAY: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NODELAY); break; case SCTP_USE_EXT_RCVINFO: val = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO); break; case SCTP_AUTOCLOSE: if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTOCLOSE)) val = TICKS_TO_SEC(inp->sctp_ep.auto_close_time); else val = 0; break; default: error = ENOPROTOOPT; } /* end switch (sopt->sopt_name) */ if (optname != SCTP_AUTOCLOSE) { /* make it an "on/off" value */ val = (val != 0); } if (*optsize < sizeof(val)) { error = EINVAL; } SCTP_INP_RUNLOCK(inp); if (error == 0) { /* return the option value */ *(int *)optval = val; *optsize = sizeof(val); } break; case SCTP_PARTIAL_DELIVERY_POINT: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); *value = inp->partial_delivery_point; *optsize = sizeof(uint32_t); } break; case SCTP_FRAGMENT_INTERLEAVE: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) { if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS)) { *value = SCTP_FRAG_LEVEL_2; } else { *value = SCTP_FRAG_LEVEL_1; } } else { *value = SCTP_FRAG_LEVEL_0; } *optsize = sizeof(uint32_t); } break; case SCTP_CMT_ON_OFF: { struct sctp_assoc_value *av; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize); if (sctp_cmt_on_off) { SCTP_FIND_STCB(inp, stcb, av->assoc_id); if (stcb) { av->assoc_value = stcb->asoc.sctp_cmt_on_off; SCTP_TCB_UNLOCK(stcb); } else { error = ENOTCONN; } } else { error = ENOPROTOOPT; } *optsize = sizeof(*av); } break; case SCTP_GET_ADDR_LEN: { struct sctp_assoc_value *av; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize); error = EINVAL; #ifdef INET if (av->assoc_value == AF_INET) { av->assoc_value = sizeof(struct sockaddr_in); error = 0; } #endif #ifdef INET6 if (av->assoc_value == AF_INET6) { av->assoc_value = sizeof(struct sockaddr_in6); error = 0; } #endif *optsize = sizeof(*av); } break; case SCTP_GET_ASOC_ID_LIST: { struct sctp_assoc_ids *ids; int cnt, at; uint16_t orig; SCTP_CHECK_AND_CAST(ids, optval, struct sctp_assoc_ids, *optsize); cnt = 0; SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { none_out_now: ids->asls_numb_present = 0; ids->asls_more_to_get = 0; SCTP_INP_RUNLOCK(inp); break; } orig = ids->asls_assoc_start; stcb = LIST_FIRST(&inp->sctp_asoc_list); while (orig) { stcb = LIST_NEXT(stcb, sctp_tcblist); orig--; cnt--; if (stcb == NULL) goto none_out_now; } if (stcb == NULL) goto none_out_now; at = 0; ids->asls_numb_present = 0; ids->asls_more_to_get = 1; while (at < MAX_ASOC_IDS_RET) { ids->asls_assoc_id[at] = sctp_get_associd(stcb); at++; ids->asls_numb_present++; stcb = LIST_NEXT(stcb, sctp_tcblist); if (stcb == NULL) { ids->asls_more_to_get = 0; break; } } SCTP_INP_RUNLOCK(inp); } break; case SCTP_CONTEXT: { struct sctp_assoc_value *av; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize); SCTP_FIND_STCB(inp, stcb, av->assoc_id); if (stcb) { av->assoc_value = stcb->asoc.context; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); av->assoc_value = inp->sctp_context; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*av); } break; case SCTP_VRF_ID: { uint32_t *vrf_id; SCTP_CHECK_AND_CAST(vrf_id, optval, uint32_t, *optsize); *vrf_id = inp->def_vrf_id; break; } case SCTP_GET_ASOC_VRF: { struct sctp_assoc_value *id; SCTP_CHECK_AND_CAST(id, optval, struct sctp_assoc_value, *optsize); SCTP_FIND_STCB(inp, stcb, id->assoc_id); if (stcb == NULL) { error = EINVAL; break; } id->assoc_value = stcb->asoc.vrf_id; break; } case SCTP_GET_VRF_IDS: { error = EOPNOTSUPP; break; } case SCTP_GET_NONCE_VALUES: { struct sctp_get_nonce_values *gnv; SCTP_CHECK_AND_CAST(gnv, optval, struct sctp_get_nonce_values, *optsize); SCTP_FIND_STCB(inp, stcb, gnv->gn_assoc_id); if (stcb) { gnv->gn_peers_tag = stcb->asoc.peer_vtag; gnv->gn_local_tag = stcb->asoc.my_vtag; SCTP_TCB_UNLOCK(stcb); } else { error = ENOTCONN; } *optsize = sizeof(*gnv); } break; case SCTP_DELAYED_ACK_TIME: { struct sctp_assoc_value *tm; SCTP_CHECK_AND_CAST(tm, optval, struct sctp_assoc_value, *optsize); SCTP_FIND_STCB(inp, stcb, tm->assoc_id); if (stcb) { tm->assoc_value = stcb->asoc.delayed_ack; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); tm->assoc_value = TICKS_TO_MSEC(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]); SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*tm); } break; case SCTP_GET_SNDBUF_USE: { struct sctp_sockstat *ss; SCTP_CHECK_AND_CAST(ss, optval, struct sctp_sockstat, *optsize); SCTP_FIND_STCB(inp, stcb, ss->ss_assoc_id); if (stcb) { ss->ss_total_sndbuf = stcb->asoc.total_output_queue_size; ss->ss_total_recv_buf = (stcb->asoc.size_on_reasm_queue + stcb->asoc.size_on_all_streams); SCTP_TCB_UNLOCK(stcb); } else { error = ENOTCONN; } *optsize = sizeof(struct sctp_sockstat); } break; case SCTP_MAXBURST: { uint8_t *value; SCTP_CHECK_AND_CAST(value, optval, uint8_t, *optsize); SCTP_INP_RLOCK(inp); *value = inp->sctp_ep.max_burst; SCTP_INP_RUNLOCK(inp); *optsize = sizeof(uint8_t); } break; case SCTP_MAXSEG: { struct sctp_assoc_value *av; int ovh; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize); if (av->assoc_id) { SCTP_FIND_STCB(inp, stcb, av->assoc_id); } else { stcb = NULL; } if (stcb) { av->assoc_value = sctp_get_frag_point(stcb, &stcb->asoc); SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ovh = SCTP_MED_OVERHEAD; } else { ovh = SCTP_MED_V4_OVERHEAD; } av->assoc_value = inp->sctp_frag_point - ovh; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(struct sctp_assoc_value); } break; case SCTP_GET_STAT_LOG: #ifdef SCTP_STAT_LOGGING error = sctp_fill_stat_log(optval, optsize); #else error = EOPNOTSUPP; #endif break; case SCTP_EVENTS: { struct sctp_event_subscribe *events; SCTP_CHECK_AND_CAST(events, optval, struct sctp_event_subscribe, *optsize); memset(events, 0, sizeof(*events)); SCTP_INP_RLOCK(inp); if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) events->sctp_data_io_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT)) events->sctp_association_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVPADDREVNT)) events->sctp_address_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT)) events->sctp_send_failure_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVPEERERR)) events->sctp_peer_error_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT)) events->sctp_shutdown_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PDAPIEVNT)) events->sctp_partial_delivery_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT)) events->sctp_adaptation_layer_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_AUTHEVNT)) events->sctp_authentication_event = 1; if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT)) events->sctp_stream_reset_events = 1; SCTP_INP_RUNLOCK(inp); *optsize = sizeof(struct sctp_event_subscribe); } break; case SCTP_ADAPTATION_LAYER: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); SCTP_INP_RLOCK(inp); *value = inp->sctp_ep.adaptation_layer_indicator; SCTP_INP_RUNLOCK(inp); *optsize = sizeof(uint32_t); } break; case SCTP_SET_INITIAL_DBG_SEQ: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); SCTP_INP_RLOCK(inp); *value = inp->sctp_ep.initial_sequence_debug; SCTP_INP_RUNLOCK(inp); *optsize = sizeof(uint32_t); } break; case SCTP_GET_LOCAL_ADDR_SIZE: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); SCTP_INP_RLOCK(inp); *value = sctp_count_max_addresses(inp); SCTP_INP_RUNLOCK(inp); *optsize = sizeof(uint32_t); } break; case SCTP_GET_REMOTE_ADDR_SIZE: { uint32_t *value; size_t size; struct sctp_nets *net; SCTP_CHECK_AND_CAST(value, optval, uint32_t, *optsize); /* FIXME MT: change to sctp_assoc_value? */ SCTP_FIND_STCB(inp, stcb, (sctp_assoc_t) * value); if (stcb) { size = 0; /* Count the sizes */ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) || (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET6)) { size += sizeof(struct sockaddr_in6); } else if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { size += sizeof(struct sockaddr_in); } else { /* huh */ break; } } SCTP_TCB_UNLOCK(stcb); *value = (uint32_t) size; } else { error = ENOTCONN; } *optsize = sizeof(uint32_t); } break; case SCTP_GET_PEER_ADDRESSES: /* * Get the address information, an array is passed in to * fill up we pack it. */ { size_t cpsz, left; struct sockaddr_storage *sas; struct sctp_nets *net; struct sctp_getaddresses *saddr; SCTP_CHECK_AND_CAST(saddr, optval, struct sctp_getaddresses, *optsize); SCTP_FIND_STCB(inp, stcb, saddr->sget_assoc_id); if (stcb) { left = (*optsize) - sizeof(struct sctp_getaddresses); *optsize = sizeof(struct sctp_getaddresses); sas = (struct sockaddr_storage *)&saddr->addr[0]; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) || (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET6)) { cpsz = sizeof(struct sockaddr_in6); } else if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { cpsz = sizeof(struct sockaddr_in); } else { /* huh */ break; } if (left < cpsz) { /* not enough room. */ break; } if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET)) { /* Must map the address */ in6_sin_2_v4mapsin6((struct sockaddr_in *)&net->ro._l_addr, (struct sockaddr_in6 *)sas); } else { memcpy(sas, &net->ro._l_addr, cpsz); } ((struct sockaddr_in *)sas)->sin_port = stcb->rport; sas = (struct sockaddr_storage *)((caddr_t)sas + cpsz); left -= cpsz; *optsize += cpsz; } SCTP_TCB_UNLOCK(stcb); } else { error = ENOENT; } } break; case SCTP_GET_LOCAL_ADDRESSES: { size_t limit, actual; struct sockaddr_storage *sas; struct sctp_getaddresses *saddr; SCTP_CHECK_AND_CAST(saddr, optval, struct sctp_getaddresses, *optsize); SCTP_FIND_STCB(inp, stcb, saddr->sget_assoc_id); sas = (struct sockaddr_storage *)&saddr->addr[0]; limit = *optsize - sizeof(sctp_assoc_t); actual = sctp_fill_up_addresses(inp, stcb, limit, sas); if (stcb) SCTP_TCB_UNLOCK(stcb); *optsize = sizeof(struct sockaddr_storage) + actual; } break; case SCTP_PEER_ADDR_PARAMS: { struct sctp_paddrparams *paddrp; struct sctp_nets *net; SCTP_CHECK_AND_CAST(paddrp, optval, struct sctp_paddrparams, *optsize); SCTP_FIND_STCB(inp, stcb, paddrp->spp_assoc_id); net = NULL; if (stcb) { net = sctp_findnet(stcb, (struct sockaddr *)&paddrp->spp_address); } else { /* * We increment here since * sctp_findassociation_ep_addr() wil do a * decrement if it finds the stcb as long as * the locked tcb (last argument) is NOT a * TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, (struct sockaddr *)&paddrp->spp_address, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } } if (stcb) { /* Applys to the specific association */ paddrp->spp_flags = 0; if (net) { paddrp->spp_pathmaxrxt = net->failure_threshold; paddrp->spp_pathmtu = net->mtu; /* get flags for HB */ if (net->dest_state & SCTP_ADDR_NOHB) paddrp->spp_flags |= SPP_HB_DISABLE; else paddrp->spp_flags |= SPP_HB_ENABLE; /* get flags for PMTU */ if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) { paddrp->spp_flags |= SPP_PMTUD_ENABLE; } else { paddrp->spp_flags |= SPP_PMTUD_DISABLE; } #ifdef INET if (net->ro._l_addr.sin.sin_family == AF_INET) { paddrp->spp_ipv4_tos = net->tos_flowlabel & 0x000000fc; paddrp->spp_flags |= SPP_IPV4_TOS; } #endif #ifdef INET6 if (net->ro._l_addr.sin6.sin6_family == AF_INET6) { paddrp->spp_ipv6_flowlabel = net->tos_flowlabel; paddrp->spp_flags |= SPP_IPV6_FLOWLABEL; } #endif } else { /* * No destination so return default * value */ paddrp->spp_pathmaxrxt = stcb->asoc.def_net_failure; paddrp->spp_pathmtu = sctp_get_frag_point(stcb, &stcb->asoc); #ifdef INET paddrp->spp_ipv4_tos = stcb->asoc.default_tos & 0x000000fc; paddrp->spp_flags |= SPP_IPV4_TOS; #endif #ifdef INET6 paddrp->spp_ipv6_flowlabel = stcb->asoc.default_flowlabel; paddrp->spp_flags |= SPP_IPV6_FLOWLABEL; #endif /* default settings should be these */ if (sctp_is_hb_timer_running(stcb)) { paddrp->spp_flags |= SPP_HB_ENABLE; } } paddrp->spp_hbinterval = stcb->asoc.heart_beat_delay; paddrp->spp_assoc_id = sctp_get_associd(stcb); SCTP_TCB_UNLOCK(stcb); } else { /* Use endpoint defaults */ SCTP_INP_RLOCK(inp); paddrp->spp_pathmaxrxt = inp->sctp_ep.def_net_failure; paddrp->spp_hbinterval = TICKS_TO_MSEC(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]); paddrp->spp_assoc_id = (sctp_assoc_t) 0; /* get inp's default */ #ifdef INET paddrp->spp_ipv4_tos = inp->ip_inp.inp.inp_ip_tos; paddrp->spp_flags |= SPP_IPV4_TOS; #endif #ifdef INET6 if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { paddrp->spp_ipv6_flowlabel = ((struct in6pcb *)inp)->in6p_flowinfo; paddrp->spp_flags |= SPP_IPV6_FLOWLABEL; } #endif /* can't return this */ paddrp->spp_pathmaxrxt = 0; paddrp->spp_pathmtu = 0; /* default behavior, no stcb */ paddrp->spp_flags = SPP_HB_ENABLE | SPP_PMTUD_ENABLE; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(struct sctp_paddrparams); } break; case SCTP_GET_PEER_ADDR_INFO: { struct sctp_paddrinfo *paddri; struct sctp_nets *net; SCTP_CHECK_AND_CAST(paddri, optval, struct sctp_paddrinfo, *optsize); SCTP_FIND_STCB(inp, stcb, paddri->spinfo_assoc_id); net = NULL; if (stcb) { net = sctp_findnet(stcb, (struct sockaddr *)&paddri->spinfo_address); } else { /* * We increment here since * sctp_findassociation_ep_addr() wil do a * decrement if it finds the stcb as long as * the locked tcb (last argument) is NOT a * TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, (struct sockaddr *)&paddri->spinfo_address, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } } if ((stcb) && (net)) { paddri->spinfo_state = net->dest_state & (SCTP_REACHABLE_MASK | SCTP_ADDR_NOHB); paddri->spinfo_cwnd = net->cwnd; paddri->spinfo_srtt = ((net->lastsa >> 2) + net->lastsv) >> 1; paddri->spinfo_rto = net->RTO; paddri->spinfo_assoc_id = sctp_get_associd(stcb); SCTP_TCB_UNLOCK(stcb); } else { if (stcb) { SCTP_TCB_UNLOCK(stcb); } error = ENOENT; } *optsize = sizeof(struct sctp_paddrinfo); } break; case SCTP_PCB_STATUS: { struct sctp_pcbinfo *spcb; SCTP_CHECK_AND_CAST(spcb, optval, struct sctp_pcbinfo, *optsize); sctp_fill_pcbinfo(spcb); *optsize = sizeof(struct sctp_pcbinfo); } break; case SCTP_STATUS: { struct sctp_nets *net; struct sctp_status *sstat; SCTP_CHECK_AND_CAST(sstat, optval, struct sctp_status, *optsize); SCTP_FIND_STCB(inp, stcb, sstat->sstat_assoc_id); if (stcb == NULL) { error = EINVAL; break; } /* * I think passing the state is fine since * sctp_constants.h will be available to the user * land. */ sstat->sstat_state = stcb->asoc.state; sstat->sstat_rwnd = stcb->asoc.peers_rwnd; sstat->sstat_unackdata = stcb->asoc.sent_queue_cnt; /* * We can't include chunks that have been passed to * the socket layer. Only things in queue. */ sstat->sstat_penddata = (stcb->asoc.cnt_on_reasm_queue + stcb->asoc.cnt_on_all_streams); sstat->sstat_instrms = stcb->asoc.streamincnt; sstat->sstat_outstrms = stcb->asoc.streamoutcnt; sstat->sstat_fragmentation_point = sctp_get_frag_point(stcb, &stcb->asoc); memcpy(&sstat->sstat_primary.spinfo_address, &stcb->asoc.primary_destination->ro._l_addr, ((struct sockaddr *)(&stcb->asoc.primary_destination->ro._l_addr))->sa_len); net = stcb->asoc.primary_destination; ((struct sockaddr_in *)&sstat->sstat_primary.spinfo_address)->sin_port = stcb->rport; /* * Again the user can get info from sctp_constants.h * for what the state of the network is. */ sstat->sstat_primary.spinfo_state = net->dest_state & SCTP_REACHABLE_MASK; sstat->sstat_primary.spinfo_cwnd = net->cwnd; sstat->sstat_primary.spinfo_srtt = net->lastsa; sstat->sstat_primary.spinfo_rto = net->RTO; sstat->sstat_primary.spinfo_mtu = net->mtu; sstat->sstat_primary.spinfo_assoc_id = sctp_get_associd(stcb); SCTP_TCB_UNLOCK(stcb); *optsize = sizeof(*sstat); } break; case SCTP_RTOINFO: { struct sctp_rtoinfo *srto; SCTP_CHECK_AND_CAST(srto, optval, struct sctp_rtoinfo, *optsize); SCTP_FIND_STCB(inp, stcb, srto->srto_assoc_id); if (stcb) { srto->srto_initial = stcb->asoc.initial_rto; srto->srto_max = stcb->asoc.maxrto; srto->srto_min = stcb->asoc.minrto; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); srto->srto_initial = inp->sctp_ep.initial_rto; srto->srto_max = inp->sctp_ep.sctp_maxrto; srto->srto_min = inp->sctp_ep.sctp_minrto; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*srto); } break; case SCTP_ASSOCINFO: { struct sctp_assocparams *sasoc; SCTP_CHECK_AND_CAST(sasoc, optval, struct sctp_assocparams, *optsize); SCTP_FIND_STCB(inp, stcb, sasoc->sasoc_assoc_id); if (stcb) { sasoc->sasoc_asocmaxrxt = stcb->asoc.max_send_times; sasoc->sasoc_number_peer_destinations = stcb->asoc.numnets; sasoc->sasoc_peer_rwnd = stcb->asoc.peers_rwnd; sasoc->sasoc_local_rwnd = stcb->asoc.my_rwnd; sasoc->sasoc_cookie_life = stcb->asoc.cookie_life; sasoc->sasoc_sack_delay = stcb->asoc.delayed_ack; sasoc->sasoc_sack_freq = stcb->asoc.sack_freq; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); sasoc->sasoc_asocmaxrxt = inp->sctp_ep.max_send_times; sasoc->sasoc_number_peer_destinations = 0; sasoc->sasoc_peer_rwnd = 0; sasoc->sasoc_local_rwnd = sbspace(&inp->sctp_socket->so_rcv); sasoc->sasoc_cookie_life = inp->sctp_ep.def_cookie_life; sasoc->sasoc_sack_delay = TICKS_TO_MSEC(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]); sasoc->sasoc_sack_freq = inp->sctp_ep.sctp_sack_freq; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*sasoc); } break; case SCTP_DEFAULT_SEND_PARAM: { struct sctp_sndrcvinfo *s_info; SCTP_CHECK_AND_CAST(s_info, optval, struct sctp_sndrcvinfo, *optsize); SCTP_FIND_STCB(inp, stcb, s_info->sinfo_assoc_id); if (stcb) { *s_info = stcb->asoc.def_send; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_RLOCK(inp); *s_info = inp->def_send; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*s_info); } break; case SCTP_INITMSG: { struct sctp_initmsg *sinit; SCTP_CHECK_AND_CAST(sinit, optval, struct sctp_initmsg, *optsize); SCTP_INP_RLOCK(inp); sinit->sinit_num_ostreams = inp->sctp_ep.pre_open_stream_count; sinit->sinit_max_instreams = inp->sctp_ep.max_open_streams_intome; sinit->sinit_max_attempts = inp->sctp_ep.max_init_times; sinit->sinit_max_init_timeo = inp->sctp_ep.initial_init_rto_max; SCTP_INP_RUNLOCK(inp); *optsize = sizeof(*sinit); } break; case SCTP_PRIMARY_ADDR: /* we allow a "get" operation on this */ { struct sctp_setprim *ssp; SCTP_CHECK_AND_CAST(ssp, optval, struct sctp_setprim, *optsize); SCTP_FIND_STCB(inp, stcb, ssp->ssp_assoc_id); if (stcb) { /* simply copy out the sockaddr_storage... */ memcpy(&ssp->ssp_addr, &stcb->asoc.primary_destination->ro._l_addr, ((struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr)->sa_len); SCTP_TCB_UNLOCK(stcb); } else { error = EINVAL; } *optsize = sizeof(*ssp); } break; case SCTP_HMAC_IDENT: { struct sctp_hmacalgo *shmac; sctp_hmaclist_t *hmaclist; uint32_t size; int i; SCTP_CHECK_AND_CAST(shmac, optval, struct sctp_hmacalgo, *optsize); SCTP_INP_RLOCK(inp); hmaclist = inp->sctp_ep.local_hmacs; if (hmaclist == NULL) { /* no HMACs to return */ *optsize = sizeof(*shmac); SCTP_INP_RUNLOCK(inp); break; } /* is there room for all of the hmac ids? */ size = sizeof(*shmac) + (hmaclist->num_algo * sizeof(shmac->shmac_idents[0])); if ((size_t)(*optsize) < size) { error = EINVAL; SCTP_INP_RUNLOCK(inp); break; } /* copy in the list */ for (i = 0; i < hmaclist->num_algo; i++) shmac->shmac_idents[i] = hmaclist->hmac[i]; SCTP_INP_RUNLOCK(inp); *optsize = size; break; } case SCTP_AUTH_ACTIVE_KEY: { struct sctp_authkeyid *scact; SCTP_CHECK_AND_CAST(scact, optval, struct sctp_authkeyid, *optsize); SCTP_FIND_STCB(inp, stcb, scact->scact_assoc_id); if (stcb) { /* get the active key on the assoc */ scact->scact_keynumber = stcb->asoc.authinfo.assoc_keyid; SCTP_TCB_UNLOCK(stcb); } else { /* get the endpoint active key */ SCTP_INP_RLOCK(inp); scact->scact_keynumber = inp->sctp_ep.default_keyid; SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(*scact); break; } case SCTP_LOCAL_AUTH_CHUNKS: { struct sctp_authchunks *sac; sctp_auth_chklist_t *chklist = NULL; size_t size = 0; SCTP_CHECK_AND_CAST(sac, optval, struct sctp_authchunks, *optsize); SCTP_FIND_STCB(inp, stcb, sac->gauth_assoc_id); if (stcb) { /* get off the assoc */ chklist = stcb->asoc.local_auth_chunks; /* is there enough space? */ size = sctp_auth_get_chklist_size(chklist); if (*optsize < (sizeof(struct sctp_authchunks) + size)) { error = EINVAL; } else { /* copy in the chunks */ sctp_serialize_auth_chunks(chklist, sac->gauth_chunks); } SCTP_TCB_UNLOCK(stcb); } else { /* get off the endpoint */ SCTP_INP_RLOCK(inp); chklist = inp->sctp_ep.local_auth_chunks; /* is there enough space? */ size = sctp_auth_get_chklist_size(chklist); if (*optsize < (sizeof(struct sctp_authchunks) + size)) { error = EINVAL; } else { /* copy in the chunks */ sctp_serialize_auth_chunks(chklist, sac->gauth_chunks); } SCTP_INP_RUNLOCK(inp); } *optsize = sizeof(struct sctp_authchunks) + size; break; } case SCTP_PEER_AUTH_CHUNKS: { struct sctp_authchunks *sac; sctp_auth_chklist_t *chklist = NULL; size_t size = 0; SCTP_CHECK_AND_CAST(sac, optval, struct sctp_authchunks, *optsize); SCTP_FIND_STCB(inp, stcb, sac->gauth_assoc_id); if (stcb) { /* get off the assoc */ chklist = stcb->asoc.peer_auth_chunks; /* is there enough space? */ size = sctp_auth_get_chklist_size(chklist); if (*optsize < (sizeof(struct sctp_authchunks) + size)) { error = EINVAL; } else { /* copy in the chunks */ sctp_serialize_auth_chunks(chklist, sac->gauth_chunks); } SCTP_TCB_UNLOCK(stcb); } else { error = ENOENT; } *optsize = sizeof(struct sctp_authchunks) + size; break; } default: error = ENOPROTOOPT; *optsize = 0; break; } /* end switch (sopt->sopt_name) */ return (error); } static int sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize, void *p) { int error, set_opt; uint32_t *mopt; struct sctp_tcb *stcb = NULL; struct sctp_inpcb *inp; uint32_t vrf_id; if (optval == NULL) { printf("optval is NULL\n"); return (EINVAL); } inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { printf("inp is NULL?\n"); return EINVAL; } vrf_id = inp->def_vrf_id; error = 0; switch (optname) { case SCTP_NODELAY: case SCTP_AUTOCLOSE: case SCTP_AUTO_ASCONF: case SCTP_EXPLICIT_EOR: case SCTP_DISABLE_FRAGMENTS: case SCTP_USE_EXT_RCVINFO: case SCTP_I_WANT_MAPPED_V4_ADDR: /* copy in the option value */ SCTP_CHECK_AND_CAST(mopt, optval, uint32_t, optsize); set_opt = 0; if (error) break; switch (optname) { case SCTP_DISABLE_FRAGMENTS: set_opt = SCTP_PCB_FLAGS_NO_FRAGMENT; break; case SCTP_AUTO_ASCONF: set_opt = SCTP_PCB_FLAGS_AUTO_ASCONF; break; case SCTP_EXPLICIT_EOR: set_opt = SCTP_PCB_FLAGS_EXPLICIT_EOR; break; case SCTP_USE_EXT_RCVINFO: set_opt = SCTP_PCB_FLAGS_EXT_RCVINFO; break; case SCTP_I_WANT_MAPPED_V4_ADDR: if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { set_opt = SCTP_PCB_FLAGS_NEEDS_MAPPED_V4; } else { return (EINVAL); } break; case SCTP_NODELAY: set_opt = SCTP_PCB_FLAGS_NODELAY; break; case SCTP_AUTOCLOSE: set_opt = SCTP_PCB_FLAGS_AUTOCLOSE; /* * The value is in ticks. Note this does not effect * old associations, only new ones. */ inp->sctp_ep.auto_close_time = SEC_TO_TICKS(*mopt); break; } SCTP_INP_WLOCK(inp); if (*mopt != 0) { sctp_feature_on(inp, set_opt); } else { sctp_feature_off(inp, set_opt); } SCTP_INP_WUNLOCK(inp); break; case SCTP_PARTIAL_DELIVERY_POINT: { uint32_t *value; SCTP_CHECK_AND_CAST(value, optval, uint32_t, optsize); if (*value > SCTP_SB_LIMIT_RCV(so)) { error = EINVAL; break; } inp->partial_delivery_point = *value; } break; case SCTP_FRAGMENT_INTERLEAVE: /* not yet until we re-write sctp_recvmsg() */ { uint32_t *level; SCTP_CHECK_AND_CAST(level, optval, uint32_t, optsize); if (*level == SCTP_FRAG_LEVEL_2) { sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE); sctp_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS); } else if (*level == SCTP_FRAG_LEVEL_1) { sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE); sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS); } else if (*level == SCTP_FRAG_LEVEL_0) { sctp_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE); sctp_feature_off(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS); } else { error = EINVAL; } } break; case SCTP_CMT_ON_OFF: { struct sctp_assoc_value *av; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize); if (sctp_cmt_on_off) { SCTP_FIND_STCB(inp, stcb, av->assoc_id); if (stcb) { stcb->asoc.sctp_cmt_on_off = (uint8_t) av->assoc_value; SCTP_TCB_UNLOCK(stcb); } else { error = ENOTCONN; } } else { error = ENOPROTOOPT; } } break; case SCTP_CLR_STAT_LOG: #ifdef SCTP_STAT_LOGGING sctp_clr_stat_log(); #else error = EOPNOTSUPP; #endif break; case SCTP_CONTEXT: { struct sctp_assoc_value *av; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize); SCTP_FIND_STCB(inp, stcb, av->assoc_id); if (stcb) { stcb->asoc.context = av->assoc_value; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); inp->sctp_context = av->assoc_value; SCTP_INP_WUNLOCK(inp); } } break; case SCTP_VRF_ID: { uint32_t *vrf_id; SCTP_CHECK_AND_CAST(vrf_id, optval, uint32_t, optsize); if (*vrf_id > SCTP_MAX_VRF_ID) { error = EINVAL; break; } inp->def_vrf_id = *vrf_id; break; } case SCTP_DEL_VRF_ID: { error = EOPNOTSUPP; break; } case SCTP_ADD_VRF_ID: { error = EOPNOTSUPP; break; } case SCTP_DELAYED_ACK_TIME: { struct sctp_assoc_value *tm; SCTP_CHECK_AND_CAST(tm, optval, struct sctp_assoc_value, optsize); SCTP_FIND_STCB(inp, stcb, tm->assoc_id); if (stcb) { stcb->asoc.delayed_ack = tm->assoc_value; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(tm->assoc_value); SCTP_INP_WUNLOCK(inp); } break; } case SCTP_AUTH_CHUNK: { struct sctp_authchunk *sauth; SCTP_CHECK_AND_CAST(sauth, optval, struct sctp_authchunk, optsize); SCTP_INP_WLOCK(inp); if (sctp_auth_add_chunk(sauth->sauth_chunk, inp->sctp_ep.local_auth_chunks)) error = EINVAL; SCTP_INP_WUNLOCK(inp); break; } case SCTP_AUTH_KEY: { struct sctp_authkey *sca; struct sctp_keyhead *shared_keys; sctp_sharedkey_t *shared_key; sctp_key_t *key = NULL; size_t size; SCTP_CHECK_AND_CAST(sca, optval, struct sctp_authkey, optsize); SCTP_FIND_STCB(inp, stcb, sca->sca_assoc_id) size = optsize - sizeof(*sca); if (stcb) { /* set it on the assoc */ shared_keys = &stcb->asoc.shared_keys; /* clear the cached keys for this key id */ sctp_clear_cachedkeys(stcb, sca->sca_keynumber); /* * create the new shared key and * insert/replace it */ if (size > 0) { key = sctp_set_key(sca->sca_key, (uint32_t) size); if (key == NULL) { error = ENOMEM; SCTP_TCB_UNLOCK(stcb); break; } } shared_key = sctp_alloc_sharedkey(); if (shared_key == NULL) { sctp_free_key(key); error = ENOMEM; SCTP_TCB_UNLOCK(stcb); break; } shared_key->key = key; shared_key->keyid = sca->sca_keynumber; sctp_insert_sharedkey(shared_keys, shared_key); SCTP_TCB_UNLOCK(stcb); } else { /* set it on the endpoint */ SCTP_INP_WLOCK(inp); shared_keys = &inp->sctp_ep.shared_keys; /* * clear the cached keys on all assocs for * this key id */ sctp_clear_cachedkeys_ep(inp, sca->sca_keynumber); /* * create the new shared key and * insert/replace it */ if (size > 0) { key = sctp_set_key(sca->sca_key, (uint32_t) size); if (key == NULL) { error = ENOMEM; SCTP_INP_WUNLOCK(inp); break; } } shared_key = sctp_alloc_sharedkey(); if (shared_key == NULL) { sctp_free_key(key); error = ENOMEM; SCTP_INP_WUNLOCK(inp); break; } shared_key->key = key; shared_key->keyid = sca->sca_keynumber; sctp_insert_sharedkey(shared_keys, shared_key); SCTP_INP_WUNLOCK(inp); } break; } case SCTP_HMAC_IDENT: { struct sctp_hmacalgo *shmac; sctp_hmaclist_t *hmaclist; uint32_t hmacid; size_t size, i; SCTP_CHECK_AND_CAST(shmac, optval, struct sctp_hmacalgo, optsize); size = (optsize - sizeof(*shmac)) / sizeof(shmac->shmac_idents[0]); hmaclist = sctp_alloc_hmaclist(size); if (hmaclist == NULL) { error = ENOMEM; break; } for (i = 0; i < size; i++) { hmacid = shmac->shmac_idents[i]; if (sctp_auth_add_hmacid(hmaclist, (uint16_t) hmacid)) { /* invalid HMACs were found */ ; error = EINVAL; sctp_free_hmaclist(hmaclist); goto sctp_set_hmac_done; } } /* set it on the endpoint */ SCTP_INP_WLOCK(inp); if (inp->sctp_ep.local_hmacs) sctp_free_hmaclist(inp->sctp_ep.local_hmacs); inp->sctp_ep.local_hmacs = hmaclist; SCTP_INP_WUNLOCK(inp); sctp_set_hmac_done: break; } case SCTP_AUTH_ACTIVE_KEY: { struct sctp_authkeyid *scact; SCTP_CHECK_AND_CAST(scact, optval, struct sctp_authkeyid, optsize); SCTP_FIND_STCB(inp, stcb, scact->scact_assoc_id); /* set the active key on the right place */ if (stcb) { /* set the active key on the assoc */ if (sctp_auth_setactivekey(stcb, scact->scact_keynumber)) error = EINVAL; SCTP_TCB_UNLOCK(stcb); } else { /* set the active key on the endpoint */ SCTP_INP_WLOCK(inp); if (sctp_auth_setactivekey_ep(inp, scact->scact_keynumber)) error = EINVAL; SCTP_INP_WUNLOCK(inp); } break; } case SCTP_AUTH_DELETE_KEY: { struct sctp_authkeyid *scdel; SCTP_CHECK_AND_CAST(scdel, optval, struct sctp_authkeyid, optsize); SCTP_FIND_STCB(inp, stcb, scdel->scact_assoc_id); /* delete the key from the right place */ if (stcb) { if (sctp_delete_sharedkey(stcb, scdel->scact_keynumber)) error = EINVAL; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); if (sctp_delete_sharedkey_ep(inp, scdel->scact_keynumber)) error = EINVAL; SCTP_INP_WUNLOCK(inp); } break; } case SCTP_RESET_STREAMS: { struct sctp_stream_reset *strrst; uint8_t send_in = 0, send_tsn = 0, send_out = 0; int i; SCTP_CHECK_AND_CAST(strrst, optval, struct sctp_stream_reset, optsize); SCTP_FIND_STCB(inp, stcb, strrst->strrst_assoc_id); if (stcb == NULL) { error = ENOENT; break; } if (stcb->asoc.peer_supports_strreset == 0) { /* * Peer does not support it, we return * protocol not supported since this is true * for this feature and this peer, not the * socket request in general. */ error = EPROTONOSUPPORT; SCTP_TCB_UNLOCK(stcb); break; } if (stcb->asoc.stream_reset_outstanding) { error = EALREADY; SCTP_TCB_UNLOCK(stcb); break; } if (strrst->strrst_flags == SCTP_RESET_LOCAL_RECV) { send_in = 1; } else if (strrst->strrst_flags == SCTP_RESET_LOCAL_SEND) { send_out = 1; } else if (strrst->strrst_flags == SCTP_RESET_BOTH) { send_in = 1; send_out = 1; } else if (strrst->strrst_flags == SCTP_RESET_TSN) { send_tsn = 1; } else { error = EINVAL; SCTP_TCB_UNLOCK(stcb); break; } for (i = 0; i < strrst->strrst_num_streams; i++) { if ((send_in) && (strrst->strrst_list[i] > stcb->asoc.streamincnt)) { error = EINVAL; goto get_out; } if ((send_out) && (strrst->strrst_list[i] > stcb->asoc.streamoutcnt)) { error = EINVAL; goto get_out; } } if (error) { get_out: SCTP_TCB_UNLOCK(stcb); break; } error = sctp_send_str_reset_req(stcb, strrst->strrst_num_streams, strrst->strrst_list, send_out, (stcb->asoc.str_reset_seq_in - 3), send_in, send_tsn); sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_REQ); SCTP_TCB_UNLOCK(stcb); } break; case SCTP_CONNECT_X: if (optsize < (sizeof(int) + sizeof(struct sockaddr_in))) { error = EINVAL; break; } error = sctp_do_connect_x(so, inp, optval, optsize, p, 0); break; case SCTP_CONNECT_X_DELAYED: if (optsize < (sizeof(int) + sizeof(struct sockaddr_in))) { error = EINVAL; break; } error = sctp_do_connect_x(so, inp, optval, optsize, p, 1); break; case SCTP_CONNECT_X_COMPLETE: { struct sockaddr *sa; struct sctp_nets *net; /* FIXME MT: check correct? */ SCTP_CHECK_AND_CAST(sa, optval, struct sockaddr, optsize); /* find tcb */ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb) { SCTP_TCB_LOCK(stcb); net = sctp_findnet(stcb, sa); } SCTP_INP_RUNLOCK(inp); } else { /* * We increment here since * sctp_findassociation_ep_addr() wil do a * decrement if it finds the stcb as long as * the locked tcb (last argument) is NOT a * TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, sa, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } } if (stcb == NULL) { error = ENOENT; break; } if (stcb->asoc.delayed_connection == 1) { stcb->asoc.delayed_connection = 0; - SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, stcb->asoc.primary_destination, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_9); sctp_send_initiate(inp, stcb); } else { /* * already expired or did not use delayed * connectx */ error = EALREADY; } SCTP_TCB_UNLOCK(stcb); } break; case SCTP_MAXBURST: { uint8_t *burst; SCTP_CHECK_AND_CAST(burst, optval, uint8_t, optsize); SCTP_INP_WLOCK(inp); if (*burst) { inp->sctp_ep.max_burst = *burst; } SCTP_INP_WUNLOCK(inp); } break; case SCTP_MAXSEG: { struct sctp_assoc_value *av; int ovh; SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize); SCTP_FIND_STCB(inp, stcb, av->assoc_id); if (stcb) { error = EINVAL; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ovh = SCTP_MED_OVERHEAD; } else { ovh = SCTP_MED_V4_OVERHEAD; } /* * FIXME MT: I think this is not in tune * with the API ID */ if (av->assoc_value) { inp->sctp_frag_point = (av->assoc_value + ovh); } else { error = EINVAL; } SCTP_INP_WUNLOCK(inp); } } break; case SCTP_EVENTS: { struct sctp_event_subscribe *events; SCTP_CHECK_AND_CAST(events, optval, struct sctp_event_subscribe, optsize); SCTP_INP_WLOCK(inp); if (events->sctp_data_io_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT); } if (events->sctp_association_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVASSOCEVNT); } if (events->sctp_address_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVPADDREVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVPADDREVNT); } if (events->sctp_send_failure_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVSENDFAILEVNT); } if (events->sctp_peer_error_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVPEERERR); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVPEERERR); } if (events->sctp_shutdown_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT); } if (events->sctp_partial_delivery_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_PDAPIEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_PDAPIEVNT); } if (events->sctp_adaptation_layer_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_ADAPTATIONEVNT); } if (events->sctp_authentication_event) { sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTHEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTHEVNT); } if (events->sctp_stream_reset_events) { sctp_feature_on(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT); } else { sctp_feature_off(inp, SCTP_PCB_FLAGS_STREAM_RESETEVNT); } SCTP_INP_WUNLOCK(inp); } break; case SCTP_ADAPTATION_LAYER: { struct sctp_setadaptation *adap_bits; SCTP_CHECK_AND_CAST(adap_bits, optval, struct sctp_setadaptation, optsize); SCTP_INP_WLOCK(inp); inp->sctp_ep.adaptation_layer_indicator = adap_bits->ssb_adaptation_ind; SCTP_INP_WUNLOCK(inp); } break; #ifdef SCTP_DEBUG case SCTP_SET_INITIAL_DBG_SEQ: { uint32_t *vvv; SCTP_CHECK_AND_CAST(vvv, optval, uint32_t, optsize); SCTP_INP_WLOCK(inp); inp->sctp_ep.initial_sequence_debug = *vvv; SCTP_INP_WUNLOCK(inp); } break; #endif case SCTP_DEFAULT_SEND_PARAM: { struct sctp_sndrcvinfo *s_info; SCTP_CHECK_AND_CAST(s_info, optval, struct sctp_sndrcvinfo, optsize); SCTP_FIND_STCB(inp, stcb, s_info->sinfo_assoc_id); if (stcb) { if (s_info->sinfo_stream <= stcb->asoc.streamoutcnt) { stcb->asoc.def_send = *s_info; } else { error = EINVAL; } SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); inp->def_send = *s_info; SCTP_INP_WUNLOCK(inp); } } break; case SCTP_PEER_ADDR_PARAMS: /* Applys to the specific association */ { struct sctp_paddrparams *paddrp; struct sctp_nets *net; SCTP_CHECK_AND_CAST(paddrp, optval, struct sctp_paddrparams, optsize); SCTP_FIND_STCB(inp, stcb, paddrp->spp_assoc_id); net = NULL; if (stcb) { net = sctp_findnet(stcb, (struct sockaddr *)&paddrp->spp_address); } else { /* * We increment here since * sctp_findassociation_ep_addr() wil do a * decrement if it finds the stcb as long as * the locked tcb (last argument) is NOT a * TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, (struct sockaddr *)&paddrp->spp_address, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } } if (stcb) { /************************TCB SPECIFIC SET ******************/ /* * do we change the timer for HB, we run * only one? */ if (paddrp->spp_hbinterval) stcb->asoc.heart_beat_delay = paddrp->spp_hbinterval; else if (paddrp->spp_flags & SPP_HB_TIME_IS_ZERO) stcb->asoc.heart_beat_delay = 0; /* network sets ? */ if (net) { /************************NET SPECIFIC SET ******************/ if (paddrp->spp_flags & SPP_HB_DEMAND) { /* on demand HB */ - sctp_send_hb(stcb, 1, net); + (void)sctp_send_hb(stcb, 1, net); } if (paddrp->spp_flags & SPP_HB_DISABLE) { net->dest_state |= SCTP_ADDR_NOHB; } if (paddrp->spp_flags & SPP_HB_ENABLE) { net->dest_state &= ~SCTP_ADDR_NOHB; } if (paddrp->spp_flags & SPP_PMTUD_DISABLE) { if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) { sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_10); } if (paddrp->spp_pathmtu > SCTP_DEFAULT_MINSEGMENT) { net->mtu = paddrp->spp_pathmtu; if (net->mtu < stcb->asoc.smallest_mtu) { #ifdef SCTP_PRINT_FOR_B_AND_M printf("SCTP_PMTU_DISABLE calls sctp_pathmtu_adjustment:%d\n", net->mtu); #endif sctp_pathmtu_adjustment(inp, stcb, net, net->mtu); } } } if (paddrp->spp_flags & SPP_PMTUD_ENABLE) { if (SCTP_OS_TIMER_PENDING(&net->pmtu_timer.timer)) { sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net); } } if (paddrp->spp_pathmaxrxt) net->failure_threshold = paddrp->spp_pathmaxrxt; #ifdef INET if (paddrp->spp_flags & SPP_IPV4_TOS) { if (net->ro._l_addr.sin.sin_family == AF_INET) { net->tos_flowlabel = paddrp->spp_ipv4_tos & 0x000000fc; } } #endif #ifdef INET6 if (paddrp->spp_flags & SPP_IPV6_FLOWLABEL) { if (net->ro._l_addr.sin6.sin6_family == AF_INET6) { net->tos_flowlabel = paddrp->spp_ipv6_flowlabel; } } #endif } else { /************************ASSOC ONLY -- NO NET SPECIFIC SET ******************/ if (paddrp->spp_pathmaxrxt) stcb->asoc.def_net_failure = paddrp->spp_pathmaxrxt; if (paddrp->spp_flags & SPP_HB_ENABLE) { /* Turn back on the timer */ stcb->asoc.hb_is_disabled = 0; sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); } if (paddrp->spp_flags & SPP_HB_DISABLE) { int cnt_of_unconf = 0; struct sctp_nets *lnet; stcb->asoc.hb_is_disabled = 1; TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { if (lnet->dest_state & SCTP_ADDR_UNCONFIRMED) { cnt_of_unconf++; } } /* * stop the timer ONLY if we * have no unconfirmed * addresses */ if (cnt_of_unconf == 0) { sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_11); } } if (paddrp->spp_flags & SPP_HB_ENABLE) { /* start up the timer. */ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); } #ifdef INET if (paddrp->spp_flags & SPP_IPV4_TOS) stcb->asoc.default_tos = paddrp->spp_ipv4_tos & 0x000000fc; #endif #ifdef INET6 if (paddrp->spp_flags & SPP_IPV6_FLOWLABEL) stcb->asoc.default_flowlabel = paddrp->spp_ipv6_flowlabel; #endif } SCTP_TCB_UNLOCK(stcb); } else { /************************NO TCB, SET TO default stuff ******************/ SCTP_INP_WLOCK(inp); /* * For the TOS/FLOWLABEL stuff you set it * with the options on the socket */ if (paddrp->spp_pathmaxrxt) { inp->sctp_ep.def_net_failure = paddrp->spp_pathmaxrxt; } if (paddrp->spp_flags & SPP_HB_ENABLE) { inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = MSEC_TO_TICKS(paddrp->spp_hbinterval); sctp_feature_off(inp, SCTP_PCB_FLAGS_DONOT_HEARTBEAT); } else if (paddrp->spp_flags & SPP_HB_DISABLE) { sctp_feature_on(inp, SCTP_PCB_FLAGS_DONOT_HEARTBEAT); } SCTP_INP_WUNLOCK(inp); } } break; case SCTP_RTOINFO: { struct sctp_rtoinfo *srto; SCTP_CHECK_AND_CAST(srto, optval, struct sctp_rtoinfo, optsize); SCTP_FIND_STCB(inp, stcb, srto->srto_assoc_id); if (stcb) { /* Set in ms we hope :-) */ if (srto->srto_initial) stcb->asoc.initial_rto = srto->srto_initial; if (srto->srto_max) stcb->asoc.maxrto = srto->srto_max; if (srto->srto_min) stcb->asoc.minrto = srto->srto_min; SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); /* * If we have a null asoc, its default for * the endpoint */ if (srto->srto_initial) inp->sctp_ep.initial_rto = srto->srto_initial; if (srto->srto_max) inp->sctp_ep.sctp_maxrto = srto->srto_max; if (srto->srto_min) inp->sctp_ep.sctp_minrto = srto->srto_min; SCTP_INP_WUNLOCK(inp); } } break; case SCTP_ASSOCINFO: { struct sctp_assocparams *sasoc; SCTP_CHECK_AND_CAST(sasoc, optval, struct sctp_assocparams, optsize); SCTP_FIND_STCB(inp, stcb, sasoc->sasoc_assoc_id); if (stcb) { if (sasoc->sasoc_asocmaxrxt) stcb->asoc.max_send_times = sasoc->sasoc_asocmaxrxt; sasoc->sasoc_number_peer_destinations = stcb->asoc.numnets; sasoc->sasoc_peer_rwnd = 0; sasoc->sasoc_local_rwnd = 0; if (stcb->asoc.cookie_life) stcb->asoc.cookie_life = sasoc->sasoc_cookie_life; stcb->asoc.delayed_ack = sasoc->sasoc_sack_delay; if (sasoc->sasoc_sack_freq) { stcb->asoc.sack_freq = sasoc->sasoc_sack_freq; } SCTP_TCB_UNLOCK(stcb); } else { SCTP_INP_WLOCK(inp); if (sasoc->sasoc_asocmaxrxt) inp->sctp_ep.max_send_times = sasoc->sasoc_asocmaxrxt; sasoc->sasoc_number_peer_destinations = 0; sasoc->sasoc_peer_rwnd = 0; sasoc->sasoc_local_rwnd = 0; if (sasoc->sasoc_cookie_life) inp->sctp_ep.def_cookie_life = sasoc->sasoc_cookie_life; inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(sasoc->sasoc_sack_delay); if (sasoc->sasoc_sack_freq) { inp->sctp_ep.sctp_sack_freq = sasoc->sasoc_sack_freq; } SCTP_INP_WUNLOCK(inp); } } break; case SCTP_INITMSG: { struct sctp_initmsg *sinit; SCTP_CHECK_AND_CAST(sinit, optval, struct sctp_initmsg, optsize); SCTP_INP_WLOCK(inp); if (sinit->sinit_num_ostreams) inp->sctp_ep.pre_open_stream_count = sinit->sinit_num_ostreams; if (sinit->sinit_max_instreams) inp->sctp_ep.max_open_streams_intome = sinit->sinit_max_instreams; if (sinit->sinit_max_attempts) inp->sctp_ep.max_init_times = sinit->sinit_max_attempts; if (sinit->sinit_max_init_timeo) inp->sctp_ep.initial_init_rto_max = sinit->sinit_max_init_timeo; SCTP_INP_WUNLOCK(inp); } break; case SCTP_PRIMARY_ADDR: { struct sctp_setprim *spa; struct sctp_nets *net, *lnet; SCTP_CHECK_AND_CAST(spa, optval, struct sctp_setprim, optsize); SCTP_FIND_STCB(inp, stcb, spa->ssp_assoc_id); net = NULL; if (stcb) { net = sctp_findnet(stcb, (struct sockaddr *)&spa->ssp_addr); } else { /* * We increment here since * sctp_findassociation_ep_addr() wil do a * decrement if it finds the stcb as long as * the locked tcb (last argument) is NOT a * TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, (struct sockaddr *)&spa->ssp_addr, &net, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } } if ((stcb) && (net)) { if ((net != stcb->asoc.primary_destination) && (!(net->dest_state & SCTP_ADDR_UNCONFIRMED))) { /* Ok we need to set it */ lnet = stcb->asoc.primary_destination; if (sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, net) == 0) { if (net->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { net->dest_state |= SCTP_ADDR_DOUBLE_SWITCH; } net->dest_state |= SCTP_ADDR_SWITCH_PRIMARY; } } } else { error = EINVAL; } if (stcb) { SCTP_TCB_UNLOCK(stcb); } } break; case SCTP_SET_DYNAMIC_PRIMARY: { union sctp_sockstore *ss; error = priv_check_cred(curthread->td_ucred, PRIV_NETINET_RESERVEDPORT, SUSER_ALLOWJAIL); if (error) break; SCTP_CHECK_AND_CAST(ss, optval, union sctp_sockstore, optsize); /* SUPER USER CHECK? */ error = sctp_dynamic_set_primary(&ss->sa, vrf_id); } break; case SCTP_SET_PEER_PRIMARY_ADDR: { struct sctp_setpeerprim *sspp; SCTP_CHECK_AND_CAST(sspp, optval, struct sctp_setpeerprim, optsize); SCTP_FIND_STCB(inp, stcb, sspp->sspp_assoc_id); if (stcb != NULL) { if (sctp_set_primary_ip_address_sa(stcb, (struct sockaddr *)&sspp->sspp_addr) != 0) { error = EINVAL; } SCTP_TCB_UNLOCK(stcb); } else { error = EINVAL; } } break; case SCTP_BINDX_ADD_ADDR: { struct sctp_getaddresses *addrs; struct sockaddr *addr_touse; struct sockaddr_in sin; SCTP_CHECK_AND_CAST(addrs, optval, struct sctp_getaddresses, optsize); /* see if we're bound all already! */ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { error = EINVAL; break; } /* Is the VRF one we have */ addr_touse = addrs->addr; #if defined(INET6) if (addrs->addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)addr_touse; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { in6_sin6_2_sin(&sin, sin6); addr_touse = (struct sockaddr *)&sin; } } #endif if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { if (p == NULL) { /* Can't get proc for Net/Open BSD */ error = EINVAL; break; } error = sctp_inpcb_bind(so, addr_touse, p); break; } /* * No locks required here since bind and mgmt_ep_sa * all do their own locking. If we do something for * the FIX: below we may need to lock in that case. */ if (addrs->sget_assoc_id == 0) { /* add the address */ struct sctp_inpcb *lep; ((struct sockaddr_in *)addr_touse)->sin_port = inp->sctp_lport; lep = sctp_pcb_findep(addr_touse, 1, 0, vrf_id); if (lep != NULL) { /* * We must decrement the refcount * since we have the ep already and * are binding. No remove going on * here. */ SCTP_INP_DECR_REF(inp); } if (lep == inp) { /* already bound to it.. ok */ break; } else if (lep == NULL) { ((struct sockaddr_in *)addr_touse)->sin_port = 0; error = sctp_addr_mgmt_ep_sa(inp, addr_touse, SCTP_ADD_IP_ADDRESS, vrf_id); } else { error = EADDRNOTAVAIL; } if (error) break; } else { /* * FIX: decide whether we allow assoc based * bindx */ } } break; case SCTP_BINDX_REM_ADDR: { struct sctp_getaddresses *addrs; struct sockaddr *addr_touse; struct sockaddr_in sin; SCTP_CHECK_AND_CAST(addrs, optval, struct sctp_getaddresses, optsize); /* see if we're bound all already! */ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { error = EINVAL; break; } addr_touse = addrs->addr; #if defined(INET6) if (addrs->addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)addr_touse; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { in6_sin6_2_sin(&sin, sin6); addr_touse = (struct sockaddr *)&sin; } } #endif /* * No lock required mgmt_ep_sa does its own locking. * If the FIX: below is ever changed we may need to * lock before calling association level binding. */ if (addrs->sget_assoc_id == 0) { /* delete the address */ sctp_addr_mgmt_ep_sa(inp, addr_touse, SCTP_DEL_IP_ADDRESS, vrf_id); } else { /* * FIX: decide whether we allow assoc based * bindx */ } } break; default: error = ENOPROTOOPT; break; } /* end switch (opt) */ return (error); } int sctp_ctloutput(struct socket *so, struct sockopt *sopt) { void *optval = NULL; size_t optsize = 0; struct sctp_inpcb *inp; void *p; int error = 0; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { /* I made the same as TCP since we are not setup? */ return (ECONNRESET); } if (sopt->sopt_level != IPPROTO_SCTP) { /* wrong proto level... send back up to IP */ #ifdef INET6 if (INP_CHECK_SOCKAF(so, AF_INET6)) error = ip6_ctloutput(so, sopt); else #endif /* INET6 */ error = ip_ctloutput(so, sopt); return (error); } optsize = sopt->sopt_valsize; if (optsize) { SCTP_MALLOC(optval, void *, optsize, "SCTPSockOpt"); if (optval == NULL) { return (ENOBUFS); } error = sooptcopyin(sopt, optval, optsize, optsize); if (error) { SCTP_FREE(optval); goto out; } } p = (void *)sopt->sopt_td; if (sopt->sopt_dir == SOPT_SET) { error = sctp_setopt(so, sopt->sopt_name, optval, optsize, p); } else if (sopt->sopt_dir == SOPT_GET) { error = sctp_getopt(so, sopt->sopt_name, optval, &optsize, p); } else { error = EINVAL; } if ((error == 0) && (optval != NULL)) { error = sooptcopyout(sopt, optval, optsize); SCTP_FREE(optval); } else if (optval != NULL) { SCTP_FREE(optval); } out: return (error); } static int sctp_connect(struct socket *so, struct sockaddr *addr, struct thread *p) { int error = 0; int create_lock_on = 0; uint32_t vrf_id; struct sctp_inpcb *inp; struct sctp_tcb *stcb = NULL; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { /* I made the same as TCP since we are not setup? */ return (ECONNRESET); } SCTP_ASOC_CREATE_LOCK(inp); create_lock_on = 1; SCTP_INP_INCR_REF(inp); if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { /* Should I really unlock ? */ error = EFAULT; goto out_now; } #ifdef INET6 if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && (addr->sa_family == AF_INET6)) { error = EINVAL; goto out_now; } #endif /* INET6 */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == SCTP_PCB_FLAGS_UNBOUND) { /* Bind a ephemeral port */ error = sctp_inpcb_bind(so, NULL, p); if (error) { goto out_now; } } /* Now do we connect? */ if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { error = EINVAL; goto out_now; } if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { /* We are already connected AND the TCP model */ error = EADDRINUSE; goto out_now; } if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); SCTP_INP_RUNLOCK(inp); } else { /* * We increment here since sctp_findassociation_ep_addr() * wil do a decrement if it finds the stcb as long as the * locked tcb (last argument) is NOT a TCB.. aka NULL. */ SCTP_INP_INCR_REF(inp); stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL); if (stcb == NULL) { SCTP_INP_DECR_REF(inp); } else { SCTP_TCB_LOCK(stcb); } } if (stcb != NULL) { /* Already have or am bring up an association */ error = EALREADY; goto out_now; } vrf_id = inp->def_vrf_id; /* We are GOOD to go */ stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0, vrf_id); if (stcb == NULL) { /* Gak! no memory */ goto out_now; } if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; /* Set the connected flag so we can queue data */ soisconnecting(so); } stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; - SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + (void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); /* initialize authentication parameters for the assoc */ sctp_initialize_auth_params(inp, stcb); sctp_send_initiate(inp, stcb); SCTP_TCB_UNLOCK(stcb); out_now: if (create_lock_on) SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_INP_DECR_REF(inp); return error; } int sctp_listen(struct socket *so, int backlog, struct thread *p) { /* * Note this module depends on the protocol processing being called * AFTER any socket level flags and backlog are applied to the * socket. The traditional way that the socket flags are applied is * AFTER protocol processing. We have made a change to the * sys/kern/uipc_socket.c module to reverse this but this MUST be in * place if the socket API for SCTP is to work properly. */ int error = 0; struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { /* I made the same as TCP since we are not setup? */ return (ECONNRESET); } SCTP_INP_RLOCK(inp); #ifdef SCTP_LOCK_LOGGING sctp_log_lock(inp, (struct sctp_tcb *)NULL, SCTP_LOG_LOCK_SOCK); #endif SOCK_LOCK(so); error = solisten_proto_check(so); if (error) { SOCK_UNLOCK(so); SCTP_INP_RUNLOCK(inp); return (error); } if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { /* We are already connected AND the TCP model */ SCTP_INP_RUNLOCK(inp); SOCK_UNLOCK(so); return (EADDRINUSE); } if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { /* We must do a bind. */ SOCK_UNLOCK(so); SCTP_INP_RUNLOCK(inp); if ((error = sctp_inpcb_bind(so, NULL, p))) { /* bind error, probably perm */ return (error); } SOCK_LOCK(so); } else { SCTP_INP_RUNLOCK(inp); } /* It appears for 7.0 and on, we must always call this. */ solisten_proto(so, backlog); if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { /* remove the ACCEPTCONN flag for one-to-many sockets */ so->so_options &= ~SO_ACCEPTCONN; } if (backlog == 0) { /* turning off listen */ so->so_options &= ~SO_ACCEPTCONN; } SOCK_UNLOCK(so); return (error); } static int sctp_defered_wakeup_cnt = 0; int sctp_accept(struct socket *so, struct sockaddr **addr) { struct sctp_tcb *stcb; struct sctp_inpcb *inp; union sctp_sockstore store; int error; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { return (ECONNRESET); } SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { SCTP_INP_RUNLOCK(inp); return (ENOTSUP); } if (so->so_state & SS_ISDISCONNECTED) { SCTP_INP_RUNLOCK(inp); return (ECONNABORTED); } stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { SCTP_INP_RUNLOCK(inp); return (ECONNRESET); } SCTP_TCB_LOCK(stcb); SCTP_INP_RUNLOCK(inp); store = stcb->asoc.primary_destination->ro._l_addr; SCTP_TCB_UNLOCK(stcb); if (store.sa.sa_family == AF_INET) { struct sockaddr_in *sin; SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_port = ((struct sockaddr_in *)&store)->sin_port; sin->sin_addr = ((struct sockaddr_in *)&store)->sin_addr; *addr = (struct sockaddr *)sin; } else { struct sockaddr_in6 *sin6; SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); sin6->sin6_port = ((struct sockaddr_in6 *)&store)->sin6_port; sin6->sin6_addr = ((struct sockaddr_in6 *)&store)->sin6_addr; if ((error = sa6_recoverscope(sin6)) != 0) { SCTP_FREE_SONAME(sin6); return (error); } *addr = (struct sockaddr *)sin6; } /* Wake any delayed sleep action */ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { SCTP_INP_WLOCK(inp); inp->sctp_flags &= ~SCTP_PCB_FLAGS_DONT_WAKE; if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT) { inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT; SCTP_INP_WUNLOCK(inp); SOCKBUF_LOCK(&inp->sctp_socket->so_snd); if (sowriteable(inp->sctp_socket)) { sowwakeup_locked(inp->sctp_socket); } else { SOCKBUF_UNLOCK(&inp->sctp_socket->so_snd); } SCTP_INP_WLOCK(inp); } if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT) { inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT; SCTP_INP_WUNLOCK(inp); SOCKBUF_LOCK(&inp->sctp_socket->so_rcv); if (soreadable(inp->sctp_socket)) { sctp_defered_wakeup_cnt++; sorwakeup_locked(inp->sctp_socket); } else { SOCKBUF_UNLOCK(&inp->sctp_socket->so_rcv); } SCTP_INP_WLOCK(inp); } SCTP_INP_WUNLOCK(inp); } return (0); } int sctp_ingetaddr(struct socket *so, struct sockaddr **addr) { struct sockaddr_in *sin; uint32_t vrf_id; struct sctp_inpcb *inp; struct sctp_ifa *sctp_ifa; /* * Do the malloc first in case it blocks. */ SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); inp = (struct sctp_inpcb *)so->so_pcb; if (!inp) { SCTP_FREE_SONAME(sin); return ECONNRESET; } SCTP_INP_RLOCK(inp); sin->sin_port = inp->sctp_lport; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { struct sctp_tcb *stcb; struct sockaddr_in *sin_a; struct sctp_nets *net; int fnd; stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { goto notConn; } fnd = 0; sin_a = NULL; SCTP_TCB_LOCK(stcb); TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { sin_a = (struct sockaddr_in *)&net->ro._l_addr; if (sin_a == NULL) /* this will make coverity happy */ continue; if (sin_a->sin_family == AF_INET) { fnd = 1; break; } } if ((!fnd) || (sin_a == NULL)) { /* punt */ SCTP_TCB_UNLOCK(stcb); goto notConn; } vrf_id = inp->def_vrf_id; sctp_ifa = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, vrf_id); if (sctp_ifa) { sin->sin_addr = sctp_ifa->address.sin.sin_addr; sctp_free_ifa(sctp_ifa); } SCTP_TCB_UNLOCK(stcb); } else { /* For the bound all case you get back 0 */ notConn: sin->sin_addr.s_addr = 0; } } else { /* Take the first IPv4 address in the list */ struct sctp_laddr *laddr; int fnd = 0; LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa->address.sa.sa_family == AF_INET) { struct sockaddr_in *sin_a; sin_a = (struct sockaddr_in *)&laddr->ifa->address.sa; sin->sin_addr = sin_a->sin_addr; fnd = 1; break; } } if (!fnd) { SCTP_FREE_SONAME(sin); SCTP_INP_RUNLOCK(inp); return ENOENT; } } SCTP_INP_RUNLOCK(inp); (*addr) = (struct sockaddr *)sin; return (0); } int sctp_peeraddr(struct socket *so, struct sockaddr **addr) { struct sockaddr_in *sin = (struct sockaddr_in *)*addr; int fnd; struct sockaddr_in *sin_a; struct sctp_inpcb *inp; struct sctp_tcb *stcb; struct sctp_nets *net; /* Do the malloc first in case it blocks. */ inp = (struct sctp_inpcb *)so->so_pcb; if ((inp == NULL) || ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { /* UDP type and listeners will drop out here */ return (ENOTCONN); } SCTP_MALLOC_SONAME(sin, struct sockaddr_in *, sizeof *sin); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); /* We must recapture incase we blocked */ inp = (struct sctp_inpcb *)so->so_pcb; if (!inp) { SCTP_FREE_SONAME(sin); return ECONNRESET; } SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb) SCTP_TCB_LOCK(stcb); SCTP_INP_RUNLOCK(inp); if (stcb == NULL) { SCTP_FREE_SONAME(sin); return ECONNRESET; } fnd = 0; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { sin_a = (struct sockaddr_in *)&net->ro._l_addr; if (sin_a->sin_family == AF_INET) { fnd = 1; sin->sin_port = stcb->rport; sin->sin_addr = sin_a->sin_addr; break; } } SCTP_TCB_UNLOCK(stcb); if (!fnd) { /* No IPv4 address */ SCTP_FREE_SONAME(sin); return ENOENT; } (*addr) = (struct sockaddr *)sin; return (0); } struct pr_usrreqs sctp_usrreqs = { .pru_abort = sctp_abort, .pru_accept = sctp_accept, .pru_attach = sctp_attach, .pru_bind = sctp_bind, .pru_connect = sctp_connect, .pru_control = in_control, .pru_close = sctp_close, .pru_detach = sctp_close, .pru_sopoll = sopoll_generic, .pru_disconnect = sctp_disconnect, .pru_listen = sctp_listen, .pru_peeraddr = sctp_peeraddr, .pru_send = sctp_sendm, .pru_shutdown = sctp_shutdown, .pru_sockaddr = sctp_ingetaddr, .pru_sosend = sctp_sosend, .pru_soreceive = sctp_soreceive }; diff --git a/sys/netinet/sctp_var.h b/sys/netinet/sctp_var.h index ae68b23a89b9..a7a3f6b6d188 100644 --- a/sys/netinet/sctp_var.h +++ b/sys/netinet/sctp_var.h @@ -1,313 +1,313 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp_var.h,v 1.24 2005/03/06 16:04:19 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #ifndef _NETINET_SCTP_VAR_H_ #define _NETINET_SCTP_VAR_H_ #include #if defined(_KERNEL) extern struct pr_usrreqs sctp_usrreqs; #define sctp_feature_on(inp, feature) (inp->sctp_features |= feature) #define sctp_feature_off(inp, feature) (inp->sctp_features &= ~feature) #define sctp_is_feature_on(inp, feature) (inp->sctp_features & feature) #define sctp_is_feature_off(inp, feature) ((inp->sctp_features & feature) == 0) #define sctp_sbspace(asoc, sb) ((long) (((sb)->sb_hiwat > (asoc)->sb_cc) ? ((sb)->sb_hiwat - (asoc)->sb_cc) : 0)) #define sctp_sbspace_failedmsgs(sb) ((long) (((sb)->sb_hiwat > (sb)->sb_cc) ? ((sb)->sb_hiwat - (sb)->sb_cc) : 0)) #define sctp_sbspace_sub(a,b) ((a > b) ? (a - b) : 0) /* * I tried to cache the readq entries at one point. But the reality * is that it did not add any performance since this meant we had to * lock the STCB on read. And at that point once you have to do an * extra lock, it really does not matter if the lock is in the ZONE * stuff or in our code. Note that this same problem would occur with * an mbuf cache as well so it is not really worth doing, at least * right now :-D */ #define sctp_free_a_readq(_stcb, _readq) { \ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, (_readq)); \ SCTP_DECR_READQ_COUNT(); \ } #define sctp_alloc_a_readq(_stcb, _readq) { \ (_readq) = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_readq, struct sctp_queued_to_read); \ if ((_readq)) { \ SCTP_INCR_READQ_COUNT(); \ } \ } #define sctp_free_a_strmoq(_stcb, _strmoq) { \ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_strmoq, (_strmoq)); \ SCTP_DECR_STRMOQ_COUNT(); \ } #define sctp_alloc_a_strmoq(_stcb, _strmoq) { \ (_strmoq) = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_strmoq, struct sctp_stream_queue_pending); \ if ((_strmoq)) { \ SCTP_INCR_STRMOQ_COUNT(); \ } \ } #define sctp_free_a_chunk(_stcb, _chk) { \ if (((_stcb)->asoc.free_chunk_cnt > sctp_asoc_free_resc_limit) || \ (sctppcbinfo.ipi_free_chunks > sctp_system_free_resc_limit)) { \ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, (_chk)); \ SCTP_DECR_CHK_COUNT(); \ } else { \ TAILQ_INSERT_TAIL(&(_stcb)->asoc.free_chunks, (_chk), sctp_next); \ (_stcb)->asoc.free_chunk_cnt++; \ atomic_add_int(&sctppcbinfo.ipi_free_chunks, 1); \ } \ } #define sctp_alloc_a_chunk(_stcb, _chk) { \ if (TAILQ_EMPTY(&(_stcb)->asoc.free_chunks)) { \ (_chk) = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk, struct sctp_tmit_chunk); \ if ((_chk)) { \ SCTP_INCR_CHK_COUNT(); \ } \ } else { \ (_chk) = TAILQ_FIRST(&(_stcb)->asoc.free_chunks); \ TAILQ_REMOVE(&(_stcb)->asoc.free_chunks, (_chk), sctp_next); \ atomic_subtract_int(&sctppcbinfo.ipi_free_chunks, 1); \ SCTP_STAT_INCR(sctps_cached_chk); \ (_stcb)->asoc.free_chunk_cnt--; \ } \ } #define sctp_free_remote_addr(__net) { \ if ((__net)) { \ if (atomic_fetchadd_int(&(__net)->ref_count, -1) == 1) { \ - SCTP_OS_TIMER_STOP(&(__net)->rxt_timer.timer); \ - SCTP_OS_TIMER_STOP(&(__net)->pmtu_timer.timer); \ - SCTP_OS_TIMER_STOP(&(__net)->fr_timer.timer); \ + (void)SCTP_OS_TIMER_STOP(&(__net)->rxt_timer.timer); \ + (void)SCTP_OS_TIMER_STOP(&(__net)->pmtu_timer.timer); \ + (void)SCTP_OS_TIMER_STOP(&(__net)->fr_timer.timer); \ if ((__net)->ro.ro_rt) { \ RTFREE((__net)->ro.ro_rt); \ (__net)->ro.ro_rt = NULL; \ } \ if ((__net)->src_addr_selected) { \ sctp_free_ifa((__net)->ro._s_addr); \ (__net)->ro._s_addr = NULL; \ } \ (__net)->src_addr_selected = 0; \ (__net)->dest_state = SCTP_ADDR_NOT_REACHABLE; \ SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_net, (__net)); \ SCTP_DECR_RADDR_COUNT(); \ } \ } \ } #define sctp_sbfree(ctl, stcb, sb, m) { \ uint32_t val; \ val = atomic_fetchadd_int(&(sb)->sb_cc,-(SCTP_BUF_LEN((m)))); \ if (val < SCTP_BUF_LEN((m))) { \ panic("sb_cc goes negative"); \ } \ val = atomic_fetchadd_int(&(sb)->sb_mbcnt,-(MSIZE)); \ if (val < MSIZE) { \ panic("sb_mbcnt goes negative"); \ } \ if (SCTP_BUF_IS_EXTENDED(m)) { \ val = atomic_fetchadd_int(&(sb)->sb_mbcnt,-(SCTP_BUF_EXTEND_SIZE(m))); \ if (val < SCTP_BUF_EXTEND_SIZE(m)) { \ panic("sb_mbcnt goes negative2"); \ } \ } \ if (((ctl)->do_not_ref_stcb == 0) && stcb) {\ val = atomic_fetchadd_int(&(stcb)->asoc.sb_cc,-(SCTP_BUF_LEN((m)))); \ if (val < SCTP_BUF_LEN((m))) {\ panic("stcb->sb_cc goes negative"); \ } \ val = atomic_fetchadd_int(&(stcb)->asoc.sb_mbcnt,-(MSIZE)); \ if (val < MSIZE) { \ panic("asoc->mbcnt goes negative"); \ } \ if (SCTP_BUF_IS_EXTENDED(m)) { \ val = atomic_fetchadd_int(&(stcb)->asoc.sb_mbcnt,-(SCTP_BUF_EXTEND_SIZE(m))); \ if (val < SCTP_BUF_EXTEND_SIZE(m)) { \ panic("assoc stcb->mbcnt would go negative"); \ } \ } \ } \ if (SCTP_BUF_TYPE(m) != MT_DATA && SCTP_BUF_TYPE(m) != MT_HEADER && \ SCTP_BUF_TYPE(m) != MT_OOBDATA) \ atomic_subtract_int(&(sb)->sb_ctl,SCTP_BUF_LEN((m))); \ } #define sctp_sballoc(stcb, sb, m) { \ atomic_add_int(&(sb)->sb_cc,SCTP_BUF_LEN((m))); \ atomic_add_int(&(sb)->sb_mbcnt, MSIZE); \ if (SCTP_BUF_IS_EXTENDED(m)) \ atomic_add_int(&(sb)->sb_mbcnt,SCTP_BUF_EXTEND_SIZE(m)); \ if (stcb) { \ atomic_add_int(&(stcb)->asoc.sb_cc,SCTP_BUF_LEN((m))); \ atomic_add_int(&(stcb)->asoc.sb_mbcnt, MSIZE); \ if (SCTP_BUF_IS_EXTENDED(m)) \ atomic_add_int(&(stcb)->asoc.sb_mbcnt,SCTP_BUF_EXTEND_SIZE(m)); \ } \ if (SCTP_BUF_TYPE(m) != MT_DATA && SCTP_BUF_TYPE(m) != MT_HEADER && \ SCTP_BUF_TYPE(m) != MT_OOBDATA) \ atomic_add_int(&(sb)->sb_ctl,SCTP_BUF_LEN((m))); \ } #define sctp_ucount_incr(val) { \ val++; \ } #define sctp_ucount_decr(val) { \ if (val > 0) { \ val--; \ } else { \ val = 0; \ } \ } #define sctp_mbuf_crush(data) do { \ struct mbuf *_m; \ _m = (data); \ while(_m && (SCTP_BUF_LEN(_m) == 0)) { \ (data) = SCTP_BUF_NEXT(_m); \ SCTP_BUF_NEXT(_m) = NULL; \ sctp_m_free(_m); \ _m = (data); \ } \ } while (0) #ifdef RANDY_WILL_USE_LATER /* this will be the non-invarant version */ #define sctp_flight_size_decrease(tp1) do { \ if (tp1->whoTo->flight_size >= tp1->book_size) \ tp1->whoTo->flight_size -= tp1->book_size; \ else \ tp1->whoTo->flight_size = 0; \ } while (0) #define sctp_total_flight_decrease(stcb, tp1) do { \ if (stcb->asoc.total_flight >= tp1->book_size) { \ stcb->asoc.total_flight -= tp1->book_size; \ if (stcb->asoc.total_flight_count > 0) \ stcb->asoc.total_flight_count--; \ } else { \ stcb->asoc.total_flight = 0; \ stcb->asoc.total_flight_count = 0; \ } \ } while (0) #else #define sctp_flight_size_decrease(tp1) do { \ if (tp1->whoTo->flight_size >= tp1->book_size) \ tp1->whoTo->flight_size -= tp1->book_size; \ else \ panic("flight size corruption"); \ } while (0) #define sctp_total_flight_decrease(stcb, tp1) do { \ if (stcb->asoc.total_flight >= tp1->book_size) { \ stcb->asoc.total_flight -= tp1->book_size; \ if (stcb->asoc.total_flight_count > 0) \ stcb->asoc.total_flight_count--; \ } else { \ panic("total flight size corruption"); \ } \ } while (0) #endif #define sctp_flight_size_increase(tp1) do { \ (tp1)->whoTo->flight_size += (tp1)->book_size; \ } while (0) #define sctp_total_flight_increase(stcb, tp1) do { \ (stcb)->asoc.total_flight_count++; \ (stcb)->asoc.total_flight += (tp1)->book_size; \ } while (0) struct sctp_nets; struct sctp_inpcb; struct sctp_tcb; struct sctphdr; void sctp_ctlinput __P((int, struct sockaddr *, void *)); int sctp_ctloutput __P((struct socket *, struct sockopt *)); void sctp_input __P((struct mbuf *, int)); void sctp_drain __P((void)); void sctp_init __P((void)); void sctp_pcbinfo_cleanup(void); int sctp_shutdown __P((struct socket *)); void sctp_notify __P((struct sctp_inpcb *, int, struct sctphdr *, struct sockaddr *, struct sctp_tcb *, struct sctp_nets *)); int sctp_bindx(struct socket *, int, struct sockaddr_storage *, int, int, struct proc *); /* can't use sctp_assoc_t here */ int sctp_peeloff(struct socket *, struct socket *, int, caddr_t, int *); int sctp_ingetaddr(struct socket *, struct sockaddr ** ); int sctp_peeraddr(struct socket *, struct sockaddr ** ); int sctp_listen(struct socket *, int, struct thread *); int sctp_accept(struct socket *, struct sockaddr **); #endif /* _KERNEL */ #endif /* !_NETINET_SCTP_VAR_H_ */ diff --git a/sys/netinet/sctputil.c b/sys/netinet/sctputil.c index c9b422e9baa5..44c8475e6ca8 100644 --- a/sys/netinet/sctputil.c +++ b/sys/netinet/sctputil.c @@ -1,5766 +1,5765 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctputil.c,v 1.37 2005/03/07 23:26:09 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #ifdef INET6 #include #endif #include #include #include #include #include #include /* for sctp_deliver_data() */ #include #include #define NUMBER_OF_MTU_SIZES 18 #ifdef SCTP_STAT_LOGGING int global_sctp_cwnd_log_at = 0; int global_sctp_cwnd_log_rolled = 0; struct sctp_cwnd_log sctp_clog[SCTP_STAT_LOG_SIZE]; static uint32_t sctp_get_time_of_event(void) { struct timeval now; uint32_t timeval; SCTP_GETPTIME_TIMEVAL(&now); timeval = (now.tv_sec % 0x00000fff); timeval <<= 20; timeval |= now.tv_usec & 0xfffff; return (timeval); } void sctp_clr_stat_log(void) { global_sctp_cwnd_log_at = 0; global_sctp_cwnd_log_rolled = 0; } void sctp_sblog(struct sockbuf *sb, struct sctp_tcb *stcb, int from, int incr) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_SB; sctp_clog[sctp_cwnd_log_at].x.sb.stcb = stcb; sctp_clog[sctp_cwnd_log_at].x.sb.so_sbcc = sb->sb_cc; if (stcb) sctp_clog[sctp_cwnd_log_at].x.sb.stcb_sbcc = stcb->asoc.sb_cc; else sctp_clog[sctp_cwnd_log_at].x.sb.stcb_sbcc = 0; sctp_clog[sctp_cwnd_log_at].x.sb.incr = incr; } void sctp_log_closing(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int16_t loc) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = 0; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_CLOSE; sctp_clog[sctp_cwnd_log_at].x.close.inp = (void *)inp; sctp_clog[sctp_cwnd_log_at].x.close.sctp_flags = inp->sctp_flags; if (stcb) { sctp_clog[sctp_cwnd_log_at].x.close.stcb = (void *)stcb; sctp_clog[sctp_cwnd_log_at].x.close.state = (uint16_t) stcb->asoc.state; } else { sctp_clog[sctp_cwnd_log_at].x.close.stcb = 0; sctp_clog[sctp_cwnd_log_at].x.close.state = 0; } sctp_clog[sctp_cwnd_log_at].x.close.loc = loc; } void rto_logging(struct sctp_nets *net, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_RTT; sctp_clog[sctp_cwnd_log_at].x.rto.net = (void *)net; sctp_clog[sctp_cwnd_log_at].x.rto.rtt = net->prev_rtt; } void sctp_log_strm_del_alt(struct sctp_tcb *stcb, uint32_t tsn, uint16_t sseq, uint16_t stream, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_STRM; sctp_clog[sctp_cwnd_log_at].x.strlog.stcb = stcb; sctp_clog[sctp_cwnd_log_at].x.strlog.n_tsn = tsn; sctp_clog[sctp_cwnd_log_at].x.strlog.n_sseq = sseq; sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = 0; sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = 0; sctp_clog[sctp_cwnd_log_at].x.strlog.strm = stream; } void sctp_log_nagle_event(struct sctp_tcb *stcb, int action) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) action; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_NAGLE; sctp_clog[sctp_cwnd_log_at].x.nagle.stcb = (void *)stcb; sctp_clog[sctp_cwnd_log_at].x.nagle.total_flight = stcb->asoc.total_flight; sctp_clog[sctp_cwnd_log_at].x.nagle.total_in_queue = stcb->asoc.total_output_queue_size; sctp_clog[sctp_cwnd_log_at].x.nagle.count_in_queue = stcb->asoc.chunks_on_out_queue; sctp_clog[sctp_cwnd_log_at].x.nagle.count_in_flight = stcb->asoc.total_flight_count; } void sctp_log_sack(uint32_t old_cumack, uint32_t cumack, uint32_t tsn, uint16_t gaps, uint16_t dups, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_SACK; sctp_clog[sctp_cwnd_log_at].x.sack.cumack = cumack; sctp_clog[sctp_cwnd_log_at].x.sack.oldcumack = old_cumack; sctp_clog[sctp_cwnd_log_at].x.sack.tsn = tsn; sctp_clog[sctp_cwnd_log_at].x.sack.numGaps = gaps; sctp_clog[sctp_cwnd_log_at].x.sack.numDups = dups; } void sctp_log_map(uint32_t map, uint32_t cum, uint32_t high, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_MAP; sctp_clog[sctp_cwnd_log_at].x.map.base = map; sctp_clog[sctp_cwnd_log_at].x.map.cum = cum; sctp_clog[sctp_cwnd_log_at].x.map.high = high; } void sctp_log_fr(uint32_t biggest_tsn, uint32_t biggest_new_tsn, uint32_t tsn, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_FR; sctp_clog[sctp_cwnd_log_at].x.fr.largest_tsn = biggest_tsn; sctp_clog[sctp_cwnd_log_at].x.fr.largest_new_tsn = biggest_new_tsn; sctp_clog[sctp_cwnd_log_at].x.fr.tsn = tsn; } void sctp_log_mb(struct mbuf *m, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_MBUF; sctp_clog[sctp_cwnd_log_at].x.mb.mp = m; sctp_clog[sctp_cwnd_log_at].x.mb.mbuf_flags = (uint8_t) (SCTP_BUF_GET_FLAGS(m)); sctp_clog[sctp_cwnd_log_at].x.mb.size = (uint16_t) (SCTP_BUF_LEN(m)); sctp_clog[sctp_cwnd_log_at].x.mb.data = SCTP_BUF_AT(m, 0); if (SCTP_BUF_IS_EXTENDED(m)) { sctp_clog[sctp_cwnd_log_at].x.mb.ext = SCTP_BUF_EXTEND_BASE(m); sctp_clog[sctp_cwnd_log_at].x.mb.refcnt = (uint8_t) (SCTP_BUF_EXTEND_REFCNT(m)); } else { sctp_clog[sctp_cwnd_log_at].x.mb.ext = 0; sctp_clog[sctp_cwnd_log_at].x.mb.refcnt = 0; } } void sctp_log_strm_del(struct sctp_queued_to_read *control, struct sctp_queued_to_read *poschk, int from) { int sctp_cwnd_log_at; if (control == NULL) { printf("Gak log of NULL?\n"); return; } SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_STRM; sctp_clog[sctp_cwnd_log_at].x.strlog.stcb = control->stcb; sctp_clog[sctp_cwnd_log_at].x.strlog.n_tsn = control->sinfo_tsn; sctp_clog[sctp_cwnd_log_at].x.strlog.n_sseq = control->sinfo_ssn; sctp_clog[sctp_cwnd_log_at].x.strlog.strm = control->sinfo_stream; if (poschk != NULL) { sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = poschk->sinfo_tsn; sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = poschk->sinfo_ssn; } else { sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = 0; sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = 0; } } void sctp_log_cwnd(struct sctp_tcb *stcb, struct sctp_nets *net, int augment, uint8_t from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_CWND; sctp_clog[sctp_cwnd_log_at].x.cwnd.net = net; if (stcb->asoc.send_queue_cnt > 255) sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = 255; else sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt; if (stcb->asoc.stream_queue_cnt > 255) sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = 255; else sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt; if (net) { sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_new_value = net->cwnd; sctp_clog[sctp_cwnd_log_at].x.cwnd.inflight = net->flight_size; sctp_clog[sctp_cwnd_log_at].x.cwnd.pseudo_cumack = net->pseudo_cumack; sctp_clog[sctp_cwnd_log_at].x.cwnd.meets_pseudo_cumack = net->new_pseudo_cumack; sctp_clog[sctp_cwnd_log_at].x.cwnd.need_new_pseudo_cumack = net->find_pseudo_cumack; } if (SCTP_CWNDLOG_PRESEND == from) { sctp_clog[sctp_cwnd_log_at].x.cwnd.meets_pseudo_cumack = stcb->asoc.peers_rwnd; } sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_augment = augment; } void sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_LOCK_EVENT; if (inp) { sctp_clog[sctp_cwnd_log_at].x.lock.sock = (void *)inp->sctp_socket; } else { sctp_clog[sctp_cwnd_log_at].x.lock.sock = (void *)NULL; } sctp_clog[sctp_cwnd_log_at].x.lock.inp = (void *)inp; if (stcb) { sctp_clog[sctp_cwnd_log_at].x.lock.tcb_lock = mtx_owned(&stcb->tcb_mtx); } else { sctp_clog[sctp_cwnd_log_at].x.lock.tcb_lock = SCTP_LOCK_UNKNOWN; } if (inp) { sctp_clog[sctp_cwnd_log_at].x.lock.inp_lock = mtx_owned(&inp->inp_mtx); sctp_clog[sctp_cwnd_log_at].x.lock.create_lock = mtx_owned(&inp->inp_create_mtx); } else { sctp_clog[sctp_cwnd_log_at].x.lock.inp_lock = SCTP_LOCK_UNKNOWN; sctp_clog[sctp_cwnd_log_at].x.lock.create_lock = SCTP_LOCK_UNKNOWN; } sctp_clog[sctp_cwnd_log_at].x.lock.info_lock = mtx_owned(&sctppcbinfo.ipi_ep_mtx); if (inp->sctp_socket) { sctp_clog[sctp_cwnd_log_at].x.lock.sock_lock = mtx_owned(&(inp->sctp_socket->so_rcv.sb_mtx)); sctp_clog[sctp_cwnd_log_at].x.lock.sockrcvbuf_lock = mtx_owned(&(inp->sctp_socket->so_rcv.sb_mtx)); sctp_clog[sctp_cwnd_log_at].x.lock.socksndbuf_lock = mtx_owned(&(inp->sctp_socket->so_snd.sb_mtx)); } else { sctp_clog[sctp_cwnd_log_at].x.lock.sock_lock = SCTP_LOCK_UNKNOWN; sctp_clog[sctp_cwnd_log_at].x.lock.sockrcvbuf_lock = SCTP_LOCK_UNKNOWN; sctp_clog[sctp_cwnd_log_at].x.lock.socksndbuf_lock = SCTP_LOCK_UNKNOWN; } } void sctp_log_maxburst(struct sctp_tcb *stcb, struct sctp_nets *net, int error, int burst, uint8_t from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_MAXBURST; sctp_clog[sctp_cwnd_log_at].x.cwnd.net = net; sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_new_value = error; sctp_clog[sctp_cwnd_log_at].x.cwnd.inflight = net->flight_size; sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_augment = burst; if (stcb->asoc.send_queue_cnt > 255) sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = 255; else sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt; if (stcb->asoc.stream_queue_cnt > 255) sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = 255; else sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt; } void sctp_log_rwnd(uint8_t from, uint32_t peers_rwnd, uint32_t snd_size, uint32_t overhead) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_RWND; sctp_clog[sctp_cwnd_log_at].x.rwnd.rwnd = peers_rwnd; sctp_clog[sctp_cwnd_log_at].x.rwnd.send_size = snd_size; sctp_clog[sctp_cwnd_log_at].x.rwnd.overhead = overhead; sctp_clog[sctp_cwnd_log_at].x.rwnd.new_rwnd = 0; } void sctp_log_rwnd_set(uint8_t from, uint32_t peers_rwnd, uint32_t flight_size, uint32_t overhead, uint32_t a_rwndval) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_RWND; sctp_clog[sctp_cwnd_log_at].x.rwnd.rwnd = peers_rwnd; sctp_clog[sctp_cwnd_log_at].x.rwnd.send_size = flight_size; sctp_clog[sctp_cwnd_log_at].x.rwnd.overhead = overhead; sctp_clog[sctp_cwnd_log_at].x.rwnd.new_rwnd = a_rwndval; } void sctp_log_mbcnt(uint8_t from, uint32_t total_oq, uint32_t book, uint32_t total_mbcnt_q, uint32_t mbcnt) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_MBCNT; sctp_clog[sctp_cwnd_log_at].x.mbcnt.total_queue_size = total_oq; sctp_clog[sctp_cwnd_log_at].x.mbcnt.size_change = book; sctp_clog[sctp_cwnd_log_at].x.mbcnt.total_queue_mb_size = total_mbcnt_q; sctp_clog[sctp_cwnd_log_at].x.mbcnt.mbcnt_change = mbcnt; } void sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_MISC_EVENT; sctp_clog[sctp_cwnd_log_at].x.misc.log1 = a; sctp_clog[sctp_cwnd_log_at].x.misc.log2 = b; sctp_clog[sctp_cwnd_log_at].x.misc.log3 = c; sctp_clog[sctp_cwnd_log_at].x.misc.log4 = d; } void sctp_wakeup_log(struct sctp_tcb *stcb, uint32_t cumtsn, uint32_t wake_cnt, int from) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_WAKE; sctp_clog[sctp_cwnd_log_at].x.wake.stcb = (void *)stcb; sctp_clog[sctp_cwnd_log_at].x.wake.wake_cnt = wake_cnt; sctp_clog[sctp_cwnd_log_at].x.wake.flight = stcb->asoc.total_flight_count; sctp_clog[sctp_cwnd_log_at].x.wake.send_q = stcb->asoc.send_queue_cnt; sctp_clog[sctp_cwnd_log_at].x.wake.sent_q = stcb->asoc.sent_queue_cnt; if (stcb->asoc.stream_queue_cnt < 0xff) sctp_clog[sctp_cwnd_log_at].x.wake.stream_qcnt = (uint8_t) stcb->asoc.stream_queue_cnt; else sctp_clog[sctp_cwnd_log_at].x.wake.stream_qcnt = 0xff; if (stcb->asoc.chunks_on_out_queue < 0xff) sctp_clog[sctp_cwnd_log_at].x.wake.chunks_on_oque = (uint8_t) stcb->asoc.chunks_on_out_queue; else sctp_clog[sctp_cwnd_log_at].x.wake.chunks_on_oque = 0xff; sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags = 0; /* set in the defered mode stuff */ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 1; if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT) sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 2; if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT) sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 4; /* what about the sb */ if (stcb->sctp_socket) { struct socket *so = stcb->sctp_socket; sctp_clog[sctp_cwnd_log_at].x.wake.sbflags = (uint8_t) ((so->so_snd.sb_flags & 0x00ff)); } else { sctp_clog[sctp_cwnd_log_at].x.wake.sbflags = 0xff; } } void sctp_log_block(uint8_t from, struct socket *so, struct sctp_association *asoc, int sendlen) { int sctp_cwnd_log_at; SCTP_STATLOG_GETREF(sctp_cwnd_log_at); sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from; sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event(); sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_BLOCK; sctp_clog[sctp_cwnd_log_at].x.blk.onsb = asoc->total_output_queue_size; sctp_clog[sctp_cwnd_log_at].x.blk.send_sent_qcnt = (uint16_t) (asoc->send_queue_cnt + asoc->sent_queue_cnt); sctp_clog[sctp_cwnd_log_at].x.blk.peer_rwnd = asoc->peers_rwnd; sctp_clog[sctp_cwnd_log_at].x.blk.stream_qcnt = (uint16_t) asoc->stream_queue_cnt; sctp_clog[sctp_cwnd_log_at].x.blk.chunks_on_oque = (uint16_t) asoc->chunks_on_out_queue; sctp_clog[sctp_cwnd_log_at].x.blk.flight_size = (uint16_t) (asoc->total_flight / 1024); sctp_clog[sctp_cwnd_log_at].x.blk.sndlen = sendlen; } int sctp_fill_stat_log(void *optval, size_t *optsize) { int sctp_cwnd_log_at; struct sctp_cwnd_log_req *req; size_t size_limit; int num, i, at, cnt_out = 0; if (*optsize < sizeof(struct sctp_cwnd_log_req)) { return (EINVAL); } size_limit = (*optsize - sizeof(struct sctp_cwnd_log_req)); if (size_limit < sizeof(struct sctp_cwnd_log)) { return (EINVAL); } sctp_cwnd_log_at = global_sctp_cwnd_log_at; req = (struct sctp_cwnd_log_req *)optval; num = size_limit / sizeof(struct sctp_cwnd_log); if (global_sctp_cwnd_log_rolled) { req->num_in_log = SCTP_STAT_LOG_SIZE; } else { req->num_in_log = sctp_cwnd_log_at; /* * if the log has not rolled, we don't let you have old * data. */ if (req->end_at > sctp_cwnd_log_at) { req->end_at = sctp_cwnd_log_at; } } if ((num < SCTP_STAT_LOG_SIZE) && ((global_sctp_cwnd_log_rolled) || (sctp_cwnd_log_at > num))) { /* we can't return all of it */ if (((req->start_at == 0) && (req->end_at == 0)) || (req->start_at >= SCTP_STAT_LOG_SIZE) || (req->end_at >= SCTP_STAT_LOG_SIZE)) { /* No user request or user is wacked. */ req->num_ret = num; req->end_at = sctp_cwnd_log_at - 1; if ((sctp_cwnd_log_at - num) < 0) { int cc; cc = num - sctp_cwnd_log_at; req->start_at = SCTP_STAT_LOG_SIZE - cc; } else { req->start_at = sctp_cwnd_log_at - num; } } else { /* a user request */ int cc; if (req->start_at > req->end_at) { cc = (SCTP_STAT_LOG_SIZE - req->start_at) + (req->end_at + 1); } else { cc = (req->end_at - req->start_at) + 1; } if (cc < num) { num = cc; } req->num_ret = num; } } else { /* We can return all of it */ req->start_at = 0; req->end_at = sctp_cwnd_log_at - 1; req->num_ret = sctp_cwnd_log_at; } #ifdef INVARIANTS if (req->num_ret > num) { panic("Bad statlog get?"); } #endif for (i = 0, at = req->start_at; i < req->num_ret; i++) { req->log[i] = sctp_clog[at]; cnt_out++; at++; if (at >= SCTP_STAT_LOG_SIZE) at = 0; } *optsize = (cnt_out * sizeof(struct sctp_cwnd_log)) + sizeof(struct sctp_cwnd_log_req); return (0); } #endif #ifdef SCTP_AUDITING_ENABLED uint8_t sctp_audit_data[SCTP_AUDIT_SIZE][2]; static int sctp_audit_indx = 0; static void sctp_print_audit_report(void) { int i; int cnt; cnt = 0; for (i = sctp_audit_indx; i < SCTP_AUDIT_SIZE; i++) { if ((sctp_audit_data[i][0] == 0xe0) && (sctp_audit_data[i][1] == 0x01)) { cnt = 0; printf("\n"); } else if (sctp_audit_data[i][0] == 0xf0) { cnt = 0; printf("\n"); } else if ((sctp_audit_data[i][0] == 0xc0) && (sctp_audit_data[i][1] == 0x01)) { printf("\n"); cnt = 0; } printf("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0], (uint32_t) sctp_audit_data[i][1]); cnt++; if ((cnt % 14) == 0) printf("\n"); } for (i = 0; i < sctp_audit_indx; i++) { if ((sctp_audit_data[i][0] == 0xe0) && (sctp_audit_data[i][1] == 0x01)) { cnt = 0; printf("\n"); } else if (sctp_audit_data[i][0] == 0xf0) { cnt = 0; printf("\n"); } else if ((sctp_audit_data[i][0] == 0xc0) && (sctp_audit_data[i][1] == 0x01)) { printf("\n"); cnt = 0; } printf("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0], (uint32_t) sctp_audit_data[i][1]); cnt++; if ((cnt % 14) == 0) printf("\n"); } printf("\n"); } void sctp_auditing(int from, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { int resend_cnt, tot_out, rep, tot_book_cnt; struct sctp_nets *lnet; struct sctp_tmit_chunk *chk; sctp_audit_data[sctp_audit_indx][0] = 0xAA; sctp_audit_data[sctp_audit_indx][1] = 0x000000ff & from; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } if (inp == NULL) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0x01; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } return; } if (stcb == NULL) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0x02; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } return; } sctp_audit_data[sctp_audit_indx][0] = 0xA1; sctp_audit_data[sctp_audit_indx][1] = (0x000000ff & stcb->asoc.sent_queue_retran_cnt); sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } rep = 0; tot_book_cnt = 0; resend_cnt = tot_out = 0; TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if (chk->sent == SCTP_DATAGRAM_RESEND) { resend_cnt++; } else if (chk->sent < SCTP_DATAGRAM_RESEND) { tot_out += chk->book_size; tot_book_cnt++; } } if (resend_cnt != stcb->asoc.sent_queue_retran_cnt) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0xA1; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } printf("resend_cnt:%d asoc-tot:%d\n", resend_cnt, stcb->asoc.sent_queue_retran_cnt); rep = 1; stcb->asoc.sent_queue_retran_cnt = resend_cnt; sctp_audit_data[sctp_audit_indx][0] = 0xA2; sctp_audit_data[sctp_audit_indx][1] = (0x000000ff & stcb->asoc.sent_queue_retran_cnt); sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } } if (tot_out != stcb->asoc.total_flight) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0xA2; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } rep = 1; printf("tot_flt:%d asoc_tot:%d\n", tot_out, (int)stcb->asoc.total_flight); stcb->asoc.total_flight = tot_out; } if (tot_book_cnt != stcb->asoc.total_flight_count) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0xA5; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } rep = 1; printf("tot_flt_book:%d\n", tot_book); stcb->asoc.total_flight_count = tot_book_cnt; } tot_out = 0; TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { tot_out += lnet->flight_size; } if (tot_out != stcb->asoc.total_flight) { sctp_audit_data[sctp_audit_indx][0] = 0xAF; sctp_audit_data[sctp_audit_indx][1] = 0xA3; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } rep = 1; printf("real flight:%d net total was %d\n", stcb->asoc.total_flight, tot_out); /* now corrective action */ TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { tot_out = 0; TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if ((chk->whoTo == lnet) && (chk->sent < SCTP_DATAGRAM_RESEND)) { tot_out += chk->book_size; } } if (lnet->flight_size != tot_out) { printf("net:%x flight was %d corrected to %d\n", (uint32_t) lnet, lnet->flight_size, tot_out); lnet->flight_size = tot_out; } } } if (rep) { sctp_print_audit_report(); } } void sctp_audit_log(uint8_t ev, uint8_t fd) { sctp_audit_data[sctp_audit_indx][0] = ev; sctp_audit_data[sctp_audit_indx][1] = fd; sctp_audit_indx++; if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { sctp_audit_indx = 0; } } #endif /* * a list of sizes based on typical mtu's, used only if next hop size not * returned. */ static int sctp_mtu_sizes[] = { 68, 296, 508, 512, 544, 576, 1006, 1492, 1500, 1536, 2002, 2048, 4352, 4464, 8166, 17914, 32000, 65535 }; void sctp_stop_timers_for_shutdown(struct sctp_tcb *stcb) { struct sctp_association *asoc; struct sctp_nets *net; asoc = &stcb->asoc; - SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); - SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->hb_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->dack_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->strreset_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->asconf_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->autoclose_timer.timer); + (void)SCTP_OS_TIMER_STOP(&asoc->delayed_event_timer.timer); TAILQ_FOREACH(net, &asoc->nets, sctp_next) { - SCTP_OS_TIMER_STOP(&net->fr_timer.timer); - SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->fr_timer.timer); + (void)SCTP_OS_TIMER_STOP(&net->pmtu_timer.timer); } } int find_next_best_mtu(int totsz) { int i, perfer; /* * if we are in here we must find the next best fit based on the * size of the dg that failed to be sent. */ perfer = 0; for (i = 0; i < NUMBER_OF_MTU_SIZES; i++) { if (totsz < sctp_mtu_sizes[i]) { perfer = i - 1; if (perfer < 0) perfer = 0; break; } } return (sctp_mtu_sizes[perfer]); } void sctp_fill_random_store(struct sctp_pcb *m) { /* * Here we use the MD5/SHA-1 to hash with our good randomNumbers and * our counter. The result becomes our good random numbers and we * then setup to give these out. Note that we do no locking to * protect this. This is ok, since if competing folks call this we * will get more gobbled gook in the random store which is what we * want. There is a danger that two guys will use the same random * numbers, but thats ok too since that is random as well :-> */ m->store_at = 0; sctp_hmac(SCTP_HMAC, (uint8_t *) m->random_numbers, sizeof(m->random_numbers), (uint8_t *) & m->random_counter, sizeof(m->random_counter), (uint8_t *) m->random_store); m->random_counter++; } uint32_t sctp_select_initial_TSN(struct sctp_pcb *m) { /* * A true implementation should use random selection process to get * the initial stream sequence number, using RFC1750 as a good * guideline */ uint32_t x, *xp; uint8_t *p; if (m->initial_sequence_debug != 0) { uint32_t ret; ret = m->initial_sequence_debug; m->initial_sequence_debug++; return (ret); } if ((m->store_at + sizeof(u_long)) > SCTP_SIGNATURE_SIZE) { /* Refill the random store */ sctp_fill_random_store(m); } p = &m->random_store[(int)m->store_at]; xp = (uint32_t *) p; x = *xp; m->store_at += sizeof(uint32_t); return (x); } uint32_t sctp_select_a_tag(struct sctp_inpcb *m) { u_long x, not_done; struct timeval now; - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); not_done = 1; while (not_done) { x = sctp_select_initial_TSN(&m->sctp_ep); if (x == 0) { /* we never use 0 */ continue; } if (sctp_is_vtag_good(m, x, &now)) { not_done = 0; } } return (x); } int sctp_init_asoc(struct sctp_inpcb *m, struct sctp_association *asoc, int for_a_init, uint32_t override_tag, uint32_t vrf_id) { /* * Anything set to zero is taken care of by the allocation routine's * bzero */ /* * Up front select what scoping to apply on addresses I tell my peer * Not sure what to do with these right now, we will need to come up * with a way to set them. We may need to pass them through from the * caller in the sctp_aloc_assoc() function. */ int i; /* init all variables to a known value. */ asoc->state = SCTP_STATE_INUSE; asoc->max_burst = m->sctp_ep.max_burst; asoc->heart_beat_delay = TICKS_TO_MSEC(m->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]); asoc->cookie_life = m->sctp_ep.def_cookie_life; asoc->sctp_cmt_on_off = (uint8_t) sctp_cmt_on_off; #ifdef INET asoc->default_tos = m->ip_inp.inp.inp_ip_tos; #else asoc->default_tos = 0; #endif #ifdef INET6 asoc->default_flowlabel = ((struct in6pcb *)m)->in6p_flowinfo; #else asoc->default_flowlabel = 0; #endif if (override_tag) { struct timeval now; - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); if (sctp_is_vtag_good(m, override_tag, &now)) { asoc->my_vtag = override_tag; } else { return (ENOMEM); } } else { asoc->my_vtag = sctp_select_a_tag(m); } /* Get the nonce tags */ asoc->my_vtag_nonce = sctp_select_a_tag(m); asoc->peer_vtag_nonce = sctp_select_a_tag(m); asoc->vrf_id = vrf_id; if (sctp_is_feature_on(m, SCTP_PCB_FLAGS_DONOT_HEARTBEAT)) asoc->hb_is_disabled = 1; else asoc->hb_is_disabled = 0; asoc->refcnt = 0; asoc->assoc_up_sent = 0; asoc->assoc_id = asoc->my_vtag; asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number = asoc->sending_seq = sctp_select_initial_TSN(&m->sctp_ep); /* we are optimisitic here */ asoc->peer_supports_pktdrop = 1; asoc->sent_queue_retran_cnt = 0; /* for CMT */ asoc->last_net_data_came_from = NULL; /* This will need to be adjusted */ asoc->last_cwr_tsn = asoc->init_seq_number - 1; asoc->last_acked_seq = asoc->init_seq_number - 1; asoc->advanced_peer_ack_point = asoc->last_acked_seq; asoc->asconf_seq_in = asoc->last_acked_seq; /* here we are different, we hold the next one we expect */ asoc->str_reset_seq_in = asoc->last_acked_seq + 1; asoc->initial_init_rto_max = m->sctp_ep.initial_init_rto_max; asoc->initial_rto = m->sctp_ep.initial_rto; asoc->max_init_times = m->sctp_ep.max_init_times; asoc->max_send_times = m->sctp_ep.max_send_times; asoc->def_net_failure = m->sctp_ep.def_net_failure; asoc->free_chunk_cnt = 0; asoc->iam_blocking = 0; /* ECN Nonce initialization */ asoc->context = m->sctp_context; asoc->def_send = m->def_send; asoc->ecn_nonce_allowed = 0; asoc->receiver_nonce_sum = 1; asoc->nonce_sum_expect_base = 1; asoc->nonce_sum_check = 1; asoc->nonce_resync_tsn = 0; asoc->nonce_wait_for_ecne = 0; asoc->nonce_wait_tsn = 0; asoc->delayed_ack = TICKS_TO_MSEC(m->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]); asoc->sack_freq = m->sctp_ep.sctp_sack_freq; asoc->pr_sctp_cnt = 0; asoc->total_output_queue_size = 0; if (m->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { struct in6pcb *inp6; /* Its a V6 socket */ inp6 = (struct in6pcb *)m; asoc->ipv6_addr_legal = 1; /* Now look at the binding flag to see if V4 will be legal */ if (SCTP_IPV6_V6ONLY(inp6) == 0) { asoc->ipv4_addr_legal = 1; } else { /* V4 addresses are NOT legal on the association */ asoc->ipv4_addr_legal = 0; } } else { /* Its a V4 socket, no - V6 */ asoc->ipv4_addr_legal = 1; asoc->ipv6_addr_legal = 0; } asoc->my_rwnd = max(SCTP_SB_LIMIT_RCV(m->sctp_socket), SCTP_MINIMAL_RWND); asoc->peers_rwnd = SCTP_SB_LIMIT_RCV(m->sctp_socket); asoc->smallest_mtu = m->sctp_frag_point; #ifdef SCTP_PRINT_FOR_B_AND_M printf("smallest_mtu init'd with asoc to :%d\n", asoc->smallest_mtu); #endif asoc->minrto = m->sctp_ep.sctp_minrto; asoc->maxrto = m->sctp_ep.sctp_maxrto; asoc->locked_on_sending = NULL; asoc->stream_locked_on = 0; asoc->ecn_echo_cnt_onq = 0; asoc->stream_locked = 0; asoc->send_sack = 1; LIST_INIT(&asoc->sctp_restricted_addrs); TAILQ_INIT(&asoc->nets); TAILQ_INIT(&asoc->pending_reply_queue); asoc->last_asconf_ack_sent = NULL; /* Setup to fill the hb random cache at first HB */ asoc->hb_random_idx = 4; asoc->sctp_autoclose_ticks = m->sctp_ep.auto_close_time; /* * Now the stream parameters, here we allocate space for all streams * that we request by default. */ asoc->streamoutcnt = asoc->pre_open_streams = m->sctp_ep.pre_open_stream_count; SCTP_MALLOC(asoc->strmout, struct sctp_stream_out *, asoc->streamoutcnt * sizeof(struct sctp_stream_out), "StreamsOut"); if (asoc->strmout == NULL) { /* big trouble no memory */ return (ENOMEM); } for (i = 0; i < asoc->streamoutcnt; i++) { /* * inbound side must be set to 0xffff, also NOTE when we get * the INIT-ACK back (for INIT sender) we MUST reduce the * count (streamoutcnt) but first check if we sent to any of * the upper streams that were dropped (if some were). Those * that were dropped must be notified to the upper layer as * failed to send. */ asoc->strmout[i].next_sequence_sent = 0x0; TAILQ_INIT(&asoc->strmout[i].outqueue); asoc->strmout[i].stream_no = i; asoc->strmout[i].last_msg_incomplete = 0; asoc->strmout[i].next_spoke.tqe_next = 0; asoc->strmout[i].next_spoke.tqe_prev = 0; } /* Now the mapping array */ asoc->mapping_array_size = SCTP_INITIAL_MAPPING_ARRAY; SCTP_MALLOC(asoc->mapping_array, uint8_t *, asoc->mapping_array_size, "MappingArray"); if (asoc->mapping_array == NULL) { SCTP_FREE(asoc->strmout); return (ENOMEM); } memset(asoc->mapping_array, 0, asoc->mapping_array_size); /* Now the init of the other outqueues */ TAILQ_INIT(&asoc->free_chunks); TAILQ_INIT(&asoc->out_wheel); TAILQ_INIT(&asoc->control_send_queue); TAILQ_INIT(&asoc->send_queue); TAILQ_INIT(&asoc->sent_queue); TAILQ_INIT(&asoc->reasmqueue); TAILQ_INIT(&asoc->resetHead); asoc->max_inbound_streams = m->sctp_ep.max_open_streams_intome; TAILQ_INIT(&asoc->asconf_queue); /* authentication fields */ asoc->authinfo.random = NULL; asoc->authinfo.assoc_key = NULL; asoc->authinfo.assoc_keyid = 0; asoc->authinfo.recv_key = NULL; asoc->authinfo.recv_keyid = 0; LIST_INIT(&asoc->shared_keys); asoc->marked_retrans = 0; asoc->timoinit = 0; asoc->timodata = 0; asoc->timosack = 0; asoc->timoshutdown = 0; asoc->timoheartbeat = 0; asoc->timocookie = 0; asoc->timoshutdownack = 0; - SCTP_GETTIME_TIMEVAL(&asoc->start_time); - SCTP_GETTIME_TIMEVAL(&asoc->discontinuity_time); - + (void)SCTP_GETTIME_TIMEVAL(&asoc->start_time); + asoc->discontinuity_time = asoc->start_time; return (0); } int sctp_expand_mapping_array(struct sctp_association *asoc) { /* mapping array needs to grow */ uint8_t *new_array; uint16_t new_size; new_size = asoc->mapping_array_size + SCTP_MAPPING_ARRAY_INCR; SCTP_MALLOC(new_array, uint8_t *, new_size, "MappingArray"); if (new_array == NULL) { /* can't get more, forget it */ printf("No memory for expansion of SCTP mapping array %d\n", new_size); return (-1); } memset(new_array, 0, new_size); memcpy(new_array, asoc->mapping_array, asoc->mapping_array_size); SCTP_FREE(asoc->mapping_array); asoc->mapping_array = new_array; asoc->mapping_array_size = new_size; return (0); } #if defined(SCTP_USE_THREAD_BASED_ITERATOR) static void sctp_iterator_work(struct sctp_iterator *it) { int iteration_count = 0; int inp_skip = 0; SCTP_ITERATOR_LOCK(); if (it->inp) SCTP_INP_DECR_REF(it->inp); if (it->inp == NULL) { /* iterator is complete */ done_with_iterator: SCTP_ITERATOR_UNLOCK(); if (it->function_atend != NULL) { (*it->function_atend) (it->pointer, it->val); } SCTP_FREE(it); return; } select_a_new_ep: SCTP_INP_WLOCK(it->inp); while (((it->pcb_flags) && ((it->inp->sctp_flags & it->pcb_flags) != it->pcb_flags)) || ((it->pcb_features) && ((it->inp->sctp_features & it->pcb_features) != it->pcb_features))) { /* endpoint flags or features don't match, so keep looking */ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { SCTP_INP_WUNLOCK(it->inp); goto done_with_iterator; } SCTP_INP_WUNLOCK(it->inp); it->inp = LIST_NEXT(it->inp, sctp_list); if (it->inp == NULL) { goto done_with_iterator; } SCTP_INP_WLOCK(it->inp); } /* mark the current iterator on the endpoint */ it->inp->inp_starting_point_for_iterator = it; SCTP_INP_WUNLOCK(it->inp); SCTP_INP_RLOCK(it->inp); /* now go through each assoc which is in the desired state */ if (it->done_current_ep == 0) { if (it->function_inp != NULL) inp_skip = (*it->function_inp) (it->inp, it->pointer, it->val); it->done_current_ep = 1; } if (it->stcb == NULL) { /* run the per instance function */ it->stcb = LIST_FIRST(&it->inp->sctp_asoc_list); } if ((inp_skip) || it->stcb == NULL) { if (it->function_inp_end != NULL) { inp_skip = (*it->function_inp_end) (it->inp, it->pointer, it->val); } SCTP_INP_RUNLOCK(it->inp); goto no_stcb; } if ((it->stcb) && (it->stcb->asoc.stcb_starting_point_for_iterator == it)) { it->stcb->asoc.stcb_starting_point_for_iterator = NULL; } while (it->stcb) { SCTP_TCB_LOCK(it->stcb); if (it->asoc_state && ((it->stcb->asoc.state & it->asoc_state) != it->asoc_state)) { /* not in the right state... keep looking */ SCTP_TCB_UNLOCK(it->stcb); goto next_assoc; } /* mark the current iterator on the assoc */ it->stcb->asoc.stcb_starting_point_for_iterator = it; /* see if we have limited out the iterator loop */ iteration_count++; if (iteration_count > SCTP_ITERATOR_MAX_AT_ONCE) { /* Pause to let others grab the lock */ atomic_add_int(&it->stcb->asoc.refcnt, 1); SCTP_TCB_UNLOCK(it->stcb); SCTP_INP_RUNLOCK(it->inp); SCTP_ITERATOR_UNLOCK(); SCTP_ITERATOR_LOCK(); SCTP_INP_RLOCK(it->inp); SCTP_TCB_LOCK(it->stcb); atomic_add_int(&it->stcb->asoc.refcnt, -1); iteration_count = 0; } /* run function on this one */ (*it->function_assoc) (it->inp, it->stcb, it->pointer, it->val); /* * we lie here, it really needs to have its own type but * first I must verify that this won't effect things :-0 */ if (it->no_chunk_output == 0) sctp_chunk_output(it->inp, it->stcb, SCTP_OUTPUT_FROM_T3); SCTP_TCB_UNLOCK(it->stcb); next_assoc: it->stcb = LIST_NEXT(it->stcb, sctp_tcblist); if (it->stcb == NULL) { /* Run last function */ if (it->function_inp_end != NULL) { inp_skip = (*it->function_inp_end) (it->inp, it->pointer, it->val); } } } SCTP_INP_RUNLOCK(it->inp); no_stcb: /* done with all assocs on this endpoint, move on to next endpoint */ it->done_current_ep = 0; SCTP_INP_WLOCK(it->inp); it->inp->inp_starting_point_for_iterator = NULL; SCTP_INP_WUNLOCK(it->inp); if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { it->inp = NULL; } else { SCTP_INP_INFO_RLOCK(); it->inp = LIST_NEXT(it->inp, sctp_list); SCTP_INP_INFO_RUNLOCK(); } if (it->inp == NULL) { goto done_with_iterator; } goto select_a_new_ep; } void sctp_iterator_worker(void) { struct sctp_iterator *it = NULL; /* This function is called with the WQ lock in place */ sctppcbinfo.iterator_running = 1; again: it = TAILQ_FIRST(&sctppcbinfo.iteratorhead); while (it) { /* now lets work on this one */ TAILQ_REMOVE(&sctppcbinfo.iteratorhead, it, sctp_nxt_itr); SCTP_IPI_ITERATOR_WQ_UNLOCK(); sctp_iterator_work(it); SCTP_IPI_ITERATOR_WQ_LOCK(); it = TAILQ_FIRST(&sctppcbinfo.iteratorhead); } if (TAILQ_FIRST(&sctppcbinfo.iteratorhead)) { goto again; } sctppcbinfo.iterator_running = 0; return; } #endif static void sctp_handle_addr_wq(void) { /* deal with the ADDR wq from the rtsock calls */ struct sctp_laddr *wi; struct sctp_asconf_iterator *asc; SCTP_MALLOC(asc, struct sctp_asconf_iterator *, sizeof(struct sctp_asconf_iterator), "SCTP_ASCONF_ITERATOR"); if (asc == NULL) { /* Try later, no memory */ sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ, (struct sctp_inpcb *)NULL, (struct sctp_tcb *)NULL, (struct sctp_nets *)NULL); return; } LIST_INIT(&asc->list_of_work); asc->cnt = 0; SCTP_IPI_ITERATOR_WQ_LOCK(); wi = LIST_FIRST(&sctppcbinfo.addr_wq); while (wi != NULL) { LIST_REMOVE(wi, sctp_nxt_addr); LIST_INSERT_HEAD(&asc->list_of_work, wi, sctp_nxt_addr); asc->cnt++; wi = LIST_FIRST(&sctppcbinfo.addr_wq); } SCTP_IPI_ITERATOR_WQ_UNLOCK(); if (asc->cnt == 0) { SCTP_FREE(asc); } else { sctp_initiate_iterator(sctp_iterator_ep, sctp_iterator_stcb, NULL, /* No ep end for boundall */ SCTP_PCB_FLAGS_BOUNDALL, SCTP_PCB_ANY_FEATURES, SCTP_ASOC_ANY_STATE, (void *)asc, 0, sctp_iterator_end, NULL, 0); } } void sctp_timeout_handler(void *t) { struct sctp_inpcb *inp; struct sctp_tcb *stcb; struct sctp_nets *net; struct sctp_timer *tmr; int did_output; struct sctp_iterator *it = NULL; tmr = (struct sctp_timer *)t; inp = (struct sctp_inpcb *)tmr->ep; stcb = (struct sctp_tcb *)tmr->tcb; net = (struct sctp_nets *)tmr->net; did_output = 1; #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xF0, (uint8_t) tmr->type); sctp_auditing(3, inp, stcb, net); #endif /* sanity checks... */ if (tmr->self != (void *)tmr) { /* * printf("Stale SCTP timer fired (%p), ignoring...\n", * tmr); */ return; } tmr->stopped_from = 0xa001; if (!SCTP_IS_TIMER_TYPE_VALID(tmr->type)) { /* * printf("SCTP timer fired with invalid type: 0x%x\n", * tmr->type); */ return; } tmr->stopped_from = 0xa002; if ((tmr->type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL)) { return; } /* if this is an iterator timeout, get the struct and clear inp */ tmr->stopped_from = 0xa003; if (tmr->type == SCTP_TIMER_TYPE_ITERATOR) { it = (struct sctp_iterator *)inp; inp = NULL; } if (inp) { SCTP_INP_INCR_REF(inp); if ((inp->sctp_socket == 0) && ((tmr->type != SCTP_TIMER_TYPE_INPKILL) && (tmr->type != SCTP_TIMER_TYPE_SHUTDOWN) && (tmr->type != SCTP_TIMER_TYPE_SHUTDOWNACK) && (tmr->type != SCTP_TIMER_TYPE_SHUTDOWNGUARD) && (tmr->type != SCTP_TIMER_TYPE_ASOCKILL)) ) { SCTP_INP_DECR_REF(inp); return; } } tmr->stopped_from = 0xa004; if (stcb) { atomic_add_int(&stcb->asoc.refcnt, 1); if (stcb->asoc.state == 0) { atomic_add_int(&stcb->asoc.refcnt, -1); if (inp) { SCTP_INP_DECR_REF(inp); } return; } } tmr->stopped_from = 0xa005; #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("Timer type %d goes off\n", tmr->type); } #endif /* SCTP_DEBUG */ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) { if (inp) { SCTP_INP_DECR_REF(inp); } return; } tmr->stopped_from = 0xa006; if (stcb) { SCTP_TCB_LOCK(stcb); atomic_add_int(&stcb->asoc.refcnt, -1); } /* record in stopped what t-o occured */ tmr->stopped_from = tmr->type; /* mark as being serviced now */ if (SCTP_OS_TIMER_PENDING(&tmr->timer)) { /* * Callout has been rescheduled. */ goto get_out; } if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) { /* * Not active, so no action. */ goto get_out; } SCTP_OS_TIMER_DEACTIVATE(&tmr->timer); /* call the handler for the appropriate timer type */ switch (tmr->type) { case SCTP_TIMER_TYPE_ADDR_WQ: sctp_handle_addr_wq(); break; case SCTP_TIMER_TYPE_ITERATOR: SCTP_STAT_INCR(sctps_timoiterator); sctp_iterator_timer(it); break; case SCTP_TIMER_TYPE_SEND: SCTP_STAT_INCR(sctps_timodata); stcb->asoc.timodata++; stcb->asoc.num_send_timers_up--; if (stcb->asoc.num_send_timers_up < 0) { stcb->asoc.num_send_timers_up = 0; } if (sctp_t3rxt_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3); if ((stcb->asoc.num_send_timers_up == 0) && (stcb->asoc.sent_queue_cnt > 0) ) { struct sctp_tmit_chunk *chk; /* * safeguard. If there on some on the sent queue * somewhere but no timers running something is * wrong... so we start a timer on the first chunk * on the send queue on whatever net it is sent to. */ chk = TAILQ_FIRST(&stcb->asoc.sent_queue); sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, chk->whoTo); } break; case SCTP_TIMER_TYPE_INIT: SCTP_STAT_INCR(sctps_timoinit); stcb->asoc.timoinit++; if (sctp_t1init_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } /* We do output but not here */ did_output = 0; break; case SCTP_TIMER_TYPE_RECV: SCTP_STAT_INCR(sctps_timosack); stcb->asoc.timosack++; sctp_send_sack(stcb); #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SACK_TMR); break; case SCTP_TIMER_TYPE_SHUTDOWN: if (sctp_shutdown_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } SCTP_STAT_INCR(sctps_timoshutdown); stcb->asoc.timoshutdown++; #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_TMR); break; case SCTP_TIMER_TYPE_HEARTBEAT: { struct sctp_nets *net; int cnt_of_unconf = 0; SCTP_STAT_INCR(sctps_timoheartbeat); stcb->asoc.timoheartbeat++; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) && (net->dest_state & SCTP_ADDR_REACHABLE)) { cnt_of_unconf++; } } if (cnt_of_unconf == 0) { if (sctp_heartbeat_timer(inp, stcb, net, cnt_of_unconf)) { /* no need to unlock on tcb its gone */ goto out_decr; } } #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, stcb, net); sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_HB_TMR); } break; case SCTP_TIMER_TYPE_COOKIE: if (sctp_cookie_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } SCTP_STAT_INCR(sctps_timocookie); stcb->asoc.timocookie++; #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif /* * We consider T3 and Cookie timer pretty much the same with * respect to where from in chunk_output. */ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3); break; case SCTP_TIMER_TYPE_NEWCOOKIE: { struct timeval tv; int i, secret; SCTP_STAT_INCR(sctps_timosecret); - SCTP_GETTIME_TIMEVAL(&tv); + (void)SCTP_GETTIME_TIMEVAL(&tv); SCTP_INP_WLOCK(inp); inp->sctp_ep.time_of_secret_change = tv.tv_sec; inp->sctp_ep.last_secret_number = inp->sctp_ep.current_secret_number; inp->sctp_ep.current_secret_number++; if (inp->sctp_ep.current_secret_number >= SCTP_HOW_MANY_SECRETS) { inp->sctp_ep.current_secret_number = 0; } secret = (int)inp->sctp_ep.current_secret_number; for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) { inp->sctp_ep.secret_key[secret][i] = sctp_select_initial_TSN(&inp->sctp_ep); } SCTP_INP_WUNLOCK(inp); sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, stcb, net); } did_output = 0; break; case SCTP_TIMER_TYPE_PATHMTURAISE: SCTP_STAT_INCR(sctps_timopathmtu); sctp_pathmtu_timer(inp, stcb, net); did_output = 0; break; case SCTP_TIMER_TYPE_SHUTDOWNACK: if (sctp_shutdownack_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } SCTP_STAT_INCR(sctps_timoshutdownack); stcb->asoc.timoshutdownack++; #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_ACK_TMR); break; case SCTP_TIMER_TYPE_SHUTDOWNGUARD: SCTP_STAT_INCR(sctps_timoshutdownguard); sctp_abort_an_association(inp, stcb, SCTP_SHUTDOWN_GUARD_EXPIRES, NULL); /* no need to unlock on tcb its gone */ goto out_decr; break; case SCTP_TIMER_TYPE_STRRESET: if (sctp_strreset_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } SCTP_STAT_INCR(sctps_timostrmrst); sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_TMR); break; case SCTP_TIMER_TYPE_EARLYFR: /* Need to do FR of things for net */ SCTP_STAT_INCR(sctps_timoearlyfr); sctp_early_fr_timer(inp, stcb, net); break; case SCTP_TIMER_TYPE_ASCONF: if (sctp_asconf_timer(inp, stcb, net)) { /* no need to unlock on tcb its gone */ goto out_decr; } SCTP_STAT_INCR(sctps_timoasconf); #ifdef SCTP_AUDITING_ENABLED sctp_auditing(4, inp, stcb, net); #endif sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_ASCONF_TMR); break; case SCTP_TIMER_TYPE_AUTOCLOSE: SCTP_STAT_INCR(sctps_timoautoclose); sctp_autoclose_timer(inp, stcb, net); sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR); did_output = 0; break; case SCTP_TIMER_TYPE_ASOCKILL: SCTP_STAT_INCR(sctps_timoassockill); /* Can we free it yet? */ SCTP_INP_DECR_REF(inp); sctp_timer_stop(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL, SCTP_FROM_SCTPUTIL + SCTP_LOC_1); sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTPUTIL + SCTP_LOC_2); /* * free asoc, always unlocks (or destroy's) so prevent * duplicate unlock or unlock of a free mtx :-0 */ stcb = NULL; goto out_no_decr; break; case SCTP_TIMER_TYPE_INPKILL: SCTP_STAT_INCR(sctps_timoinpkill); /* * special case, take away our increment since WE are the * killer */ SCTP_INP_DECR_REF(inp); sctp_timer_stop(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL, SCTP_FROM_SCTPUTIL + SCTP_LOC_3); sctp_inpcb_free(inp, 1, 0); goto out_no_decr; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("sctp_timeout_handler:unknown timer %d\n", tmr->type); } #endif /* SCTP_DEBUG */ break; }; #ifdef SCTP_AUDITING_ENABLED sctp_audit_log(0xF1, (uint8_t) tmr->type); if (inp) sctp_auditing(5, inp, stcb, net); #endif if ((did_output) && stcb) { /* * Now we need to clean up the control chunk chain if an * ECNE is on it. It must be marked as UNSENT again so next * call will continue to send it until such time that we get * a CWR, to remove it. It is, however, less likely that we * will find a ecn echo on the chain though. */ sctp_fix_ecn_echo(&stcb->asoc); } get_out: if (stcb) { SCTP_TCB_UNLOCK(stcb); } out_decr: if (inp) { SCTP_INP_DECR_REF(inp); } out_no_decr: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("Timer now complete (type %d)\n", tmr->type); } #endif /* SCTP_DEBUG */ if (inp) { } } int sctp_timer_start(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net) { int to_ticks; struct sctp_timer *tmr; if ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL)) return (EFAULT); to_ticks = 0; tmr = NULL; if (stcb) { SCTP_TCB_LOCK_ASSERT(stcb); } switch (t_type) { case SCTP_TIMER_TYPE_ADDR_WQ: /* Only 1 tick away :-) */ tmr = &sctppcbinfo.addr_wq_timer; to_ticks = SCTP_ADDRESS_TICK_DELAY; break; case SCTP_TIMER_TYPE_ITERATOR: { struct sctp_iterator *it; it = (struct sctp_iterator *)inp; tmr = &it->tmr; to_ticks = SCTP_ITERATOR_TICKS; } break; case SCTP_TIMER_TYPE_SEND: /* Here we use the RTO timer */ { int rto_val; if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } tmr = &net->rxt_timer; if (net->RTO == 0) { rto_val = stcb->asoc.initial_rto; } else { rto_val = net->RTO; } to_ticks = MSEC_TO_TICKS(rto_val); } break; case SCTP_TIMER_TYPE_INIT: /* * Here we use the INIT timer default usually about 1 * minute. */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } tmr = &net->rxt_timer; if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } break; case SCTP_TIMER_TYPE_RECV: /* * Here we use the Delayed-Ack timer value from the inp * ususually about 200ms. */ if (stcb == NULL) { return (EFAULT); } tmr = &stcb->asoc.dack_timer; to_ticks = MSEC_TO_TICKS(stcb->asoc.delayed_ack); break; case SCTP_TIMER_TYPE_SHUTDOWN: /* Here we use the RTO of the destination. */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_HEARTBEAT: /* * the net is used here so that we can add in the RTO. Even * though we use a different timer. We also add the HB timer * PLUS a random jitter. */ if (stcb == NULL) { return (EFAULT); } { uint32_t rndval; uint8_t this_random; int cnt_of_unconf = 0; struct sctp_nets *lnet; TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { if ((lnet->dest_state & SCTP_ADDR_UNCONFIRMED) && (lnet->dest_state & SCTP_ADDR_REACHABLE)) { cnt_of_unconf++; } } if (cnt_of_unconf) { lnet = NULL; sctp_heartbeat_timer(inp, stcb, lnet, cnt_of_unconf); } if (stcb->asoc.hb_random_idx > 3) { rndval = sctp_select_initial_TSN(&inp->sctp_ep); memcpy(stcb->asoc.hb_random_values, &rndval, sizeof(stcb->asoc.hb_random_values)); stcb->asoc.hb_random_idx = 0; } this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx]; stcb->asoc.hb_random_idx++; stcb->asoc.hb_ect_randombit = 0; /* * this_random will be 0 - 256 ms RTO is in ms. */ if ((stcb->asoc.hb_is_disabled) && (cnt_of_unconf == 0)) { return (0); } if (net) { struct sctp_nets *lnet; int delay; delay = stcb->asoc.heart_beat_delay; TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { if ((lnet->dest_state & SCTP_ADDR_UNCONFIRMED) && ((lnet->dest_state & SCTP_ADDR_OUT_OF_SCOPE) == 0) && (lnet->dest_state & SCTP_ADDR_REACHABLE)) { delay = 0; } } if (net->RTO == 0) { /* Never been checked */ to_ticks = this_random + stcb->asoc.initial_rto + delay; } else { /* set rto_val to the ms */ to_ticks = delay + net->RTO + this_random; } } else { if (cnt_of_unconf) { to_ticks = this_random + stcb->asoc.initial_rto; } else { to_ticks = stcb->asoc.heart_beat_delay + this_random + stcb->asoc.initial_rto; } } /* * Now we must convert the to_ticks that are now in * ms to ticks. */ to_ticks = MSEC_TO_TICKS(to_ticks); tmr = &stcb->asoc.hb_timer; } break; case SCTP_TIMER_TYPE_COOKIE: /* * Here we can use the RTO timer from the network since one * RTT was compelete. If a retran happened then we will be * using the RTO initial value. */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_NEWCOOKIE: /* * nothing needed but the endpoint here ususually about 60 * minutes. */ tmr = &inp->sctp_ep.signature_change; to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_SIGNATURE]; break; case SCTP_TIMER_TYPE_ASOCKILL: if (stcb == NULL) { return (EFAULT); } tmr = &stcb->asoc.strreset_timer; to_ticks = MSEC_TO_TICKS(SCTP_ASOC_KILL_TIMEOUT); break; case SCTP_TIMER_TYPE_INPKILL: /* * The inp is setup to die. We re-use the signature_chage * timer since that has stopped and we are in the GONE * state. */ tmr = &inp->sctp_ep.signature_change; to_ticks = MSEC_TO_TICKS(SCTP_INP_KILL_TIMEOUT); break; case SCTP_TIMER_TYPE_PATHMTURAISE: /* * Here we use the value found in the EP for PMTU ususually * about 10 minutes. */ if (stcb == NULL) { return (EFAULT); } if (net == NULL) { return (EFAULT); } to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_PMTU]; tmr = &net->pmtu_timer; break; case SCTP_TIMER_TYPE_SHUTDOWNACK: /* Here we use the RTO of the destination */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_SHUTDOWNGUARD: /* * Here we use the endpoints shutdown guard timer usually * about 3 minutes. */ if (stcb == NULL) { return (EFAULT); } to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN]; tmr = &stcb->asoc.shut_guard_timer; break; case SCTP_TIMER_TYPE_STRRESET: /* * Here the timer comes from the inp but its value is from * the RTO. */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } tmr = &stcb->asoc.strreset_timer; break; case SCTP_TIMER_TYPE_EARLYFR: { unsigned int msec; if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->flight_size > net->cwnd) { /* no need to start */ return (0); } SCTP_STAT_INCR(sctps_earlyfrstart); if (net->lastsa == 0) { /* Hmm no rtt estimate yet? */ msec = stcb->asoc.initial_rto >> 2; } else { msec = ((net->lastsa >> 2) + net->lastsv) >> 1; } if (msec < sctp_early_fr_msec) { msec = sctp_early_fr_msec; if (msec < SCTP_MINFR_MSEC_FLOOR) { msec = SCTP_MINFR_MSEC_FLOOR; } } to_ticks = MSEC_TO_TICKS(msec); tmr = &net->fr_timer; } break; case SCTP_TIMER_TYPE_ASCONF: /* * Here the timer comes from the inp but its value is from * the RTO. */ if ((stcb == NULL) || (net == NULL)) { return (EFAULT); } if (net->RTO == 0) { to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); } else { to_ticks = MSEC_TO_TICKS(net->RTO); } tmr = &stcb->asoc.asconf_timer; break; case SCTP_TIMER_TYPE_AUTOCLOSE: if (stcb == NULL) { return (EFAULT); } if (stcb->asoc.sctp_autoclose_ticks == 0) { /* * Really an error since stcb is NOT set to * autoclose */ return (0); } to_ticks = stcb->asoc.sctp_autoclose_ticks; tmr = &stcb->asoc.autoclose_timer; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("sctp_timer_start:Unknown timer type %d\n", t_type); } #endif /* SCTP_DEBUG */ return (EFAULT); break; }; if ((to_ticks <= 0) || (tmr == NULL)) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("sctp_timer_start:%d:software error to_ticks:%d tmr:%p not set ??\n", t_type, to_ticks, tmr); } #endif /* SCTP_DEBUG */ return (EFAULT); } if (SCTP_OS_TIMER_PENDING(&tmr->timer)) { /* * we do NOT allow you to have it already running. if it is * we leave the current one up unchanged */ return (EALREADY); } /* At this point we can proceed */ if (t_type == SCTP_TIMER_TYPE_SEND) { stcb->asoc.num_send_timers_up++; } tmr->stopped_from = 0; tmr->type = t_type; tmr->ep = (void *)inp; tmr->tcb = (void *)stcb; tmr->net = (void *)net; tmr->self = (void *)tmr; tmr->ticks = ticks; SCTP_OS_TIMER_START(&tmr->timer, to_ticks, sctp_timeout_handler, tmr); return (0); } -int +void sctp_timer_stop(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t from) { struct sctp_timer *tmr; if ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL)) - return (EFAULT); + return; tmr = NULL; if (stcb) { SCTP_TCB_LOCK_ASSERT(stcb); } switch (t_type) { case SCTP_TIMER_TYPE_ADDR_WQ: tmr = &sctppcbinfo.addr_wq_timer; break; case SCTP_TIMER_TYPE_EARLYFR: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->fr_timer; SCTP_STAT_INCR(sctps_earlyfrstop); break; case SCTP_TIMER_TYPE_ITERATOR: { struct sctp_iterator *it; it = (struct sctp_iterator *)inp; tmr = &it->tmr; } break; case SCTP_TIMER_TYPE_SEND: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_INIT: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_RECV: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.dack_timer; break; case SCTP_TIMER_TYPE_SHUTDOWN: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_HEARTBEAT: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.hb_timer; break; case SCTP_TIMER_TYPE_COOKIE: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_NEWCOOKIE: /* nothing needed but the endpoint here */ tmr = &inp->sctp_ep.signature_change; /* * We re-use the newcookie timer for the INP kill timer. We * must assure that we do not kill it by accident. */ break; case SCTP_TIMER_TYPE_ASOCKILL: /* * Stop the asoc kill timer. */ if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.strreset_timer; break; case SCTP_TIMER_TYPE_INPKILL: /* * The inp is setup to die. We re-use the signature_chage * timer since that has stopped and we are in the GONE * state. */ tmr = &inp->sctp_ep.signature_change; break; case SCTP_TIMER_TYPE_PATHMTURAISE: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->pmtu_timer; break; case SCTP_TIMER_TYPE_SHUTDOWNACK: if ((stcb == NULL) || (net == NULL)) { - return (EFAULT); + return; } tmr = &net->rxt_timer; break; case SCTP_TIMER_TYPE_SHUTDOWNGUARD: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.shut_guard_timer; break; case SCTP_TIMER_TYPE_STRRESET: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.strreset_timer; break; case SCTP_TIMER_TYPE_ASCONF: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.asconf_timer; break; case SCTP_TIMER_TYPE_AUTOCLOSE: if (stcb == NULL) { - return (EFAULT); + return; } tmr = &stcb->asoc.autoclose_timer; break; default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_TIMER1) { printf("sctp_timer_stop:Unknown timer type %d\n", t_type); } #endif /* SCTP_DEBUG */ break; }; if (tmr == NULL) { - return (EFAULT); + return; } if ((tmr->type != t_type) && tmr->type) { /* * Ok we have a timer that is under joint use. Cookie timer * per chance with the SEND timer. We therefore are NOT * running the timer that the caller wants stopped. So just * return. */ - return (0); + return; } if (t_type == SCTP_TIMER_TYPE_SEND) { stcb->asoc.num_send_timers_up--; if (stcb->asoc.num_send_timers_up < 0) { stcb->asoc.num_send_timers_up = 0; } } tmr->self = NULL; tmr->stopped_from = from; - SCTP_OS_TIMER_STOP(&tmr->timer); - return (0); + (void)SCTP_OS_TIMER_STOP(&tmr->timer); + return; } #ifdef SCTP_USE_ADLER32 static uint32_t update_adler32(uint32_t adler, uint8_t * buf, int32_t len) { uint32_t s1 = adler & 0xffff; uint32_t s2 = (adler >> 16) & 0xffff; int n; for (n = 0; n < len; n++, buf++) { /* s1 = (s1 + buf[n]) % BASE */ /* first we add */ s1 = (s1 + *buf); /* * now if we need to, we do a mod by subtracting. It seems a * bit faster since I really will only ever do one subtract * at the MOST, since buf[n] is a max of 255. */ if (s1 >= SCTP_ADLER32_BASE) { s1 -= SCTP_ADLER32_BASE; } /* s2 = (s2 + s1) % BASE */ /* first we add */ s2 = (s2 + s1); /* * again, it is more efficent (it seems) to subtract since * the most s2 will ever be is (BASE-1 + BASE-1) in the * worse case. This would then be (2 * BASE) - 2, which will * still only do one subtract. On Intel this is much better * to do this way and avoid the divide. Have not -pg'd on * sparc. */ if (s2 >= SCTP_ADLER32_BASE) { s2 -= SCTP_ADLER32_BASE; } } /* Return the adler32 of the bytes buf[0..len-1] */ return ((s2 << 16) + s1); } #endif uint32_t sctp_calculate_len(struct mbuf *m) { uint32_t tlen = 0; struct mbuf *at; at = m; while (at) { tlen += SCTP_BUF_LEN(at); at = SCTP_BUF_NEXT(at); } return (tlen); } #if defined(SCTP_WITH_NO_CSUM) uint32_t sctp_calculate_sum(struct mbuf *m, int32_t * pktlen, uint32_t offset) { /* * given a mbuf chain with a packetheader offset by 'offset' * pointing at a sctphdr (with csum set to 0) go through the chain * of SCTP_BUF_NEXT()'s and calculate the SCTP checksum. This is * currently Adler32 but will change to CRC32x soon. Also has a side * bonus calculate the total length of the mbuf chain. Note: if * offset is greater than the total mbuf length, checksum=1, * pktlen=0 is returned (ie. no real error code) */ if (pktlen == NULL) return (0); *pktlen = sctp_calculate_len(m); return (0); } #elif defined(SCTP_USE_INCHKSUM) #include uint32_t sctp_calculate_sum(struct mbuf *m, int32_t * pktlen, uint32_t offset) { /* * given a mbuf chain with a packetheader offset by 'offset' * pointing at a sctphdr (with csum set to 0) go through the chain * of SCTP_BUF_NEXT()'s and calculate the SCTP checksum. This is * currently Adler32 but will change to CRC32x soon. Also has a side * bonus calculate the total length of the mbuf chain. Note: if * offset is greater than the total mbuf length, checksum=1, * pktlen=0 is returned (ie. no real error code) */ int32_t tlen = 0; struct mbuf *at; uint32_t the_sum, retsum; at = m; while (at) { tlen += SCTP_BUF_LEN(at); at = SCTP_BUF_NEXT(at); } the_sum = (uint32_t) (in_cksum_skip(m, tlen, offset)); if (pktlen != NULL) *pktlen = (tlen - offset); retsum = htons(the_sum); return (the_sum); } #else uint32_t sctp_calculate_sum(struct mbuf *m, int32_t * pktlen, uint32_t offset) { /* * given a mbuf chain with a packetheader offset by 'offset' * pointing at a sctphdr (with csum set to 0) go through the chain * of SCTP_BUF_NEXT()'s and calculate the SCTP checksum. This is * currently Adler32 but will change to CRC32x soon. Also has a side * bonus calculate the total length of the mbuf chain. Note: if * offset is greater than the total mbuf length, checksum=1, * pktlen=0 is returned (ie. no real error code) */ int32_t tlen = 0; #ifdef SCTP_USE_ADLER32 uint32_t base = 1L; #else uint32_t base = 0xffffffff; #endif /* SCTP_USE_ADLER32 */ struct mbuf *at; at = m; /* find the correct mbuf and offset into mbuf */ while ((at != NULL) && (offset > (uint32_t) SCTP_BUF_LEN(at))) { offset -= SCTP_BUF_LEN(at); /* update remaining offset * left */ at = SCTP_BUF_NEXT(at); } while (at != NULL) { if ((SCTP_BUF_LEN(at) - offset) > 0) { #ifdef SCTP_USE_ADLER32 base = update_adler32(base, (unsigned char *)(SCTP_BUF_AT(at, offset)), (unsigned int)(SCTP_BUF_LEN(at) - offset)); #else if ((SCTP_BUF_LEN(at) - offset) < 4) { /* Use old method if less than 4 bytes */ base = old_update_crc32(base, (unsigned char *)(SCTP_BUF_AT(at, offset)), (unsigned int)(SCTP_BUF_LEN(at) - offset)); } else { base = update_crc32(base, (unsigned char *)(SCTP_BUF_AT(at, offset)), (unsigned int)(SCTP_BUF_LEN(at) - offset)); } #endif /* SCTP_USE_ADLER32 */ tlen += SCTP_BUF_LEN(at) - offset; /* we only offset once into the first mbuf */ } if (offset) { if (offset < SCTP_BUF_LEN(at)) offset = 0; else offset -= SCTP_BUF_LEN(at); } at = SCTP_BUF_NEXT(at); } if (pktlen != NULL) { *pktlen = tlen; } #ifdef SCTP_USE_ADLER32 /* Adler32 */ base = htonl(base); #else /* CRC-32c */ base = sctp_csum_finalize(base); #endif return (base); } #endif void sctp_mtu_size_reset(struct sctp_inpcb *inp, struct sctp_association *asoc, uint32_t mtu) { /* * Reset the P-MTU size on this association, this involves changing * the asoc MTU, going through ANY chunk+overhead larger than mtu to * allow the DF flag to be cleared. */ struct sctp_tmit_chunk *chk; unsigned int eff_mtu, ovh; #ifdef SCTP_PRINT_FOR_B_AND_M printf("sctp_mtu_size_reset(%p, asoc:%p mtu:%d\n", inp, asoc, mtu); #endif asoc->smallest_mtu = mtu; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { ovh = SCTP_MIN_OVERHEAD; } else { ovh = SCTP_MIN_V4_OVERHEAD; } eff_mtu = mtu - ovh; TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { if (chk->send_size > eff_mtu) { chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; } } TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { if (chk->send_size > eff_mtu) { chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; } } } /* * given an association and starting time of the current RTT period return * RTO in number of msecs net should point to the current network */ uint32_t sctp_calculate_rto(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_nets *net, struct timeval *old) { /* * given an association and the starting time of the current RTT * period (in value1/value2) return RTO in number of msecs. */ int calc_time = 0; int o_calctime; uint32_t new_rto = 0; int first_measure = 0; struct timeval now; /************************/ /* 1. calculate new RTT */ /************************/ /* get the current time */ - SCTP_GETTIME_TIMEVAL(&now); + (void)SCTP_GETTIME_TIMEVAL(&now); /* compute the RTT value */ if ((u_long)now.tv_sec > (u_long)old->tv_sec) { calc_time = ((u_long)now.tv_sec - (u_long)old->tv_sec) * 1000; if ((u_long)now.tv_usec > (u_long)old->tv_usec) { calc_time += (((u_long)now.tv_usec - (u_long)old->tv_usec) / 1000); } else if ((u_long)now.tv_usec < (u_long)old->tv_usec) { /* Borrow 1,000ms from current calculation */ calc_time -= 1000; /* Add in the slop over */ calc_time += ((int)now.tv_usec / 1000); /* Add in the pre-second ms's */ calc_time += (((int)1000000 - (int)old->tv_usec) / 1000); } } else if ((u_long)now.tv_sec == (u_long)old->tv_sec) { if ((u_long)now.tv_usec > (u_long)old->tv_usec) { calc_time = ((u_long)now.tv_usec - (u_long)old->tv_usec) / 1000; } else if ((u_long)now.tv_usec < (u_long)old->tv_usec) { /* impossible .. garbage in nothing out */ goto calc_rto; } else if ((u_long)now.tv_usec == (u_long)old->tv_usec) { /* * We have to have 1 usec :-D this must be the * loopback. */ calc_time = 1; } else { /* impossible .. garbage in nothing out */ goto calc_rto; } } else { /* Clock wrapped? */ goto calc_rto; } /***************************/ /* 2. update RTTVAR & SRTT */ /***************************/ o_calctime = calc_time; /* this is Van Jacobson's integer version */ if (net->RTO) { calc_time -= (net->lastsa >> 3); #ifdef SCTP_RTTVAR_LOGGING rto_logging(net, SCTP_LOG_RTTVAR); #endif net->prev_rtt = o_calctime; net->lastsa += calc_time; if (calc_time < 0) { calc_time = -calc_time; } calc_time -= (net->lastsv >> 2); net->lastsv += calc_time; if (net->lastsv == 0) { net->lastsv = SCTP_CLOCK_GRANULARITY; } } else { /* First RTO measurment */ net->lastsa = calc_time; net->lastsv = calc_time >> 1; first_measure = 1; net->prev_rtt = o_calctime; #ifdef SCTP_RTTVAR_LOGGING rto_logging(net, SCTP_LOG_INITIAL_RTT); #endif } calc_rto: new_rto = ((net->lastsa >> 2) + net->lastsv) >> 1; if ((new_rto > SCTP_SAT_NETWORK_MIN) && (stcb->asoc.sat_network_lockout == 0)) { stcb->asoc.sat_network = 1; } else if ((!first_measure) && stcb->asoc.sat_network) { stcb->asoc.sat_network = 0; stcb->asoc.sat_network_lockout = 1; } /* bound it, per C6/C7 in Section 5.3.1 */ if (new_rto < stcb->asoc.minrto) { new_rto = stcb->asoc.minrto; } if (new_rto > stcb->asoc.maxrto) { new_rto = stcb->asoc.maxrto; } /* we are now returning the RTO */ return (new_rto); } /* * return a pointer to a contiguous piece of data from the given mbuf chain * starting at 'off' for 'len' bytes. If the desired piece spans more than * one mbuf, a copy is made at 'ptr'. caller must ensure that the buffer size * is >= 'len' returns NULL if there there isn't 'len' bytes in the chain. */ __inline caddr_t sctp_m_getptr(struct mbuf *m, int off, int len, uint8_t * in_ptr) { uint32_t count; uint8_t *ptr; ptr = in_ptr; if ((off < 0) || (len <= 0)) return (NULL); /* find the desired start location */ while ((m != NULL) && (off > 0)) { if (off < SCTP_BUF_LEN(m)) break; off -= SCTP_BUF_LEN(m); m = SCTP_BUF_NEXT(m); } if (m == NULL) return (NULL); /* is the current mbuf large enough (eg. contiguous)? */ if ((SCTP_BUF_LEN(m) - off) >= len) { return (mtod(m, caddr_t)+off); } else { /* else, it spans more than one mbuf, so save a temp copy... */ while ((m != NULL) && (len > 0)) { count = min(SCTP_BUF_LEN(m) - off, len); bcopy(mtod(m, caddr_t)+off, ptr, count); len -= count; ptr += count; off = 0; m = SCTP_BUF_NEXT(m); } if ((m == NULL) && (len > 0)) return (NULL); else return ((caddr_t)in_ptr); } } struct sctp_paramhdr * sctp_get_next_param(struct mbuf *m, int offset, struct sctp_paramhdr *pull, int pull_limit) { /* This just provides a typed signature to Peter's Pull routine */ return ((struct sctp_paramhdr *)sctp_m_getptr(m, offset, pull_limit, (uint8_t *) pull)); } int sctp_add_pad_tombuf(struct mbuf *m, int padlen) { /* * add padlen bytes of 0 filled padding to the end of the mbuf. If * padlen is > 3 this routine will fail. */ uint8_t *dp; int i; if (padlen > 3) { return (ENOBUFS); } if (M_TRAILINGSPACE(m)) { /* * The easy way. We hope the majority of the time we hit * here :) */ dp = (uint8_t *) (mtod(m, caddr_t)+SCTP_BUF_LEN(m)); SCTP_BUF_LEN(m) += padlen; } else { /* Hard way we must grow the mbuf */ struct mbuf *tmp; tmp = sctp_get_mbuf_for_msg(padlen, 0, M_DONTWAIT, 1, MT_DATA); if (tmp == NULL) { /* Out of space GAK! we are in big trouble. */ return (ENOSPC); } /* setup and insert in middle */ SCTP_BUF_NEXT(tmp) = SCTP_BUF_NEXT(m); SCTP_BUF_LEN(tmp) = padlen; SCTP_BUF_NEXT(m) = tmp; dp = mtod(tmp, uint8_t *); } /* zero out the pad */ for (i = 0; i < padlen; i++) { *dp = 0; dp++; } return (0); } int sctp_pad_lastmbuf(struct mbuf *m, int padval, struct mbuf *last_mbuf) { /* find the last mbuf in chain and pad it */ struct mbuf *m_at; m_at = m; if (last_mbuf) { return (sctp_add_pad_tombuf(last_mbuf, padval)); } else { while (m_at) { if (SCTP_BUF_NEXT(m_at) == NULL) { return (sctp_add_pad_tombuf(m_at, padval)); } m_at = SCTP_BUF_NEXT(m_at); } } return (EFAULT); } int sctp_asoc_change_wake = 0; static void sctp_notify_assoc_change(uint32_t event, struct sctp_tcb *stcb, uint32_t error, void *data) { struct mbuf *m_notify; struct sctp_assoc_change *sac; struct sctp_queued_to_read *control; /* * First if we are are going down dump everything we can to the * socket rcv queue. */ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) ) { /* If the socket is gone we are out of here */ return; } /* * For TCP model AND UDP connected sockets we will send an error up * when an ABORT comes in. */ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && ((event == SCTP_COMM_LOST) || (event == SCTP_SHUTDOWN_COMP))) { if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_WAIT) stcb->sctp_socket->so_error = ECONNREFUSED; else stcb->sctp_socket->so_error = ECONNRESET; /* Wake ANY sleepers */ sorwakeup(stcb->sctp_socket); sowwakeup(stcb->sctp_socket); sctp_asoc_change_wake++; } if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVASSOCEVNT)) { /* event not enabled */ return; } m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_assoc_change), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; sac = mtod(m_notify, struct sctp_assoc_change *); sac->sac_type = SCTP_ASSOC_CHANGE; sac->sac_flags = 0; sac->sac_length = sizeof(struct sctp_assoc_change); sac->sac_state = event; sac->sac_error = error; /* XXX verify these stream counts */ sac->sac_outbound_streams = stcb->asoc.streamoutcnt; sac->sac_inbound_streams = stcb->asoc.streamincnt; sac->sac_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_assoc_change); SCTP_BUF_NEXT(m_notify) = NULL; control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->length = SCTP_BUF_LEN(m_notify); /* not that we need this */ control->tail_mbuf = m_notify; control->spec_flags = M_NOTIFICATION; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); if (event == SCTP_COMM_LOST) { /* Wake up any sleeper */ sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); } } static void sctp_notify_peer_addr_change(struct sctp_tcb *stcb, uint32_t state, struct sockaddr *sa, uint32_t error) { struct mbuf *m_notify; struct sctp_paddr_change *spc; struct sctp_queued_to_read *control; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVPADDREVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_paddr_change), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) return; SCTP_BUF_LEN(m_notify) = 0; spc = mtod(m_notify, struct sctp_paddr_change *); spc->spc_type = SCTP_PEER_ADDR_CHANGE; spc->spc_flags = 0; spc->spc_length = sizeof(struct sctp_paddr_change); if (sa->sa_family == AF_INET) { memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in)); } else { struct sockaddr_in6 *sin6; memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in6)); sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr; if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) { if (sin6->sin6_scope_id == 0) { /* recover scope_id for user */ (void)sa6_recoverscope(sin6); } else { /* clear embedded scope_id for user */ in6_clearscope(&sin6->sin6_addr); } } } spc->spc_state = state; spc->spc_error = error; spc->spc_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_paddr_change); SCTP_BUF_NEXT(m_notify) = NULL; /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->length = SCTP_BUF_LEN(m_notify); control->spec_flags = M_NOTIFICATION; /* not that we need this */ control->tail_mbuf = m_notify; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } static void sctp_notify_send_failed(struct sctp_tcb *stcb, uint32_t error, struct sctp_tmit_chunk *chk) { struct mbuf *m_notify; struct sctp_send_failed *ssf; struct sctp_queued_to_read *control; int length; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSENDFAILEVNT)) /* event not enabled */ return; length = sizeof(struct sctp_send_failed) + chk->send_size; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_send_failed), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; ssf = mtod(m_notify, struct sctp_send_failed *); ssf->ssf_type = SCTP_SEND_FAILED; if (error == SCTP_NOTIFY_DATAGRAM_UNSENT) ssf->ssf_flags = SCTP_DATA_UNSENT; else ssf->ssf_flags = SCTP_DATA_SENT; ssf->ssf_length = length; ssf->ssf_error = error; /* not exactly what the user sent in, but should be close :) */ ssf->ssf_info.sinfo_stream = chk->rec.data.stream_number; ssf->ssf_info.sinfo_ssn = chk->rec.data.stream_seq; ssf->ssf_info.sinfo_flags = chk->rec.data.rcv_flags; ssf->ssf_info.sinfo_ppid = chk->rec.data.payloadtype; ssf->ssf_info.sinfo_context = chk->rec.data.context; ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb); ssf->ssf_assoc_id = sctp_get_associd(stcb); SCTP_BUF_NEXT(m_notify) = chk->data; SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_send_failed); /* Steal off the mbuf */ chk->data = NULL; /* * For this case, we check the actual socket buffer, since the assoc * is going away we don't want to overfill the socket buffer for a * non-reader */ if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) { sctp_m_freem(m_notify); return; } /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } static void sctp_notify_send_failed2(struct sctp_tcb *stcb, uint32_t error, struct sctp_stream_queue_pending *sp) { struct mbuf *m_notify; struct sctp_send_failed *ssf; struct sctp_queued_to_read *control; int length; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSENDFAILEVNT)) /* event not enabled */ return; length = sizeof(struct sctp_send_failed) + sp->length; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_adaption_event), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; ssf = mtod(m_notify, struct sctp_send_failed *); ssf->ssf_type = SCTP_SEND_FAILED; if (error == SCTP_NOTIFY_DATAGRAM_UNSENT) ssf->ssf_flags = SCTP_DATA_UNSENT; else ssf->ssf_flags = SCTP_DATA_SENT; ssf->ssf_length = length; ssf->ssf_error = error; /* not exactly what the user sent in, but should be close :) */ ssf->ssf_info.sinfo_stream = sp->stream; ssf->ssf_info.sinfo_ssn = sp->strseq; ssf->ssf_info.sinfo_flags = sp->sinfo_flags; ssf->ssf_info.sinfo_ppid = sp->ppid; ssf->ssf_info.sinfo_context = sp->context; ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb); ssf->ssf_assoc_id = sctp_get_associd(stcb); SCTP_BUF_NEXT(m_notify) = sp->data; SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_send_failed); /* Steal off the mbuf */ sp->data = NULL; /* * For this case, we check the actual socket buffer, since the assoc * is going away we don't want to overfill the socket buffer for a * non-reader */ if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) { sctp_m_freem(m_notify); return; } /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } static void sctp_notify_adaptation_layer(struct sctp_tcb *stcb, uint32_t error) { struct mbuf *m_notify; struct sctp_adaptation_event *sai; struct sctp_queued_to_read *control; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_ADAPTATIONEVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_adaption_event), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; sai = mtod(m_notify, struct sctp_adaptation_event *); sai->sai_type = SCTP_ADAPTATION_INDICATION; sai->sai_flags = 0; sai->sai_length = sizeof(struct sctp_adaptation_event); sai->sai_adaptation_ind = error; sai->sai_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_adaptation_event); SCTP_BUF_NEXT(m_notify) = NULL; /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->length = SCTP_BUF_LEN(m_notify); control->spec_flags = M_NOTIFICATION; /* not that we need this */ control->tail_mbuf = m_notify; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } /* This always must be called with the read-queue LOCKED in the INP */ void sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb, uint32_t error, int nolock, uint32_t val) { struct mbuf *m_notify; struct sctp_pdapi_event *pdapi; struct sctp_queued_to_read *control; struct sockbuf *sb; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_PDAPIEVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_pdapi_event), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; pdapi = mtod(m_notify, struct sctp_pdapi_event *); pdapi->pdapi_type = SCTP_PARTIAL_DELIVERY_EVENT; pdapi->pdapi_flags = 0; pdapi->pdapi_length = sizeof(struct sctp_pdapi_event); pdapi->pdapi_indication = error; pdapi->pdapi_stream = (val >> 16); pdapi->pdapi_seq = (val & 0x0000ffff); pdapi->pdapi_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_pdapi_event); SCTP_BUF_NEXT(m_notify) = NULL; control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; control->length = SCTP_BUF_LEN(m_notify); /* not that we need this */ control->tail_mbuf = m_notify; control->held_length = 0; control->length = 0; if (nolock == 0) { SCTP_INP_READ_LOCK(stcb->sctp_ep); } sb = &stcb->sctp_socket->so_rcv; #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m_notify)); #endif sctp_sballoc(stcb, sb, m_notify); #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif atomic_add_int(&control->length, SCTP_BUF_LEN(m_notify)); control->end_added = 1; if (stcb->asoc.control_pdapi) TAILQ_INSERT_AFTER(&stcb->sctp_ep->read_queue, stcb->asoc.control_pdapi, control, next); else { /* we really should not see this case */ TAILQ_INSERT_TAIL(&stcb->sctp_ep->read_queue, control, next); } if (nolock == 0) { SCTP_INP_READ_UNLOCK(stcb->sctp_ep); } if (stcb->sctp_ep && stcb->sctp_socket) { /* This should always be the case */ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); } } static void sctp_notify_shutdown_event(struct sctp_tcb *stcb) { struct mbuf *m_notify; struct sctp_shutdown_event *sse; struct sctp_queued_to_read *control; /* * For TCP model AND UDP connected sockets we will send an error up * when an SHUTDOWN completes */ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* mark socket closed for read/write and wakeup! */ socantsendmore(stcb->sctp_socket); } if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_event), 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; sse = mtod(m_notify, struct sctp_shutdown_event *); sse->sse_type = SCTP_SHUTDOWN_EVENT; sse->sse_flags = 0; sse->sse_length = sizeof(struct sctp_shutdown_event); sse->sse_assoc_id = sctp_get_associd(stcb); SCTP_BUF_LEN(m_notify) = sizeof(struct sctp_shutdown_event); SCTP_BUF_NEXT(m_notify) = NULL; /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; control->length = SCTP_BUF_LEN(m_notify); /* not that we need this */ control->tail_mbuf = m_notify; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } static void sctp_notify_stream_reset(struct sctp_tcb *stcb, int number_entries, uint16_t * list, int flag) { struct mbuf *m_notify; struct sctp_queued_to_read *control; struct sctp_stream_reset_event *strreset; int len; if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_STREAM_RESETEVNT)) /* event not enabled */ return; m_notify = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_DONTWAIT, 1, MT_DATA); if (m_notify == NULL) /* no space left */ return; SCTP_BUF_LEN(m_notify) = 0; len = sizeof(struct sctp_stream_reset_event) + (number_entries * sizeof(uint16_t)); if (len > M_TRAILINGSPACE(m_notify)) { /* never enough room */ sctp_m_freem(m_notify); return; } strreset = mtod(m_notify, struct sctp_stream_reset_event *); strreset->strreset_type = SCTP_STREAM_RESET_EVENT; if (number_entries == 0) { strreset->strreset_flags = flag | SCTP_STRRESET_ALL_STREAMS; } else { strreset->strreset_flags = flag | SCTP_STRRESET_STREAM_LIST; } strreset->strreset_length = len; strreset->strreset_assoc_id = sctp_get_associd(stcb); if (number_entries) { int i; for (i = 0; i < number_entries; i++) { strreset->strreset_list[i] = ntohs(list[i]); } } SCTP_BUF_LEN(m_notify) = len; SCTP_BUF_NEXT(m_notify) = NULL; if (sctp_sbspace(&stcb->asoc, &stcb->sctp_socket->so_rcv) < SCTP_BUF_LEN(m_notify)) { /* no space */ sctp_m_freem(m_notify); return; } /* append to socket */ control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination, 0, 0, 0, 0, 0, 0, m_notify); if (control == NULL) { /* no memory */ sctp_m_freem(m_notify); return; } control->spec_flags = M_NOTIFICATION; control->length = SCTP_BUF_LEN(m_notify); /* not that we need this */ control->tail_mbuf = m_notify; sctp_add_to_readq(stcb->sctp_ep, stcb, control, &stcb->sctp_socket->so_rcv, 1); } void sctp_ulp_notify(uint32_t notification, struct sctp_tcb *stcb, uint32_t error, void *data) { if (stcb == NULL) { /* unlikely but */ return; } if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) ) { /* No notifications up when we are in a no socket state */ return; } if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) { /* Can't send up to a closed socket any notifications */ return; } if (stcb && ((stcb->asoc.state & SCTP_STATE_COOKIE_WAIT) || (stcb->asoc.state & SCTP_STATE_COOKIE_ECHOED))) { if ((notification == SCTP_NOTIFY_INTERFACE_DOWN) || (notification == SCTP_NOTIFY_INTERFACE_UP) || (notification == SCTP_NOTIFY_INTERFACE_CONFIRMED)) { /* Don't report these in front states */ return; } } if (stcb && (stcb->asoc.assoc_up_sent == 0) && (notification != SCTP_NOTIFY_ASSOC_UP)) { if ((notification != SCTP_NOTIFY_ASSOC_DOWN) && (notification != SCTP_NOTIFY_ASSOC_ABORTED) && (notification != SCTP_NOTIFY_SPECIAL_SP_FAIL) && (notification != SCTP_NOTIFY_DG_FAIL) && (notification != SCTP_NOTIFY_PEER_SHUTDOWN)) { sctp_notify_assoc_change(SCTP_COMM_UP, stcb, 0, NULL); stcb->asoc.assoc_up_sent = 1; } } switch (notification) { case SCTP_NOTIFY_ASSOC_UP: if (stcb->asoc.assoc_up_sent == 0) { sctp_notify_assoc_change(SCTP_COMM_UP, stcb, error, NULL); stcb->asoc.assoc_up_sent = 1; } break; case SCTP_NOTIFY_ASSOC_DOWN: sctp_notify_assoc_change(SCTP_SHUTDOWN_COMP, stcb, error, NULL); break; case SCTP_NOTIFY_INTERFACE_DOWN: { struct sctp_nets *net; net = (struct sctp_nets *)data; sctp_notify_peer_addr_change(stcb, SCTP_ADDR_UNREACHABLE, (struct sockaddr *)&net->ro._l_addr, error); break; } case SCTP_NOTIFY_INTERFACE_UP: { struct sctp_nets *net; net = (struct sctp_nets *)data; sctp_notify_peer_addr_change(stcb, SCTP_ADDR_AVAILABLE, (struct sockaddr *)&net->ro._l_addr, error); break; } case SCTP_NOTIFY_INTERFACE_CONFIRMED: { struct sctp_nets *net; net = (struct sctp_nets *)data; sctp_notify_peer_addr_change(stcb, SCTP_ADDR_CONFIRMED, (struct sockaddr *)&net->ro._l_addr, error); break; } case SCTP_NOTIFY_SPECIAL_SP_FAIL: sctp_notify_send_failed2(stcb, error, (struct sctp_stream_queue_pending *)data); break; case SCTP_NOTIFY_DG_FAIL: sctp_notify_send_failed(stcb, error, (struct sctp_tmit_chunk *)data); break; case SCTP_NOTIFY_ADAPTATION_INDICATION: /* Here the error is the adaptation indication */ sctp_notify_adaptation_layer(stcb, error); break; case SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION: { uint32_t val; val = *((uint32_t *) data); sctp_notify_partial_delivery_indication(stcb, error, 0, val); } break; case SCTP_NOTIFY_STRDATA_ERR: break; case SCTP_NOTIFY_ASSOC_ABORTED: if ((stcb) && (((stcb->asoc.state & SCTP_STATE_MASK) == SCTP_STATE_COOKIE_WAIT) || ((stcb->asoc.state & SCTP_STATE_MASK) == SCTP_STATE_COOKIE_ECHOED))) { sctp_notify_assoc_change(SCTP_CANT_STR_ASSOC, stcb, error, NULL); } else { sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, NULL); } break; case SCTP_NOTIFY_PEER_OPENED_STREAM: break; case SCTP_NOTIFY_STREAM_OPENED_OK: break; case SCTP_NOTIFY_ASSOC_RESTART: sctp_notify_assoc_change(SCTP_RESTART, stcb, error, data); break; case SCTP_NOTIFY_HB_RESP: break; case SCTP_NOTIFY_STR_RESET_SEND: sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STRRESET_OUTBOUND_STR); break; case SCTP_NOTIFY_STR_RESET_RECV: sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STRRESET_INBOUND_STR); break; case SCTP_NOTIFY_STR_RESET_FAILED_OUT: sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), (SCTP_STRRESET_OUTBOUND_STR | SCTP_STRRESET_INBOUND_STR)); break; case SCTP_NOTIFY_STR_RESET_FAILED_IN: sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), (SCTP_STRRESET_INBOUND_STR | SCTP_STRRESET_INBOUND_STR)); break; case SCTP_NOTIFY_ASCONF_ADD_IP: sctp_notify_peer_addr_change(stcb, SCTP_ADDR_ADDED, data, error); break; case SCTP_NOTIFY_ASCONF_DELETE_IP: sctp_notify_peer_addr_change(stcb, SCTP_ADDR_REMOVED, data, error); break; case SCTP_NOTIFY_ASCONF_SET_PRIMARY: sctp_notify_peer_addr_change(stcb, SCTP_ADDR_MADE_PRIM, data, error); break; case SCTP_NOTIFY_ASCONF_SUCCESS: break; case SCTP_NOTIFY_ASCONF_FAILED: break; case SCTP_NOTIFY_PEER_SHUTDOWN: sctp_notify_shutdown_event(stcb); break; case SCTP_NOTIFY_AUTH_NEW_KEY: sctp_notify_authentication(stcb, SCTP_AUTH_NEWKEY, error, (uint16_t) (uintptr_t) data); break; #if 0 case SCTP_NOTIFY_AUTH_KEY_CONFLICT: sctp_notify_authentication(stcb, SCTP_AUTH_KEY_CONFLICT, error, (uint16_t) (uintptr_t) data); break; #endif /* not yet? remove? */ default: #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_UTIL1) { printf("NOTIFY: unknown notification %xh (%u)\n", notification, notification); } #endif /* SCTP_DEBUG */ break; } /* end switch */ } void sctp_report_all_outbound(struct sctp_tcb *stcb, int holds_lock) { struct sctp_association *asoc; struct sctp_stream_out *outs; struct sctp_tmit_chunk *chk; struct sctp_stream_queue_pending *sp; int i; asoc = &stcb->asoc; if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) { return; } /* now through all the gunk freeing chunks */ if (holds_lock == 0) SCTP_TCB_SEND_LOCK(stcb); for (i = 0; i < stcb->asoc.streamoutcnt; i++) { /* For each stream */ outs = &stcb->asoc.strmout[i]; /* clean up any sends there */ stcb->asoc.locked_on_sending = NULL; sp = TAILQ_FIRST(&outs->outqueue); while (sp) { stcb->asoc.stream_queue_cnt--; TAILQ_REMOVE(&outs->outqueue, sp, next); sctp_free_spbufspace(stcb, asoc, sp); sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb, SCTP_NOTIFY_DATAGRAM_UNSENT, (void *)sp); if (sp->data) { sctp_m_freem(sp->data); sp->data = NULL; } if (sp->net) sctp_free_remote_addr(sp->net); sp->net = NULL; /* Free the chunk */ sctp_free_a_strmoq(stcb, sp); sp = TAILQ_FIRST(&outs->outqueue); } } /* pending send queue SHOULD be empty */ if (!TAILQ_EMPTY(&asoc->send_queue)) { chk = TAILQ_FIRST(&asoc->send_queue); while (chk) { TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next); asoc->send_queue_cnt--; if (chk->data) { /* * trim off the sctp chunk header(it should * be there) */ if (chk->send_size >= sizeof(struct sctp_data_chunk)) { m_adj(chk->data, sizeof(struct sctp_data_chunk)); sctp_mbuf_crush(chk->data); } } sctp_free_bufspace(stcb, asoc, chk, 1); sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, SCTP_NOTIFY_DATAGRAM_UNSENT, chk); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } if (chk->whoTo) sctp_free_remote_addr(chk->whoTo); chk->whoTo = NULL; sctp_free_a_chunk(stcb, chk); chk = TAILQ_FIRST(&asoc->send_queue); } } /* sent queue SHOULD be empty */ if (!TAILQ_EMPTY(&asoc->sent_queue)) { chk = TAILQ_FIRST(&asoc->sent_queue); while (chk) { TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); asoc->sent_queue_cnt--; if (chk->data) { /* * trim off the sctp chunk header(it should * be there) */ if (chk->send_size >= sizeof(struct sctp_data_chunk)) { m_adj(chk->data, sizeof(struct sctp_data_chunk)); sctp_mbuf_crush(chk->data); } } sctp_free_bufspace(stcb, asoc, chk, 1); sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, SCTP_NOTIFY_DATAGRAM_SENT, chk); if (chk->data) { sctp_m_freem(chk->data); chk->data = NULL; } if (chk->whoTo) sctp_free_remote_addr(chk->whoTo); chk->whoTo = NULL; sctp_free_a_chunk(stcb, chk); chk = TAILQ_FIRST(&asoc->sent_queue); } } if (holds_lock == 0) SCTP_TCB_SEND_UNLOCK(stcb); } void sctp_abort_notification(struct sctp_tcb *stcb, int error) { if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) || (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) { return; } /* Tell them we lost the asoc */ sctp_report_all_outbound(stcb, 1); if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) || ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED))) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_WAS_ABORTED; } sctp_ulp_notify(SCTP_NOTIFY_ASSOC_ABORTED, stcb, error, NULL); } void sctp_abort_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct mbuf *m, int iphlen, struct sctphdr *sh, struct mbuf *op_err, uint32_t vrf_id, uint32_t table_id) { uint32_t vtag; vtag = 0; if (stcb != NULL) { /* We have a TCB to abort, send notification too */ vtag = stcb->asoc.peer_vtag; sctp_abort_notification(stcb, 0); /* get the assoc vrf id and table id */ vrf_id = stcb->asoc.vrf_id; table_id = stcb->asoc.table_id; } sctp_send_abort(m, iphlen, sh, vtag, op_err, vrf_id, table_id); if (stcb != NULL) { /* Ok, now lets free it */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTPUTIL + SCTP_LOC_4); } else { if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { sctp_inpcb_free(inp, 1, 0); } } } } #ifdef SCTP_ASOCLOG_OF_TSNS void sctp_print_out_track_log(struct sctp_tcb *stcb) { int i; printf("Last ep reason:%x\n", stcb->sctp_ep->last_abort_code); printf("IN bound TSN log-aaa\n"); if ((stcb->asoc.tsn_in_at == 0) && (stcb->asoc.tsn_in_wrapped == 0)) { printf("None rcvd\n"); goto none_in; } if (stcb->asoc.tsn_in_wrapped) { for (i = stcb->asoc.tsn_in_at; i < SCTP_TSN_LOG_SIZE; i++) { printf("TSN:%x strm:%d seq:%d flags:%x sz:%d\n", stcb->asoc.in_tsnlog[i].tsn, stcb->asoc.in_tsnlog[i].strm, stcb->asoc.in_tsnlog[i].seq, stcb->asoc.in_tsnlog[i].flgs, stcb->asoc.in_tsnlog[i].sz); } } if (stcb->asoc.tsn_in_at) { for (i = 0; i < stcb->asoc.tsn_in_at; i++) { printf("TSN:%x strm:%d seq:%d flags:%x sz:%d\n", stcb->asoc.in_tsnlog[i].tsn, stcb->asoc.in_tsnlog[i].strm, stcb->asoc.in_tsnlog[i].seq, stcb->asoc.in_tsnlog[i].flgs, stcb->asoc.in_tsnlog[i].sz); } } none_in: printf("OUT bound TSN log-aaa\n"); if ((stcb->asoc.tsn_out_at == 0) && (stcb->asoc.tsn_out_wrapped == 0)) { printf("None sent\n"); } if (stcb->asoc.tsn_out_wrapped) { for (i = stcb->asoc.tsn_out_at; i < SCTP_TSN_LOG_SIZE; i++) { printf("TSN:%x strm:%d seq:%d flags:%x sz:%d\n", stcb->asoc.out_tsnlog[i].tsn, stcb->asoc.out_tsnlog[i].strm, stcb->asoc.out_tsnlog[i].seq, stcb->asoc.out_tsnlog[i].flgs, stcb->asoc.out_tsnlog[i].sz); } } if (stcb->asoc.tsn_out_at) { for (i = 0; i < stcb->asoc.tsn_out_at; i++) { printf("TSN:%x strm:%d seq:%d flags:%x sz:%d\n", stcb->asoc.out_tsnlog[i].tsn, stcb->asoc.out_tsnlog[i].strm, stcb->asoc.out_tsnlog[i].seq, stcb->asoc.out_tsnlog[i].flgs, stcb->asoc.out_tsnlog[i].sz); } } } #endif void sctp_abort_an_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int error, struct mbuf *op_err) { uint32_t vtag; if (stcb == NULL) { /* Got to have a TCB */ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { sctp_inpcb_free(inp, 1, 0); } } return; } vtag = stcb->asoc.peer_vtag; /* notify the ulp */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) sctp_abort_notification(stcb, error); /* notify the peer */ sctp_send_abort_tcb(stcb, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } /* now free the asoc */ #ifdef SCTP_ASOCLOG_OF_TSNS sctp_print_out_track_log(stcb); #endif sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTPUTIL + SCTP_LOC_5); } void sctp_handle_ootb(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, struct sctp_inpcb *inp, struct mbuf *op_err, uint32_t vrf_id, uint32_t table_id) { struct sctp_chunkhdr *ch, chunk_buf; unsigned int chk_length; SCTP_STAT_INCR_COUNTER32(sctps_outoftheblue); /* Generate a TO address for future reference */ if (inp && (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { sctp_inpcb_free(inp, 1, 0); } } ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch), (uint8_t *) & chunk_buf); while (ch != NULL) { chk_length = ntohs(ch->chunk_length); if (chk_length < sizeof(*ch)) { /* break to abort land */ break; } switch (ch->chunk_type) { case SCTP_PACKET_DROPPED: /* we don't respond to pkt-dropped */ return; case SCTP_ABORT_ASSOCIATION: /* we don't respond with an ABORT to an ABORT */ return; case SCTP_SHUTDOWN_COMPLETE: /* * we ignore it since we are not waiting for it and * peer is gone */ return; case SCTP_SHUTDOWN_ACK: sctp_send_shutdown_complete2(m, iphlen, sh, vrf_id, table_id); return; default: break; } offset += SCTP_SIZE32(chk_length); ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch), (uint8_t *) & chunk_buf); } sctp_send_abort(m, iphlen, sh, 0, op_err, vrf_id, table_id); } /* * check the inbound datagram to make sure there is not an abort inside it, * if there is return 1, else return 0. */ int sctp_is_there_an_abort_here(struct mbuf *m, int iphlen, uint32_t * vtagfill) { struct sctp_chunkhdr *ch; struct sctp_init_chunk *init_chk, chunk_buf; int offset; unsigned int chk_length; offset = iphlen + sizeof(struct sctphdr); ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch), (uint8_t *) & chunk_buf); while (ch != NULL) { chk_length = ntohs(ch->chunk_length); if (chk_length < sizeof(*ch)) { /* packet is probably corrupt */ break; } /* we seem to be ok, is it an abort? */ if (ch->chunk_type == SCTP_ABORT_ASSOCIATION) { /* yep, tell them */ return (1); } if (ch->chunk_type == SCTP_INITIATION) { /* need to update the Vtag */ init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, offset, sizeof(*init_chk), (uint8_t *) & chunk_buf); if (init_chk != NULL) { *vtagfill = ntohl(init_chk->init.initiate_tag); } } /* Nope, move to the next chunk */ offset += SCTP_SIZE32(chk_length); ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch), (uint8_t *) & chunk_buf); } return (0); } /* * currently (2/02), ifa_addr embeds scope_id's and don't have sin6_scope_id * set (i.e. it's 0) so, create this function to compare link local scopes */ uint32_t sctp_is_same_scope(struct sockaddr_in6 *addr1, struct sockaddr_in6 *addr2) { struct sockaddr_in6 a, b; /* save copies */ a = *addr1; b = *addr2; if (a.sin6_scope_id == 0) if (sa6_recoverscope(&a)) { /* can't get scope, so can't match */ return (0); } if (b.sin6_scope_id == 0) if (sa6_recoverscope(&b)) { /* can't get scope, so can't match */ return (0); } if (a.sin6_scope_id != b.sin6_scope_id) return (0); return (1); } /* * returns a sockaddr_in6 with embedded scope recovered and removed */ struct sockaddr_in6 * sctp_recover_scope(struct sockaddr_in6 *addr, struct sockaddr_in6 *store) { /* check and strip embedded scope junk */ if (addr->sin6_family == AF_INET6) { if (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr)) { if (addr->sin6_scope_id == 0) { *store = *addr; if (!sa6_recoverscope(store)) { /* use the recovered scope */ addr = store; } } else { /* else, return the original "to" addr */ in6_clearscope(&addr->sin6_addr); } } } return (addr); } /* * are the two addresses the same? currently a "scopeless" check returns: 1 * if same, 0 if not */ __inline int sctp_cmpaddr(struct sockaddr *sa1, struct sockaddr *sa2) { /* must be valid */ if (sa1 == NULL || sa2 == NULL) return (0); /* must be the same family */ if (sa1->sa_family != sa2->sa_family) return (0); if (sa1->sa_family == AF_INET6) { /* IPv6 addresses */ struct sockaddr_in6 *sin6_1, *sin6_2; sin6_1 = (struct sockaddr_in6 *)sa1; sin6_2 = (struct sockaddr_in6 *)sa2; return (SCTP6_ARE_ADDR_EQUAL(&sin6_1->sin6_addr, &sin6_2->sin6_addr)); } else if (sa1->sa_family == AF_INET) { /* IPv4 addresses */ struct sockaddr_in *sin_1, *sin_2; sin_1 = (struct sockaddr_in *)sa1; sin_2 = (struct sockaddr_in *)sa2; return (sin_1->sin_addr.s_addr == sin_2->sin_addr.s_addr); } else { /* we don't do these... */ return (0); } } void sctp_print_address(struct sockaddr *sa) { if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; char ip6buf[INET6_ADDRSTRLEN]; sin6 = (struct sockaddr_in6 *)sa; printf("IPv6 address: %s:port:%d scope:%u\n", ip6_sprintf(ip6buf, &sin6->sin6_addr), ntohs(sin6->sin6_port), sin6->sin6_scope_id); } else if (sa->sa_family == AF_INET) { struct sockaddr_in *sin; unsigned char *p; sin = (struct sockaddr_in *)sa; p = (unsigned char *)&sin->sin_addr; printf("IPv4 address: %u.%u.%u.%u:%d\n", p[0], p[1], p[2], p[3], ntohs(sin->sin_port)); } else { printf("?\n"); } } void sctp_print_address_pkt(struct ip *iph, struct sctphdr *sh) { if (iph->ip_v == IPVERSION) { struct sockaddr_in lsa, fsa; bzero(&lsa, sizeof(lsa)); lsa.sin_len = sizeof(lsa); lsa.sin_family = AF_INET; lsa.sin_addr = iph->ip_src; lsa.sin_port = sh->src_port; bzero(&fsa, sizeof(fsa)); fsa.sin_len = sizeof(fsa); fsa.sin_family = AF_INET; fsa.sin_addr = iph->ip_dst; fsa.sin_port = sh->dest_port; printf("src: "); sctp_print_address((struct sockaddr *)&lsa); printf("dest: "); sctp_print_address((struct sockaddr *)&fsa); } else if (iph->ip_v == (IPV6_VERSION >> 4)) { struct ip6_hdr *ip6; struct sockaddr_in6 lsa6, fsa6; ip6 = (struct ip6_hdr *)iph; bzero(&lsa6, sizeof(lsa6)); lsa6.sin6_len = sizeof(lsa6); lsa6.sin6_family = AF_INET6; lsa6.sin6_addr = ip6->ip6_src; lsa6.sin6_port = sh->src_port; bzero(&fsa6, sizeof(fsa6)); fsa6.sin6_len = sizeof(fsa6); fsa6.sin6_family = AF_INET6; fsa6.sin6_addr = ip6->ip6_dst; fsa6.sin6_port = sh->dest_port; printf("src: "); sctp_print_address((struct sockaddr *)&lsa6); printf("dest: "); sctp_print_address((struct sockaddr *)&fsa6); } } void sctp_pull_off_control_to_new_inp(struct sctp_inpcb *old_inp, struct sctp_inpcb *new_inp, struct sctp_tcb *stcb, int waitflags) { /* * go through our old INP and pull off any control structures that * belong to stcb and move then to the new inp. */ struct socket *old_so, *new_so; struct sctp_queued_to_read *control, *nctl; struct sctp_readhead tmp_queue; struct mbuf *m; int error = 0; old_so = old_inp->sctp_socket; new_so = new_inp->sctp_socket; TAILQ_INIT(&tmp_queue); error = sblock(&old_so->so_rcv, waitflags); if (error) { /* * Gak, can't get sblock, we have a problem. data will be * left stranded.. and we don't dare look at it since the * other thread may be reading something. Oh well, its a * screwed up app that does a peeloff OR a accept while * reading from the main socket... actually its only the * peeloff() case, since I think read will fail on a * listening socket.. */ return; } /* lock the socket buffers */ SCTP_INP_READ_LOCK(old_inp); control = TAILQ_FIRST(&old_inp->read_queue); /* Pull off all for out target stcb */ while (control) { nctl = TAILQ_NEXT(control, next); if (control->stcb == stcb) { /* remove it we want it */ TAILQ_REMOVE(&old_inp->read_queue, control, next); TAILQ_INSERT_TAIL(&tmp_queue, control, next); m = control->data; while (m) { #ifdef SCTP_SB_LOGGING sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, SCTP_BUF_LEN(m)); #endif sctp_sbfree(control, stcb, &old_so->so_rcv, m); #ifdef SCTP_SB_LOGGING sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif m = SCTP_BUF_NEXT(m); } } control = nctl; } SCTP_INP_READ_UNLOCK(old_inp); /* Remove the sb-lock on the old socket */ sbunlock(&old_so->so_rcv); /* Now we move them over to the new socket buffer */ control = TAILQ_FIRST(&tmp_queue); SCTP_INP_READ_LOCK(new_inp); while (control) { nctl = TAILQ_NEXT(control, next); TAILQ_INSERT_TAIL(&new_inp->read_queue, control, next); m = control->data; while (m) { #ifdef SCTP_SB_LOGGING sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m)); #endif sctp_sballoc(stcb, &new_so->so_rcv, m); #ifdef SCTP_SB_LOGGING sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif m = SCTP_BUF_NEXT(m); } control = nctl; } SCTP_INP_READ_UNLOCK(new_inp); } void sctp_add_to_readq(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_queued_to_read *control, struct sockbuf *sb, int end) { /* * Here we must place the control on the end of the socket read * queue AND increment sb_cc so that select will work properly on * read. */ struct mbuf *m, *prev = NULL; if (inp == NULL) { /* Gak, TSNH!! */ #ifdef INVARIANTS panic("Gak, inp NULL on add_to_readq"); #endif return; } SCTP_INP_READ_LOCK(inp); if (!(control->spec_flags & M_NOTIFICATION)) { atomic_add_int(&inp->total_recvs, 1); if (!control->do_not_ref_stcb) { atomic_add_int(&stcb->total_recvs, 1); } } m = control->data; control->held_length = 0; control->length = 0; while (m) { if (SCTP_BUF_LEN(m) == 0) { /* Skip mbufs with NO length */ if (prev == NULL) { /* First one */ control->data = sctp_m_free(m); m = control->data; } else { SCTP_BUF_NEXT(prev) = sctp_m_free(m); m = SCTP_BUF_NEXT(prev); } if (m == NULL) { control->tail_mbuf = prev;; } continue; } prev = m; #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(m)); #endif sctp_sballoc(stcb, sb, m); #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif atomic_add_int(&control->length, SCTP_BUF_LEN(m)); m = SCTP_BUF_NEXT(m); } if (prev != NULL) { control->tail_mbuf = prev; } else { /* Everything got collapsed out?? */ return; } if (end) { control->end_added = 1; } TAILQ_INSERT_TAIL(&inp->read_queue, control, next); SCTP_INP_READ_UNLOCK(inp); if (inp && inp->sctp_socket) { if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) { SCTP_ZERO_COPY_EVENT(inp, inp->sctp_socket); } else sctp_sorwakeup(inp, inp->sctp_socket); } } int sctp_append_to_readq(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_queued_to_read *control, struct mbuf *m, int end, int ctls_cumack, struct sockbuf *sb) { /* * A partial delivery API event is underway. OR we are appending on * the reassembly queue. * * If PDAPI this means we need to add m to the end of the data. * Increase the length in the control AND increment the sb_cc. * Otherwise sb is NULL and all we need to do is put it at the end * of the mbuf chain. */ int len = 0; struct mbuf *mm, *tail = NULL, *prev = NULL; if (inp) { SCTP_INP_READ_LOCK(inp); } if (control == NULL) { get_out: if (inp) { SCTP_INP_READ_UNLOCK(inp); } return (-1); } if (control->end_added) { /* huh this one is complete? */ goto get_out; } mm = m; if (mm == NULL) { goto get_out; } while (mm) { if (SCTP_BUF_LEN(mm) == 0) { /* Skip mbufs with NO lenght */ if (prev == NULL) { /* First one */ m = sctp_m_free(mm); mm = m; } else { SCTP_BUF_NEXT(prev) = sctp_m_free(mm); mm = SCTP_BUF_NEXT(prev); } continue; } prev = mm; len += SCTP_BUF_LEN(mm); if (sb) { #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, SCTP_BUF_LEN(mm)); #endif sctp_sballoc(stcb, sb, mm); #ifdef SCTP_SB_LOGGING sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif } mm = SCTP_BUF_NEXT(mm); } if (prev) { tail = prev; } else { /* Really there should always be a prev */ if (m == NULL) { /* Huh nothing left? */ #ifdef INVARIANTS panic("Nothing left to add?"); #else goto get_out; #endif } tail = m; } if (end) { /* message is complete */ if (control == stcb->asoc.control_pdapi) { stcb->asoc.control_pdapi = NULL; } control->held_length = 0; control->end_added = 1; } atomic_add_int(&control->length, len); if (control->tail_mbuf) { /* append */ SCTP_BUF_NEXT(control->tail_mbuf) = m; control->tail_mbuf = tail; } else { /* nothing there */ #ifdef INVARIANTS if (control->data != NULL) { panic("This should NOT happen"); } #endif control->data = m; control->tail_mbuf = tail; } /* * When we are appending in partial delivery, the cum-ack is used * for the actual pd-api highest tsn on this mbuf. The true cum-ack * is populated in the outbound sinfo structure from the true cumack * if the association exists... */ control->sinfo_tsn = control->sinfo_cumtsn = ctls_cumack; if (inp) { SCTP_INP_READ_UNLOCK(inp); } if (inp && inp->sctp_socket) { if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_ZERO_COPY_ACTIVE)) { SCTP_ZERO_COPY_EVENT(inp, inp->sctp_socket); } else sctp_sorwakeup(inp, inp->sctp_socket); } return (0); } /*************HOLD THIS COMMENT FOR PATCH FILE OF *************ALTERNATE ROUTING CODE */ /*************HOLD THIS COMMENT FOR END OF PATCH FILE OF *************ALTERNATE ROUTING CODE */ struct mbuf * sctp_generate_invmanparam(int err) { /* Return a MBUF with a invalid mandatory parameter */ struct mbuf *m; m = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, MT_DATA); if (m) { struct sctp_paramhdr *ph; SCTP_BUF_LEN(m) = sizeof(struct sctp_paramhdr); ph = mtod(m, struct sctp_paramhdr *); ph->param_length = htons(sizeof(struct sctp_paramhdr)); ph->param_type = htons(err); } return (m); } #ifdef SCTP_MBCNT_LOGGING void sctp_free_bufspace(struct sctp_tcb *stcb, struct sctp_association *asoc, struct sctp_tmit_chunk *tp1, int chk_cnt) { if (tp1->data == NULL) { return; } asoc->chunks_on_out_queue -= chk_cnt; sctp_log_mbcnt(SCTP_LOG_MBCNT_DECREASE, asoc->total_output_queue_size, tp1->book_size, 0, tp1->mbcnt); if (asoc->total_output_queue_size >= tp1->book_size) { atomic_add_int(&asoc->total_output_queue_size, -tp1->book_size); } else { asoc->total_output_queue_size = 0; } if (stcb->sctp_socket && (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) || ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)))) { if (stcb->sctp_socket->so_snd.sb_cc >= tp1->book_size) { stcb->sctp_socket->so_snd.sb_cc -= tp1->book_size; } else { stcb->sctp_socket->so_snd.sb_cc = 0; } } } #endif int sctp_release_pr_sctp_chunk(struct sctp_tcb *stcb, struct sctp_tmit_chunk *tp1, int reason, struct sctpchunk_listhead *queue) { int ret_sz = 0; int notdone; uint8_t foundeom = 0; do { ret_sz += tp1->book_size; tp1->sent = SCTP_FORWARD_TSN_SKIP; if (tp1->data) { sctp_free_bufspace(stcb, &stcb->asoc, tp1, 1); sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, reason, tp1); sctp_m_freem(tp1->data); tp1->data = NULL; sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); } if (PR_SCTP_BUF_ENABLED(tp1->flags)) { stcb->asoc.sent_queue_cnt_removeable--; } if (queue == &stcb->asoc.send_queue) { TAILQ_REMOVE(&stcb->asoc.send_queue, tp1, sctp_next); /* on to the sent queue */ TAILQ_INSERT_TAIL(&stcb->asoc.sent_queue, tp1, sctp_next); stcb->asoc.sent_queue_cnt++; } if ((tp1->rec.data.rcv_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG) { /* not frag'ed we ae done */ notdone = 0; foundeom = 1; } else if (tp1->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { /* end of frag, we are done */ notdone = 0; foundeom = 1; } else { /* * Its a begin or middle piece, we must mark all of * it */ notdone = 1; tp1 = TAILQ_NEXT(tp1, sctp_next); } } while (tp1 && notdone); if ((foundeom == 0) && (queue == &stcb->asoc.sent_queue)) { /* * The multi-part message was scattered across the send and * sent queue. */ tp1 = TAILQ_FIRST(&stcb->asoc.send_queue); /* * recurse throught the send_queue too, starting at the * beginning. */ if (tp1) { ret_sz += sctp_release_pr_sctp_chunk(stcb, tp1, reason, &stcb->asoc.send_queue); } else { printf("hmm, nothing on the send queue and no EOM?\n"); } } return (ret_sz); } /* * checks to see if the given address, sa, is one that is currently known by * the kernel note: can't distinguish the same address on multiple interfaces * and doesn't handle multiple addresses with different zone/scope id's note: * ifa_ifwithaddr() compares the entire sockaddr struct */ struct sctp_ifa * sctp_find_ifa_in_ep(struct sctp_inpcb *inp, struct sockaddr *addr, int holds_lock) { struct sctp_laddr *laddr; if (holds_lock == 0) SCTP_INP_RLOCK(inp); LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) continue; if (addr->sa_family != laddr->ifa->address.sa.sa_family) continue; if (addr->sa_family == AF_INET) { if (((struct sockaddr_in *)addr)->sin_addr.s_addr == laddr->ifa->address.sin.sin_addr.s_addr) { /* found him. */ if (holds_lock == 0) SCTP_INP_RUNLOCK(inp); return (laddr->ifa); break; } } else if (addr->sa_family == AF_INET6) { if (SCTP6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)addr)->sin6_addr, &laddr->ifa->address.sin6.sin6_addr)) { /* found him. */ if (holds_lock == 0) SCTP_INP_RUNLOCK(inp); return (laddr->ifa); break; } } } if (holds_lock == 0) SCTP_INP_RUNLOCK(inp); return (NULL); } uint32_t sctp_get_ifa_hash_val(struct sockaddr *addr) { if (addr->sa_family == AF_INET) { struct sockaddr_in *sin; sin = (struct sockaddr_in *)addr; return (sin->sin_addr.s_addr ^ (sin->sin_addr.s_addr >> 16)); } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; uint32_t hash_of_addr; sin6 = (struct sockaddr_in6 *)addr; hash_of_addr = (sin6->sin6_addr.s6_addr32[0] + sin6->sin6_addr.s6_addr32[1] + sin6->sin6_addr.s6_addr32[2] + sin6->sin6_addr.s6_addr32[3]); hash_of_addr = (hash_of_addr ^ (hash_of_addr >> 16)); return (hash_of_addr); } return (0); } struct sctp_ifa * sctp_find_ifa_by_addr(struct sockaddr *addr, uint32_t vrf_id, int holds_lock) { struct sctp_ifa *sctp_ifap; struct sctp_vrf *vrf; struct sctp_ifalist *hash_head; uint32_t hash_of_addr; if (holds_lock == 0) SCTP_IPI_ADDR_LOCK(); vrf = sctp_find_vrf(vrf_id); if (vrf == NULL) { if (holds_lock == 0) SCTP_IPI_ADDR_UNLOCK(); return (NULL); } hash_of_addr = sctp_get_ifa_hash_val(addr); hash_head = &vrf->vrf_addr_hash[(hash_of_addr & vrf->vrf_addr_hashmark)]; if (hash_head == NULL) { printf("hash_of_addr:%x mask:%x table:%x - ", (u_int)hash_of_addr, (u_int)vrf->vrf_addr_hashmark, (u_int)(hash_of_addr & vrf->vrf_addr_hashmark)); sctp_print_address(addr); printf("No such bucket for address\n"); if (holds_lock == 0) SCTP_IPI_ADDR_UNLOCK(); return (NULL); } LIST_FOREACH(sctp_ifap, hash_head, next_bucket) { if (sctp_ifap == NULL) { panic("Huh LIST_FOREACH corrupt"); } if (addr->sa_family != sctp_ifap->address.sa.sa_family) continue; if (addr->sa_family == AF_INET) { if (((struct sockaddr_in *)addr)->sin_addr.s_addr == sctp_ifap->address.sin.sin_addr.s_addr) { /* found him. */ if (holds_lock == 0) SCTP_IPI_ADDR_UNLOCK(); return (sctp_ifap); break; } } else if (addr->sa_family == AF_INET6) { if (SCTP6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)addr)->sin6_addr, &sctp_ifap->address.sin6.sin6_addr)) { /* found him. */ if (holds_lock == 0) SCTP_IPI_ADDR_UNLOCK(); return (sctp_ifap); break; } } } if (holds_lock == 0) SCTP_IPI_ADDR_UNLOCK(); return (NULL); } static void sctp_user_rcvd(struct sctp_tcb *stcb, int *freed_so_far, int hold_rlock, uint32_t rwnd_req) { /* User pulled some data, do we need a rwnd update? */ int r_unlocked = 0; uint32_t dif, rwnd; struct socket *so = NULL; if (stcb == NULL) return; atomic_add_int(&stcb->asoc.refcnt, 1); if (stcb->asoc.state & (SCTP_STATE_ABOUT_TO_BE_FREED | SCTP_STATE_SHUTDOWN_RECEIVED | SCTP_STATE_SHUTDOWN_ACK_SENT) ) { /* Pre-check If we are freeing no update */ goto no_lock; } SCTP_INP_INCR_REF(stcb->sctp_ep); if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { goto out; } so = stcb->sctp_socket; if (so == NULL) { goto out; } atomic_add_int(&stcb->freed_by_sorcv_sincelast, *freed_so_far); /* Have you have freed enough to look */ #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_ENTER_USER_RECV, (stcb->asoc.my_rwnd - stcb->asoc.my_last_reported_rwnd), *freed_so_far, stcb->freed_by_sorcv_sincelast, rwnd_req); #endif *freed_so_far = 0; /* Yep, its worth a look and the lock overhead */ /* Figure out what the rwnd would be */ rwnd = sctp_calc_rwnd(stcb, &stcb->asoc); if (rwnd >= stcb->asoc.my_last_reported_rwnd) { dif = rwnd - stcb->asoc.my_last_reported_rwnd; } else { dif = 0; } if (dif >= rwnd_req) { if (hold_rlock) { SCTP_INP_READ_UNLOCK(stcb->sctp_ep); r_unlocked = 1; } if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* * One last check before we allow the guy possibly * to get in. There is a race, where the guy has not * reached the gate. In that case */ goto out; } SCTP_TCB_LOCK(stcb); if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* No reports here */ SCTP_TCB_UNLOCK(stcb); goto out; } #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_USER_RECV_SACKS, stcb->asoc.my_rwnd, stcb->asoc.my_last_reported_rwnd, stcb->freed_by_sorcv_sincelast, dif); #endif SCTP_STAT_INCR(sctps_wu_sacks_sent); sctp_send_sack(stcb); sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_USR_RCVD); /* make sure no timer is running */ sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL, SCTP_FROM_SCTPUTIL + SCTP_LOC_6); SCTP_TCB_UNLOCK(stcb); } else { /* Update how much we have pending */ stcb->freed_by_sorcv_sincelast = dif; #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_USER_RECV_SACKS, stcb->asoc.my_rwnd, stcb->asoc.my_last_reported_rwnd, stcb->freed_by_sorcv_sincelast, 0); #endif } out: if (so && r_unlocked && hold_rlock) { SCTP_INP_READ_LOCK(stcb->sctp_ep); } SCTP_INP_DECR_REF(stcb->sctp_ep); no_lock: atomic_add_int(&stcb->asoc.refcnt, -1); return; } int sctp_sorecvmsg(struct socket *so, struct uio *uio, struct mbuf **mp, struct sockaddr *from, int fromlen, int *msg_flags, struct sctp_sndrcvinfo *sinfo, int filling_sinfo) { /* * MSG flags we will look at MSG_DONTWAIT - non-blocking IO. * MSG_PEEK - Look don't touch :-D (only valid with OUT mbuf copy * mp=NULL thus uio is the copy method to userland) MSG_WAITALL - ?? * On the way out we may send out any combination of: * MSG_NOTIFICATION MSG_EOR * */ struct sctp_inpcb *inp = NULL; int my_len = 0; int cp_len = 0, error = 0; struct sctp_queued_to_read *control = NULL, *ctl = NULL, *nxt = NULL; struct mbuf *m = NULL, *embuf = NULL; struct sctp_tcb *stcb = NULL; int wakeup_read_socket = 0; int freecnt_applied = 0; int out_flags = 0, in_flags = 0; int block_allowed = 1; int freed_so_far = 0; int copied_so_far = 0; int in_eeor_mode = 0; int no_rcv_needed = 0; uint32_t rwnd_req = 0; int hold_sblock = 0; int hold_rlock = 0; int alen = 0; int slen = 0; int held_length = 0; int sockbuf_lock = 0; if (uio == NULL) { return (EINVAL); } if (msg_flags) { in_flags = *msg_flags; if (in_flags & MSG_PEEK) SCTP_STAT_INCR(sctps_read_peeks); } else { in_flags = 0; } slen = uio->uio_resid; /* Pull in and set up our int flags */ if (in_flags & MSG_OOB) { /* Out of band's NOT supported */ return (EOPNOTSUPP); } if ((in_flags & MSG_PEEK) && (mp != NULL)) { return (EINVAL); } if ((in_flags & (MSG_DONTWAIT | MSG_NBIO )) || SCTP_SO_IS_NBIO(so)) { block_allowed = 0; } /* setup the endpoint */ inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { return (EFAULT); } rwnd_req = (SCTP_SB_LIMIT_RCV(so) >> SCTP_RWND_HIWAT_SHIFT); /* Must be at least a MTU's worth */ if (rwnd_req < SCTP_MIN_RWND) rwnd_req = SCTP_MIN_RWND; in_eeor_mode = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR); #ifdef SCTP_RECV_RWND_LOGGING sctp_misc_ints(SCTP_SORECV_ENTER, rwnd_req, in_eeor_mode, so->so_rcv.sb_cc, uio->uio_resid); #endif #ifdef SCTP_RECV_RWND_LOGGING sctp_misc_ints(SCTP_SORECV_ENTERPL, rwnd_req, block_allowed, so->so_rcv.sb_cc, uio->uio_resid); #endif error = sblock(&so->so_rcv, (block_allowed ? M_WAITOK : 0)); sockbuf_lock = 1; if (error) { goto release_unlocked; } restart: restart_nosblocks: if (hold_sblock == 0) { SOCKBUF_LOCK(&so->so_rcv); hold_sblock = 1; } if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { goto out; } if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { if (so->so_error) { error = so->so_error; if ((in_flags & MSG_PEEK) == 0) so->so_error = 0; } else { error = ENOTCONN; } goto out; } if ((so->so_rcv.sb_cc <= held_length) && block_allowed) { /* we need to wait for data */ #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORECV_BLOCKSA, 0, 0, so->so_rcv.sb_cc, uio->uio_resid); #endif if ((so->so_rcv.sb_cc == 0) && ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) { /* * For active open side clear flags for * re-use passive open is blocked by * connect. */ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED) { /* * You were aborted, passive side * always hits here */ error = ECONNRESET; /* * You get this once if you are * active open side */ if (!(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* * Remove flag if on the * active open side */ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_ABORTED; } } so->so_state &= ~(SS_ISCONNECTING | SS_ISDISCONNECTING | SS_ISCONFIRMING | SS_ISCONNECTED); if (error == 0) { if ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) == 0) { error = ENOTCONN; } else { inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_CONNECTED; } } goto out; } } error = sbwait(&so->so_rcv); if (error) { goto out; } held_length = 0; goto restart_nosblocks; } else if (so->so_rcv.sb_cc == 0) { if (so->so_error) { error = so->so_error; if ((in_flags & MSG_PEEK) == 0) so->so_error = 0; } else { if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) { /* * For active open side clear flags * for re-use passive open is * blocked by connect. */ if (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED) { /* * You were aborted, passive * side always hits here */ error = ECONNRESET; /* * You get this once if you * are active open side */ if (!(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* * Remove flag if on * the active open * side */ inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_ABORTED; } } so->so_state &= ~(SS_ISCONNECTING | SS_ISDISCONNECTING | SS_ISCONFIRMING | SS_ISCONNECTED); if (error == 0) { if ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) == 0) { error = ENOTCONN; } else { inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_CONNECTED; } } goto out; } } error = EWOULDBLOCK; } goto out; } if (hold_sblock == 1) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } /* we possibly have data we can read */ control = TAILQ_FIRST(&inp->read_queue); if (control == NULL) { /* * This could be happening since the appender did the * increment but as not yet did the tailq insert onto the * read_queue */ if (hold_rlock == 0) { SCTP_INP_READ_LOCK(inp); hold_rlock = 1; } control = TAILQ_FIRST(&inp->read_queue); if ((control == NULL) && (so->so_rcv.sb_cc != 0)) { #ifdef INVARIANTS panic("Huh, its non zero and nothing on control?"); #endif so->so_rcv.sb_cc = 0; } SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; goto restart; } if ((control->length == 0) && (control->do_not_ref_stcb)) { /* * Clean up code for freeing assoc that left behind a * pdapi.. maybe a peer in EEOR that just closed after * sending and never indicated a EOR. */ if (hold_rlock == 0) { hold_rlock = 1; SCTP_INP_READ_LOCK(inp); } control->held_length = 0; if (control->data) { /* Hmm there is data here .. fix */ struct mbuf *m; int cnt = 0; m = control->data; while (m) { cnt += SCTP_BUF_LEN(m); if (SCTP_BUF_NEXT(m) == NULL) { control->tail_mbuf = m; control->end_added = 1; } m = SCTP_BUF_NEXT(m); } control->length = cnt; } else { /* remove it */ TAILQ_REMOVE(&inp->read_queue, control, next); /* Add back any hiddend data */ sctp_free_remote_addr(control->whoFrom); sctp_free_a_readq(stcb, control); } if (hold_rlock) { hold_rlock = 0; SCTP_INP_READ_UNLOCK(inp); } goto restart; } if (control->length == 0) { if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) && (filling_sinfo)) { /* find a more suitable one then this */ ctl = TAILQ_NEXT(control, next); while (ctl) { if ((ctl->stcb != control->stcb) && (ctl->length) && (ctl->some_taken || (ctl->spec_flags & M_NOTIFICATION) || ((ctl->do_not_ref_stcb == 0) && (ctl->stcb->asoc.strmin[ctl->sinfo_stream].delivery_started == 0))) ) { /*- * If we have a different TCB next, and there is data * present. If we have already taken some (pdapi), OR we can * ref the tcb and no delivery as started on this stream, we * take it. Note we allow a notification on a different * assoc to be delivered.. */ control = ctl; goto found_one; } else if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_INTERLEAVE_STRMS)) && (ctl->length) && ((ctl->some_taken) || ((ctl->do_not_ref_stcb == 0) && ((ctl->spec_flags & M_NOTIFICATION) == 0) && (ctl->stcb->asoc.strmin[ctl->sinfo_stream].delivery_started == 0))) ) { /*- * If we have the same tcb, and there is data present, and we * have the strm interleave feature present. Then if we have * taken some (pdapi) or we can refer to tht tcb AND we have * not started a delivery for this stream, we can take it. * Note we do NOT allow a notificaiton on the same assoc to * be delivered. */ control = ctl; goto found_one; } ctl = TAILQ_NEXT(ctl, next); } } /* * if we reach here, not suitable replacement is available * fragment interleave is NOT on. So stuff the sb_cc * into the our held count, and its time to sleep again. */ held_length = so->so_rcv.sb_cc; control->held_length = so->so_rcv.sb_cc; goto restart; } /* Clear the held length since there is something to read */ control->held_length = 0; if (hold_rlock) { SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; } found_one: /* * If we reach here, control has a some data for us to read off. * Note that stcb COULD be NULL. */ control->some_taken = 1; if (hold_sblock) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } stcb = control->stcb; if (stcb) { if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) && (control->do_not_ref_stcb == 0)) { if (freecnt_applied == 0) stcb = NULL; } else if (control->do_not_ref_stcb == 0) { /* you can't free it on me please */ /* * The lock on the socket buffer protects us so the * free code will stop. But since we used the * socketbuf lock and the sender uses the tcb_lock * to increment, we need to use the atomic add to * the refcnt */ atomic_add_int(&stcb->asoc.refcnt, 1); freecnt_applied = 1; /* * Setup to remember how much we have not yet told * the peer our rwnd has opened up. Note we grab the * value from the tcb from last time. Note too that * sack sending clears this when a sack is sent.. * which is fine. Once we hit the rwnd_req, we then * will go to the sctp_user_rcvd() that will not * lock until it KNOWs it MUST send a WUP-SACK. * */ freed_so_far = stcb->freed_by_sorcv_sincelast; stcb->freed_by_sorcv_sincelast = 0; } } if (stcb && ((control->spec_flags & M_NOTIFICATION) == 0) && control->do_not_ref_stcb == 0) { stcb->asoc.strmin[control->sinfo_stream].delivery_started = 1; } /* First lets get off the sinfo and sockaddr info */ if ((sinfo) && filling_sinfo) { memcpy(sinfo, control, sizeof(struct sctp_nonpad_sndrcvinfo)); nxt = TAILQ_NEXT(control, next); if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO)) { struct sctp_extrcvinfo *s_extra; s_extra = (struct sctp_extrcvinfo *)sinfo; if ((nxt) && (nxt->length)) { s_extra->sreinfo_next_flags = SCTP_NEXT_MSG_AVAIL; if (nxt->sinfo_flags & SCTP_UNORDERED) { s_extra->sreinfo_next_flags |= SCTP_NEXT_MSG_IS_UNORDERED; } if (nxt->spec_flags & M_NOTIFICATION) { s_extra->sreinfo_next_flags |= SCTP_NEXT_MSG_IS_NOTIFICATION; } s_extra->sreinfo_next_aid = nxt->sinfo_assoc_id; s_extra->sreinfo_next_length = nxt->length; s_extra->sreinfo_next_ppid = nxt->sinfo_ppid; s_extra->sreinfo_next_stream = nxt->sinfo_stream; if (nxt->tail_mbuf != NULL) { if (nxt->end_added) { s_extra->sreinfo_next_flags |= SCTP_NEXT_MSG_ISCOMPLETE; } } } else { /* * we explicitly 0 this, since the memcpy * got some other things beyond the older * sinfo_ that is on the control's structure * :-D */ nxt = NULL; s_extra->sreinfo_next_flags = SCTP_NO_NEXT_MSG; s_extra->sreinfo_next_aid = 0; s_extra->sreinfo_next_length = 0; s_extra->sreinfo_next_ppid = 0; s_extra->sreinfo_next_stream = 0; } } /* * update off the real current cum-ack, if we have an stcb. */ if (stcb) sinfo->sinfo_cumtsn = stcb->asoc.cumulative_tsn; /* * mask off the high bits, we keep the actual chunk bits in * there. */ sinfo->sinfo_flags &= 0x00ff; } if (fromlen && from) { struct sockaddr *to; #ifdef INET cp_len = min(fromlen, control->whoFrom->ro._l_addr.sin.sin_len); memcpy(from, &control->whoFrom->ro._l_addr, cp_len); ((struct sockaddr_in *)from)->sin_port = control->port_from; #else /* No AF_INET use AF_INET6 */ cp_len = min(fromlen, control->whoFrom->ro._l_addr.sin6.sin6_len); memcpy(from, &control->whoFrom->ro._l_addr, cp_len); ((struct sockaddr_in6 *)from)->sin6_port = control->port_from; #endif to = from; #if defined(INET) && defined(INET6) if ((inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && (to->sa_family == AF_INET) && ((size_t)fromlen >= sizeof(struct sockaddr_in6))) { struct sockaddr_in *sin; struct sockaddr_in6 sin6; sin = (struct sockaddr_in *)to; bzero(&sin6, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(struct sockaddr_in6); sin6.sin6_addr.s6_addr16[2] = 0xffff; bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], sizeof(sin6.sin6_addr.s6_addr16[3])); sin6.sin6_port = sin->sin_port; memcpy(from, (caddr_t)&sin6, sizeof(sin6)); } #endif #if defined(INET6) { struct sockaddr_in6 lsa6, *to6; to6 = (struct sockaddr_in6 *)to; sctp_recover_scope_mac(to6, (&lsa6)); } #endif } /* now copy out what data we can */ if (mp == NULL) { /* copy out each mbuf in the chain up to length */ get_more_data: m = control->data; while (m) { /* Move out all we can */ cp_len = (int)uio->uio_resid; my_len = (int)SCTP_BUF_LEN(m); if (cp_len > my_len) { /* not enough in this buf */ cp_len = my_len; } if (hold_rlock) { SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; } if (cp_len > 0) error = uiomove(mtod(m, char *), cp_len, uio); #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_DOESCPY, so->so_rcv.sb_cc, cp_len, 0, 0); #endif /* re-read */ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { goto release; } if (stcb && stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { no_rcv_needed = 1; } if (error) { /* error we are out of here */ goto release; } if ((SCTP_BUF_NEXT(m) == NULL) && (cp_len >= SCTP_BUF_LEN(m)) && ((control->end_added == 0) || (control->end_added && (TAILQ_NEXT(control, next) == NULL))) ) { #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_DOESLCK, so->so_rcv.sb_cc, cp_len, SCTP_BUF_LEN(m), control->length); #endif SCTP_INP_READ_LOCK(inp); hold_rlock = 1; } if (cp_len == SCTP_BUF_LEN(m)) { #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_DOESADJ, so->so_rcv.sb_cc, control->length, cp_len, 0); #endif if ((SCTP_BUF_NEXT(m) == NULL) && (control->end_added)) { out_flags |= MSG_EOR; if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0)) control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0; } if (control->spec_flags & M_NOTIFICATION) { out_flags |= MSG_NOTIFICATION; } /* we ate up the mbuf */ if (in_flags & MSG_PEEK) { /* just looking */ m = SCTP_BUF_NEXT(m); copied_so_far += cp_len; } else { /* dispose of the mbuf */ #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, SCTP_BUF_LEN(m)); #endif sctp_sbfree(control, stcb, &so->so_rcv, m); #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif embuf = m; copied_so_far += cp_len; freed_so_far += cp_len; alen = atomic_fetchadd_int(&control->length, -(cp_len)); if (alen < cp_len) { panic("Control length goes negative?"); } #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_PASSBF, so->so_rcv.sb_cc, control->length, 0, 0); #endif control->data = sctp_m_free(m); m = control->data; /* * been through it all, must hold sb * lock ok to null tail */ if (control->data == NULL) { #ifdef INVARIANTS if ((control->end_added == 0) || (TAILQ_NEXT(control, next) == NULL)) { /* * If the end is not * added, OR the * next is NOT null * we MUST have the * lock. */ if (mtx_owned(&inp->inp_rdata_mtx) == 0) { panic("Hmm we don't own the lock?"); } } #endif control->tail_mbuf = NULL; #ifdef INVARIANTS if ((control->end_added) && ((out_flags & MSG_EOR) == 0)) { panic("end_added, nothing left and no MSG_EOR"); } #endif } #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_ADJD, so->so_rcv.sb_cc, control->length, 0, 0); #endif } } else { /* Do we need to trim the mbuf? */ if (control->spec_flags & M_NOTIFICATION) { out_flags |= MSG_NOTIFICATION; } if ((in_flags & MSG_PEEK) == 0) { SCTP_BUF_RESV_UF(m, cp_len); SCTP_BUF_LEN(m) -= cp_len; #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, cp_len); #endif atomic_subtract_int(&so->so_rcv.sb_cc, cp_len); if (stcb) { atomic_subtract_int(&stcb->asoc.sb_cc, cp_len); } copied_so_far += cp_len; embuf = m; freed_so_far += cp_len; #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif alen = atomic_fetchadd_int(&control->length, -(cp_len)); if (alen < cp_len) { panic("Control length goes negative2?"); } } else { copied_so_far += cp_len; } } if ((out_flags & MSG_EOR) || (uio->uio_resid == 0) ) { break; } if (((stcb) && (in_flags & MSG_PEEK) == 0) && (control->do_not_ref_stcb == 0) && (freed_so_far >= rwnd_req)) { sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req); } #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_BOTWHILE, so->so_rcv.sb_cc, control->length, 0, 0); #endif } /* end while(m) */ /* * At this point we have looked at it all and we either have * a MSG_EOR/or read all the user wants... * control->length == 0. */ if ((out_flags & MSG_EOR) && ((in_flags & MSG_PEEK) == 0)) { /* we are done with this control */ if (control->length == 0) { if (control->data) { #ifdef INVARIANTS panic("control->data not null at read eor?"); #else printf("Strange, data left in the control buffer .. invarients would panic?\n"); sctp_m_freem(control->data); control->data = NULL; #endif } done_with_control: #ifdef SCTP_RECV_DETAIL_RWND_LOGGING sctp_misc_ints(SCTP_SORCV_FREECTL, so->so_rcv.sb_cc, 0, 0, 0); #endif if (TAILQ_NEXT(control, next) == NULL) { /* * If we don't have a next we need a * lock, if there is a next interupt * is filling ahead of us and we * don't need a lock to remove this * guy (which is the head of the * queue). */ if (hold_rlock == 0) { SCTP_INP_READ_LOCK(inp); hold_rlock = 1; } } TAILQ_REMOVE(&inp->read_queue, control, next); /* Add back any hiddend data */ if (control->held_length) { held_length = 0; control->held_length = 0; wakeup_read_socket = 1; } if (control->aux_data) { sctp_m_free(control->aux_data); control->aux_data = NULL; } no_rcv_needed = control->do_not_ref_stcb; sctp_free_remote_addr(control->whoFrom); control->data = NULL; sctp_free_a_readq(stcb, control); control = NULL; if ((freed_so_far >= rwnd_req) && (no_rcv_needed == 0)) sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req); } else { /* * The user did not read all of this * message, turn off the returned MSG_EOR * since we are leaving more behind on the * control to read. */ #ifdef INVARIANTS if (control->end_added && (control->data == NULL) && (control->tail_mbuf == NULL)) { panic("Gak, control->length is corrupt?"); } #endif no_rcv_needed = control->do_not_ref_stcb; out_flags &= ~MSG_EOR; } } if (out_flags & MSG_EOR) { goto release; } if ((uio->uio_resid == 0) || ((in_eeor_mode) && (copied_so_far >= max(so->so_rcv.sb_lowat, 1))) ) { goto release; } /* * If I hit here the receiver wants more and this message is * NOT done (pd-api). So two questions. Can we block? if not * we are done. Did the user NOT set MSG_WAITALL? */ if (block_allowed == 0) { goto release; } /* * We need to wait for more data a few things: - We don't * sbunlock() so we don't get someone else reading. - We * must be sure to account for the case where what is added * is NOT to our control when we wakeup. */ /* * Do we need to tell the transport a rwnd update might be * needed before we go to sleep? */ if (((stcb) && (in_flags & MSG_PEEK) == 0) && ((freed_so_far >= rwnd_req) && (control->do_not_ref_stcb == 0) && (no_rcv_needed == 0))) { sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req); } wait_some_more: if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { goto release; } if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) goto release; if (hold_rlock == 1) { SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; } if (hold_sblock == 0) { SOCKBUF_LOCK(&so->so_rcv); hold_sblock = 1; } #ifdef SCTP_RECV_DETAIL_RWND_LOGGING if (stcb) sctp_misc_ints(SCTP_SORECV_BLOCKSB, freed_so_far, stcb->asoc.my_rwnd, so->so_rcv.sb_cc, uio->uio_resid); else sctp_misc_ints(SCTP_SORECV_BLOCKSB, freed_so_far, 0, so->so_rcv.sb_cc, uio->uio_resid); #endif if (so->so_rcv.sb_cc <= control->held_length) { error = sbwait(&so->so_rcv); if (error) { goto release; } control->held_length = 0; } if (hold_sblock) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } if (control->length == 0) { /* still nothing here */ if (control->end_added == 1) { /* he aborted, or is done i.e.did a shutdown */ out_flags |= MSG_EOR; if (control->pdapi_aborted) { if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0)) control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0; out_flags |= MSG_TRUNC; } else { if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0)) control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0; } goto done_with_control; } if (so->so_rcv.sb_cc > held_length) { control->held_length = so->so_rcv.sb_cc; held_length = 0; } goto wait_some_more; } else if (control->data == NULL) { /* * we must re-sync since data is probably being * added */ SCTP_INP_READ_LOCK(inp); if ((control->length > 0) && (control->data == NULL)) { /* * big trouble.. we have the lock and its * corrupt? */ panic("Impossible data==NULL length !=0"); } SCTP_INP_READ_UNLOCK(inp); /* We will fall around to get more data */ } goto get_more_data; } else { /*- * Give caller back the mbuf chain, * store in uio_resid the length */ wakeup_read_socket = 0; if ((control->end_added == 0) || (TAILQ_NEXT(control, next) == NULL)) { /* Need to get rlock */ if (hold_rlock == 0) { SCTP_INP_READ_LOCK(inp); hold_rlock = 1; } } if (control->end_added) { out_flags |= MSG_EOR; if ((control->do_not_ref_stcb == 0) && ((control->spec_flags & M_NOTIFICATION) == 0)) control->stcb->asoc.strmin[control->sinfo_stream].delivery_started = 0; } if (control->spec_flags & M_NOTIFICATION) { out_flags |= MSG_NOTIFICATION; } uio->uio_resid = control->length; *mp = control->data; m = control->data; while (m) { #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, SCTP_BUF_LEN(m)); #endif sctp_sbfree(control, stcb, &so->so_rcv, m); freed_so_far += SCTP_BUF_LEN(m); #ifdef SCTP_SB_LOGGING sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0); #endif m = SCTP_BUF_NEXT(m); } control->data = control->tail_mbuf = NULL; control->length = 0; if (out_flags & MSG_EOR) { /* Done with this control */ goto done_with_control; } } release: if (hold_rlock == 1) { SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; } if (hold_sblock == 1) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } sbunlock(&so->so_rcv); sockbuf_lock = 0; release_unlocked: if (hold_sblock) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } if ((stcb) && (in_flags & MSG_PEEK) == 0) { if ((freed_so_far >= rwnd_req) && (control && (control->do_not_ref_stcb == 0)) && (no_rcv_needed == 0)) sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req); } if (msg_flags) *msg_flags |= out_flags; out: if (((out_flags & MSG_EOR) == 0) && ((in_flags & MSG_PEEK) == 0) && (sinfo) && (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO))) { struct sctp_extrcvinfo *s_extra; s_extra = (struct sctp_extrcvinfo *)sinfo; s_extra->sreinfo_next_flags = SCTP_NO_NEXT_MSG; } if (hold_rlock == 1) { SCTP_INP_READ_UNLOCK(inp); hold_rlock = 0; } if (hold_sblock) { SOCKBUF_UNLOCK(&so->so_rcv); hold_sblock = 0; } if (sockbuf_lock) { sbunlock(&so->so_rcv); } if (freecnt_applied) { /* * The lock on the socket buffer protects us so the free * code will stop. But since we used the socketbuf lock and * the sender uses the tcb_lock to increment, we need to use * the atomic add to the refcnt. */ if (stcb == NULL) { panic("stcb for refcnt has gone NULL?"); } atomic_add_int(&stcb->asoc.refcnt, -1); freecnt_applied = 0; /* Save the value back for next time */ stcb->freed_by_sorcv_sincelast = freed_so_far; } #ifdef SCTP_RECV_RWND_LOGGING if (stcb) { sctp_misc_ints(SCTP_SORECV_DONE, freed_so_far, ((uio) ? (slen - uio->uio_resid) : slen), stcb->asoc.my_rwnd, so->so_rcv.sb_cc); } else { sctp_misc_ints(SCTP_SORECV_DONE, freed_so_far, ((uio) ? (slen - uio->uio_resid) : slen), 0, so->so_rcv.sb_cc); } #endif if (wakeup_read_socket) { sctp_sorwakeup(inp, so); } return (error); } #ifdef SCTP_MBUF_LOGGING struct mbuf * sctp_m_free(struct mbuf *m) { if (SCTP_BUF_IS_EXTENDED(m)) { sctp_log_mb(m, SCTP_MBUF_IFREE); } return (m_free(m)); } void sctp_m_freem(struct mbuf *mb) { while (mb != NULL) mb = sctp_m_free(mb); } #endif int sctp_dynamic_set_primary(struct sockaddr *sa, uint32_t vrf_id) { /* * Given a local address. For all associations that holds the * address, request a peer-set-primary. */ struct sctp_ifa *ifa; struct sctp_laddr *wi; ifa = sctp_find_ifa_by_addr(sa, vrf_id, 0); if (ifa == NULL) { return (EADDRNOTAVAIL); } /* * Now that we have the ifa we must awaken the iterator with this * message. */ wi = SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr, struct sctp_laddr); if (wi == NULL) { return (ENOMEM); } /* Now incr the count and int wi structure */ SCTP_INCR_LADDR_COUNT(); bzero(wi, sizeof(*wi)); wi->ifa = ifa; wi->action = SCTP_SET_PRIM_ADDR; atomic_add_int(&ifa->refcount, 1); /* Now add it to the work queue */ SCTP_IPI_ITERATOR_WQ_LOCK(); /* * Should this really be a tailq? As it is we will process the * newest first :-0 */ LIST_INSERT_HEAD(&sctppcbinfo.addr_wq, wi, sctp_nxt_addr); sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ, (struct sctp_inpcb *)NULL, (struct sctp_tcb *)NULL, (struct sctp_nets *)NULL); SCTP_IPI_ITERATOR_WQ_UNLOCK(); return (0); } int sctp_soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { int error, fromlen; uint8_t sockbuf[256]; struct sockaddr *from; struct sctp_extrcvinfo sinfo; int filling_sinfo = 1; struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; /* pickup the assoc we are reading from */ if (inp == NULL) { return (EINVAL); } if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) || (controlp == NULL)) { /* user does not want the sndrcv ctl */ filling_sinfo = 0; } if (psa) { from = (struct sockaddr *)sockbuf; fromlen = sizeof(sockbuf); from->sa_len = 0; } else { from = NULL; fromlen = 0; } error = sctp_sorecvmsg(so, uio, mp0, from, fromlen, flagsp, (struct sctp_sndrcvinfo *)&sinfo, filling_sinfo); if ((controlp) && (filling_sinfo)) { /* copy back the sinfo in a CMSG format */ if (filling_sinfo) *controlp = sctp_build_ctl_nchunk(inp, (struct sctp_sndrcvinfo *)&sinfo); else *controlp = NULL; } if (psa) { /* copy back the address info */ if (from && from->sa_len) { *psa = sodupsockaddr(from, M_NOWAIT); } else { *psa = NULL; } } return (error); } int sctp_l_soreceive(struct socket *so, struct sockaddr **name, struct uio *uio, char **controlp, int *controllen, int *flag) { int error, fromlen; uint8_t sockbuf[256]; struct sockaddr *from; struct sctp_extrcvinfo sinfo; int filling_sinfo = 1; struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; /* pickup the assoc we are reading from */ if (inp == NULL) { return (EINVAL); } if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_RECVDATAIOEVNT)) || (controlp == NULL)) { /* user does not want the sndrcv ctl */ filling_sinfo = 0; } if (name) { from = (struct sockaddr *)sockbuf; fromlen = sizeof(sockbuf); from->sa_len = 0; } else { from = NULL; fromlen = 0; } error = sctp_sorecvmsg(so, uio, (struct mbuf **)NULL, from, fromlen, flag, (struct sctp_sndrcvinfo *)&sinfo, filling_sinfo); if ((controlp) && (filling_sinfo)) { /* * copy back the sinfo in a CMSG format note that the caller * has reponsibility for freeing the memory. */ if (filling_sinfo) *controlp = sctp_build_ctl_cchunk(inp, controllen, (struct sctp_sndrcvinfo *)&sinfo); } if (name) { /* copy back the address info */ if (from && from->sa_len) { *name = sodupsockaddr(from, M_WAIT); } else { *name = NULL; } } return (error); } int sctp_connectx_helper_add(struct sctp_tcb *stcb, struct sockaddr *addr, int totaddr, int *error) { int added = 0; int i; struct sctp_inpcb *inp; struct sockaddr *sa; size_t incr = 0; sa = addr; inp = stcb->sctp_ep; *error = 0; for (i = 0; i < totaddr; i++) { if (sa->sa_family == AF_INET) { incr = sizeof(struct sockaddr_in); if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) { /* assoc gone no un-lock */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_7); *error = ENOBUFS; goto out_now; } added++; } else if (sa->sa_family == AF_INET6) { incr = sizeof(struct sockaddr_in6); if (sctp_add_remote_addr(stcb, sa, SCTP_DONOT_SETSCOPE, SCTP_ADDR_IS_CONFIRMED)) { /* assoc gone no un-lock */ sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_8); *error = ENOBUFS; goto out_now; } added++; } sa = (struct sockaddr *)((caddr_t)sa + incr); } out_now: return (added); } struct sctp_tcb * sctp_connectx_helper_find(struct sctp_inpcb *inp, struct sockaddr *addr, int *totaddr, int *num_v4, int *num_v6, int *error, int max) { struct sockaddr *sa; struct sctp_tcb *stcb = NULL; size_t incr, at, i; at = incr = 0; sa = addr; *error = *num_v6 = *num_v4 = 0; /* account and validate addresses */ for (i = 0; i < *totaddr; i++) { if (sa->sa_family == AF_INET) { (*num_v4) += 1; incr = sizeof(struct sockaddr_in); } else if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)sa; if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { /* Must be non-mapped for connectx */ *error = EINVAL; return (NULL); } (*num_v6) += 1; incr = sizeof(struct sockaddr_in6); } else { *totaddr = i; /* we are done */ break; } stcb = sctp_findassociation_ep_addr(&inp, sa, NULL, NULL, NULL); if (stcb != NULL) { /* Already have or am bring up an association */ return (stcb); } if ((at + incr) > max) { *totaddr = i; break; } sa = (struct sockaddr *)((caddr_t)sa + incr); } return ((struct sctp_tcb *)NULL); } diff --git a/sys/netinet/sctputil.h b/sys/netinet/sctputil.h index 41a509219a41..43410fa31806 100644 --- a/sys/netinet/sctputil.h +++ b/sys/netinet/sctputil.h @@ -1,401 +1,401 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctputil.h,v 1.15 2005/03/06 16:04:19 itojun Exp $ */ #include __FBSDID("$FreeBSD$"); #ifndef __sctputil_h__ #define __sctputil_h__ #if defined(_KERNEL) /*- * Any new logging added must also define SCTP_STAT_LOGGING if * its not already defined. */ #if defined(SCTP_LOG_MAXBURST) || defined(SCTP_LOG_RWND) || defined(SCTP_LOG_RWND) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_CWND_LOGGING) || defined(SCTP_CWND_MONITOR) || defined(SCTP_BLK_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_STR_LOGGING) || defined(SCTP_FR_LOGGING) || defined(SCTP_MAP_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_SACK_LOGGING) || defined(SCTP_LOCK_LOGGING) || defined(SCTP_STAT_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_RTTVAR_LOGGING) || defined(SCTP_SB_LOGGING) || defined(SCTP_EARLYFR_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_NAGLE_LOGGING) || defined(SCTP_WAKE_LOGGING) || defined(SCTP_RECV_RWND_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_SACK_RWND_LOGGING) || defined(SCTP_FLIGHT_LOGGING) || defined(SCTP_MBUF_LOGGING) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #if defined(SCTP_LOG_SACK_ARRIVALS) #ifndef SCTP_STAT_LOGGING #define SCTP_STAT_LOGGING 1 #endif #endif #ifdef SCTP_ASOCLOG_OF_TSNS void sctp_print_out_track_log(struct sctp_tcb *stcb); #endif #ifdef SCTP_MBUF_LOGGING struct mbuf *sctp_m_free(struct mbuf *m); void sctp_m_freem(struct mbuf *m); #else #define sctp_m_free m_free #define sctp_m_freem m_freem #endif #define sctp_get_associd(stcb) ((sctp_assoc_t)stcb->asoc.assoc_id) /* * Function prototypes */ uint32_t sctp_get_ifa_hash_val(struct sockaddr *addr); struct sctp_ifa * sctp_find_ifa_in_ep(struct sctp_inpcb *inp, struct sockaddr *addr, int hold_lock); struct sctp_ifa * sctp_find_ifa_by_addr(struct sockaddr *addr, uint32_t vrf_id, int holds_lock); uint32_t sctp_select_initial_TSN(struct sctp_pcb *); uint32_t sctp_select_a_tag(struct sctp_inpcb *); int sctp_init_asoc(struct sctp_inpcb *, struct sctp_association *, int, uint32_t, uint32_t); void sctp_fill_random_store(struct sctp_pcb *); int sctp_timer_start(int, struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *); -int +void sctp_timer_stop(int, struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *, uint32_t); int sctp_dynamic_set_primary(struct sockaddr *sa, uint32_t vrf_id); uint32_t sctp_calculate_sum(struct mbuf *, int32_t *, uint32_t); void sctp_mtu_size_reset(struct sctp_inpcb *, struct sctp_association *, uint32_t); void sctp_add_to_readq(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_queued_to_read *control, struct sockbuf *sb, int end); int sctp_append_to_readq(struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_queued_to_read *control, struct mbuf *m, int end, int new_cumack, struct sockbuf *sb); void sctp_iterator_worker(void); int find_next_best_mtu(int); void sctp_timeout_handler(void *); uint32_t sctp_calculate_rto(struct sctp_tcb *, struct sctp_association *, struct sctp_nets *, struct timeval *); uint32_t sctp_calculate_len(struct mbuf *); caddr_t sctp_m_getptr(struct mbuf *, int, int, uint8_t *); struct sctp_paramhdr * sctp_get_next_param(struct mbuf *, int, struct sctp_paramhdr *, int); int sctp_add_pad_tombuf(struct mbuf *, int); int sctp_pad_lastmbuf(struct mbuf *, int, struct mbuf *); void sctp_ulp_notify(uint32_t, struct sctp_tcb *, uint32_t, void *); void sctp_pull_off_control_to_new_inp(struct sctp_inpcb *old_inp, struct sctp_inpcb *new_inp, struct sctp_tcb *stcb, int waitflags); void sctp_stop_timers_for_shutdown(struct sctp_tcb *); void sctp_report_all_outbound(struct sctp_tcb *, int); int sctp_expand_mapping_array(struct sctp_association *); void sctp_abort_notification(struct sctp_tcb *, int); /* We abort responding to an IP packet for some reason */ void sctp_abort_association(struct sctp_inpcb *, struct sctp_tcb *, struct mbuf *, int, struct sctphdr *, struct mbuf *, uint32_t, uint32_t); /* We choose to abort via user input */ void sctp_abort_an_association(struct sctp_inpcb *, struct sctp_tcb *, int, struct mbuf *); void sctp_handle_ootb(struct mbuf *, int, int, struct sctphdr *, struct sctp_inpcb *, struct mbuf *, uint32_t, uint32_t); int sctp_connectx_helper_add(struct sctp_tcb *stcb, struct sockaddr *addr, int totaddr, int *error); struct sctp_tcb * sctp_connectx_helper_find(struct sctp_inpcb *inp, struct sockaddr *addr, int *totaddr, int *num_v4, int *num_v6, int *error, int max); int sctp_is_there_an_abort_here(struct mbuf *, int, uint32_t *); uint32_t sctp_is_same_scope(struct sockaddr_in6 *, struct sockaddr_in6 *); struct sockaddr_in6 * sctp_recover_scope(struct sockaddr_in6 *, struct sockaddr_in6 *); #define sctp_recover_scope_mac(addr, store) do { \ if ((addr->sin6_family == AF_INET6) && \ (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr))) { \ *store = *addr; \ if (addr->sin6_scope_id == 0) { \ if (!sa6_recoverscope(store)) { \ addr = store; \ } \ } else { \ in6_clearscope(&addr->sin6_addr); \ addr = store; \ } \ } \ } while (0) int sctp_cmpaddr(struct sockaddr *, struct sockaddr *); void sctp_print_address(struct sockaddr *); void sctp_print_address_pkt(struct ip *, struct sctphdr *); void sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb, uint32_t error, int no_lock, uint32_t strseq); int sctp_release_pr_sctp_chunk(struct sctp_tcb *, struct sctp_tmit_chunk *, int, struct sctpchunk_listhead *); struct mbuf *sctp_generate_invmanparam(int); #ifdef SCTP_MBCNT_LOGGING void sctp_free_bufspace(struct sctp_tcb *, struct sctp_association *, struct sctp_tmit_chunk *); #else #define sctp_free_bufspace(stcb, asoc, tp1, chk_cnt) \ do { \ if (tp1->data != NULL) { \ atomic_add_int(&((asoc)->chunks_on_out_queue), -chk_cnt); \ if ((asoc)->total_output_queue_size >= tp1->book_size) { \ atomic_add_int(&((asoc)->total_output_queue_size), -tp1->book_size); \ } else { \ (asoc)->total_output_queue_size = 0; \ } \ if (stcb->sctp_socket && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \ if (stcb->sctp_socket->so_snd.sb_cc >= tp1->book_size) { \ atomic_add_int(&((stcb)->sctp_socket->so_snd.sb_cc), -tp1->book_size); \ } else { \ stcb->sctp_socket->so_snd.sb_cc = 0; \ } \ } \ } \ } while (0) #endif #define sctp_free_spbufspace(stcb, asoc, sp) \ do { \ if (sp->data != NULL) { \ atomic_add_int(&(asoc)->chunks_on_out_queue, -1); \ if ((asoc)->total_output_queue_size >= sp->length) { \ atomic_add_int(&(asoc)->total_output_queue_size,sp->length); \ } else { \ (asoc)->total_output_queue_size = 0; \ } \ if (stcb->sctp_socket && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \ if (stcb->sctp_socket->so_snd.sb_cc >= sp->length) { \ atomic_add_int(&stcb->sctp_socket->so_snd.sb_cc,sp->length); \ } else { \ stcb->sctp_socket->so_snd.sb_cc = 0; \ } \ } \ } \ } while (0) #define sctp_snd_sb_alloc(stcb, sz) \ do { \ atomic_add_int(&stcb->asoc.total_output_queue_size,sz); \ if ((stcb->sctp_socket != NULL) && \ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \ atomic_add_int(&stcb->sctp_socket->so_snd.sb_cc,sz); \ } \ } while (0) int sctp_soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp); /* For those not passing mbufs, this does the * translations for you. Caller owns memory * of size controllen returned in controlp. */ int sctp_l_soreceive(struct socket *so, struct sockaddr **name, struct uio *uio, char **controlp, int *controllen, int *flag); #ifdef SCTP_STAT_LOGGING void sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d); void sctp_wakeup_log(struct sctp_tcb *stcb, uint32_t cumtsn, uint32_t wake_cnt, int from); void sctp_log_strm_del_alt(struct sctp_tcb *stcb, uint32_t, uint16_t, uint16_t, int); void sctp_log_nagle_event(struct sctp_tcb *stcb, int action); void sctp_log_mb(struct mbuf *m, int from); void sctp_sblog(struct sockbuf *sb, struct sctp_tcb *stcb, int from, int incr); void sctp_log_strm_del(struct sctp_queued_to_read *control, struct sctp_queued_to_read *poschk, int from); void sctp_log_cwnd(struct sctp_tcb *stcb, struct sctp_nets *, int, uint8_t); void rto_logging(struct sctp_nets *net, int from); void sctp_log_closing(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int16_t loc); void sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from); void sctp_log_maxburst(struct sctp_tcb *stcb, struct sctp_nets *, int, int, uint8_t); void sctp_log_block(uint8_t, struct socket *, struct sctp_association *, int); void sctp_log_rwnd(uint8_t, uint32_t, uint32_t, uint32_t); void sctp_log_mbcnt(uint8_t, uint32_t, uint32_t, uint32_t, uint32_t); void sctp_log_rwnd_set(uint8_t, uint32_t, uint32_t, uint32_t, uint32_t); int sctp_fill_stat_log(void *, size_t *); void sctp_log_fr(uint32_t, uint32_t, uint32_t, int); void sctp_log_sack(uint32_t, uint32_t, uint32_t, uint16_t, uint16_t, int); void sctp_log_map(uint32_t, uint32_t, uint32_t, int); void sctp_clr_stat_log(void); #endif #ifdef SCTP_AUDITING_ENABLED void sctp_auditing(int, struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *); void sctp_audit_log(uint8_t, uint8_t); #endif #endif /* _KERNEL */ #endif diff --git a/sys/netinet6/sctp6_usrreq.c b/sys/netinet6/sctp6_usrreq.c index d9c0890a5818..9804571fa8ff 100644 --- a/sys/netinet6/sctp6_usrreq.c +++ b/sys/netinet6/sctp6_usrreq.c @@ -1,1282 +1,1282 @@ /*- * Copyright (c) 2001-2007, Cisco Systems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * a) Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * b) Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * * c) Neither the name of Cisco Systems, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* $KAME: sctp6_usrreq.c,v 1.38 2005/08/24 08:08:56 suz Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #if defined(INET6) #include #endif #include #include #include #include #include #include #include #include #include #include extern struct protosw inetsw[]; int sctp6_input(i_pak, offp, proto) struct mbuf **i_pak; int *offp; int proto; { struct mbuf *m; struct ip6_hdr *ip6; struct sctphdr *sh; struct sctp_inpcb *in6p = NULL; struct sctp_nets *net; int refcount_up = 0; uint32_t check, calc_check; uint32_t vrf_id = 0, table_id = 0; struct inpcb *in6p_ip; struct sctp_chunkhdr *ch; int length, mlen, offset, iphlen; uint8_t ecn_bits; struct sctp_tcb *stcb = NULL; int off = *offp; /* get the VRF and table id's */ if (SCTP_GET_PKT_VRFID(*i_pak, vrf_id)) { SCTP_RELEASE_PKT(*i_pak); return (-1); } if (SCTP_GET_PKT_TABLEID(*i_pak, table_id)) { SCTP_RELEASE_PKT(*i_pak); return (-1); } m = SCTP_HEADER_TO_CHAIN(*i_pak); ip6 = mtod(m, struct ip6_hdr *); /* Ensure that (sctphdr + sctp_chunkhdr) in a row. */ IP6_EXTHDR_GET(sh, struct sctphdr *, m, off, sizeof(*sh) + sizeof(*ch)); if (sh == NULL) { SCTP_STAT_INCR(sctps_hdrops); return IPPROTO_DONE; } ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr)); iphlen = off; offset = iphlen + sizeof(*sh) + sizeof(*ch); #if defined(NFAITH) && NFAITH > 0 if (faithprefix_p != NULL && (*faithprefix_p) (&ip6->ip6_dst)) { /* XXX send icmp6 host/port unreach? */ goto bad; } #endif /* NFAITH defined and > 0 */ SCTP_STAT_INCR(sctps_recvpackets); SCTP_STAT_INCR_COUNTER64(sctps_inpackets); #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("V6 input gets a packet iphlen:%d pktlen:%d\n", iphlen, SCTP_HEADER_LEN((*i_pak))); } #endif if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { /* No multi-cast support in SCTP */ goto bad; } /* destination port of 0 is illegal, based on RFC2960. */ if (sh->dest_port == 0) goto bad; if ((sctp_no_csum_on_loopback == 0) || (!SCTP_IS_IT_LOOPBACK(m))) { /* * we do NOT validate things from the loopback if the sysctl * is set to 1. */ check = sh->checksum; /* save incoming checksum */ if ((check == 0) && (sctp_no_csum_on_loopback)) { /* * special hook for where we got a local address * somehow routed across a non IFT_LOOP type * interface */ if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &ip6->ip6_dst)) goto sctp_skip_csum; } sh->checksum = 0; /* prepare for calc */ calc_check = sctp_calculate_sum(m, &mlen, iphlen); if (calc_check != check) { #ifdef SCTP_DEBUG if (sctp_debug_on & SCTP_DEBUG_INPUT1) { printf("Bad CSUM on SCTP packet calc_check:%x check:%x m:%p mlen:%d iphlen:%d\n", calc_check, check, m, mlen, iphlen); } #endif stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), sh, ch, &in6p, &net, vrf_id); /* in6p's ref-count increased && stcb locked */ if ((in6p) && (stcb)) { sctp_send_packet_dropped(stcb, net, m, iphlen, 1); sctp_chunk_output((struct sctp_inpcb *)in6p, stcb, 2); } else if ((in6p != NULL) && (stcb == NULL)) { refcount_up = 1; } SCTP_STAT_INCR(sctps_badsum); SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors); goto bad; } sh->checksum = calc_check; } sctp_skip_csum: net = NULL; /* * Locate pcb and tcb for datagram sctp_findassociation_addr() wants * IP/SCTP/first chunk header... */ stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), sh, ch, &in6p, &net, vrf_id); /* in6p's ref-count increased */ if (in6p == NULL) { struct sctp_init_chunk *init_chk, chunk_buf; SCTP_STAT_INCR(sctps_noport); if (ch->chunk_type == SCTP_INITIATION) { /* * we do a trick here to get the INIT tag, dig in * and get the tag from the INIT and put it in the * common header. */ init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, iphlen + sizeof(*sh), sizeof(*init_chk), (uint8_t *) & chunk_buf); sh->v_tag = init_chk->init.initiate_tag; } if (ch->chunk_type == SCTP_SHUTDOWN_ACK) { sctp_send_shutdown_complete2(m, iphlen, sh, vrf_id, table_id); goto bad; } if (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) { goto bad; } if (ch->chunk_type != SCTP_ABORT_ASSOCIATION) sctp_send_abort(m, iphlen, sh, 0, NULL, vrf_id, table_id); goto bad; } else if (stcb == NULL) { refcount_up = 1; } in6p_ip = (struct inpcb *)in6p; #ifdef IPSEC /* * Check AH/ESP integrity. */ if (in6p_ip && (ipsec6_in_reject(m, in6p_ip))) { /* XXX */ ipsec6stat.in_polvio++; goto bad; } #endif /* IPSEC */ /* * CONTROL chunk processing */ offset -= sizeof(*ch); ecn_bits = ((ntohl(ip6->ip6_flow) >> 20) & 0x000000ff); /* Length now holds the total packet length payload + iphlen */ length = ntohs(ip6->ip6_plen) + iphlen; - (void)sctp_common_input_processing(&m, iphlen, offset, length, sh, ch, + sctp_common_input_processing(&m, iphlen, offset, length, sh, ch, in6p, stcb, net, ecn_bits, vrf_id, table_id); /* inp's ref-count reduced && stcb unlocked */ /* XXX this stuff below gets moved to appropriate parts later... */ if (m) sctp_m_freem(m); if ((in6p) && refcount_up) { /* reduce ref-count */ SCTP_INP_WLOCK(in6p); SCTP_INP_DECR_REF(in6p); SCTP_INP_WUNLOCK(in6p); } return IPPROTO_DONE; bad: if (stcb) SCTP_TCB_UNLOCK(stcb); if ((in6p) && refcount_up) { /* reduce ref-count */ SCTP_INP_WLOCK(in6p); SCTP_INP_DECR_REF(in6p); SCTP_INP_WUNLOCK(in6p); } if (m) sctp_m_freem(m); /* For BSD/MAC this does nothing */ SCTP_DETACH_HEADER_FROM_CHAIN(*i_pak); SCTP_RELEASE_HEADER(*i_pak); return IPPROTO_DONE; } static void sctp6_notify_mbuf(struct sctp_inpcb *inp, struct icmp6_hdr *icmp6, struct sctphdr *sh, struct sctp_tcb *stcb, struct sctp_nets *net) { uint32_t nxtsz; if ((inp == NULL) || (stcb == NULL) || (net == NULL) || (icmp6 == NULL) || (sh == NULL)) { goto out; } /* First do we even look at it? */ if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) goto out; if (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG) { /* not PACKET TO BIG */ goto out; } /* * ok we need to look closely. We could even get smarter and look at * anyone that we sent to in case we get a different ICMP that tells * us there is no way to reach a host, but for this impl, all we * care about is MTU discovery. */ nxtsz = ntohl(icmp6->icmp6_mtu); /* Stop any PMTU timer */ sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL, SCTP_FROM_SCTP6_USRREQ + SCTP_LOC_1); /* Adjust destination size limit */ if (net->mtu > nxtsz) { net->mtu = nxtsz; } /* now what about the ep? */ if (stcb->asoc.smallest_mtu > nxtsz) { struct sctp_tmit_chunk *chk; /* Adjust that too */ stcb->asoc.smallest_mtu = nxtsz; /* now off to subtract IP_DF flag if needed */ TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) { if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > nxtsz) { chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; } } TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > nxtsz) { /* * For this guy we also mark for immediate * resend since we sent to big of chunk */ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; if (chk->sent != SCTP_DATAGRAM_RESEND) stcb->asoc.sent_queue_retran_cnt++; chk->sent = SCTP_DATAGRAM_RESEND; chk->rec.data.doing_fast_retransmit = 0; chk->sent = SCTP_DATAGRAM_RESEND; /* Clear any time so NO RTT is being done */ chk->sent_rcv_time.tv_sec = 0; chk->sent_rcv_time.tv_usec = 0; stcb->asoc.total_flight -= chk->send_size; net->flight_size -= chk->send_size; } } } sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL); out: if (stcb) SCTP_TCB_UNLOCK(stcb); } void sctp6_ctlinput(cmd, pktdst, d) int cmd; struct sockaddr *pktdst; void *d; { struct sctphdr sh; struct ip6ctlparam *ip6cp = NULL; uint32_t vrf_id; int cm; vrf_id = SCTP_DEFAULT_VRFID; if (pktdst->sa_family != AF_INET6 || pktdst->sa_len != sizeof(struct sockaddr_in6)) return; if ((unsigned)cmd >= PRC_NCMDS) return; if (PRC_IS_REDIRECT(cmd)) { d = NULL; } else if (inet6ctlerrmap[cmd] == 0) { return; } /* if the parameter is from icmp6, decode it. */ if (d != NULL) { ip6cp = (struct ip6ctlparam *)d; } else { ip6cp = (struct ip6ctlparam *)NULL; } if (ip6cp) { /* * XXX: We assume that when IPV6 is non NULL, M and OFF are * valid. */ /* check if we can safely examine src and dst ports */ struct sctp_inpcb *inp = NULL; struct sctp_tcb *stcb = NULL; struct sctp_nets *net = NULL; struct sockaddr_in6 final; if (ip6cp->ip6c_m == NULL) return; bzero(&sh, sizeof(sh)); bzero(&final, sizeof(final)); inp = NULL; net = NULL; m_copydata(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(sh), (caddr_t)&sh); ip6cp->ip6c_src->sin6_port = sh.src_port; final.sin6_len = sizeof(final); final.sin6_family = AF_INET6; final.sin6_addr = ((struct sockaddr_in6 *)pktdst)->sin6_addr; final.sin6_port = sh.dest_port; stcb = sctp_findassociation_addr_sa((struct sockaddr *)ip6cp->ip6c_src, (struct sockaddr *)&final, &inp, &net, 1, vrf_id); /* inp's ref-count increased && stcb locked */ if (stcb != NULL && inp && (inp->sctp_socket != NULL)) { if (cmd == PRC_MSGSIZE) { sctp6_notify_mbuf(inp, ip6cp->ip6c_icmp6, &sh, stcb, net); /* inp's ref-count reduced && stcb unlocked */ } else { if (cmd == PRC_HOSTDEAD) { cm = EHOSTUNREACH; } else { cm = inet6ctlerrmap[cmd]; } sctp_notify(inp, cm, &sh, (struct sockaddr *)&final, stcb, net); /* inp's ref-count reduced && stcb unlocked */ } } else { if (PRC_IS_REDIRECT(cmd) && inp) { in6_rtchange((struct in6pcb *)inp, inet6ctlerrmap[cmd]); } if (inp) { /* reduce inp's ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } if (stcb) SCTP_TCB_UNLOCK(stcb); } } } /* * this routine can probably be collasped into the one in sctp_userreq.c * since they do the same thing and now we lookup with a sockaddr */ static int sctp6_getcred(SYSCTL_HANDLER_ARGS) { struct xucred xuc; struct sockaddr_in6 addrs[2]; struct sctp_inpcb *inp; struct sctp_nets *net; struct sctp_tcb *stcb; int error; uint32_t vrf_id; vrf_id = SCTP_DEFAULT_VRFID; /* * XXXRW: Other instances of getcred use SUSER_ALLOWJAIL, as socket * visibility is scoped using cr_canseesocket(), which it is not * here. */ error = priv_check_cred(req->td->td_ucred, PRIV_NETINET_RESERVEDPORT, 0); if (error) return (error); if (req->newlen != sizeof(addrs)) return (EINVAL); if (req->oldlen != sizeof(struct ucred)) return (EINVAL); error = SYSCTL_IN(req, addrs, sizeof(addrs)); if (error) return (error); stcb = sctp_findassociation_addr_sa(sin6tosa(&addrs[0]), sin6tosa(&addrs[1]), &inp, &net, 1, vrf_id); if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) { if ((inp != NULL) && (stcb == NULL)) { /* reduce ref-count */ SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); goto cred_can_cont; } error = ENOENT; goto out; } SCTP_TCB_UNLOCK(stcb); /* * We use the write lock here, only since in the error leg we need * it. If we used RLOCK, then we would have to * wlock/decr/unlock/rlock. Which in theory could create a hole. * Better to use higher wlock. */ SCTP_INP_WLOCK(inp); cred_can_cont: error = cr_canseesocket(req->td->td_ucred, inp->sctp_socket); if (error) { SCTP_INP_WUNLOCK(inp); goto out; } cru2x(inp->sctp_socket->so_cred, &xuc); SCTP_INP_WUNLOCK(inp); error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); out: return (error); } SYSCTL_PROC(_net_inet6_sctp6, OID_AUTO, getcred, CTLTYPE_OPAQUE | CTLFLAG_RW, 0, 0, sctp6_getcred, "S,ucred", "Get the ucred of a SCTP6 connection"); /* This is the same as the sctp_abort() could be made common */ static void sctp6_abort(struct socket *so) { struct sctp_inpcb *inp; uint32_t flags; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return; sctp_must_try_again: flags = inp->sctp_flags; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 17); #endif if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) && (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 16); #endif sctp_inpcb_free(inp, 1, 0); SOCK_LOCK(so); SCTP_SB_CLEAR(so->so_snd); /* * same for the rcv ones, they are only here for the * accounting/select. */ SCTP_SB_CLEAR(so->so_rcv); /* Now null out the reference, we are completely detached. */ so->so_pcb = NULL; SOCK_UNLOCK(so); } else { flags = inp->sctp_flags; if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) { goto sctp_must_try_again; } } return; } static int sctp6_attach(struct socket *so, int proto, struct thread *p) { struct in6pcb *inp6; int error; struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; if (inp != NULL) return EINVAL; if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { error = SCTP_SORESERVE(so, sctp_sendspace, sctp_recvspace); if (error) return error; } error = sctp_inpcb_alloc(so); if (error) return error; inp = (struct sctp_inpcb *)so->so_pcb; inp->sctp_flags |= SCTP_PCB_FLAGS_BOUND_V6; /* I'm v6! */ inp6 = (struct in6pcb *)inp; inp6->inp_vflag |= INP_IPV6; inp6->in6p_hops = -1; /* use kernel default */ inp6->in6p_cksum = -1; /* just to be sure */ #ifdef INET /* * XXX: ugly!! IPv4 TTL initialization is necessary for an IPv6 * socket as well, because the socket may be bound to an IPv6 * wildcard address, which may match an IPv4-mapped IPv6 address. */ inp6->inp_ip_ttl = ip_defttl; #endif /* * Hmm what about the IPSEC stuff that is missing here but in * sctp_attach()? */ return 0; } static int sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) { struct sctp_inpcb *inp; struct in6pcb *inp6; int error; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return EINVAL; inp6 = (struct in6pcb *)inp; inp6->inp_vflag &= ~INP_IPV4; inp6->inp_vflag |= INP_IPV6; if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) { if (addr->sa_family == AF_INET) { /* binding v4 addr to v6 socket, so reset flags */ inp6->inp_vflag |= INP_IPV4; inp6->inp_vflag &= ~INP_IPV6; } else { struct sockaddr_in6 *sin6_p; sin6_p = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) { inp6->inp_vflag |= INP_IPV4; } else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { struct sockaddr_in sin; in6_sin6_2_sin(&sin, sin6_p); inp6->inp_vflag |= INP_IPV4; inp6->inp_vflag &= ~INP_IPV6; error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, p); return error; } } } else if (addr != NULL) { /* IPV6_V6ONLY socket */ if (addr->sa_family == AF_INET) { /* can't bind v4 addr to v6 only socket! */ return EINVAL; } else { struct sockaddr_in6 *sin6_p; sin6_p = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) /* can't bind v4-mapped addrs either! */ /* NOTE: we don't support SIIT */ return EINVAL; } } error = sctp_inpcb_bind(so, addr, p); return error; } static void sctp6_close(struct socket *so) { struct sctp_inpcb *inp; uint32_t flags; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) return; /* * Inform all the lower layer assoc that we are done. */ sctp_must_try_again: flags = inp->sctp_flags; #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 17); #endif if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) && (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) { if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || (so->so_rcv.sb_cc > 0)) { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 13); #endif sctp_inpcb_free(inp, 1, 1); } else { #ifdef SCTP_LOG_CLOSING sctp_log_closing(inp, NULL, 14); #endif sctp_inpcb_free(inp, 0, 1); } /* * The socket is now detached, no matter what the state of * the SCTP association. */ SOCK_LOCK(so); SCTP_SB_CLEAR(so->so_snd); /* * same for the rcv ones, they are only here for the * accounting/select. */ SCTP_SB_CLEAR(so->so_rcv); /* Now null out the reference, we are completely detached. */ so->so_pcb = NULL; SOCK_UNLOCK(so); } else { flags = inp->sctp_flags; if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) { goto sctp_must_try_again; } } return; } /* This could be made common with sctp_detach() since they are identical */ static int sctp6_disconnect(struct socket *so) { struct sctp_inpcb *inp; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { return (ENOTCONN); } SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { if (SCTP_LIST_EMPTY(&inp->sctp_asoc_list)) { /* No connection */ SCTP_INP_RUNLOCK(inp); return (ENOTCONN); } else { int some_on_streamwheel = 0; struct sctp_association *asoc; struct sctp_tcb *stcb; stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { SCTP_INP_RUNLOCK(inp); return (EINVAL); } SCTP_TCB_LOCK(stcb); asoc = &stcb->asoc; if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || (so->so_rcv.sb_cc > 0)) { if (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) { /* Left with Data unread */ struct mbuf *op_err; op_err = sctp_generate_invmanparam(SCTP_CAUSE_USER_INITIATED_ABT); sctp_send_abort_tcb(stcb, op_err); SCTP_STAT_INCR_COUNTER32(sctps_aborted); } SCTP_INP_RUNLOCK(inp); if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } sctp_free_assoc(inp, stcb, SCTP_DONOT_SETSCOPE, SCTP_FROM_SCTP6_USRREQ + SCTP_LOC_2); /* No unlock tcb assoc is gone */ return (0); } if (!TAILQ_EMPTY(&asoc->out_wheel)) { /* Check to see if some data queued */ struct sctp_stream_out *outs; TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { if (!TAILQ_EMPTY(&outs->outqueue)) { some_on_streamwheel = 1; break; } } } if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && (some_on_streamwheel == 0)) { /* nothing queued to send, so I'm done... */ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { /* only send SHUTDOWN the first time */ sctp_send_shutdown(stcb, stcb->asoc.primary_destination); sctp_chunk_output(stcb->sctp_ep, stcb, 1); if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) || (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) { SCTP_STAT_DECR_GAUGE32(sctps_currestab); } asoc->state = SCTP_STATE_SHUTDOWN_SENT; sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, asoc->primary_destination); sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, asoc->primary_destination); } } else { /* * we still got (or just got) data to send, * so set SHUTDOWN_PENDING */ /* * XXX sockets draft says that MSG_EOF * should be sent with no data. currently, * we will allow user data to be sent first * and move to SHUTDOWN-PENDING */ asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; } SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); return (0); } } else { /* UDP model does not support this */ SCTP_INP_RUNLOCK(inp); return EOPNOTSUPP; } } int sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *p); static int sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *p) { struct sctp_inpcb *inp; struct inpcb *in_inp; struct in6pcb *inp6; #ifdef INET struct sockaddr_in6 *sin6; #endif /* INET */ /* No SPL needed since sctp_output does this */ inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { if (control) { SCTP_RELEASE_PKT(control); control = NULL; } SCTP_RELEASE_PKT(m); return EINVAL; } in_inp = (struct inpcb *)inp; inp6 = (struct in6pcb *)inp; /* * For the TCP model we may get a NULL addr, if we are a connected * socket thats ok. */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) && (addr == NULL)) { goto connected_type; } if (addr == NULL) { SCTP_RELEASE_PKT(m); if (control) { SCTP_RELEASE_PKT(control); control = NULL; } return (EDESTADDRREQ); } #ifdef INET sin6 = (struct sockaddr_in6 *)addr; if (SCTP_IPV6_V6ONLY(inp6)) { /* * if IPV6_V6ONLY flag, we discard datagrams destined to a * v4 addr or v4-mapped addr */ if (addr->sa_family == AF_INET) { return EINVAL; } if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { return EINVAL; } } if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { if (!ip6_v6only) { struct sockaddr_in sin; /* convert v4-mapped into v4 addr and send */ in6_sin6_2_sin(&sin, sin6); return sctp_sendm(so, flags, m, (struct sockaddr *)&sin, control, p); } else { /* mapped addresses aren't enabled */ return EINVAL; } } #endif /* INET */ connected_type: /* now what about control */ if (control) { if (inp->control) { printf("huh? control set?\n"); SCTP_RELEASE_PKT(inp->control); inp->control = NULL; } inp->control = control; } /* Place the data */ if (inp->pkt) { SCTP_BUF_NEXT(inp->pkt_last) = m; inp->pkt_last = m; } else { inp->pkt_last = inp->pkt = m; } if ( /* FreeBSD and MacOSX uses a flag passed */ ((flags & PRUS_MORETOCOME) == 0) ) { /* * note with the current version this code will only be used * by OpenBSD, NetBSD and FreeBSD have methods for * re-defining sosend() to use sctp_sosend(). One can * optionaly switch back to this code (by changing back the * defininitions but this is not advisable. */ int ret; ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags); inp->pkt = NULL; inp->control = NULL; return (ret); } else { return (0); } } static int sctp6_connect(struct socket *so, struct sockaddr *addr, struct thread *p) { uint32_t vrf_id; int error = 0; struct sctp_inpcb *inp; struct in6pcb *inp6; struct sctp_tcb *stcb; #ifdef INET struct sockaddr_in6 *sin6; struct sockaddr_storage ss; #endif /* INET */ inp6 = (struct in6pcb *)so->so_pcb; inp = (struct sctp_inpcb *)so->so_pcb; if (inp == 0) { return (ECONNRESET); /* I made the same as TCP since we are * not setup? */ } vrf_id = inp->def_vrf_id; SCTP_ASOC_CREATE_LOCK(inp); SCTP_INP_RLOCK(inp); if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == SCTP_PCB_FLAGS_UNBOUND) { /* Bind a ephemeral port */ SCTP_INP_RUNLOCK(inp); error = sctp6_bind(so, NULL, p); if (error) { SCTP_ASOC_CREATE_UNLOCK(inp); return (error); } SCTP_INP_RLOCK(inp); } if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { /* We are already connected AND the TCP model */ SCTP_INP_RUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); return (EADDRINUSE); } #ifdef INET sin6 = (struct sockaddr_in6 *)addr; if (SCTP_IPV6_V6ONLY(inp6)) { /* * if IPV6_V6ONLY flag, ignore connections destined to a v4 * addr or v4-mapped addr */ if (addr->sa_family == AF_INET) { SCTP_INP_RUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); return EINVAL; } if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { SCTP_INP_RUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); return EINVAL; } } if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { if (!ip6_v6only) { /* convert v4-mapped into v4 addr */ in6_sin6_2_sin((struct sockaddr_in *)&ss, sin6); addr = (struct sockaddr *)&ss; } else { /* mapped addresses aren't enabled */ SCTP_INP_RUNLOCK(inp); SCTP_ASOC_CREATE_UNLOCK(inp); return EINVAL; } } else #endif /* INET */ addr = addr; /* for true v6 address case */ /* Now do we connect? */ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb) SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); } else { SCTP_INP_RUNLOCK(inp); SCTP_INP_WLOCK(inp); SCTP_INP_INCR_REF(inp); SCTP_INP_WUNLOCK(inp); stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL); if (stcb == NULL) { SCTP_INP_WLOCK(inp); SCTP_INP_DECR_REF(inp); SCTP_INP_WUNLOCK(inp); } } if (stcb != NULL) { /* Already have or am bring up an association */ SCTP_ASOC_CREATE_UNLOCK(inp); SCTP_TCB_UNLOCK(stcb); return (EALREADY); } /* We are GOOD to go */ stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0, vrf_id); SCTP_ASOC_CREATE_UNLOCK(inp); if (stcb == NULL) { /* Gak! no memory */ return (error); } if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; /* Set the connected flag so we can queue data */ soisconnecting(so); } stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); /* initialize authentication parameters for the assoc */ sctp_initialize_auth_params(inp, stcb); sctp_send_initiate(inp, stcb); SCTP_TCB_UNLOCK(stcb); return error; } static int sctp6_getaddr(struct socket *so, struct sockaddr **addr) { struct sockaddr_in6 *sin6; struct sctp_inpcb *inp; uint32_t vrf_id; struct sctp_ifa *sctp_ifa; int error; /* * Do the malloc first in case it blocks. */ SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { SCTP_FREE_SONAME(sin6); return ECONNRESET; } SCTP_INP_RLOCK(inp); sin6->sin6_port = inp->sctp_lport; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* For the bound all case you get back 0 */ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { struct sctp_tcb *stcb; struct sockaddr_in6 *sin_a6; struct sctp_nets *net; int fnd; stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { goto notConn6; } fnd = 0; sin_a6 = NULL; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr; if (sin_a6 == NULL) /* this will make coverity happy */ continue; if (sin_a6->sin6_family == AF_INET6) { fnd = 1; break; } } if ((!fnd) || (sin_a6 == NULL)) { /* punt */ goto notConn6; } vrf_id = inp->def_vrf_id; sctp_ifa = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, vrf_id); if (sctp_ifa) { sin6->sin6_addr = sctp_ifa->address.sin6.sin6_addr; } } else { /* For the bound all case you get back 0 */ notConn6: memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr)); } } else { /* Take the first IPv6 address in the list */ struct sctp_laddr *laddr; int fnd = 0; LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa->address.sa.sa_family == AF_INET6) { struct sockaddr_in6 *sin_a; sin_a = (struct sockaddr_in6 *)&laddr->ifa->address.sin6; sin6->sin6_addr = sin_a->sin6_addr; fnd = 1; break; } } if (!fnd) { SCTP_FREE_SONAME(sin6); SCTP_INP_RUNLOCK(inp); return ENOENT; } } SCTP_INP_RUNLOCK(inp); /* Scoping things for v6 */ if ((error = sa6_recoverscope(sin6)) != 0) { SCTP_FREE_SONAME(sin6); return (error); } (*addr) = (struct sockaddr *)sin6; return (0); } static int sctp6_peeraddr(struct socket *so, struct sockaddr **addr) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)*addr; int fnd; struct sockaddr_in6 *sin_a6; struct sctp_inpcb *inp; struct sctp_tcb *stcb; struct sctp_nets *net; int error; /* * Do the malloc first in case it blocks. */ inp = (struct sctp_inpcb *)so->so_pcb; if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) { /* UDP type and listeners will drop out here */ return (ENOTCONN); } SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); /* We must recapture incase we blocked */ inp = (struct sctp_inpcb *)so->so_pcb; if (inp == NULL) { SCTP_FREE_SONAME(sin6); return ECONNRESET; } SCTP_INP_RLOCK(inp); stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb) SCTP_TCB_LOCK(stcb); SCTP_INP_RUNLOCK(inp); if (stcb == NULL) { SCTP_FREE_SONAME(sin6); return ECONNRESET; } fnd = 0; TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr; if (sin_a6->sin6_family == AF_INET6) { fnd = 1; sin6->sin6_port = stcb->rport; sin6->sin6_addr = sin_a6->sin6_addr; break; } } SCTP_TCB_UNLOCK(stcb); if (!fnd) { /* No IPv4 address */ SCTP_FREE_SONAME(sin6); return ENOENT; } if ((error = sa6_recoverscope(sin6)) != 0) return (error); *addr = (struct sockaddr *)sin6; return (0); } static int sctp6_in6getaddr(struct socket *so, struct sockaddr **nam) { struct sockaddr *addr; struct in6pcb *inp6 = sotoin6pcb(so); int error; if (inp6 == NULL) return EINVAL; /* allow v6 addresses precedence */ error = sctp6_getaddr(so, nam); if (error) { /* try v4 next if v6 failed */ error = sctp_ingetaddr(so, nam); if (error) { return (error); } addr = *nam; /* if I'm V6ONLY, convert it to v4-mapped */ if (SCTP_IPV6_V6ONLY(inp6)) { struct sockaddr_in6 sin6; in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6); memcpy(addr, &sin6, sizeof(struct sockaddr_in6)); } } return (error); } static int sctp6_getpeeraddr(struct socket *so, struct sockaddr **nam) { struct sockaddr *addr = *nam; struct in6pcb *inp6 = sotoin6pcb(so); int error; if (inp6 == NULL) return EINVAL; /* allow v6 addresses precedence */ error = sctp6_peeraddr(so, nam); if (error) { /* try v4 next if v6 failed */ error = sctp_peeraddr(so, nam); if (error) { return (error); } /* if I'm V6ONLY, convert it to v4-mapped */ if (SCTP_IPV6_V6ONLY(inp6)) { struct sockaddr_in6 sin6; in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6); memcpy(addr, &sin6, sizeof(struct sockaddr_in6)); } } return error; } struct pr_usrreqs sctp6_usrreqs = { .pru_abort = sctp6_abort, .pru_accept = sctp_accept, .pru_attach = sctp6_attach, .pru_bind = sctp6_bind, .pru_connect = sctp6_connect, .pru_control = in6_control, .pru_close = sctp6_close, .pru_detach = sctp6_close, .pru_sopoll = sopoll_generic, .pru_disconnect = sctp6_disconnect, .pru_listen = sctp_listen, .pru_peeraddr = sctp6_getpeeraddr, .pru_send = sctp6_send, .pru_shutdown = sctp_shutdown, .pru_sockaddr = sctp6_in6getaddr, .pru_sosend = sctp_sosend, .pru_soreceive = sctp_soreceive };