1697 lines
56 KiB
C
1697 lines
56 KiB
C
/*
|
|
******************************************************************************
|
|
* SOURCE FILE
|
|
*
|
|
* Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipnet_routesock.c,v $ $Revision: 1.159.10.10 $
|
|
* $Source: /home/interpeak/CVSRoot/ipnet2/src/ipnet_routesock.c,v $
|
|
* $Author: jwang9 $
|
|
* $State: Exp $ $Locker: $
|
|
*
|
|
* INTERPEAK_COPYRIGHT_STRING
|
|
* Design and implementation by Kenneth Jonsson <kenneth@interpeak.se>
|
|
******************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifndef IPCOM_KERNEL
|
|
#define IPCOM_KERNEL
|
|
#endif
|
|
#include "ipnet_config.h"
|
|
|
|
#ifdef IPNET_USE_ROUTESOCK
|
|
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_syslog.h>
|
|
|
|
#include "ipnet_h.h"
|
|
#include "ipnet_loopback.h"
|
|
#include "ipnet_route.h"
|
|
#include "ipnet_routesock.h"
|
|
#include "ipnet_usr_sock.h"
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#define IPNET_ROUTESOCK_INIT_PKT(pkt, size) \
|
|
do { \
|
|
(pkt)->start = (int) ((Ip_ptrdiff_t) (pkt)->data & 0x3); \
|
|
if ((pkt)->start) \
|
|
(pkt)->start = 4 - (pkt)->start; \
|
|
(pkt)->end = (int) (size) + pkt->start; \
|
|
} while ((0))
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 6 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
IP_STATIC int ipnet_routesock_input(Ipcom_pkt *pkt);
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 9 LOCAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_route_lookup
|
|
*===========================================================================
|
|
* Description: Returns the route socket matching the parametres. Source is
|
|
* always this host and the destination is the peer.
|
|
* Parameters: vr - The virtual route to use.
|
|
* version - The protocol version for the routing packet.
|
|
* Returns: A pointer to the matching socket or IP_NULL if no socket match.
|
|
*
|
|
*/
|
|
IP_STATIC Ipnet_socket *
|
|
ipnet_sock_route_lookup(Ip_u16 vr, int version,
|
|
IP_CONST void *unused1, Ip_u16 unused2,
|
|
IP_CONST void *unused3, Ip_u16 unused4)
|
|
{
|
|
Ipnet_socket_lookup_key key;
|
|
|
|
(void)unused1;
|
|
(void)unused2;
|
|
(void)unused3;
|
|
(void)unused4;
|
|
|
|
ipcom_memset(&key, 0, sizeof(key));
|
|
key.domain = IP_AF_ROUTE;
|
|
key.proto = (Ip_u16) version;
|
|
key.vr = vr;
|
|
return ipnet_sock_get(&key, IP_NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_route_init
|
|
*===========================================================================
|
|
* Description: The route domain socket initialization.
|
|
* Parameters: sock - The socket that is beeing created.
|
|
* user_context - not used.
|
|
* Returns: 0 = success, <0 = error code
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_route_init(Ipnet_socket *sock, Ip_bool user_context)
|
|
{
|
|
IPCOM_UNUSED_ARG(user_context);
|
|
ipnet->num_route_sock++;
|
|
ipnet_sock_set_reachable(sock);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_route_destroy
|
|
*===========================================================================
|
|
* Description: Destroys the route socket.
|
|
* Parameters: sock - The socket beeing closed.
|
|
* Returns: 0 = success
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_sock_route_destroy(Ipnet_socket *sock)
|
|
{
|
|
ipnet_sock_unbind(sock);
|
|
ipnet->num_route_sock--;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_route_send
|
|
*===========================================================================
|
|
* Description: Inputs a rouet socket message to the stack.
|
|
* Parameters: sock - The socket to use when sending.
|
|
* msg - The send parameters.
|
|
* pkt - The packet to send.
|
|
* Returns: >=0 = application bytes sent, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_sock_route_send(Ipnet_socket *sock,
|
|
IP_CONST struct Ip_msghdr *msg,
|
|
Ipcom_pkt *pkt)
|
|
{
|
|
int length, ret;
|
|
|
|
IPCOM_UNUSED_ARG(sock);
|
|
IPCOM_UNUSED_ARG(msg);
|
|
|
|
length = ipcom_pkt_get_length(pkt);
|
|
ret = ipnet_routesock_input(pkt);
|
|
return ret < 0 ? ret : length;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_output
|
|
*===========================================================================
|
|
* Description: Write the packet to all affected sockets.
|
|
* Parameters: pkt - The route socket to write.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_output(Ipcom_pkt *pkt)
|
|
{
|
|
struct Ipnet_rt_msghdr *rtm;
|
|
|
|
rtm = ipcom_pkt_get_data(pkt, 0);
|
|
if (rtm->rtm_type <= IPNET_RTM_LOCK)
|
|
pkt->ifindex = (Ip_u32)rtm->rtm_pid;
|
|
ip_assert(ipcom_pkt_get_length(pkt) == rtm->rtm_msglen);
|
|
|
|
return ipnet_raw_input(pkt, IP_TRUE, 0, IP_NULL, 0, IP_NULL, 0,
|
|
(Ipnet_sock_lookup_f) ipnet_sock_route_lookup);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_get_addrs_from_rt_msg
|
|
*===========================================================================
|
|
* Description: Extracts the pointers for all addresses in the message.
|
|
* Parameters: rtm - The message to extract from.
|
|
* addr - Will hold the pointers to the addresses.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_get_addrs_from_rt_msg(struct Ipnet_rt_msghdr *rtm,
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX])
|
|
{
|
|
Ip_size_t size;
|
|
struct Ip_sockaddr *rtm_addr;
|
|
unsigned i;
|
|
Ip_size_t sa_len;
|
|
|
|
size = rtm->rtm_msglen;
|
|
|
|
if (size < sizeof(struct Ipnet_rt_msghdr))
|
|
/* Cannot be a valid Ipnet_rt_msghdr structure */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
rtm_addr = (struct Ip_sockaddr *) (rtm + 1);
|
|
size -= sizeof(struct Ipnet_rt_msghdr);
|
|
|
|
for (i = 0; i < IPNET_RTAX_MAX; i++)
|
|
{
|
|
if (IP_BIT_ISFALSE(rtm->rtm_addrs, IP_BIT_UVAL(i)))
|
|
{
|
|
addr[i] = IP_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (size < sizeof(Ip_u32))
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
addr[i] = rtm_addr;
|
|
sa_len = IPCOM_SA_LEN_GET(addr[i]) ? IP_ROUNDUP(IPCOM_SA_LEN_GET(addr[i]), sizeof(Ip_u32)) : sizeof(Ip_u32);
|
|
rtm_addr = (struct Ip_sockaddr *) ((Ip_u8*) rtm_addr + sa_len);
|
|
size -= sa_len;
|
|
}
|
|
return 0;
|
|
}
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_get_flowspec
|
|
*===========================================================================
|
|
* Description: Get flowspec according to the route information.
|
|
* Parameters: [in] rtm - A route socket message.
|
|
* [out] flow_spec - Will contain flowspec information.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_get_flowspec(Ip_u16 vr,
|
|
struct Ipnet_rt_msghdr *rtm,
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX],
|
|
Ipnet_flow_spec *flow_spec)
|
|
{
|
|
int domain;
|
|
|
|
if (addr == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
if (addr[IPNET_RTAX_DST] == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
flow_spec->vr = vr;
|
|
flow_spec->egress_ifindex = rtm->rtm_index;
|
|
|
|
domain = addr[IPNET_RTAX_DST]->sa_family;
|
|
#ifdef IPCOM_USE_INET
|
|
if (domain == IP_AF_INET)
|
|
{
|
|
struct Ip_in_addr *dst;
|
|
flow_spec->zone_id = 0;
|
|
dst = &((struct Ip_sockaddr_in *) addr[IPNET_RTAX_DST])->sin_addr;
|
|
ipcom_memcpy(&flow_spec->to.in, dst, sizeof(struct Ip_in_addr));
|
|
}
|
|
#endif
|
|
|
|
#ifdef IPCOM_USE_INET6
|
|
if (domain == IP_AF_INET6)
|
|
{
|
|
struct Ip_sockaddr_in6 *sin6_dst = (struct Ip_sockaddr_in6 *) addr[IPNET_RTAX_DST];
|
|
|
|
flow_spec->zone_id = sin6_dst->sin6_scope_id;
|
|
ipcom_memcpy(&flow_spec->to.in6, sin6_dst, sizeof(struct Ip_in6_addr));
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_get_rt
|
|
*===========================================================================
|
|
* Description: Get the route corresponding the information in the 'rtm' message.
|
|
* Parameters: [in] rtm - A route socket message.
|
|
* [out] re - Will contain the route.
|
|
* [out] domain - Will contain the domain.
|
|
* Returns: IPNET_ROUTE_PERFECT_MATCH = perfect match,
|
|
* IPNET_ROUTE_MASK_MATCH = mask match,
|
|
* <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_get_rt(Ip_u16 vr,
|
|
struct Ipnet_rt_msghdr *rtm,
|
|
Ipnet_route_entry **re,
|
|
int *domain,
|
|
Ip_bool use_gw)
|
|
{
|
|
int ret;
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX];
|
|
|
|
ip_assert(re != IP_NULL);
|
|
|
|
*re = IP_NULL;
|
|
|
|
ret = ipnet_routesock_get_addrs_from_rt_msg(rtm, addr);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (addr[IPNET_RTAX_DST] == IP_NULL)
|
|
/* Cannot be a valid Ipnet_rt_msghdr structure */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
*domain = addr[IPNET_RTAX_DST]->sa_family;
|
|
#ifdef IPCOM_USE_INET
|
|
if (*domain == IP_AF_INET)
|
|
{
|
|
struct Ip_in_addr *dst;
|
|
struct Ip_in_addr *mask = IP_NULL;
|
|
|
|
dst = &((struct Ip_sockaddr_in *) addr[IPNET_RTAX_DST])->sin_addr;
|
|
if (addr[IPNET_RTAX_NETMASK])
|
|
mask = &((struct Ip_sockaddr_in *) addr[IPNET_RTAX_NETMASK])->sin_addr;
|
|
|
|
ret = ipnet_route_raw_lookup2(IP_AF_INET,
|
|
vr,
|
|
rtm->rtm_table,
|
|
IPNET_RTL_FLAG_DONTCLONE,
|
|
dst,
|
|
0,
|
|
mask,
|
|
use_gw ? addr[IPNET_RTAX_GATEWAY] : IP_NULL,
|
|
rtm->rtm_index,
|
|
re);
|
|
}
|
|
else
|
|
#endif /* IPCOM_USE_INET */
|
|
#ifdef IPCOM_USE_INET6
|
|
if (*domain == IP_AF_INET6)
|
|
{
|
|
struct Ip_sockaddr_in6 *sin6_dst = (struct Ip_sockaddr_in6 *) addr[IPNET_RTAX_DST];
|
|
struct Ip_in6_addr *mask = IP_NULL;
|
|
|
|
if (addr[IPNET_RTAX_NETMASK])
|
|
mask = &((struct Ip_sockaddr_in6 *) addr[IPNET_RTAX_NETMASK])->sin6_addr;
|
|
|
|
ipnet_ip6_extract_scoped_addr(addr[IPNET_RTAX_GATEWAY], rtm->rtm_index);
|
|
|
|
ret = ipnet_route_raw_lookup2(IP_AF_INET6,
|
|
vr,
|
|
rtm->rtm_table,
|
|
IPNET_RTL_FLAG_DONTCLONE,
|
|
&sin6_dst->sin6_addr,
|
|
sin6_dst->sin6_scope_id,
|
|
mask,
|
|
use_gw ? addr[IPNET_RTAX_GATEWAY] : IP_NULL,
|
|
rtm->rtm_index,
|
|
re);
|
|
}
|
|
else
|
|
#endif /* IPCOM_USE_INET6 */
|
|
{
|
|
return -IP_ERRNO_EAFNOSUPPORT;
|
|
}
|
|
|
|
if (ret < 0)
|
|
*re = IP_NULL;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_create_addrs
|
|
*===========================================================================
|
|
* Description: Construct a rtm_addrs value from a route entry.
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_routesock_create_addrs(int domain, Ipnet_route_entry *rt, Ip_size_t *size, Ip_u32 *addrs)
|
|
{
|
|
Ip_size_t domain_addr_size;
|
|
|
|
ip_assert(rt);
|
|
ip_assert(size);
|
|
|
|
if (domain == IP_AF_INET)
|
|
domain_addr_size = sizeof(struct Ip_sockaddr_in);
|
|
else
|
|
domain_addr_size = sizeof(struct Ip_sockaddr_in6);
|
|
|
|
*size = (sizeof(struct Ipnet_rt_msghdr) + domain_addr_size);
|
|
*addrs = IPNET_RTA_DST;
|
|
|
|
if (rt->netif)
|
|
{
|
|
*size += (ipnet_if_to_sockaddr_dl(rt->netif, IP_NULL) + domain_addr_size);
|
|
*addrs |= IPNET_RTA_IFA | IPNET_RTA_IFP;
|
|
}
|
|
|
|
if (rt->gateway)
|
|
{
|
|
*size += IPCOM_SA_LEN_GET(rt->gateway);
|
|
IP_BIT_SET(*addrs, IPNET_RTA_GATEWAY);
|
|
}
|
|
|
|
if (IP_BIT_ISSET(rt->hdr.flags, IPNET_RTF_MASK))
|
|
{
|
|
if (rt->hdr.mask != IP_NULL)
|
|
{
|
|
*size += (domain == IP_AF_INET
|
|
? sizeof(struct Ip_sockaddr_in)
|
|
: sizeof(struct Ip_sockaddr_in6));
|
|
IP_BIT_SET(*addrs, IPNET_RTA_NETMASK);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_is_valid_addr
|
|
*===========================================================================
|
|
* Description: Determines if the address is OK to dump via routesock.
|
|
* Parameters: domain - address domain
|
|
* addr - The address entry.
|
|
* Returns: IP_TRUE, IP_FALSE
|
|
*
|
|
*/
|
|
|
|
IP_STATIC Ip_bool
|
|
ipnet_routesock_is_valid_addr(int domain, void *addr)
|
|
{
|
|
#ifdef IPQUAGGA
|
|
Ip_bool ret = IP_FALSE;
|
|
|
|
if (domain == IP_AF_INET)
|
|
{
|
|
Ipnet_ip4_addr_entry *addr4 = (Ipnet_ip4_addr_entry *)addr;
|
|
|
|
if (addr4->type == IPNET_ADDR_TYPE_UNICAST)
|
|
ret = IP_TRUE;
|
|
}
|
|
else if (domain == IP_AF_INET6)
|
|
{
|
|
Ipnet_ip6_addr_entry *addr6 = (Ipnet_ip6_addr_entry *)addr;
|
|
|
|
if (addr6->type == IPNET_ADDR_TYPE_UNICAST)
|
|
ret = IP_TRUE;
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
(void)domain;
|
|
(void)addr;
|
|
|
|
return IP_TRUE;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_output_addr
|
|
*===========================================================================
|
|
* Description: Create a route socket message about an address.
|
|
* Parameters: type - The type of message to create
|
|
* (IPNET_RTM_NEWADDR or IPNET_RTM_DELADDR).
|
|
* netif - The affected interface.
|
|
* domain - The address domain.
|
|
* addr - The address entry.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipnet_routesock_output_addr(int type, Ipnet_netif *netif, int domain, void *addr)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
Ip_size_t size = 0;
|
|
struct Ipnet_ifa_msghdr *ifa;
|
|
|
|
if (!ipnet_routesock_is_valid_addr(domain, addr))
|
|
return;
|
|
|
|
(void)ipnet_sysctl_if_add_addrs_one(IP_NULL, netif, domain, addr, &size);
|
|
|
|
pkt = ipcom_pkt_malloc(size, IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return;
|
|
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, size);
|
|
ifa = ipcom_pkt_get_data(pkt, 0);
|
|
(void)ipnet_sysctl_if_add_addrs_one(ifa, netif, domain, addr, &size);
|
|
ifa->ifam_type = (Ip_u8)type;
|
|
|
|
pkt->vr_index = netif->vr_index;
|
|
(void)ipnet_routesock_output(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_output_rt
|
|
*===========================================================================
|
|
* Description: Create a message and send it to all routing sockets.
|
|
* Parameters:
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_output_rt(Ip_u8 type,
|
|
Ip_pid_t pid,
|
|
Ip_u32 seq,
|
|
Ip_u16 vr,
|
|
Ip_u32 table,
|
|
Ipnet_route_entry *rt,
|
|
Ip_size_t size,
|
|
Ip_u32 addrs,
|
|
int domain,
|
|
int err)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
struct Ipnet_rt_msghdr *rtm_reply;
|
|
struct Ip_sockaddr *rtm_addr;
|
|
struct Ip_sockaddr *addr;
|
|
int i;
|
|
Ip_u8 rtm_addr_buf[IP_INET6_ADDRSTRLEN];
|
|
|
|
pkt = ipcom_pkt_malloc(size + 3,
|
|
IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return -IP_ERRNO_ENOBUFS;
|
|
pkt->vr_index = vr;
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
|
|
/* Make sure that 'rtm_reply' is on a 4-byte boundary */
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, 0);
|
|
rtm_reply = ipcom_pkt_get_data(pkt, 0);
|
|
|
|
ipcom_memset(rtm_reply, 0, size);
|
|
rtm_reply->rtm_version = IPNET_RTM_VERSION;
|
|
rtm_reply->rtm_type = type;
|
|
rtm_reply->rtm_errno = (Ip_u32) -err;
|
|
rtm_reply->rtm_table = table;
|
|
rtm_reply->rtm_pid = pid;
|
|
rtm_reply->rtm_seq = seq;
|
|
rtm_addr = (struct Ip_sockaddr *) (rtm_reply + 1);
|
|
|
|
if (err == 0)
|
|
{
|
|
/* Reset the PID and sequence number of the last operation */
|
|
rt->pid = 0;
|
|
rt->seq = 0;
|
|
|
|
rtm_reply->rtm_index = (rt->netif ? rt->netif->ipcom.ifindex : 0);
|
|
ipcom_memcpy(&rtm_reply->rtm_rmx, &rt->metrics, sizeof(struct Ipnet_rt_metrics));
|
|
if (rtm_reply->rtm_rmx.rmx_expire == IPCOM_ADDR_INFINITE)
|
|
rtm_reply->rtm_rmx.rmx_expire = 0;
|
|
else
|
|
rtm_reply->rtm_rmx.rmx_expire = rtm_reply->rtm_rmx.rmx_expire - IPNET_SECONDS_ABS;
|
|
rtm_reply->rtm_inits = ~0u;
|
|
|
|
rtm_reply->rtm_flags = rt->hdr.flags & IPNET_RTF_PUBLIC_FLAGS;
|
|
rtm_reply->rtm_use = rt->use;
|
|
|
|
for (i = 0; i < IPNET_RTAX_MAX; i++)
|
|
{
|
|
if (IP_BIT_ISFALSE(addrs, IP_BIT_UVAL(i)))
|
|
continue;
|
|
|
|
addr = (struct Ip_sockaddr *) &rtm_addr_buf[0];
|
|
switch (i)
|
|
{
|
|
case IPNET_RTAX_DST:
|
|
ipnet_route_key_to_sockaddr(domain, rt->hdr.key, addr);
|
|
break;
|
|
case IPNET_RTAX_GATEWAY:
|
|
addr = rt->gateway;
|
|
break;
|
|
case IPNET_RTAX_NETMASK:
|
|
ipnet_route_key_to_sockaddr(domain, rt->hdr.mask, addr);
|
|
break;
|
|
case IPNET_RTAX_IFP:
|
|
(void)ipnet_if_to_sockaddr_dl(rt->netif, (struct Ip_sockaddr_dl *) addr);
|
|
break;
|
|
case IPNET_RTAX_IFA:
|
|
#ifdef IPCOM_USE_INET
|
|
if (domain == IP_AF_INET)
|
|
{
|
|
struct Ipnet_ipv4_key *ipv4_dst = (struct Ipnet_ipv4_key *) rt->hdr.key;
|
|
IP_CONST struct Ip_in_addr *in;
|
|
|
|
in = ipnet_ip4_get_src_addr(vr, &ipv4_dst->addr, rt, rt->netif);
|
|
if (in == IP_NULL)
|
|
{
|
|
size -= sizeof(struct Ip_sockaddr_in);
|
|
IP_BIT_CLR(addrs, 1 << i);
|
|
continue;
|
|
}
|
|
ipnet_route_key_to_sockaddr(IP_AF_INET, in, addr);
|
|
}
|
|
#endif /* IPCOM_USE_INET */
|
|
#ifdef IPCOM_USE_INET6
|
|
if (domain == IP_AF_INET6)
|
|
{
|
|
struct Ipnet_ipv6_key *ipv6_dst = (struct Ipnet_ipv6_key *) rt->hdr.key;
|
|
struct Ipnet_ipv6_key ipv6_ifa;
|
|
IP_CONST struct Ip_in6_addr *in6;
|
|
|
|
in6 = ipnet_ip6_get_src_addr(vr,
|
|
&ipv6_dst->addr,
|
|
(int) rt->netif->ipcom.ifindex,
|
|
rt,
|
|
IP_NULL,
|
|
0);
|
|
|
|
if (in6 == IP_NULL)
|
|
{
|
|
size -= sizeof(struct Ip_sockaddr_in6);
|
|
IP_BIT_CLR(addrs, 1 << i);
|
|
continue;
|
|
}
|
|
IPNET_IP6_SET_ADDR(&ipv6_ifa.addr, in6);
|
|
if (ipnet_ip6_is_scoped_addr(in6))
|
|
ipv6_ifa.scope_id = ipnet_ip6_get_zone(rt->netif->ipcom.ifindex,
|
|
in6);
|
|
else
|
|
ipv6_ifa.scope_id = 0;
|
|
ipnet_route_key_to_sockaddr(IP_AF_INET6, &ipv6_ifa, addr);
|
|
}
|
|
#endif /* IPCOM_USE_INET6 */
|
|
break;
|
|
default:
|
|
IP_PANIC();
|
|
return 0;
|
|
}
|
|
if(IP_NULL != addr)
|
|
{
|
|
ipcom_memcpy(rtm_addr, addr, IPCOM_SA_LEN_GET(addr));
|
|
rtm_addr = (struct Ip_sockaddr *) ((Ip_u8 *) rtm_addr + IPCOM_SA_LEN_GET(addr));
|
|
}
|
|
}
|
|
}
|
|
rtm_reply->rtm_addrs = addrs;
|
|
rtm_reply->rtm_msglen = (Ip_u16) size;
|
|
pkt->end = pkt->start + (int) size;
|
|
(void)ipnet_routesock_output(pkt);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_output_get
|
|
*===========================================================================
|
|
* Description: Respond on a IPNET_RTM_GET request.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_GET.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_output_get(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
Ip_size_t size;
|
|
int err = 0;
|
|
int ret;
|
|
Ip_u32 addrs;
|
|
int domain;
|
|
Ipnet_route_entry *re;
|
|
Ipnet_flow_spec flow_spec;
|
|
struct Ip_sockaddr *saddr[IPNET_RTAX_MAX];
|
|
|
|
ipcom_memset(&flow_spec, 0, sizeof(Ipnet_flow_spec));
|
|
ret = ipnet_routesock_get_addrs_from_rt_msg(rtm, saddr);
|
|
if (ret != 0)
|
|
{
|
|
addrs = 0;
|
|
size = sizeof(struct Ipnet_rt_msghdr);
|
|
return ipnet_routesock_output_rt(IPNET_RTM_GET,
|
|
rtm->rtm_pid,
|
|
rtm->rtm_seq,
|
|
vr,
|
|
rtm->rtm_table,
|
|
re, size, addrs,
|
|
domain, ret);
|
|
}
|
|
|
|
ret = ipnet_routesock_get_rt(vr, rtm, &re, &domain, IP_TRUE);
|
|
if (ret == IPNET_ROUTE_MASK_MATCH &&
|
|
saddr[IPNET_RTAX_GATEWAY] == IP_NULL &&
|
|
saddr[IPNET_RTAX_NETMASK] == IP_NULL)
|
|
{
|
|
err = ipnet_routesock_get_flowspec(vr, rtm, saddr, &flow_spec);
|
|
if (err == 0)
|
|
{
|
|
err = ipnet_route_lookup_ecmp(domain,
|
|
flow_spec.vr,
|
|
IPNET_RTL_FLAG_DONTCLONE,
|
|
&flow_spec.to,
|
|
flow_spec.zone_id,
|
|
flow_spec.egress_ifindex,
|
|
IP_NULL,
|
|
&flow_spec,
|
|
&re);
|
|
|
|
}
|
|
|
|
}
|
|
if (ret < 0 || err < 0)
|
|
{
|
|
addrs = 0;
|
|
size = sizeof(struct Ipnet_rt_msghdr);
|
|
}
|
|
else
|
|
{
|
|
ret = 0;
|
|
ipnet_routesock_create_addrs(domain, re, &size, &addrs);
|
|
}
|
|
|
|
return ipnet_routesock_output_rt(IPNET_RTM_GET,
|
|
rtm->rtm_pid,
|
|
rtm->rtm_seq,
|
|
vr,
|
|
rtm->rtm_table,
|
|
re, size, addrs,
|
|
domain, ret);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_output_err_reply
|
|
*===========================================================================
|
|
* Description: Creates and sends a reply to a request that is a copy of
|
|
* the request with possibly a changed rtm_errno value.
|
|
* Parameters: vr - The virtual router that can see this packet.
|
|
* rtm - The message to respond to.
|
|
* err - Value to place in rtm_errno.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_output_err_reply(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm, int err)
|
|
{
|
|
int retval;
|
|
struct Ipnet_rt_msghdr *rtm_reply;
|
|
Ipcom_pkt *pkt;
|
|
|
|
pkt = ipcom_pkt_malloc(rtm->rtm_msglen + 3u,
|
|
IP_FLAG_FC_BLOCKOK | IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return -IP_ERRNO_ENOBUFS;
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, rtm->rtm_msglen);
|
|
pkt->vr_index = vr;
|
|
rtm_reply = ipcom_pkt_get_data(pkt, 0);
|
|
|
|
ipcom_memcpy(rtm_reply, rtm, rtm->rtm_msglen);
|
|
if (rtm_reply->rtm_pid == 0)
|
|
rtm_reply->rtm_pid = ipnet_get_effective_pid();
|
|
rtm_reply->rtm_errno = (Ip_u32) err;
|
|
|
|
retval = ipnet_routesock_output(pkt);
|
|
if(retval < 0)
|
|
return retval;
|
|
return -err;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_add_rt
|
|
*===========================================================================
|
|
* Description: Act on a IPNET_RTM_ADD request.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_ADD.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_add_rt(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
struct Ipnet_route_add_param param;
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX];
|
|
int ret;
|
|
#ifdef IPCOM_USE_INET
|
|
struct Ip_in_addr mask;
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
struct Ipnet_ipv6_key dst6;
|
|
struct Ipnet_ipv6_key mask6;
|
|
#endif /* IPCOM_USE_INET6 */
|
|
|
|
ipcom_memset(¶m, 0, sizeof(struct Ipnet_route_add_param));
|
|
if (ipnet_routesock_get_addrs_from_rt_msg(rtm, addr) < 0)
|
|
/* Invalid message */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
if (addr[IPNET_RTAX_DST] == IP_NULL)
|
|
/* No destination */
|
|
return ipnet_routesock_output_err_reply(vr, rtm, IP_ERRNO_EDESTADDRREQ);
|
|
|
|
if (addr[IPNET_RTAX_NETMASK] != IP_NULL &&
|
|
addr[IPNET_RTAX_DST]->sa_family != addr[IPNET_RTAX_NETMASK]->sa_family)
|
|
/* The address family of the mask does not match the destination */
|
|
return ipnet_routesock_output_err_reply(vr, rtm, IP_ERRNO_EINVAL);
|
|
|
|
param.netif = ipnet_if_indextonetif((Ip_u16)(ipnet_has_priv(IPNET_PRIV_CHVR)
|
|
? IPCOM_VR_ANY
|
|
: vr),
|
|
rtm->rtm_index);
|
|
param.flags = rtm->rtm_flags;
|
|
param.pid = rtm->rtm_pid;
|
|
param.seq = rtm->rtm_seq;
|
|
param.metrics = &rtm->rtm_rmx;
|
|
if (rtm->rtm_rmx.rmx_expire == 0)
|
|
rtm->rtm_rmx.rmx_expire = IPCOM_ADDR_INFINITE;
|
|
|
|
if (IP_BIT_ISSET(param.flags, IPNET_RTF_GATEWAY | IPNET_RTF_TUNNELEND))
|
|
{
|
|
if (addr[IPNET_RTAX_GATEWAY] == IP_NULL)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, IP_ERRNO_EINVAL);
|
|
}
|
|
param.gateway = addr[IPNET_RTAX_GATEWAY];
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
if (addr[IPNET_RTAX_DST]->sa_family == IP_AF_INET)
|
|
{
|
|
param.key = &((struct Ip_sockaddr_in *) addr[IPNET_RTAX_DST])->sin_addr;
|
|
if (addr[IPNET_RTAX_NETMASK] != IP_NULL)
|
|
{
|
|
struct Ip_sockaddr_in *sin_mask = (struct Ip_sockaddr_in *) addr[IPNET_RTAX_NETMASK];
|
|
|
|
ipcom_memset(&mask, 0, sizeof(mask));
|
|
if (IPCOM_SA_LEN_GET(sin_mask)
|
|
> (unsigned) ip_offsetof(struct Ip_sockaddr_in, sin_addr))
|
|
ipcom_memcpy(&mask,
|
|
&sin_mask->sin_addr,
|
|
IP_MIN(sizeof(struct Ip_in_addr),
|
|
IPCOM_SA_LEN_GET(sin_mask)
|
|
- (unsigned) ip_offsetof(struct Ip_sockaddr_in, sin_addr)));
|
|
param.netmask = &mask;
|
|
}
|
|
}
|
|
#endif /* IPCOM_USE_INET */
|
|
#ifdef IPCOM_USE_INET6
|
|
if (addr[IPNET_RTAX_DST]->sa_family == IP_AF_INET6)
|
|
{
|
|
ipnet_route_sockaddr_to_key(IP_AF_INET6, addr[IPNET_RTAX_DST], &dst6);
|
|
param.key = &dst6;
|
|
if (addr[IPNET_RTAX_NETMASK] != IP_NULL)
|
|
{
|
|
ipnet_route_sockaddr_to_key(IP_AF_INET6, addr[IPNET_RTAX_NETMASK], &mask6);
|
|
param.netmask = &mask6;
|
|
}
|
|
}
|
|
#endif /* IPCOM_USE_INET6 */
|
|
|
|
param.domain = addr[IPNET_RTAX_DST]->sa_family;
|
|
param.vr = vr;
|
|
param.table = rtm->rtm_table;
|
|
param.no_ref_count = IP_TRUE;
|
|
ret = ipnet_route_add(¶m);
|
|
if (ret < 0)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_delete_rt
|
|
*===========================================================================
|
|
* Description: Act on a IPNET_RTM_DELETE request.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_DELETE.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_delete_rt(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX];
|
|
Ipnet_route_entry *re;
|
|
int domain;
|
|
int ret;
|
|
IP_CONST void *mask;
|
|
|
|
ret = ipnet_routesock_get_rt(vr, rtm, &re, &domain, IP_TRUE);
|
|
if (ret < 0)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
if (ret != IPNET_ROUTE_PERFECT_MATCH)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, IP_ERRNO_ESRCH);
|
|
|
|
if (ipnet_routesock_get_addrs_from_rt_msg(rtm, addr) < 0)
|
|
/* Invalid message */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
mask = IP_NULL;
|
|
if (addr[IPNET_RTAX_NETMASK] != IP_NULL)
|
|
{
|
|
#ifdef IPCOM_USE_INET
|
|
if (domain == IP_AF_INET)
|
|
mask = &((struct Ip_sockaddr_in *) addr[IPNET_RTAX_NETMASK])->sin_addr;
|
|
else
|
|
#endif /* IPCOM_USE_INET */
|
|
#ifdef IPCOM_USE_INET6
|
|
if (domain == IP_AF_INET6)
|
|
mask = &((struct Ip_sockaddr_in6 *) addr[IPNET_RTAX_NETMASK])->sin6_addr;
|
|
else
|
|
#endif /* IPCOM_USE_INET6 */
|
|
{
|
|
IP_PANIC();
|
|
}
|
|
}
|
|
ret = ipnet_route_delete2(domain,
|
|
vr,
|
|
rtm->rtm_table,
|
|
re->hdr.key,
|
|
mask,
|
|
addr[IPNET_RTAX_GATEWAY],
|
|
rtm->rtm_index,
|
|
rtm->rtm_pid,
|
|
rtm->rtm_seq,
|
|
IP_FALSE);
|
|
|
|
if (ret != 0)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, IP_ERRNO_ESRCH);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_change_rt
|
|
*===========================================================================
|
|
* Description: Act on a IPNET_RTM_CHANGE request (change metrics, gateway or flags).
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_CHANGE.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_change_rt(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
Ipnet_route_entry *rt;
|
|
int domain;
|
|
int ret;
|
|
struct Ip_sockaddr *addr[IPNET_RTAX_MAX];
|
|
Ip_u32 rtm_inits;
|
|
|
|
if (ipnet_routesock_get_addrs_from_rt_msg(rtm, addr) < 0)
|
|
/* Invalid message */
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
ret = ipnet_routesock_get_rt(vr, rtm, &rt, &domain, IP_TRUE);
|
|
if (ret != IPNET_ROUTE_PERFECT_MATCH)
|
|
{
|
|
if (addr[IPNET_RTAX_GATEWAY] != IP_NULL)
|
|
{
|
|
ret = ipnet_routesock_get_rt(vr, rtm, &rt, &domain, IP_FALSE);
|
|
if (ret != IPNET_ROUTE_PERFECT_MATCH)
|
|
{
|
|
if (ret >= 0)
|
|
ret = -IP_ERRNO_ESRCH;
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
}
|
|
if (rt->gateway == IP_NULL)
|
|
{
|
|
ret = -IP_ERRNO_ESRCH;
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
}
|
|
|
|
/* do a check on the gateway parameter */
|
|
Ipnet_route_entry *gw = IP_NULL;
|
|
ret = -IP_ERRNO_ENETUNREACH;
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
if (rt->gateway->sa_family == IP_AF_INET)
|
|
{
|
|
Ipnet_flow_spec flow_spec;
|
|
ipnet_flow_spec_from_info(&flow_spec, vr, IP_NULL,
|
|
&((struct Ip_sockaddr_in *) addr[IPNET_RTAX_DST])->sin_addr, 0, 0, IP_NULL, IP_NULL, ipnet_ip4_flow_spec_from_info);
|
|
|
|
/* Do a policy based lookup */
|
|
ret = ipnet_route_lookup_ecmp(IP_AF_INET,
|
|
vr,
|
|
IPNET_RTL_FLAG_DONTCLONE,
|
|
&((struct Ip_sockaddr_in *) addr[IPNET_RTAX_GATEWAY])->sin_addr,
|
|
0,
|
|
0,
|
|
IP_NULL,
|
|
&flow_spec,
|
|
&gw);
|
|
}
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
if (rt->gateway->sa_family == IP_AF_INET6)
|
|
{
|
|
struct Ip_sockaddr_in6 *in6_gw = (struct Ip_sockaddr_in6 *) addr[IPNET_RTAX_GATEWAY];
|
|
struct Ipnet_ipv6_key dst6;
|
|
Ipnet_flow_spec flow_spec;
|
|
ipnet_route_sockaddr_to_key(IP_AF_INET6, addr[IPNET_RTAX_DST], &dst6);
|
|
ipnet_flow_spec_from_info(&flow_spec, vr, IP_NULL, &dst6, in6_gw->sin6_scope_id, 0, IP_NULL, IP_NULL, ipnet_ip6_flow_spec_from_info);
|
|
|
|
/* Do a policy based lookup */
|
|
ret = ipnet_route_lookup_ecmp(IP_AF_INET6,
|
|
vr,
|
|
IPNET_RTL_FLAG_DONTCLONE,
|
|
&in6_gw->sin6_addr,
|
|
in6_gw->sin6_scope_id,
|
|
0,
|
|
IP_NULL,
|
|
&flow_spec,
|
|
&gw);
|
|
}
|
|
#endif
|
|
|
|
if (ret < 0)
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
|
|
/* Could not modify default gateway */
|
|
if (addr[IPNET_RTAX_GATEWAY]->sa_family == rt->gateway->sa_family)
|
|
{
|
|
ip_assert(gw != IP_NULL);
|
|
rt->netif = gw->netif;
|
|
|
|
ipcom_memcpy(rt->gateway, addr[IPNET_RTAX_GATEWAY], addr[IPNET_RTAX_GATEWAY]->sa_len);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
ret = -IP_ERRNO_ESRCH;
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -ret);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ipnet_if_indextonetif(vr, rtm->rtm_index) != IP_NULL)
|
|
rt->netif = ipnet_if_indextonetif(vr, rtm->rtm_index);
|
|
}
|
|
|
|
/* Change metric if flag is set */
|
|
rtm_inits = IP_GET_32ON16(&rtm->rtm_inits);
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_HOPCOUNT))
|
|
rt->metrics.rmx_hopcount = rtm->rtm_rmx.rmx_hopcount;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_RTT))
|
|
rt->metrics.rmx_rtt = rtm->rtm_rmx.rmx_rtt;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_RTTVAR))
|
|
rt->metrics.rmx_rttvar = rtm->rtm_rmx.rmx_rttvar;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_MTU))
|
|
rt->metrics.rmx_mtu = rtm->rtm_rmx.rmx_mtu;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_EXPIRE))
|
|
{
|
|
if (rtm->rtm_rmx.rmx_expire == 0)
|
|
rtm->rtm_rmx.rmx_expire = IPCOM_ADDR_INFINITE;
|
|
ipnet_route_set_lifetime(rt, rtm->rtm_rmx.rmx_expire);
|
|
}
|
|
|
|
#ifdef IP_PORT_OSE5
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_PROTO))
|
|
rt->metrics.rmx_proto = rtm->rtm_rmx.rmx_proto;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_USR1))
|
|
rt->metrics.rmx_usr1 = rtm->rtm_rmx.rmx_usr1;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_USR2))
|
|
rt->metrics.rmx_usr2 = rtm->rtm_rmx.rmx_usr2;
|
|
if (IP_BIT_ISSET(rtm_inits, IPNET_RTV_USR3))
|
|
rt->metrics.rmx_usr3 = rtm->rtm_rmx.rmx_usr3;
|
|
#endif /* IP_PORT_OSE5 */
|
|
|
|
rt->hdr.flags = (rtm->rtm_flags & IPNET_RTF_PUBLIC_FLAGS)
|
|
| (rt->hdr.flags & ~IPNET_RTF_PUBLIC_FLAGS);
|
|
|
|
if (rt->gateway == IP_NULL
|
|
|| (rt->gateway->sa_family != IP_AF_INET && rt->gateway->sa_family != IP_AF_INET6))
|
|
{
|
|
IP_BIT_CLR(rt->hdr.flags, IPNET_RTF_GATEWAY);
|
|
}
|
|
|
|
ipnet_route_has_changed(rt);
|
|
|
|
rt->pid = rtm->rtm_pid;
|
|
rt->seq = rtm->rtm_seq;
|
|
|
|
IPNET_ROUTESOCK(ipnet_routesock_rt_change(rt));
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_lock_rt
|
|
*===========================================================================
|
|
* Description: Act on a IPNET_RTM_LOCK request.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_LOCK.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_lock_rt(struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
IPCOM_UNUSED_ARG(rtm);
|
|
|
|
/* NOT IMPLEMENTED */
|
|
IP_PANIC2();
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_new_vr
|
|
*===========================================================================
|
|
* Description: Adds a new route table.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_NEWTAB.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_new_vr(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
int err;
|
|
|
|
err = ipnet_route_add_table(vr, rtm->rtm_table, IP_NULL, IP_NULL);
|
|
return ipnet_routesock_output_err_reply(vr, rtm, err >= 0 ? 0 : -err);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input_delete_vr
|
|
*===========================================================================
|
|
* Description: Adds a new route table.
|
|
* Parameters: rtm - The user message requesting the IPNET_RTM_DELTAB.
|
|
* Returns: >=0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input_delete_vr(Ip_u16 vr, struct Ipnet_rt_msghdr *rtm)
|
|
{
|
|
int err;
|
|
|
|
if (IPCOM_ROUTE_TABLE_IS_DEFAULT(rtm->rtm_table))
|
|
err = ipnet_route_delete_vr(vr);
|
|
else
|
|
err = ipnet_route_delete_table(vr, rtm->rtm_table);
|
|
|
|
return ipnet_routesock_output_err_reply(vr, rtm, -err);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_input
|
|
*===========================================================================
|
|
* Description: Incoming route socket request.
|
|
* Parameters: pkt - the data sent to the socket.
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipnet_routesock_input(Ipcom_pkt *pkt)
|
|
{
|
|
struct Ipnet_rs_msghdr *msghdr;
|
|
int ret;
|
|
|
|
ip_assert(((Ip_ptrdiff_t) &pkt->data[pkt->start] & 0x3) == 0);
|
|
|
|
if (ipcom_pkt_get_length(pkt) < (int) sizeof(struct Ipnet_rs_msghdr))
|
|
{
|
|
ret = -IP_ERRNO_EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
msghdr = ipcom_pkt_get_data(pkt, 0);
|
|
if (msghdr->version != IPNET_RTM_VERSION)
|
|
{
|
|
ret = -IP_ERRNO_EPROTONOSUPPORT;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ipcom_pkt_get_length(pkt) < msghdr->msglen)
|
|
{
|
|
ret = -IP_ERRNO_EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
switch (msghdr->type)
|
|
{
|
|
case IPNET_RTM_ADD:
|
|
ret = ipnet_routesock_input_add_rt(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_DELETE:
|
|
ret = ipnet_routesock_input_delete_rt(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_GET:
|
|
ret = ipnet_routesock_output_get(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_CHANGE:
|
|
ret = ipnet_routesock_input_change_rt(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_LOCK:
|
|
ret = ipnet_routesock_input_lock_rt((struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_NEWVR:
|
|
ret = ipnet_routesock_input_new_vr(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
case IPNET_RTM_DELVR:
|
|
ret = ipnet_routesock_input_delete_vr(pkt->vr_index, (struct Ipnet_rt_msghdr *) msghdr);
|
|
break;
|
|
default:
|
|
ret = -IP_ERRNO_EINVAL;
|
|
break;
|
|
}
|
|
cleanup:
|
|
ipcom_pkt_free(pkt);
|
|
return IP_MIN(ret, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 GLOBAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_addr_add
|
|
*===========================================================================
|
|
* Description: A new address was added, create a IPNET_RTM_NEWADDR.
|
|
* Parameters: netif - The interface on which the address was added.
|
|
* domain - The domain of the new address.
|
|
* addr - The address entry.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_addr_add(Ipnet_netif *netif, int domain, void *addr)
|
|
{
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
ipnet_routesock_output_addr(IPNET_RTM_NEWADDR, netif, domain, addr);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_addr_delete
|
|
*===========================================================================
|
|
* Description: A address was deleted, create a IPLTIE_RTM_DELADDR.
|
|
* Parameters: netif - The interface on which the address was deleted.
|
|
* domain - The domain of the deleted address.
|
|
* addr - The address entry.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_addr_delete(Ipnet_netif *netif, int domain, void *addr)
|
|
{
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
ipnet_routesock_output_addr(IPNET_RTM_DELADDR, netif, domain, addr);
|
|
}
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_addr_conflict
|
|
*===========================================================================
|
|
* Description: A conflict for the address was detected,
|
|
* Create a IPNET_RTM_ADDRCONFLICT.
|
|
* Parameters: netif - The interface on which the address was deleted.
|
|
* domain - The domain of the deleted address.
|
|
* addr - The address entry.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_addr_conflict(Ipnet_netif *netif, int domain, void *addr)
|
|
{
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
ipnet_routesock_output_addr(IPNET_RTM_ADDRCONFLICT, netif, domain, addr);
|
|
}
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_if_change
|
|
*===========================================================================
|
|
* Description: Status of a network interface has changed, create a IPLTIE_RTM_IFINFO.
|
|
* Parameters: netif - The interface.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_if_change(Ipnet_netif *netif)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
Ip_size_t size;
|
|
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
|
|
size = ipnet_sysctl_create_ifinfo(IP_NULL, netif);
|
|
|
|
pkt = ipcom_pkt_malloc(size, IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return;
|
|
|
|
/**/
|
|
pkt->vr_index = netif->vr_index;
|
|
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, size);
|
|
|
|
(void)ipnet_sysctl_create_ifinfo(ipcom_pkt_get_data(pkt, 0), netif);
|
|
|
|
pkt->vr_index = netif->vr_index;
|
|
(void)ipnet_routesock_output(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_if_announce
|
|
*===========================================================================
|
|
* Description: Announce arrival/departure of a network interface by creating
|
|
* and sending a IPNET_RTM_IFANNOUNCE message.
|
|
* Parameters: netif - The attached/deatached interface.
|
|
*
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_if_announce(Ipnet_netif *netif, Ip_u16 what)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
struct Ipnet_if_announcemsghdr *ifan;
|
|
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
|
|
pkt = ipcom_pkt_malloc(sizeof(struct Ipnet_if_announcemsghdr), IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return;
|
|
|
|
pkt->vr_index = netif->vr_index;
|
|
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, sizeof(struct Ipnet_if_announcemsghdr));
|
|
|
|
ifan = ipcom_pkt_get_data(pkt, 0);
|
|
ifan->ifan_msglen = sizeof(struct Ipnet_if_announcemsghdr);
|
|
ifan->ifan_version = IPNET_RTM_VERSION;
|
|
ifan->ifan_type = IPNET_RTM_IFANNOUNCE;
|
|
ifan->ifan_index = netif->ipcom.ifindex;
|
|
ipcom_strcpy(ifan->ifan_name, netif->ipcom.name);
|
|
ifan->ifan_what = what;
|
|
|
|
(void)ipnet_routesock_output(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_rt_add
|
|
*===========================================================================
|
|
* Description: A new route was added, create IPNET_RTM_ADD message.
|
|
* Parameters: rt - The added route entry
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_rt_add(Ipnet_route_entry *rt)
|
|
{
|
|
Ip_size_t size;
|
|
Ip_u32 addrs;
|
|
|
|
#ifndef IPNET_USE_ROUTESOCK_LINKINFO
|
|
if (IP_BIT_ISSET(rt->hdr.flags, IPNET_RTF_LLINFO) && rt->pid == 0)
|
|
return;
|
|
#endif
|
|
|
|
ipnet_routesock_create_addrs(IPNET_ROUTE_GET_FAMILY(rt->head), rt, &size, &addrs);
|
|
(void)ipnet_routesock_output_rt(IPNET_RTM_ADD, rt->pid, rt->seq,
|
|
IPNET_ROUTE_GET_VR(rt->head),
|
|
IPNET_ROUTE_GET_TABLE(rt->head),
|
|
rt, size, addrs, IPNET_ROUTE_GET_FAMILY(rt->head), 0);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_rt_delete
|
|
*===========================================================================
|
|
* Description: A route was deleted, create IPNET_RTM_DELETE message.
|
|
* Parameters: rt - The removed route entry
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_rt_delete(Ipnet_route_entry *rt)
|
|
{
|
|
Ip_size_t size;
|
|
Ip_u32 addrs;
|
|
|
|
#ifndef IPNET_USE_ROUTESOCK_LINKINFO
|
|
if (IP_BIT_ISSET(rt->hdr.flags, IPNET_RTF_LLINFO) && rt->pid == 0)
|
|
return;
|
|
#endif
|
|
|
|
ipnet_routesock_create_addrs(IPNET_ROUTE_GET_FAMILY(rt->head), rt, &size, &addrs);
|
|
(void)ipnet_routesock_output_rt(IPNET_RTM_DELETE, rt->pid, rt->seq,
|
|
IPNET_ROUTE_GET_VR(rt->head),
|
|
IPNET_ROUTE_GET_TABLE(rt->head),
|
|
rt, size, addrs, IPNET_ROUTE_GET_FAMILY(rt->head), 0);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_rt_change
|
|
*===========================================================================
|
|
* Description: Notifies all IP_AF_ROUTE socket listener about the change
|
|
* in the route parameters.
|
|
* Parameters: rt - The route entry that has been changed.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_rt_change(Ipnet_route_entry *rt)
|
|
{
|
|
Ip_size_t size;
|
|
Ip_u32 addrs;
|
|
|
|
ipnet_routesock_create_addrs(IPNET_ROUTE_GET_FAMILY(rt->head), rt, &size, &addrs);
|
|
(void)ipnet_routesock_output_rt(IPNET_RTM_CHANGE, rt->pid, rt->seq,
|
|
IPNET_ROUTE_GET_VR(rt->head),
|
|
IPNET_ROUTE_GET_TABLE(rt->head),
|
|
rt, size, addrs, IPNET_ROUTE_GET_FAMILY(rt->head), 0);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_rt_miss
|
|
*===========================================================================
|
|
* Description: A route lookup failed, create IPNET_RTM_MISS message.
|
|
* Parameters: rtab - The route table used during the lookup.
|
|
* key - The search key that did not match anything.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_rt_miss(Ipcom_route *rtab, IP_CONST void *key)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
Ip_size_t size;
|
|
struct Ipnet_rt_msghdr *rtm;
|
|
int domain;
|
|
|
|
domain = IPNET_ROUTE_GET_FAMILY(rtab);
|
|
size = sizeof(struct Ipnet_rt_msghdr);
|
|
|
|
if (domain == IP_AF_INET)
|
|
size += sizeof(struct Ip_sockaddr_in);
|
|
else
|
|
size += sizeof(struct Ip_sockaddr_in6);
|
|
|
|
pkt = ipcom_pkt_malloc(size, IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return;
|
|
pkt->vr_index = IPNET_ROUTE_GET_VR(rtab);
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, size);
|
|
rtm = ipcom_pkt_get_data(pkt, 0);
|
|
|
|
ipcom_memset(rtm, 0, size);
|
|
rtm->rtm_msglen = (Ip_u16) size;
|
|
rtm->rtm_version = IPNET_RTM_VERSION;
|
|
rtm->rtm_type = IPNET_RTM_MISS;
|
|
rtm->rtm_addrs = IPNET_RTA_DST;
|
|
|
|
ipnet_route_key_to_sockaddr(domain, key, (struct Ip_sockaddr *) (rtm + 1));
|
|
|
|
(void)ipnet_routesock_output(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_rt_redirect
|
|
*===========================================================================
|
|
* Description: The stack was told to use a different route.
|
|
* Parameters: rtab - The route table to use.
|
|
* ifindex - The interface the redirect message was received on.
|
|
* dst - The final destination.
|
|
* new_gw - A better first hop gateway.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_rt_redirect(Ipcom_route *rtab, Ip_u32 ifindex,
|
|
IP_CONST void *dst, IP_CONST void *new_gw)
|
|
{
|
|
Ipcom_pkt *pkt;
|
|
Ip_size_t size;
|
|
struct Ipnet_rt_msghdr *rtm;
|
|
int domain;
|
|
|
|
if (ipnet->num_route_sock == 0)
|
|
return;
|
|
|
|
domain = IPNET_ROUTE_GET_FAMILY(rtab);
|
|
size = sizeof(struct Ipnet_rt_msghdr);
|
|
|
|
if (domain == IP_AF_INET)
|
|
size += sizeof(struct Ip_sockaddr_in) * 2;
|
|
else
|
|
size += sizeof(struct Ip_sockaddr_in6) * 2;
|
|
|
|
pkt = ipcom_pkt_malloc(size, IP_FLAG_FC_STACKCONTEXT);
|
|
if (pkt == IP_NULL)
|
|
return;
|
|
pkt->vr_index = IPNET_ROUTE_GET_VR(rtab);
|
|
IPCOM_PKT_TRACE(pkt, IPCOM_PKT_ID_RTSOCK);
|
|
IPNET_ROUTESOCK_INIT_PKT(pkt, size);
|
|
rtm = ipcom_pkt_get_data(pkt, 0);
|
|
|
|
ipcom_memset(rtm, 0, size);
|
|
rtm->rtm_msglen = (Ip_u16) size;
|
|
rtm->rtm_version = IPNET_RTM_VERSION;
|
|
rtm->rtm_type = IPNET_RTM_REDIRECT;
|
|
rtm->rtm_addrs = IPNET_RTA_DST | IPNET_RTA_GATEWAY;
|
|
|
|
if (domain == IP_AF_INET)
|
|
{
|
|
struct Ip_sockaddr_in *inet;
|
|
struct Ip_sockaddr_in *gw;
|
|
|
|
inet = (struct Ip_sockaddr_in *) (rtm + 1);
|
|
inet->sin_family = IP_AF_INET;
|
|
IPCOM_SA_LEN_SET(inet, sizeof(struct Ip_sockaddr_in));
|
|
ipcom_memcpy(&inet->sin_addr, dst, sizeof(inet->sin_addr));
|
|
|
|
gw = inet + 1;
|
|
gw->sin_family = IP_AF_INET;
|
|
IPCOM_SA_LEN_SET(gw, sizeof(struct Ip_sockaddr_in));
|
|
ipcom_memcpy(&gw->sin_addr, new_gw, sizeof(gw->sin_addr));
|
|
}
|
|
else
|
|
{
|
|
struct Ip_sockaddr_in6 *inet6;
|
|
struct Ip_sockaddr_in6 *gw;
|
|
|
|
inet6 = (struct Ip_sockaddr_in6 *) (rtm + 1);
|
|
inet6->sin6_family = IP_AF_INET6;
|
|
IPCOM_SA_LEN_SET(inet6, sizeof(struct Ip_sockaddr_in6));
|
|
ipcom_memcpy(&inet6->sin6_addr, dst, sizeof(inet6->sin6_addr));
|
|
if (IP_IN6_IS_ADDR_LINK_LOCAL(&inet6->sin6_addr))
|
|
inet6->sin6_scope_id = ifindex;
|
|
|
|
gw = inet6 + 1;
|
|
gw->sin6_family = IP_AF_INET6;
|
|
IPCOM_SA_LEN_SET(gw, sizeof(struct Ip_sockaddr_in6));
|
|
ipcom_memcpy(&gw->sin6_addr, new_gw, sizeof(gw->sin6_addr));
|
|
if (IP_IN6_IS_ADDR_LINK_LOCAL(&gw->sin6_addr))
|
|
gw->sin6_scope_id = ifindex;
|
|
}
|
|
|
|
(void)ipnet_routesock_output(pkt);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_routesock_neigh_resolve
|
|
*===========================================================================
|
|
* Description: Kernel failed to resolve this address to a link-layer address,
|
|
* request any route socket listener to create the mapping.
|
|
* Parameters: neigh - The neigh for which no link layer address was found.
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipnet_routesock_neigh_resolve(Ipnet_neigh *neigh)
|
|
{
|
|
Ip_size_t size;
|
|
Ip_u32 addrs;
|
|
Ipnet_route_entry rt;
|
|
Ipnet_netif *netif = neigh->netif;
|
|
|
|
/*
|
|
* Create a route entry with just enough information to create a
|
|
* valid RTM_RESOLVE message.
|
|
*/
|
|
ipcom_memset(&rt, 0, sizeof(rt));
|
|
rt.hdr.key = &neigh->addr;
|
|
rt.hdr.flags = IPNET_RTF_UP | IPNET_RTF_HOST | IPNET_RTF_LLINFO;
|
|
rt.netif = netif;
|
|
|
|
ipnet_routesock_create_addrs(neigh->domain, &rt, &size, &addrs);
|
|
(void)ipnet_routesock_output_rt(IPNET_RTM_RESOLVE, 0, 0,
|
|
netif->vr_index,
|
|
IPCOM_ROUTE_TABLE_DEFAULT,
|
|
&rt, size, addrs, neigh->domain, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipnet_sock_route_register
|
|
*===========================================================================
|
|
* Description: Registers the IP_AF_ROUTE domain.
|
|
* Parameters:
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipnet_sock_route_register(void)
|
|
{
|
|
Ipnet_sock_ops *ops;
|
|
|
|
ops = ipcom_calloc(1, sizeof(*ops));
|
|
if (ops == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(CRIT, "Could not register the IP_AF_ROUTE domain, out of memory");
|
|
IP_PANIC();
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
ipnet_usr_sock_get_ops(ops);
|
|
ops->domain = IP_AF_ROUTE;
|
|
ops->type = IP_SOCK_RAW;
|
|
ops->destroy = ipnet_sock_route_destroy;
|
|
ops->init = ipnet_sock_route_init;
|
|
ops->lookup = ipnet_sock_route_lookup;
|
|
ops->send = ipnet_sock_route_send;
|
|
|
|
/* memory allocated at system startup, never freed */
|
|
/* coverity[leaked_storage] */
|
|
return ipnet_sock_register_ops(ops);
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 11 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
#endif /* IPNET_USE_ROUTESOCK */
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|
|
|