diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 83ba7a4f5474..997be62dfbba 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -2183,17 +2183,173 @@ Redirect and LSNAT support follow closely the syntax used in See Section .Sx EXAMPLES for some examples on how to do redirect and lsnat. +.Sh SCTP NAT SUPPORT +Sctp nat can be configured in a simillar manner to TCP through the +ipfw command line tool +.Xr ipfw 8 +, the main difference is that +.Nm sctp nat +does not do port +translation. Since the local and global side ports will be the same, +there is no need to specify both. Ports are redirected as follows: +.Bd -ragged -offset indent +.Bk -words +.Cm nat +.Ar nat_number +.Cm config if +.Ar nic +.Cm redirect_port sctp +.Ar ip_address [,addr_list] {[port | port-port] [,ports]} +.Ek +.Ed +.Pp +. +Most +.B sctp nat +configuration can be done in real-time through the +.B sysctl(8) +interface. All may be changed dynamically, though the hash_table size will only +change for new +.Nm nat +instances. See +.Sx SYSCTL VARIABLES +for more info. .Sh SYSCTL VARIABLES A set of .Xr sysctl 8 variables controls the behaviour of the firewall and associated modules -.Pq Nm dummynet , bridge . +.Pq Nm dummynet , bridge , sctp nat . These are shown below together with their default value (but always check with the .Xr sysctl 8 command what value is actually in use) and meaning: .Bl -tag -width indent +.It Va net.inet.ip.alias.sctp.accept_global_ootb_addip: No 0 +Defines how the +.Nm nat +responds to receipt of global OOTB ASCONF-AddIP: +.Bl -tag -width indent +.It Cm 0 +No response (unless a partially matching association exists - +ports and vtags match but global address does not) +.It Cm 1 +.Nm nat +will accept and process all OOTB global AddIP messages. +.El +.Pp +Option 1 should never be selected as this forms a security risk. An attacker can +establish multiple fake associations by sending AddIP messages. +.It Va net.inet.ip.alias.sctp.chunk_proc_limit: No 5 +Defines the maximum number of chunks in an SCTP packet that will be parsed for a +packet that matches an existing association. This value is enforced to be greater or equal +than +.Cm net.inet.ip.alias.sctp.initialising_chunk_proc_limit . +A high value is +a DoS risk yet setting too low a value may result in important control chunks in +the packet not being located and parsed. +.It Va net.inet.ip.alias.sctp.error_on_ootb: No 1 +Defines when the +.Nm nat +responds to any Out-of-the-Blue (OOTB) packets with ErrorM +packets. An OOTB packet is a packet that arrives with no existing association +registered in the +.Nm nat +and is not an INIT or ASCONF-AddIP packet: +.Bl -tag -width indent +.It Cm 0 +ErrorM is never sent in response to OOTB packets. +.It Cm 1 +ErrorM is only sent to OOTB packets received on the local side. +.It Cm 2 +ErrorM is sent to the local side and on the global side ONLY if there is a +partial match (ports and vtags match but the source global IP does not). This +value is only useful if the +.Nm nat +is tracking global IP addresses. +.It Cm 3 +ErrorM is sent in response to all OOTB packets on both the local and global side +(DoS risk). +.El +.Pp +At the moment the default is 0, since the ErrorM packet is not yet +supported by most SCTP stacks. When it is supported, and if not tracking +global addresses, we recommend setting this value to 1 to allow +multi-homed local hosts to function with the +.Nm nat . +To track global addresses, we recommend setting this value to 2 to +allow global hosts to be informed when they need to (re)send an +ASCONF-AddIP. Value 3 should never be chosen (except for debugging) as +the +.Nm nat +will respond to all OOTB global packets (a DoS risk). +.It Va net.inet.ip.alias.sctp.hashtable_size: No 2003 +Size of hash tables used for +.Nm nat +lookups (100 < prime_number > 1000001) +This value sets the +.Nm hash table +size for any future created +.Nm nat +instance and therefore must be set prior to creating a +.Nm nat +instance. +The table sizes my be changed to suit specific needs. If there will be few +concurrent associations, and memory is scarce, you may make these smaller. If +there will be many thousands (or millions) of concurrent associations, you +should make these larger. A prime number is best for the table size. The sysctl +update function will adjust your input value to the next highest prime number. +.It Va net.inet.ip.alias.sctp.holddown_time: No 0 +Hold association in table for this many seconds after receiving a +SHUTDOWN-COMPLETE. This allows endpoints to correct shutdown gracefully if a +shutdown_complete is lost and retransmissions are required. +.It Va net.inet.ip.alias.sctp.init_timer: No 15 +Timeout value while waiting for (INIT-ACK|AddIP-ACK). +This value cannot be 0. +.It Va net.inet.ip.alias.sctp.initialising_chunk_proc_limit: No 2 +Defines the maximum number of chunks in an SCTP packet that will be parsed when +no existing association exists that matches that packet. Ideally this packet +will only be an INIT or ASCONF-AddIP packet. A higher value may become a DoS +risk as malformed packets can consume processing resources. +.It Va net.inet.ip.alias.sctp.param_proc_limit: No 25 +Defines the maximum number of parameters within a chunk that will be parsed in a +packet. As for other similar sysctl variables, larger values pose a DoS risk. +.It Va net.inet.ip.alias.sctp.log_level: No 0 +Level of detail in the system log messages (0 \- minimal, 1 \- event, +2 \- info, 3 \- detail, 4 \- debug, 5 \- max debug). May be a good +option in high loss environments. +.It Va net.inet.ip.alias.sctp.shutdown_time: No 15 +Timeout value while waiting for SHUTDOWN-COMPLETE. +This value cannot be 0. +.It Va net.inet.ip.alias.sctp.track_global_addresses: No 0 +Enables/disables global IP address tracking within the +.Nm nat +and places an +upper limit on the number of addresses tracked for each association: +.Bl -tag -width indent +.It Cm 0 +Global tracking is disabled +.It Cm >1 +Enables tracking, the maximum number of addresses tracked for each +association is limited to this value +.El +.Pp +This variable is fully dynamic, the new value will be adopted for all newly +arriving associations, existing association are treated as they were previously. +Global tracking will decrease the number of collisions within the +.Nm nat +at a cost +of increased processing load, memory usage, complexity, and possible +.Nm nat +state +problems in complex networks with multiple +.Nm nats . +We recommend not tracking +global IP addresses, this will still result in a fully functional +.Nm nat . +.It Va net.inet.ip.alias.sctp.up_timer: No 300 +Timeout value to keep an association up with no traffic. +This value cannot be 0. .It Va net.inet.ip.dummynet.expire : No 1 Lazily delete dynamic pipes/queue once they have no pending traffic. You can disable this by setting the variable to 0, in which case @@ -2718,6 +2874,15 @@ as part of a Summer of Code 2005 project. Work on .Nm dummynet traffic shaper supported by Akamba Corp. +.Pp +Sctp +.Nm nat +support has been developed by +.An The Centre for Advanced Internet Architectures (CAIA) Aq http://www.caia.swin.edu.au . +The primary developers and maintainers are David Hayes and Jason But. +For further information visit: +.Aq http://www.caia.swin.edu.au/urp/SONATA +. .Sh BUGS The syntax has grown over the years and sometimes it might be confusing. Unfortunately, backward compatibility prevents cleaning up mistakes diff --git a/sbin/ipfw/nat.c b/sbin/ipfw/nat.c index da8896c157c7..bfc325ab0ca8 100644 --- a/sbin/ipfw/nat.c +++ b/sbin/ipfw/nat.c @@ -257,7 +257,9 @@ StrToProto (const char* str) if (!strcmp (str, "udp")) return IPPROTO_UDP; - errx (EX_DATAERR, "unknown protocol %s. Expected tcp or udp", str); + if (!strcmp (str, "sctp")) + return IPPROTO_SCTP; + errx (EX_DATAERR, "unknown protocol %s. Expected sctp, tcp or udp", str); } static int @@ -433,13 +435,27 @@ setup_redir_port(char *spool_buf, int len, strncpy(tmp_spool_buf, *av, strlen(*av)+1); lsnat = 1; } else { - if (StrToAddrAndPortRange (*av, &r->laddr, protoName, - &portRange) != 0) - errx(EX_DATAERR, "redirect_port:" - "invalid local port range"); + /* + * The sctp nat does not allow the port numbers to be mapped to + * new port numbers. Therefore, no ports are to be specified + * in the target port field. + */ + if (r->proto == IPPROTO_SCTP) { + if (strchr (*av, ':')) + errx(EX_DATAERR, "redirect_port:" + "port numbers do not change in sctp, so do not " + "specify them as part of the target"); + else + StrToAddr(*av, &r->laddr); + } else { + if (StrToAddrAndPortRange (*av, &r->laddr, protoName, + &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid local port range"); - r->lport = GETLOPORT(portRange); - numLocalPorts = GETNUMPORTS(portRange); + r->lport = GETLOPORT(portRange); + numLocalPorts = GETNUMPORTS(portRange); + } } INC_ARGCV(); @@ -463,6 +479,10 @@ setup_redir_port(char *spool_buf, int len, } r->pport = GETLOPORT(portRange); + if (r->proto == IPPROTO_SCTP) { /* so the logic below still works */ + numLocalPorts = GETNUMPORTS(portRange); + r->lport = r->pport; + } r->pport_cnt = GETNUMPORTS(portRange); INC_ARGCV(); @@ -518,14 +538,31 @@ setup_redir_port(char *spool_buf, int len, goto nospace; len -= SOF_SPOOL; space += SOF_SPOOL; - if (StrToAddrAndPortRange(sep, &tmp->addr, protoName, - &portRange) != 0) - errx(EX_DATAERR, "redirect_port:" - "invalid local port range"); - if (GETNUMPORTS(portRange) != 1) - errx(EX_DATAERR, "redirect_port: local port" - "must be single in this context"); - tmp->port = GETLOPORT(portRange); + /* + * The sctp nat does not allow the port numbers to be mapped to new port numbers + * Therefore, no ports are to be specified in the target port field + */ + if (r->proto == IPPROTO_SCTP) { + if (strchr (sep, ':')) { + errx(EX_DATAERR, "redirect_port:" + "port numbers do not change in " + "sctp, so do not specify them as " + "part of the target"); + } else { + StrToAddr(sep, &tmp->addr); + tmp->port = r->pport; + } + } else { + if (StrToAddrAndPortRange(sep, &tmp->addr, + protoName, &portRange) != 0) + errx(EX_DATAERR, "redirect_port:" + "invalid local port range"); + if (GETNUMPORTS(portRange) != 1) + errx(EX_DATAERR, "redirect_port: " + "local port must be single in " + "this context"); + tmp->port = GETLOPORT(portRange); + } r->spool_cnt++; /* Point to the next possible cfg_spool. */ spool_buf = &spool_buf[SOF_SPOOL]; diff --git a/sys/modules/libalias/libalias/Makefile b/sys/modules/libalias/libalias/Makefile index a3b5347e926d..d1030c801334 100644 --- a/sys/modules/libalias/libalias/Makefile +++ b/sys/modules/libalias/libalias/Makefile @@ -3,6 +3,6 @@ .PATH: ${.CURDIR}/../../../netinet/libalias KMOD= libalias -SRCS= alias.c alias_db.c alias_proxy.c alias_util.c alias_mod.c +SRCS= alias.c alias_db.c alias_proxy.c alias_util.c alias_mod.c alias_sctp.c .include diff --git a/sys/netinet/ip_fw_nat.c b/sys/netinet/ip_fw_nat.c index 54c91d94109c..6ba0412e5378 100644 --- a/sys/netinet/ip_fw_nat.c +++ b/sys/netinet/ip_fw_nat.c @@ -326,6 +326,10 @@ ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m) else retval = LibAliasOut(t->lib, c, mcl->m_len + M_TRAILINGSPACE(mcl)); + if (retval == PKT_ALIAS_RESPOND) { + m->m_flags |= M_SKIP_FIREWALL; + retval = PKT_ALIAS_OK; + } if (retval != PKT_ALIAS_OK && retval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) { /* XXX - should i add some logging? */ diff --git a/sys/netinet/libalias/alias.c b/sys/netinet/libalias/alias.c index 7d38578c75d7..9d80da93e7db 100644 --- a/sys/netinet/libalias/alias.c +++ b/sys/netinet/libalias/alias.c @@ -115,6 +115,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #else #include #include @@ -143,6 +144,17 @@ __FBSDID("$FreeBSD$"); #include "alias_mod.h" #endif +/* + * Define libalias SYSCTL Node + */ +#ifdef SYSCTL_NODE + +SYSCTL_DECL(_net_inet); +SYSCTL_DECL(_net_inet_ip); +SYSCTL_NODE(_net_inet_ip, OID_AUTO, alias, CTLFLAG_RW, NULL, "Libalias sysctl API"); + +#endif + static __inline int twowords(void *p) { @@ -1335,6 +1347,11 @@ LibAliasInLocked(struct libalias *la, char *ptr, int maxpacketsize) case IPPROTO_TCP: iresult = TcpAliasIn(la, pip); break; +#ifdef _KERNEL + case IPPROTO_SCTP: + iresult = SctpAlias(la, pip, SN_TO_LOCAL); + break; +#endif case IPPROTO_GRE: { int error; struct alias_data ad = { @@ -1477,10 +1494,15 @@ LibAliasOutLocked(struct libalias *la, char *ptr, /* valid IP packet */ case IPPROTO_UDP: iresult = UdpAliasOut(la, pip, maxpacketsize, create); break; - case IPPROTO_TCP: + case IPPROTO_TCP: iresult = TcpAliasOut(la, pip, maxpacketsize, create); break; - case IPPROTO_GRE: { +#ifdef _KERNEL + case IPPROTO_SCTP: + iresult = SctpAlias(la, pip, SN_TO_GLOBAL); + break; +#endif + case IPPROTO_GRE: { int error; struct alias_data ad = { .lnk = NULL, diff --git a/sys/netinet/libalias/alias_db.c b/sys/netinet/libalias/alias_db.c index 93b226e57a81..489933f6385c 100644 --- a/sys/netinet/libalias/alias_db.c +++ b/sys/netinet/libalias/alias_db.c @@ -411,6 +411,8 @@ static void ShowAliasStats(struct libalias *); static int InitPacketAliasLog(struct libalias *); static void UninitPacketAliasLog(struct libalias *); +void SctpShowAliasStats(struct libalias *la); + static u_int StartPointIn(struct in_addr alias_addr, u_short alias_port, @@ -489,15 +491,17 @@ ShowAliasStats(struct libalias *la) /* Used for debugging */ if (la->logDesc) { int tot = la->icmpLinkCount + la->udpLinkCount + + (la->sctpLinkCount>>1) + /* sctp counts half associations */ la->tcpLinkCount + la->pptpLinkCount + la->protoLinkCount + la->fragmentIdLinkCount + la->fragmentPtrLinkCount; AliasLog(la->logDesc, - "icmp=%u, udp=%u, tcp=%u, pptp=%u, proto=%u, frag_id=%u frag_ptr=%u / tot=%u", + "icmp=%u, udp=%u, tcp=%u, sctp=%u, pptp=%u, proto=%u, frag_id=%u frag_ptr=%u / tot=%u", la->icmpLinkCount, la->udpLinkCount, la->tcpLinkCount, + la->sctpLinkCount>>1, /* sctp counts half associations */ la->pptpLinkCount, la->protoLinkCount, la->fragmentIdLinkCount, @@ -508,6 +512,13 @@ ShowAliasStats(struct libalias *la) } } +void SctpShowAliasStats(struct libalias *la) +{ + + ShowAliasStats(la); +} + + /* Internal routines for finding, deleting and adding links Port Allocation: @@ -1278,6 +1289,11 @@ _FindLinkIn(struct libalias *la, struct in_addr dst_addr, src_port = lnk->src_port; } + if (link_type == LINK_SCTP) { + lnk->src_addr = src_addr; + lnk->src_port = src_port; + return(lnk); + } lnk = ReLink(lnk, src_addr, dst_addr, alias_addr, src_port, dst_port, alias_port, @@ -2277,10 +2293,13 @@ LibAliasRedirectPort(struct libalias *la, struct in_addr src_addr, u_short src_p case IPPROTO_TCP: link_type = LINK_TCP; break; + case IPPROTO_SCTP: + link_type = LINK_SCTP; + break; default: #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAliasRedirectPort(): "); - fprintf(stderr, "only TCP and UDP protocols allowed\n"); + fprintf(stderr, "only SCTP, TCP and UDP protocols allowed\n"); #endif lnk = NULL; goto getout; @@ -2496,6 +2515,9 @@ LibAliasInit(struct libalias *la) LIST_INIT(&la->linkTableOut[i]); for (i = 0; i < LINK_TABLE_IN_SIZE; i++) LIST_INIT(&la->linkTableIn[i]); +#ifdef _KERNEL + AliasSctpInit(la); +#endif LIBALIAS_LOCK_INIT(la); LIBALIAS_LOCK(la); } else { @@ -2503,6 +2525,10 @@ LibAliasInit(struct libalias *la) la->deleteAllLinks = 1; CleanupAliasData(la); la->deleteAllLinks = 0; +#ifdef _KERNEL + AliasSctpTerm(la); + AliasSctpInit(la); +#endif } la->aliasAddress.s_addr = INADDR_ANY; @@ -2511,6 +2537,7 @@ LibAliasInit(struct libalias *la) la->icmpLinkCount = 0; la->udpLinkCount = 0; la->tcpLinkCount = 0; + la->sctpLinkCount = 0; la->pptpLinkCount = 0; la->protoLinkCount = 0; la->fragmentIdLinkCount = 0; @@ -2539,6 +2566,9 @@ LibAliasUninit(struct libalias *la) { LIBALIAS_LOCK(la); +#ifdef _KERNEL + AliasSctpTerm(la); +#endif la->deleteAllLinks = 1; CleanupAliasData(la); la->deleteAllLinks = 0; @@ -2879,3 +2909,30 @@ LibAliasSetSkinnyPort(struct libalias *la, unsigned int port) la->skinnyPort = port; LIBALIAS_UNLOCK(la); } + +/* + * Find the address to redirect incoming packets + */ +struct in_addr +FindSctpRedirectAddress(struct libalias *la, struct sctp_nat_msg *sm) +{ + struct alias_link *lnk; + struct in_addr redir; + + LIBALIAS_LOCK_ASSERT(la); + lnk = FindLinkIn(la, sm->ip_hdr->ip_src, sm->ip_hdr->ip_dst, + sm->sctp_hdr->dest_port,sm->sctp_hdr->dest_port, LINK_SCTP, 1); + if (lnk != NULL) { + return(lnk->src_addr); /* port redirect */ + } else { + redir = FindOriginalAddress(la,sm->ip_hdr->ip_dst); + if (redir.s_addr == la->aliasAddress.s_addr || + redir.s_addr == la->targetAddress.s_addr) { /* No address found */ + lnk = FindLinkIn(la, sm->ip_hdr->ip_src, sm->ip_hdr->ip_dst, + NO_DEST_PORT, 0, LINK_SCTP, 1); + if (lnk != NULL) + return(lnk->src_addr); /* redirect proto */ + } + return(redir); /* address redirect */ + } +} diff --git a/sys/netinet/libalias/alias_local.h b/sys/netinet/libalias/alias_local.h index fc7d649cec7b..e201394ce6e4 100644 --- a/sys/netinet/libalias/alias_local.h +++ b/sys/netinet/libalias/alias_local.h @@ -57,6 +57,10 @@ /* XXX: LibAliasSetTarget() uses this constant. */ #define INADDR_NONE 0xffffffff + +#include +#else +#include "alias_sctp.h" #endif /* Sizes of input and output link tables */ @@ -147,7 +151,29 @@ struct libalias { struct in_addr true_addr; /* in network byte order. */ u_short true_port; /* in host byte order. */ + + /* + * sctp code support + */ + + /* counts associations that have progressed to UP and not yet removed */ + int sctpLinkCount; #ifdef _KERNEL + /* timing queue for keeping track of association timeouts */ + struct sctp_nat_timer sctpNatTimer; + + /* size of hash table used in this instance */ + u_int sctpNatTableSize; + +/* + * local look up table sorted by l_vtag/l_port + */ + LIST_HEAD(sctpNatTableL, sctp_nat_assoc) *sctpTableLocal; +/* + * global look up table sorted by g_vtag/g_port + */ + LIST_HEAD(sctpNatTableG, sctp_nat_assoc) *sctpTableGlobal; + /* * avoid races in libalias: every public function has to use it. */ @@ -198,6 +224,14 @@ struct libalias { /* Prototypes */ +/* + * SctpFunction prototypes + * + */ +void AliasSctpInit(struct libalias *la); +void AliasSctpTerm(struct libalias *la); +int SctpAlias(struct libalias *la, struct ip *ip, int direction); + /* * We do not calculate TCP checksums when libalias is a kernel * module, since it has no idea about checksum offloading. @@ -264,6 +298,8 @@ struct in_addr FindOriginalAddress(struct libalias *la, struct in_addr _alias_addr); struct in_addr FindAliasAddress(struct libalias *la, struct in_addr _original_addr); +struct in_addr +FindSctpRedirectAddress(struct libalias *la, struct sctp_nat_msg *sm); /* External data access/modification */ int diff --git a/sys/netinet/libalias/alias_sctp.c b/sys/netinet/libalias/alias_sctp.c new file mode 100644 index 000000000000..89dc97980adc --- /dev/null +++ b/sys/netinet/libalias/alias_sctp.c @@ -0,0 +1,2665 @@ +/** + * @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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include "alias_sctp.h" +#include +#include "alias.h" +#include "alias_local.h" +#include +#include +#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; /** 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 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 = sctp_finalize_crc32(update_crc32(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 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 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 diff --git a/sys/netinet/libalias/alias_sctp.h b/sys/netinet/libalias/alias_sctp.h new file mode 100644 index 000000000000..7953f4391ec2 --- /dev/null +++ b/sys/netinet/libalias/alias_sctp.h @@ -0,0 +1,201 @@ +/** + * @file alias_sctp.h + * 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 + * with leadership and advice from 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. + * + */ + +/* $FreeBSD$ */ + +#ifndef _ALIAS_SCTP_H_ +#define _ALIAS_SCTP_H_ + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#endif // #ifdef _KERNEL +#include + +#include +#include +#include + +#include +#include +#include + +/** + * These are defined in sctp_os_bsd.h, but it can't be included due to its local file + * inclusion, so I'm defining them here. + * + */ +#include +#include +/* The packed define for 64 bit platforms */ +#ifndef SCTP_PACKED +#define SCTP_PACKED __attribute__((packed)) +#endif //#ifndef SCTP_PACKED +#ifndef SCTP_UNUSED +#define SCTP_UNUSED __attribute__((unused)) +#endif //#ifndef SCTP_UNUSED + + +#include +//#include --might be needed later for mbuf stuff +#include + +#ifndef _KERNEL +#include +#include +#include +#endif //#ifdef _KERNEL + + +#define LINK_SCTP IPPROTO_SCTP + + +#define SN_TO_LOCAL 0 /**< packet traveling from global to local */ +#define SN_TO_GLOBAL 1 /**< packet traveling from local to global */ +#define SN_TO_NODIR 99 /**< used where direction is not important */ + +#define SN_NAT_PKT 0x0000 /**< Network Address Translate packet */ +#define SN_DROP_PKT 0x0001 /**< drop packet (don't forward it) */ +#define SN_PROCESSING_ERROR 0x0003 /**< Packet processing error */ +#define SN_REPLY_ABORT 0x0010 /**< Reply with ABORT to sender (don't forward it) */ +#define SN_SEND_ABORT 0x0020 /**< Send ABORT to destination */ +#define SN_TX_ABORT 0x0030 /**< mask for transmitting abort */ +#define SN_REFLECT_ERROR 0x0100 /**< Reply with ERROR to sender on OOTB packet Tbit set */ +#define SN_REPLY_ERROR 0x0200 /**< Reply with ERROR to sender on ASCONF clash */ +#define SN_TX_ERROR 0x0300 /**< mask for transmitting error */ + + +#define PKT_ALIAS_RESPOND 0x1000 /**< Signal to libalias that there is a response packet to send */ +/* + * Data structures + */ + +/** + * @brief sctp association information + * + * Structure that contains information about a particular sctp association + * currently under Network Address Translation. + * Information is stored in network byte order (as is libalias)*** + */ +struct sctp_nat_assoc { + uint32_t l_vtag; /**< local side verification tag */ + uint16_t l_port; /**< local side port number */ + uint32_t g_vtag; /**< global side verification tag */ + uint16_t g_port; /**< global side port number */ + struct in_addr l_addr; /**< local ip address */ + struct in_addr a_addr; /**< alias ip address */ + int state; /**< current state of NAT association */ + int TableRegister; /**< stores which look up tables association is registered in */ + int exp; /**< timer expiration in seconds from uptime */ + int exp_loc; /**< current location in timer_Q */ + int num_Gaddr; /**< number of global IP addresses in the list */ + LIST_HEAD(sctpGlobalAddresshead,sctp_GlobalAddress) Gaddr; /**< List of global addresses */ + LIST_ENTRY (sctp_nat_assoc) list_L; /**< Linked list of pointers for Local table*/ + LIST_ENTRY (sctp_nat_assoc) list_G; /**< Linked list of pointers for Global table */ + LIST_ENTRY (sctp_nat_assoc) timer_Q; /**< Linked list of pointers for timer Q */ +//Using libalias locking +}; + +struct sctp_GlobalAddress { + struct in_addr g_addr; + LIST_ENTRY (sctp_GlobalAddress) list_Gaddr; /**< Linked list of pointers for Global table */ +}; + +/** + * @brief SCTP chunk of interest + * + * The only chunks whose contents are of any interest are the INIT and ASCONF_AddIP + */ +union sctpChunkOfInt { + struct sctp_init *Init; /**< Pointer to Init Chunk */ + struct sctp_init_ack *InitAck; /**< Pointer to Init Chunk */ + struct sctp_paramhdr *Asconf; /**< Pointer to ASCONF chunk */ +}; + + +/** + * @brief SCTP message + * + * Structure containing the relevant information from the SCTP message + */ +struct sctp_nat_msg { + uint16_t msg; /**< one of the key messages defined above */ +#ifdef INET6 + // struct ip6_hdr *ip_hdr; /**< pointer to ip packet header */ /*no inet6 support yet*/ +#else + struct ip *ip_hdr; /**< pointer to ip packet header */ +#endif //#ifdef INET6 + struct sctphdr *sctp_hdr; /**< pointer to sctp common header */ + union sctpChunkOfInt sctpchnk; /**< union of pointers to the chunk of interest */ + int chunk_length; /**< length of chunk of interest */ +}; + + +/** + * @brief sctp nat timer queue structure + * + */ + +struct sctp_nat_timer { + int loc_time; /**< time in seconds for the current location in the queue */ + int cur_loc; /**< index of the current location in the circular queue */ + LIST_HEAD(sctpTimerQ,sctp_nat_assoc) *TimerQ; /**< List of associations at this position in the timer Q */ +}; + + + +#endif //#ifndef _ALIAS_SCTP_H diff --git a/sys/netinet/sctp_crc32.c b/sys/netinet/sctp_crc32.c index b85ccdd91485..3fba39e588ee 100644 --- a/sys/netinet/sctp_crc32.c +++ b/sys/netinet/sctp_crc32.c @@ -599,7 +599,7 @@ sctp_crc32c_sb8_64_bit(uint32_t crc, * * none */ -static uint32_t +uint32_t update_crc32(uint32_t crc32c, unsigned char *buffer, unsigned int length) @@ -697,7 +697,7 @@ old_update_crc32(uint32_t crc32c, } -static uint32_t +uint32_t sctp_finalize_crc32(uint32_t crc32c) { uint32_t result; diff --git a/sys/netinet/sctp_crc32.h b/sys/netinet/sctp_crc32.h index 634acd493d5b..2c353d2de10e 100644 --- a/sys/netinet/sctp_crc32.h +++ b/sys/netinet/sctp_crc32.h @@ -39,6 +39,8 @@ __FBSDID("$FreeBSD$"); #if defined(_KERNEL) || defined(__Userspace__) uint32_t sctp_calculate_cksum(struct mbuf *, uint32_t); void sctp_delayed_cksum(struct mbuf *); +uint32_t update_crc32(uint32_t, unsigned char *, unsigned int); +uint32_t sctp_finalize_crc32(uint32_t); #endif /* _KERNEL */