diff --git a/sys/netinet/libalias/alias_db.c b/sys/netinet/libalias/alias_db.c index fca2f33906d3..8f6db015fe86 100644 --- a/sys/netinet/libalias/alias_db.c +++ b/sys/netinet/libalias/alias_db.c @@ -1,2820 +1,2838 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2001 Charles Mott * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* Alias_db.c encapsulates all data structures used for storing packet aliasing data. Other parts of the aliasing software access data through functions provided in this file. Data storage is based on the notion of a "link", which is established for ICMP echo/reply packets, UDP datagrams and TCP stream connections. A link stores the original source and destination addresses. For UDP and TCP, it also stores source and destination port numbers, as well as an alias port number. Links are also used to store information about fragments. There is a facility for sweeping through and deleting old links as new packets are sent through. A simple timeout is used for ICMP and UDP links. TCP links are left alone unless there is an incomplete connection, in which case the link can be deleted after a certain amount of time. Initial version: August, 1996 (cjm) Version 1.4: September 16, 1996 (cjm) Facility for handling incoming links added. Version 1.6: September 18, 1996 (cjm) ICMP data handling simplified. Version 1.7: January 9, 1997 (cjm) Fragment handling simplified. Saves pointers for unresolved fragments. Permits links for unspecified remote ports or unspecified remote addresses. Fixed bug which did not properly zero port table entries after a link was deleted. Cleaned up some obsolete comments. Version 1.8: January 14, 1997 (cjm) Fixed data type error in StartPoint(). (This error did not exist prior to v1.7 and was discovered and fixed by Ari Suutari) Version 1.9: February 1, 1997 Optionally, connections initiated from packet aliasing host machine will will not have their port number aliased unless it conflicts with an aliasing port already being used. (cjm) All options earlier being #ifdef'ed are now available through a new interface, SetPacketAliasMode(). This allows run time control (which is now available in PPP+pktAlias through the 'alias' keyword). (ee) Added ability to create an alias port without either destination address or port specified. port type = ALIAS_PORT_UNKNOWN_DEST_ALL (ee) Removed K&R style function headers and general cleanup. (ee) Added packetAliasMode to replace compiler #defines's (ee) Allocates sockets for partially specified ports if ALIAS_USE_SOCKETS defined. (cjm) Version 2.0: March, 1997 SetAliasAddress() will now clean up alias links if the aliasing address is changed. (cjm) PacketAliasPermanentLink() function added to support permanent links. (J. Fortes suggested the need for this.) Examples: (192.168.0.1, port 23) <-> alias port 6002, unknown dest addr/port (192.168.0.2, port 21) <-> alias port 3604, known dest addr unknown dest port These permanent links allow for incoming connections to machines on the local network. They can be given with a user-chosen amount of specificity, with increasing specificity meaning more security. (cjm) Quite a bit of rework to the basic engine. The portTable[] array, which kept track of which ports were in use was replaced by a table/linked list structure. (cjm) SetExpire() function added. (cjm) DeleteLink() no longer frees memory association with a pointer to a fragment (this bug was first recognized by E. Eklund in v1.9). Version 2.1: May, 1997 (cjm) Packet aliasing engine reworked so that it can handle multiple external addresses rather than just a single host address. PacketAliasRedirectPort() and PacketAliasRedirectAddr() added to the API. The first function is a more generalized version of PacketAliasPermanentLink(). The second function implements static network address translation. Version 3.2: July, 2000 (salander and satoh) Added FindNewPortGroup to get contiguous range of port values. Added QueryUdpTcpIn and QueryUdpTcpOut to look for an aliasing link but not actually add one. Added FindRtspOut, which is closely derived from FindUdpTcpOut, except that the alias port (from FindNewPortGroup) is provided as input. See HISTORY file for additional revisions. */ #ifdef _KERNEL #include #include #include #include #include #include #include #include #else #include #include #include #include #include #include #endif #include #include #ifdef _KERNEL #include #include #include #include #else #include "alias.h" #include "alias_local.h" #include "alias_mod.h" #endif static LIST_HEAD(, libalias) instancehead = LIST_HEAD_INITIALIZER(instancehead); +int LibAliasTime; /* Constants (note: constants are also defined near relevant functions or structs) */ /* Timeouts (in seconds) for different link types */ #define ICMP_EXPIRE_TIME 60 #define UDP_EXPIRE_TIME 60 #define PROTO_EXPIRE_TIME 60 #define FRAGMENT_ID_EXPIRE_TIME 10 #define FRAGMENT_PTR_EXPIRE_TIME 30 /* TCP link expire time for different cases */ /* When the link has been used and closed - minimal grace time to allow ACKs and potential re-connect in FTP (XXX - is this allowed?) */ #ifndef TCP_EXPIRE_DEAD #define TCP_EXPIRE_DEAD 10 #endif /* When the link has been used and closed on one side - the other side is allowed to still send data */ #ifndef TCP_EXPIRE_SINGLEDEAD #define TCP_EXPIRE_SINGLEDEAD 90 #endif /* When the link isn't yet up */ #ifndef TCP_EXPIRE_INITIAL #define TCP_EXPIRE_INITIAL 300 #endif /* When the link is up */ #ifndef TCP_EXPIRE_CONNECTED #define TCP_EXPIRE_CONNECTED 86400 #endif /* Dummy port number codes used for FindLinkIn/Out() and AddLink(). These constants can be anything except zero, which indicates an unknown port number. */ #define NO_DEST_PORT 1 #define NO_SRC_PORT 1 /* Matches any/unknown address in FindLinkIn/Out() and AddLink(). */ static struct in_addr const ANY_ADDR = { INADDR_ANY }; /* Data Structures The fundamental data structure used in this program is "struct alias_link". Whenever a TCP connection is made, a UDP datagram is sent out, or an ICMP echo request is made, a link record is made (if it has not already been created). The link record is identified by the source address/port and the destination address/port. In the case of an ICMP echo request, the source port is treated as being equivalent with the 16-bit ID number of the ICMP packet. The link record also can store some auxiliary data. For TCP connections that have had sequence and acknowledgment modifications, data space is available to track these changes. A state field is used to keep track in changes to the TCP connection state. ID numbers of fragments can also be stored in the auxiliary space. Pointers to unresolved fragments can also be stored. The link records support two independent chainings. Lookup tables for input and out tables hold the initial pointers the link chains. On input, the lookup table indexes on alias port and link type. On output, the lookup table indexes on source address, destination address, source port, destination port and link type. */ /* used to save changes to ACK/sequence numbers */ struct ack_data_record { u_long ack_old; u_long ack_new; int delta; int active; }; /* Information about TCP connection */ struct tcp_state { int in; /* State for outside -> inside */ int out; /* State for inside -> outside */ int index; /* Index to ACK data array */ /* Indicates whether ACK and sequence numbers been modified */ int ack_modified; }; /* Number of distinct ACK number changes * saved for a modified TCP stream */ #define N_LINK_TCP_DATA 3 struct tcp_dat { struct tcp_state state; struct ack_data_record ack[N_LINK_TCP_DATA]; /* Which firewall record is used for this hole? */ int fwhole; }; /* LSNAT server pool (circular list) */ struct server { struct in_addr addr; u_short port; struct server *next; }; /* Main data structure */ struct alias_link { struct libalias *la; /* Address and port information */ struct in_addr src_addr; struct in_addr dst_addr; struct in_addr alias_addr; struct in_addr proxy_addr; u_short src_port; u_short dst_port; u_short alias_port; u_short proxy_port; struct server *server; /* Type of link: TCP, UDP, ICMP, proto, frag */ int link_type; /* values for link_type */ #define LINK_ICMP IPPROTO_ICMP #define LINK_UDP IPPROTO_UDP #define LINK_TCP IPPROTO_TCP #define LINK_FRAGMENT_ID (IPPROTO_MAX + 1) #define LINK_FRAGMENT_PTR (IPPROTO_MAX + 2) #define LINK_ADDR (IPPROTO_MAX + 3) #define LINK_PPTP (IPPROTO_MAX + 4) int flags; /* indicates special characteristics */ int pflags; /* protocol-specific flags */ /* flag bits */ #define LINK_UNKNOWN_DEST_PORT 0x01 #define LINK_UNKNOWN_DEST_ADDR 0x02 #define LINK_PERMANENT 0x04 #define LINK_PARTIALLY_SPECIFIED 0x03 /* logical-or of first two bits */ #define LINK_UNFIREWALLED 0x08 int timestamp; /* Time link was last accessed */ int expire_time; /* Expire time for link */ #ifndef NO_USE_SOCKETS int sockfd; /* socket descriptor */ #endif /* Linked list of pointers for input and output lookup tables */ LIST_ENTRY (alias_link) list_out; LIST_ENTRY (alias_link) list_in; TAILQ_ENTRY (alias_link) list_expire; /* Auxiliary data */ union { char *frag_ptr; struct in_addr frag_addr; struct tcp_dat *tcp; } data; }; /* Clean up procedure. */ static void finishoff(void); /* Kernel module definition. */ #ifdef _KERNEL MALLOC_DEFINE(M_ALIAS, "libalias", "packet aliasing"); MODULE_VERSION(libalias, 1); static int alias_mod_handler(module_t mod, int type, void *data) { switch (type) { case MOD_QUIESCE: case MOD_UNLOAD: finishoff(); case MOD_LOAD: return (0); default: return (EINVAL); } } static moduledata_t alias_mod = { "alias", alias_mod_handler, NULL }; DECLARE_MODULE(alias, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND); #endif /* Internal utility routines (used only in alias_db.c) Lookup table starting points: StartPointIn() -- link table initial search point for incoming packets StartPointOut() -- link table initial search point for outgoing packets Miscellaneous: SeqDiff() -- difference between two TCP sequences ShowAliasStats() -- send alias statistics to a monitor file */ /* Local prototypes */ static u_int StartPointIn(struct in_addr, u_short, int); static u_int StartPointOut(struct in_addr, struct in_addr, u_short, u_short, int); static int SeqDiff(u_long, u_long); #ifndef NO_FW_PUNCH /* Firewall control */ static void InitPunchFW(struct libalias *); static void UninitPunchFW(struct libalias *); static void ClearFWHole(struct alias_link *); #endif /* Log file control */ 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, int link_type) { u_int n; n = alias_addr.s_addr; if (link_type != LINK_PPTP) n += alias_port; n += link_type; return (n % LINK_TABLE_IN_SIZE); } static u_int StartPointOut(struct in_addr src_addr, struct in_addr dst_addr, u_short src_port, u_short dst_port, int link_type) { u_int n; n = src_addr.s_addr; n += dst_addr.s_addr; if (link_type != LINK_PPTP) { n += src_port; n += dst_port; } n += link_type; return (n % LINK_TABLE_OUT_SIZE); } static int SeqDiff(u_long x, u_long y) { /* Return the difference between two TCP sequence numbers * This function is encapsulated in case there are any unusual * arithmetic conditions that need to be considered. */ return (ntohl(y) - ntohl(x)); } #ifdef _KERNEL static void AliasLog(char *str, const char *format, ...) { va_list ap; va_start(ap, format); vsnprintf(str, LIBALIAS_BUF_SIZE, format, ap); va_end(ap); } #else static void AliasLog(FILE *stream, const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stream, format, ap); va_end(ap); fflush(stream); } #endif static void ShowAliasStats(struct libalias *la) { LIBALIAS_LOCK_ASSERT(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, 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, la->fragmentPtrLinkCount, tot); #ifndef _KERNEL AliasLog(la->logDesc, " (sock=%u)\n", la->sockCount); #endif } } void SctpShowAliasStats(struct libalias *la) { ShowAliasStats(la); } /* Internal routines for finding, deleting and adding links Port Allocation: GetNewPort() -- find and reserve new alias port number GetSocket() -- try to allocate a socket for a given port Link creation and deletion: CleanupAliasData() - remove all link chains from lookup table CleanupLink() - look for a stale link DeleteLink() - remove link AddLink() - add link ReLink() - change link Link search: FindLinkOut() - find link for outgoing packets FindLinkIn() - find link for incoming packets Port search: FindNewPortGroup() - find an available group of ports */ /* Local prototypes */ static int GetNewPort(struct libalias *, struct alias_link *, int); #ifndef NO_USE_SOCKETS static u_short GetSocket(struct libalias *, u_short, int *, int); #endif static void CleanupAliasData(struct libalias *); static void CleanupLink(struct libalias *, struct alias_link **); static void DeleteLink(struct alias_link **); static struct alias_link * ReLink(struct alias_link *, struct in_addr, struct in_addr, struct in_addr, u_short, u_short, int, int); static struct alias_link * FindLinkOut(struct libalias *, struct in_addr, struct in_addr, u_short, u_short, int, int); static struct alias_link * FindLinkIn(struct libalias *, struct in_addr, struct in_addr, u_short, u_short, int, int); #define ALIAS_PORT_BASE 0x08000 #define ALIAS_PORT_MASK 0x07fff #define ALIAS_PORT_MASK_EVEN 0x07ffe #define GET_NEW_PORT_MAX_ATTEMPTS 20 #define FIND_EVEN_ALIAS_BASE 1 /* GetNewPort() allocates port numbers. Note that if a port number is already in use, that does not mean that it cannot be used by another link concurrently. This is because GetNewPort() looks for unused triplets: (dest addr, dest port, alias port). */ static int GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param) { int i; int max_trials; u_short port_sys; u_short port_net; LIBALIAS_LOCK_ASSERT(la); /* * Description of alias_port_param for GetNewPort(). When * this parameter is zero or positive, it precisely specifies * the port number. GetNewPort() will return this number * without check that it is in use. * When this parameter is GET_ALIAS_PORT, it indicates to get * a randomly selected port number. */ if (alias_port_param == GET_ALIAS_PORT) { /* * The aliasing port is automatically selected by one of * two methods below: */ max_trials = GET_NEW_PORT_MAX_ATTEMPTS; if (la->packetAliasMode & PKT_ALIAS_SAME_PORTS) { /* * When the PKT_ALIAS_SAME_PORTS option is chosen, * the first try will be the actual source port. If * this is already in use, the remainder of the * trials will be random. */ port_net = lnk->src_port; port_sys = ntohs(port_net); } else if (la->aliasPortLower) { /* First trial is a random port in the aliasing range. */ port_sys = la->aliasPortLower + (arc4random() % la->aliasPortLength); port_net = htons(port_sys); } else { /* First trial and all subsequent are random. */ port_sys = arc4random() & ALIAS_PORT_MASK; port_sys += ALIAS_PORT_BASE; port_net = htons(port_sys); } } else if (alias_port_param >= 0 && alias_port_param < 0x10000) { lnk->alias_port = (u_short) alias_port_param; return (0); } else { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/GetNewPort(): "); fprintf(stderr, "input parameter error\n"); #endif return (-1); } /* Port number search */ for (i = 0; i < max_trials; i++) { int go_ahead; struct alias_link *search_result; search_result = FindLinkIn(la, lnk->dst_addr, lnk->alias_addr, lnk->dst_port, port_net, lnk->link_type, 0); if (search_result == NULL) go_ahead = 1; else if (!(lnk->flags & LINK_PARTIALLY_SPECIFIED) && (search_result->flags & LINK_PARTIALLY_SPECIFIED)) go_ahead = 1; else go_ahead = 0; if (go_ahead) { #ifndef NO_USE_SOCKETS if ((la->packetAliasMode & PKT_ALIAS_USE_SOCKETS) && (lnk->flags & LINK_PARTIALLY_SPECIFIED) && ((lnk->link_type == LINK_TCP) || (lnk->link_type == LINK_UDP))) { if (GetSocket(la, port_net, &lnk->sockfd, lnk->link_type)) { lnk->alias_port = port_net; return (0); } } else { #endif lnk->alias_port = port_net; return (0); #ifndef NO_USE_SOCKETS } #endif } if (la->aliasPortLower) { port_sys = la->aliasPortLower + (arc4random() % la->aliasPortLength); port_net = htons(port_sys); } else { port_sys = arc4random() & ALIAS_PORT_MASK; port_sys += ALIAS_PORT_BASE; port_net = htons(port_sys); } } #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/GetNewPort(): "); fprintf(stderr, "could not find free port\n"); #endif return (-1); } #ifndef NO_USE_SOCKETS static u_short GetSocket(struct libalias *la, u_short port_net, int *sockfd, int link_type) { int err; int sock; struct sockaddr_in sock_addr; LIBALIAS_LOCK_ASSERT(la); if (link_type == LINK_TCP) sock = socket(AF_INET, SOCK_STREAM, 0); else if (link_type == LINK_UDP) sock = socket(AF_INET, SOCK_DGRAM, 0); else { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/GetSocket(): "); fprintf(stderr, "incorrect link type\n"); #endif return (0); } if (sock < 0) { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/GetSocket(): "); fprintf(stderr, "socket() error %d\n", *sockfd); #endif return (0); } sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); sock_addr.sin_port = port_net; err = bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); if (err == 0) { la->sockCount++; *sockfd = sock; return (1); } else { close(sock); return (0); } } #endif /* FindNewPortGroup() returns a base port number for an available range of contiguous port numbers. Note that if a port number is already in use, that does not mean that it cannot be used by another link concurrently. This is because FindNewPortGroup() looks for unused triplets: (dest addr, dest port, alias port). */ int FindNewPortGroup(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short src_port, u_short dst_port, u_short port_count, u_char proto, u_char align) { int i, j; int max_trials; u_short port_sys; int link_type; LIBALIAS_LOCK_ASSERT(la); /* * Get link_type from protocol */ switch (proto) { case IPPROTO_UDP: link_type = LINK_UDP; break; case IPPROTO_TCP: link_type = LINK_TCP; break; default: return (0); break; } /* * The aliasing port is automatically selected by one of two * methods below: */ max_trials = GET_NEW_PORT_MAX_ATTEMPTS; if (la->packetAliasMode & PKT_ALIAS_SAME_PORTS) { /* * When the ALIAS_SAME_PORTS option is chosen, the first * try will be the actual source port. If this is already * in use, the remainder of the trials will be random. */ port_sys = ntohs(src_port); } else { /* First trial and all subsequent are random. */ if (align == FIND_EVEN_ALIAS_BASE) port_sys = arc4random() & ALIAS_PORT_MASK_EVEN; else port_sys = arc4random() & ALIAS_PORT_MASK; port_sys += ALIAS_PORT_BASE; } /* Port number search */ for (i = 0; i < max_trials; i++) { struct alias_link *search_result; for (j = 0; j < port_count; j++) if ((search_result = FindLinkIn(la, dst_addr, alias_addr, dst_port, htons(port_sys + j), link_type, 0)) != NULL) break; /* Found a good range, return base */ if (j == port_count) return (htons(port_sys)); /* Find a new base to try */ if (align == FIND_EVEN_ALIAS_BASE) port_sys = arc4random() & ALIAS_PORT_MASK_EVEN; else port_sys = arc4random() & ALIAS_PORT_MASK; port_sys += ALIAS_PORT_BASE; } #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/FindNewPortGroup(): "); fprintf(stderr, "could not find free port(s)\n"); #endif return (0); } static void CleanupAliasData(struct libalias *la) { struct alias_link *lnk, *lnk_tmp; LIBALIAS_LOCK_ASSERT(la); /* permanent entries may stay */ TAILQ_FOREACH_SAFE(lnk, &la->checkExpire, list_expire, lnk_tmp) DeleteLink(&lnk); } static void CleanupLink(struct libalias *la, struct alias_link **lnk) { LIBALIAS_LOCK_ASSERT(la); if (lnk == NULL || *lnk == NULL) return; - if (la->timeStamp - (*lnk)->timestamp > (*lnk)->expire_time) { + if (LibAliasTime - (*lnk)->timestamp > (*lnk)->expire_time) { DeleteLink(lnk); if ((*lnk) == NULL) return; } /* move to end, swap may fail on a single entry list */ TAILQ_REMOVE(&la->checkExpire, (*lnk), list_expire); TAILQ_INSERT_TAIL(&la->checkExpire, (*lnk), list_expire); } static void DeleteLink(struct alias_link **plnk) { struct alias_link *lnk = *plnk; struct libalias *la = lnk->la; LIBALIAS_LOCK_ASSERT(la); /* Don't do anything if the link is marked permanent */ if (la->deleteAllLinks == 0 && lnk->flags & LINK_PERMANENT) return; #ifndef NO_FW_PUNCH /* Delete associated firewall hole, if any */ ClearFWHole(lnk); #endif /* Free memory allocated for LSNAT server pool */ if (lnk->server != NULL) { struct server *head, *curr, *next; head = curr = lnk->server; do { next = curr->next; free(curr); } while ((curr = next) != head); } /* Adjust output table pointers */ LIST_REMOVE(lnk, list_out); /* Adjust input table pointers */ LIST_REMOVE(lnk, list_in); /* remove from housekeeping */ TAILQ_REMOVE(&la->checkExpire, lnk, list_expire); #ifndef NO_USE_SOCKETS /* Close socket, if one has been allocated */ if (lnk->sockfd != -1) { la->sockCount--; close(lnk->sockfd); } #endif /* Link-type dependent cleanup */ switch (lnk->link_type) { case LINK_ICMP: la->icmpLinkCount--; break; case LINK_UDP: la->udpLinkCount--; break; case LINK_TCP: la->tcpLinkCount--; free(lnk->data.tcp); break; case LINK_PPTP: la->pptpLinkCount--; break; case LINK_FRAGMENT_ID: la->fragmentIdLinkCount--; break; case LINK_FRAGMENT_PTR: la->fragmentPtrLinkCount--; if (lnk->data.frag_ptr != NULL) free(lnk->data.frag_ptr); break; case LINK_ADDR: break; default: la->protoLinkCount--; break; } /* Free memory */ free(lnk); *plnk = NULL; /* Write statistics, if logging enabled */ if (la->packetAliasMode & PKT_ALIAS_LOG) { ShowAliasStats(la); } } struct alias_link * AddLink(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, struct in_addr alias_addr, u_short src_port, u_short dst_port, int alias_port_param, int link_type) { u_int start_point; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = malloc(sizeof(struct alias_link)); if (lnk != NULL) { /* Basic initialization */ lnk->la = la; lnk->src_addr = src_addr; lnk->dst_addr = dst_addr; lnk->alias_addr = alias_addr; lnk->proxy_addr.s_addr = INADDR_ANY; lnk->src_port = src_port; lnk->dst_port = dst_port; lnk->proxy_port = 0; lnk->server = NULL; lnk->link_type = link_type; #ifndef NO_USE_SOCKETS lnk->sockfd = -1; #endif lnk->flags = 0; lnk->pflags = 0; - lnk->timestamp = la->timeStamp; + lnk->timestamp = LibAliasTime; /* Expiration time */ switch (link_type) { case LINK_ICMP: lnk->expire_time = ICMP_EXPIRE_TIME; break; case LINK_UDP: lnk->expire_time = UDP_EXPIRE_TIME; break; case LINK_TCP: lnk->expire_time = TCP_EXPIRE_INITIAL; break; case LINK_PPTP: lnk->flags |= LINK_PERMANENT; /* no timeout. */ break; case LINK_FRAGMENT_ID: lnk->expire_time = FRAGMENT_ID_EXPIRE_TIME; break; case LINK_FRAGMENT_PTR: lnk->expire_time = FRAGMENT_PTR_EXPIRE_TIME; break; case LINK_ADDR: break; default: lnk->expire_time = PROTO_EXPIRE_TIME; break; } /* Determine alias flags */ if (dst_addr.s_addr == INADDR_ANY) lnk->flags |= LINK_UNKNOWN_DEST_ADDR; if (dst_port == 0) lnk->flags |= LINK_UNKNOWN_DEST_PORT; /* Determine alias port */ if (GetNewPort(la, lnk, alias_port_param) != 0) { free(lnk); return (NULL); } /* Link-type dependent initialization */ switch (link_type) { struct tcp_dat *aux_tcp; case LINK_ICMP: la->icmpLinkCount++; break; case LINK_UDP: la->udpLinkCount++; break; case LINK_TCP: aux_tcp = malloc(sizeof(struct tcp_dat)); if (aux_tcp != NULL) { int i; la->tcpLinkCount++; aux_tcp->state.in = ALIAS_TCP_STATE_NOT_CONNECTED; aux_tcp->state.out = ALIAS_TCP_STATE_NOT_CONNECTED; aux_tcp->state.index = 0; aux_tcp->state.ack_modified = 0; for (i = 0; i < N_LINK_TCP_DATA; i++) aux_tcp->ack[i].active = 0; aux_tcp->fwhole = -1; lnk->data.tcp = aux_tcp; } else { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/AddLink: "); fprintf(stderr, " cannot allocate auxiliary TCP data\n"); #endif free(lnk); return (NULL); } break; case LINK_PPTP: la->pptpLinkCount++; break; case LINK_FRAGMENT_ID: la->fragmentIdLinkCount++; break; case LINK_FRAGMENT_PTR: la->fragmentPtrLinkCount++; break; case LINK_ADDR: break; default: la->protoLinkCount++; break; } /* Set up pointers for output lookup table */ start_point = StartPointOut(src_addr, dst_addr, src_port, dst_port, link_type); LIST_INSERT_HEAD(&la->linkTableOut[start_point], lnk, list_out); /* Set up pointers for input lookup table */ start_point = StartPointIn(alias_addr, lnk->alias_port, link_type); LIST_INSERT_HEAD(&la->linkTableIn[start_point], lnk, list_in); /* Include the element into the housekeeping list */ TAILQ_INSERT_TAIL(&la->checkExpire, lnk, list_expire); } else { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/AddLink(): "); fprintf(stderr, "malloc() call failed.\n"); #endif } if (la->packetAliasMode & PKT_ALIAS_LOG) { ShowAliasStats(la); } return (lnk); } /* * If alias_port_param is less than zero, alias port will be automatically * chosen. If greater than zero, equal to alias port */ static struct alias_link * ReLink(struct alias_link *old_lnk, struct in_addr src_addr, struct in_addr dst_addr, struct in_addr alias_addr, u_short src_port, u_short dst_port, int alias_port_param, int link_type) { struct alias_link *new_lnk; struct libalias *la = old_lnk->la; LIBALIAS_LOCK_ASSERT(la); new_lnk = AddLink(la, src_addr, dst_addr, alias_addr, src_port, dst_port, alias_port_param, link_type); #ifndef NO_FW_PUNCH if (new_lnk != NULL && old_lnk->link_type == LINK_TCP && old_lnk->data.tcp->fwhole > 0) { PunchFWHole(new_lnk); } #endif DeleteLink(&old_lnk); return (new_lnk); } static struct alias_link * _FindLinkOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_short src_port, u_short dst_port, int link_type, int replace_partial_links) { u_int i; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); i = StartPointOut(src_addr, dst_addr, src_port, dst_port, link_type); LIST_FOREACH(lnk, &la->linkTableOut[i], list_out) { if (lnk->dst_addr.s_addr == dst_addr.s_addr && lnk->src_addr.s_addr == src_addr.s_addr && lnk->src_port == src_port && lnk->dst_port == dst_port && lnk->link_type == link_type && lnk->server == NULL) break; } CleanupLink(la, &lnk); if (lnk != NULL) - lnk->timestamp = la->timeStamp; + lnk->timestamp = LibAliasTime; /* Search for partially specified links. */ if (lnk == NULL && replace_partial_links) { if (dst_port != 0 && dst_addr.s_addr != INADDR_ANY) { lnk = _FindLinkOut(la, src_addr, dst_addr, src_port, 0, link_type, 0); if (lnk == NULL) lnk = _FindLinkOut(la, src_addr, ANY_ADDR, src_port, dst_port, link_type, 0); } if (lnk == NULL && (dst_port != 0 || dst_addr.s_addr != INADDR_ANY)) { lnk = _FindLinkOut(la, src_addr, ANY_ADDR, src_port, 0, link_type, 0); } if (lnk != NULL) { lnk = ReLink(lnk, src_addr, dst_addr, lnk->alias_addr, src_port, dst_port, lnk->alias_port, link_type); } } return (lnk); } static struct alias_link * FindLinkOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_short src_port, u_short dst_port, int link_type, int replace_partial_links) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = _FindLinkOut(la, src_addr, dst_addr, src_port, dst_port, link_type, replace_partial_links); if (lnk == NULL) { /* * The following allows permanent links to be specified as * using the default source address (i.e. device interface * address) without knowing in advance what that address * is. */ if (la->aliasAddress.s_addr != INADDR_ANY && src_addr.s_addr == la->aliasAddress.s_addr) { lnk = _FindLinkOut(la, ANY_ADDR, dst_addr, src_port, dst_port, link_type, replace_partial_links); } } return (lnk); } static struct alias_link * _FindLinkIn(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short dst_port, u_short alias_port, int link_type, int replace_partial_links) { int flags_in; u_int start_point; struct alias_link *lnk; struct alias_link *lnk_fully_specified; struct alias_link *lnk_unknown_all; struct alias_link *lnk_unknown_dst_addr; struct alias_link *lnk_unknown_dst_port; LIBALIAS_LOCK_ASSERT(la); /* Initialize pointers */ lnk_fully_specified = NULL; lnk_unknown_all = NULL; lnk_unknown_dst_addr = NULL; lnk_unknown_dst_port = NULL; /* If either the dest addr or port is unknown, the search * loop will have to know about this. */ flags_in = 0; if (dst_addr.s_addr == INADDR_ANY) flags_in |= LINK_UNKNOWN_DEST_ADDR; if (dst_port == 0) flags_in |= LINK_UNKNOWN_DEST_PORT; /* Search loop */ start_point = StartPointIn(alias_addr, alias_port, link_type); LIST_FOREACH(lnk, &la->linkTableIn[start_point], list_in) { int flags; flags = flags_in | lnk->flags; if (!(flags & LINK_PARTIALLY_SPECIFIED)) { if (lnk->alias_addr.s_addr == alias_addr.s_addr && lnk->alias_port == alias_port && lnk->dst_addr.s_addr == dst_addr.s_addr && lnk->dst_port == dst_port && lnk->link_type == link_type) { lnk_fully_specified = lnk; break; } } else if ((flags & LINK_UNKNOWN_DEST_ADDR) && (flags & LINK_UNKNOWN_DEST_PORT)) { if (lnk->alias_addr.s_addr == alias_addr.s_addr && lnk->alias_port == alias_port && lnk->link_type == link_type) { if (lnk_unknown_all == NULL) lnk_unknown_all = lnk; } } else if (flags & LINK_UNKNOWN_DEST_ADDR) { if (lnk->alias_addr.s_addr == alias_addr.s_addr && lnk->alias_port == alias_port && lnk->link_type == link_type && lnk->dst_port == dst_port) { if (lnk_unknown_dst_addr == NULL) lnk_unknown_dst_addr = lnk; } } else if (flags & LINK_UNKNOWN_DEST_PORT) { if (lnk->alias_addr.s_addr == alias_addr.s_addr && lnk->alias_port == alias_port && lnk->link_type == link_type && lnk->dst_addr.s_addr == dst_addr.s_addr) { if (lnk_unknown_dst_port == NULL) lnk_unknown_dst_port = lnk; } } } CleanupLink(la, &lnk_fully_specified); if (lnk_fully_specified != NULL) { - lnk_fully_specified->timestamp = la->timeStamp; + lnk_fully_specified->timestamp = LibAliasTime; lnk = lnk_fully_specified; } else if (lnk_unknown_dst_port != NULL) lnk = lnk_unknown_dst_port; else if (lnk_unknown_dst_addr != NULL) lnk = lnk_unknown_dst_addr; else if (lnk_unknown_all != NULL) lnk = lnk_unknown_all; else return (NULL); if (replace_partial_links && (lnk->flags & LINK_PARTIALLY_SPECIFIED || lnk->server != NULL)) { struct in_addr src_addr; u_short src_port; if (lnk->server != NULL) { /* LSNAT link */ src_addr = lnk->server->addr; src_port = lnk->server->port; lnk->server = lnk->server->next; } else { src_addr = lnk->src_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, link_type); } return (lnk); } static struct alias_link * FindLinkIn(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short dst_port, u_short alias_port, int link_type, int replace_partial_links) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = _FindLinkIn(la, dst_addr, alias_addr, dst_port, alias_port, link_type, replace_partial_links); if (lnk == NULL) { /* * The following allows permanent links to be specified as * using the default aliasing address (i.e. device * interface address) without knowing in advance what that * address is. */ if (la->aliasAddress.s_addr != INADDR_ANY && alias_addr.s_addr == la->aliasAddress.s_addr) { lnk = _FindLinkIn(la, dst_addr, ANY_ADDR, dst_port, alias_port, link_type, replace_partial_links); } } return (lnk); } /* External routines for finding/adding links -- "external" means outside alias_db.c, but within alias*.c -- FindIcmpIn(), FindIcmpOut() FindFragmentIn1(), FindFragmentIn2() AddFragmentPtrLink(), FindFragmentPtr() FindProtoIn(), FindProtoOut() FindUdpTcpIn(), FindUdpTcpOut() AddPptp(), FindPptpOutByCallId(), FindPptpInByCallId(), FindPptpOutByPeerCallId(), FindPptpInByPeerCallId() FindOriginalAddress(), FindAliasAddress() (prototypes in alias_local.h) */ struct alias_link * FindIcmpIn(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short id_alias, int create) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkIn(la, dst_addr, alias_addr, NO_DEST_PORT, id_alias, LINK_ICMP, 0); if (lnk == NULL && create && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) { struct in_addr target_addr; target_addr = FindOriginalAddress(la, alias_addr); lnk = AddLink(la, target_addr, dst_addr, alias_addr, id_alias, NO_DEST_PORT, id_alias, LINK_ICMP); } return (lnk); } struct alias_link * FindIcmpOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_short id, int create) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkOut(la, src_addr, dst_addr, id, NO_DEST_PORT, LINK_ICMP, 0); if (lnk == NULL && create) { struct in_addr alias_addr; alias_addr = FindAliasAddress(la, src_addr); lnk = AddLink(la, src_addr, dst_addr, alias_addr, id, NO_DEST_PORT, GET_ALIAS_ID, LINK_ICMP); } return (lnk); } struct alias_link * FindFragmentIn1(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short ip_id) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkIn(la, dst_addr, alias_addr, NO_DEST_PORT, ip_id, LINK_FRAGMENT_ID, 0); if (lnk == NULL) { lnk = AddLink(la, ANY_ADDR, dst_addr, alias_addr, NO_SRC_PORT, NO_DEST_PORT, ip_id, LINK_FRAGMENT_ID); } return (lnk); } /* Doesn't add a link if one is not found. */ struct alias_link * FindFragmentIn2(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short ip_id) { LIBALIAS_LOCK_ASSERT(la); return FindLinkIn(la, dst_addr, alias_addr, NO_DEST_PORT, ip_id, LINK_FRAGMENT_ID, 0); } struct alias_link * AddFragmentPtrLink(struct libalias *la, struct in_addr dst_addr, u_short ip_id) { LIBALIAS_LOCK_ASSERT(la); return AddLink(la, ANY_ADDR, dst_addr, ANY_ADDR, NO_SRC_PORT, NO_DEST_PORT, ip_id, LINK_FRAGMENT_PTR); } struct alias_link * FindFragmentPtr(struct libalias *la, struct in_addr dst_addr, u_short ip_id) { LIBALIAS_LOCK_ASSERT(la); return FindLinkIn(la, dst_addr, ANY_ADDR, NO_DEST_PORT, ip_id, LINK_FRAGMENT_PTR, 0); } struct alias_link * FindProtoIn(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_char proto) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkIn(la, dst_addr, alias_addr, NO_DEST_PORT, 0, proto, 1); if (lnk == NULL && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) { struct in_addr target_addr; target_addr = FindOriginalAddress(la, alias_addr); lnk = AddLink(la, target_addr, dst_addr, alias_addr, NO_SRC_PORT, NO_DEST_PORT, 0, proto); } return (lnk); } struct alias_link * FindProtoOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_char proto) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkOut(la, src_addr, dst_addr, NO_SRC_PORT, NO_DEST_PORT, proto, 1); if (lnk == NULL) { struct in_addr alias_addr; alias_addr = FindAliasAddress(la, src_addr); lnk = AddLink(la, src_addr, dst_addr, alias_addr, NO_SRC_PORT, NO_DEST_PORT, 0, proto); } return (lnk); } struct alias_link * FindUdpTcpIn(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_short dst_port, u_short alias_port, u_char proto, int create) { int link_type; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); switch (proto) { case IPPROTO_UDP: link_type = LINK_UDP; break; case IPPROTO_TCP: link_type = LINK_TCP; break; default: return (NULL); break; } lnk = FindLinkIn(la, dst_addr, alias_addr, dst_port, alias_port, link_type, create); if (lnk == NULL && create && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) { struct in_addr target_addr; target_addr = FindOriginalAddress(la, alias_addr); lnk = AddLink(la, target_addr, dst_addr, alias_addr, alias_port, dst_port, alias_port, link_type); } return (lnk); } struct alias_link * FindUdpTcpOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_short src_port, u_short dst_port, u_char proto, int create) { int link_type; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); switch (proto) { case IPPROTO_UDP: link_type = LINK_UDP; break; case IPPROTO_TCP: link_type = LINK_TCP; break; default: return (NULL); break; } lnk = FindLinkOut(la, src_addr, dst_addr, src_port, dst_port, link_type, create); if (lnk == NULL && create) { struct in_addr alias_addr; alias_addr = FindAliasAddress(la, src_addr); lnk = AddLink(la, src_addr, dst_addr, alias_addr, src_port, dst_port, GET_ALIAS_PORT, link_type); } return (lnk); } struct alias_link * AddPptp(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, struct in_addr alias_addr, u_int16_t src_call_id) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = AddLink(la, src_addr, dst_addr, alias_addr, src_call_id, 0, GET_ALIAS_PORT, LINK_PPTP); return (lnk); } struct alias_link * FindPptpOutByCallId(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_int16_t src_call_id) { u_int i; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); i = StartPointOut(src_addr, dst_addr, 0, 0, LINK_PPTP); LIST_FOREACH(lnk, &la->linkTableOut[i], list_out) if (lnk->link_type == LINK_PPTP && lnk->src_addr.s_addr == src_addr.s_addr && lnk->dst_addr.s_addr == dst_addr.s_addr && lnk->src_port == src_call_id) break; CleanupLink(la, &lnk); return (lnk); } struct alias_link * FindPptpOutByPeerCallId(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_int16_t dst_call_id) { u_int i; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); i = StartPointOut(src_addr, dst_addr, 0, 0, LINK_PPTP); LIST_FOREACH(lnk, &la->linkTableOut[i], list_out) if (lnk->link_type == LINK_PPTP && lnk->src_addr.s_addr == src_addr.s_addr && lnk->dst_addr.s_addr == dst_addr.s_addr && lnk->dst_port == dst_call_id) break; CleanupLink(la, &lnk); return (lnk); } struct alias_link * FindPptpInByCallId(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_int16_t dst_call_id) { u_int i; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); i = StartPointIn(alias_addr, 0, LINK_PPTP); LIST_FOREACH(lnk, &la->linkTableIn[i], list_in) if (lnk->link_type == LINK_PPTP && lnk->dst_addr.s_addr == dst_addr.s_addr && lnk->alias_addr.s_addr == alias_addr.s_addr && lnk->dst_port == dst_call_id) break; CleanupLink(la, &lnk); return (lnk); } struct alias_link * FindPptpInByPeerCallId(struct libalias *la, struct in_addr dst_addr, struct in_addr alias_addr, u_int16_t alias_call_id) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkIn(la, dst_addr, alias_addr, 0 /* any */ , alias_call_id, LINK_PPTP, 0); return (lnk); } struct alias_link * FindRtspOut(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, u_short src_port, u_short alias_port, u_char proto) { int link_type; struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); switch (proto) { case IPPROTO_UDP: link_type = LINK_UDP; break; case IPPROTO_TCP: link_type = LINK_TCP; break; default: return (NULL); break; } lnk = FindLinkOut(la, src_addr, dst_addr, src_port, 0, link_type, 1); if (lnk == NULL) { struct in_addr alias_addr; alias_addr = FindAliasAddress(la, src_addr); lnk = AddLink(la, src_addr, dst_addr, alias_addr, src_port, 0, alias_port, link_type); } return (lnk); } struct in_addr FindOriginalAddress(struct libalias *la, struct in_addr alias_addr) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkIn(la, ANY_ADDR, alias_addr, 0, 0, LINK_ADDR, 0); if (lnk == NULL) { if (la->targetAddress.s_addr == INADDR_ANY) return (alias_addr); else if (la->targetAddress.s_addr == INADDR_NONE) return (la->aliasAddress.s_addr != INADDR_ANY) ? la->aliasAddress : alias_addr; else return (la->targetAddress); } else { if (lnk->server != NULL) { /* LSNAT link */ struct in_addr src_addr; src_addr = lnk->server->addr; lnk->server = lnk->server->next; return (src_addr); } else if (lnk->src_addr.s_addr == INADDR_ANY) return (la->aliasAddress.s_addr != INADDR_ANY) ? la->aliasAddress : alias_addr; else return (lnk->src_addr); } } struct in_addr FindAliasAddress(struct libalias *la, struct in_addr original_addr) { struct alias_link *lnk; LIBALIAS_LOCK_ASSERT(la); lnk = FindLinkOut(la, original_addr, ANY_ADDR, 0, 0, LINK_ADDR, 0); if (lnk == NULL) { return (la->aliasAddress.s_addr != INADDR_ANY) ? la->aliasAddress : original_addr; } else { if (lnk->alias_addr.s_addr == INADDR_ANY) return (la->aliasAddress.s_addr != INADDR_ANY) ? la->aliasAddress : original_addr; else return (lnk->alias_addr); } } /* External routines for getting or changing link data (external to alias_db.c, but internal to alias*.c) SetFragmentData(), GetFragmentData() SetFragmentPtr(), GetFragmentPtr() SetStateIn(), SetStateOut(), GetStateIn(), GetStateOut() GetOriginalAddress(), GetDestAddress(), GetAliasAddress() GetOriginalPort(), GetAliasPort() SetAckModified(), GetAckModified() GetDeltaAckIn(), GetDeltaSeqOut(), AddSeq() SetProtocolFlags(), GetProtocolFlags() SetDestCallId() */ void SetFragmentAddr(struct alias_link *lnk, struct in_addr src_addr) { lnk->data.frag_addr = src_addr; } void GetFragmentAddr(struct alias_link *lnk, struct in_addr *src_addr) { *src_addr = lnk->data.frag_addr; } void SetFragmentPtr(struct alias_link *lnk, void *fptr) { lnk->data.frag_ptr = fptr; } void GetFragmentPtr(struct alias_link *lnk, void **fptr) { *fptr = lnk->data.frag_ptr; } void SetStateIn(struct alias_link *lnk, int state) { /* TCP input state */ switch (state) { case ALIAS_TCP_STATE_DISCONNECTED: if (lnk->data.tcp->state.out != ALIAS_TCP_STATE_CONNECTED) lnk->expire_time = TCP_EXPIRE_DEAD; else lnk->expire_time = TCP_EXPIRE_SINGLEDEAD; break; case ALIAS_TCP_STATE_CONNECTED: if (lnk->data.tcp->state.out == ALIAS_TCP_STATE_CONNECTED) lnk->expire_time = TCP_EXPIRE_CONNECTED; break; default: #ifdef _KERNEL panic("libalias:SetStateIn() unknown state"); #else abort(); #endif } lnk->data.tcp->state.in = state; } void SetStateOut(struct alias_link *lnk, int state) { /* TCP output state */ switch (state) { case ALIAS_TCP_STATE_DISCONNECTED: if (lnk->data.tcp->state.in != ALIAS_TCP_STATE_CONNECTED) lnk->expire_time = TCP_EXPIRE_DEAD; else lnk->expire_time = TCP_EXPIRE_SINGLEDEAD; break; case ALIAS_TCP_STATE_CONNECTED: if (lnk->data.tcp->state.in == ALIAS_TCP_STATE_CONNECTED) lnk->expire_time = TCP_EXPIRE_CONNECTED; break; default: #ifdef _KERNEL panic("libalias:SetStateOut() unknown state"); #else abort(); #endif } lnk->data.tcp->state.out = state; } int GetStateIn(struct alias_link *lnk) { /* TCP input state */ return (lnk->data.tcp->state.in); } int GetStateOut(struct alias_link *lnk) { /* TCP output state */ return (lnk->data.tcp->state.out); } struct in_addr GetOriginalAddress(struct alias_link *lnk) { if (lnk->src_addr.s_addr == INADDR_ANY) return (lnk->la->aliasAddress); else return (lnk->src_addr); } struct in_addr GetDestAddress(struct alias_link *lnk) { return (lnk->dst_addr); } struct in_addr GetAliasAddress(struct alias_link *lnk) { if (lnk->alias_addr.s_addr == INADDR_ANY) return (lnk->la->aliasAddress); else return (lnk->alias_addr); } struct in_addr GetDefaultAliasAddress(struct libalias *la) { LIBALIAS_LOCK_ASSERT(la); return (la->aliasAddress); } void SetDefaultAliasAddress(struct libalias *la, struct in_addr alias_addr) { LIBALIAS_LOCK_ASSERT(la); la->aliasAddress = alias_addr; } u_short GetOriginalPort(struct alias_link *lnk) { return (lnk->src_port); } u_short GetAliasPort(struct alias_link *lnk) { return (lnk->alias_port); } #ifndef NO_FW_PUNCH static u_short GetDestPort(struct alias_link *lnk) { return (lnk->dst_port); } #endif /* Indicate that ACK numbers have been modified in a TCP connection */ void SetAckModified(struct alias_link *lnk) { lnk->data.tcp->state.ack_modified = 1; } struct in_addr GetProxyAddress(struct alias_link *lnk) { return (lnk->proxy_addr); } void SetProxyAddress(struct alias_link *lnk, struct in_addr addr) { lnk->proxy_addr = addr; } u_short GetProxyPort(struct alias_link *lnk) { return (lnk->proxy_port); } void SetProxyPort(struct alias_link *lnk, u_short port) { lnk->proxy_port = port; } /* See if ACK numbers have been modified */ int GetAckModified(struct alias_link *lnk) { return (lnk->data.tcp->state.ack_modified); } /* * Find out how much the ACK number has been altered for an * incoming TCP packet. To do this, a circular list of ACK * numbers where the TCP packet size was altered is searched. */ // XXX ip free int GetDeltaAckIn(u_long ack, struct alias_link *lnk) { int i, j; int delta, ack_diff_min; delta = 0; ack_diff_min = -1; i = lnk->data.tcp->state.index; for (j = 0; j < N_LINK_TCP_DATA; j++) { struct ack_data_record x; if (i == 0) i = N_LINK_TCP_DATA; i--; x = lnk->data.tcp->ack[i]; if (x.active == 1) { int ack_diff; ack_diff = SeqDiff(x.ack_new, ack); if (ack_diff >= 0) { if (ack_diff_min >= 0) { if (ack_diff < ack_diff_min) { delta = x.delta; ack_diff_min = ack_diff; } } else { delta = x.delta; ack_diff_min = ack_diff; } } } } return (delta); } /* * Find out how much the sequence number has been altered for an * outgoing TCP packet. To do this, a circular list of ACK numbers * where the TCP packet size was altered is searched. */ // XXX ip free int GetDeltaSeqOut(u_long seq, struct alias_link *lnk) { int i, j; int delta, seq_diff_min; delta = 0; seq_diff_min = -1; i = lnk->data.tcp->state.index; for (j = 0; j < N_LINK_TCP_DATA; j++) { struct ack_data_record x; if (i == 0) i = N_LINK_TCP_DATA; i--; x = lnk->data.tcp->ack[i]; if (x.active == 1) { int seq_diff; seq_diff = SeqDiff(x.ack_old, seq); if (seq_diff >= 0) { if (seq_diff_min >= 0) { if (seq_diff < seq_diff_min) { delta = x.delta; seq_diff_min = seq_diff; } } else { delta = x.delta; seq_diff_min = seq_diff; } } } } return (delta); } /* * When a TCP packet has been altered in length, save this * information in a circular list. If enough packets have been * altered, then this list will begin to overwrite itself. */ // XXX ip free void AddSeq(struct alias_link *lnk, int delta, u_int ip_hl, u_short ip_len, u_long th_seq, u_int th_off) { struct ack_data_record x; int hlen, tlen, dlen; int i; hlen = (ip_hl + th_off) << 2; tlen = ntohs(ip_len); dlen = tlen - hlen; x.ack_old = htonl(ntohl(th_seq) + dlen); x.ack_new = htonl(ntohl(th_seq) + dlen + delta); x.delta = delta; x.active = 1; i = lnk->data.tcp->state.index; lnk->data.tcp->ack[i] = x; i++; if (i == N_LINK_TCP_DATA) lnk->data.tcp->state.index = 0; else lnk->data.tcp->state.index = i; } void SetExpire(struct alias_link *lnk, int expire) { if (expire == 0) { lnk->flags &= ~LINK_PERMANENT; DeleteLink(&lnk); } else if (expire == -1) { lnk->flags |= LINK_PERMANENT; } else if (expire > 0) { lnk->expire_time = expire; } else { #ifdef LIBALIAS_DEBUG fprintf(stderr, "PacketAlias/SetExpire(): "); fprintf(stderr, "error in expire parameter\n"); #endif } } void SetProtocolFlags(struct alias_link *lnk, int pflags) { lnk->pflags = pflags; } int GetProtocolFlags(struct alias_link *lnk) { return (lnk->pflags); } void SetDestCallId(struct alias_link *lnk, u_int16_t cid) { struct libalias *la = lnk->la; LIBALIAS_LOCK_ASSERT(la); la->deleteAllLinks = 1; ReLink(lnk, lnk->src_addr, lnk->dst_addr, lnk->alias_addr, lnk->src_port, cid, lnk->alias_port, lnk->link_type); la->deleteAllLinks = 0; } /* Miscellaneous Functions HouseKeeping() InitPacketAliasLog() UninitPacketAliasLog() */ /* Whenever an outgoing or incoming packet is handled, HouseKeeping() is called to find and remove timed-out aliasing links. Logic exists to sweep through the entire table and linked list structure every 60 seconds. (prototype in alias_local.h) */ void HouseKeeping(struct libalias *la) { - struct alias_link * lnk = TAILQ_FIRST(&la->checkExpire); -#ifndef _KERNEL - struct timeval tv; -#endif + static unsigned int packets = 0; + static unsigned int packet_limit = 1000; LIBALIAS_LOCK_ASSERT(la); + packets++; + /* - * Save system time (seconds) in global variable timeStamp for use - * by other functions. This is done so as not to unnecessarily - * waste timeline by making system calls. + * User space time/gettimeofday/... is very expensive. + * Kernel space cache trashing is unnecessary. + * + * Save system time (seconds) in global variable LibAliasTime + * for use by other functions. This is done so as not to + * unnecessarily waste timeline by making system calls. + * + * Reduce the amount of house keeping work substantially by + * sampling over the packets. */ + if (packets % packet_limit == 0) { + time_t now; + #ifdef _KERNEL - la->timeStamp = time_uptime; + now = time_uptime; #else - gettimeofday(&tv, NULL); - la->timeStamp = tv.tv_sec; + now = time(NULL); #endif - CleanupLink(la, &lnk); + if (now != LibAliasTime) { + /* retry three times a second */ + packet_limit = packets / 3; + packets = 0; + LibAliasTime = now; + } + + } + /* Do a cleanup for the first packets of the new second only */ + if (packets < (la->udpLinkCount + la->tcpLinkCount)) { + struct alias_link * lnk = TAILQ_FIRST(&la->checkExpire); + + CleanupLink(la, &lnk); + } } /* Init the log file and enable logging */ static int InitPacketAliasLog(struct libalias *la) { LIBALIAS_LOCK_ASSERT(la); if (~la->packetAliasMode & PKT_ALIAS_LOG) { #ifdef _KERNEL if ((la->logDesc = malloc(LIBALIAS_BUF_SIZE))) ; #else if ((la->logDesc = fopen("/var/log/alias.log", "w"))) fprintf(la->logDesc, "PacketAlias/InitPacketAliasLog: Packet alias logging enabled.\n"); #endif else return (ENOMEM); /* log initialization failed */ la->packetAliasMode |= PKT_ALIAS_LOG; } return (1); } /* Close the log-file and disable logging. */ static void UninitPacketAliasLog(struct libalias *la) { LIBALIAS_LOCK_ASSERT(la); if (la->logDesc) { #ifdef _KERNEL free(la->logDesc); #else fclose(la->logDesc); #endif la->logDesc = NULL; } la->packetAliasMode &= ~PKT_ALIAS_LOG; } /* Outside world interfaces -- "outside world" means other than alias*.c routines -- PacketAliasRedirectPort() PacketAliasAddServer() PacketAliasRedirectProto() PacketAliasRedirectAddr() PacketAliasRedirectDynamic() PacketAliasRedirectDelete() PacketAliasSetAddress() PacketAliasInit() PacketAliasUninit() PacketAliasSetMode() (prototypes in alias.h) */ /* Redirection from a specific public addr:port to a private addr:port */ struct alias_link * LibAliasRedirectPort(struct libalias *la, struct in_addr src_addr, u_short src_port, struct in_addr dst_addr, u_short dst_port, struct in_addr alias_addr, u_short alias_port, u_char proto) { int link_type; struct alias_link *lnk; LIBALIAS_LOCK(la); switch (proto) { case IPPROTO_UDP: link_type = LINK_UDP; break; 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 SCTP, TCP and UDP protocols allowed\n"); #endif lnk = NULL; goto getout; } lnk = AddLink(la, src_addr, dst_addr, alias_addr, src_port, dst_port, alias_port, link_type); if (lnk != NULL) { lnk->flags |= LINK_PERMANENT; } #ifdef LIBALIAS_DEBUG else { fprintf(stderr, "PacketAliasRedirectPort(): " "call to AddLink() failed\n"); } #endif getout: LIBALIAS_UNLOCK(la); return (lnk); } /* Add server to the pool of servers */ int LibAliasAddServer(struct libalias *la, struct alias_link *lnk, struct in_addr addr, u_short port) { struct server *server; int res; LIBALIAS_LOCK(la); (void)la; server = malloc(sizeof(struct server)); if (server != NULL) { struct server *head; server->addr = addr; server->port = port; head = lnk->server; if (head == NULL) server->next = server; else { struct server *s; for (s = head; s->next != head; s = s->next) ; s->next = server; server->next = head; } lnk->server = server; res = 0; } else res = -1; LIBALIAS_UNLOCK(la); return (res); } /* Redirect packets of a given IP protocol from a specific public address to a private address */ struct alias_link * LibAliasRedirectProto(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, struct in_addr alias_addr, u_char proto) { struct alias_link *lnk; LIBALIAS_LOCK(la); lnk = AddLink(la, src_addr, dst_addr, alias_addr, NO_SRC_PORT, NO_DEST_PORT, 0, proto); if (lnk != NULL) { lnk->flags |= LINK_PERMANENT; } #ifdef LIBALIAS_DEBUG else { fprintf(stderr, "PacketAliasRedirectProto(): " "call to AddLink() failed\n"); } #endif LIBALIAS_UNLOCK(la); return (lnk); } /* Static address translation */ struct alias_link * LibAliasRedirectAddr(struct libalias *la, struct in_addr src_addr, struct in_addr alias_addr) { struct alias_link *lnk; LIBALIAS_LOCK(la); lnk = AddLink(la, src_addr, ANY_ADDR, alias_addr, 0, 0, 0, LINK_ADDR); if (lnk != NULL) { lnk->flags |= LINK_PERMANENT; } #ifdef LIBALIAS_DEBUG else { fprintf(stderr, "PacketAliasRedirectAddr(): " "call to AddLink() failed\n"); } #endif LIBALIAS_UNLOCK(la); return (lnk); } /* Mark the aliasing link dynamic */ int LibAliasRedirectDynamic(struct libalias *la, struct alias_link *lnk) { int res; LIBALIAS_LOCK(la); (void)la; if (lnk->flags & LINK_PARTIALLY_SPECIFIED) res = -1; else { lnk->flags &= ~LINK_PERMANENT; res = 0; } LIBALIAS_UNLOCK(la); return (res); } /* This is a dangerous function to put in the API, because an invalid pointer can crash the program. */ void LibAliasRedirectDelete(struct libalias *la, struct alias_link *lnk) { LIBALIAS_LOCK(la); la->deleteAllLinks = 1; DeleteLink(&lnk); la->deleteAllLinks = 0; LIBALIAS_UNLOCK(la); } void LibAliasSetAddress(struct libalias *la, struct in_addr addr) { LIBALIAS_LOCK(la); if (la->packetAliasMode & PKT_ALIAS_RESET_ON_ADDR_CHANGE && la->aliasAddress.s_addr != addr.s_addr) CleanupAliasData(la); la->aliasAddress = addr; LIBALIAS_UNLOCK(la); } void LibAliasSetAliasPortRange(struct libalias *la, u_short port_low, u_short port_high) { LIBALIAS_LOCK(la); la->aliasPortLower = port_low; /* Add 1 to the aliasPortLength as modulo has range of 1 to n-1 */ la->aliasPortLength = port_high - port_low + 1; LIBALIAS_UNLOCK(la); } void LibAliasSetTarget(struct libalias *la, struct in_addr target_addr) { LIBALIAS_LOCK(la); la->targetAddress = target_addr; LIBALIAS_UNLOCK(la); } static void finishoff(void) { while (!LIST_EMPTY(&instancehead)) LibAliasUninit(LIST_FIRST(&instancehead)); } struct libalias * LibAliasInit(struct libalias *la) { int i; -#ifndef _KERNEL - struct timeval tv; -#endif if (la == NULL) { #ifdef _KERNEL #undef malloc /* XXX: ugly */ la = malloc(sizeof *la, M_ALIAS, M_WAITOK | M_ZERO); #else la = calloc(sizeof *la, 1); if (la == NULL) return (la); #endif #ifndef _KERNEL /* kernel cleans up on module unload */ if (LIST_EMPTY(&instancehead)) atexit(finishoff); #endif LIST_INSERT_HEAD(&instancehead, la, instancelist); #ifdef _KERNEL - la->timeStamp = time_uptime; + LibAliasTime = time_uptime; #else - gettimeofday(&tv, NULL); - la->timeStamp = tv.tv_sec; + LibAliasTime = time(NULL); #endif for (i = 0; i < LINK_TABLE_OUT_SIZE; i++) LIST_INIT(&la->linkTableOut[i]); for (i = 0; i < LINK_TABLE_IN_SIZE; i++) LIST_INIT(&la->linkTableIn[i]); TAILQ_INIT(&la->checkExpire); #ifdef _KERNEL AliasSctpInit(la); #endif LIBALIAS_LOCK_INIT(la); LIBALIAS_LOCK(la); } else { LIBALIAS_LOCK(la); la->deleteAllLinks = 1; CleanupAliasData(la); la->deleteAllLinks = 0; #ifdef _KERNEL AliasSctpTerm(la); AliasSctpInit(la); #endif } la->aliasAddress.s_addr = INADDR_ANY; la->targetAddress.s_addr = INADDR_ANY; la->icmpLinkCount = 0; la->udpLinkCount = 0; la->tcpLinkCount = 0; la->sctpLinkCount = 0; la->pptpLinkCount = 0; la->protoLinkCount = 0; la->fragmentIdLinkCount = 0; la->fragmentPtrLinkCount = 0; la->sockCount = 0; la->packetAliasMode = PKT_ALIAS_SAME_PORTS #ifndef NO_USE_SOCKETS | PKT_ALIAS_USE_SOCKETS #endif | PKT_ALIAS_RESET_ON_ADDR_CHANGE; #ifndef NO_FW_PUNCH la->fireWallFD = -1; #endif #ifndef _KERNEL LibAliasRefreshModules(); #endif LIBALIAS_UNLOCK(la); return (la); } void LibAliasUninit(struct libalias *la) { LIBALIAS_LOCK(la); #ifdef _KERNEL AliasSctpTerm(la); #endif la->deleteAllLinks = 1; CleanupAliasData(la); la->deleteAllLinks = 0; UninitPacketAliasLog(la); #ifndef NO_FW_PUNCH UninitPunchFW(la); #endif LIST_REMOVE(la, instancelist); LIBALIAS_UNLOCK(la); LIBALIAS_LOCK_DESTROY(la); free(la); } /* Change mode for some operations */ unsigned int LibAliasSetMode( struct libalias *la, unsigned int flags, /* Which state to bring flags to */ unsigned int mask /* Mask of which flags to affect (use 0 to * do a probe for flag values) */ ) { int res = -1; LIBALIAS_LOCK(la); if (flags & mask & PKT_ALIAS_LOG) { /* Enable logging */ if (InitPacketAliasLog(la) == ENOMEM) goto getout; } else if (~flags & mask & PKT_ALIAS_LOG) /* _Disable_ logging */ UninitPacketAliasLog(la); #ifndef NO_FW_PUNCH if (flags & mask & PKT_ALIAS_PUNCH_FW) /* Start punching holes in the firewall? */ InitPunchFW(la); else if (~flags & mask & PKT_ALIAS_PUNCH_FW) /* Stop punching holes in the firewall? */ UninitPunchFW(la); #endif /* Other flags can be set/cleared without special action */ la->packetAliasMode = (flags & mask) | (la->packetAliasMode & ~mask); res = la->packetAliasMode; getout: LIBALIAS_UNLOCK(la); return (res); } #ifndef NO_FW_PUNCH /***************** Code to support firewall punching. This shouldn't really be in this file, but making variables global is evil too. ****************/ /* Firewall include files */ #include #include #include #include /* * helper function, updates the pointer to cmd with the length * of the current command, and also cleans up the first word of * the new command in case it has been clobbered before. */ static ipfw_insn * next_cmd(ipfw_insn * cmd) { cmd += F_LEN(cmd); bzero(cmd, sizeof(*cmd)); return (cmd); } /* * A function to fill simple commands of size 1. * Existing flags are preserved. */ static ipfw_insn * fill_cmd(ipfw_insn * cmd, enum ipfw_opcodes opcode, int size, int flags, u_int16_t arg) { cmd->opcode = opcode; cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | (size & F_LEN_MASK); cmd->arg1 = arg; return next_cmd(cmd); } static ipfw_insn * fill_ip(ipfw_insn * cmd1, enum ipfw_opcodes opcode, u_int32_t addr) { ipfw_insn_ip *cmd = (ipfw_insn_ip *)cmd1; cmd->addr.s_addr = addr; return fill_cmd(cmd1, opcode, F_INSN_SIZE(ipfw_insn_u32), 0, 0); } static ipfw_insn * fill_one_port(ipfw_insn * cmd1, enum ipfw_opcodes opcode, u_int16_t port) { ipfw_insn_u16 *cmd = (ipfw_insn_u16 *)cmd1; cmd->ports[0] = cmd->ports[1] = port; return fill_cmd(cmd1, opcode, F_INSN_SIZE(ipfw_insn_u16), 0, 0); } static int fill_rule(void *buf, int bufsize, int rulenum, enum ipfw_opcodes action, int proto, struct in_addr sa, u_int16_t sp, struct in_addr da, u_int16_t dp) { struct ip_fw *rule = (struct ip_fw *)buf; ipfw_insn *cmd = (ipfw_insn *)rule->cmd; bzero(buf, bufsize); rule->rulenum = rulenum; cmd = fill_cmd(cmd, O_PROTO, F_INSN_SIZE(ipfw_insn), 0, proto); cmd = fill_ip(cmd, O_IP_SRC, sa.s_addr); cmd = fill_one_port(cmd, O_IP_SRCPORT, sp); cmd = fill_ip(cmd, O_IP_DST, da.s_addr); cmd = fill_one_port(cmd, O_IP_DSTPORT, dp); rule->act_ofs = (u_int32_t *)cmd - (u_int32_t *)rule->cmd; cmd = fill_cmd(cmd, action, F_INSN_SIZE(ipfw_insn), 0, 0); rule->cmd_len = (u_int32_t *)cmd - (u_int32_t *)rule->cmd; return ((char *)cmd - (char *)buf); } static void ClearAllFWHoles(struct libalias *la); #define fw_setfield(la, field, num) \ do { \ (field)[(num) - la->fireWallBaseNum] = 1; \ } /*lint -save -e717 */ while(0)/* lint -restore */ #define fw_clrfield(la, field, num) \ do { \ (field)[(num) - la->fireWallBaseNum] = 0; \ } /*lint -save -e717 */ while(0)/* lint -restore */ #define fw_tstfield(la, field, num) ((field)[(num) - la->fireWallBaseNum]) static void InitPunchFW(struct libalias *la) { la->fireWallField = malloc(la->fireWallNumNums); if (la->fireWallField) { memset(la->fireWallField, 0, la->fireWallNumNums); if (la->fireWallFD < 0) { la->fireWallFD = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); } ClearAllFWHoles(la); la->fireWallActiveNum = la->fireWallBaseNum; } } static void UninitPunchFW(struct libalias *la) { ClearAllFWHoles(la); if (la->fireWallFD >= 0) close(la->fireWallFD); la->fireWallFD = -1; if (la->fireWallField) free(la->fireWallField); la->fireWallField = NULL; la->packetAliasMode &= ~PKT_ALIAS_PUNCH_FW; } /* Make a certain link go through the firewall */ void PunchFWHole(struct alias_link *lnk) { struct libalias *la; int r; /* Result code */ struct ip_fw rule; /* On-the-fly built rule */ int fwhole; /* Where to punch hole */ la = lnk->la; /* Don't do anything unless we are asked to */ if (!(la->packetAliasMode & PKT_ALIAS_PUNCH_FW) || la->fireWallFD < 0 || lnk->link_type != LINK_TCP) return; memset(&rule, 0, sizeof rule); /** Build rule **/ /* Find empty slot */ for (fwhole = la->fireWallActiveNum; fwhole < la->fireWallBaseNum + la->fireWallNumNums && fw_tstfield(la, la->fireWallField, fwhole); fwhole++); if (fwhole == la->fireWallBaseNum + la->fireWallNumNums) { for (fwhole = la->fireWallBaseNum; fwhole < la->fireWallActiveNum && fw_tstfield(la, la->fireWallField, fwhole); fwhole++); if (fwhole == la->fireWallActiveNum) { /* No rule point empty - we can't punch more holes. */ la->fireWallActiveNum = la->fireWallBaseNum; #ifdef LIBALIAS_DEBUG fprintf(stderr, "libalias: Unable to create firewall hole!\n"); #endif return; } } /* Start next search at next position */ la->fireWallActiveNum = fwhole + 1; /* * generate two rules of the form * * add fwhole accept tcp from OAddr OPort to DAddr DPort add fwhole * accept tcp from DAddr DPort to OAddr OPort */ if (GetOriginalPort(lnk) != 0 && GetDestPort(lnk) != 0) { u_int32_t rulebuf[255]; int i; i = fill_rule(rulebuf, sizeof(rulebuf), fwhole, O_ACCEPT, IPPROTO_TCP, GetOriginalAddress(lnk), ntohs(GetOriginalPort(lnk)), GetDestAddress(lnk), ntohs(GetDestPort(lnk))); r = setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_ADD, rulebuf, i); if (r) err(1, "alias punch inbound(1) setsockopt(IP_FW_ADD)"); i = fill_rule(rulebuf, sizeof(rulebuf), fwhole, O_ACCEPT, IPPROTO_TCP, GetDestAddress(lnk), ntohs(GetDestPort(lnk)), GetOriginalAddress(lnk), ntohs(GetOriginalPort(lnk))); r = setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_ADD, rulebuf, i); if (r) err(1, "alias punch inbound(2) setsockopt(IP_FW_ADD)"); } /* Indicate hole applied */ lnk->data.tcp->fwhole = fwhole; fw_setfield(la, la->fireWallField, fwhole); } /* Remove a hole in a firewall associated with a particular alias lnk. Calling this too often is harmless. */ static void ClearFWHole(struct alias_link *lnk) { struct libalias *la; la = lnk->la; if (lnk->link_type == LINK_TCP) { int fwhole = lnk->data.tcp->fwhole; /* Where is the firewall hole? */ struct ip_fw rule; if (fwhole < 0) return; memset(&rule, 0, sizeof rule); /* useless for ipfw2 */ while (!setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_DEL, &fwhole, sizeof fwhole)); fw_clrfield(la, la->fireWallField, fwhole); lnk->data.tcp->fwhole = -1; } } /* Clear out the entire range dedicated to firewall holes. */ static void ClearAllFWHoles(struct libalias *la) { struct ip_fw rule; /* On-the-fly built rule */ int i; if (la->fireWallFD < 0) return; memset(&rule, 0, sizeof rule); for (i = la->fireWallBaseNum; i < la->fireWallBaseNum + la->fireWallNumNums; i++) { int r = i; while (!setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_DEL, &r, sizeof r)); } /* XXX: third arg correct here ? /phk */ memset(la->fireWallField, 0, la->fireWallNumNums); } #endif /* !NO_FW_PUNCH */ void LibAliasSetFWBase(struct libalias *la, unsigned int base, unsigned int num) { LIBALIAS_LOCK(la); #ifndef NO_FW_PUNCH la->fireWallBaseNum = base; la->fireWallNumNums = num; #endif LIBALIAS_UNLOCK(la); } void LibAliasSetSkinnyPort(struct libalias *la, unsigned int port) { LIBALIAS_LOCK(la); 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) { /* port redirect */ return (lnk->src_addr); } 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) /* redirect proto */ return (lnk->src_addr); } return (redir); /* address redirect */ } } diff --git a/sys/netinet/libalias/alias_local.h b/sys/netinet/libalias/alias_local.h index 8e2fe88fe620..f1b40f0438da 100644 --- a/sys/netinet/libalias/alias_local.h +++ b/sys/netinet/libalias/alias_local.h @@ -1,377 +1,378 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2001 Charles Mott * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ /* * Alias_local.h contains the function prototypes for alias.c, * alias_db.c, alias_util.c and alias_ftp.c, alias_irc.c (as well * as any future add-ons). It also includes macros, globals and * struct definitions shared by more than one alias*.c file. * * This include file is intended to be used only within the aliasing * software. Outside world interfaces are defined in alias.h * * This software is placed into the public domain with no restrictions * on its distribution. * * Initial version: August, 1996 (cjm) * * */ #ifndef _ALIAS_LOCAL_H_ #define _ALIAS_LOCAL_H_ #include #include #ifdef _KERNEL #include #include #include #include /* XXX: LibAliasSetTarget() uses this constant. */ #define INADDR_NONE 0xffffffff #include #else #include "alias_sctp.h" #endif /* Sizes of input and output link tables */ #define LINK_TABLE_OUT_SIZE 4001 #define LINK_TABLE_IN_SIZE 4001 #define GET_ALIAS_PORT -1 #define GET_ALIAS_ID GET_ALIAS_PORT #ifdef _KERNEL #define INET_NTOA_BUF(buf) (buf) #else #define INET_NTOA_BUF(buf) (buf), sizeof(buf) #endif struct proxy_entry; struct libalias { LIST_ENTRY(libalias) instancelist; /* Mode flags documented in alias.h */ int packetAliasMode; /* Address written onto source field of IP packet. */ struct in_addr aliasAddress; /* IP address incoming packets are sent to * if no aliasing link already exists */ struct in_addr targetAddress; /* Lookup table of pointers to chains of link records. * Each link record is doubly indexed into input and * output lookup tables. */ LIST_HEAD (, alias_link) linkTableOut[LINK_TABLE_OUT_SIZE]; LIST_HEAD (, alias_link) linkTableIn[LINK_TABLE_IN_SIZE]; /* HouseKeeping */ TAILQ_HEAD (, alias_link) checkExpire; /* Link statistics */ unsigned int icmpLinkCount; unsigned int udpLinkCount; unsigned int tcpLinkCount; unsigned int pptpLinkCount; unsigned int protoLinkCount; unsigned int fragmentIdLinkCount; unsigned int fragmentPtrLinkCount; unsigned int sockCount; - /* System time in seconds for current packet */ - int timeStamp; /* If equal to zero, DeleteLink() * will not remove permanent links */ int deleteAllLinks; /* log descriptor */ #ifdef _KERNEL char *logDesc; #else FILE *logDesc; #endif #ifndef NO_FW_PUNCH /* File descriptor to be able to control firewall. * Opened by PacketAliasSetMode on first setting * the PKT_ALIAS_PUNCH_FW flag. */ int fireWallFD; /* The first firewall entry free for our use */ int fireWallBaseNum; /* How many entries can we use? */ int fireWallNumNums; /* Which entry did we last use? */ int fireWallActiveNum; /* bool array for entries */ char *fireWallField; #endif /* TCP port used by the Skinny protocol. */ unsigned int skinnyPort; struct proxy_entry *proxyList; struct in_addr true_addr; /* in network byte order. */ u_short true_port; /* in host byte order. */ /* Port ranges for aliasing. */ u_short aliasPortLower; u_short aliasPortLength; /* * 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. */ struct mtx mutex; #endif }; /* Macros */ #ifdef _KERNEL #define LIBALIAS_LOCK_INIT(l) \ mtx_init(&l->mutex, "per-instance libalias mutex", NULL, MTX_DEF) #define LIBALIAS_LOCK_ASSERT(l) mtx_assert(&l->mutex, MA_OWNED) #define LIBALIAS_LOCK(l) mtx_lock(&l->mutex) #define LIBALIAS_UNLOCK(l) mtx_unlock(&l->mutex) #define LIBALIAS_LOCK_DESTROY(l) mtx_destroy(&l->mutex) #else #define LIBALIAS_LOCK_INIT(l) #define LIBALIAS_LOCK_ASSERT(l) #define LIBALIAS_LOCK(l) #define LIBALIAS_UNLOCK(l) #define LIBALIAS_LOCK_DESTROY(l) #endif /* * The following macro is used to update an * internet checksum. "delta" is a 32-bit * accumulation of all the changes to the * checksum (adding in new 16-bit words and * subtracting out old words), and "cksum" * is the checksum value to be updated. */ #define ADJUST_CHECKSUM(acc, cksum) \ do { \ acc += cksum; \ if (acc < 0) { \ acc = -acc; \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) ~acc; \ } else { \ acc = (acc >> 16) + (acc & 0xffff); \ acc += acc >> 16; \ cksum = (u_short) acc; \ } \ } while (0) /* Prototypes */ +/* System time in seconds for current packet */ +extern int LibAliasTime; + /* * 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. * If TCP data has changed, then we just set checksum to zero, * and caller must recalculate it himself. * In case if libalias will edit UDP data, the same approach * should be used. */ #ifndef _KERNEL u_short IpChecksum(struct ip *_pip); u_short TcpChecksum(struct ip *_pip); #endif void DifferentialChecksum(u_short * _cksum, void * _new, void * _old, int _n); /* Internal data access */ struct alias_link * AddLink(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr, struct in_addr alias_addr, u_short src_port, u_short dst_port, int alias_param, int link_type); struct alias_link * FindIcmpIn(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_short _id_alias, int _create); struct alias_link * FindIcmpOut(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_short _id, int _create); struct alias_link * FindFragmentIn1(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_short _ip_id); struct alias_link * FindFragmentIn2(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_short _ip_id); struct alias_link * AddFragmentPtrLink(struct libalias *la, struct in_addr _dst_addr, u_short _ip_id); struct alias_link * FindFragmentPtr(struct libalias *la, struct in_addr _dst_addr, u_short _ip_id); struct alias_link * FindProtoIn(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_char _proto); struct alias_link * FindProtoOut(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_char _proto); struct alias_link * FindUdpTcpIn(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_short _dst_port, u_short _alias_port, u_char _proto, int _create); struct alias_link * FindUdpTcpOut(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_short _src_port, u_short _dst_port, u_char _proto, int _create); struct alias_link * AddPptp(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, struct in_addr _alias_addr, u_int16_t _src_call_id); struct alias_link * FindPptpOutByCallId(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_int16_t _src_call_id); struct alias_link * FindPptpInByCallId(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_int16_t _dst_call_id); struct alias_link * FindPptpOutByPeerCallId(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_int16_t _dst_call_id); struct alias_link * FindPptpInByPeerCallId(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_int16_t _alias_call_id); struct alias_link * FindRtspOut(struct libalias *la, struct in_addr _src_addr, struct in_addr _dst_addr, u_short _src_port, u_short _alias_port, u_char _proto); 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 FindNewPortGroup(struct libalias *la, struct in_addr _dst_addr, struct in_addr _alias_addr, u_short _src_port, u_short _dst_port, u_short _port_count, u_char _proto, u_char _align); void GetFragmentAddr(struct alias_link *_lnk, struct in_addr *_src_addr); void SetFragmentAddr(struct alias_link *_lnk, struct in_addr _src_addr); void GetFragmentPtr(struct alias_link *_lnk, void **_fptr); void SetFragmentPtr(struct alias_link *_lnk, void *fptr); void SetStateIn(struct alias_link *_lnk, int _state); void SetStateOut(struct alias_link *_lnk, int _state); int GetStateIn (struct alias_link *_lnk); int GetStateOut(struct alias_link *_lnk); struct in_addr GetOriginalAddress(struct alias_link *_lnk); struct in_addr GetDestAddress(struct alias_link *_lnk); struct in_addr GetAliasAddress(struct alias_link *_lnk); struct in_addr GetDefaultAliasAddress(struct libalias *la); void SetDefaultAliasAddress(struct libalias *la, struct in_addr _alias_addr); u_short GetOriginalPort(struct alias_link *_lnk); u_short GetAliasPort(struct alias_link *_lnk); struct in_addr GetProxyAddress(struct alias_link *_lnk); void SetProxyAddress(struct alias_link *_lnk, struct in_addr _addr); u_short GetProxyPort(struct alias_link *_lnk); void SetProxyPort(struct alias_link *_lnk, u_short _port); void SetAckModified(struct alias_link *_lnk); int GetAckModified(struct alias_link *_lnk); int GetDeltaAckIn(u_long, struct alias_link *_lnk); int GetDeltaSeqOut(u_long, struct alias_link *lnk); void AddSeq(struct alias_link *lnk, int delta, u_int ip_hl, u_short ip_len, u_long th_seq, u_int th_off); void SetExpire (struct alias_link *_lnk, int _expire); void SetProtocolFlags(struct alias_link *_lnk, int _pflags); int GetProtocolFlags(struct alias_link *_lnk); void SetDestCallId(struct alias_link *_lnk, u_int16_t _cid); #ifndef NO_FW_PUNCH void PunchFWHole(struct alias_link *_lnk); #endif /* Housekeeping function */ void HouseKeeping(struct libalias *); /* Transparent proxy routines */ int ProxyCheck(struct libalias *la, struct in_addr *proxy_server_addr, u_short * proxy_server_port, struct in_addr src_addr, struct in_addr dst_addr, u_short dst_port, u_char ip_p); void ProxyModify(struct libalias *la, struct alias_link *_lnk, struct ip *_pip, int _maxpacketsize, int _proxy_type); /* Tcp specific routines */ /* lint -save -library Suppress flexelint warnings */ enum alias_tcp_state { ALIAS_TCP_STATE_NOT_CONNECTED, ALIAS_TCP_STATE_CONNECTED, ALIAS_TCP_STATE_DISCONNECTED }; #if defined(_NETINET_IP_H_) static __inline void * ip_next(struct ip *iphdr) { char *p = (char *)iphdr; return (&p[iphdr->ip_hl * 4]); } #endif #if defined(_NETINET_TCP_H_) static __inline void * tcp_next(struct tcphdr *tcphdr) { char *p = (char *)tcphdr; return (&p[tcphdr->th_off * 4]); } #endif #if defined(_NETINET_UDP_H_) static __inline void * udp_next(struct udphdr *udphdr) { return ((void *)(udphdr + 1)); } #endif #endif /* !_ALIAS_LOCAL_H_ */ diff --git a/sys/netinet/libalias/alias_sctp.c b/sys/netinet/libalias/alias_sctp.c index 719af1512a05..b88ad2b28ca8 100644 --- a/sys/netinet/libalias/alias_sctp.c +++ b/sys/netinet/libalias/alias_sctp.c @@ -1,2740 +1,2740 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 * Swinburne University of Technology, Melbourne, Australia. * * 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. * * 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 #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 static 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) mallocarray((n), (x), M_SCTPNAT, M_NOWAIT|M_ZERO) #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 */ +#define SN_I_T(la) (LibAliasTime + sysctl_init_timer) /**< INIT State expiration time in seconds */ +#define SN_U_T(la) (LibAliasTime + sysctl_up_timer) /**< UP State expiration time in seconds */ +#define SN_C_T(la) (LibAliasTime + sysctl_shutdown_timer) /**< CL State expiration time in seconds */ +#define SN_X_T(la) (LibAliasTime + 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); static SYSCTL_NODE(_net_inet_ip_alias, OID_AUTO, sctp, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, "SCTP NAT"); SYSCTL_PROC(_net_inet_ip_alias_sctp, OID_AUTO, log_level, CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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 | CTLFLAG_NEEDGIANT, &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); level = (level > SN_LOG_DEBUG_MAX) ? (SN_LOG_DEBUG_MAX) : (level); level = (level < SN_LOG_LOW) ? (SN_LOG_LOW) : (level); sysctl_log_level = 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 */ + la->sctpNatTimer.loc_time=time_uptime; /* LibAliasTime is not set yet */ #else - la->sctpNatTimer.loc_time=la->timeStamp; + la->sctpNatTimer.loc_time=LibAliasTime; #endif la->sctpNatTimer.cur_loc = 0; la->sctpLinkCount = 0; } /** * @brief Cleans-up the SCTP NAT Implementation prior to unloading * * Removes all entries from the timer queue, freeing associations as it goes. * We then free memory allocated to the look-up tables and the time queue * * NOTE: We do not need to traverse the look-up tables as each association * will always have an entry in the timer queue, freeing this memory * once will free all memory allocated to entries in the look-up tables * * @param la Pointer to the relevant libalias instance */ void AliasSctpTerm(struct libalias *la) { struct sctp_nat_assoc *assoc1, *assoc2; int i; LIBALIAS_LOCK_ASSERT(la); SN_LOG(SN_LOG_EVENT, SctpAliasLog("Removing SCTP NAT Instance\n")); for (i = 0; i < SN_TIMER_QUEUE_SIZE; i++) { assoc1 = LIST_FIRST(&la->sctpNatTimer.TimerQ[i]); while (assoc1 != NULL) { freeGlobalAddressList(assoc1); assoc2 = LIST_NEXT(assoc1, timer_Q); sn_free(assoc1); assoc1 = assoc2; } } sn_free(la->sctpTableLocal); sn_free(la->sctpTableGlobal); sn_free(la->sctpNatTimer.TimerQ); } /** * @brief Handles SCTP packets passed from libalias * * This function needs to actually NAT/drop packets and possibly create and * send AbortM or ErrorM packets in response. The process involves: * - Validating the direction parameter passed by the caller * - Checking and handling any expired timers for the NAT * - Calling sctp_PktParser() to parse the packet * - Call ProcessSctpMsg() to decide the appropriate outcome and to update * the NAT tables * - Based on the return code either: * - NAT the packet * - Construct and send an ErrorM|AbortM packet * - Mark the association for removal from the tables * - Potentially remove the association from all lookup tables * - Return the appropriate result to libalias * * @param la Pointer to the relevant libalias instance * @param pip Pointer to IP packet to process * @param direction SN_TO_LOCAL | SN_TO_GLOBAL * * @return PKT_ALIAS_OK | PKT_ALIAS_IGNORE | PKT_ALIAS_ERROR */ int SctpAlias(struct libalias *la, struct ip *pip, int direction) { int rtnval; struct sctp_nat_msg msg; struct sctp_nat_assoc *assoc = NULL; if ((direction != SN_TO_LOCAL) && (direction != SN_TO_GLOBAL)) { SctpAliasLog("ERROR: Invalid direction\n"); return (PKT_ALIAS_ERROR); } sctp_CheckTimers(la); /* Check timers */ /* Parse the packet */ rtnval = sctp_PktParser(la, direction, pip, &msg, &assoc); //using *char (change to mbuf when get code from paolo) switch (rtnval) { case SN_PARSE_OK: break; case SN_PARSE_ERROR_CHHL: /* Not an error if there is a chunk length parsing error and this is a fragmented packet */ if (ntohs(pip->ip_off) & IP_MF) { rtnval = SN_PARSE_OK; break; } SN_LOG(SN_LOG_EVENT, logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction)); return (PKT_ALIAS_ERROR); case SN_PARSE_ERROR_PARTIALLOOKUP: if (sysctl_error_on_ootb > SN_LOCALandPARTIAL_ERROR_ON_OOTB) { SN_LOG(SN_LOG_EVENT, logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction)); return (PKT_ALIAS_ERROR); } case SN_PARSE_ERROR_LOOKUP: if (sysctl_error_on_ootb == SN_ERROR_ON_OOTB || (sysctl_error_on_ootb == SN_LOCALandPARTIAL_ERROR_ON_OOTB && direction == SN_TO_LOCAL) || (sysctl_error_on_ootb == SN_LOCAL_ERROR_ON_OOTB && direction == SN_TO_GLOBAL)) { TxAbortErrorM(la, &msg, assoc, SN_REFLECT_ERROR, direction); /*NB assoc=NULL */ return (PKT_ALIAS_RESPOND); } default: SN_LOG(SN_LOG_EVENT, logsctperror("SN_PARSE_ERROR", msg.sctp_hdr->v_tag, rtnval, direction)); return (PKT_ALIAS_ERROR); } SN_LOG(SN_LOG_DETAIL, logsctpassoc(assoc, "*"); logsctpparse(direction, &msg); ); /* Process the SCTP message */ rtnval = ProcessSctpMsg(la, direction, &msg, assoc); SN_LOG(SN_LOG_DEBUG_MAX, logsctpassoc(assoc, "-"); logSctpLocal(la); logSctpGlobal(la); ); SN_LOG(SN_LOG_DEBUG, logTimerQ(la)); switch (rtnval) { case SN_NAT_PKT: switch (direction) { case SN_TO_LOCAL: DifferentialChecksum(&(msg.ip_hdr->ip_sum), &(assoc->l_addr), &(msg.ip_hdr->ip_dst), 2); msg.ip_hdr->ip_dst = assoc->l_addr; /* change dst address to local address*/ break; case SN_TO_GLOBAL: DifferentialChecksum(&(msg.ip_hdr->ip_sum), &(assoc->a_addr), &(msg.ip_hdr->ip_src), 2); msg.ip_hdr->ip_src = assoc->a_addr; /* change src to alias addr*/ break; default: rtnval = SN_DROP_PKT; /* shouldn't get here, but if it does drop packet */ SN_LOG(SN_LOG_LOW, logsctperror("ERROR: Invalid direction", msg.sctp_hdr->v_tag, rtnval, direction)); break; } break; case SN_DROP_PKT: SN_LOG(SN_LOG_DETAIL, logsctperror("SN_DROP_PKT", msg.sctp_hdr->v_tag, rtnval, direction)); break; case SN_REPLY_ABORT: case SN_REPLY_ERROR: case SN_SEND_ABORT: TxAbortErrorM(la, &msg, assoc, rtnval, direction); break; default: // big error, remove association and go to idle and write log messages SN_LOG(SN_LOG_LOW, logsctperror("SN_PROCESSING_ERROR", msg.sctp_hdr->v_tag, rtnval, direction)); assoc->state = SN_RM;/* Mark for removal*/ break; } /* Remove association if tagged for removal */ if (assoc->state == SN_RM) { if (assoc->TableRegister) { sctp_RmTimeOut(la, assoc); RmSctpAssoc(la, assoc); } LIBALIAS_LOCK_ASSERT(la); freeGlobalAddressList(assoc); sn_free(assoc); } switch (rtnval) { case SN_NAT_PKT: return (PKT_ALIAS_OK); case SN_SEND_ABORT: return (PKT_ALIAS_OK); case SN_REPLY_ABORT: case SN_REPLY_ERROR: case SN_REFLECT_ERROR: return (PKT_ALIAS_RESPOND); case SN_DROP_PKT: default: return (PKT_ALIAS_ERROR); } } /** * @brief Send an AbortM or ErrorM * * We construct the new SCTP packet to send in place of the existing packet we * have been asked to NAT. This function can only be called if the original * packet was successfully parsed as a valid SCTP packet. * * An AbortM (without cause) packet is the smallest SCTP packet available and as * such there is always space in the existing packet buffer to fit the AbortM * packet. An ErrorM packet is 4 bytes longer than the (the error cause is not * optional). An ErrorM is sent in response to an AddIP when the Vtag/address * combination, if added, will produce a conflict in the association look up * tables. It may also be used for an unexpected packet - a packet with no * matching association in the NAT table and we are requesting an AddIP so we * can add it. The smallest valid SCTP packet while the association is in an * up-state is a Heartbeat packet, which is big enough to be transformed to an * ErrorM. * * We create a temporary character array to store the packet as we are constructing * it. We then populate the array with appropriate values based on: * - Packet type (AbortM | ErrorM) * - Initial packet direction (SN_TO_LOCAL | SN_TO_GLOBAL) * - NAT response (Send packet | Reply packet) * * Once complete, we copy the contents of the temporary packet over the original * SCTP packet we were asked to NAT * * @param la Pointer to the relevant libalias instance * @param sm Pointer to sctp message information * @param assoc Pointer to current association details * @param sndrply SN_SEND_ABORT | SN_REPLY_ABORT | SN_REPLY_ERROR * @param direction SN_TO_LOCAL | SN_TO_GLOBAL */ static uint32_t local_sctp_finalize_crc32(uint32_t crc32c) { /* This routine is duplicated from SCTP * we need to do that since it MAY be that SCTP * is NOT compiled into the kernel. The CRC32C routines * however are always available in libkern. */ uint32_t result; #if BYTE_ORDER == BIG_ENDIAN uint8_t byte0, byte1, byte2, byte3; #endif /* Complement the result */ result = ~crc32c; #if BYTE_ORDER == BIG_ENDIAN /* * For BIG-ENDIAN.. aka Motorola byte order the result is in * little-endian form. So we must manually swap the bytes. Then we * can call htonl() which does nothing... */ byte0 = result & 0x000000ff; byte1 = (result >> 8) & 0x000000ff; byte2 = (result >> 16) & 0x000000ff; byte3 = (result >> 24) & 0x000000ff; crc32c = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); #else /* * For INTEL platforms the result comes out in network order. No * htonl is required or the swap above. So we optimize out both the * htonl and the manual swap above. */ crc32c = result; #endif return (crc32c); } static void TxAbortErrorM(struct libalias *la, struct sctp_nat_msg *sm, struct sctp_nat_assoc *assoc, int sndrply, int direction) { int sctp_size = sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) + sizeof(struct sctp_error_cause); int ip_size = sizeof(struct ip) + sctp_size; int include_error_cause = 1; char tmp_ip[ip_size]; char addrbuf[INET_ADDRSTRLEN]; if (ntohs(sm->ip_hdr->ip_len) < ip_size) { /* short packet, cannot send error cause */ include_error_cause = 0; ip_size = ip_size - sizeof(struct sctp_error_cause); sctp_size = sctp_size - sizeof(struct sctp_error_cause); } /* Assign header pointers packet */ struct ip* ip = (struct ip *) tmp_ip; struct sctphdr* sctp_hdr = (struct sctphdr *) ((char *) ip + sizeof(*ip)); struct sctp_chunkhdr* chunk_hdr = (struct sctp_chunkhdr *) ((char *) sctp_hdr + sizeof(*sctp_hdr)); struct sctp_error_cause* error_cause = (struct sctp_error_cause *) ((char *) chunk_hdr + sizeof(*chunk_hdr)); /* construct ip header */ ip->ip_v = sm->ip_hdr->ip_v; ip->ip_hl = 5; /* 5*32 bit words */ ip->ip_tos = 0; ip->ip_len = htons(ip_size); ip->ip_id = sm->ip_hdr->ip_id; ip->ip_off = 0; ip->ip_ttl = 255; ip->ip_p = IPPROTO_SCTP; /* The definitions below should be removed when they make it into the SCTP stack */ #define SCTP_MIDDLEBOX_FLAG 0x02 #define SCTP_NAT_TABLE_COLLISION 0x00b0 #define SCTP_MISSING_NAT 0x00b1 chunk_hdr->chunk_type = (sndrply & SN_TX_ABORT) ? SCTP_ABORT_ASSOCIATION : SCTP_OPERATION_ERROR; chunk_hdr->chunk_flags = SCTP_MIDDLEBOX_FLAG; if (include_error_cause) { error_cause->code = htons((sndrply & SN_REFLECT_ERROR) ? SCTP_MISSING_NAT : SCTP_NAT_TABLE_COLLISION); error_cause->length = htons(sizeof(struct sctp_error_cause)); chunk_hdr->chunk_length = htons(sizeof(*chunk_hdr) + sizeof(struct sctp_error_cause)); } else { chunk_hdr->chunk_length = htons(sizeof(*chunk_hdr)); } /* set specific values */ switch (sndrply) { case SN_REFLECT_ERROR: chunk_hdr->chunk_flags |= SCTP_HAD_NO_TCB; /* set Tbit */ sctp_hdr->v_tag = sm->sctp_hdr->v_tag; break; case SN_REPLY_ERROR: sctp_hdr->v_tag = (direction == SN_TO_LOCAL) ? assoc->g_vtag : assoc->l_vtag ; break; case SN_SEND_ABORT: sctp_hdr->v_tag = sm->sctp_hdr->v_tag; break; case SN_REPLY_ABORT: sctp_hdr->v_tag = sm->sctpchnk.Init->initiate_tag; break; } /* Set send/reply values */ if (sndrply == SN_SEND_ABORT) { /*pass through NAT */ ip->ip_src = (direction == SN_TO_LOCAL) ? sm->ip_hdr->ip_src : assoc->a_addr; ip->ip_dst = (direction == SN_TO_LOCAL) ? assoc->l_addr : sm->ip_hdr->ip_dst; sctp_hdr->src_port = sm->sctp_hdr->src_port; sctp_hdr->dest_port = sm->sctp_hdr->dest_port; } else { /* reply and reflect */ ip->ip_src = sm->ip_hdr->ip_dst; ip->ip_dst = sm->ip_hdr->ip_src; sctp_hdr->src_port = sm->sctp_hdr->dest_port; sctp_hdr->dest_port = sm->sctp_hdr->src_port; } /* Calculate IP header checksum */ ip->ip_sum = in_cksum_hdr(ip); /* calculate SCTP header CRC32 */ sctp_hdr->checksum = 0; sctp_hdr->checksum = local_sctp_finalize_crc32(calculate_crc32c(0xffffffff, (unsigned char *) sctp_hdr, sctp_size)); memcpy(sm->ip_hdr, ip, ip_size); SN_LOG(SN_LOG_EVENT,SctpAliasLog("%s %s 0x%x (->%s:%u vtag=0x%x crc=0x%x)\n", ((sndrply == SN_SEND_ABORT) ? "Sending" : "Replying"), ((sndrply & SN_TX_ERROR) ? "ErrorM" : "AbortM"), (include_error_cause ? ntohs(error_cause->code) : 0), inet_ntoa_r(ip->ip_dst, INET_NTOA_BUF(addrbuf)), 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); } /* Initialize association - sn_malloc initializes 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); } /* Initialize association - sn_malloc initializes 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; /* FALLTHROUGH */ 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); /* FALLTHROUGH */ 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 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.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(LibAliasTime >= 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 */ + if (LibAliasTime >= 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; char addrbuf[INET_ADDRSTRLEN]; 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_r(assoc->l_addr, addrbuf), 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_r(G_Addr->g_addr, addrbuf)); } } /** @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