freebsd-dev/sys/netinet/libalias/alias_sctp.c
Randall Stewart eef9e53e55 This commit fixes the issue with alias_sctp.c. No
longer do we require SCTP to be in the kernel for the
lib to be able to handle SCTP. We do this by moving
the CRC32c checksum into libkern/crc32.c and then adjusting
all routines to use the common methods. Note that this
will improve the performance of iSCSI since they were
using the old single 256 bit table lookup versus the
slicing 8 algorithm (which gives a 4x speed up in
CRC32c calculation :-D)

Reviewed by:rwatson, gnn, scottl, paolo
MFC after:	4 week? (assuming we MFC the alias_sctp changes)
2009-02-14 11:34:57 +00:00

2703 lines
98 KiB
C

/**
* @file alias_sctp.c
* Copyright (c) 2008, Centre for Advanced Internet Architectures
* Swinburne University of Technology, Melbourne, Australia
* (CRICOS number 00111D).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The names of the authors, the "Centre for Advanced Internet Architectures"
* and "Swinburne University of Technology" may not be used to endorse
* or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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.
*
* Alias_sctp forms part of the libalias kernel module to handle
* Network Address Translation (NAT) for the SCTP protocol.
*
* This software was developed by David A. Hayes and Jason But
*
* The design is outlined in CAIA technical report number 080618A
* (D. Hayes and J. But, "Alias_sctp Version 0.1: SCTP NAT implementation in IPFW")
*
* Development is part of the CAIA SONATA project,
* proposed by Jason But and Grenville Armitage:
* http://caia.swin.edu.au/urp/sonata/
*
*
* This project has been made possible in part by a grant from
* the Cisco University Research Program Fund at Community
* Foundation Silicon Valley.
*
*/
/** @mainpage
* Alias_sctp is part of the SONATA (http://caia.swin.edu.au/urp/sonata) project
* to develop and release a BSD licensed implementation of a Network Address
* Translation (NAT) module that supports the Stream Control Transmission
* Protocol (SCTP).
*
* Traditional address and port number look ups are inadequate for SCTP's
* operation due to both processing requirements and issues with multi-homing.
* Alias_sctp integrates with FreeBSD's ipfw/libalias NAT system.
*
* Version 0.2 features include:
* - Support for global multi-homing
* - Support for ASCONF modification from Internet Draft
* (draft-stewart-behave-sctpnat-04, R. Stewart and M. Tuexen, "Stream control
* transmission protocol (SCTP) network address translation," Jul. 2008) to
* provide support for multi-homed privately addressed hosts
* - Support for forwarding of T-flagged packets
* - Generation and delivery of AbortM/ErrorM packets upon detection of NAT
* collisions
* - Per-port forwarding rules
* - Dynamically controllable logging and statistics
* - Dynamic management of timers
* - Dynamic control of hash-table size
*/
/* $FreeBSD$ */
#ifdef _KERNEL
#include <machine/stdarg.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/syslog.h>
#include <netinet/libalias/alias_sctp.h>
#include <netinet/libalias/alias.h>
#include <netinet/libalias/alias_local.h>
#include <netinet/sctp_crc32.h>
#include <machine/in_cksum.h>
#else
#include "alias_sctp.h"
#include <arpa/inet.h>
#include "alias.h"
#include "alias_local.h"
#include <machine/in_cksum.h>
#include <sys/libkern.h>
#endif //#ifdef _KERNEL
/* ----------------------------------------------------------------------
* FUNCTION PROTOTYPES
* ----------------------------------------------------------------------
*/
/* Packet Parsing Functions */
static int sctp_PktParser(struct libalias *la, int direction, struct ip *pip,
struct sctp_nat_msg *sm, struct sctp_nat_assoc **passoc);
static int GetAsconfVtags(struct libalias *la, struct sctp_nat_msg *sm,
uint32_t *l_vtag, uint32_t *g_vtag, int direction);
static int IsASCONFack(struct libalias *la, struct sctp_nat_msg *sm, int direction);
static void AddGlobalIPAddresses(struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int direction);
static int Add_Global_Address_to_List(struct sctp_nat_assoc *assoc, struct sctp_GlobalAddress *G_addr);
static void RmGlobalIPAddresses(struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int direction);
static int IsADDorDEL(struct libalias *la, struct sctp_nat_msg *sm, int direction);
/* State Machine Functions */
static int ProcessSctpMsg(struct libalias *la, int direction, \
struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc);
static int ID_process(struct libalias *la, int direction,\
struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm);
static int INi_process(struct libalias *la, int direction,\
struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm);
static int INa_process(struct libalias *la, int direction,\
struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm);
static int UP_process(struct libalias *la, int direction,\
struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm);
static int CL_process(struct libalias *la, int direction,\
struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm);
static void TxAbortErrorM(struct libalias *la, struct sctp_nat_msg *sm,\
struct sctp_nat_assoc *assoc, int sndrply, int direction);
/* Hash Table Functions */
static struct sctp_nat_assoc*
FindSctpLocal(struct libalias *la, struct in_addr l_addr, struct in_addr g_addr, uint32_t l_vtag, uint16_t l_port, uint16_t g_port);
static struct sctp_nat_assoc*
FindSctpGlobal(struct libalias *la, struct in_addr g_addr, uint32_t g_vtag, uint16_t g_port, uint16_t l_port, int *partial_match);
static struct sctp_nat_assoc*
FindSctpGlobalClash(struct libalias *la, struct sctp_nat_assoc *Cassoc);
static struct sctp_nat_assoc*
FindSctpLocalT(struct libalias *la, struct in_addr g_addr, uint32_t l_vtag, uint16_t g_port, uint16_t l_port);
static struct sctp_nat_assoc*
FindSctpGlobalT(struct libalias *la, struct in_addr g_addr, uint32_t g_vtag, uint16_t l_port, uint16_t g_port);
static int AddSctpAssocLocal(struct libalias *la, struct sctp_nat_assoc *assoc, struct in_addr g_addr);
static int AddSctpAssocGlobal(struct libalias *la, struct sctp_nat_assoc *assoc);
static void RmSctpAssoc(struct libalias *la, struct sctp_nat_assoc *assoc);
static void freeGlobalAddressList(struct sctp_nat_assoc *assoc);
/* Timer Queue Functions */
static void sctp_AddTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc);
static void sctp_RmTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc);
static void sctp_ResetTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc, int newexp);
void sctp_CheckTimers(struct libalias *la);
/* Logging Functions */
static void logsctperror(char* errormsg, uint32_t vtag, int error, int direction);
static void logsctpparse(int direction, struct sctp_nat_msg *sm);
static void logsctpassoc(struct sctp_nat_assoc *assoc, char *s);
static void logTimerQ(struct libalias *la);
static void logSctpGlobal(struct libalias *la);
static void logSctpLocal(struct libalias *la);
#ifdef _KERNEL
static void SctpAliasLog(const char *format, ...);
#endif
/** @defgroup external External code changes and modifications
*
* Some changes have been made to files external to alias_sctp.(c|h). These
* changes are primarily due to code needing to call static functions within
* those files or to perform extra functionality that can only be performed
* within these files.
*/
/** @ingroup external
* @brief Log current statistics for the libalias instance
*
* This function is defined in alias_db.c, since it calls static functions in
* this file
*
* Calls the higher level ShowAliasStats() in alias_db.c which logs all current
* statistics about the libalias instance - including SCTP statistics
*
* @param la Pointer to the libalias instance
*/
void SctpShowAliasStats(struct libalias *la);
#ifdef _KERNEL
MALLOC_DEFINE(M_SCTPNAT, "sctpnat", "sctp nat dbs");
/* Use kernel allocator. */
#ifdef _SYS_MALLOC_H_
#define sn_malloc(x) malloc(x, M_SCTPNAT, M_NOWAIT|M_ZERO)
#define sn_calloc(n,x) sn_malloc(x * n)
#define sn_free(x) free(x, M_SCTPNAT)
#endif// #ifdef _SYS_MALLOC_H_
#else //#ifdef _KERNEL
#define sn_malloc(x) malloc(x)
#define sn_calloc(n, x) calloc(n, x)
#define sn_free(x) free(x)
#endif //#ifdef _KERNEL
/** @defgroup packet_parser SCTP Packet Parsing
*
* Macros to:
* - Return pointers to the first and next SCTP chunks within an SCTP Packet
* - Define possible return values of the packet parsing process
* - SCTP message types for storing in the sctp_nat_msg structure @{
*/
#define SN_SCTP_FIRSTCHUNK(sctphead) (struct sctp_chunkhdr *)(((char *)sctphead) + sizeof(struct sctphdr))
/**< Returns a pointer to the first chunk in an SCTP packet given a pointer to the SCTP header */
#define SN_SCTP_NEXTCHUNK(chunkhead) (struct sctp_chunkhdr *)(((char *)chunkhead) + SCTP_SIZE32(ntohs(chunkhead->chunk_length)))
/**< Returns a pointer to the next chunk in an SCTP packet given a pointer to the current chunk */
#define SN_SCTP_NEXTPARAM(param) (struct sctp_paramhdr *)(((char *)param) + SCTP_SIZE32(ntohs(param->param_length)))
/**< Returns a pointer to the next parameter in an SCTP packet given a pointer to the current parameter */
#define SN_MIN_CHUNK_SIZE 4 /**< Smallest possible SCTP chunk size in bytes */
#define SN_MIN_PARAM_SIZE 4 /**< Smallest possible SCTP param size in bytes */
#define SN_VTAG_PARAM_SIZE 12 /**< Size of SCTP ASCONF vtag param in bytes */
#define SN_ASCONFACK_PARAM_SIZE 8 /**< Size of SCTP ASCONF ACK param in bytes */
/* Packet parsing return codes */
#define SN_PARSE_OK 0 /**< Packet parsed for SCTP messages */
#define SN_PARSE_ERROR_IPSHL 1 /**< Packet parsing error - IP and SCTP common header len */
#define SN_PARSE_ERROR_AS_MALLOC 2 /**< Packet parsing error - assoc malloc */
#define SN_PARSE_ERROR_CHHL 3 /**< Packet parsing error - Chunk header len */
#define SN_PARSE_ERROR_DIR 4 /**< Packet parsing error - Direction */
#define SN_PARSE_ERROR_VTAG 5 /**< Packet parsing error - Vtag */
#define SN_PARSE_ERROR_CHUNK 6 /**< Packet parsing error - Chunk */
#define SN_PARSE_ERROR_PORT 7 /**< Packet parsing error - Port=0 */
#define SN_PARSE_ERROR_LOOKUP 8 /**< Packet parsing error - Lookup */
#define SN_PARSE_ERROR_PARTIALLOOKUP 9 /**< Packet parsing error - partial lookup only found */
#define SN_PARSE_ERROR_LOOKUP_ABORT 10 /**< Packet parsing error - Lookup - but abort packet */
/* Alias_sctp performs its processing based on a number of key messages */
#define SN_SCTP_ABORT 0x0000 /**< a packet containing an ABORT chunk */
#define SN_SCTP_INIT 0x0001 /**< a packet containing an INIT chunk */
#define SN_SCTP_INITACK 0x0002 /**< a packet containing an INIT-ACK chunk */
#define SN_SCTP_SHUTCOMP 0x0010 /**< a packet containing a SHUTDOWN-COMPLETE chunk */
#define SN_SCTP_SHUTACK 0x0020 /**< a packet containing a SHUTDOWN-ACK chunk */
#define SN_SCTP_ASCONF 0x0100 /**< a packet containing an ASCONF chunk */
#define SN_SCTP_ASCONFACK 0x0200 /**< a packet containing an ASCONF-ACK chunk */
#define SN_SCTP_OTHER 0xFFFF /**< a packet containing a chunk that is not of interest */
/** @}
* @defgroup state_machine SCTP NAT State Machine
*
* Defines the various states an association can be within the NAT @{
*/
#define SN_ID 0x0000 /**< Idle state */
#define SN_INi 0x0010 /**< Initialising, waiting for InitAck state */
#define SN_INa 0x0020 /**< Initialising, waiting for AddIpAck state */
#define SN_UP 0x0100 /**< Association in UP state */
#define SN_CL 0x1000 /**< Closing state */
#define SN_RM 0x2000 /**< Removing state */
/** @}
* @defgroup Logging Logging Functionality
*
* Define various log levels and a macro to call specified log functions only if
* the current log level (sysctl_log_level) matches the specified level @{
*/
#define SN_LOG_LOW 0
#define SN_LOG_EVENT 1
#define SN_LOG_INFO 2
#define SN_LOG_DETAIL 3
#define SN_LOG_DEBUG 4
#define SN_LOG_DEBUG_MAX 5
#define SN_LOG(level, action) if (sysctl_log_level >= level) { action; } /**< Perform log action ONLY if the current log level meets the specified log level */
/** @}
* @defgroup Hash Hash Table Macros and Functions
*
* Defines minimum/maximum/default values for the hash table size @{
*/
#define SN_MIN_HASH_SIZE 101 /**< Minimum hash table size (set to stop users choosing stupid values) */
#define SN_MAX_HASH_SIZE 1000001 /**< Maximum hash table size (NB must be less than max int) */
#define SN_DEFAULT_HASH_SIZE 2003 /**< A reasonable default size for the hash tables */
#define SN_LOCAL_TBL 0x01 /**< assoc in local table */
#define SN_GLOBAL_TBL 0x02 /**< assoc in global table */
#define SN_BOTH_TBL 0x03 /**< assoc in both tables */
#define SN_WAIT_TOLOCAL 0x10 /**< assoc waiting for TOLOCAL asconf ACK*/
#define SN_WAIT_TOGLOBAL 0x20 /**< assoc waiting for TOLOCAL asconf ACK*/
#define SN_NULL_TBL 0x00 /**< assoc in No table */
#define SN_MAX_GLOBAL_ADDRESSES 100 /**< absolute maximum global address count*/
#define SN_ADD_OK 0 /**< Association added to the table */
#define SN_ADD_CLASH 1 /**< Clash when trying to add the assoc. info to the table */
#define SN_TABLE_HASH(vtag, port, size) (((u_int) vtag + (u_int) port) % (u_int) size) /**< Calculate the hash table lookup position */
/** @}
* @defgroup Timer Timer Queue Macros and Functions
*
* Timer macros set minimum/maximum timeout values and calculate timer expiry
* times for the provided libalias instance @{
*/
#define SN_MIN_TIMER 1
#define SN_MAX_TIMER 600
#define SN_TIMER_QUEUE_SIZE SN_MAX_TIMER+2
#define SN_I_T(la) (la->timeStamp + sysctl_init_timer) /**< INIT State expiration time in seconds */
#define SN_U_T(la) (la->timeStamp + sysctl_up_timer) /**< UP State expiration time in seconds */
#define SN_C_T(la) (la->timeStamp + sysctl_shutdown_timer) /**< CL State expiration time in seconds */
#define SN_X_T(la) (la->timeStamp + sysctl_holddown_timer) /**< Wait after a shutdown complete in seconds */
/** @}
* @defgroup sysctl SysCtl Variable and callback function declarations
*
* Sysctl variables to modify NAT functionality in real-time along with associated functions
* to manage modifications to the sysctl variables @{
*/
/* Callbacks */
int sysctl_chg_loglevel(SYSCTL_HANDLER_ARGS);
int sysctl_chg_timer(SYSCTL_HANDLER_ARGS);
int sysctl_chg_hashtable_size(SYSCTL_HANDLER_ARGS);
int sysctl_chg_error_on_ootb(SYSCTL_HANDLER_ARGS);
int sysctl_chg_accept_global_ootb_addip(SYSCTL_HANDLER_ARGS);
int sysctl_chg_initialising_chunk_proc_limit(SYSCTL_HANDLER_ARGS);
int sysctl_chg_chunk_proc_limit(SYSCTL_HANDLER_ARGS);
int sysctl_chg_param_proc_limit(SYSCTL_HANDLER_ARGS);
int sysctl_chg_track_global_addresses(SYSCTL_HANDLER_ARGS);
/* Sysctl variables */
/** @brief net.inet.ip.alias.sctp.log_level */
static u_int sysctl_log_level = 0; /**< Stores the current level of logging */
/** @brief net.inet.ip.alias.sctp.init_timer */
static u_int sysctl_init_timer = 15; /**< Seconds to hold an association in the table waiting for an INIT-ACK or AddIP-ACK */
/** @brief net.inet.ip.alias.sctp.up_timer */
static u_int sysctl_up_timer = 300; /**< Seconds to hold an association in the table while no packets are transmitted */
/** @brief net.inet.ip.alias.sctp.shutdown_timer */
static u_int sysctl_shutdown_timer = 15; /**< Seconds to hold an association in the table waiting for a SHUTDOWN-COMPLETE */
/** @brief net.inet.ip.alias.sctp.holddown_timer */
static u_int sysctl_holddown_timer = 0; /**< Seconds to hold an association in the table after it has been shutdown (to allow for lost SHUTDOWN-COMPLETEs) */
/** @brief net.inet.ip.alias.sctp.hashtable_size */
static u_int sysctl_hashtable_size = SN_DEFAULT_HASH_SIZE; /**< Sets the hash table size for any NEW NAT instances (existing instances retain their existing Hash Table */
/** @brief net.inet.ip.alias.sctp.error_on_ootb */
static u_int sysctl_error_on_ootb = 1; /**< NAT response to receipt of OOTB packet
(0 - No response, 1 - NAT will send ErrorM only to local side,
2 - NAT will send local ErrorM and global ErrorM if there was a partial association match
3 - NAT will send ErrorM to both local and global) */
/** @brief net.inet.ip.alias.sctp.accept_global_ootb_addip */
static u_int sysctl_accept_global_ootb_addip = 0; /**<NAT responset to receipt of global OOTB AddIP (0 - No response, 1 - NAT will accept OOTB global AddIP messages for processing (Security risk)) */
/** @brief net.inet.ip.alias.sctp.initialising_chunk_proc_limit */
static u_int sysctl_initialising_chunk_proc_limit = 2; /**< A limit on the number of chunks that should be searched if there is no matching association (DoS prevention) */
/** @brief net.inet.ip.alias.sctp.param_proc_limit */
static u_int sysctl_chunk_proc_limit = 5; /**< A limit on the number of chunks that should be searched (DoS prevention) */
/** @brief net.inet.ip.alias.sctp.param_proc_limit */
static u_int sysctl_param_proc_limit = 25; /**< A limit on the number of parameters (in chunks) that should be searched (DoS prevention) */
/** @brief net.inet.ip.alias.sctp.track_global_addresses */
static u_int sysctl_track_global_addresses = 0; /**< Configures the global address tracking option within the NAT (0 - Global tracking is disabled, > 0 - enables tracking but limits the number of global IP addresses to this value)
If set to >=1 the NAT will track that many global IP addresses. This may reduce look up table conflicts, but increases processing */
#define SN_NO_ERROR_ON_OOTB 0 /**< Send no errorM on out of the blue packets */
#define SN_LOCAL_ERROR_ON_OOTB 1 /**< Send only local errorM on out of the blue packets */
#define SN_LOCALandPARTIAL_ERROR_ON_OOTB 2 /**< Send local errorM and global errorM for out of the blue packets only if partial match found */
#define SN_ERROR_ON_OOTB 3 /**< Send errorM on out of the blue packets */
#ifdef SYSCTL_NODE
SYSCTL_DECL(_net_inet);
SYSCTL_DECL(_net_inet_ip);
SYSCTL_DECL(_net_inet_ip_alias);
SYSCTL_NODE(_net_inet_ip_alias, OID_AUTO, sctp, CTLFLAG_RW, NULL, "SCTP NAT");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, log_level, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_log_level, 0, sysctl_chg_loglevel, "IU",
"Level of detail (0 - default, 1 - event, 2 - info, 3 - detail, 4 - debug, 5 - max debug)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, init_timer, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_init_timer, 0, sysctl_chg_timer, "IU",
"Timeout value (s) while waiting for (INIT-ACK|AddIP-ACK)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, up_timer, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_up_timer, 0, sysctl_chg_timer, "IU",
"Timeout value (s) to keep an association up with no traffic");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, shutdown_timer, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_shutdown_timer, 0, sysctl_chg_timer, "IU",
"Timeout value (s) while waiting for SHUTDOWN-COMPLETE");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, holddown_timer, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_holddown_timer, 0, sysctl_chg_timer, "IU",
"Hold association in table for this many seconds after receiving a SHUTDOWN-COMPLETE");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, hashtable_size, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_hashtable_size, 0, sysctl_chg_hashtable_size, "IU",
"Size of hash tables used for NAT lookups (100 < prime_number > 1000001)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, error_on_ootb, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_error_on_ootb, 0, sysctl_chg_error_on_ootb, "IU",
"ErrorM sent on receipt of ootb packet:\n\t0 - none,\n\t1 - to local only,\n\t2 - to local and global if a partial association match,\n\t3 - to local and global (DoS risk)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, accept_global_ootb_addip, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_accept_global_ootb_addip, 0, sysctl_chg_accept_global_ootb_addip, "IU",
"NAT response to receipt of global OOTB AddIP:\n\t0 - No response,\n\t1 - NAT will accept OOTB global AddIP messages for processing (Security risk)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, initialising_chunk_proc_limit, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_initialising_chunk_proc_limit, 0, sysctl_chg_initialising_chunk_proc_limit, "IU",
"Number of chunks that should be processed if there is no current association found:\n\t > 0 (A high value is a DoS risk)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, chunk_proc_limit, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_chunk_proc_limit, 0, sysctl_chg_chunk_proc_limit, "IU",
"Number of chunks that should be processed to find key chunk:\n\t>= initialising_chunk_proc_limit (A high value is a DoS risk)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, param_proc_limit, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_param_proc_limit, 0, sysctl_chg_param_proc_limit, "IU",
"Number of parameters (in a chunk) that should be processed to find key parameters:\n\t> 1 (A high value is a DoS risk)");
SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, track_global_addresses, CTLTYPE_UINT | CTLFLAG_RW,
&sysctl_track_global_addresses, 0, sysctl_chg_track_global_addresses, "IU",
"Configures the global address tracking option within the NAT:\n\t0 - Global tracking is disabled,\n\t> 0 - enables tracking but limits the number of global IP addresses to this value");
#endif /* SYSCTL_NODE */
/** @}
* @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.fw.sctp.log_level
*
* Updates the variable sysctl_log_level to the provided value and ensures
* it is in the valid range (SN_LOG_LOW -> SN_LOG_DEBUG)
*/
int sysctl_chg_loglevel(SYSCTL_HANDLER_ARGS)
{
u_int level = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &level, 0, req);
if (error) return (error);
sysctl_log_level = (level > SN_LOG_DEBUG_MAX)?(SN_LOG_DEBUG_MAX):(level);
sysctl_log_level = (level < SN_LOG_LOW)?(SN_LOG_LOW):(level);
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.fw.sctp.(init_timer|up_timer|shutdown_timer)
*
* Updates the timer-based sysctl variables. The new values are sanity-checked
* to make sure that they are within the range SN_MIN_TIMER-SN_MAX_TIMER. The
* holddown timer is allowed to be 0
*/
int sysctl_chg_timer(SYSCTL_HANDLER_ARGS)
{
u_int timer = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &timer, 0, req);
if (error) return (error);
timer = (timer > SN_MAX_TIMER)?(SN_MAX_TIMER):(timer);
if (((u_int *)arg1) != &sysctl_holddown_timer)
{
timer = (timer < SN_MIN_TIMER)?(SN_MIN_TIMER):(timer);
}
*(u_int *)arg1 = timer;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.hashtable_size
*
* Updates the hashtable_size sysctl variable. The new value should be a prime
* number. We sanity check to ensure that the size is within the range
* SN_MIN_HASH_SIZE-SN_MAX_HASH_SIZE. We then check the provided number to see
* if it is prime. We approximate by checking that (2,3,5,7,11) are not factors,
* incrementing the user provided value until we find a suitable number.
*/
int sysctl_chg_hashtable_size(SYSCTL_HANDLER_ARGS)
{
u_int size = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &size, 0, req);
if (error) return (error);
size = (size < SN_MIN_HASH_SIZE)?(SN_MIN_HASH_SIZE):((size > SN_MAX_HASH_SIZE)?(SN_MAX_HASH_SIZE):(size));
size |= 0x00000001; /* make odd */
for(;(((size % 3) == 0) || ((size % 5) == 0) || ((size % 7) == 0) || ((size % 11) == 0)); size+=2);
sysctl_hashtable_size = size;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.error_on_ootb
*
* Updates the error_on_clash sysctl variable.
* If set to 0, no ErrorM will be sent if there is a look up table clash
* If set to 1, an ErrorM is sent only to the local side
* If set to 2, an ErrorM is sent to the local side and global side if there is
* a partial association match
* If set to 3, an ErrorM is sent to both local and global sides (DoS) risk.
*/
int sysctl_chg_error_on_ootb(SYSCTL_HANDLER_ARGS)
{
u_int flag = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &flag, 0, req);
if (error) return (error);
sysctl_error_on_ootb = (flag > SN_ERROR_ON_OOTB) ? SN_ERROR_ON_OOTB: flag;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.accept_global_ootb_addip
*
* If set to 1 the NAT will accept ootb global addip messages for processing (Security risk)
* Default is 0, only responding to local ootb AddIP messages
*/
int sysctl_chg_accept_global_ootb_addip(SYSCTL_HANDLER_ARGS)
{
u_int flag = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &flag, 0, req);
if (error) return (error);
sysctl_accept_global_ootb_addip = (flag == 1) ? 1: 0;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.initialising_chunk_proc_limit
*
* Updates the initialising_chunk_proc_limit sysctl variable. Number of chunks
* that should be processed if there is no current association found: > 0 (A
* high value is a DoS risk)
*/
int sysctl_chg_initialising_chunk_proc_limit(SYSCTL_HANDLER_ARGS)
{
u_int proclimit = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &proclimit, 0, req);
if (error) return (error);
sysctl_initialising_chunk_proc_limit = (proclimit < 1) ? 1: proclimit;
sysctl_chunk_proc_limit =
(sysctl_chunk_proc_limit < sysctl_initialising_chunk_proc_limit) ? sysctl_initialising_chunk_proc_limit : sysctl_chunk_proc_limit;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.chunk_proc_limit
*
* Updates the chunk_proc_limit sysctl variable.
* Number of chunks that should be processed to find key chunk:
* >= initialising_chunk_proc_limit (A high value is a DoS risk)
*/
int sysctl_chg_chunk_proc_limit(SYSCTL_HANDLER_ARGS)
{
u_int proclimit = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &proclimit, 0, req);
if (error) return (error);
sysctl_chunk_proc_limit =
(proclimit < sysctl_initialising_chunk_proc_limit) ? sysctl_initialising_chunk_proc_limit : proclimit;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.param_proc_limit
*
* Updates the param_proc_limit sysctl variable.
* Number of parameters that should be processed to find key parameters:
* > 1 (A high value is a DoS risk)
*/
int sysctl_chg_param_proc_limit(SYSCTL_HANDLER_ARGS)
{
u_int proclimit = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &proclimit, 0, req);
if (error) return (error);
sysctl_param_proc_limit =
(proclimit < 2) ? 2 : proclimit;
return (0);
}
/** @ingroup sysctl
* @brief sysctl callback for changing net.inet.ip.alias.sctp.track_global_addresses
*
*Configures the global address tracking option within the NAT (0 - Global
*tracking is disabled, > 0 - enables tracking but limits the number of global
*IP addresses to this value)
*/
int sysctl_chg_track_global_addresses(SYSCTL_HANDLER_ARGS)
{
u_int num_to_track = *(u_int *)arg1;
int error;
error = sysctl_handle_int(oidp, &num_to_track, 0, req);
if (error) return (error);
sysctl_track_global_addresses = (num_to_track > SN_MAX_GLOBAL_ADDRESSES) ? SN_MAX_GLOBAL_ADDRESSES : num_to_track;
return (0);
}
/* ----------------------------------------------------------------------
* CODE BEGINS HERE
* ----------------------------------------------------------------------
*/
/**
* @brief Initialises the SCTP NAT Implementation
*
* Creates the look-up tables and the timer queue and initialises all state
* variables
*
* @param la Pointer to the relevant libalias instance
*/
void AliasSctpInit(struct libalias *la)
{
/* Initialise association tables*/
int i;
la->sctpNatTableSize = sysctl_hashtable_size;
SN_LOG(SN_LOG_EVENT,
SctpAliasLog("Initialising SCTP NAT Instance (hash_table_size:%d)\n", la->sctpNatTableSize));
la->sctpTableLocal = sn_calloc(la->sctpNatTableSize, sizeof(struct sctpNatTableL));
la->sctpTableGlobal = sn_calloc(la->sctpNatTableSize, sizeof(struct sctpNatTableG));
la->sctpNatTimer.TimerQ = sn_calloc(SN_TIMER_QUEUE_SIZE, sizeof(struct sctpTimerQ));
/* Initialise hash table */
for (i = 0; i < la->sctpNatTableSize; i++) {
LIST_INIT(&la->sctpTableLocal[i]);
LIST_INIT(&la->sctpTableGlobal[i]);
}
/* Initialise circular timer Q*/
for (i = 0; i < SN_TIMER_QUEUE_SIZE; i++)
LIST_INIT(&la->sctpNatTimer.TimerQ[i]);
#ifdef _KERNEL
la->sctpNatTimer.loc_time=time_uptime; /* la->timeStamp is not set yet */
#else
la->sctpNatTimer.loc_time=la->timeStamp;
#endif
la->sctpNatTimer.cur_loc = 0;
la->sctpLinkCount = 0;
}
/**
* @brief Cleans-up the SCTP NAT Implementation prior to unloading
*
* Removes all entries from the timer queue, freeing associations as it goes.
* We then free memory allocated to the look-up tables and the time queue
*
* NOTE: We do not need to traverse the look-up tables as each association
* will always have an entry in the timer queue, freeing this memory
* once will free all memory allocated to entries in the look-up tables
*
* @param la Pointer to the relevant libalias instance
*/
void AliasSctpTerm(struct libalias *la)
{
struct sctp_nat_assoc *assoc1, *assoc2;
int i;
LIBALIAS_LOCK_ASSERT(la);
SN_LOG(SN_LOG_EVENT,
SctpAliasLog("Removing SCTP NAT Instance\n"));
for (i = 0; i < SN_TIMER_QUEUE_SIZE; i++) {
assoc1 = LIST_FIRST(&la->sctpNatTimer.TimerQ[i]);
while (assoc1 != NULL) {
freeGlobalAddressList(assoc1);
assoc2 = LIST_NEXT(assoc1, timer_Q);
sn_free(assoc1);
assoc1 = assoc2;
}
}
sn_free(la->sctpTableLocal);
sn_free(la->sctpTableGlobal);
sn_free(la->sctpNatTimer.TimerQ);
}
/**
* @brief Handles SCTP packets passed from libalias
*
* This function needs to actually NAT/drop packets and possibly create and
* send AbortM or ErrorM packets in response. The process involves:
* - Validating the direction parameter passed by the caller
* - Checking and handling any expired timers for the NAT
* - Calling sctp_PktParser() to parse the packet
* - Call ProcessSctpMsg() to decide the appropriate outcome and to update
* the NAT tables
* - Based on the return code either:
* - NAT the packet
* - Construct and send an ErrorM|AbortM packet
* - Mark the association for removal from the tables
* - Potentially remove the association from all lookup tables
* - Return the appropriate result to libalias
*
* @param la Pointer to the relevant libalias instance
* @param pip Pointer to IP packet to process
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
* @return PKT_ALIAS_OK | PKT_ALIAS_IGNORE | PKT_ALIAS_ERROR
*/
int
SctpAlias(struct libalias *la, struct ip *pip, int direction)
{
int rtnval;
struct sctp_nat_msg msg;
struct sctp_nat_assoc *assoc = NULL;
if ((direction != SN_TO_LOCAL) && (direction != SN_TO_GLOBAL)) {
SctpAliasLog("ERROR: Invalid direction\n");
return(PKT_ALIAS_ERROR);
}
sctp_CheckTimers(la); /* Check timers */
/* Parse the packet */
rtnval = sctp_PktParser(la, direction, pip, &msg, &assoc); //using *char (change to mbuf when get code from paolo)
switch (rtnval) {
case SN_PARSE_OK:
break;
case SN_PARSE_ERROR_CHHL:
/* Not an error if there is a chunk length parsing error and this is a fragmented packet */
if (ntohs(pip->ip_off) & IP_MF) {
rtnval = SN_PARSE_OK;
break;
}
SN_LOG(SN_LOG_EVENT,
logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction));
return(PKT_ALIAS_ERROR);
case SN_PARSE_ERROR_PARTIALLOOKUP:
if (sysctl_error_on_ootb > SN_LOCALandPARTIAL_ERROR_ON_OOTB) {
SN_LOG(SN_LOG_EVENT,
logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction));
return(PKT_ALIAS_ERROR);
}
case SN_PARSE_ERROR_LOOKUP:
if (sysctl_error_on_ootb == SN_ERROR_ON_OOTB ||
(sysctl_error_on_ootb == SN_LOCALandPARTIAL_ERROR_ON_OOTB && direction == SN_TO_LOCAL) ||
(sysctl_error_on_ootb == SN_LOCAL_ERROR_ON_OOTB && direction == SN_TO_GLOBAL)) {
TxAbortErrorM(la, &msg, assoc, SN_REFLECT_ERROR, direction); /*NB assoc=NULL */
return(PKT_ALIAS_RESPOND);
}
default:
SN_LOG(SN_LOG_EVENT,
logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction));
return(PKT_ALIAS_ERROR);
}
SN_LOG(SN_LOG_DETAIL,
logsctpassoc(assoc, "*");
logsctpparse(direction, &msg);
);
/* Process the SCTP message */
rtnval = ProcessSctpMsg(la, direction, &msg, assoc);
SN_LOG(SN_LOG_DEBUG_MAX,
logsctpassoc(assoc, "-");
logSctpLocal(la);
logSctpGlobal(la);
);
SN_LOG(SN_LOG_DEBUG, logTimerQ(la));
switch(rtnval){
case SN_NAT_PKT:
switch(direction) {
case SN_TO_LOCAL:
DifferentialChecksum(&(msg.ip_hdr->ip_sum),
&(assoc->l_addr), &(msg.ip_hdr->ip_dst), 2);
msg.ip_hdr->ip_dst = assoc->l_addr; /* change dst address to local address*/
break;
case SN_TO_GLOBAL:
DifferentialChecksum(&(msg.ip_hdr->ip_sum),
&(assoc->a_addr), &(msg.ip_hdr->ip_src), 2);
msg.ip_hdr->ip_src = assoc->a_addr; /* change src to alias addr*/
break;
default:
rtnval = SN_DROP_PKT; /* shouldn't get here, but if it does drop packet */
SN_LOG(SN_LOG_LOW, logsctperror("ERROR: Invalid direction", msg.sctp_hdr->v_tag, rtnval, direction));
break;
}
break;
case SN_DROP_PKT:
SN_LOG(SN_LOG_DETAIL, logsctperror("SN_DROP_PKT", msg.sctp_hdr->v_tag, rtnval, direction));
break;
case SN_REPLY_ABORT:
case SN_REPLY_ERROR:
case SN_SEND_ABORT:
TxAbortErrorM(la, &msg, assoc, rtnval, direction);
break;
default:
// big error, remove association and go to idle and write log messages
SN_LOG(SN_LOG_LOW, logsctperror("SN_PROCESSING_ERROR", msg.sctp_hdr->v_tag, rtnval, direction));
assoc->state=SN_RM;/* Mark for removal*/
break;
}
/* Remove association if tagged for removal */
if (assoc->state == SN_RM) {
if (assoc->TableRegister) {
sctp_RmTimeOut(la, assoc);
RmSctpAssoc(la, assoc);
}
LIBALIAS_LOCK_ASSERT(la);
freeGlobalAddressList(assoc);
sn_free(assoc);
}
switch(rtnval) {
case SN_NAT_PKT:
return(PKT_ALIAS_OK);
case SN_SEND_ABORT:
return(PKT_ALIAS_OK);
case SN_REPLY_ABORT:
case SN_REPLY_ERROR:
case SN_REFLECT_ERROR:
return(PKT_ALIAS_RESPOND);
case SN_DROP_PKT:
default:
return(PKT_ALIAS_ERROR);
}
}
/**
* @brief Send an AbortM or ErrorM
*
* We construct the new SCTP packet to send in place of the existing packet we
* have been asked to NAT. This function can only be called if the original
* packet was successfully parsed as a valid SCTP packet.
*
* An AbortM (without cause) packet is the smallest SCTP packet available and as
* such there is always space in the existing packet buffer to fit the AbortM
* packet. An ErrorM packet is 4 bytes longer than the (the error cause is not
* optional). An ErrorM is sent in response to an AddIP when the Vtag/address
* combination, if added, will produce a conflict in the association look up
* tables. It may also be used for an unexpected packet - a packet with no
* matching association in the NAT table and we are requesting an AddIP so we
* can add it. The smallest valid SCTP packet while the association is in an
* up-state is a Heartbeat packet, which is big enough to be transformed to an
* ErrorM.
*
* We create a temporary character array to store the packet as we are constructing
* it. We then populate the array with appropriate values based on:
* - Packet type (AbortM | ErrorM)
* - Initial packet direction (SN_TO_LOCAL | SN_TO_GLOBAL)
* - NAT response (Send packet | Reply packet)
*
* Once complete, we copy the contents of the temporary packet over the original
* SCTP packet we were asked to NAT
*
* @param la Pointer to the relevant libalias instance
* @param sm Pointer to sctp message information
* @param assoc Pointer to current association details
* @param sndrply SN_SEND_ABORT | SN_REPLY_ABORT | SN_REPLY_ERROR
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*/
static uint32_t
local_sctp_finalize_crc32(uint32_t crc32c)
{
/* This routine is duplicated from SCTP
* we need to do that since it MAY be that SCTP
* is NOT compiled into the kernel. The CRC32C routines
* however are always available in libkern.
*/
uint32_t result;
#if BYTE_ORDER == BIG_ENDIAN
uint8_t byte0, byte1, byte2, byte3;
#endif
/* Complement the result */
result = ~crc32c;
#if BYTE_ORDER == BIG_ENDIAN
/*
* For BIG-ENDIAN.. aka Motorola byte order the result is in
* little-endian form. So we must manually swap the bytes. Then we
* can call htonl() which does nothing...
*/
byte0 = result & 0x000000ff;
byte1 = (result >> 8) & 0x000000ff;
byte2 = (result >> 16) & 0x000000ff;
byte3 = (result >> 24) & 0x000000ff;
crc32c = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
#else
/*
* For INTEL platforms the result comes out in network order. No
* htonl is required or the swap above. So we optimize out both the
* htonl and the manual swap above.
*/
crc32c = result;
#endif
return (crc32c);
}
static void
TxAbortErrorM(struct libalias *la, struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int sndrply, int direction)
{
int sctp_size = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + sizeof(struct sctp_error_cause);
int ip_size = sizeof(struct ip) + sctp_size;
int include_error_cause = 1;
char tmp_ip[ip_size];
if (ntohs(sm->ip_hdr->ip_len) < ip_size) { /* short packet, cannot send error cause */
include_error_cause = 0;
ip_size = ip_size - sizeof(struct sctp_error_cause);
sctp_size = sctp_size - sizeof(struct sctp_error_cause);
}
/* Assign header pointers packet */
struct ip* ip = (struct ip *) tmp_ip;
struct sctphdr* sctp_hdr = (struct sctphdr *) ((char *) ip + sizeof(*ip));
struct sctp_chunkhdr* chunk_hdr = (struct sctp_chunkhdr *) ((char *) sctp_hdr + sizeof(*sctp_hdr));
struct sctp_error_cause* error_cause = (struct sctp_error_cause *) ((char *) chunk_hdr + sizeof(*chunk_hdr));
/* construct ip header */
ip->ip_v = sm->ip_hdr->ip_v;
ip->ip_hl = 5; /* 5*32 bit words */
ip->ip_tos = 0;
ip->ip_len = htons(ip_size);
ip->ip_id = sm->ip_hdr->ip_id;
ip->ip_off = 0;
ip->ip_ttl = 255;
ip->ip_p = IPPROTO_SCTP;
/*
The definitions below should be removed when they make it into the SCTP stack
*/
#define SCTP_MIDDLEBOX_FLAG 0x02
#define SCTP_NAT_TABLE_COLLISION 0x00b0
#define SCTP_MISSING_NAT 0x00b1
chunk_hdr->chunk_type = (sndrply & SN_TX_ABORT) ? SCTP_ABORT_ASSOCIATION : SCTP_OPERATION_ERROR;
chunk_hdr->chunk_flags = SCTP_MIDDLEBOX_FLAG;
if (include_error_cause) {
error_cause->code = htons((sndrply & SN_REFLECT_ERROR) ? SCTP_MISSING_NAT : SCTP_NAT_TABLE_COLLISION);
error_cause->length = htons(sizeof(struct sctp_error_cause));
chunk_hdr->chunk_length = htons(sizeof(*chunk_hdr) + sizeof(struct sctp_error_cause));
} else {
chunk_hdr->chunk_length = htons(sizeof(*chunk_hdr));
}
/* set specific values */
switch(sndrply) {
case SN_REFLECT_ERROR:
chunk_hdr->chunk_flags |= SCTP_HAD_NO_TCB; /* set Tbit */
sctp_hdr->v_tag = sm->sctp_hdr->v_tag;
break;
case SN_REPLY_ERROR:
sctp_hdr->v_tag = (direction == SN_TO_LOCAL) ? assoc->g_vtag : assoc->l_vtag ;
break;
case SN_SEND_ABORT:
sctp_hdr->v_tag = sm->sctp_hdr->v_tag;
break;
case SN_REPLY_ABORT:
sctp_hdr->v_tag = sm->sctpchnk.Init->initiate_tag;
break;
}
/* Set send/reply values */
if (sndrply == SN_SEND_ABORT) { /*pass through NAT */
ip->ip_src = (direction == SN_TO_LOCAL) ? sm->ip_hdr->ip_src : assoc->a_addr;
ip->ip_dst = (direction == SN_TO_LOCAL) ? assoc->l_addr : sm->ip_hdr->ip_dst;
sctp_hdr->src_port = sm->sctp_hdr->src_port;
sctp_hdr->dest_port = sm->sctp_hdr->dest_port;
} else { /* reply and reflect */
ip->ip_src = sm->ip_hdr->ip_dst;
ip->ip_dst = sm->ip_hdr->ip_src;
sctp_hdr->src_port = sm->sctp_hdr->dest_port;
sctp_hdr->dest_port = sm->sctp_hdr->src_port;
}
/* Calculate IP header checksum */
ip->ip_sum = in_cksum_hdr(ip);
/* calculate SCTP header CRC32 */
sctp_hdr->checksum = 0;
sctp_hdr->checksum = local_sctp_finalize_crc32(calculate_crc32c(0xffffffff, (unsigned char *) sctp_hdr, sctp_size));
memcpy(sm->ip_hdr, ip, ip_size);
SN_LOG(SN_LOG_EVENT,SctpAliasLog("%s %s 0x%x (->%s:%u vtag=0x%x crc=0x%x)\n",
((sndrply == SN_SEND_ABORT) ? "Sending" : "Replying"),
((sndrply & SN_TX_ERROR) ? "ErrorM" : "AbortM"),
(include_error_cause ? ntohs(error_cause->code) : 0),
inet_ntoa(ip->ip_dst),ntohs(sctp_hdr->dest_port),
ntohl(sctp_hdr->v_tag), ntohl(sctp_hdr->checksum)));
}
/* ----------------------------------------------------------------------
* PACKET PARSER CODE
* ----------------------------------------------------------------------
*/
/** @addtogroup packet_parser
*
* These functions parse the SCTP packet and fill a sctp_nat_msg structure
* with the parsed contents.
*/
/** @ingroup packet_parser
* @brief Parses SCTP packets for the key SCTP chunk that will be processed
*
* This module parses SCTP packets for the key SCTP chunk that will be processed
* The module completes the sctp_nat_msg structure and either retrieves the
* relevant (existing) stored association from the Hash Tables or creates a new
* association entity with state SN_ID
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param pip
* @param sm Pointer to sctp message information
* @param passoc Pointer to the association this SCTP Message belongs to
*
* @return SN_PARSE_OK | SN_PARSE_ERROR_*
*/
static int
sctp_PktParser(struct libalias *la, int direction, struct ip *pip,
struct sctp_nat_msg *sm, struct sctp_nat_assoc **passoc)
//sctp_PktParser(int direction, struct mbuf *ipak, int ip_hdr_len,struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc)
{
struct sctphdr *sctp_hdr;
struct sctp_chunkhdr *chunk_hdr;
struct sctp_paramhdr *param_hdr;
struct in_addr ipv4addr;
int bytes_left; /* bytes left in ip packet */
int chunk_length;
int chunk_count;
int partial_match = 0;
// mbuf *mp;
// int mlen;
// mlen = SCTP_HEADER_LEN(i_pak);
// mp = SCTP_HEADER_TO_CHAIN(i_pak); /* does nothing in bsd since header and chain not separate */
/*
* Note, that if the VTag is zero, it must be an INIT
* Also, I am only interested in the content of INIT and ADDIP chunks
*/
// no mbuf stuff from Paolo yet so ...
sm->ip_hdr = pip;
/* remove ip header length from the bytes_left */
bytes_left = ntohs(pip->ip_len) - (pip->ip_hl << 2);
/* Check SCTP header length and move to first chunk */
if (bytes_left < sizeof(struct sctphdr)) {
sm->sctp_hdr = NULL;
return(SN_PARSE_ERROR_IPSHL); /* packet not long enough*/
}
sm->sctp_hdr = sctp_hdr = (struct sctphdr *) ip_next(pip);
bytes_left -= sizeof(struct sctphdr);
/* Check for valid ports (zero valued ports would find partially initialised associations */
if (sctp_hdr->src_port == 0 || sctp_hdr->dest_port == 0)
return(SN_PARSE_ERROR_PORT);
/* Check length of first chunk */
if (bytes_left < SN_MIN_CHUNK_SIZE) /* malformed chunk - could cause endless loop*/
return(SN_PARSE_ERROR_CHHL); /* packet not long enough for this chunk */
/* First chunk */
chunk_hdr = SN_SCTP_FIRSTCHUNK(sctp_hdr);
chunk_length = SCTP_SIZE32(ntohs(chunk_hdr->chunk_length));
if ((chunk_length < SN_MIN_CHUNK_SIZE) || (chunk_length > bytes_left)) /* malformed chunk - could cause endless loop*/
return(SN_PARSE_ERROR_CHHL);
if ((chunk_hdr->chunk_flags & SCTP_HAD_NO_TCB) &&
((chunk_hdr->chunk_type == SCTP_ABORT_ASSOCIATION) ||
(chunk_hdr->chunk_type == SCTP_SHUTDOWN_COMPLETE))) {
/* T-Bit set */
if (direction == SN_TO_LOCAL)
*passoc = FindSctpGlobalT(la, pip->ip_src, sctp_hdr->v_tag, sctp_hdr->dest_port, sctp_hdr->src_port);
else
*passoc = FindSctpLocalT(la, pip->ip_dst, sctp_hdr->v_tag, sctp_hdr->dest_port, sctp_hdr->src_port);
} else {
/* Proper v_tag settings */
if (direction == SN_TO_LOCAL)
*passoc = FindSctpGlobal(la, pip->ip_src, sctp_hdr->v_tag, sctp_hdr->src_port, sctp_hdr->dest_port, &partial_match);
else
*passoc = FindSctpLocal(la, pip->ip_src, pip->ip_dst, sctp_hdr->v_tag, sctp_hdr->src_port, sctp_hdr->dest_port);
}
chunk_count = 1;
/* Real packet parsing occurs below */
sm->msg = SN_SCTP_OTHER;/* Initialise to largest value*/
sm->chunk_length = 0; /* only care about length for key chunks */
while (IS_SCTP_CONTROL(chunk_hdr)) {
switch(chunk_hdr->chunk_type) {
case SCTP_INITIATION:
if (chunk_length < sizeof(struct sctp_init_chunk)) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
sm->msg = SN_SCTP_INIT;
sm->sctpchnk.Init = (struct sctp_init *) ((char *) chunk_hdr + sizeof(struct sctp_chunkhdr));
sm->chunk_length = chunk_length;
/* if no existing association, create a new one */
if (*passoc == NULL) {
if (sctp_hdr->v_tag == 0){ //Init requires vtag=0
*passoc = (struct sctp_nat_assoc *) sn_malloc(sizeof(struct sctp_nat_assoc));
if (*passoc == NULL) {/* out of resources */
return(SN_PARSE_ERROR_AS_MALLOC);
}
/* Initialise association - malloc initialises memory to zeros */
(*passoc)->state = SN_ID;
LIST_INIT(&((*passoc)->Gaddr)); /* always initialise to avoid memory problems */
(*passoc)->TableRegister = SN_NULL_TBL;
return(SN_PARSE_OK);
}
return(SN_PARSE_ERROR_VTAG);
}
return(SN_PARSE_ERROR_LOOKUP);
case SCTP_INITIATION_ACK:
if (chunk_length < sizeof(struct sctp_init_ack_chunk)) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
sm->msg = SN_SCTP_INITACK;
sm->sctpchnk.InitAck = (struct sctp_init_ack *) ((char *) chunk_hdr + sizeof(struct sctp_chunkhdr));
sm->chunk_length = chunk_length;
return ((*passoc == NULL)?(SN_PARSE_ERROR_LOOKUP):(SN_PARSE_OK));
case SCTP_ABORT_ASSOCIATION: /* access only minimum sized chunk */
sm->msg = SN_SCTP_ABORT;
sm->chunk_length = chunk_length;
return ((*passoc == NULL)?(SN_PARSE_ERROR_LOOKUP_ABORT):(SN_PARSE_OK));
case SCTP_SHUTDOWN_ACK:
if (chunk_length < sizeof(struct sctp_shutdown_ack_chunk)) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
if (sm->msg > SN_SCTP_SHUTACK) {
sm->msg = SN_SCTP_SHUTACK;
sm->chunk_length = chunk_length;
}
break;
case SCTP_SHUTDOWN_COMPLETE: /* minimum sized chunk */
if (sm->msg > SN_SCTP_SHUTCOMP) {
sm->msg = SN_SCTP_SHUTCOMP;
sm->chunk_length = chunk_length;
}
return ((*passoc == NULL)?(SN_PARSE_ERROR_LOOKUP):(SN_PARSE_OK));
case SCTP_ASCONF:
if (sm->msg > SN_SCTP_ASCONF) {
if (chunk_length < (sizeof(struct sctp_asconf_chunk) + sizeof(struct sctp_ipv4addr_param))) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
//leave parameter searching to later, if required
param_hdr = (struct sctp_paramhdr *) ((char *) chunk_hdr + sizeof(struct sctp_asconf_chunk)); /*compulsory IP parameter*/
if (ntohs(param_hdr->param_type) == SCTP_IPV4_ADDRESS) {
if ((*passoc == NULL) && (direction == SN_TO_LOCAL)) { /* AddIP with no association */
/* try look up with the ASCONF packet's alternative address */
ipv4addr.s_addr = ((struct sctp_ipv4addr_param *) param_hdr)->addr;
*passoc = FindSctpGlobal(la, ipv4addr, sctp_hdr->v_tag, sctp_hdr->src_port, sctp_hdr->dest_port, &partial_match);
}
param_hdr = (struct sctp_paramhdr *)
((char *) param_hdr + sizeof(struct sctp_ipv4addr_param)); /*asconf's compulsory address parameter */
sm->chunk_length = chunk_length - sizeof(struct sctp_asconf_chunk) - sizeof(struct sctp_ipv4addr_param); /* rest of chunk */
} else {
if (chunk_length < (sizeof(struct sctp_asconf_chunk) + sizeof(struct sctp_ipv6addr_param))) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
param_hdr = (struct sctp_paramhdr *)
((char *) param_hdr + sizeof(struct sctp_ipv6addr_param)); /*asconf's compulsory address parameter */
sm->chunk_length = chunk_length - sizeof(struct sctp_asconf_chunk) - sizeof(struct sctp_ipv6addr_param); /* rest of chunk */
}
sm->msg = SN_SCTP_ASCONF;
sm->sctpchnk.Asconf = param_hdr;
if (*passoc == NULL) { /* AddIP with no association */
*passoc = (struct sctp_nat_assoc *) sn_malloc(sizeof(struct sctp_nat_assoc));
if (*passoc == NULL) {/* out of resources */
return(SN_PARSE_ERROR_AS_MALLOC);
}
/* Initialise association - malloc initialises memory to zeros */
(*passoc)->state = SN_ID;
LIST_INIT(&((*passoc)->Gaddr)); /* always initialise to avoid memory problems */
(*passoc)->TableRegister = SN_NULL_TBL;
return(SN_PARSE_OK);
}
}
break;
case SCTP_ASCONF_ACK:
if (sm->msg > SN_SCTP_ASCONFACK) {
if (chunk_length < sizeof(struct sctp_asconf_ack_chunk)) /* malformed chunk*/
return(SN_PARSE_ERROR_CHHL);
//leave parameter searching to later, if required
param_hdr = (struct sctp_paramhdr *) ((char *) chunk_hdr
+ sizeof(struct sctp_asconf_ack_chunk));
sm->msg = SN_SCTP_ASCONFACK;
sm->sctpchnk.Asconf = param_hdr;
sm->chunk_length = chunk_length - sizeof(struct sctp_asconf_ack_chunk);
}
break;
default:
break; /* do nothing*/
}
/* if no association is found exit - we need to find an Init or AddIP within sysctl_initialising_chunk_proc_limit */
if ((*passoc == NULL) && (chunk_count >= sysctl_initialising_chunk_proc_limit))
return(SN_PARSE_ERROR_LOOKUP);
/* finished with this chunk, on to the next chunk*/
bytes_left-= chunk_length;
/* Is this the end of the packet ? */
if (bytes_left == 0)
return (*passoc == NULL)?(SN_PARSE_ERROR_LOOKUP):(SN_PARSE_OK);
/* Are there enough bytes in packet to at least retrieve length of next chunk ? */
if (bytes_left < SN_MIN_CHUNK_SIZE)
return(SN_PARSE_ERROR_CHHL);
chunk_hdr = SN_SCTP_NEXTCHUNK(chunk_hdr);
/* Is the chunk long enough to not cause endless look and are there enough bytes in packet to read the chunk ? */
chunk_length = SCTP_SIZE32(ntohs(chunk_hdr->chunk_length));
if ((chunk_length < SN_MIN_CHUNK_SIZE) || (chunk_length > bytes_left))
return(SN_PARSE_ERROR_CHHL);
if(++chunk_count > sysctl_chunk_proc_limit)
return(SN_PARSE_OK); /* limit for processing chunks, take what we get */
}
if (*passoc == NULL)
return (partial_match)?(SN_PARSE_ERROR_PARTIALLOOKUP):(SN_PARSE_ERROR_LOOKUP);
else
return(SN_PARSE_OK);
}
/** @ingroup packet_parser
* @brief Extract Vtags from Asconf Chunk
*
* GetAsconfVtags scans an Asconf Chunk for the vtags parameter, and then
* extracts the vtags.
*
* GetAsconfVtags is not called from within sctp_PktParser. It is called only
* from within ID_process when an AddIP has been received.
*
* @param la Pointer to the relevant libalias instance
* @param sm Pointer to sctp message information
* @param l_vtag Pointer to the local vtag in the association this SCTP Message belongs to
* @param g_vtag Pointer to the local vtag in the association this SCTP Message belongs to
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
* @return 1 - success | 0 - fail
*/
static int
GetAsconfVtags(struct libalias *la, struct sctp_nat_msg *sm, uint32_t *l_vtag, uint32_t *g_vtag, int direction)
{
/* To be removed when information is in the sctp headers */
#define SCTP_VTAG_PARAM 0xC007
struct sctp_vtag_param {
struct sctp_paramhdr ph;/* type=SCTP_VTAG_PARAM */
uint32_t local_vtag;
uint32_t remote_vtag;
} __attribute__((packed));
struct sctp_vtag_param *vtag_param;
struct sctp_paramhdr *param;
int bytes_left;
int param_size;
int param_count;
param_count = 1;
param = sm->sctpchnk.Asconf;
param_size = SCTP_SIZE32(ntohs(param->param_length));
bytes_left = sm->chunk_length;
/* step through Asconf parameters */
while((bytes_left >= param_size) && (bytes_left >= SN_VTAG_PARAM_SIZE)) {
if (ntohs(param->param_type) == SCTP_VTAG_PARAM) {
vtag_param = (struct sctp_vtag_param *) param;
switch(direction) {
/* The Internet draft is a little ambigious as to order of these vtags.
We think it is this way around. If we are wrong, the order will need
to be changed. */
case SN_TO_GLOBAL:
*g_vtag = vtag_param->local_vtag;
*l_vtag = vtag_param->remote_vtag;
break;
case SN_TO_LOCAL:
*g_vtag = vtag_param->remote_vtag;
*l_vtag = vtag_param->local_vtag;
break;
}
return(1); /* found */
}
bytes_left -= param_size;
if (bytes_left < SN_MIN_PARAM_SIZE) return(0);
param = SN_SCTP_NEXTPARAM(param);
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (++param_count > sysctl_param_proc_limit) {
SN_LOG(SN_LOG_EVENT,
logsctperror("Parameter parse limit exceeded (GetAsconfVtags)",
sm->sctp_hdr->v_tag, sysctl_param_proc_limit, direction));
return(0); /* not found limit exceeded*/
}
}
return(0); /* not found */
}
/** @ingroup packet_parser
* @brief AddGlobalIPAddresses from Init,InitAck,or AddIP packets
*
* AddGlobalIPAddresses scans an SCTP chunk (in sm) for Global IP addresses, and
* adds them.
*
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
*/
static void
AddGlobalIPAddresses(struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int direction)
{
struct sctp_ipv4addr_param *ipv4_param;
struct sctp_paramhdr *param = NULL;
struct sctp_GlobalAddress *G_Addr;
struct in_addr g_addr = {0};
int bytes_left = 0;
int param_size;
int param_count, addr_param_count = 0;
switch(direction) {
case SN_TO_GLOBAL: /* does not contain global addresses */
g_addr = sm->ip_hdr->ip_dst;
bytes_left = 0; /* force exit */
break;
case SN_TO_LOCAL:
g_addr = sm->ip_hdr->ip_src;
param_count = 1;
switch(sm->msg) {
case SN_SCTP_INIT:
bytes_left = sm->chunk_length - sizeof(struct sctp_init_chunk);
param = (struct sctp_paramhdr *)((char *)sm->sctpchnk.Init + sizeof(struct sctp_init));
break;
case SN_SCTP_INITACK:
bytes_left = sm->chunk_length - sizeof(struct sctp_init_ack_chunk);
param = (struct sctp_paramhdr *)((char *)sm->sctpchnk.InitAck + sizeof(struct sctp_init_ack));
break;
case SN_SCTP_ASCONF:
bytes_left = sm->chunk_length;
param = sm->sctpchnk.Asconf;
break;
}
}
if (bytes_left >= SN_MIN_PARAM_SIZE)
param_size = SCTP_SIZE32(ntohs(param->param_length));
else
param_size = bytes_left+1; /* force skip loop */
if ((assoc->state == SN_ID) && ((sm->msg == SN_SCTP_INIT) || (bytes_left < SN_MIN_PARAM_SIZE))) {/* add pkt address */
G_Addr = (struct sctp_GlobalAddress *) sn_malloc(sizeof(struct sctp_GlobalAddress));
if (G_Addr == NULL) {/* out of resources */
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: No resources for adding global address - revert to no tracking",
sm->sctp_hdr->v_tag, 0, direction));
assoc->num_Gaddr = 0; /* don't track any more for this assoc*/
sysctl_track_global_addresses=0;
return;
}
G_Addr->g_addr = g_addr;
if (!Add_Global_Address_to_List(assoc, G_Addr))
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: Address already in list",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
}
/* step through parameters */
while((bytes_left >= param_size) && (bytes_left >= sizeof(struct sctp_ipv4addr_param))) {
if (assoc->num_Gaddr >= sysctl_track_global_addresses) {
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: Maximum Number of addresses reached",
sm->sctp_hdr->v_tag, sysctl_track_global_addresses, direction));
return;
}
switch(ntohs(param->param_type)) {
case SCTP_ADD_IP_ADDRESS:
/* skip to address parameter - leave param_size so bytes left will be calculated properly*/
param = (struct sctp_paramhdr *) &((struct sctp_asconf_addrv4_param *) param)->addrp;
case SCTP_IPV4_ADDRESS:
ipv4_param = (struct sctp_ipv4addr_param *) param;
/* add addresses to association */
G_Addr = (struct sctp_GlobalAddress *) sn_malloc(sizeof(struct sctp_GlobalAddress));
if (G_Addr == NULL) {/* out of resources */
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: No resources for adding global address - revert to no tracking",
sm->sctp_hdr->v_tag, 0, direction));
assoc->num_Gaddr = 0; /* don't track any more for this assoc*/
sysctl_track_global_addresses=0;
return;
}
/* add address */
addr_param_count++;
if ((sm->msg == SN_SCTP_ASCONF) && (ipv4_param->addr == INADDR_ANY)) { /* use packet address */
G_Addr->g_addr = g_addr;
if (!Add_Global_Address_to_List(assoc, G_Addr))
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: Address already in list",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
return; /*shouldn't be any other addresses if the zero address is given*/
} else {
G_Addr->g_addr.s_addr = ipv4_param->addr;
if (!Add_Global_Address_to_List(assoc, G_Addr))
SN_LOG(SN_LOG_EVENT,
logsctperror("AddGlobalIPAddress: Address already in list",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
}
}
bytes_left -= param_size;
if (bytes_left < SN_MIN_PARAM_SIZE)
break;
param = SN_SCTP_NEXTPARAM(param);
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (++param_count > sysctl_param_proc_limit) {
SN_LOG(SN_LOG_EVENT,
logsctperror("Parameter parse limit exceeded (AddGlobalIPAddress)",
sm->sctp_hdr->v_tag, sysctl_param_proc_limit, direction));
break; /* limit exceeded*/
}
}
if (addr_param_count == 0) {
SN_LOG(SN_LOG_DETAIL,
logsctperror("AddGlobalIPAddress: no address parameters to add",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
}
}
/**
* @brief Add_Global_Address_to_List
*
* Adds a global IP address to an associations address list, if it is not
* already there. The first address added us usually the packet's address, and
* is most likely to be used, so it is added at the beginning. Subsequent
* addresses are added after this one.
*
* @param assoc Pointer to the association this SCTP Message belongs to
* @param G_addr Pointer to the global address to add
*
* @return 1 - success | 0 - fail
*/
static int Add_Global_Address_to_List(struct sctp_nat_assoc *assoc, struct sctp_GlobalAddress *G_addr)
{
struct sctp_GlobalAddress *iter_G_Addr = NULL, *first_G_Addr = NULL;
first_G_Addr = LIST_FIRST(&(assoc->Gaddr));
if (first_G_Addr == NULL) {
LIST_INSERT_HEAD(&(assoc->Gaddr), G_addr, list_Gaddr); /* add new address to beginning of list*/
} else {
LIST_FOREACH(iter_G_Addr, &(assoc->Gaddr), list_Gaddr) {
if (G_addr->g_addr.s_addr == iter_G_Addr->g_addr.s_addr)
return(0); /* already exists, so don't add */
}
LIST_INSERT_AFTER(first_G_Addr, G_addr, list_Gaddr); /* add address to end of list*/
}
assoc->num_Gaddr++;
return(1); /* success */
}
/** @ingroup packet_parser
* @brief RmGlobalIPAddresses from DelIP packets
*
* RmGlobalIPAddresses scans an ASCONF chunk for DelIP parameters to remove the
* given Global IP addresses from the association. It will not delete the
* the address if it is a list of one address.
*
*
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
*/
static void
RmGlobalIPAddresses(struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int direction)
{
struct sctp_asconf_addrv4_param *asconf_ipv4_param;
struct sctp_paramhdr *param;
struct sctp_GlobalAddress *G_Addr, *G_Addr_tmp;
struct in_addr g_addr;
int bytes_left;
int param_size;
int param_count;
if(direction == SN_TO_GLOBAL)
g_addr = sm->ip_hdr->ip_dst;
else
g_addr = sm->ip_hdr->ip_src;
bytes_left = sm->chunk_length;
param_count = 1;
param = sm->sctpchnk.Asconf;
if (bytes_left >= SN_MIN_PARAM_SIZE) {
param_size = SCTP_SIZE32(ntohs(param->param_length));
} else {
SN_LOG(SN_LOG_EVENT,
logsctperror("RmGlobalIPAddress: truncated packet - cannot remove IP addresses",
sm->sctp_hdr->v_tag, sysctl_track_global_addresses, direction));
return;
}
/* step through Asconf parameters */
while((bytes_left >= param_size) && (bytes_left >= sizeof(struct sctp_ipv4addr_param))) {
if (ntohs(param->param_type) == SCTP_DEL_IP_ADDRESS) {
asconf_ipv4_param = (struct sctp_asconf_addrv4_param *) param;
if (asconf_ipv4_param->addrp.addr == INADDR_ANY) { /* remove all bar pkt address */
LIST_FOREACH_SAFE(G_Addr, &(assoc->Gaddr), list_Gaddr, G_Addr_tmp) {
if(G_Addr->g_addr.s_addr != sm->ip_hdr->ip_src.s_addr) {
if (assoc->num_Gaddr > 1) { /* only delete if more than one */
LIST_REMOVE(G_Addr, list_Gaddr);
sn_free(G_Addr);
assoc->num_Gaddr--;
} else {
SN_LOG(SN_LOG_EVENT,
logsctperror("RmGlobalIPAddress: Request to remove last IP address (didn't)",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
}
}
}
return; /*shouldn't be any other addresses if the zero address is given*/
} else {
LIST_FOREACH_SAFE(G_Addr, &(assoc->Gaddr), list_Gaddr, G_Addr_tmp) {
if(G_Addr->g_addr.s_addr == asconf_ipv4_param->addrp.addr) {
if (assoc->num_Gaddr > 1) { /* only delete if more than one */
LIST_REMOVE(G_Addr, list_Gaddr);
sn_free(G_Addr);
assoc->num_Gaddr--;
break; /* Since add only adds new addresses, there should be no double entries */
} else {
SN_LOG(SN_LOG_EVENT,
logsctperror("RmGlobalIPAddress: Request to remove last IP address (didn't)",
sm->sctp_hdr->v_tag, assoc->num_Gaddr, direction));
}
}
}
}
}
bytes_left -= param_size;
if (bytes_left == 0) return;
else if (bytes_left < SN_MIN_PARAM_SIZE) {
SN_LOG(SN_LOG_EVENT,
logsctperror("RmGlobalIPAddress: truncated packet - may not have removed all IP addresses",
sm->sctp_hdr->v_tag, sysctl_track_global_addresses, direction));
return;
}
param = SN_SCTP_NEXTPARAM(param);
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (++param_count > sysctl_param_proc_limit) {
SN_LOG(SN_LOG_EVENT,
logsctperror("Parameter parse limit exceeded (RmGlobalIPAddress)",
sm->sctp_hdr->v_tag, sysctl_param_proc_limit, direction));
return; /* limit exceeded*/
}
}
}
/** @ingroup packet_parser
* @brief Check that ASCONF was successful
*
* Each ASCONF configuration parameter carries a correlation ID which should be
* matched with an ASCONFack. This is difficult for a NAT, since every
* association could potentially have a number of outstanding ASCONF
* configuration parameters, which should only be activated on receipt of the
* ACK.
*
* Currently we only look for an ACK when the NAT is setting up a new
* association (ie AddIP for a connection that the NAT does not know about
* because the original Init went through a public interface or another NAT)
* Since there is currently no connection on this path, there should be no other
* ASCONF configuration parameters outstanding, so we presume that if there is
* an ACK that it is responding to the AddIP and activate the new association.
*
* @param la Pointer to the relevant libalias instance
* @param sm Pointer to sctp message information
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
* @return 1 - success | 0 - fail
*/
static int
IsASCONFack(struct libalias *la, struct sctp_nat_msg *sm, int direction)
{
struct sctp_paramhdr *param;
int bytes_left;
int param_size;
int param_count;
param_count = 1;
param = sm->sctpchnk.Asconf;
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (param_size == 8)
return(1); /*success - default acknowledgement of everything */
bytes_left = sm->chunk_length;
if (bytes_left < param_size)
return(0); /* not found */
/* step through Asconf parameters */
while(bytes_left >= SN_ASCONFACK_PARAM_SIZE) {
if (ntohs(param->param_type) == SCTP_SUCCESS_REPORT)
return(1); /* success - but can't match correlation IDs - should only be one */
/* check others just in case */
bytes_left -= param_size;
if (bytes_left >= SN_MIN_PARAM_SIZE) {
param = SN_SCTP_NEXTPARAM(param);
} else {
return(0);
}
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (bytes_left < param_size) return(0);
if (++param_count > sysctl_param_proc_limit) {
SN_LOG(SN_LOG_EVENT,
logsctperror("Parameter parse limit exceeded (IsASCONFack)",
sm->sctp_hdr->v_tag, sysctl_param_proc_limit, direction));
return(0); /* not found limit exceeded*/
}
}
return(0); /* not success */
}
/** @ingroup packet_parser
* @brief Check to see if ASCONF contains an Add IP or Del IP parameter
*
* IsADDorDEL scans an ASCONF packet to see if it contains an AddIP or DelIP
* parameter
*
* @param la Pointer to the relevant libalias instance
* @param sm Pointer to sctp message information
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
*
* @return SCTP_ADD_IP_ADDRESS | SCTP_DEL_IP_ADDRESS | 0 - fail
*/
static int
IsADDorDEL(struct libalias *la, struct sctp_nat_msg *sm, int direction)
{
struct sctp_paramhdr *param;
int bytes_left;
int param_size;
int param_count;
param_count = 1;
param = sm->sctpchnk.Asconf;
param_size = SCTP_SIZE32(ntohs(param->param_length));
bytes_left = sm->chunk_length;
if (bytes_left < param_size)
return(0); /* not found */
/* step through Asconf parameters */
while(bytes_left >= SN_ASCONFACK_PARAM_SIZE) {
if (ntohs(param->param_type) == SCTP_ADD_IP_ADDRESS)
return(SCTP_ADD_IP_ADDRESS);
else if (ntohs(param->param_type) == SCTP_DEL_IP_ADDRESS)
return(SCTP_DEL_IP_ADDRESS);
/* check others just in case */
bytes_left -= param_size;
if (bytes_left >= SN_MIN_PARAM_SIZE) {
param = SN_SCTP_NEXTPARAM(param);
} else {
return(0); /*Neither found */
}
param_size = SCTP_SIZE32(ntohs(param->param_length));
if (bytes_left < param_size) return(0);
if (++param_count > sysctl_param_proc_limit) {
SN_LOG(SN_LOG_EVENT,
logsctperror("Parameter parse limit exceeded IsADDorDEL)",
sm->sctp_hdr->v_tag, sysctl_param_proc_limit, direction));
return(0); /* not found limit exceeded*/
}
}
return(0); /*Neither found */
}
/* ----------------------------------------------------------------------
* STATE MACHINE CODE
* ----------------------------------------------------------------------
*/
/** @addtogroup state_machine
*
* The SCTP NAT State Machine functions will:
* - Process an already parsed packet
* - Use the existing NAT Hash Tables
* - Determine the next state for the association
* - Update the NAT Hash Tables and Timer Queues
* - Return the appropriate action to take with the packet
*/
/** @ingroup state_machine
* @brief Process SCTP message
*
* This function is the base state machine. It calls the processing engine for
* each state.
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_DROP_PKT | SN_NAT_PKT | SN_REPLY_ABORT | SN_REPLY_ERROR | SN_PROCESSING_ERROR
*/
static int
ProcessSctpMsg(struct libalias *la, int direction, struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc)
{
int rtnval;
switch (assoc->state) {
case SN_ID: /* Idle */
rtnval = ID_process(la, direction, assoc, sm);
if (rtnval != SN_NAT_PKT) {
assoc->state = SN_RM;/* Mark for removal*/
}
return(rtnval);
case SN_INi: /* Initialising - Init */
return(INi_process(la, direction, assoc, sm));
case SN_INa: /* Initialising - AddIP */
return(INa_process(la, direction, assoc, sm));
case SN_UP: /* Association UP */
return(UP_process(la, direction, assoc, sm));
case SN_CL: /* Association Closing */
return(CL_process(la, direction, assoc, sm));
}
return(SN_PROCESSING_ERROR);
}
/** @ingroup state_machine
* @brief Process SCTP message while in the Idle state
*
* This function looks for an Incoming INIT or AddIP message.
*
* All other SCTP messages are invalid when in SN_ID, and are dropped.
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_NAT_PKT | SN_DROP_PKT | SN_REPLY_ABORT | SN_REPLY_ERROR
*/
static int
ID_process(struct libalias *la, int direction, struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm)
{
switch(sm->msg) {
case SN_SCTP_ASCONF: /* a packet containing an ASCONF chunk with ADDIP */
if (!sysctl_accept_global_ootb_addip && (direction == SN_TO_LOCAL))
return(SN_DROP_PKT);
/* if this Asconf packet does not contain the Vtag parameters it is of no use in Idle state */
if (!GetAsconfVtags(la, sm, &(assoc->l_vtag), &(assoc->g_vtag), direction))
return(SN_DROP_PKT);
case SN_SCTP_INIT: /* a packet containing an INIT chunk or an ASCONF AddIP */
if (sysctl_track_global_addresses)
AddGlobalIPAddresses(sm, assoc, direction);
switch(direction){
case SN_TO_GLOBAL:
assoc->l_addr = sm->ip_hdr->ip_src;
assoc->a_addr = FindAliasAddress(la, assoc->l_addr);
assoc->l_port = sm->sctp_hdr->src_port;
assoc->g_port = sm->sctp_hdr->dest_port;
if(sm->msg == SN_SCTP_INIT)
assoc->g_vtag = sm->sctpchnk.Init->initiate_tag;
if (AddSctpAssocGlobal(la, assoc)) /* DB clash *///**** need to add dst address
return((sm->msg == SN_SCTP_INIT) ? SN_REPLY_ABORT : SN_REPLY_ERROR);
if(sm->msg == SN_SCTP_ASCONF) {
if (AddSctpAssocLocal(la, assoc, sm->ip_hdr->ip_dst)) /* DB clash */
return(SN_REPLY_ERROR);
assoc->TableRegister |= SN_WAIT_TOLOCAL; /* wait for tolocal ack */
}
break;
case SN_TO_LOCAL:
assoc->l_addr = FindSctpRedirectAddress(la, sm);
assoc->a_addr = sm->ip_hdr->ip_dst;
assoc->l_port = sm->sctp_hdr->dest_port;
assoc->g_port = sm->sctp_hdr->src_port;
if(sm->msg == SN_SCTP_INIT)
assoc->l_vtag = sm->sctpchnk.Init->initiate_tag;
if (AddSctpAssocLocal(la, assoc, sm->ip_hdr->ip_src)) /* DB clash */
return((sm->msg == SN_SCTP_INIT) ? SN_REPLY_ABORT : SN_REPLY_ERROR);
if(sm->msg == SN_SCTP_ASCONF) {
if (AddSctpAssocGlobal(la, assoc)) /* DB clash */ //**** need to add src address
return(SN_REPLY_ERROR);
assoc->TableRegister |= SN_WAIT_TOGLOBAL; /* wait for toglobal ack */
}
break;
}
assoc->state = (sm->msg == SN_SCTP_INIT) ? SN_INi : SN_INa;
assoc->exp = SN_I_T(la);
sctp_AddTimeOut(la,assoc);
return(SN_NAT_PKT);
default: /* Any other type of SCTP message is not valid in Idle */
return(SN_DROP_PKT);
}
return(SN_DROP_PKT);/* shouldn't get here very bad: log, drop and hope for the best */
}
/** @ingroup state_machine
* @brief Process SCTP message while waiting for an INIT-ACK message
*
* Only an INIT-ACK, resent INIT, or an ABORT SCTP packet are valid in this
* state, all other packets are dropped.
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_NAT_PKT | SN_DROP_PKT | SN_REPLY_ABORT
*/
static int
INi_process(struct libalias *la, int direction, struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm)
{
switch(sm->msg) {
case SN_SCTP_INIT: /* a packet containing a retransmitted INIT chunk */
sctp_ResetTimeOut(la, assoc, SN_I_T(la));
return(SN_NAT_PKT);
case SN_SCTP_INITACK: /* a packet containing an INIT-ACK chunk */
switch(direction){
case SN_TO_LOCAL:
if (assoc->num_Gaddr) /*If tracking global addresses for this association */
AddGlobalIPAddresses(sm, assoc, direction);
assoc->l_vtag = sm->sctpchnk.Init->initiate_tag;
if (AddSctpAssocLocal(la, assoc, sm->ip_hdr->ip_src)) { /* DB clash */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_SEND_ABORT);
}
break;
case SN_TO_GLOBAL:
assoc->l_addr = sm->ip_hdr->ip_src; // Only if not set in Init! *
assoc->g_vtag = sm->sctpchnk.Init->initiate_tag;
if (AddSctpAssocGlobal(la, assoc)) { /* DB clash */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_SEND_ABORT);
}
break;
}
assoc->state = SN_UP;/* association established for NAT */
sctp_ResetTimeOut(la,assoc, SN_U_T(la));
return(SN_NAT_PKT);
case SN_SCTP_ABORT: /* a packet containing an ABORT chunk */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
default:
return(SN_DROP_PKT);
}
return(SN_DROP_PKT);/* shouldn't get here very bad: log, drop and hope for the best */
}
/** @ingroup state_machine
* @brief Process SCTP message while waiting for an AddIp-ACK message
*
* Only an AddIP-ACK, resent AddIP, or an ABORT message are valid, all other
* SCTP packets are dropped
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_NAT_PKT | SN_DROP_PKT
*/
static int
INa_process(struct libalias *la, int direction,struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm)
{
switch(sm->msg) {
case SN_SCTP_ASCONF: /* a packet containing an ASCONF chunk*/
sctp_ResetTimeOut(la,assoc, SN_I_T(la));
return(SN_NAT_PKT);
case SN_SCTP_ASCONFACK: /* a packet containing an ASCONF chunk with a ADDIP-ACK */
switch(direction){
case SN_TO_LOCAL:
if (!(assoc->TableRegister & SN_WAIT_TOLOCAL)) /* wrong direction */
return(SN_DROP_PKT);
break;
case SN_TO_GLOBAL:
if (!(assoc->TableRegister & SN_WAIT_TOGLOBAL)) /* wrong direction */
return(SN_DROP_PKT);
}
if (IsASCONFack(la,sm,direction)) {
assoc->TableRegister &= SN_BOTH_TBL; /* remove wait flags */
assoc->state = SN_UP; /* association established for NAT */
sctp_ResetTimeOut(la,assoc, SN_U_T(la));
return(SN_NAT_PKT);
} else {
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
}
case SN_SCTP_ABORT: /* a packet containing an ABORT chunk */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
default:
return(SN_DROP_PKT);
}
return(SN_DROP_PKT);/* shouldn't get here very bad: log, drop and hope for the best */
}
/** @ingroup state_machine
* @brief Process SCTP messages while association is UP redirecting packets
*
* While in the SN_UP state, all packets for the particular association
* are passed. Only a SHUT-ACK or an ABORT will cause a change of state.
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_NAT_PKT | SN_DROP_PKT
*/
static int
UP_process(struct libalias *la, int direction, struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm)
{
switch(sm->msg) {
case SN_SCTP_SHUTACK: /* a packet containing a SHUTDOWN-ACK chunk */
assoc->state = SN_CL;
sctp_ResetTimeOut(la,assoc, SN_C_T(la));
return(SN_NAT_PKT);
case SN_SCTP_ABORT: /* a packet containing an ABORT chunk */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
case SN_SCTP_ASCONF: /* a packet containing an ASCONF chunk*/
if ((direction == SN_TO_LOCAL) && assoc->num_Gaddr) /*If tracking global addresses for this association & from global side */
switch(IsADDorDEL(la,sm,direction)) {
case SCTP_ADD_IP_ADDRESS:
AddGlobalIPAddresses(sm, assoc, direction);
break;
case SCTP_DEL_IP_ADDRESS:
RmGlobalIPAddresses(sm, assoc, direction);
break;
} /* fall through to default */
default:
sctp_ResetTimeOut(la,assoc, SN_U_T(la));
return(SN_NAT_PKT); /* forward packet */
}
return(SN_DROP_PKT);/* shouldn't get here very bad: log, drop and hope for the best */
}
/** @ingroup state_machine
* @brief Process SCTP message while association is in the process of closing
*
* This function waits for a SHUT-COMP to close the association. Depending on
* the the setting of sysctl_holddown_timer it may not remove the association
* immediately, but leave it up until SN_X_T(la). Only SHUT-COMP, SHUT-ACK, and
* ABORT packets are permitted in this state. All other packets are dropped.
*
* @param la Pointer to the relevant libalias instance
* @param direction SN_TO_LOCAL | SN_TO_GLOBAL
* @param sm Pointer to sctp message information
* @param assoc Pointer to the association this SCTP Message belongs to
*
* @return SN_NAT_PKT | SN_DROP_PKT
*/
static int
CL_process(struct libalias *la, int direction,struct sctp_nat_assoc *assoc, struct sctp_nat_msg *sm)
{
switch(sm->msg) {
case SN_SCTP_SHUTCOMP: /* a packet containing a SHUTDOWN-COMPLETE chunk */
assoc->state = SN_CL; /* Stay in Close state until timeout */
if (sysctl_holddown_timer > 0)
sctp_ResetTimeOut(la, assoc, SN_X_T(la));/* allow to stay open for Tbit packets*/
else
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
case SN_SCTP_SHUTACK: /* a packet containing a SHUTDOWN-ACK chunk */
assoc->state = SN_CL; /* Stay in Close state until timeout */
sctp_ResetTimeOut(la, assoc, SN_C_T(la));
return(SN_NAT_PKT);
case SN_SCTP_ABORT: /* a packet containing an ABORT chunk */
assoc->state = SN_RM;/* Mark for removal*/
return(SN_NAT_PKT);
default:
return(SN_DROP_PKT);
}
return(SN_DROP_PKT);/* shouldn't get here very bad: log, drop and hope for the best */
}
/* ----------------------------------------------------------------------
* HASH TABLE CODE
* ----------------------------------------------------------------------
*/
/** @addtogroup Hash
*
* The Hash functions facilitate searching the NAT Hash Tables for associations
* as well as adding/removing associations from the table(s).
*/
/** @ingroup Hash
* @brief Find the SCTP association given the local address, port and vtag
*
* Searches the local look-up table for the association entry matching the
* provided local <address:ports:vtag> tuple
*
* @param la Pointer to the relevant libalias instance
* @param l_addr local address
* @param g_addr global address
* @param l_vtag local Vtag
* @param l_port local Port
* @param g_port global Port
*
* @return pointer to association or NULL
*/
static struct sctp_nat_assoc*
FindSctpLocal(struct libalias *la, struct in_addr l_addr, struct in_addr g_addr, uint32_t l_vtag, uint16_t l_port, uint16_t g_port)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
struct sctp_GlobalAddress *G_Addr = NULL;
if (l_vtag != 0) { /* an init packet, vtag==0 */
i = SN_TABLE_HASH(l_vtag, l_port, la->sctpNatTableSize);
LIST_FOREACH(assoc, &la->sctpTableLocal[i], list_L) {
if ((assoc->l_vtag == l_vtag) && (assoc->l_port == l_port) && (assoc->g_port == g_port)\
&& (assoc->l_addr.s_addr == l_addr.s_addr)) {
if (assoc->num_Gaddr) {
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
if(G_Addr->g_addr.s_addr == g_addr.s_addr)
return(assoc);
}
} else {
return(assoc);
}
}
}
}
return(NULL);
}
/** @ingroup Hash
* @brief Check for Global Clash
*
* Searches the global look-up table for the association entry matching the
* provided global <(addresses):ports:vtag> tuple
*
* @param la Pointer to the relevant libalias instance
* @param Cassoc association being checked for a clash
*
* @return pointer to association or NULL
*/
static struct sctp_nat_assoc*
FindSctpGlobalClash(struct libalias *la, struct sctp_nat_assoc *Cassoc)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
struct sctp_GlobalAddress *G_Addr = NULL;
struct sctp_GlobalAddress *G_AddrC = NULL;
if (Cassoc->g_vtag != 0) { /* an init packet, vtag==0 */
i = SN_TABLE_HASH(Cassoc->g_vtag, Cassoc->g_port, la->sctpNatTableSize);
LIST_FOREACH(assoc, &la->sctpTableGlobal[i], list_G) {
if ((assoc->g_vtag == Cassoc->g_vtag) && (assoc->g_port == Cassoc->g_port) && (assoc->l_port == Cassoc->l_port)) {
if (assoc->num_Gaddr) {
LIST_FOREACH(G_AddrC, &(Cassoc->Gaddr), list_Gaddr) {
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
if(G_Addr->g_addr.s_addr == G_AddrC->g_addr.s_addr)
return(assoc);
}
}
} else {
return(assoc);
}
}
}
}
return(NULL);
}
/** @ingroup Hash
* @brief Find the SCTP association given the global port and vtag
*
* Searches the global look-up table for the association entry matching the
* provided global <address:ports:vtag> tuple
*
* If all but the global address match it sets partial_match to 1 to indicate a
* partial match. If the NAT is tracking global IP addresses for this
* association, the NAT may respond with an ERRORM to request the missing
* address to be added.
*
* @param la Pointer to the relevant libalias instance
* @param g_addr global address
* @param g_vtag global vtag
* @param g_port global port
* @param l_port local port
*
* @return pointer to association or NULL
*/
static struct sctp_nat_assoc*
FindSctpGlobal(struct libalias *la, struct in_addr g_addr, uint32_t g_vtag, uint16_t g_port, uint16_t l_port, int *partial_match)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
struct sctp_GlobalAddress *G_Addr = NULL;
*partial_match = 0;
if (g_vtag != 0) { /* an init packet, vtag==0 */
i = SN_TABLE_HASH(g_vtag, g_port, la->sctpNatTableSize);
LIST_FOREACH(assoc, &la->sctpTableGlobal[i], list_G) {
if ((assoc->g_vtag == g_vtag) && (assoc->g_port == g_port) && (assoc->l_port == l_port)) {
*partial_match = 1;
if (assoc->num_Gaddr) {
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
if(G_Addr->g_addr.s_addr == g_addr.s_addr)
return(assoc);
}
} else {
return(assoc);
}
}
}
}
return(NULL);
}
/** @ingroup Hash
* @brief Find the SCTP association for a T-Flag message (given the global port and local vtag)
*
* Searches the local look-up table for a unique association entry matching the
* provided global port and local vtag information
*
* @param la Pointer to the relevant libalias instance
* @param g_addr global address
* @param l_vtag local Vtag
* @param g_port global Port
* @param l_port local Port
*
* @return pointer to association or NULL
*/
static struct sctp_nat_assoc*
FindSctpLocalT(struct libalias *la, struct in_addr g_addr, uint32_t l_vtag, uint16_t g_port, uint16_t l_port)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL, *lastmatch = NULL;
struct sctp_GlobalAddress *G_Addr = NULL;
int cnt = 0;
if (l_vtag != 0) { /* an init packet, vtag==0 */
i = SN_TABLE_HASH(l_vtag, g_port, la->sctpNatTableSize);
LIST_FOREACH(assoc, &la->sctpTableGlobal[i], list_G) {
if ((assoc->g_vtag == l_vtag) && (assoc->g_port == g_port) && (assoc->l_port == l_port)) {
if (assoc->num_Gaddr) {
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
if(G_Addr->g_addr.s_addr == G_Addr->g_addr.s_addr)
return(assoc); /* full match */
}
} else {
if (++cnt > 1) return(NULL);
lastmatch = assoc;
}
}
}
}
/* If there is more than one match we do not know which local address to send to */
return( cnt ? lastmatch : NULL );
}
/** @ingroup Hash
* @brief Find the SCTP association for a T-Flag message (given the local port and global vtag)
*
* Searches the global look-up table for a unique association entry matching the
* provided local port and global vtag information
*
* @param la Pointer to the relevant libalias instance
* @param g_addr global address
* @param g_vtag global vtag
* @param l_port local port
* @param g_port global port
*
* @return pointer to association or NULL
*/
static struct sctp_nat_assoc*
FindSctpGlobalT(struct libalias *la, struct in_addr g_addr, uint32_t g_vtag, uint16_t l_port, uint16_t g_port)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
struct sctp_GlobalAddress *G_Addr = NULL;
if (g_vtag != 0) { /* an init packet, vtag==0 */
i = SN_TABLE_HASH(g_vtag, l_port, la->sctpNatTableSize);
LIST_FOREACH(assoc, &la->sctpTableLocal[i], list_L) {
if ((assoc->l_vtag == g_vtag) && (assoc->l_port == l_port) && (assoc->g_port == g_port)) {
if (assoc->num_Gaddr) {
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
if(G_Addr->g_addr.s_addr == g_addr.s_addr)
return(assoc);
}
} else {
return(assoc);
}
}
}
}
return(NULL);
}
/** @ingroup Hash
* @brief Add the sctp association information to the local look up table
*
* Searches the local look-up table for an existing association with the same
* details. If a match exists and is ONLY in the local look-up table then this
* is a repeated INIT packet, we need to remove this association from the
* look-up table and add the new association
*
* The new association is added to the head of the list and state is updated
*
* @param la Pointer to the relevant libalias instance
* @param assoc pointer to sctp association
* @param g_addr global address
*
* @return SN_ADD_OK | SN_ADD_CLASH
*/
static int
AddSctpAssocLocal(struct libalias *la, struct sctp_nat_assoc *assoc, struct in_addr g_addr)
{
struct sctp_nat_assoc *found;
LIBALIAS_LOCK_ASSERT(la);
found = FindSctpLocal(la, assoc->l_addr, g_addr, assoc->l_vtag, assoc->l_port, assoc->g_port);
/*
* Note that if a different global address initiated this Init,
* ie it wasn't resent as presumed:
* - the local receiver if receiving it for the first time will establish
* an association with the new global host
* - if receiving an init from a different global address after sending a
* lost initack it will send an initack to the new global host, the first
* association attempt will then be blocked if retried.
*/
if (found != NULL) {
if ((found->TableRegister == SN_LOCAL_TBL) && (found->g_port == assoc->g_port)) { /* resent message */
RmSctpAssoc(la, found);
sctp_RmTimeOut(la, found);
freeGlobalAddressList(found);
sn_free(found);
} else
return(SN_ADD_CLASH);
}
LIST_INSERT_HEAD(&la->sctpTableLocal[SN_TABLE_HASH(assoc->l_vtag, assoc->l_port, la->sctpNatTableSize)],
assoc, list_L);
assoc->TableRegister |= SN_LOCAL_TBL;
la->sctpLinkCount++; //increment link count
if (assoc->TableRegister == SN_BOTH_TBL) {
/* libalias log -- controlled by libalias */
if (la->packetAliasMode & PKT_ALIAS_LOG)
SctpShowAliasStats(la);
SN_LOG(SN_LOG_INFO, logsctpassoc(assoc, "^"));
}
return(SN_ADD_OK);
}
/** @ingroup Hash
* @brief Add the sctp association information to the global look up table
*
* Searches the global look-up table for an existing association with the same
* details. If a match exists and is ONLY in the global look-up table then this
* is a repeated INIT packet, we need to remove this association from the
* look-up table and add the new association
*
* The new association is added to the head of the list and state is updated
*
* @param la Pointer to the relevant libalias instance
* @param assoc pointer to sctp association
*
* @return SN_ADD_OK | SN_ADD_CLASH
*/
static int
AddSctpAssocGlobal(struct libalias *la, struct sctp_nat_assoc *assoc)
{
struct sctp_nat_assoc *found;
LIBALIAS_LOCK_ASSERT(la);
found = FindSctpGlobalClash(la, assoc);
if (found != NULL) {
if ((found->TableRegister == SN_GLOBAL_TBL) && \
(found->l_addr.s_addr == assoc->l_addr.s_addr) && (found->l_port == assoc->l_port)) { /* resent message */
RmSctpAssoc(la, found);
sctp_RmTimeOut(la, found);
freeGlobalAddressList(found);
sn_free(found);
} else
return(SN_ADD_CLASH);
}
LIST_INSERT_HEAD(&la->sctpTableGlobal[SN_TABLE_HASH(assoc->g_vtag, assoc->g_port, la->sctpNatTableSize)],
assoc, list_G);
assoc->TableRegister |= SN_GLOBAL_TBL;
la->sctpLinkCount++; //increment link count
if (assoc->TableRegister == SN_BOTH_TBL) {
/* libalias log -- controlled by libalias */
if (la->packetAliasMode & PKT_ALIAS_LOG)
SctpShowAliasStats(la);
SN_LOG(SN_LOG_INFO, logsctpassoc(assoc, "^"));
}
return(SN_ADD_OK);
}
/** @ingroup Hash
* @brief Remove the sctp association information from the look up table
*
* For each of the two (local/global) look-up tables, remove the association
* from that table IF it has been registered in that table.
*
* NOTE: The calling code is responsible for freeing memory allocated to the
* association structure itself
*
* NOTE: The association is NOT removed from the timer queue
*
* @param la Pointer to the relevant libalias instance
* @param assoc pointer to sctp association
*/
static void
RmSctpAssoc(struct libalias *la, struct sctp_nat_assoc *assoc)
{
// struct sctp_nat_assoc *found;
if (assoc == NULL) {
/* very bad, log and die*/
SN_LOG(SN_LOG_LOW,
logsctperror("ERROR: alias_sctp:RmSctpAssoc(NULL)\n", 0, 0, SN_TO_NODIR));
return;
}
/* log if association is fully up and now closing */
if (assoc->TableRegister == SN_BOTH_TBL) {
SN_LOG(SN_LOG_INFO, logsctpassoc(assoc, "$"));
}
LIBALIAS_LOCK_ASSERT(la);
if (assoc->TableRegister & SN_LOCAL_TBL) {
assoc->TableRegister ^= SN_LOCAL_TBL;
la->sctpLinkCount--; //decrement link count
LIST_REMOVE(assoc, list_L);
}
if (assoc->TableRegister & SN_GLOBAL_TBL) {
assoc->TableRegister ^= SN_GLOBAL_TBL;
la->sctpLinkCount--; //decrement link count
LIST_REMOVE(assoc, list_G);
}
// sn_free(assoc); //Don't remove now, remove if needed later
/* libalias logging -- controlled by libalias log definition */
if (la->packetAliasMode & PKT_ALIAS_LOG)
SctpShowAliasStats(la);
}
/**
* @ingroup Hash
* @brief free the Global Address List memory
*
* freeGlobalAddressList deletes all global IP addresses in an associations
* global IP address list.
*
* @param assoc
*/
static void freeGlobalAddressList(struct sctp_nat_assoc *assoc)
{
struct sctp_GlobalAddress *gaddr1=NULL,*gaddr2=NULL;
/*free global address list*/
gaddr1 = LIST_FIRST(&(assoc->Gaddr));
while (gaddr1 != NULL) {
gaddr2 = LIST_NEXT(gaddr1, list_Gaddr);
sn_free(gaddr1);
gaddr1 = gaddr2;
}
}
/* ----------------------------------------------------------------------
* TIMER QUEUE CODE
* ----------------------------------------------------------------------
*/
/** @addtogroup Timer
*
* The timer queue management functions are designed to operate efficiently with
* a minimum of interaction with the queues.
*
* Once a timeout is set in the queue it will not be altered in the queue unless
* it has to be changed to a shorter time (usually only for aborts and closing).
* On a queue timeout, the real expiry time is checked, and if not leq than the
* timeout it is requeued (O(1)) at its later time. This is especially important
* for normal packets sent during an association. When a timer expires, it is
* updated to its new expiration time if necessary, or processed as a
* timeout. This means that while in UP state, the timing queue is only altered
* every U_T (every few minutes) for a particular association.
*/
/** @ingroup Timer
* @brief Add an association timeout to the timer queue
*
* Determine the location in the queue to add the timeout and insert the
* association into the list at that queue position
*
* @param la
* @param assoc
*/
static void
sctp_AddTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc)
{
int add_loc;
LIBALIAS_LOCK_ASSERT(la);
add_loc = assoc->exp - la->sctpNatTimer.loc_time + la->sctpNatTimer.cur_loc;
if (add_loc >= SN_TIMER_QUEUE_SIZE)
add_loc -= SN_TIMER_QUEUE_SIZE;
LIST_INSERT_HEAD(&la->sctpNatTimer.TimerQ[add_loc], assoc, timer_Q);
assoc->exp_loc = add_loc;
}
/** @ingroup Timer
* @brief Remove an association from timer queue
*
* This is an O(1) operation to remove the association pointer from its
* current position in the timer queue
*
* @param la Pointer to the relevant libalias instance
* @param assoc pointer to sctp association
*/
static void
sctp_RmTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc)
{
LIBALIAS_LOCK_ASSERT(la);
LIST_REMOVE(assoc, timer_Q);/* Note this is O(1) */
}
/** @ingroup Timer
* @brief Reset timer in timer queue
*
* Reset the actual timeout for the specified association. If it is earlier than
* the existing timeout, then remove and re-install the association into the
* queue
*
* @param la Pointer to the relevant libalias instance
* @param assoc pointer to sctp association
* @param newexp New expiration time
*/
static void
sctp_ResetTimeOut(struct libalias *la, struct sctp_nat_assoc *assoc, int newexp)
{
if (newexp < assoc->exp) {
sctp_RmTimeOut(la, assoc);
assoc->exp = newexp;
sctp_AddTimeOut(la, assoc);
} else {
assoc->exp = newexp;
}
}
/** @ingroup Timer
* @brief Check timer Q against current time
*
* Loop through each entry in the timer queue since the last time we processed
* the timer queue until now (the current time). For each association in the
* event list, we remove it from that position in the timer queue and check if
* it has really expired. If so we:
* - Log the timer expiry
* - Remove the association from the NAT tables
* - Release the memory used by the association
*
* If the timer hasn't really expired we place the association into its new
* correct position in the timer queue.
*
* @param la Pointer to the relevant libalias instance
*/
void
sctp_CheckTimers(struct libalias *la)
{
struct sctp_nat_assoc *assoc;
LIBALIAS_LOCK_ASSERT(la);
while(la->timeStamp >= la->sctpNatTimer.loc_time) {
while (!LIST_EMPTY(&la->sctpNatTimer.TimerQ[la->sctpNatTimer.cur_loc])) {
assoc = LIST_FIRST(&la->sctpNatTimer.TimerQ[la->sctpNatTimer.cur_loc]);
//SLIST_REMOVE_HEAD(&la->sctpNatTimer.TimerQ[la->sctpNatTimer.cur_loc], timer_Q);
LIST_REMOVE(assoc, timer_Q);
if (la->timeStamp >= assoc->exp) { /* state expired */
SN_LOG(((assoc->state == SN_CL)?(SN_LOG_DEBUG):(SN_LOG_INFO)),
logsctperror("Timer Expired", assoc->g_vtag, assoc->state, SN_TO_NODIR));
RmSctpAssoc(la, assoc);
freeGlobalAddressList(assoc);
sn_free(assoc);
} else {/* state not expired, reschedule timer*/
sctp_AddTimeOut(la, assoc);
}
}
/* Goto next location in the timer queue*/
++la->sctpNatTimer.loc_time;
if (++la->sctpNatTimer.cur_loc >= SN_TIMER_QUEUE_SIZE)
la->sctpNatTimer.cur_loc = 0;
}
}
/* ----------------------------------------------------------------------
* LOGGING CODE
* ----------------------------------------------------------------------
*/
/** @addtogroup Logging
*
* The logging functions provide logging of different items ranging from logging
* a simple message, through logging an association details to logging the
* current state of the NAT tables
*/
/** @ingroup Logging
* @brief Log sctp nat errors
*
* @param errormsg Error message to be logged
* @param vtag Current Vtag
* @param error Error number
* @param direction Direction of packet
*/
static void
logsctperror(char* errormsg, uint32_t vtag, int error, int direction)
{
char dir;
switch(direction) {
case SN_TO_LOCAL:
dir = 'L';
break;
case SN_TO_GLOBAL:
dir = 'G';
break;
default:
dir = '*';
break;
}
SctpAliasLog("->%c %s (vt=%u) %d\n", dir, errormsg, ntohl(vtag), error);
}
/** @ingroup Logging
* @brief Log what the parser parsed
*
* @param direction Direction of packet
* @param sm Pointer to sctp message information
*/
static void
logsctpparse(int direction, struct sctp_nat_msg *sm)
{
char *ploc, *pstate;
switch(direction) {
case SN_TO_LOCAL:
ploc = "TO_LOCAL -";
break;
case SN_TO_GLOBAL:
ploc = "TO_GLOBAL -";
break;
default:
ploc = "";
}
switch(sm->msg) {
case SN_SCTP_INIT:
pstate = "Init";
break;
case SN_SCTP_INITACK:
pstate = "InitAck";
break;
case SN_SCTP_ABORT:
pstate = "Abort";
break;
case SN_SCTP_SHUTACK:
pstate = "ShutAck";
break;
case SN_SCTP_SHUTCOMP:
pstate = "ShutComp";
break;
case SN_SCTP_ASCONF:
pstate = "Asconf";
break;
case SN_SCTP_ASCONFACK:
pstate = "AsconfAck";
break;
case SN_SCTP_OTHER:
pstate = "Other";
break;
default:
pstate = "***ERROR***";
break;
}
SctpAliasLog("Parsed: %s %s\n", ploc, pstate);
}
/** @ingroup Logging
* @brief Log an SCTP association's details
*
* @param assoc pointer to sctp association
* @param s Character that indicates the state of processing for this packet
*/
static void logsctpassoc(struct sctp_nat_assoc *assoc, char* s)
{
struct sctp_GlobalAddress *G_Addr = NULL;
char *sp;
switch(assoc->state) {
case SN_ID:
sp = "ID ";
break;
case SN_INi:
sp = "INi ";
break;
case SN_INa:
sp = "INa ";
break;
case SN_UP:
sp = "UP ";
break;
case SN_CL:
sp = "CL ";
break;
case SN_RM:
sp = "RM ";
break;
default:
sp = "***ERROR***";
break;
}
SctpAliasLog("%sAssoc: %s exp=%u la=%s lv=%u lp=%u gv=%u gp=%u tbl=%d\n",
s, sp, assoc->exp, inet_ntoa(assoc->l_addr), ntohl(assoc->l_vtag),
ntohs(assoc->l_port), ntohl(assoc->g_vtag), ntohs(assoc->g_port),
assoc->TableRegister);
/* list global addresses */
LIST_FOREACH(G_Addr, &(assoc->Gaddr), list_Gaddr) {
SctpAliasLog("\t\tga=%s\n",inet_ntoa(G_Addr->g_addr));
}
}
/** @ingroup Logging
* @brief Output Global table to log
*
* @param la Pointer to the relevant libalias instance
*/
static void logSctpGlobal(struct libalias *la)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
SctpAliasLog("G->\n");
for (i=0; i < la->sctpNatTableSize; i++) {
LIST_FOREACH(assoc, &la->sctpTableGlobal[i], list_G) {
logsctpassoc(assoc, " ");
}
}
}
/** @ingroup Logging
* @brief Output Local table to log
*
* @param la Pointer to the relevant libalias instance
*/
static void logSctpLocal(struct libalias *la)
{
u_int i;
struct sctp_nat_assoc *assoc = NULL;
SctpAliasLog("L->\n");
for (i=0; i < la->sctpNatTableSize; i++) {
LIST_FOREACH(assoc, &la->sctpTableLocal[i], list_L) {
logsctpassoc(assoc, " ");
}
}
}
/** @ingroup Logging
* @brief Output timer queue to log
*
* @param la Pointer to the relevant libalias instance
*/
static void logTimerQ(struct libalias *la)
{
static char buf[50];
u_int i;
struct sctp_nat_assoc *assoc = NULL;
SctpAliasLog("t->\n");
for (i=0; i < SN_TIMER_QUEUE_SIZE; i++) {
LIST_FOREACH(assoc, &la->sctpNatTimer.TimerQ[i], timer_Q) {
snprintf(buf, 50, " l=%u ",i);
//SctpAliasLog(la->logDesc," l=%d ",i);
logsctpassoc(assoc, buf);
}
}
}
/** @ingroup Logging
* @brief Sctp NAT logging function
*
* This function is based on a similar function in alias_db.c
*
* @param str/stream logging descriptor
* @param format printf type string
*/
#ifdef _KERNEL
static void
SctpAliasLog(const char *format, ...)
{
char buffer[LIBALIAS_BUF_SIZE];
va_list ap;
va_start(ap, format);
vsnprintf(buffer, LIBALIAS_BUF_SIZE, format, ap);
va_end(ap);
log(LOG_SECURITY | LOG_INFO,
"alias_sctp: %s", buffer);
}
#else
static void
SctpAliasLog(FILE *stream, const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stream, format, ap);
va_end(ap);
fflush(stream);
}
#endif