1975 lines
57 KiB
C
1975 lines
57 KiB
C
/*
|
|
******************************************************************************
|
|
* INTERPEAK SOURCE FILE
|
|
*
|
|
* Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipmpls.c,v $ $Revision: 1.73.10.3 $
|
|
* $Source: /home/interpeak/CVSRoot/ipmpls/src/ipmpls.c,v $
|
|
* $Author: hsun $
|
|
* $State: Exp $ $Locker: $
|
|
*
|
|
* INTERPEAK_COPYRIGHT_STRING
|
|
* Design and implementation by Jonas Rendel <Jonas.Rendel@interpeak.se>
|
|
******************************************************************************
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifdef IPMPLS
|
|
|
|
#include "ipmpls_config.h"
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_cstyle.h>
|
|
#include <ipcom_type.h>
|
|
#include <ipcom_os.h>
|
|
#include <ipcom_pkt.h>
|
|
#include <ipcom_route.h>
|
|
#include <ipcom_netif.h>
|
|
#include <ipcom_shell.h>
|
|
#include <ipcom_strlib.h>
|
|
#include <ipcom_sysvar.h>
|
|
#include <ipcom_syslog.h>
|
|
|
|
#include "ipmpls.h"
|
|
#include "ipmpls_h.h"
|
|
#include "ipmpls_ipstack.h"
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
#define IPMPLS_NETIF_LINK_HDR_SIZE 14
|
|
|
|
#if defined(_WRS_CONFIG_IPMPLS_SYSLOG_PRIORITY)
|
|
#define IPCOM_SYSLOG_PRIORITY _WRS_CONFIG_IPMPLS_SYSLOG_PRIORITY
|
|
#else
|
|
#define IPCOM_SYSLOG_PRIORITY IPCOM_SYSLOG_PRIO_DEFAULT
|
|
#endif
|
|
#define IPCOM_SYSLOG_FACILITY IPCOM_LOG_IPMPLS
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 6 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
IP_STATIC int ipmpls_dlv(Ipcom_netif *netif, struct Ip_sockaddr *dst,
|
|
Ipcom_pkt *pkt, int proto);
|
|
|
|
IP_STATIC int ipmpls_process_label (Ipcom_netif *netif,
|
|
Ipmpls_route *entry,
|
|
Ip_bool first, Ipcom_pkt *pkt,
|
|
int recursion_limit);
|
|
|
|
IP_STATIC void ipmpls_netif_nhlfe_flush(Ipcom_netif *netif, Ip_u16 vr, Ipmpls_nhlfe *nhlfe);
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
Ipmpls_data *ipmpls;
|
|
|
|
IP_STATIC Ipmpls_route ipmpls_ilm_explicit_ipv4;
|
|
IP_STATIC Ipmpls_route ipmpls_ilm_explicit_ipv6;
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 9 STATIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_sysv_fwdconf_parse_cb
|
|
*===========================================================================
|
|
* Description: Parses and executes preconfigures mplsctl commands stored
|
|
* as sysvars
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
#ifdef IPMPLS_INCLUDE_CMD
|
|
IP_STATIC int
|
|
ipmpls_sysv_fwdconf_parse_cb(const char *name, const char *value,
|
|
int flags, void *cookie)
|
|
{
|
|
int retval = -1;
|
|
int argc;
|
|
char **argv = IP_NULL;
|
|
Ipcom_shell_cmd *cmd;
|
|
|
|
(void)name;
|
|
(void)cookie;
|
|
(void)flags;
|
|
|
|
|
|
if (ipcom_parse_argstr((char*)value, (int*)&argc, &argv) != IPCOM_SUCCESS)
|
|
{
|
|
/* Add log */
|
|
goto out;
|
|
}
|
|
|
|
if ((cmd = ipcom_shell_find_cmd(argv[0])) == IP_NULL)
|
|
{
|
|
/* Add log */
|
|
goto out;
|
|
}
|
|
|
|
if (cmd->hook(argc, argv) != IPCOM_SUCCESS)
|
|
{
|
|
/* Add log */
|
|
goto out;
|
|
}
|
|
|
|
retval = 0;
|
|
|
|
out:
|
|
|
|
if(argv != IP_NULL)
|
|
ipcom_free(argv);
|
|
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_load_fwdconfig
|
|
*===========================================================================
|
|
* Description: Loads ipmpls forwarding preconfiguration
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
#ifdef IPMPLS_INCLUDE_CMD
|
|
IP_STATIC void
|
|
ipmpls_load_fwdconfig(void)
|
|
{
|
|
(void)ipcom_sysvar_for_each("ipmpls.fwdconf",
|
|
ipmpls_sysv_fwdconf_parse_cb,
|
|
IP_NULL);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_nhlfe_lock
|
|
*===========================================================================
|
|
* Description: Lock NHLFE object
|
|
* Parameters: nhlfe - NHLFE object
|
|
*
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipmpls_nhlfe_lock(Ipmpls_nhlfe *nhlfe)
|
|
{
|
|
if (nhlfe)
|
|
nhlfe->refcnt++;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_nhlfe_unlock
|
|
*===========================================================================
|
|
* Description: Unlock NHLFE object
|
|
* Parameters: nhlfe - NHLFE object
|
|
*
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipmpls_nhlfe_unlock(Ipmpls_nhlfe *nhlfe)
|
|
{
|
|
if (nhlfe)
|
|
{
|
|
if (--nhlfe->refcnt < 0)
|
|
ipcom_free(nhlfe);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_mpls_route_notify
|
|
*===========================================================================
|
|
* Description: Used as IPCOM route table callback for ILM route objects.
|
|
* Initiates a newly added ILM entry with some default
|
|
* operations (pop,peek).
|
|
*
|
|
* Parameters: rtab - ILM route table
|
|
* ilm - ILM entry
|
|
* code - Route table operation code
|
|
* Returns: -
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipmpls_mpls_route_notify(struct Ipcom_route_struct *rtab,
|
|
Ipmpls_route *route,
|
|
int code)
|
|
{
|
|
(void)rtab;
|
|
|
|
switch (code)
|
|
{
|
|
case IPCOM_ROUTE_CODE_ADD:
|
|
if (IPMPLS_KEY_GET_TYPE(route->key) == IPMPLS_LABEL_TYPE_GEN)
|
|
{
|
|
/* Setup default ILM op list (pop,peek) */
|
|
route->oplist = ipmpls_oplist_create(2);
|
|
|
|
if (route->oplist)
|
|
{
|
|
Ipmpls_opcode_elem *ops = route->oplist->opcodes;
|
|
/* Setup default ops */
|
|
ops[0].opcode = IPMPLS_OP_POP;
|
|
ops[0].data.ptr = IP_NULL;
|
|
ops[1].opcode = IPMPLS_OP_PEEK;
|
|
ops[1].data.ptr = IP_NULL;
|
|
}
|
|
}
|
|
break;
|
|
case IPCOM_ROUTE_CODE_REMOVE:
|
|
if (IPMPLS_KEY_GET_TYPE(route->key) == IPMPLS_LABEL_TYPE_GEN)
|
|
{
|
|
/* Release resources */
|
|
ipmpls_oplist_remove(route->oplist);
|
|
ipcom_free(route);
|
|
}
|
|
else if (IPMPLS_KEY_GET_TYPE(route->key) == IPMPLS_LABEL_TYPE_KEY)
|
|
{
|
|
if (route->refcnt > 0)
|
|
{
|
|
/*
|
|
* Scan interface list for MPLS tunnel interfaces
|
|
* referencing this NHLFE entry.
|
|
*/
|
|
(void)ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXFOREACHNETIF,
|
|
(void*)ipmpls_netif_nhlfe_flush,
|
|
route,
|
|
IP_NULL);
|
|
|
|
/*
|
|
* Scan the IP route tables for pseudo wire routes
|
|
* referencing this NHLFE entry.
|
|
*/
|
|
(void)ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXFLUSHSHORTCUTROUTES,
|
|
route,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
}
|
|
/* Release resources */
|
|
if (route->oplist)
|
|
ipmpls_oplist_remove(route->oplist);
|
|
|
|
ipmpls_nhlfe_unlock((Ipmpls_nhlfe*)route);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_flush
|
|
*===========================================================================
|
|
* Description: Used as callback when flushing a MPLS route table.
|
|
* Parameters: rt - Route entry
|
|
* data -
|
|
* Returns: Always IP_TRUE
|
|
*
|
|
*/
|
|
IP_STATIC Ip_bool
|
|
ipmpls_route_flush(Ipmpls_route *rt, void* data)
|
|
{
|
|
Ip_u32 *type = (Ip_u32*)data;
|
|
|
|
if (IPMPLS_KEY_GET_TYPE(rt->key) == *type)
|
|
return IP_TRUE;
|
|
else
|
|
return IP_FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_label_table_hash_key
|
|
*===========================================================================
|
|
* Description: Calculates a hash value for the label table search key.
|
|
* Parameters: key - A search key.
|
|
* Returns: Hash value.
|
|
*
|
|
*/
|
|
IP_STATIC unsigned
|
|
ipmpls_route_table_hash_key(Ip_u16 *key)
|
|
{
|
|
return (unsigned)*key;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_il,_table_hash_obj
|
|
*===========================================================================
|
|
* Description: Calculates a hash value for the label table.
|
|
* Parameters: rt_head - A route table head.
|
|
* Returns: Hash value.
|
|
*
|
|
*/
|
|
IP_STATIC unsigned
|
|
ipmpls_route_table_hash_obj(Ipmpls_route *rt)
|
|
{
|
|
|
|
return (unsigned)rt->vr;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_table_hash_cmp
|
|
*===========================================================================
|
|
* Description: Checks if the key matches the label table head.
|
|
* Parameters: rt_head - A route table head.
|
|
* key - A search key.
|
|
* Returns: IP_TRUE if the key matches the element.
|
|
*
|
|
*/
|
|
IP_STATIC Ip_bool
|
|
ipmpls_route_table_hash_cmp(Ipmpls_route *rt, Ip_u16 *vr)
|
|
{
|
|
return (rt->vr == *vr);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_tablecreate
|
|
*===========================================================================
|
|
* Description: IPMPLS route table constructor routine
|
|
* Parameters:
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipmpls_route_table_create(Ip_u16 vr)
|
|
{
|
|
Ipmpls_route *tree;
|
|
|
|
tree = ipcom_calloc(1, sizeof(Ipmpls_route));
|
|
|
|
if (tree == IP_NULL)
|
|
{
|
|
IPCOM_LOG1(WARNING,"IPMPLS :: Failed to create MPLS route table for vr=%d", vr);
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
|
|
/* Add a MPLS route tree for VR 0 */
|
|
tree->head = ipcom_route_new_table(IPMPLS_KEY_BITS,
|
|
(Ipcom_route_notify_func)ipmpls_mpls_route_notify);
|
|
|
|
if (tree->head == IP_NULL)
|
|
{
|
|
IPCOM_LOG1(WARNING,"IPMPLS :: Failed to create MPLS route table for vr=%d", vr);
|
|
ipcom_free(tree);
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
tree->vr = vr;
|
|
|
|
if (ipcom_hash_add(ipmpls->label_tables, tree) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(WARNING,"IPMPLS :: Failed to add MPLS route table for vr=%d to MPLS route table hash table.", vr);
|
|
ipcom_route_free_table(tree->head);
|
|
ipcom_free(tree);
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_dlv
|
|
*===========================================================================
|
|
* Description: Delivers to IP stack
|
|
* Parameters: netif - Receiving interface
|
|
* dst - Specific destinatination address
|
|
* pkt - IP packet
|
|
* proto - IPv4 or IPv6
|
|
*
|
|
* Returns: 0 - ok
|
|
* <0 - failed
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipmpls_dlv(Ipcom_netif *netif, struct Ip_sockaddr *dst,
|
|
Ipcom_pkt *pkt, int proto)
|
|
{
|
|
switch (proto)
|
|
{
|
|
#ifdef IPCOM_USE_INET
|
|
case IP_IPPROTO_IP:
|
|
if (netif->link_tap)
|
|
{
|
|
/* Let the tap handler copy the packet */
|
|
netif->link_tap(netif, pkt, IP_PACKET_HOST, ip_htons(IP_ETH_P_IP), pkt->start, pkt->start);
|
|
}
|
|
return ipcom_mpls_ioevent(netif, IPMPLS_EIOXINPUT_IN, pkt, dst, IP_NULL);
|
|
#endif
|
|
|
|
#ifdef IPCOM_USE_INET6
|
|
case IP_IPPROTO_IPV6:
|
|
if (netif->link_tap)
|
|
{
|
|
/* Let the tap handler copy the packet */
|
|
netif->link_tap(netif, pkt, IP_PACKET_HOST, ip_htons(IP_ETH_P_IPV6), pkt->start, pkt->start);
|
|
}
|
|
return ipcom_mpls_ioevent(netif, IPMPLS_EIOXINPUT_IN6, pkt, dst, IP_NULL);
|
|
#endif
|
|
|
|
default:
|
|
return -IP_ERRNO_EPFNOSUPPORT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_output
|
|
*===========================================================================
|
|
* Description: Outputs a MPLS packet through the link layer of the local IP.
|
|
* stack.
|
|
* Layer 2 info is exctracted by the local IP stack using
|
|
* standard neighbor probing.
|
|
*
|
|
* Parameters: netif - Outgoing interface
|
|
* nexthop - Next hop Layer3 address (IP_AF_INET/IP_AF_INET6)
|
|
* pkt - MPLS packet.
|
|
* penultimate - Tells if IPMPLS is acting as a penultimate LSR on
|
|
* for this packet. The frame type shall then be set
|
|
* according to the PDU.
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failure
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipmpls_output(Ipcom_netif *netif, struct Ip_sockaddr *nexthop, Ipcom_pkt *pkt, Ip_bool penultimate)
|
|
{
|
|
int ret;
|
|
int ev;
|
|
|
|
if (netif == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
switch(netif->type)
|
|
{
|
|
case IP_IFT_ETHER:
|
|
case IP_IFT_L2VLAN:
|
|
if (nexthop == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_ENETUNREACH);
|
|
|
|
switch(nexthop->sa_family)
|
|
{
|
|
#ifdef IPCOM_USE_INET
|
|
case IP_AF_INET:
|
|
ev = IPMPLS_EIOXARPPROBE;
|
|
break;
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
case IP_AF_INET6:
|
|
ev = IPMPLS_EIOXNDPROBE;
|
|
break;
|
|
#endif
|
|
default:
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
}
|
|
|
|
pkt->ifindex = netif->ifindex;
|
|
|
|
ret = ipcom_mpls_ioevent(netif,
|
|
ev,
|
|
pkt,
|
|
nexthop,
|
|
&penultimate);
|
|
/* The packet is taken care of by the ARP/ND probe,
|
|
* so don't attempt to free it here
|
|
*/
|
|
if (ret != 0)
|
|
return ret;
|
|
break;
|
|
|
|
default:
|
|
IPMPLS_ERROUT(IP_ERRNO_ENODEV);
|
|
}
|
|
|
|
return 0;
|
|
|
|
errout:
|
|
ipcom_pkt_free(pkt);
|
|
return -ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_process_label
|
|
*===========================================================================
|
|
* Description: Do label processing on a packet. Used on both transmit and
|
|
* receive packets.
|
|
*
|
|
* Parameters: netif - interface
|
|
* entry - IPMPLS route entry (ILM/NHLFE)
|
|
* dir - Direction (IN/OUT)
|
|
* first - used for s_bit handling on the label
|
|
* pkt - MPLS packet (IN) or PDU (out)
|
|
* peeked - Is true if this routine is called recursively
|
|
* upon a peek operation.
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipmpls_process_label (Ipcom_netif *netif, Ipmpls_route *entry,
|
|
Ip_bool first, Ipcom_pkt *pkt, int recursion_limit)
|
|
{
|
|
Ipmpls_nhlfe *nhlfe = (Ipmpls_nhlfe *) entry;
|
|
Ipmpls_ilm *ilm = (Ipmpls_ilm *) entry;
|
|
int i = 0;
|
|
Ipcom_pkt *new_pkt;
|
|
int pkt_len;
|
|
Ipmpls_opcode_elem *op;
|
|
Ipmpls_label_hdr label;
|
|
Ip_bool s_bit = first;
|
|
int ret;
|
|
Ip_u8 ttl = 255;
|
|
int dir;
|
|
struct Ip_sockaddr *sa = IP_NULL;
|
|
|
|
dir = IPMPLS_KEY_GET_TYPE(entry->key) == IPMPLS_LABEL_TYPE_GEN ? IPMPLS_IN : IPMPLS_OUT;
|
|
|
|
if (entry->oplist == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
if ((dir == IPMPLS_OUT) && (nhlfe->ttl == 1) && first)
|
|
{
|
|
ttl = (Ip_u8)ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXGET_TTL,
|
|
pkt,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
}
|
|
|
|
for (i = 0; i < entry->oplist->num_opcodes; i++)
|
|
{
|
|
op = &entry->oplist->opcodes[i];
|
|
label = IP_GET_NTOHL(&pkt->data[pkt->start]);
|
|
|
|
switch (op->opcode)
|
|
{
|
|
case IPMPLS_OP_PUSH:
|
|
/* Push a label onto the label stack
|
|
* Valid: both IN and OUT
|
|
*/
|
|
ipcom_memset(&label, 0, IPMPLS_LABEL_SIZE);
|
|
IPMPLS_LABEL_SET_LABEL(label, op->data.push.u.ml_gen);
|
|
IPMPLS_LABEL_SET_TTL(label,
|
|
(dir == IPMPLS_IN) ? ilm->ttl : ttl);
|
|
/* If the current label has the S bit set, re-set it */
|
|
if (first)
|
|
{
|
|
IPMPLS_LABEL_SET_S(label);
|
|
first = IP_FALSE;
|
|
}
|
|
label = ip_htonl(label);
|
|
|
|
|
|
/* Check if the packet is big enough
|
|
* to accomodate a pushed label
|
|
*/
|
|
pkt_len = pkt->end - pkt->start;
|
|
/* if (pkt->start >= ipcom_conf_max_link_hdr_size + IPMPLS_LABEL_SIZE) */
|
|
if (pkt->start >= IPMPLS_NETIF_LINK_HDR_SIZE + IPMPLS_LABEL_SIZE)
|
|
{
|
|
pkt->start -= IPMPLS_LABEL_SIZE;
|
|
IP_SET_32ON8(&pkt->data[pkt->start], label);
|
|
break;
|
|
}
|
|
|
|
|
|
new_pkt = ipcom_pkt_malloc(pkt->end + IPMPLS_LABEL_SIZE,
|
|
IP_FLAG_FC_STACKCONTEXT);
|
|
if (new_pkt == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_ENOMEM);
|
|
|
|
new_pkt->fd = pkt->fd;
|
|
new_pkt->vr_index = pkt->vr_index;
|
|
new_pkt->ifindex = pkt->ifindex;
|
|
new_pkt->flags = pkt->flags;
|
|
new_pkt->ipstart = pkt->ipstart + IPMPLS_LABEL_SIZE;
|
|
new_pkt->start = pkt->start;
|
|
new_pkt->end = pkt->end + IPMPLS_LABEL_SIZE;
|
|
|
|
/* Copy the packet's data preceeding the start */
|
|
ipcom_memcpy(new_pkt->data, pkt->data, pkt->start);
|
|
|
|
|
|
*(Ipmpls_label_hdr *) &new_pkt->data[pkt->start] = label;
|
|
/* Last copy the data proceeding the label */
|
|
ipcom_memcpy(&new_pkt->data[pkt->start + IPMPLS_LABEL_SIZE],
|
|
&pkt->data[pkt->start],
|
|
pkt_len);
|
|
|
|
/* Free the original packet and assign the new packet */
|
|
ipcom_pkt_free(pkt);
|
|
|
|
/* Update the packet end indicator, in order to reflect the
|
|
* addition of the new label
|
|
*/
|
|
pkt = new_pkt;
|
|
break;
|
|
|
|
case IPMPLS_OP_POP:
|
|
/* Pop a label from the label stack
|
|
* Valid: IN & OUT
|
|
*/
|
|
|
|
/* If the first variable is set, it means we have already reached
|
|
* the bottom of the stack
|
|
*/
|
|
if (first)
|
|
IPMPLS_ERROUT(IP_ERRNO_EPERM);
|
|
|
|
first = s_bit = IPMPLS_LABEL_GET_S(label);
|
|
pkt->start += IPMPLS_LABEL_SIZE;
|
|
|
|
if (first)
|
|
/* We are at the IP header */
|
|
pkt->ipstart = pkt->start;
|
|
break;
|
|
|
|
case IPMPLS_OP_SET:
|
|
/* For outgoing: Set Tx interface and nexthop address.
|
|
* For incoming: Set destination address to route packet to.
|
|
* Valid: IN & OUT
|
|
*/
|
|
if (dir == IPMPLS_OUT)
|
|
{
|
|
/* Set Tx interface */
|
|
if (ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXINDEXTONETIF,
|
|
&op->data.set.ifindex,
|
|
&netif,
|
|
IP_NULL) != 0)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
}
|
|
sa = &op->data.set.a.sa;
|
|
break;
|
|
|
|
case IPMPLS_OP_SET_RX:
|
|
/* Set Rx interface
|
|
* Valid: IN
|
|
*/
|
|
if (dir != IPMPLS_IN)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
/* Set Rx interface */
|
|
if (ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXINDEXTONETIF,
|
|
&op->data.set.ifindex,
|
|
&netif,
|
|
IP_NULL) != 0)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
pkt->ifindex = netif->ifindex;
|
|
break;
|
|
|
|
case IPMPLS_OP_SET_VR:
|
|
/* Set a specific RTAB to use when routing the packet.
|
|
* Valid: IN
|
|
*/
|
|
{
|
|
Ip_u16 vr = (Ip_u16)op->data.set.ifindex;
|
|
|
|
if (ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXRTABTONETIF,
|
|
&vr,
|
|
&netif,
|
|
IP_NULL) != 0)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
pkt->vr_index = vr;
|
|
pkt->ifindex = netif->ifindex;
|
|
}
|
|
|
|
break;
|
|
|
|
case IPMPLS_OP_PEEK:
|
|
|
|
if (recursion_limit < 0)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
if (s_bit)
|
|
return ipmpls_dlv(netif, sa, pkt, IPMPLS_ILM_PROTO(ilm));
|
|
else
|
|
return ipmpls_input(netif, pkt, recursion_limit - 1);
|
|
|
|
case IPMPLS_OP_DLV:
|
|
/* Deliver packet to stack
|
|
* Valid: IN
|
|
*/
|
|
if (dir != IPMPLS_IN)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
return ipmpls_dlv(netif, sa, pkt, IPMPLS_ILM_PROTO(ilm));
|
|
|
|
case IPMPLS_OP_FWD:
|
|
{
|
|
Ipmpls_route *next;
|
|
|
|
/* Forward to another MPLS object
|
|
* Valid: IN | OUT
|
|
*
|
|
* This will recursively call back into here,
|
|
* but the recursion limit will prevent it from doing it too
|
|
* many times.
|
|
*/
|
|
|
|
if (recursion_limit < 0)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
next = ipmpls_route_lookup(pkt->vr_index, &op->data.fwd);
|
|
|
|
if (next == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_EHOSTUNREACH);
|
|
|
|
/* Not permitted to forward to itself */
|
|
if (next == entry)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
/* We are assuming that the FWD is last on the op list */
|
|
return ipmpls_process_label(netif, next, first,
|
|
pkt, recursion_limit - 1);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we are here and the packet is inbound, then we might have an
|
|
* op problem, since one of the ops should have taken care of the packet...
|
|
*/
|
|
if (dir == IPMPLS_IN)
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
|
|
/* If we are here it is an outbound packet...*/
|
|
return ipmpls_output(netif, sa, pkt, first);
|
|
|
|
errout:
|
|
ipcom_pkt_free(pkt);
|
|
return -ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_netif_nhlfe_flush
|
|
*===========================================================================
|
|
* Description: Remove a NHLFE reference from a MPLS tunnel interface.
|
|
* Parameters: netif - MPLS tunnel interface
|
|
* nhlfe - NHLFE object.
|
|
*
|
|
* Returns: -
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipmpls_netif_nhlfe_flush(Ipcom_netif *netif, Ip_u16 vr, Ipmpls_nhlfe *nhlfe)
|
|
{
|
|
Ipmpls_netif *mplsif;
|
|
|
|
(void)vr;
|
|
|
|
if (netif->type == IP_IFT_MPLS_TUNNEL)
|
|
{
|
|
mplsif = (Ipmpls_netif*)netif->plink;
|
|
|
|
if (mplsif && (mplsif->nhlfe == nhlfe))
|
|
{
|
|
ipmpls_nhlfe_unlock(mplsif->nhlfe);
|
|
mplsif->nhlfe = IP_NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_do_input
|
|
*===========================================================================
|
|
* Description: Input handler for ethernet frames. pkt->start is the offset
|
|
* to the ethernet header.
|
|
* Parameters: pkt - The ethernet frame.
|
|
* Returns: 0 = success, <0 error code
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipmpls_do_input(Ipcom_netif *netif, Ipcom_pkt *pkt, int rlimit)
|
|
{
|
|
Ipmpls_label_hdr lh;
|
|
Ip_u32 lblval;
|
|
Ipmpls_label label;
|
|
int labelspace;
|
|
Ipmpls_ilm *ilm;
|
|
Ipmpls_key key;
|
|
int ret;
|
|
|
|
/* Extract the label, and adapt it to use label space info */
|
|
lh = IP_GET_NTOHL(&pkt->data[pkt->start]);
|
|
/* Get labelspace */
|
|
ret = ipmpls_labelspace_get(netif, &labelspace);
|
|
if (ret != 0)
|
|
IPMPLS_ERROUT(-ret);
|
|
|
|
lblval = IPMPLS_LABEL_GET_LABEL(lh);
|
|
|
|
/* Check for special handling in the case of implicit labels */
|
|
switch (lblval)
|
|
{
|
|
case IPMPLS_LABEL_IPV4_EXPLICIT_NULL:
|
|
return ipmpls_process_label(netif, &ipmpls_ilm_explicit_ipv4, IP_FALSE,
|
|
pkt, rlimit);
|
|
|
|
case IPMPLS_LABEL_IPV6_EXPLICIT_NULL:
|
|
return ipmpls_process_label(netif, &ipmpls_ilm_explicit_ipv6, IP_FALSE,
|
|
pkt, rlimit);
|
|
|
|
case IPMPLS_LABEL_IMPLICIT_NULL:
|
|
/* This shouldn't appear in traffic,
|
|
* it is only used by the LDP to indicate stack popping and
|
|
* thus should be translated to a pop by the LDP stack.
|
|
*/
|
|
IPMPLS_ERROUT(IP_ERRNO_EINVAL);
|
|
}
|
|
|
|
/* Create a label used in the key_create function */
|
|
label.__refcnt = 0;
|
|
label.ml_type = IPMPLS_LABEL_TYPE_GEN;
|
|
label.u.ml_gen = lblval;
|
|
ipmpls_key_create(labelspace, &label, &key);
|
|
|
|
ilm = ipmpls_route_lookup(pkt->vr_index, &key);
|
|
if (ilm == IP_NULL)
|
|
IPMPLS_ERROUT(IP_ERRNO_EHOSTUNREACH);
|
|
|
|
/* Now perform label specific operations as described by the
|
|
* ILM instructions this should leave the packet in a state that
|
|
* can be presented to the next layer, be that IP, MPLS or link layer
|
|
*/
|
|
return ipmpls_process_label(netif, ilm, IP_FALSE, pkt, rlimit);
|
|
|
|
errout:
|
|
|
|
ipcom_pkt_free(pkt);
|
|
return -ret;
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 GLOBAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_nhlfe_key_autogenerate
|
|
*===========================================================================
|
|
* Description: Auto generate a new unique NHLFE key.
|
|
* Parameters: key - Pointer to NHLFE key
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_nhlfe_key_autogenerate(Ipmpls_key *key)
|
|
{
|
|
int ret = -1;
|
|
Ip_u16 vr = 0;
|
|
|
|
if (ipmpls->ipmpls_last_nhlfe_label == 0xFFFF)
|
|
ipmpls->ipmpls_last_nhlfe_label = 0;
|
|
|
|
*key = IPMPLS_KEY_SET_TYPE(*key, ((Ip_u32)IPMPLS_LABEL_TYPE_KEY));
|
|
*key = IPMPLS_KEY_SET_LABELSPACE(*key, 0);
|
|
*key = IPMPLS_KEY_SET_LABEL(*key, ++(ipmpls->ipmpls_last_nhlfe_label));
|
|
|
|
(void)ipcom_mpls_ioevent(IP_NULL,IPMPLS_EIOXGET_USER_VR, &vr,IP_NULL,IP_NULL);
|
|
|
|
while (IPMPLS_KEY_GET_LABEL(*key) < 0xFFFF)
|
|
{
|
|
if (!ipmpls_route_lookup(vr, key))
|
|
{
|
|
ipmpls->ipmpls_last_nhlfe_label = (Ip_u16)IPMPLS_KEY_GET_LABEL(*key);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
*key = IPMPLS_KEY_SET_LABEL(*key, IPMPLS_KEY_GET_LABEL(*key) + 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_flush_tree
|
|
*===========================================================================
|
|
* Description: Flush a MPLS route table
|
|
* Parameters: tree_ix - table index
|
|
* type - table type (ILM/NHLFE)
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_route_flush_tree(Ip_u16 ix, Ip_u32 type)
|
|
{
|
|
int ret = 0;
|
|
Ipmpls_route *tree;
|
|
|
|
tree = (Ipmpls_route*)ipcom_hash_get(ipmpls->label_tables, &ix);
|
|
|
|
if (tree == IP_NULL)
|
|
IPMPLS_ERROUT(-IP_ERRNO_EINVAL);
|
|
|
|
/* Flush tree */
|
|
ipcom_route_walk_tree(tree->head,
|
|
(Ipcom_route_walk_cb)ipmpls_route_flush,
|
|
&type);
|
|
errout:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_lookup
|
|
*===========================================================================
|
|
* Description: Do a lookup in a MPLS route table.
|
|
* Parameters: tree_ix - Route table index
|
|
* key - MPLS key
|
|
* type - Table type (ILM/NHLFE)
|
|
*
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipmpls_route*
|
|
ipmpls_route_lookup(Ip_u16 ix, Ipmpls_key *key)
|
|
{
|
|
Ipmpls_route *route = IP_NULL;
|
|
Ipmpls_route *tree = IP_NULL;
|
|
Ip_bool perfect = IP_FALSE;
|
|
|
|
tree = (Ipmpls_route*)ipcom_hash_get(ipmpls->label_tables, &ix);
|
|
|
|
if (tree)
|
|
{
|
|
route = (Ipmpls_route*)ipcom_route_lookup(tree->head,
|
|
(const void *) key,
|
|
&perfect);
|
|
if (!perfect)
|
|
route = IP_NULL;
|
|
}
|
|
|
|
return route;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_add
|
|
*===========================================================================
|
|
* Description: Add a new entry in a MPLS route table.
|
|
* Parameters: tree_index - Route table index
|
|
* key - MPLS key for the new entry
|
|
* route - The MPLS route entry (ILM/NHLFE)
|
|
* type - ILM/NHLFE
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_route_add(Ip_u16 vr, Ipmpls_key *key, Ipmpls_route *route)
|
|
{
|
|
Ipmpls_route *tree;
|
|
Ip_time_t t;
|
|
|
|
tree = ipcom_hash_get(ipmpls->label_tables, &vr);
|
|
|
|
if (tree == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
route->age = ipcom_time(&t);
|
|
route->hdr.key = &route->key;
|
|
route->vr = vr;
|
|
route->head = tree->head;
|
|
ipcom_memcpy(&route->key, key, sizeof(Ipmpls_key));
|
|
|
|
return ipcom_route_add(route->head, &route->hdr);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_remove
|
|
*===========================================================================
|
|
* Description: Remove a MPLS route entry (ILM/NHLFE)
|
|
* Parameters: route - The route entry
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_route_remove(Ipmpls_route *route)
|
|
{
|
|
return ipcom_route_remove(route->head, &route->hdr);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_key_create
|
|
*===========================================================================
|
|
* Description: Compose a MPLS key out of label space and label value
|
|
* Parameters: labelspace - Label Space
|
|
* label - Label entry
|
|
* key - Key entry
|
|
*
|
|
* Returns: Pointer to key.
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipmpls_key *
|
|
ipmpls_key_create(int labelspace, Ipmpls_label *label, Ipmpls_key *key)
|
|
{
|
|
ipcom_memset(key, 0, sizeof(*key)),
|
|
|
|
*key = IPMPLS_KEY_SET_TYPE(*key, label->ml_type);
|
|
*key = IPMPLS_KEY_SET_LABEL(*key, label->u.ml_gen);
|
|
*key = IPMPLS_KEY_SET_LABELSPACE(*key, labelspace);
|
|
|
|
return key;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_set_labelspace
|
|
*===========================================================================
|
|
* Description: Sets MPLS label space on an ethernet device
|
|
* Parameters: [in] netif - device descriptor
|
|
* [in] labelspace - label space
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_labelspace_set(Ipcom_netif *netif, int labelspace)
|
|
{
|
|
struct Ipmpls_eth_if_mpls_data *mpls_data;
|
|
int ret = -IP_ERRNO_EFAULT;
|
|
|
|
if ((netif->type == IP_IFT_ETHER) || (netif->type == IP_IFT_L2VLAN))
|
|
{
|
|
/* Access the mpls private data on the ethernet device */
|
|
if (netif->plink)
|
|
mpls_data = (struct Ipmpls_eth_if_mpls_data*)netif->plink;
|
|
else
|
|
{
|
|
mpls_data = ipcom_calloc(1, sizeof(struct Ipmpls_eth_if_mpls_data));
|
|
netif->plink = mpls_data;
|
|
}
|
|
|
|
if(mpls_data)
|
|
{
|
|
mpls_data->mpls_labelspace = labelspace;
|
|
ret = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Not an Ethernet interface */
|
|
ret = -IP_ERRNO_EPERM;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_labelspace_get
|
|
*===========================================================================
|
|
* Description: Gets MPLS label space from an ethernet device
|
|
* Parameters: [in] netif - device descriptor
|
|
* [out] labelspace - label space
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_labelspace_get(Ipcom_netif *netif, int *labelspace)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((netif->type == IP_IFT_ETHER) || (netif->type == IP_IFT_L2VLAN))
|
|
{
|
|
/* Access the mpls private data on the ethernet device */
|
|
if (netif->plink)
|
|
*labelspace = ((struct Ipmpls_eth_if_mpls_data*)netif->plink)->mpls_labelspace;
|
|
else
|
|
*labelspace = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Not an Ethernet interface */
|
|
ret = -IP_ERRNO_EPERM;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_oplist_remove
|
|
*===========================================================================
|
|
* Description: Flush a MPLS operation list
|
|
* Parameters: oplist - Pointer to operation list.
|
|
*
|
|
* Returns: -
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipmpls_oplist_remove(Ipmpls_opcode_list *oplist)
|
|
{
|
|
int i;
|
|
Ipmpls_opcode_elem *op;
|
|
|
|
for (i = 0; i < oplist->num_opcodes; i++)
|
|
{
|
|
op = &oplist->opcodes[i];
|
|
|
|
switch (op->opcode)
|
|
{
|
|
case IPMPLS_OP_POP:
|
|
case IPMPLS_OP_PEEK:
|
|
case IPMPLS_OP_PUSH:
|
|
case IPMPLS_OP_FWD:
|
|
case IPMPLS_OP_DLV:
|
|
case IPMPLS_OP_SET_RX:
|
|
case IPMPLS_OP_SET:
|
|
break;
|
|
}
|
|
}
|
|
|
|
ipcom_free(oplist);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_oplist_create
|
|
*===========================================================================
|
|
* Description: Allocate memory for a new operation list
|
|
* Parameters: n_elem - Number of elements to be held in the list
|
|
*
|
|
* Returns: Pointer to oplist Ok
|
|
* IP_NULL Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipmpls_opcode_list *ipmpls_oplist_create(Ip_u8 n_elem)
|
|
{
|
|
Ipmpls_opcode_list *oplist;
|
|
|
|
oplist = ipcom_calloc(1, sizeof(Ipmpls_opcode_list));
|
|
if (oplist == IP_NULL)
|
|
return IP_NULL;
|
|
|
|
oplist->num_opcodes = n_elem;
|
|
return oplist;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_opcode_setup
|
|
*===========================================================================
|
|
* Description: Setup a MPLS operation list.
|
|
* Parameters: dst - The MPLS operation list.
|
|
* src - The MPLS operation configuration list.
|
|
* n_elem - Number of elements to handle.
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_opcode_setup(Ipmpls_opcode_elem *dst, Ipmpls_instr_elem *src,
|
|
Ip_u8 n_elem)
|
|
{
|
|
Ip_u8 i;
|
|
Ipcom_netif *netif;
|
|
int ret = 0;
|
|
int ifindex;
|
|
|
|
for(i = 0; i < n_elem; i++)
|
|
{
|
|
dst[i].opcode = src[i].mir_opcode;
|
|
|
|
switch (dst[i].opcode)
|
|
{
|
|
case IPMPLS_OP_POP:
|
|
case IPMPLS_OP_PEEK:
|
|
case IPMPLS_OP_DLV:
|
|
/* These operations do not carry any data */
|
|
dst[i].data.ptr = IP_NULL;
|
|
break;
|
|
case IPMPLS_OP_SET_RX:
|
|
/* Set receiving interface. Check if valid index */
|
|
ifindex = src[i].mir_data.set_rx;
|
|
if (ipcom_mpls_ioevent(IP_NULL, IPMPLS_EIOXINDEXTONETIF,
|
|
&ifindex, &netif, IP_NULL) == 0)
|
|
{
|
|
dst[i].data.set.ifindex = ifindex;
|
|
ipcom_memset(&dst[i].data.set.a,0,sizeof(dst[i].data.set.a));
|
|
}
|
|
else
|
|
IPMPLS_ERROUT(-IP_ERRNO_EINVAL);
|
|
break;
|
|
case IPMPLS_OP_SET_VR:
|
|
/* Set route . Check if valid index */
|
|
if (ipcom_mpls_ioevent(IP_NULL, IPMPLS_EIOXRTABTONETIF,
|
|
&src[i].mir_data.set_rtab,
|
|
&netif, IP_NULL) == 0)
|
|
{
|
|
dst[i].data.set.ifindex = src[i].mir_data.set_rtab;
|
|
ipcom_memset(&dst[i].data.set.a, 0, sizeof(dst[i].data.set.a));
|
|
}
|
|
else
|
|
IPMPLS_ERROUT(-IP_ERRNO_EINVAL);
|
|
break;
|
|
case IPMPLS_OP_PUSH:
|
|
/* Push a new MPLS label on the packet */
|
|
dst[i].data.push = src[i].mir_data.push;
|
|
break;
|
|
case IPMPLS_OP_FWD:
|
|
/* Store key to next MPLS route object */
|
|
ipmpls_key_create(0, &src[i].mir_data.fwd, &dst[i].data.fwd);
|
|
|
|
/* FWD must always be last */
|
|
goto errout;
|
|
case IPMPLS_OP_SET:
|
|
{
|
|
/*
|
|
* Save GW address to be used when doing DLV
|
|
*/
|
|
ipcom_memcpy(&dst[i].data.set.a,
|
|
&src[i].mir_data.set.a,
|
|
sizeof(src[i].mir_data.set.a));
|
|
|
|
if (src[i].mir_direction == IPMPLS_OUT)
|
|
{
|
|
/*
|
|
* NHLFE SET
|
|
* Set nexthop address and outgoing interface.
|
|
*/
|
|
if (ipcom_mpls_ioevent(IP_NULL,
|
|
IPMPLS_EIOXINDEXTONETIF,
|
|
&src[i].mir_data.set.mni_if,
|
|
&netif,
|
|
IP_NULL) != 0)
|
|
IPMPLS_ERROUT(-IP_ERRNO_EINVAL);
|
|
|
|
if (((netif->type != IP_IFT_ETHER) && (netif->type != IP_IFT_L2VLAN)))
|
|
IPMPLS_ERROUT(-IP_ERRNO_EINVAL);
|
|
|
|
/* Convert to internal ifindex for faster lookup */
|
|
dst[i].data.set.ifindex = netif->ifindex;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
errout:
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_opcode_copy
|
|
*===========================================================================
|
|
* Description: Copy a operation list to a operation configuration list.
|
|
* Parameters: src - The MPLS operation list
|
|
* dst - The MPLS operation configuration list
|
|
* n_elem - Number of elements
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_opcode_copy(Ipmpls_opcode_list *src,
|
|
Ipmpls_instr_elem *dst,
|
|
Ip_u8 *n_elem)
|
|
{
|
|
Ip_u8 i;
|
|
int ret = 0;
|
|
Ipmpls_opcode_elem *op;
|
|
|
|
for (i = 0; i < src->num_opcodes; i++)
|
|
{
|
|
op = &src->opcodes[i];
|
|
ipcom_memset(&dst[i], 0, sizeof(Ipmpls_instr_elem));
|
|
dst[i].mir_opcode = op->opcode;
|
|
|
|
switch (op->opcode)
|
|
{
|
|
case IPMPLS_OP_POP:
|
|
case IPMPLS_OP_PEEK:
|
|
case IPMPLS_OP_DLV:
|
|
/* These operations do not carry any data */
|
|
break;
|
|
case IPMPLS_OP_SET_RX:
|
|
dst[i].mir_data.set_rx = op->data.set.ifindex;
|
|
break;
|
|
case IPMPLS_OP_SET_VR:
|
|
dst[i].mir_data.set_rtab = op->data.set.ifindex;
|
|
break;
|
|
case IPMPLS_OP_PUSH:
|
|
/* Push a new MPLS label on the packet */
|
|
dst[i].mir_data.push = op->data.push;
|
|
break;
|
|
case IPMPLS_OP_FWD:
|
|
dst[i].mir_data.fwd.ml_type = IPMPLS_KEY_GET_TYPE(op->data.fwd);
|
|
dst[i].mir_data.fwd.ml_index = IPMPLS_KEY_GET_LABELSPACE(op->data.fwd);
|
|
dst[i].mir_data.fwd.u.ml_gen = IPMPLS_KEY_GET_LABEL(op->data.fwd);
|
|
break;
|
|
case IPMPLS_OP_SET:
|
|
{
|
|
dst[i].mir_data.set.mni_if = op->data.set.ifindex;
|
|
ipcom_memcpy(&dst[i].mir_data.set.a,
|
|
&op->data.set.a,
|
|
sizeof(dst[i].mir_data.set.a));
|
|
#ifdef IP_PORT_LKM
|
|
{
|
|
/* Fix for Linux style sockaddr*/
|
|
Ip_u16 *family = (Ip_u16*)&dst[i].mir_data.set.a.sa.sa_family;
|
|
|
|
*family = (Ip_u16)(dst[i].mir_data.set.a.sa.sa_family);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
case IPMPLS_OP_NOP:
|
|
/* finished */
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
*n_elem = i;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_opcode_get_last
|
|
*===========================================================================
|
|
* Description: Get last operation element in a list.
|
|
* Parameters: oplist - Operation list
|
|
*
|
|
* Returns: Last operation element.
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipmpls_opcode_elem *
|
|
ipmpls_opcode_get_last(Ipmpls_opcode_list *oplist)
|
|
{
|
|
return &oplist->opcodes[oplist->num_opcodes - 1];
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_opcode_get_last
|
|
*===========================================================================
|
|
* Description: Associate a specified ILM with a certain tunnel interface.
|
|
* Parameters: netif - Pointer to tunnel interface.
|
|
* data - Contains key to ILM.
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Error
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_ilm_to_tunnel_map (Ipcom_netif *netif, Ip_u16 vr, Ip_u32 data)
|
|
{
|
|
int ret = -1;
|
|
Ipmpls_key key;
|
|
Ipmpls_label label;
|
|
Ipmpls_route *ilm;
|
|
|
|
/*
|
|
* An in label has been specified and shall
|
|
* be associated with this interface.
|
|
*/
|
|
label.ml_type = IPMPLS_LABEL_TYPE_GEN;
|
|
label.u.ml_key = data;
|
|
label.ml_index = 0;
|
|
|
|
(void)ipmpls_key_create(0, &label, &key);
|
|
ilm = ipmpls_route_lookup(vr, &key);
|
|
|
|
if (ilm)
|
|
{
|
|
if (ilm->oplist)
|
|
ipmpls_oplist_remove(ilm->oplist);
|
|
|
|
/*
|
|
* Set the ILM oplist to:
|
|
* pop,setrx="this tunnel",dlv
|
|
*/
|
|
ilm->oplist = ipmpls_oplist_create(3);
|
|
|
|
if (ilm->oplist)
|
|
{
|
|
ilm->oplist->opcodes[0].opcode = IPMPLS_OP_POP;
|
|
ilm->oplist->opcodes[0].data.ptr = IP_NULL;
|
|
ilm->oplist->opcodes[1].opcode = IPMPLS_OP_SET_RX;
|
|
ilm->oplist->opcodes[1].data.set.ifindex = netif->ifindex;
|
|
ilm->oplist->opcodes[2].opcode = IPMPLS_OP_DLV;
|
|
ilm->oplist->opcodes[2].data.ptr = IP_NULL;
|
|
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_set_cookie
|
|
*===========================================================================
|
|
* Description: Set a cooke on a certain MPLS route entry
|
|
* Parameters: l - label (key)
|
|
* vr - VR index
|
|
* data - cookie data.
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Error
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipmpls_set_cookie(Ipmpls_label *l, Ip_u16 vr, void* data)
|
|
{
|
|
Ipmpls_key key;
|
|
Ipmpls_route *rt;
|
|
|
|
|
|
(void)ipmpls_key_create(l->ml_index, l, &key);
|
|
rt = ipmpls_route_lookup(vr, &key);
|
|
|
|
if (rt)
|
|
{
|
|
ipcom_memcpy(&rt->cookie, data, sizeof(Ipmpls_rt_cookie));
|
|
return 0;
|
|
}
|
|
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 11 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_create
|
|
*===========================================================================
|
|
* Description: IPMPLS constructor routine
|
|
* Parameters:
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_create(void)
|
|
{
|
|
int ret;
|
|
Ipmpls_route *rt;
|
|
Ipmpls_opcode_list *oplist;
|
|
|
|
ipmpls = ipcom_calloc(1, sizeof(Ipmpls_data));
|
|
|
|
if (ipmpls == IP_NULL)
|
|
return -IP_ERRNO_ENOMEM;
|
|
|
|
ipcom_memset(&ipmpls_ilm_explicit_ipv4,
|
|
0,
|
|
sizeof(ipmpls_ilm_explicit_ipv4));
|
|
ipcom_memset(&ipmpls_ilm_explicit_ipv6,
|
|
0,
|
|
sizeof(ipmpls_ilm_explicit_ipv6));
|
|
|
|
ipmpls->label_tables = ipcom_hash_new((Ipcom_hash_obj_func) ipmpls_route_table_hash_obj,
|
|
(Ipcom_hash_key_func) ipmpls_route_table_hash_key,
|
|
(Ipcom_hash_cmp_func) ipmpls_route_table_hash_cmp);
|
|
if (ipmpls->label_tables == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(WARNING,"IPMPLS :: Failed to create ILM hash table");
|
|
return -IP_ERRNO_ENOMEM;
|
|
}
|
|
|
|
ret = ipmpls_route_table_create(0);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Setup the explicit labels */
|
|
|
|
|
|
/* Prepare the oplist */
|
|
oplist = ipcom_calloc(1, sizeof(Ipmpls_opcode_list));
|
|
if (oplist == IP_NULL)
|
|
return IP_ERRNO_ENOMEM;
|
|
|
|
oplist->num_opcodes = 2;
|
|
oplist->opcodes[0].dir = IPMPLS_IN;
|
|
oplist->opcodes[0].opcode = IPMPLS_OP_POP;
|
|
oplist->opcodes[1].dir = IPMPLS_IN;
|
|
oplist->opcodes[1].opcode = IPMPLS_OP_DLV;
|
|
|
|
/* Setup the entry, reuse the oplist if same ops are required */
|
|
rt = &ipmpls_ilm_explicit_ipv4;
|
|
rt->oplist = oplist;
|
|
rt->key = IPMPLS_KEY_SET_TYPE(rt->key, IPMPLS_LABEL_TYPE_GEN);
|
|
IPMPLS_ILM_PROTO(rt) = IP_IPPROTO_IP;
|
|
|
|
rt = &ipmpls_ilm_explicit_ipv6;
|
|
rt->oplist = oplist;
|
|
rt->key = IPMPLS_KEY_SET_TYPE(rt->key, IPMPLS_LABEL_TYPE_GEN);
|
|
IPMPLS_ILM_PROTO(rt) = IP_IPPROTO_IPV6;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_start
|
|
*===========================================================================
|
|
* Description: IPMPLS start routine
|
|
* Parameters: -
|
|
* Returns: IPCOM_SUCESS - Ok
|
|
* other - Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipmpls_start(void)
|
|
{
|
|
Ip_err ret = IPCOM_SUCCESS;
|
|
|
|
#ifdef IPMPLS_INCLUDE_CMD
|
|
ret = ipcom_shell_add_cmd("mplsctl",
|
|
"mplsctl ...",
|
|
"MPLS forwarding engine shell tool",
|
|
(Ipcom_shell_cmd_type) ipmpls_cmd_mplsctl,
|
|
IPCOM_SHELL_PRIO_SAME,
|
|
IPCOM_PROC_STACK_DEFAULT);
|
|
|
|
/* Load preconfigured forwarding configs */
|
|
ipmpls_load_fwdconfig();
|
|
#endif
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_version
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC const char *
|
|
ipmpls_version(void)
|
|
{
|
|
return "@(#) IPMPLS $Name: VXWORKS_ITER40_2019102207 $ - INTERPEAK_COPYRIGHT_STRING";
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_input
|
|
*===========================================================================
|
|
* Description: Input handler for ethernet frames. pkt->start is the offset
|
|
* to the ethernet header.
|
|
* Parameters: pkt - The ethernet frame.
|
|
* Returns: 0 = success, <0 error code
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_input(Ipcom_netif *netif, Ipcom_pkt *pkt, Ip_bool peeked)
|
|
{
|
|
(void)peeked;
|
|
|
|
return ipmpls_do_input(netif, pkt, IPMPLS_RECURSION_LIMIT);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_tunnel_output
|
|
*===========================================================================
|
|
* Description: Outputs a packet transmitted via a MPLS tunnel interface.
|
|
* Parameters: netif - The network interface on which the packet should
|
|
* be sent.
|
|
* pkt - The packet to send.
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_tunnel_output(Ipcom_netif *netif, Ipcom_pkt *pkt)
|
|
{
|
|
Ipmpls_netif *mplsif = (Ipmpls_netif *) netif->plink;
|
|
|
|
ip_assert(netif != IP_NULL);
|
|
ip_assert(netif->type == IP_IFT_MPLS_TUNNEL);
|
|
|
|
if (mplsif->nhlfe == IP_NULL)
|
|
{
|
|
mplsif->nhlfe = ipmpls_route_lookup(pkt->vr_index, &mplsif->key);
|
|
|
|
if (mplsif->nhlfe == IP_NULL)
|
|
return -IP_ERRNO_EHOSTUNREACH;
|
|
|
|
netif->mtu = IPMPLS_NHLFE_MTU(mplsif->nhlfe);
|
|
ipmpls_nhlfe_lock(mplsif->nhlfe);
|
|
}
|
|
|
|
if (netif->link_tap)
|
|
{
|
|
/* Let the tap handler copy the packet */
|
|
netif->link_tap(netif, pkt, IP_PACKET_OUTGOING, 0, pkt->ipstart, pkt->ipstart);
|
|
}
|
|
|
|
return ipmpls_process_label(netif, mplsif->nhlfe, IP_TRUE, pkt, IPMPLS_RECURSION_LIMIT);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_shortcut_route_output
|
|
*===========================================================================
|
|
* Description: Outputs a packet forwarded via a MPLS pseudo wire route.
|
|
* Parameters: nhlfe - Pointer to the NHLFE to be used for forwarding.
|
|
* pkt - The packet to send.
|
|
* Returns: 0 = success, <0 = error code.
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_shortcut_route_output(Ip_u32 key, Ipcom_pkt *pkt)
|
|
{
|
|
Ipmpls_label label;
|
|
Ipmpls_nhlfe *nhlfe;
|
|
|
|
label.ml_type = IPMPLS_LABEL_TYPE_KEY;
|
|
label.u.ml_key = key;
|
|
|
|
nhlfe = ipmpls_route_lookup(pkt->vr_index,
|
|
ipmpls_key_create(0, &label, &key));
|
|
|
|
if (nhlfe == IP_NULL)
|
|
return -IP_ERRNO_EHOSTUNREACH;
|
|
|
|
return ipmpls_process_label(IP_NULL,
|
|
nhlfe,
|
|
IP_TRUE,
|
|
pkt,
|
|
IPMPLS_RECURSION_LIMIT);
|
|
}
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_shortcut_route_nhlfe_lookup
|
|
*===========================================================================
|
|
* Description: Keep for backwards compatiblilty.
|
|
* Parameters:
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_shortcut_route_nhlfe_lookup(struct Ip_sockaddr_mpls *sa_mpls)
|
|
{
|
|
(void)sa_mpls;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_if_init
|
|
*===========================================================================
|
|
* Description: MPLS tunnel interface constructor routine.
|
|
* Parameters: netif - MPLS tunnel interface
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_if_init(Ipcom_netif *netif)
|
|
{
|
|
Ipmpls_netif *mplsif;
|
|
|
|
/* Allocate MPLS private link data */
|
|
netif->plink = ipcom_malloc(sizeof(Ipmpls_netif));
|
|
mplsif = (Ipmpls_netif *) netif->plink;
|
|
if (mplsif == IP_NULL)
|
|
return -IP_ERRNO_ENOMEM;
|
|
|
|
ipcom_memset(mplsif, 0, sizeof(Ipmpls_netif));
|
|
|
|
netif->flags = IP_IFF_NOARP /* | IP_IFF_POINTOPOINT */;
|
|
netif->mtu = IPMPLS_DEFAULT_MTU;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_if_free
|
|
*===========================================================================
|
|
* Description: MPLS tunnel interface destructor routine.
|
|
* Parameters: netif - MPLS tunnel interface
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_if_free(Ipcom_netif *netif)
|
|
{
|
|
Ipmpls_netif *mplsif = (Ipmpls_netif *)netif->plink;
|
|
|
|
if (mplsif)
|
|
ipcom_free(mplsif);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_tunnel_ioctl
|
|
*===========================================================================
|
|
* Description: MPLS tunnel IOCTL handler
|
|
* Parameters: netif - MPLS tunnel interface
|
|
* cmd - IOCTL command
|
|
* data - IOCTL data
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC int
|
|
ipmpls_tunnel_ioctl(Ipcom_netif *netif, Ip_u32 cmd, void *data)
|
|
{
|
|
int ret = 0;
|
|
Ipmpls_netif *mplsif;
|
|
struct Ip_ifreq *ifreq = (struct Ip_ifreq *) data;
|
|
|
|
mplsif = (Ipmpls_netif *) netif->plink;
|
|
ip_assert(mplsif != IP_NULL);
|
|
|
|
switch(cmd)
|
|
{
|
|
case IP_SIOCXOPEN:
|
|
(void)ipcom_mpls_ioevent(netif, IPMPLS_EIOXIF_OPEN, IP_NULL, IP_NULL, IP_NULL);
|
|
if (mplsif->key)
|
|
(void)ipcom_mpls_ioevent(netif, IPMPLS_EIOXIF_RUNNING, IP_NULL, IP_NULL, IP_NULL);
|
|
break;
|
|
case IP_SIOCXCLOSE:
|
|
(void)ipcom_mpls_ioevent(netif, IPMPLS_EIOXIF_CLOSE, IP_NULL, IP_NULL, IP_NULL);
|
|
(void)ipcom_mpls_ioevent(netif, IPMPLS_EIOXIF_STOP, IP_NULL, IP_NULL, IP_NULL);
|
|
break;
|
|
case IPMPLS_SIOCMPLSTUNNELADDOUT:
|
|
if (ifreq == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
ipmpls_key_create(0, (Ipmpls_label *) &ifreq->ip_ifr_data, &mplsif->key);
|
|
|
|
if (IP_BIT_ISSET(netif->flags, IP_IFF_UP))
|
|
(void)ipcom_mpls_ioevent(netif, IPMPLS_EIOXIF_RUNNING, IP_NULL, IP_NULL, IP_NULL);
|
|
break;
|
|
case IPMPLS_SIOCMPLSTUNNELDELOUT:
|
|
{
|
|
Ipmpls_key key;
|
|
|
|
if (ifreq == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
ipmpls_key_create(0, (Ipmpls_label *) &ifreq->ip_ifr_data, &key);
|
|
|
|
if (key == mplsif->key)
|
|
{
|
|
ipmpls_nhlfe_unlock(mplsif->nhlfe);
|
|
|
|
mplsif->nhlfe = IP_NULL;
|
|
mplsif->key = 0;
|
|
netif->mtu = IPMPLS_DEFAULT_MTU;
|
|
|
|
IP_BIT_CLR(netif->flags, IP_IFF_RUNNING);
|
|
}
|
|
}
|
|
break;
|
|
case IPMPLS_SIOCMPLSTUNNELGETOUT:
|
|
|
|
if (ifreq == IP_NULL)
|
|
return -IP_ERRNO_EINVAL;
|
|
|
|
{
|
|
Ipmpls_label *label = (Ipmpls_label *) &ifreq->ip_ifr_data;
|
|
|
|
label->ml_type = IPMPLS_KEY_GET_TYPE(mplsif->key);
|
|
label->u.ml_key = IPMPLS_KEY_GET_LABEL(mplsif->key);
|
|
label->ml_index = 0;
|
|
}
|
|
break;
|
|
default:
|
|
ret = -IP_ERRNO_EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipmpls_route_table_notify
|
|
*===========================================================================
|
|
* Description: MPLS route table handler
|
|
* Parameters: vr - vr index
|
|
* is_added - boolean (added or deleted)
|
|
*
|
|
* Returns: 0 Ok
|
|
* <0 Failed
|
|
*
|
|
*/
|
|
IP_PUBLIC void
|
|
ipmpls_route_table_notify(Ip_u16 vr, Ip_bool is_added)
|
|
{
|
|
Ipmpls_route *tree;
|
|
|
|
if (vr == 0)
|
|
return;
|
|
|
|
if (is_added)
|
|
(void)ipmpls_route_table_create(vr);
|
|
else
|
|
{
|
|
/* Delete table */
|
|
tree = (Ipmpls_route*)ipcom_hash_get(ipmpls->label_tables, &vr);
|
|
|
|
if (tree)
|
|
{
|
|
/* Flush tree from ILMs and NHLFEs */
|
|
(void)ipmpls_route_flush_tree(vr, IPMPLS_LABEL_TYPE_GEN);
|
|
(void)ipmpls_route_flush_tree(vr, IPMPLS_LABEL_TYPE_KEY);
|
|
|
|
ipcom_route_free_table(tree->head);
|
|
if (ipcom_hash_remove(ipmpls->label_tables, tree) != IPCOM_SUCCESS)
|
|
IP_PANIC();
|
|
ipcom_free(tree);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
int ipmpls_empty_file = 1;
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|
|
|