/* ****************************************************************************** * SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipl2tp_api.c,v $ $Revision: 1.17.10.2 $ * $Source: /home/interpeak/CVSRoot/ipl2tp/src/ipl2tp_api.c,v $ * $Author: hxu0 $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * ****************************************************************************** */ /* **************************************************************************** * DESCRIPTION **************************************************************************** * * Interpeak Layer 2 Tunneling Protocol - IPL2TP * --------------------------------------------- * * Compliant with the following RFCs and internet drafts: * RFC 2661 - L2TP * RFC 2233 - Interfaces MIB * RFC 2667 - IP Tunnel MIB * RFC 3371 - L2TP Management Information Base * */ /* **************************************************************************** * CONFIGURATION **************************************************************************** */ /* **************************************************************************** * INCLUDE FILES **************************************************************************** */ #include "ipl2tp_h.h" #include "ipl2tp_api.h" #include "ipl2tp_avp.h" #include "ipl2tp_trpt.h" #include "ipl2tp_util.h" #include "ipl2tp_attr_h.h" #include "ipl2tp_state.h" #include "ipl2tp_message.h" /* **************************************************************************** * DEFINES **************************************************************************** */ #define IPL2TP_PORT_NUMBER 1701 /* **************************************************************************** * TYPES **************************************************************************** */ /* **************************************************************************** * LOCAL DATA **************************************************************************** */ /* **************************************************************************** * PUBLIC DATA **************************************************************************** */ IP_PUBLIC int ipl2tp_occupied; /* **************************************************************************** * LOCAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipl2tp_api_attribute_vector_raw *=========================================================================== * Description: Parse a raw vector * Parameters: * Returns: * */ IP_STATIC Ip_bool ipl2tp_api_attribute_vector_raw(Ipl2tp_attrobj *attr, Ipl2tp_attr_info *info, char *ptr, int len) { if (ptr == IP_NULL) return IP_FALSE; if (ipcom_strncmp(ptr, "0x", 2) == 0) { int i; Ip_u8 *vec; char tbuf[3]; ptr += 2; len -= 2; len /= 2; vec = ipcom_malloc(len); if(vec == IP_NULL) { return IP_FALSE; } tbuf[2] = '\0'; for (i = 0; i < len; i++) { ipcom_memcpy(tbuf, ptr + i * 2, 2); vec[i] = (Ip_u8) ipcom_strtoul(tbuf, IP_NULL, 16); } ipl2tp_attr_vappend(attr, info->value, vec, len); ipcom_free(vec); return IP_TRUE; } return IP_FALSE; } /* **************************************************************************** * PUBLIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipl2tp_version *=========================================================================== * Description: Returns the IPL2TP version string. * Parameters: * Returns: Version string. * */ IP_PUBLIC const char * ipl2tp_version(void) { return "@(#) IPL2TP $Name: VXWORKS_ITER40_2019102207 $ - INTERPEAK_COPYRIGHT_STRING"; } /* *=========================================================================== * ipl2tp_api_version *=========================================================================== * Description: Returns the API version number. This function can be used * for applications to support older versions of IPL2TP. * Parameters: * Returns: The IPL2TP API version number. * */ IP_PUBLIC int ipl2tp_api_version(void) { return IPL2TP_VERSION_API; } /* *=========================================================================== * ipl2tp_api_init *=========================================================================== * Description: Initialize IPL2TP package * Parameters: * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_NOMEM if out of memory. * */ IP_PUBLIC Ipl2tp_status ipl2tp_api_init(void) { Ipl2tp_status status; /* Init data */ status = ipl2tp_data_init(); ipl2tp_occupied = 0; ipl2tp.state = IPL2TP_STATE_INIT; return status; } /* *=========================================================================== * ipl2tp_api_exit *=========================================================================== * Description: Free resources used by the IPL2TP package. * Parameters: * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_STATE if wrong state. * IPL2TP_STATUS_BUSY if tunnels/sessions open. */ IP_PUBLIC Ipl2tp_status ipl2tp_api_exit(void) { Ipl2tp_status status = IPL2TP_STATUS_OK; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_INIT) { IPCOM_LOG1(ERR, "%s: IPL2TP is open or not initialized", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; } else if (ipl2tp.open_tunnels != 0 || ipl2tp.open_sessions != 0) { IPCOM_LOG1(ERR, "%s: Called ipl2tp_exit() with open tunnels/sessions,", ipl2tp_versions[0]); IPCOM_LOG1(ERR, "%s: possibly caused by pending retransmissions", ipl2tp_versions[0]); status = IPL2TP_STATUS_BUSY; } else { ipl2tp_data_exit(); ipl2tp.state = IPL2TP_STATE_IDLE; } IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_open *=========================================================================== * Description: Open and configure IPL2TP with global attributes. * Parameters: attr - Global attributes. * tattr_v2 - Default V2 tunnel attributes. * tattr_v3 - Default V3 tunnel attributes. * sattr_v2 - Default V2 session attributes. * sattr_v3 - Default V3 session attributes. * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_STATE if wrong state. * IPL2TP_STATUS_BADPARAM if bad attribute object(s). */ IP_PUBLIC Ipl2tp_status ipl2tp_api_open(Ipl2tp_attrobj attr, Ipl2tp_attrobj tattr_v2, Ipl2tp_attrobj tattr_v3, Ipl2tp_attrobj sattr_v2, Ipl2tp_attrobj sattr_v3) { Ipl2tp_status status; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_INIT) { IPCOM_LOG1(ERR, "%s: IPL2TP is already open or not initialized", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; } else if ((status = ipl2tp_attr_global_set(attr)) != IPL2TP_STATUS_OK) { } else if ((status = ipl2tp_attr_tunnel_set(IP_NULL, &ipl2tp.tunnel.v2.attr, tattr_v2)) != IPL2TP_STATUS_OK) { } else if ((status = ipl2tp_attr_tunnel_set(IP_NULL, &ipl2tp.tunnel.v3.attr, tattr_v3)) != IPL2TP_STATUS_OK) { } else if ((status = ipl2tp_attr_session_set(IP_NULL, &ipl2tp.session.v2.attr, sattr_v2)) != IPL2TP_STATUS_OK) { } else if ((status = ipl2tp_attr_session_set(IP_NULL, &ipl2tp.session.v3.attr, sattr_v3)) != IPL2TP_STATUS_OK) { } else if (ipl2tp.wcallback == IP_NULL) { IPCOM_LOG1(ERR, "%s: Missing attribute: IPL2TP_ATTR_SET_L2TP_WRITE_CALLBACK", ipl2tp_versions[0]); status = IPL2TP_STATUS_ERROR; } if (status == IPL2TP_STATUS_OK) { ipl2tp.state = IPL2TP_STATE_OPEN; } IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_close *=========================================================================== * Description: Close IPL2TP. All tunnels and sessions are closed and deleted. * Parameters: * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_STATE if wrong state. */ IP_PUBLIC Ipl2tp_status ipl2tp_api_close(void) { Ipl2tp_status status = IPL2TP_STATUS_OK; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_OPEN) { IPCOM_LOG1(ERR, "%s: IPL2TP is not opened", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; } else { /* Teardown everything */ (void)ipl2tp_disable(); ipl2tp.state = IPL2TP_STATE_INIT; } IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_attribute_set *=========================================================================== * Description: Set global IPL2TP parameters. * Parameters: attr - Attribute object. * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_BADPARAM if bad attribute object. */ IP_PUBLIC Ipl2tp_status ipl2tp_api_attribute_set(Ipl2tp_attrobj attr) { Ipl2tp_status status; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_OPEN) { IPCOM_LOG1(ERR, "%s: IPL2TP is not opened", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; } else { /* Set global attributes */ status = ipl2tp_attr_global_set(attr); } IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_attribute_get *=========================================================================== * Description: Get global IPL2TP parameters. * Parameters: attr - Attribute object. * replies - Attribute replies. * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_BADPARAM if bad attribute object. */ IP_PUBLIC Ipl2tp_status ipl2tp_api_attribute_get(Ipl2tp_attrobj attr, Ipl2tp_attrobj *replies) { Ipl2tp_status status; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_OPEN) { IPCOM_LOG1(ERR, "%s: IPL2TP is not opened", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; } else { /* Set global attributes */ status = ipl2tp_attr_global_get(attr, replies); } IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_l2tp_input *=========================================================================== * Description: Feed an incoming L2TP packet to the L2TP engine. * Parameters: buffer - Memory buffer pointer. * payload - Pointer to packet payload, i.e. L2TP header. * length - Length of payload. * addr - IP address of sender. * transport - Transport type. UDP/IP. * Returns: IPL2TP_STATUS_OK if success. * IPL2TP_STATUS_BADID if the message session ID is invalid. * IPL2TP_STATUS_NOTL2TP if this is a non-L2TP message. * IPL2TP_STATUS_MALFORMED if the message is malformed. * IPL2TP_STATUS_IGNORE if the message is ignored. * IPL2TP_STATUS_TEARDOWN if a tunnel or session teardown is required. * Additional error codes may be returned by the user-specified * data callback routine. */ IP_PUBLIC Ipl2tp_status ipl2tp_api_l2tp_input(void *buffer, void *payload, Ip_u16 length, union Ip_sockaddr_union *addr, int transport) { Ipl2tp_status status = IPL2TP_STATUS_OK; Ip_u16 *flvp = payload; IPL2TP_OCCUPIED(return IPL2TP_STATUS_OCCUPIED); if (ipl2tp.state != IPL2TP_STATE_OPEN) { IPCOM_LOG1(ERR, "%s: IPL2TP is not opened", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; goto leave; } switch (transport) { case IPL2TP_ATTR_TRANSPORT_UDP: /* Basic transport for v2 and v3 */ switch (IP_GET_NTOHS(flvp) & IPL2TP_VERSION_MASK) { case IPL2TP_VERSION_L2TPV2: if (ipl2tp.enable_v2 == IPL2TP_MIB_DISABLED) { if (ipl2tp.enable_v3 == IPL2TP_MIB_DISABLED) { IPCOM_LOG1(ERR, "%s: IPL2TPV2 is not enabled", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; goto leave; } /* FALLTHROUGH TO v3 parsing */ } else { status = ipl2tp_v2_l2tp_input(buffer, payload, length, addr); if (status != IPL2TP_STATUS_NEGOTIATION) break; /* FALLTHROUGH TO v3 parsing */ } case IPL2TP_VERSION_L2TPV3: if (ipl2tp.enable_v3 == IPL2TP_MIB_DISABLED) { IPCOM_LOG1(ERR, "%s: IPL2TPV3 is not enabled", ipl2tp_versions[0]); status = IPL2TP_STATUS_STATE; goto leave; } status = ipl2tp_v3_l2tp_input(buffer, payload, length, addr, transport); break; default: IPCOM_LOG2(INFO, "%s: Received non-L2TP message, Ver: %d", ipl2tp_versions[0], IP_GET_NTOHS(flvp) & IPL2TP_VERSION_MASK); status = IPL2TP_STATUS_NOTL2TP; goto leave; } break; case IPL2TP_ATTR_TRANSPORT_IP: /* Transport for v3 */ status = ipl2tp_v3_l2tp_input(buffer, payload, length, addr, transport); break; } leave: IPL2TP_AVAILABLE(); return status; } /* *=========================================================================== * ipl2tp_api_errname *=========================================================================== * Description: Get error message. * Parameters: status Status code. * Returns: A human readable description of the error. * */ IP_PUBLIC CONST char * ipl2tp_api_errname(Ipl2tp_status status) { static CONST char *ipl2tp_status_string[] = { "IPL2TP_STATUS_OK: Success", "IPL2TP_STATUS_ERROR: General error", "IPL2TP_STATUS_IGNORE: Message or AVP ignored", "IPL2TP_STATUS_TEARDOWN: Tunnel or session teardown", "IPL2TP_STATUS_BADAUTH: Authentication failure", "IPL2TP_STATUS_BADAVP: Invalid attribute", "IPL2TP_STATUS_BADBEARER: Bearer type not in capabilities", "IPL2TP_STATUS_BADFRAMING: Framing type not in capabilities", "IPL2TP_STATUS_BADID: An invalid ID was specified", "IPL2TP_STATUS_BADMTYPE: Invalid message type", "IPL2TP_STATUS_BADPARAM: Bad parameter", "IPL2TP_STATUS_BADRECWIN: Invalid receive window size", "IPL2TP_STATUS_BUSY: Tunnel or session open", "IPL2TP_STATUS_CLOSE: Tunnel or session closed", "IPL2TP_STATUS_DRAIN: Tunnel or session creation disabled", "IPL2TP_STATUS_EXISTS: Tunnel or session exists", "IPL2TP_STATUS_FRAMINGCAPS: No common framing capabilities", "IPL2TP_STATUS_GOTCDN: Received CDN", "IPL2TP_STATUS_GOTSTOPCCN: Received StopCCN", "IPL2TP_STATUS_IDZERO: A zero ID was specified", "IPL2TP_STATUS_MALFORMED: Malformed message", "IPL2TP_STATUS_NOLNS: LNS operation not supported", "IPL2TP_STATUS_NOMEM: Out of memory", "IPL2TP_STATUS_NORAND: No random vector received", "IPL2TP_STATUS_NOSECRET: No shared secret configured", "IPL2TP_STATUS_NOTINIT: Item not initialized", "IPL2TP_STATUS_NOTL2TP: Non-L2TP message received", "IPL2TP_STATUS_OCCUPIED: API already occupied", "IPL2TP_STATUS_PROTVERSION: Protocol version mismatch", "IPL2TP_STATUS_RESERVEDNZ: Reserved bits non-zero", "IPL2TP_STATUS_STATE: State machine error", "IPL2TP_STATUS_TIMEOUT: Control channel timeout", "IPL2TP_STATUS_VENDOR: Unsupported vendor-specific AVP", "IPL2TP_STATUS_BADPW: Bad pseudowire type", "IPL2TP_STATUS_BADL2: Unknown L2 Sublayer", "IPL2TP_STATUS_BADSEQUENCING: Unknown data sequencing method, or no L2 Sublayer", "IPL2TP_STATUS_BADVERSION: Bad version", "IPL2TP_STATUS_CIRCUIT_DOWN: Circuit is down", }; return (status < (sizeof(ipl2tp_status_string) / sizeof(ipl2tp_status_string[0])) ? ipl2tp_status_string[status] : ""); } /* *=========================================================================== * ipl2tp_api_attribute_find *=========================================================================== * Description: Locate an attribute in the list based on name using strtok. * Parameters: info - The attribute list. * name - The attribute name * value - The pointer to where the attribute value starts. * Returns: The attribute information structure. * */ IP_PUBLIC Ipl2tp_attr_info * ipl2tp_api_attribute_find(Ipl2tp_attr_info *info, char *name, char **value) { int i; char *token; if ((token = ipcom_strtok_r(name, " \t=,", value)) == IP_NULL) return IP_NULL; for (i = 0; info[i].value != 0; i++) { if (ipcom_strcasecmp(info[i].name, token) == 0) return &info[i]; } return IP_NULL; } /* *=========================================================================== * ipl2tp_api_attribute_parse *=========================================================================== * Description: Parse an attribute value using strtok * Parameters: attr - The attribute to append the value to. * info - The attribute information object * value - The pointer containing the value offset * Returns: * */ IP_PUBLIC Ip_bool ipl2tp_api_attribute_parse(Ipl2tp_attrobj *attr, Ipl2tp_attr_info *info, char **value) { int i; char *token; switch (info->type) { case IPL2TP_ATTR_TYPE_STRING: return (ipl2tp_attr_strappend(attr, info->value, *value) == IPL2TP_STATUS_OK) ? IP_TRUE : IP_FALSE; case IPL2TP_ATTR_TYPE_INT: if (info->hr) { /* first token */ if ((token = ipcom_strtok_r(IP_NULL, " \t=,", value)) == IP_NULL) return IP_FALSE; for (i = 0; info->hr[i] != IP_NULL; i++) { if (ipcom_strlen(info->hr[i]) == 0) continue; if (ipcom_strcasecmp(info->hr[i], token) == 0) { return (ipl2tp_attr_iappend(attr, info->value, i) == IPL2TP_STATUS_OK) ? IP_TRUE : IP_FALSE; } } return IP_FALSE; } /* Fall through */ case IPL2TP_ATTR_TYPE_SEC: /* first token */ if ((token = ipcom_strtok_r(IP_NULL, " \t=,", value)) == IP_NULL) return IP_FALSE; return (ipl2tp_attr_iappend(attr, info->value, (int)ipcom_strtoul(token, IP_NULL, 10)) == IPL2TP_STATUS_OK) ? IP_TRUE : IP_FALSE; case IPL2TP_ATTR_TYPE_INT64: if ((token = ipcom_strtok_r(IP_NULL, " \t=,", value)) == IP_NULL) return IP_FALSE; else { Ip_u64 val; IP_U64_SET(val, 0, ipcom_strtoul(token, IP_NULL, 10)); ipl2tp_attr_iappend64(attr, info->value, &val); } return IP_TRUE; case IPL2TP_ATTR_TYPE_ADDRESS: { union Ip_sockaddr_union uaddr; /* first token */ if ((token = ipcom_strtok_r(IP_NULL, " \t=,", value)) == IP_NULL) return IP_FALSE; if (ipl2tp_api_attribute_address_parse(token, &uaddr) == IP_FALSE) return IP_FALSE; ipl2tp_attr_addrappend(attr, info->value, &uaddr); } return IP_TRUE; case IPL2TP_ATTR_TYPE_FUNCTION: case IPL2TP_ATTR_TYPE_POINTER: if ((token = ipcom_strtok_r(IP_NULL, " \t=,", value)) == IP_NULL) return IP_FALSE; else { unsigned long data = ipcom_strtoul(token, IP_NULL, 10); if (ipl2tp_attr_pappend(attr, info->value, (void *)data) != IPL2TP_STATUS_OK) return IP_FALSE; } return IP_TRUE; case IPL2TP_ATTR_TYPE_VECTOR: { int len; if (*value == IP_NULL) { len = 0; ipl2tp_attr_vappend(attr, info->value, *value, len); return IP_TRUE; } len = (int)ipcom_strlen(*value); if (ipl2tp_api_attribute_vector_raw(attr,info,*value,len) == IP_FALSE) { /* Append the vector */ ipl2tp_attr_vappend(attr, info->value, *value, len); } } return IP_TRUE; case IPL2TP_ATTR_TYPE_VEC8: { int len; if (*value == IP_NULL) len = 0; else len = (int)ipcom_strlen(*value); if (ipl2tp_api_attribute_vector_raw(attr,info,*value,len) == IP_FALSE) { Ip_u8 *vec = ipcom_malloc(len * sizeof(Ip_u8)); if (vec == IP_NULL) return IP_FALSE; i = 0; while ((token = ipcom_strtok_r(IP_NULL, " ,", value)) != IP_NULL) { Ip_u8 v8; if (info->hr) { /* first token */ for (i = 0; info->hr[i] != IP_NULL; i++) { if (ipcom_strlen(info->hr[i]) == 0) continue; if (ipcom_strcasecmp(info->hr[i], token) == 0) { v8 = (Ip_u8)i; goto set_vec8; } } /* Didn't find it */ ipcom_free(vec); return IP_FALSE; } else { v8 = (Ip_u8)ipcom_strtoul(token, IP_NULL, 10); } set_vec8: vec[i++] = (Ip_u8)v8; } ipl2tp_attr_vappend(attr, info->value, vec, i); ipcom_free(vec); } } return IP_TRUE; case IPL2TP_ATTR_TYPE_VEC16: { int len; if (*value == IP_NULL) len = 0; else len = (int)ipcom_strlen(*value); if (ipl2tp_api_attribute_vector_raw(attr,info,*value,len) == IP_FALSE) { Ip_u16 *vec = ipcom_malloc(len * sizeof(Ip_u16)); if(vec == IP_NULL) { return IP_FALSE; } i = 0; while ((token = ipcom_strtok_r(IP_NULL, " ,", value)) != IP_NULL) { Ip_u16 v16; if (info->hr) { /* first token */ for (i = 0; info->hr[i] != IP_NULL; i++) { if (ipcom_strlen(info->hr[i]) == 0) continue; if (ipcom_strcasecmp(info->hr[i], token) == 0) { v16 = (Ip_u16)i; goto set_vec16; } } ipcom_free(vec); return IP_FALSE; } else { v16 = (Ip_u16)ipcom_strtoul(token, IP_NULL, 10); } set_vec16: vec[i++] = v16; } ipl2tp_attr_vappend(attr, info->value, vec, i * (int)sizeof(Ip_u16)); ipcom_free(vec); } } return IP_TRUE; default: case IPL2TP_ATTR_TYPE_KEY: case IPL2TP_ATTR_TYPE_NONE: break; } return IP_FALSE; } /* *=========================================================================== * ipl2tp_api_attribute_address_parse *=========================================================================== * Description: Parse addresses. Both v4 and v6. * Parameters: str - The address to parse. * uaddr - Where to store the result. * Returns: IP_TRUE if parsing was successful. * */ IP_PUBLIC Ip_bool ipl2tp_api_attribute_address_parse(char *str, union Ip_sockaddr_union *uaddr) { ipcom_memset(uaddr, 0, sizeof(*uaddr)); if (ipcom_inet_pton(IP_AF_INET, str, &uaddr->sin.sin_addr) > 0) { uaddr->sin.sin_family = IP_AF_INET; uaddr->sin.sin_len = sizeof(struct Ip_sockaddr_in); uaddr->sin.sin_port = ip_htons(IPL2TP_PORT_NUMBER); return IP_TRUE; } #ifdef IPCOM_USE_INET6 if (ipcom_inet_pton(IP_AF_INET6, str, &uaddr->sin6.sin6_addr) > 0) { uaddr->sin6.sin6_family = IP_AF_INET6; uaddr->sin6.sin6_len = sizeof(struct Ip_sockaddr_in6); uaddr->sin6.sin6_port = ip_htons(IPL2TP_PORT_NUMBER); return IP_TRUE; } #endif /* No go */ return IP_FALSE; } /* *=========================================================================== * ipl2tp_api_attribute_info *=========================================================================== * Description: Map a tag to an info structure * Parameters: tag - The tag. * Returns: The attribute's information structure. * */ IP_PUBLIC Ipl2tp_attr_info * ipl2tp_api_attribute_info(int tag) { int i; Ipl2tp_attr_info *array; if (tag < IPL2TP_ATTR_END_ENUM) array = ipl2tp_tags; else if (tag < IPL2TP_TATTR_END_ENUM) array = ipl2tp_tunnel_tags; else array = ipl2tp_session_tags; for (i = 0; array[i].value != 0; i++) { if (array[i].value == (Ip_u32)tag) return &array[i]; } return IP_NULL; } /* **************************************************************************** * END OF FILE **************************************************************************** */