1177 lines
37 KiB
C
1177 lines
37 KiB
C
/*
|
|
******************************************************************************
|
|
* INTERPEAK SOURCE FILE
|
|
*
|
|
* Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipnet_udp.c,v $ $Revision: 1.87.10.8 $
|
|
* $Source: /home/interpeak/CVSRoot/ipnet2/src/ipnet_udp.c,v $
|
|
* $Author: svc-cmnet $
|
|
* $State: Exp $ $Locker: $
|
|
*
|
|
* INTERPEAK_COPYRIGHT_STRING
|
|
* Design and implementation by Kenneth Jonsson <kenneth@interpeak.se>
|
|
******************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/* User datagram protocol transport layer */
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifndef IPCOM_KERNEL
|
|
#define IPCOM_KERNEL
|
|
#endif
|
|
#include "ipnet_config.h"
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_cstyle.h>
|
|
#include <ipcom_inet.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_type.h>
|
|
|
|
#include "ipnet.h"
|
|
#include "ipnet_dst_cache.h"
|
|
#include "ipnet_h.h"
|
|
#include "ipnet_usr_sock.h"
|
|
#ifdef IPCOM_USE_INET6
|
|
#include "ipnet_icmp6.h"
|
|
#endif
|
|
#ifdef IPNET_USE_TUNNEL
|
|
#include "ipnet_tunnel.h"
|
|
#endif
|
|
|
|
#ifdef IPCOM_WV_INSTRUMENTATION
|
|
#include <ipcom_windview.h>
|
|
#endif
|
|
|
|
#ifdef IPCOM_USE_FORWARDER
|
|
#include <ipcom_forwarder.h>
|
|
#endif
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
typedef struct Ipnet_sock_udp_ops_struct
|
|
{
|
|
Ipnet_sock_inet_ops inet;
|
|
Ipnet_sock_init_f network_init;
|
|
Ipnet_sock_connect_f network_connect;
|
|
Ipnet_sock_bind_f network_bind;
|
|
}
|
|
Ipnet_sock_udp_ops;
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 6 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 9 STATIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udp_init
|
|
*===========================================================================
|
|
* Description: The UDP transport layer initialization.
|
|
* Parameters: sock - The socket that is being created.
|
|
* user_context - unused by UDP.
|
|
* Returns: 0 = success, <0 = error code
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_udp_init(Ipnet_socket *sock, Ip_bool user_context)
|
|
{
|
|
Ipnet_sock_udp_ops *ops;
|
|
|
|
IPCOM_UNUSED_ARG(user_context);
|
|
if (sock->proto == 0)
|
|
/*
|
|
* Default to UDP in case no protocol was specified.
|
|
*/
|
|
sock->proto = IP_IPPROTO_UDP;
|
|
|
|
sock->chk_offset = 6;
|
|
#ifdef IPCOM_USE_INET
|
|
if (sock->proto == IP_IPPROTO_UDPLITE
|
|
|| ipnet->conf.inet.udp_checksum)
|
|
#endif
|
|
IP_BIT_SET(sock->flags, IPNET_SOCKET_FLAG_ADDCHECKSUM);
|
|
|
|
ops = (Ipnet_sock_udp_ops *) ipnet_sock_get_ops(sock);
|
|
if (ops->network_init != IP_NULL)
|
|
return ops->network_init(sock, user_context);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udp_bind
|
|
*===========================================================================
|
|
* Description: The UDP network layer bind.
|
|
* Parameters: sock - The socket to bind.
|
|
* name - The socket name.
|
|
* namelen - The length of the name variable.
|
|
* check_reuse - IP_TRUE if the port should be checked for reuse.
|
|
* Returns: 0 = success, <0 = error code
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_udp_bind(struct Ipnet_socket_struct *sock,
|
|
IP_CONST struct Ip_sockaddr *name,
|
|
Ip_socklen_t namelen,
|
|
Ip_bool check_reuse)
|
|
{
|
|
int ret;
|
|
Ipnet_sock_udp_ops *ops;
|
|
|
|
ops = (Ipnet_sock_udp_ops *) ipnet_sock_get_ops(sock);
|
|
ret = ops->network_bind(sock, name, namelen, check_reuse);
|
|
if (ret >= 0)
|
|
ret = ipnet_sock_bind_to_port(sock, name, check_reuse);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udp_connect
|
|
*===========================================================================
|
|
* Description: The UDP transport layer connect.
|
|
* Parameters: sock - The socket to connect.
|
|
* to - The address to connect to.
|
|
* tolen - The size of the 'to' buffer.
|
|
* Returns: 0 = success, <0 = error code
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_udp_connect(Ipnet_socket *sock, IP_CONST struct Ip_sockaddr *to, Ip_socklen_t tolen)
|
|
{
|
|
Ipnet_sock_udp_ops *ops;
|
|
int ret;
|
|
|
|
ops = (Ipnet_sock_udp_ops *) ipnet_sock_get_ops(sock);
|
|
ret = ops->network_connect(sock, to, tolen);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret == 1)
|
|
/* Socket was unconnected */
|
|
return 0;
|
|
|
|
if (sock->dport == 0)
|
|
/* Invalid port */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
IP_BIT_SET(sock->flags, IPNET_SOCKET_FLAG_CONNECTED);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udp_tx
|
|
*===========================================================================
|
|
* Description: Adds a UDP header to the application data.
|
|
* Parameters: sock - The socket to use when sending.
|
|
* msg - The send parameters.
|
|
* pkt - Packet that contains the UDP payload.
|
|
* Returns: ==0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_udp_tx(Ipnet_socket *sock, IP_CONST struct Ip_msghdr *msg, Ipcom_pkt *pkt)
|
|
{
|
|
Ipnet_sock_udp_ops *ops;
|
|
Ip_u16 to_port_n;
|
|
Ipnet_pkt_udp *udp;
|
|
int pkt_len;
|
|
|
|
IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE,
|
|
1, 1, IPCOM_WV_NETDEVENT_START,
|
|
ipnet_sock_udp_tx,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_output++);
|
|
ip_assert(sock->ipcom.type == IP_SOCK_DGRAM);
|
|
ip_assert(sock->proto == IP_IPPROTO_UDP);
|
|
|
|
if (msg->msg_name == IP_NULL)
|
|
to_port_n = ip_htons(sock->dport);
|
|
else
|
|
/*
|
|
* Ok for both IPv4 and IPv6 since the offset for "sin_port"
|
|
* is the same as "sin6_port"
|
|
*/
|
|
to_port_n = ((struct Ip_sockaddr_in*) msg->msg_name)->sin_port;
|
|
|
|
/*
|
|
* Fill in UDP header.
|
|
*/
|
|
udp = ipcom_pkt_push_front(pkt, IPNET_UDP_HDR_SIZE);
|
|
|
|
pkt_len = ipcom_pkt_get_length(pkt);
|
|
if (IP_UNLIKELY(pkt_len > 0xffff))
|
|
{
|
|
/*
|
|
* The UDP payload length field is 16-bit.
|
|
*/
|
|
ipcom_pkt_free(pkt);
|
|
return -IP_ERRNO_EMSGSIZE;
|
|
}
|
|
|
|
udp->sport = ip_htons(sock->sport);
|
|
udp->dport = to_port_n;
|
|
udp->sum = 0;
|
|
udp->ulen = (Ip_u16) ip_htons(pkt_len);
|
|
|
|
#ifdef IPCOM_FORWARDER_NAE
|
|
if (IP_BIT_ISFALSE(pkt->flags, IPCOM_PKT_FLAG_TL_CHECKSUM))
|
|
{
|
|
/*
|
|
* Update the checksum with the UDP and payload
|
|
*/
|
|
pkt->chk = ipcom_in_checksum_update(udp, pkt_len);
|
|
}
|
|
#else
|
|
|
|
/*
|
|
* Update the checksum with the UDP header
|
|
*/
|
|
pkt->chk += ipcom_in_checksum_update(udp, IPNET_UDP_HDR_SIZE);
|
|
#endif /* !IPCOM_FORWARDER_NAE */
|
|
|
|
IPCOM_MIB2(udpOutDatagrams++);
|
|
IPCOM_MIB2_U64_INC(udpHCOutDatagrams);
|
|
|
|
/*
|
|
* Call the network layer send handler
|
|
*/
|
|
ops = (Ipnet_sock_udp_ops *) ipnet_sock_get_ops(sock);
|
|
return ops->inet.network_send(sock, msg, pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udplite_tx
|
|
*===========================================================================
|
|
* Description: Adds a UDP-lite header to the application data.
|
|
* Parameters: sock - The socket to use when sending.
|
|
* msg - The send parameters.
|
|
* pkt - Packet that contains the UDP payload.
|
|
* Returns: ==0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_udplite_tx(Ipnet_socket *sock, IP_CONST struct Ip_msghdr *msg, Ipcom_pkt *pkt)
|
|
{
|
|
Ipnet_sock_udp_ops *ops;
|
|
Ip_u16 to_port_n;
|
|
Ipnet_pkt_udplite *udplite;
|
|
Ipcom_pkt *next_part;
|
|
int total_len = 0;
|
|
int data_len;
|
|
int pkt_len;
|
|
|
|
IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE,
|
|
1, 1, IPCOM_WV_NETDEVENT_START,
|
|
ipnet_sock_udplite_tx,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
ip_assert(sock->ipcom.type == IP_SOCK_DGRAM);
|
|
ip_assert(sock->proto == IP_IPPROTO_UDPLITE);
|
|
|
|
if (msg->msg_name == IP_NULL)
|
|
to_port_n = ip_htons(sock->dport);
|
|
else
|
|
/*
|
|
* Ok for both IPv4 and IPv6 since the offset for "sin_port"
|
|
* is the same as "sin6_port"
|
|
*/
|
|
to_port_n = ((struct Ip_sockaddr_in*) msg->msg_name)->sin_port;
|
|
|
|
/*
|
|
* Fill in UDP-Lite header.
|
|
*/
|
|
udplite = ipcom_pkt_push_front(pkt, IPNET_UDPLITE_HDR_SIZE);
|
|
|
|
udplite->sport = ip_htons(sock->sport);
|
|
udplite->dport = to_port_n;
|
|
udplite->sum_cov = ip_htons(sock->udplite_sum_cov);
|
|
udplite->sum = 0;
|
|
|
|
/*
|
|
* Calculate the checksum on the covered part UDP-Lite header.
|
|
*/
|
|
next_part = pkt;
|
|
total_len = 0;
|
|
data_len = sock->udplite_sum_cov ? (int)sock->udplite_sum_cov
|
|
: ipcom_pkt_get_length(pkt);
|
|
pkt->chk = 0;
|
|
while (next_part != IP_NULL
|
|
&& total_len < data_len)
|
|
{
|
|
pkt_len = next_part->end - next_part->start;
|
|
pkt_len = IP_MIN(pkt_len, data_len - total_len);
|
|
pkt->chk += ipcom_in_checksum_update(&next_part->data[next_part->start],
|
|
pkt_len);
|
|
next_part = next_part->next_part;
|
|
total_len += pkt_len;
|
|
}
|
|
|
|
#ifdef IPCOM_USE_MIB2_FIXME
|
|
IPCOM_MIB2(udpliteOutDatagrams++);
|
|
if (sock->udplite_sum_cov != 0
|
|
&& sock->udplite_sum_cov < ipcom_pkt_get_length(pkt))
|
|
IPCOM_MIB2(udpliteOutPartialCov++);
|
|
#endif
|
|
|
|
/*
|
|
* Call the network layer send handler
|
|
*/
|
|
ops = (Ipnet_sock_udp_ops *) ipnet_sock_get_ops(sock);
|
|
return ops->inet.network_send(sock, msg, pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_nat_traversal
|
|
*===========================================================================
|
|
* Description: Check if this packet should be processed by the NAT
|
|
* traversal functionality.
|
|
* Parameters: sock - The socket
|
|
* pkt - The packet in question.
|
|
* Returns: IP_FALSE; give to UDP socket.
|
|
* IP_TRUE; Do NAT_T processing.
|
|
*
|
|
*/
|
|
IP_STATIC Ip_bool
|
|
ipnet_udp_encapsulation(Ipnet_socket *sock, Ipcom_pkt *pkt, int *nat_t)
|
|
{
|
|
/* 'pkt' is unused in some build configurations */
|
|
IPCOM_UNUSED_ARG(pkt);
|
|
|
|
/* Did we have any NAT-T's? */
|
|
if (IP_UNLIKELY(sock->udp_encapsulation != IP_UDP_NAT_T_DISABLED))
|
|
{
|
|
#if defined(IPNET_USE_TUNNEL) && defined(IPCOM_USE_INET)
|
|
/* NAT-T processing? */
|
|
if (sock->ipcom.domain == IP_AF_INET && sock->udp_encapsulation == IP_UDP_NAT_T_RFC3519)
|
|
{
|
|
Ip_u8 *data;
|
|
Ip_u8 nproto;
|
|
|
|
/* Sanity; There MUST be atleast 4 bytes available */
|
|
if (ipcom_pkt_get_length(pkt) < 4)
|
|
{
|
|
/* Give to UDP socket */
|
|
return IP_FALSE;
|
|
}
|
|
|
|
/* Grab the start */
|
|
data = ipcom_pkt_get_data(pkt, 0);
|
|
|
|
/*
|
|
* NAT-T according to RFC3519
|
|
* IP HEADER <tunnel endpoint - tunnel endpoint>
|
|
* UDP HEADER
|
|
* 4 byte NAT-T header;
|
|
* TYPE(1 byte) := 4
|
|
* NH(1 byte) := 4, 47 or 55
|
|
* RESERVED(2 bytes) := 0
|
|
*/
|
|
|
|
/* Must be 4 */
|
|
if (data[0] != 4)
|
|
{
|
|
/* Give to UDP socket */
|
|
return IP_FALSE;
|
|
}
|
|
|
|
/*
|
|
*Verify that the tunnel protocol is correct
|
|
*/
|
|
nproto = data[1];
|
|
|
|
if (nproto == IP_IPPROTO_IPIP
|
|
|| nproto == IP_IPPROTO_GRE
|
|
|| nproto == IP_IPPROTO_MOBILE)
|
|
{
|
|
/*
|
|
* Correct UDP RFC3519 encap; do process
|
|
*/
|
|
*nat_t = IP_UDP_NAT_T_RFC3519;
|
|
return IP_TRUE;
|
|
}
|
|
|
|
|
|
/* Feed to socket */
|
|
return IP_FALSE;
|
|
}
|
|
#endif /* IPNET_USE_TUNNEL */
|
|
|
|
#if defined(IPIPSEC2) && defined(IPCOM_USE_INET)
|
|
if (sock->ipcom.domain == IP_AF_INET && sock->udp_encapsulation == IP_UDP_NAT_T_RFC3948)
|
|
{
|
|
if ((pkt->end - pkt->start == 1) && (pkt->data[pkt->start] == 0xff))
|
|
{
|
|
/* NAT-keepalive Packet; Give to socket */
|
|
return IP_FALSE;
|
|
}
|
|
|
|
if ((pkt->end - pkt->start > 4) && (IP_GET_32ON16(&pkt->data[pkt->start]) == 0))
|
|
{
|
|
/* Non-ESP marker set; do give to socket - this is an IKE packet */
|
|
return IP_FALSE;
|
|
}
|
|
|
|
*nat_t = IP_UDP_NAT_T_RFC3948;
|
|
return IP_TRUE;
|
|
}
|
|
#endif
|
|
|
|
#ifdef IPNET_USE_TUNNEL
|
|
/* NAT-T processing? */
|
|
if (sock->udp_encapsulation == IP_UDP_NAT_T_DSMIPV6)
|
|
{
|
|
/* Correct UDP RFC3519 encap; do process */
|
|
*nat_t = IP_UDP_NAT_T_DSMIPV6;
|
|
return IP_TRUE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return IP_FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_port_unreachable
|
|
*===========================================================================
|
|
* Description: The destination UDP port did not have any listeners.
|
|
* Parameters: dst - destination cache entry used by the UDP datagram
|
|
* pkt - packet where pkt->start is the offset to the UDP
|
|
* header.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udp_port_unreachable(Ipnet_dst_cache *dst, Ipcom_pkt *pkt)
|
|
{
|
|
#if defined(IPCOM_FORWARDER_NAE)
|
|
IPCOM_UNUSED_ARG(dst);
|
|
|
|
IPCOM_LOG2(DEBUG2,
|
|
"ipnet_udp_port_unreachable() :: datagram (%s to %s) passed to master os",
|
|
ipcom_inet_ntop(IP_AF_INET,
|
|
&dst->flow_spec.from,
|
|
ipnet->log_buf,
|
|
sizeof(ipnet->log_buf)),
|
|
ipcom_inet_ntop(IP_AF_INET,
|
|
&dst->flow_spec.to,
|
|
ipnet->log_buf + IP_INET_ADDRSTRLEN,
|
|
sizeof(ipnet->log_buf)- IP_INET_ADDRSTRLEN));
|
|
|
|
ipcom_pkt_to_master_os(pkt);
|
|
|
|
#else /* !defined(IPCOM_FORWARDER_NAE) */
|
|
|
|
int domain = ipnet_flow_spec_domain(&dst->flow_spec);
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
if (domain == IP_AF_INET)
|
|
ipnet_icmp4_send_port_unreachable(pkt);
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
if (domain == IP_AF_INET6)
|
|
ipnet_icmp6_send_port_unreachable(pkt);
|
|
#endif
|
|
#endif /* !defined(IPCOM_FORWARDER_NAE) */
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_sock_rx
|
|
*===========================================================================
|
|
* Description: Tries to find a matching socket and queue the packet
|
|
* on that socket.
|
|
* Parameters: dst - flow followed by this packet
|
|
* pkt - UDP or UDP-Lite packet
|
|
* socklookup - network domain specific lookup function
|
|
* src_port - UDP source port
|
|
* dst_port - UDP destination port
|
|
* ip_proto - IP_IPPROTO_UDP or IP_IPPROTO_UDPLITE
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udp_sock_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt,
|
|
Ipnet_sock_lookup_f socklookup,
|
|
Ip_u16 src_port,
|
|
Ip_u16 dst_port,
|
|
Ip_bool ip_proto)
|
|
{
|
|
int ret, nat_t;
|
|
Ipnet_socket *sock;
|
|
Ipcom_socket_eventcb event_cb;
|
|
IPCOM_WV_DECLARE_VARS;
|
|
|
|
if (dst->to_type != IPNET_ADDR_TYPE_UNICAST)
|
|
{
|
|
/*
|
|
* Multicast/broadcast packets must be delivered to all
|
|
* matching socket(s). ipnet_raw_input() has the required
|
|
* logic for doing that.
|
|
*/
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
|
|
1, 7, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
|
|
ipnet_udp_sock_rx, IPCOM_WV_NETD_INFO_RECEIVE,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input_multicast++);
|
|
ret = ipnet_raw_input(pkt, IP_TRUE, ip_proto,
|
|
&dst->flow_spec.from, src_port,
|
|
&dst->flow_spec.to, dst_port,
|
|
socklookup);
|
|
if (ret < 0)
|
|
IPCOM_MIB2(udpInErrors++);
|
|
else
|
|
{
|
|
IPCOM_MIB2(udpInDatagrams++);
|
|
IPCOM_MIB2_U64_INC(udpHCInDatagrams);
|
|
}
|
|
return;
|
|
}
|
|
|
|
sock = socklookup(dst->flow_spec.vr,
|
|
ip_proto,
|
|
&dst->flow_spec.to,
|
|
dst_port,
|
|
&dst->flow_spec.from,
|
|
src_port);
|
|
#if IPCOM_VR_MAX > 1
|
|
if (IP_UNLIKELY(sock == IP_NULL))
|
|
/*
|
|
* No matching socket, try the wildcard VR group
|
|
*/
|
|
sock = socklookup(IPCOM_VR_ANY,
|
|
ip_proto,
|
|
&dst->flow_spec.to,
|
|
dst_port,
|
|
&dst->flow_spec.from,
|
|
src_port);
|
|
#endif
|
|
|
|
if (IP_UNLIKELY(sock == IP_NULL))
|
|
{
|
|
IPCOM_WV_SET_VARS(13, IPCOM_WV_NETD_NOPORT);
|
|
IPNET_STATS(udp_input_sock_nomatch++);
|
|
IPCOM_MIB2(udpNoPorts++);
|
|
IPNET_STATS(udp_input_econnrefused++);
|
|
ipnet_udp_port_unreachable(dst, pkt);
|
|
goto errout;
|
|
}
|
|
|
|
#if defined(IPCOM_FORWARDER_NAE)
|
|
if (!ipcom_nae_rate_limit_process(pkt))
|
|
{
|
|
ipcom_pkt_free(pkt);
|
|
goto errout;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Matching socket found.
|
|
*/
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
|
|
1, 8, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
|
|
ipnet_udp_sock_rx, IPCOM_WV_NETD_INFO_RECEIVE,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input_sock_match++);
|
|
pkt->fd = sock->ipcom.fd;
|
|
|
|
if (IP_UNLIKELY(ip_proto == IP_IPPROTO_UDP
|
|
&& ipnet_udp_encapsulation(sock, pkt, &nat_t)))
|
|
{
|
|
/*
|
|
* NAT traversal packet. Do not queue this on the socket.
|
|
*/
|
|
ipcom_pkt_set_info(pkt,
|
|
IPNET_PKT_INFO_ENCAP_UDP,
|
|
IPNET_UDP_HDR_SIZE,
|
|
&pkt->data[pkt->tlstart]);
|
|
pkt->start -= IPNET_UDP_HDR_SIZE;
|
|
|
|
switch (nat_t)
|
|
{
|
|
#ifdef IPIPSEC2
|
|
case IP_UDP_NAT_T_RFC3948:
|
|
ipnet_ipsec_rx(dst, pkt, IP_IPPROTO_UDP);
|
|
break;
|
|
#endif /* IPIPSEC2 */
|
|
|
|
#if defined(IPNET_USE_TUNNEL) && defined(IPCOM_USE_INET)
|
|
case IP_UDP_NAT_T_RFC3519:
|
|
ipnet_tunnel_ip4_rfc3519_rx(dst, pkt);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef IPNET_USE_TUNNEL
|
|
case IP_UDP_NAT_T_DSMIPV6:
|
|
ipnet_tunnel_dsmipv6_input(dst, pkt);
|
|
break;
|
|
#endif
|
|
default:
|
|
goto errout;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Add the packet to the receive queue of the matching socket.
|
|
*/
|
|
ret = ipnet_queue_received_packet(pkt, sock);
|
|
if (ret < 0)
|
|
{
|
|
IPCOM_WV_SET_VARS(9, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input_queue_pkt_err++);
|
|
IPCOM_MIB2(udpInErrors++);
|
|
goto errout;
|
|
}
|
|
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_INFO,
|
|
1, 10, IPCOM_WV_NETDEVENT_INFO, IPCOM_WV_NETD_RECV,
|
|
ipnet_udp_sock_rx, IPCOM_WV_NETD_INFO_RECEIVE,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input_queue_pkt_ok++);
|
|
|
|
event_cb = sock->ipcom.event_cb;
|
|
if (IP_NULL != event_cb)
|
|
event_cb(&sock->ipcom, pkt, IP_SOEVENT_CB_INPKT);
|
|
|
|
IPCOM_MIB2(udpInDatagrams++);
|
|
IPCOM_MIB2_U64_INC(udpHCInDatagrams);
|
|
|
|
return;
|
|
|
|
errout:
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
|
|
1, IPCOM_WV_VAR1, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
|
|
ipnet_udp_sock_rx, IPCOM_WV_VAR2,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
#ifndef IPCOM_FORWARDER_NAE
|
|
ipcom_pkt_free(pkt);
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_rx
|
|
*===========================================================================
|
|
* Description: Reception handler for packet to UDP sockets.
|
|
* Parameters: dst - destination cache entry matching the packet.
|
|
* pkt - The received packet, must be freed by the called if
|
|
* this function fails.
|
|
* socklookup - A pointer to a function that can return the socket
|
|
* that is associated with this packet.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udp_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt,
|
|
Ipnet_sock_lookup_f socklookup)
|
|
{
|
|
Ipnet_pkt_udp *udp_hdr;
|
|
int ulen_h;
|
|
Ip_u16 src_port;
|
|
Ip_u16 dst_port;
|
|
int len;
|
|
IPCOM_WV_DECLARE_VARS;
|
|
|
|
IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE, 1, 3, IPCOM_WV_NETDEVENT_START,
|
|
ipnet_udp_rx, IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input++);
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_UDP_INPUT);
|
|
|
|
pkt->tlstart = pkt->start;
|
|
len = ipcom_pkt_get_length(pkt);
|
|
|
|
if (IP_UNLIKELY(len < IPNET_UDP_HDR_SIZE))
|
|
{
|
|
IPCOM_WV_SET_VARS(4, IPCOM_WV_NETD_BADHLEN);
|
|
IPNET_STATS(udp_input_hdr_trunc++);
|
|
IPCOM_MIB2(udpInErrors++);
|
|
goto errout;
|
|
}
|
|
|
|
udp_hdr = ipcom_pkt_pop_front(pkt, IPNET_UDP_HDR_SIZE);
|
|
ulen_h = ip_ntohs(udp_hdr->ulen);
|
|
src_port = ip_ntohs(udp_hdr->sport);
|
|
dst_port = ip_ntohs(udp_hdr->dport);
|
|
|
|
if (IP_UNLIKELY(len < ulen_h || ulen_h < IPNET_UDP_HDR_SIZE))
|
|
{
|
|
IPCOM_WV_SET_VARS(5, IPCOM_WV_NETD_BADLEN);
|
|
IPNET_STATS(udp_input_hdr_trunc++);
|
|
IPCOM_MIB2(udpInErrors++);
|
|
goto errout;
|
|
}
|
|
|
|
if (len > ulen_h)
|
|
{
|
|
ipcom_pkt_trim_tail(pkt, len - ulen_h);
|
|
}
|
|
|
|
if (udp_hdr->sum)
|
|
{
|
|
Ip_u16 chksum;
|
|
|
|
/*
|
|
* Verify the UDP checksum
|
|
*/
|
|
|
|
#ifdef IPCOM_USE_HW_CHECKSUM_RX
|
|
if (IP_BIT_ISSET(pkt->flags, IPCOM_PKT_FLAG_HW_CHECKSUM))
|
|
chksum = 0;
|
|
else if (IP_BIT_ISSET(pkt->flags, IPCOM_PKT_FLAG_TL_CHECKSUM))
|
|
chksum = ipcom_in_checksum_finish(pkt->chk);
|
|
else
|
|
#endif /* IPCOM_USE_HW_CHECKSUM_RX */
|
|
{
|
|
pkt->chk += ipcom_in_checksum_update(udp_hdr, IPNET_UDP_HDR_SIZE);
|
|
pkt->chk += ipcom_in_checksum_update_pkt(pkt, 0);
|
|
chksum = ipcom_in_checksum_finish(pkt->chk);
|
|
}
|
|
|
|
if (IP_UNLIKELY(chksum != 0))
|
|
{
|
|
/*
|
|
* Wrong checksum
|
|
*/
|
|
IPCOM_WV_SET_VARS(6, IPCOM_WV_NETD_BADSUM);
|
|
IPNET_STATS(udp_input_badchksum++);
|
|
IPCOM_MIB2(udpInErrors++);
|
|
goto errout;
|
|
}
|
|
}
|
|
/*
|
|
* else: UDP checksum disabled.
|
|
*/
|
|
|
|
ipnet_udp_sock_rx(dst,
|
|
pkt,
|
|
socklookup,
|
|
src_port,
|
|
dst_port,
|
|
IP_IPPROTO_UDP);
|
|
return;
|
|
|
|
|
|
errout:
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
|
|
1, IPCOM_WV_VAR1, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
|
|
ipnet_udp_rx, IPCOM_WV_VAR2,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
ipcom_pkt_free(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udplite_rx
|
|
*===========================================================================
|
|
* Description: Reception handler for packet to UDP-Lite sockets.
|
|
* Parameters: dst - destination cache entry matching the packet.
|
|
* pkt - The received packet, must be freed by the called if
|
|
* this function fails.
|
|
* socklookup - A pointer to a function that can return the socket
|
|
* that is associated with this packet.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udplite_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt,
|
|
Ipnet_sock_lookup_f socklookup)
|
|
{
|
|
Ipnet_pkt_udplite *udplite_hdr;
|
|
Ip_u16 src_port;
|
|
Ip_u16 dst_port;
|
|
int sum_cov;
|
|
int len;
|
|
Ip_u16 chksum;
|
|
IPCOM_WV_DECLARE_VARS;
|
|
|
|
IPCOM_WV_MARKER_1 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_VERBOSE, 1, 3, IPCOM_WV_NETDEVENT_START,
|
|
ipnet_udplite_rx, IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
IPNET_STATS(udp_input++);
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_UDP_INPUT);
|
|
|
|
len = ipcom_pkt_get_length(pkt);
|
|
|
|
if (IP_UNLIKELY(len < IPNET_UDP_HDR_SIZE))
|
|
{
|
|
IPCOM_WV_SET_VARS(4, IPCOM_WV_NETD_BADHLEN);
|
|
goto errout;
|
|
}
|
|
|
|
udplite_hdr = ipcom_pkt_pop_front(pkt, IPNET_UDPLITE_HDR_SIZE);
|
|
sum_cov = ip_ntohs(udplite_hdr->sum_cov);
|
|
src_port = ip_ntohs(udplite_hdr->sport);
|
|
dst_port = ip_ntohs(udplite_hdr->dport);
|
|
|
|
if (IP_UNLIKELY((sum_cov > 0 && sum_cov < 8)
|
|
|| sum_cov > len))
|
|
{
|
|
/*
|
|
* The part converted by the checksum must be all octets
|
|
* (sum_cov == 0) or something equal to or larger than the
|
|
* size of the header (8 octets).
|
|
*/
|
|
IPCOM_WV_SET_VARS(5, IPCOM_WV_NETD_BADLEN);
|
|
goto errout;
|
|
}
|
|
|
|
if (sum_cov == 0)
|
|
sum_cov = len;
|
|
|
|
/*
|
|
* Verify the UDP-Lite checksum
|
|
*/
|
|
if (pkt->next_part == IP_NULL)
|
|
{
|
|
pkt->chk += ipcom_in_checksum_update(udplite_hdr, (Ip_size_t) sum_cov);
|
|
}
|
|
else /* udp checksum for scattered packet */
|
|
{
|
|
Ipcom_pkt *next_part = pkt;
|
|
int part_len = 0;
|
|
int total_len = 0;
|
|
pkt->chk += ipcom_in_checksum_update(udplite_hdr, IPNET_UDPLITE_HDR_SIZE);
|
|
total_len += IPNET_UDPLITE_HDR_SIZE;
|
|
while ((next_part != IP_NULL) && (total_len < sum_cov))
|
|
{
|
|
part_len = next_part->end - next_part->start;
|
|
if (total_len + part_len > sum_cov)
|
|
{
|
|
part_len = sum_cov - total_len;
|
|
}
|
|
pkt->chk += ipcom_in_checksum_update(next_part->data+next_part->start, (Ip_size_t)part_len);
|
|
total_len += part_len;
|
|
next_part = next_part->next_part;
|
|
}
|
|
}
|
|
chksum = ipcom_in_checksum_finish(pkt->chk);
|
|
if (IP_UNLIKELY(chksum != 0))
|
|
{
|
|
/*
|
|
* Wrong checksum
|
|
*/
|
|
IPCOM_WV_SET_VARS(6, IPCOM_WV_NETD_BADSUM);
|
|
IPNET_STATS(udp_input_badchksum++);
|
|
IPCOM_MIB2(udpInErrors++);
|
|
goto errout;
|
|
}
|
|
|
|
ipnet_udp_sock_rx(dst,
|
|
pkt,
|
|
socklookup,
|
|
src_port,
|
|
dst_port,
|
|
IP_IPPROTO_UDPLITE);
|
|
return;
|
|
|
|
errout:
|
|
IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING,
|
|
1, IPCOM_WV_VAR1, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV,
|
|
ipnet_udplite_rx, IPCOM_WV_VAR2,
|
|
IPCOM_WV_IPNET_UDP_MODULE, IPCOM_WV_NETD_UDP4);
|
|
ipcom_pkt_free(pkt);
|
|
}
|
|
|
|
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_ip4_rx
|
|
*===========================================================================
|
|
* Description: Input handler for IPv4 UDP datagrams. Calculates the
|
|
* pseudo header checkum and calls the generic input handler
|
|
* Parameters: dst - destination cache entry used by the UDP datagram
|
|
* pkt - UDP datagram
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udp_ip4_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt)
|
|
{
|
|
#ifdef IPCOM_USE_HW_CHECKSUM_RX
|
|
if (IP_UNLIKELY(IP_BIT_ISFALSE(pkt->flags, IPCOM_PKT_FLAG_HW_CHECKSUM)))
|
|
#endif
|
|
pkt->chk += ipnet_ip4_pseudo_header_checksum_update(&dst->flow_spec.from.in,
|
|
&dst->flow_spec.to.in,
|
|
IP_IPPROTO_UDP,
|
|
(Ip_u16)ipcom_pkt_get_length(pkt));
|
|
ipnet_udp_rx(dst,
|
|
pkt,
|
|
(Ipnet_sock_lookup_f) ipnet_sock_ip4_lookup);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udplite_ip4_rx
|
|
*===========================================================================
|
|
* Description: Input handler for IPv4 UDP-Lite datagrams. Calculates the
|
|
* pseudo header checkum and calls the generic input handler
|
|
* Parameters: dst - destination cache entry used by the UDP-Lite datagram
|
|
* pkt - UDP-Lite datagram
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udplite_ip4_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt)
|
|
{
|
|
/*
|
|
* RFC3828, chapter 3.2
|
|
* The UDP-Lite pseudo header is always calculated as if the whole
|
|
* packet was covered by the checksum.
|
|
*/
|
|
pkt->chk += ipnet_ip4_pseudo_header_checksum_update(&dst->flow_spec.from.in,
|
|
&dst->flow_spec.to.in,
|
|
IP_IPPROTO_UDPLITE,
|
|
(Ip_u16)ipcom_pkt_get_length(pkt));
|
|
ipnet_udplite_rx(dst,
|
|
pkt,
|
|
(Ipnet_sock_lookup_f) ipnet_sock_ip4_lookup);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef IPCOM_USE_INET6
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udp_ip6_rx
|
|
*===========================================================================
|
|
* Description: Input handler for IPv6 UDP datagrams. Calculates the
|
|
* pseudo header checkum and calls the generic input handler
|
|
* Parameters: dst - destination cache entry used by the UDP datagram
|
|
* pkt - UDP datagram
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udp_ip6_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt)
|
|
{
|
|
pkt->chk += ipnet_ip6_pseudo_header_checksum_update(&dst->flow_spec.from.in6,
|
|
&dst->flow_spec.to.in6,
|
|
IP_IPPROTO_UDP,
|
|
ipcom_pkt_get_length(pkt));
|
|
ipnet_udp_rx(dst,
|
|
pkt,
|
|
(Ipnet_sock_lookup_f) ipnet_sock_ip6_lookup);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_udplite_ip6_rx
|
|
*===========================================================================
|
|
* Description: Input handler for IPv6 UDP-Lite datagrams. Calculates the
|
|
* pseudo header checkum and calls the generic input handler
|
|
* Parameters: dst - destination cache entry used by the UDP-Lite datagram
|
|
* pkt - UDP-Lite datagram
|
|
* socklookup - domain specific socket lookup function.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_udplite_ip6_rx(Ipnet_dst_cache *dst,
|
|
Ipcom_pkt *pkt)
|
|
{
|
|
/*
|
|
* RFC3828, chapter 3.2
|
|
* The UDP-Lite pseudo header is always calculated as if the whole
|
|
* packet was covered by the checksum.
|
|
*/
|
|
pkt->chk += ipnet_ip6_pseudo_header_checksum_update(&dst->flow_spec.from.in6,
|
|
&dst->flow_spec.to.in6,
|
|
IP_IPPROTO_UDPLITE,
|
|
ipcom_pkt_get_length(pkt));
|
|
ipnet_udplite_rx(dst,
|
|
pkt,
|
|
(Ipnet_sock_lookup_f) ipnet_sock_ip6_lookup);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 GLOBAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_udp_register
|
|
*===========================================================================
|
|
* Description: Registers the UDP protocol for IPv4 and/or IPv6.
|
|
* Parameters:
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipnet_sock_udp_register(void)
|
|
{
|
|
Ipnet_sock_udp_ops *ops;
|
|
int domains[] = {
|
|
#ifdef IPCOM_USE_INET
|
|
IP_AF_INET,
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
IP_AF_INET6,
|
|
#endif
|
|
-1
|
|
};
|
|
int protos[] = { 0, IP_IPPROTO_UDP, IP_IPPROTO_UDPLITE, -1 };
|
|
int *proto;
|
|
int *domain;
|
|
int ret;
|
|
|
|
for (proto = protos; *proto >= 0; proto++)
|
|
{
|
|
for (domain = domains; *domain >= 0; domain++)
|
|
{
|
|
ops = ipcom_calloc(1, sizeof(*ops));
|
|
if (ops == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(CRIT, "Could not register the UDP protocol, out of memory");
|
|
IP_PANIC();
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
switch (*domain)
|
|
{
|
|
#ifdef IPCOM_USE_INET
|
|
case IP_AF_INET:
|
|
ipnet_sock_ip4_get_ops(&ops->inet);
|
|
break;
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
case IP_AF_INET6:
|
|
ipnet_sock_ip6_get_ops(&ops->inet);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ops->network_bind = ops->inet.network_bind;
|
|
ops->inet.network_bind = ipnet_sock_udp_bind;
|
|
|
|
ops->network_init = ops->inet.sock.init;
|
|
ops->network_connect = ops->inet.sock.connect;
|
|
ops->inet.sock.init = ipnet_sock_udp_init;
|
|
ops->inet.sock.connect = ipnet_sock_udp_connect;
|
|
|
|
ops->inet.sock.type = IP_SOCK_DGRAM;
|
|
ops->inet.sock.proto = *proto;
|
|
ops->inet.sock.allow_async_send = IP_TRUE;
|
|
ops->inet.sock.usr_recv = ipnet_usr_sock_recvmsg;
|
|
if (*proto == IP_IPPROTO_UDPLITE)
|
|
ops->inet.sock.send = ipnet_sock_udplite_tx;
|
|
else
|
|
ops->inet.sock.send = ipnet_sock_udp_tx;
|
|
ops->inet.sock.hdr_space += IPNET_UDP_HDR_SIZE;
|
|
|
|
ret = ipnet_sock_register_ops(&ops->inet.sock);
|
|
if (ret < 0)
|
|
{
|
|
ipcom_free (ops);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Register an IP protocol handler for UDP into the network layer(s)
|
|
*/
|
|
#ifdef IPCOM_USE_INET
|
|
ipnet_ip4_reg_transport_layer(IP_IPPROTO_UDP, ipnet_udp_ip4_rx);
|
|
ipnet_ip4_reg_transport_layer(IP_IPPROTO_UDPLITE, ipnet_udplite_ip4_rx);
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
ipnet_ip6_reg_transport_layer(IP_IPPROTO_UDP, ipnet_udp_ip6_rx);
|
|
ipnet_ip6_reg_transport_layer(IP_IPPROTO_UDPLITE, ipnet_udplite_ip6_rx);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 11 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|
|
|