/* ****************************************************************************** * INTERPEAK SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipmipmn_if.c,v $ $Revision: 1.33.10.2 $ * $Source: /home/interpeak/CVSRoot/ipmipmn/src/ipmipmn_if.c,v $ * $Author: jsheng $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Joel Hortelius ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ #include "ipmipmn_config.h" /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #define IPCOM_USE_CLIB_PROTO #include #include #include #include #include #include #include #include #include #include #include /* for IP_IFT_ETHER */ #include #include #include #ifdef IPIPSEC2 #include #include #endif #ifdef IPIKE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipmipmn.h" #include "ipmipmn_h.h" #include "ipmipmn_if.h" #include "ipmipmn_pktbuf.h" #include "ipmipmn_route.h" #include "ipmipmn_node.h" /* **************************************************************************** * 4 DEFINES **************************************************************************** */ /* **************************************************************************** * 5 TYPES **************************************************************************** */ typedef struct Ipmipmn_if_sainfo { Ip_bool tunnel; Ip_u32 srcaddr; } Ipmipmn_if_sainfo_t; /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 8 DATA **************************************************************************** */ /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipmipmn_if_coa_reference *=========================================================================== * Description: Reference the CoA * Parameters: data - Context * proc - Process * coa - CoA * Returns: The CoA * */ IP_STATIC void ipmipmn_if_coa_reference_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_api_coa_t *coa, Ip_bool sysref, const char *reason) { IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); if (!sysref || !coa->sysref) { if (sysref) coa->sysref = IP_TRUE; coa->refcnt++; IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA=%s upped refcount (%s) to %d (%s)", coa->name, sysref? "system" : "normal", coa->refcnt, reason)); } } /* *=========================================================================== * ipmipmn_if_coa_dereference_iflocked *=========================================================================== * Description: Reference the CoA * Parameters: data - Context * proc - Process * coa - CoA * Returns: The CoA * */ IP_STATIC Ip_bool ipmipmn_if_coa_dereference_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_api_coa_t *coa, Ip_bool sysref, const char *reason) { IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); if (!sysref || coa->sysref) { if (sysref) coa->sysref = IP_FALSE; coa->refcnt--; IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA=%s downed refcount (%s) to %d (%s)", coa->name, sysref? "system" : "normal", coa->refcnt, reason)); return coa->refcnt == 0; } return IP_FALSE; } /* *=========================================================================== * ipmipmn_if_notify_iflocked *=========================================================================== * Description: Schedule callbacks. * Parameters: data - Context * proc - Process * ifp - Interface * Returns: * */ IP_STATIC void ipmipmn_if_notify_iflocked(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp) { Ipmipmn_iff_notify_t *notifier; for (ifp = IPCOM_LIST_FIRST(&data->ifhead); ifp != IP_NULL; ifp = IPCOM_LIST_NEXT(&ifp->iff.ipmip.next)) { for (notifier = IPCOM_LIST_FIRST(&ifp->notifiers); notifier != IP_NULL; notifier = IPCOM_LIST_NEXT(¬ifier->if_next)) { ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, ¬ifier->timer); ipmip_timer_request(&data->api.ipmip4.ipmip, proc, ¬ifier->timer, notifier->lock, notifier->func, notifier->obj, ifp, 0); } } } /* *=========================================================================== * ipmipmn_if_ifstate_iflocked *=========================================================================== * Description: Verify interface state * Parameters: data - Context * proc - Process * ifp - The interface * Returns: * */ IP_STATIC void ipmipmn_if_ifstate_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { Ip_bool down = ifp->admin_down || ifp->link_down; if (down != ifp->down) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Interface %s is administratively brought %s", ifp->iff.ipmip.ifname, down? "down" : "up")); ifp->down = down; if (ifp->down) ifp->sync_count++; ipmipmn_if_notify_iflocked(data, proc, ifp); } } /* *=========================================================================== * ipmipmn_route_destroy_tunnel *=========================================================================== * Description: Destroy a particular tunnel. * Parameters: tunnel - The tunnel to destroy. * Returns: */ IP_STATIC void ipmipmn_if_destroy(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *iff) { /* No tunnel, don't bother */ if (iff->iff.ipmip.ifindex == 0) return; IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); } /* *=========================================================================== * ipmipmn_if_get_iflocked *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC Ipmipmn_iff_t * ipmipmn_if_get_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, const char *name) { /* Go through the interface list */ Ipmipmn_iff_t *ifp = (Ipmipmn_iff_t *)ipmip_if_findbyname(&data->ifhead, (char *)name); IPMIP_UNUSED_PARAMETER(proc); if (ifp != IP_NULL) ifp->references++; return ifp; } IP_STATIC Ipmipmn_iff_t * ipmipmn_if_getbyindex_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, int ifindex) { Ipmipmn_iff_t *ifp; IPMIP_UNUSED_PARAMETER(proc); /* Go through the interface list */ ifp = (Ipmipmn_iff_t *)ipmip_if_findbyindex(&data->ifhead, ifindex); if (ifp != IP_NULL) ifp->references++; return ifp; } /* *=========================================================================== * ipmipmn_if_coa_put_iflocked *=========================================================================== * Description: Return a previously retrieved CoA * Parameters: data - Context * proc - Process * ifp - Interface * coa - CoA * Returns: IP_TRUE if the CoA was deleted. * */ IP_STATIC Ip_bool ipmipmn_if_coa_put_iflocked(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa, Ip_bool hide, Ip_bool system, const char *reason) { Ip_bool dele = IP_FALSE; if (ipmipmn_if_coa_dereference_iflocked(data, proc, coa, system, reason)) { dele = IP_TRUE; if (ipmipmn_if_coa_valid(coa)) ipcom_list_remove(&coa->next); ifp->allocated--; IPMIP4_EVENT_IF(DEBUG, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA '%s' removed from interface '%s' (%d)", coa->name, ifp->iff.ipmip.ifname, ifp->allocated)); switch (coa->type) { case IPMIPMN_ATTR_COA_CCOA: /* FALL THROUGH */ case IPMIPMN_ATTR_COA_SCOA_TIA: case IPMIPMN_ATTR_COA_SCOA_PROTECTED: break; case IPMIPMN_ATTR_COA_FA: case IPMIPMN_ATTR_COA_FA_LLH: ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &coa->params.fa.timer); if (coa->params.fa.lladdr) ipcom_free(coa->params.fa.lladdr); if (coa->params.fa.lladdr_llh) ipcom_free(coa->params.fa.lladdr_llh); if (coa->params.fa.challenge) ipcom_free(coa->params.fa.challenge); break; case IPMIPMN_ATTR_COA_HA: ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &coa->params.ha.timer); if (coa->params.ha.lladdr) ipcom_free(coa->params.ha.lladdr); break; } ipcom_free(coa->name); ipcom_free(coa); } else if (hide) { if (ipmipmn_if_coa_valid(coa)) ipcom_list_remove(&coa->next); } return dele; } IP_STATIC int ipmipmn_if_agent_timeout_iflocked(int event, Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_api_coa_t *agent, Ipmipmn_iff_t *ifp) { /**/ if (event == IPMIP_TIMER_EVENT_RUN) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA '%s' timed out", agent->name)); ipmipmn_if_coa_put_iflocked(data, proc, ifp, agent, IP_TRUE, IP_TRUE, "CoA Timed Out"); ipmipmn_if_notify_iflocked(data, proc, ifp); } return IPMIP_TIMER_RET_DONE; } IP_STATIC Ip_bool ipmipmn_if_put_iflocked(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { if (--ifp->references == 0) { Ipmipmn_api_coa_t *coa; for (coa = IPCOM_LIST_FIRST(&ifp->coa); coa != IP_NULL;) { Ipmipmn_api_coa_t *ncoa = IPCOM_LIST_NEXT(&coa->next); ipmipmn_if_coa_put_iflocked(data, proc, ifp, coa, IP_FALSE, IP_TRUE, "Interface removed"); coa = ncoa; } /* MUST BE EMPTY, OR WE'LL HAVE DANGLING REFERENCES */ ip_assert(ifp->coa.size == 0); ip_assert(ifp->allocated == 0); data->ifalloc--; IPMIP4_EVENT_IF(DEBUG, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Interface '%s' removed (%d)", ifp->iff.ipmip. ifname, data->ifalloc)); /* Free the interface */ ipmip_if_free(&data->api.ipmip4.ipmip, proc, &ifp->iff.ipmip); return IP_TRUE; } return IP_FALSE; } #ifdef IPIPSEC2 IP_STATIC Ip_bool ipmipmn_if_get_sainfo(Ipipsec_sa_handle *sa, Ipmipmn_if_sainfo_t *sainfo) { Ip_tag tags[7]; int retval; /* Get additional information. */ tags[0] = IPIPSEC_SACTRLT_GET_Encapsulation; tags[1] = 0; tags[2] = IPIPSEC_SACTRLT_GET_SrcAddr; tags[3] = 0; tags[4] = IP_TAG_END; retval = ipipsec_sactrl(sa, tags); if (retval != IPCOM_SUCCESS) { ipcom_printf("ipsecctrl: ipipsec_sactrl(info) failed, error = %d"IP_LF, retval); return IP_FALSE; } sainfo->tunnel = tags[1] == 1; sainfo->srcaddr = tags[3]; return IP_TRUE; } IP_STATIC Ip_bool ipmipmn_if_secure_verify_spd(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipipsec_ctrl_selector *sel, int dir) { const char *dstr = dir == IPIPSEC_SADIR_INPUT? "inbound" : "outbound"; /* Only interested in INET */ if (sel->domain != IP_AF_INET) return IP_FALSE; /* Only parse inbound to start with */ if (sel->direction != dir) return IP_FALSE; /* Require a SA */ if (sel->sa.domain != IP_AF_INET) return IP_FALSE; /* We got a selector; MUST BE WILDCARDED - NO PORTS or PROTOCOLS */ if (sel->LocalPort || sel->RemotePort || sel->Protocol) { IPMIP4_EVENT_IF(DEBUG, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Discarding %s selector due to specified protocol or ports", dstr)); return IP_FALSE; } return IP_TRUE; } IP_STATIC Ip_bool ipmipmn_if_secure_isbound(Ipmip_buffer_t *reply, Ip_u32 addr) { struct Ip_nlmsghdr *nlh = IPMIP_BUFFER_PTR(reply); Ip_size_t nlh_size = IPMIP_BUFFER_SIZE(reply); /* Verify that the address exists */ while (IP_NLMSG_OK(nlh, nlh_size)) { /**/ if (nlh->nlmsg_type == IP_RTM_NEWADDR) { struct Ip_ifaddrmsg *ifa = IP_NLMSG_DATA(nlh); struct Ip_rtattr *ifap[IP_IFA_MAX + 1]; /* Resolve this entry; I want the prefix length and the address */ ipmip_netlink_nlmsghdr_rta_parse(IP_IFA_MAX + 1, IP_IFA_RTA(ifa), IP_IFA_PAYLOAD(nlh), ifap); /* Found one */ if (ifap[IP_IFA_LOCAL] && addr == IP_GET_32ON8(IP_RTA_DATA(ifap[IP_IFA_LOCAL]))) return IP_TRUE; } /**/ nlh = IP_NLMSG_NEXT(nlh, nlh_size); } return IP_FALSE; } #ifdef IPIKE IP_STATIC void ipmipmn_if_ike_move(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa, Ip_u32 addr) { IPMIP4_EVENT_IF(DEBUG, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Trying to move IKE endpoint %s -> %s", ipmip_ntop_inet(proc, addr), ipmip_ntop_inet(proc, coa->addr))); IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(ifp); /* Is it started? */ if (ipcom_ipd_send("ipike", IPCOM_IPD_MSGTYPE_PING) == IPCOM_SUCCESS) { Ipmip_buffer_t buf; Ipcom_ipd_msg hdr; char *argbuf; ipmip_buffer_init(&buf); ipcom_memset(&hdr, 0, sizeof(hdr)); hdr.msgtype = IPIKE_CMD_MOBIKE; ipmip_buffer_append(&buf, &hdr, sizeof(hdr)); argbuf = ipmip_util_strdup("%s %s", ipmip_ntop_inet(proc, addr), ipmip_ntop_inet(proc, coa->addr)); if (argbuf) { ipmip_buffer_append(&buf, argbuf, ipcom_strlen(argbuf) + 1); if (ipcom_ipd_sendmsg("ipike", IPMIP_BUFFER_PTR(&buf), IPMIP_BUFFER_SIZE(&buf)) == IPCOM_SUCCESS) { IPMIP4_EVENT_IF(DEBUG, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Trying to move IKE endpoint %s -- success", argbuf)); } else { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Trying to move IKE endpoint %s -- failed", argbuf)); } ipcom_free(argbuf); } ipmip_buffer_release(&buf); } else { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "IKE not started")); } } #endif /* IPIKE */ #endif IP_STATIC int ipmipmn_if_coa_type(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmip_buffer_t *reply, Ip_u32 addr, Ipmipmn_api_coa_params_t *cparam) { #ifdef IPIPSEC2 Ip_err retval; Ipipsec_ctrl_selector isel; Ipipsec_ctrl_selector osel; Ipmipmn_if_sainfo_t sainfo; isel.Index = 0; /* get first selector. */ for(;;) { Ip_bool tia = IP_TRUE; /* Get first/next Selector */ retval = ipmip_util_ioctl(&data->api.ipmip4.ipmip, proc, IP_SIOCXIPSEC_SELECTOR, &isel); if(retval != IPCOM_SUCCESS) { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Selector print aborted: error = %d", retval)); goto bail_out; } /* */ if(isel.Index == 0) break; /* Do verify the SPD */ if (!ipmipmn_if_secure_verify_spd(data, proc, &isel, IPIPSEC_SADIR_INPUT)) continue; if (addr != isel.RemoteId.in.s_addr) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Non-TIA address due to mismatching addresses (%s != %s)", ipmip_ntop_inet(proc, addr), ipmip_ntop_inet(proc, isel.RemoteId.in.s_addr))); tia = IP_FALSE; } if (tia && isel.remote_mask.in.s_addr != 0xffffffff) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Non-TIA to non-explicit host mask (%s)", ipmip_ntop_inet(proc, isel.remote_mask.in.s_addr))); tia = IP_FALSE; } /* Now; We got an inbound selector */ /* Must be protected by a different address */ if (tia && isel.RemoteId.in.s_addr == isel.sa.dst.in.s_addr) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Non-TIA due to sa destination and remote destination are equal (%s)", ipmip_ntop_inet(proc, isel.sa.dst.in.s_addr))); tia = IP_FALSE; } if (!tia) { /* Match against mask */ if ((addr & isel.remote_mask.in.s_addr) != (isel.RemoteId.in.s_addr & isel.remote_mask.in.s_addr)) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Discarding inbound selector (%s/%s) due to SPD not properly covering the address(%s)", ipmip_ntop_inet(proc, isel.RemoteId.in.s_addr), ipmip_ntop_inet(proc, isel.remote_mask.in.s_addr), ipmip_ntop_inet(proc, addr))); continue; } } else { /* Verify the scoa */ if (!ipmipmn_if_secure_isbound(reply, isel.RemoteId.in.s_addr)) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Discarding inbound selector due to remote destination not bound")); continue; } } /* Verify the end-point */ if (!ipmipmn_if_secure_isbound(reply, isel.sa.dst.in.s_addr)) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Discarding inbound selector due to SA destination not bound")); /**/ /* Current anchor address */ continue; } /* OK; Verify that we've gotten an outbound flow */ osel.Index = 0; /* get first selector. */ for(;;) { /* Get first/next Selector */ retval = ipmip_util_ioctl(&data->api.ipmip4.ipmip, proc, IP_SIOCXIPSEC_SELECTOR, &osel); if(retval != IPCOM_SUCCESS) { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Selector print aborted: error = %d", retval)); goto bail_out; } /* */ if(osel.Index == 0) break; /* Do verify the SPD */ if (!ipmipmn_if_secure_verify_spd(data, proc, &osel, IPIPSEC_SADIR_OUTPUT)) continue; /* Local origin must be the same as remote destination */ if (osel.LocalId.in.s_addr != isel.RemoteId.in.s_addr || osel.local_mask.in.s_addr != isel.remote_mask.in.s_addr) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Discarding outbound selector due inbound(%s)/outbound(%s) mismatch", ipmip_ntop_inet(proc, isel.RemoteId.in.s_addr), ipmip_ntop_inet(proc, osel.LocalId.in.s_addr))); continue; } if (!ipmipmn_if_get_sainfo(&osel.sa, &sainfo)) continue; /* Verify the end-point */ if (!ipmipmn_if_secure_isbound(reply, sainfo.srcaddr)) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Discarding outbound selector due to SA destination not bound")); /**/ /* Current anchor address */ continue; } if (tia && sainfo.tunnel == IP_FALSE) { IPMIP4_EVENT_IF(DEBUG2, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA Type/IPIPSEC: Non-TIA due to sa does not tunnel")); tia = IP_FALSE; } /* Store TIA information */ if (tia) { cparam->tia.ike_address = osel.sa.dst.in.s_addr; cparam->tia.local_address = sainfo.srcaddr; } /* We're successful; check this particular address */ if (tia) return IPMIPMN_ATTR_COA_SCOA_TIA; else return IPMIPMN_ATTR_COA_SCOA_PROTECTED; } } bail_out: #else IPMIP_UNUSED_PARAMETER(proc); IPMIP_UNUSED_PARAMETER(reply); IPMIP_UNUSED_PARAMETER(addr); IPMIP_UNUSED_PARAMETER(cparam); #endif return IPMIPMN_ATTR_COA_CCOA; } /* *=========================================================================== * ipmipmn_if_coa_relink_iflocked *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmipmn_if_coa_relink_iflocked(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa, Ip_bool first) { IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); if (ipmipmn_if_coa_valid(coa)) { ipcom_list_remove(&coa->next); if (first) ipcom_list_insert_first(&ifp->coa, &coa->next); else ipcom_list_insert_last(&ifp->coa, &coa->next); } } IP_STATIC int ipmipmn_if_coa_output_send(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_node_t *node, Ipmipmn_pktbuf_t *pbuf, struct Ipmipmn_api_coa *coa, Ip_bool deregister, Ip_u32 src, Ip_u32 dst, void *exdata) { IPMIP_UNUSED_PARAMETER(deregister); IPMIP_UNUSED_PARAMETER(coa); IPMIP_UNUSED_PARAMETER(exdata); if (node->api.params.mnRegCoA->peer == IPMIPMN_ATTR_PEER_FOREIGN_AGENT) { if (!ipmip4_pkt_udp_write(&data->api.ipmip4, proc, &pbuf->api.ipmip4.ipmip.packet, src, data->api.ipmip4.pkt->s_udp_out.sin_port, dst, ip_htons(IPMIP4_PORT), node->api.params.pubIf, node->api.params.mnRegCoA->params.fa.lladdr)) { IPMIP4_EVENT_IF(WARNING, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "%s unable to write request errno=%d", IPMIPMN_API_NODE_NAME(&data->api, proc, &node->api), ipcom_errno)); return -1; } } else if (node->api.params.mnRegCoA->type == IPMIPMN_ATTR_COA_HA) { if (!ipmip4_pkt_udp_write(&data->api.ipmip4, proc, &pbuf->api.ipmip4.ipmip.packet, src, data->api.ipmip4.pkt->s_udp_out.sin_port, dst, ip_htons(IPMIP4_PORT), node->api.params.pubIf, node->api.params.mnRegCoA->params.ha.lladdr)) { IPMIP4_EVENT_IF(WARNING, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "%s unable to send de-registration request errno=%d", IPMIPMN_API_NODE_NAME(&data->api, proc, &node->api), ipcom_errno)); return -1; } } else { if (ipmip4_pkt_udp_sendmsg(&data->api.ipmip4, proc, &pbuf->api.ipmip4.ipmip.packet, src, data->api.ipmip4.pkt->s_udp_out.sin_port, dst, ip_htons(IPMIP4_PORT), node->api.params.pubIf) == IP_FALSE) { IPMIP4_EVENT_IF(WARNING, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "%s unable to send request errno=%d", IPMIPMN_API_NODE_NAME(&data->api, proc, &node->api), ipcom_errno)); return -1; } } return 0; } /* *=========================================================================== * ipmipmn_if_coa_new_iflocked *=========================================================================== * Description: Create a CoA of a specific type. * Parameters: data - Context * proc - Process * ifp - Interface * addr - Address * type - Type of CoA * Returns: * */ IP_STATIC Ipmipmn_api_coa_t * ipmipmn_if_coa_new_iflocked(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ip_u32 addr, Ip_u32 type, Ip_bool first, Ipmipmn_api_coa_params_t *param) { Ipmipmn_api_coa_t *coa = ipcom_calloc(1, sizeof(*coa)); if (coa == IP_NULL) return IP_NULL; coa->type = type; coa->addr = addr; switch (type) { case IPMIPMN_ATTR_COA_CCOA: coa->name = ipmip_util_strdup("CCoA(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_SKIP; coa->route_reverse = IPMIPMN_ROUTE_TYPE_TUNNEL; /* Remove it from the obsolete list in case its there */ break; case IPMIPMN_ATTR_COA_SCOA_TIA: coa->params.tia = param->tia; coa->name = ipmip_util_strdup("CCoA-TIA(%s/%s/%s)", ipmip_ntop_inet(proc, addr), ipmip_ntop_inet(proc, param->tia.ike_address), ipmip_ntop_inet(proc, param->tia.local_address)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_SKIP; coa->route_reverse = IPMIPMN_ROUTE_TYPE_TUNNEL; break; case IPMIPMN_ATTR_COA_SCOA_PROTECTED: coa->name = ipmip_util_strdup("CCoA-Prot(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_SKIP; coa->route_reverse = IPMIPMN_ROUTE_TYPE_TUNNEL; break; case IPMIPMN_ATTR_COA_SCOA_DEAD: coa->params.tia = param->tia; coa->name = ipmip_util_strdup("CCoA-Dead(%s/%s/%s)", ipmip_ntop_inet(proc, addr), ipmip_ntop_inet(proc, param->tia.ike_address), ipmip_ntop_inet(proc, param->tia.local_address)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_SKIP; coa->route_reverse = IPMIPMN_ROUTE_TYPE_TUNNEL; break; case IPMIPMN_ATTR_COA_FA: coa->name = ipmip_util_strdup("FA(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_FOREIGN_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_FA; coa->route_reverse = IPMIPMN_ROUTE_TYPE_FA; IPMIP_TIMER_INIT(&coa->params.fa.timer); break; case IPMIPMN_ATTR_COA_FA_LLH: coa->name = ipmip_util_strdup("FA-LLH(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_FOREIGN_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_FA; coa->route_reverse = IPMIPMN_ROUTE_TYPE_FA; IPMIP_TIMER_INIT(&coa->params.fa.timer); break; case IPMIPMN_ATTR_COA_HA: coa->name = ipmip_util_strdup("HA(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_HOME; coa->route_reverse = IPMIPMN_ROUTE_TYPE_HOME; IPMIP_TIMER_INIT(&coa->params.ha.timer); break; default: coa->name = ipmip_util_strdup("unknown(%s)", ipmip_ntop_inet(proc, addr)); coa->peer = IPMIPMN_ATTR_PEER_HOME_AGENT; coa->route_ro = IPMIPMN_ROUTE_TYPE_BLACKHOLE; coa->route_reverse = IPMIPMN_ROUTE_TYPE_BLACKHOLE; break; } coa->output = (Ipmipmn_api_coa_output_t)ipmipmn_if_coa_output_send; ifp->allocated++; IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "CoA '%s' added to interface '%s' (%d)", coa->name, ifp->iff.ipmip.ifname, ifp->allocated)); ipmipmn_if_coa_reference_iflocked(data, proc, coa, IP_TRUE, "CoA Creation"); if (first) ipcom_list_insert_first(&ifp->coa, &coa->next); else ipcom_list_insert_last(&ifp->coa, &coa->next); return coa; } /* *=========================================================================== * ipmipmn_if_coa_get_iflocked *=========================================================================== * Description: Retrieve a CoA of a specific type. * Parameters: data - Context * proc - Process * ifp - Interface * addr - Address (may be 0) * type - Type of CoA * Returns: * */ IP_STATIC Ipmipmn_api_coa_t * ipmipmn_if_coa_get_iflocked(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ip_u32 addr, Ip_u32 type, Ip_bool down) { Ipmipmn_api_coa_t *coa; IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); for (coa = IPCOM_LIST_FIRST(&ifp->coa); coa != IP_NULL; coa = IPCOM_LIST_NEXT(&coa->next)) { if (coa->down && !down) continue; if (coa->type == type && (addr == 0 || coa->addr == addr)) break; } return coa; } /* *=========================================================================== * ipmipmn_if_attached *=========================================================================== * Description: * Parameters: * Returns: * */ IP_STATIC void ipmipmn_if_attached(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { /* */ if (ifp->iff.ipmip.ifindex != 0) { (void)ipmip_if_resolve_lla(&data->api.ipmip4.ipmip, proc, &ifp->iff.ipmip); /** JOIN **/ (void)ipmip4_pkt_icmp_join(&data->api.ipmip4, proc, ip_htonl(IP_INADDR_ALLHOSTS_GROUP), ifp->iff.ipmip.ifindex); (void)ipmip4_pkt_icmp_join(&data->api.ipmip4, proc, ip_htonl(IP_INADDR_ALLRTRS_GROUP), ifp->iff.ipmip.ifindex); } } /* **************************************************************************** * 10 GLOBAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipmipmn_if_acquire *=========================================================================== * Description: Acquire an instantiation of a interface * Parameters: data - Node context * proc - Current process * name - The name of the interface * Returns: The pointer to the interface structure on success. * */ IP_GLOBAL Ipmipmn_iff_t * ipmipmn_if_acquire(struct Ipmipmn_context *data, struct Ipmip_proc *proc, const char *name, Ipmipmn_iff_notify_t *notifier) { Ipmipmn_iff_t *ifp; Ip_bool update = IP_FALSE; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); ifp = ipmipmn_if_get_iflocked(data, proc, name); /* */ if (ifp == IP_NULL) { /* Create the interface */ ifp = (Ipmipmn_iff_t*)ipmip4_if_alloc(&data->api.ipmip4, proc, sizeof(*ifp), name, 0, IPMIP_IF_TYPE_GENERIC, IPMIPMN_IFF_IDENTITY_IFF, (Ipmip_if_cleanup_t )ipmipmn_if_destroy); /* Add */ if (ifp != IP_NULL) { Ip_u16 flags; ifp->references++; data->ifalloc++; ipcom_list_init(&ifp->notifiers); ipcom_list_init(&ifp->coa); ipcom_list_insert_last(&data->ifhead, &ifp->iff.ipmip.next); ifp->iff.ipmip.ifindex = ipcom_if_nametoindex(ifp->iff.ipmip.ifname); if (ifp->iff.ipmip.ifindex) ipmipmn_if_attached(data, proc, ifp); if (!ifp->iff.ipmip.ifindex || !ipmip_if_get_flags(&data->api.ipmip4.ipmip, proc, &ifp->iff.ipmip, &flags) || !IP_BIT_ISSET(flags, IP_IFF_UP) || !IP_BIT_ISSET(flags, IP_IFF_RUNNING)) { /* Not found; not running; not up */ ifp->link_down = IP_TRUE; ifp->down = IP_TRUE; } IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Interface '%s' added (%d)", ifp->iff.ipmip.ifname, data->ifalloc)); /* Populate interface */ update = IP_TRUE; } } /* */ if (ifp != IP_NULL && notifier != IP_NULL) ipcom_list_insert_last(&ifp->notifiers, ¬ifier->if_next); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); /* */ if (update) ipmipmn_if_update_coa(data, proc); return ifp; } /* *=========================================================================== * ipmipmn_if_reference *=========================================================================== * Description: Reference an interface pointer * Parameters: data - Node context * proc - Current process * ifp - The interface * Returns: The pointer to the interface structure on success. * */ IP_GLOBAL Ipmipmn_iff_t * ipmipmn_if_reference(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); ifp->references++; ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return ifp; } /* *=========================================================================== * ipmipmn_if_release *=========================================================================== * Description: Release a previously acquired interface reference. * Parameters: data - Context * proc - The process * iff - Interface pointer. * Returns: IP_TRUE if the interface was destroyed; IP_FALSE otherwise. * */ IP_GLOBAL Ip_bool ipmipmn_if_release(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *iff, Ipmipmn_iff_notify_t *notifier) { Ip_bool ret; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); if (notifier) { ipcom_list_remove(¬ifier->if_next); ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, ¬ifier->timer); } ret = ipmipmn_if_put_iflocked(data, proc, iff); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return ret; } /* *=========================================================================== * ipmipmn_if_update_coa *=========================================================================== * Description: Retrieve a secure address * Parameters: mn - Mobile node data * proc - Process * ifname - The interface name * addr - The address * netmask - The netmask * Returns: 0 * */ IP_GLOBAL int ipmipmn_if_update_coa(Ipmipmn_context_t *data, Ipmip_proc_t *proc) { /**/ Ip_u32 timestamp; Ipmipmn_iff_t *ifp; Ipmipmn_api_coa_t *coa; Ipmipmn_api_coa_params_t cparam; Ipmip_buffer_t reply; int ret; ipcom_memset(&cparam, 0, sizeof(cparam)); ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /**/ timestamp = data->ifstamp++; for (ifp = IPCOM_LIST_FIRST(&data->ifhead); ifp != IP_NULL; ifp = IPCOM_LIST_NEXT(&ifp->iff.ipmip.next)) { Ip_u16 flags; Ip_u32 ifindex; if ((ifindex = ipcom_if_nametoindex(ifp->iff.ipmip.ifname)) == 0 || !ipmip_if_get_flags(&data->api.ipmip4.ipmip, proc, &ifp->iff.ipmip, &flags) || !IP_BIT_ISSET(flags, IP_IFF_UP) || !IP_BIT_ISSET(flags, IP_IFF_RUNNING)) { if (!ifp->link_down) { ifp->link_down = IP_TRUE; if (!ifp->down) { ifp->sync_count++; ifp->down = IP_TRUE; ifp->timestamp = timestamp; IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Interface %s is brought down", ifp->iff.ipmip.ifname)); } } } else { if (ifp->link_down) { ifp->link_down = IP_FALSE; if (ifp->down && !ifp->admin_down) { ifp->down = IP_FALSE; ifp->timestamp = timestamp; IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Interface %s is brought up", ifp->iff.ipmip.ifname)); } } } /* Update IFINDEX */ if (ifindex != ifp->iff.ipmip.ifindex) { ifp->timestamp = timestamp; ifp->iff.ipmip.ifindex = ifindex; } if (!ifp->link_down) ipmipmn_if_attached(data, proc, ifp); } ipmip_buffer_init(&reply); if ((ret = ipmip_netlink_inX_addr_dump(&data->api.ipmip4.ipmip, proc, IP_AF_INET, &reply)) < 0) { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Unable to dump INET addresses error value %d", -ret)); } else { struct Ip_nlmsghdr *nlh = IPMIP_BUFFER_PTR(&reply); Ip_size_t nlh_size = IPMIP_BUFFER_SIZE(&reply); /* */ while (IP_NLMSG_OK(nlh, nlh_size)) { /**/ if (nlh->nlmsg_type == IP_RTM_NEWADDR) { struct Ip_ifaddrmsg *ifa = IP_NLMSG_DATA(nlh); struct Ip_rtattr *ifap[IP_IFA_MAX + 1]; ifp = ipmipmn_if_getbyindex_iflocked(data, proc, ifa->ifa_index); /* Don't have */ if (ifp != IP_NULL) { /* Parse */ ipmip_netlink_nlmsghdr_rta_parse(IP_IFA_MAX + 1, IP_IFA_RTA(ifa), IP_IFA_PAYLOAD(nlh), ifap); if (ifap[IP_IFA_LOCAL]) { Ip_u32 addr = IP_GET_32ON8(IP_RTA_DATA(ifap[IP_IFA_LOCAL])); int type = ipmipmn_if_coa_type(data, proc, &reply, addr, &cparam); switch (type) { case IPMIPMN_ATTR_COA_SCOA_PROTECTED: case IPMIPMN_ATTR_COA_SCOA_TIA: case IPMIPMN_ATTR_COA_SCOA_DEAD: case IPMIPMN_ATTR_COA_CCOA: coa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, addr, type, IP_TRUE); if (coa) { /* Timestamp it */ coa->timestamp = timestamp; /* Back in buisiness */ if (coa->down) { coa->down = IP_FALSE; ifp->timestamp = timestamp; /* Return the sysref */ ipmipmn_if_coa_reference_iflocked(data, proc, coa, IP_TRUE, "CoA Reappeared"); } ipmipmn_if_coa_relink_iflocked(data, proc, ifp, coa, IP_FALSE); } else { if (type == IPMIPMN_ATTR_COA_CCOA) { Ipmipmn_api_coa_t *scoa; /* Detached TIA */ if ((scoa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, addr, IPMIPMN_ATTR_COA_SCOA_DEAD, IP_FALSE)) != IP_NULL) { scoa->timestamp = timestamp; break; } else if ((scoa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, addr, IPMIPMN_ATTR_COA_SCOA_TIA, IP_FALSE)) != IP_NULL) { coa = ipmipmn_if_coa_new_iflocked(data, proc, ifp, addr, IPMIPMN_ATTR_COA_SCOA_DEAD, IP_FALSE, &scoa->params); coa->timestamp = timestamp; coa->params.tia.mobiked = IP_FALSE; ifp->timestamp = timestamp; ipmipmn_if_coa_put_iflocked(data, proc, ifp, scoa, IP_TRUE, IP_TRUE, "Address type changed"); break; } } else if (type == IPMIPMN_ATTR_COA_SCOA_TIA) { Ipmipmn_api_coa_t *scoa; if ((scoa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, addr, IPMIPMN_ATTR_COA_SCOA_DEAD, IP_FALSE)) != IP_NULL) { if (scoa->params.tia.mobiked) cparam.tia.mobiked = IP_TRUE; ipmipmn_if_coa_put_iflocked(data, proc, ifp, scoa, IP_TRUE, IP_TRUE, "CCoA-TIA reappered"); } } /* Add it */ coa = ipmipmn_if_coa_new_iflocked(data, proc, ifp, addr, type, IP_FALSE, &cparam); coa->timestamp = timestamp; ifp->timestamp = timestamp; } break; } } ipmipmn_if_put_iflocked(data, proc, ifp); } } /**/ nlh = IP_NLMSG_NEXT(nlh, nlh_size); } } ipmip_buffer_release(&reply); /* Remove any COA's not holding the correct timestamp */ /* Go through the interface list */ for (ifp = IPCOM_LIST_FIRST(&data->ifhead); ifp != IP_NULL; ifp = IPCOM_LIST_NEXT(&ifp->iff.ipmip.next)) { for (coa = IPCOM_LIST_FIRST(&ifp->coa); coa != IP_NULL;) { Ipmipmn_api_coa_t *ncoa = IPCOM_LIST_NEXT(&coa->next); /* Dont touch external CoA's */ if (coa->peer == IPMIPMN_ATTR_PEER_FOREIGN_AGENT) { if (ifp->down && !coa->down) { /**/ coa->down = IP_TRUE; /* Sysref? */ ipmipmn_if_coa_put_iflocked(data, proc, ifp, coa, IP_FALSE, IP_TRUE, "Interface down"); /* Flag the interface as being timestamped */ ifp->timestamp = timestamp; } } else if (coa->type != IPMIPMN_ATTR_COA_HA) { /* No longer around? */ if (coa->timestamp != timestamp) { coa->down = IP_TRUE; ipmipmn_if_coa_put_iflocked(data, proc, ifp, coa, IP_TRUE, IP_TRUE, "CoA Disappeared"); /* Flag the interface as being timestamped */ ifp->timestamp = timestamp; } } else { if (ifp->down && !coa->down) { coa->down = IP_TRUE; ipmipmn_if_coa_put_iflocked(data, proc, ifp, coa, IP_TRUE, IP_TRUE, "Interface down"); /* Flag the interface as being timestamped */ ifp->timestamp = timestamp; } else if (!ifp->down && ifp->timestamp == timestamp) { /* Something on this interface has changed */ if (coa->params.ha.verify == IPMIPMN_IFF_COA_HA_VERIFY_NONE) coa->params.ha.verify = IPMIPMN_IFF_COA_HA_VERIFY_REQUEST; } } coa = ncoa; } } /* Notify on all timestamped interfaces */ for (ifp = IPCOM_LIST_FIRST(&data->ifhead); ifp != IP_NULL; ifp = IPCOM_LIST_NEXT(&ifp->iff.ipmip.next)) { if (ifp->timestamp == timestamp) ipmipmn_if_notify_iflocked(data, proc, ifp); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return 0; } /* *=========================================================================== * ipmipmn_if_coa_get *=========================================================================== * Description: Retrieve a CoA of a specific type. * Parameters: data - Context * proc - Process * ifp - Interface * addr - Address (may be 0) * type - Type of CoA * Returns: * */ IP_GLOBAL Ipmipmn_api_coa_t * ipmipmn_if_coa_get(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ip_u32 addr, Ip_u32 type, const char *reason) { Ipmipmn_api_coa_t *coa; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); coa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, addr, type, IP_FALSE); if (coa) ipmipmn_if_coa_reference_iflocked(data, proc, coa, IP_FALSE, reason); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return coa; } /* *=========================================================================== * ipmipmn_if_coa_put *=========================================================================== * Description: Return a previously retrieved CoA * Parameters: data - Context * proc - Process * ifp - Interface * coa - CoA * Returns: IP_TRUE if the CoA was deleted. * */ IP_GLOBAL Ip_bool ipmipmn_if_coa_put(Ipmipmn_context_t *data, Ipmip_proc_t *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa, const char *reason) { Ip_bool dele; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); dele = ipmipmn_if_coa_put_iflocked(data, proc, ifp, coa, IP_FALSE, IP_FALSE, reason); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return dele; } /* *=========================================================================== * ipmipmn_if_findbyindex *=========================================================================== * Description: Find an existing interface by locating its index. * Parameters: data - Context * proc - Process * ifindex - Interface index. * Returns: A referenced handle to the interface. Must be returned by * release. * */ IP_GLOBAL Ipmipmn_iff_t * ipmipmn_if_findbyindex(struct Ipmipmn_context *data, struct Ipmip_proc *proc, int ifindex) { Ipmipmn_iff_t *ifp; IPMIP_UNUSED_PARAMETER(proc); ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /* Go through the interface list */ ifp = ipmipmn_if_getbyindex_iflocked(data, proc, ifindex); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return ifp; } /* *=========================================================================== * ipmipmn_if_fa_adv *=========================================================================== * Description: Process a foreign agent advertisement and update any * foreign agent values. * Parameters: data - Context * proc - Process * pbuf - Packet buffer. * Returns: * */ IP_GLOBAL void ipmipmn_if_fa_adv(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_pktbuf_t *pbuf) { Ipmipmn_api_coa_t *fa; Ip_bool change = IP_FALSE; Ip_bool unreliable = IP_FALSE; Ip_bool relinked = IP_FALSE; int type = pbuf->api.ipmip4.ext.icmp.lla.found? IPMIPMN_ATTR_COA_FA_LLH : IPMIPMN_ATTR_COA_FA; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /* Find it it? */ fa = ipmipmn_if_coa_get_iflocked(data, proc, pbuf->api.iff.priv, pbuf->api.advinfo.coa, type, IP_TRUE); if (fa != IP_NULL) { /* Check if FA rebooted */ if (pbuf->api.advinfo.adv_seq < fa->params.fa.adv_seq && pbuf->api.advinfo.adv_seq < 256) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Foreign Agent '%s' rebooted (seq=%d, oldseq=%d)", ipmip_ntop_inet(proc, fa->addr), (int)pbuf->api.advinfo.adv_seq, fa->params.fa.adv_seq)); fa->params.fa.revoked = IP_TRUE; change = IP_TRUE; ipmipmn_if_coa_put_iflocked(data, proc, pbuf->api.iff.priv, fa, IP_TRUE, IP_TRUE, "FA Rebooted"); fa = IP_NULL; goto create; } if (IP_BIT_ISSET(pbuf->api.advinfo.flags, IPMIP4_ICMP_ADVEXT_FLAG_BUSY)) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Foreign Agent '%s' reported as busy", ipmip_ntop_inet(proc, fa->addr))); change = IP_TRUE; fa->params.fa.revoked = IP_TRUE; ipmipmn_if_coa_put_iflocked(data, proc, pbuf->api.iff.priv, fa, IP_TRUE, IP_TRUE, "FA Busy"); fa = IP_NULL; goto done_out; } /* Seen again? */ if (fa->down) { fa->down = IP_FALSE; fa->unreliable = IP_FALSE; change = IP_TRUE; relinked = IP_TRUE; ipmipmn_if_coa_reference_iflocked(data, proc, fa, IP_TRUE, "Previously 'down' FA was advertised"); /* Relink first */ ipmipmn_if_coa_relink_iflocked(data, proc, pbuf->api.iff.priv, fa, IP_TRUE); } /* Detached? */ if (fa->unreliable) { fa->unreliable = IP_FALSE; unreliable = IP_TRUE; } } else { create: if (IP_BIT_ISSET(pbuf->api.advinfo.flags, IPMIP4_ICMP_ADVEXT_FLAG_BUSY)) goto done_out; relinked = IP_TRUE; fa = ipmipmn_if_coa_new_iflocked(data, proc, pbuf->api.iff.priv, pbuf->api.advinfo.coa, type, IP_TRUE, IP_NULL); } if (fa != IP_NULL) { /* Store the flags */ fa->params.fa.adv_flags = pbuf->api.advinfo.flags; /* Update params */ if (ipmip_lladdr_cmp(fa->params.fa.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr) == IP_FALSE) { change = IP_TRUE; /* We're using this one instead */ ipmip_util_dup(&fa->params.fa.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr->len); } /* LLH CoA */ if (pbuf->api.ipmip4.ext.icmp.lla.found > 0) { Ipmip_lladdr_t *lladdr; /* Relink first */ ipmipmn_if_coa_relink_iflocked(data, proc, pbuf->api.iff.priv, fa, IP_TRUE); change = IP_TRUE; relinked = IP_TRUE; lladdr = ipmip4_lla_llh_convert_lla_to_lladdr(IP_NULL, (Ipmip4_icmp_lla_ext_t *)pbuf->api.ipmip4.ext.icmp.lla.ext[0]); if (ipmip_lladdr_cmp(fa->params.fa.lladdr_llh, lladdr) == IP_FALSE) { /* We're using this one instead */ ipcom_free(fa->params.fa.lladdr_llh); fa->params.fa.lladdr_llh = lladdr; } else { ipcom_free(lladdr); } } /* Look for FA challenge extension */ if (fa->params.fa.challenge != IP_NULL) { ipcom_free(fa->params.fa.challenge); fa->params.fa.challenge = IP_NULL; } if (pbuf->api.ipmip4.ext.icmp.challenge) { /* Found challenge extension */ if (IPMIP4_MSGEXT_SHDR_EXT_SIZE(pbuf->api.ipmip4.ext.icmp.challenge) <= IPMIPMN_MAX_CHALLENGE_SIZE) { /* Save FA challenge */ fa->params.fa.challenge = ipmip4_msgext_shdr_dup(pbuf->api.ipmip4.ext.icmp.challenge); if(IP_NULL == fa->params.fa.challenge) goto done_out; fa->params.fa.challenge->shdr.type = IPMIP4_EXT_TYPE_AGENT_ADV_CHALLENGE; change = IP_TRUE; } else { IPMIP4_EVENT_IF(ERR, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Foreign Agent '%s' included challenge with a longer than supporter maximum size", ipmip_ntop_inet(proc, fa->addr))); } } /* Move us to be the FIRST foreign agent */ if (!relinked) { Ipmipmn_api_coa_t *coa; Ipmipmn_api_coa_t *ffa = IP_NULL; for (coa = IPCOM_LIST_PREV(&fa->next); coa != IP_NULL; coa = IPCOM_LIST_PREV(&coa->next)) { /**/ if (coa->type == IPMIPMN_ATTR_COA_FA) ffa = coa; } /* Found a previous entry? */ if (ffa != IP_NULL) { ipcom_list_remove(&fa->next); ipcom_list_insert_before(&ffa->next, &fa->next); change = IP_TRUE; } } /* Update */ fa->params.fa.adv_seq = pbuf->api.advinfo.adv_seq; if ((pbuf->api.advinfo.lifetime == 0 || fa->params.fa.lifetime == 0) && pbuf->api.advinfo.lifetime != fa->params.fa.lifetime) change = IP_TRUE; fa->params.fa.lifetime = pbuf->api.advinfo.lifetime; ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &fa->params.fa.timer); ipmip_timer_request(&data->api.ipmip4.ipmip, proc, &fa->params.fa.timer, data->iflock, (Ipmip_timer_func_t)ipmipmn_if_agent_timeout_iflocked, fa, pbuf->api.iff.priv, fa->params.fa.lifetime * 1000); } done_out: #if 1 if (unreliable && fa != IP_NULL) { Ipmipmn_api_coa_t *coa; /* Go through and locate and Home Agents located _AFTER_ the foreign agent, have we done * a 'silent' move? */ for (coa = IPCOM_LIST_PREV(&fa->next); coa != IP_NULL; coa = IPCOM_LIST_PREV(&coa->next)) { if (coa->down) continue; /**/ if (coa->type == IPMIPMN_ATTR_COA_HA && coa->params.ha.verify == IPMIPMN_IFF_COA_HA_VERIFY_NONE) { /* Need to do a directed solicitation on this HA; verify that * it's still available */ coa->params.ha.verify = IPMIPMN_IFF_COA_HA_VERIFY_REQUEST; change = IP_TRUE; } } } #endif /**/ if (change) { #if 1 if (relinked && fa != IP_NULL) { Ipmipmn_api_coa_t *coa; /* Go through and locate and Home Agents located _AFTER_ the foreign agent, have we done * a 'silent' move? */ for (coa = IPCOM_LIST_NEXT(&fa->next); coa != IP_NULL; coa = IPCOM_LIST_NEXT(&coa->next)) { if (coa->down) continue; /**/ if (coa->type == IPMIPMN_ATTR_COA_HA && coa->params.ha.verify == IPMIPMN_IFF_COA_HA_VERIFY_NONE) { /* Need to do a directed solicitation on this HA; verify that * it's still available */ coa->params.ha.verify = IPMIPMN_IFF_COA_HA_VERIFY_REQUEST; } } } #endif ipmipmn_if_notify_iflocked(data, proc, pbuf->api.iff.priv); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); } /* *=========================================================================== * ipmipmn_if_fa_arp *=========================================================================== * Description: Process an ARP reply and update any * foreign agent values. * Parameters: data - Context * proc - Process * pbuf - Packet buffer. * Returns: * */ IP_GLOBAL void ipmipmn_if_fa_arp(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_pktbuf_t *pbuf) { Ipmipmn_api_coa_t *fa; Ip_bool change = IP_FALSE; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /* Find it it? */ fa = ipmipmn_if_coa_get_iflocked(data, proc, pbuf->api.iff.priv, pbuf->api.advinfo.coa, IPMIPMN_ATTR_COA_FA, IP_TRUE); if (fa != IP_NULL) { /* Update params */ if (ipmip_lladdr_cmp(fa->params.fa.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr) == IP_FALSE) { change = IP_TRUE; /* We're using this one instead */ ipmip_util_dup(&fa->params.fa.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr->len); IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Foreign Agent '%s' changed LLA to '%s', Low Latency in effect?", ipmip_ntop_inet(proc, fa->addr), ipmip_ntop_lladdr(proc, fa->params.fa.lladdr))); } } /**/ if (change) ipmipmn_if_notify_iflocked(data, proc, pbuf->api.iff.priv); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); } /* *=========================================================================== * ipmipmn_if_ha_adv_verify *=========================================================================== * Description: Do home agent verification; if there's been a 'silent' roam * we need to discover it. * Parameters: data - Context * proc - Process * ifp - The interface * ha - The home agent. * Returns: * */ IP_GLOBAL Ip_bool ipmipmn_if_ha_adv_verify(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *ha) { Ip_bool verify; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); verify = ha->params.ha.verify == IPMIPMN_IFF_COA_HA_VERIFY_REQUEST; if (verify) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Verifying Home Agent '%s' on interface %s due to foreign agent advertisement", ha->name, ifp->iff.ipmip.ifname)); /* Reschedule timeout */ ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &ha->params.ha.timer); ipmip_timer_request(&data->api.ipmip4.ipmip, proc, &ha->params.ha.timer, data->iflock, (Ipmip_timer_func_t)ipmipmn_if_agent_timeout_iflocked, ha, ifp, 2000); ha->params.ha.verify = IPMIPMN_IFF_COA_HA_VERIFY_PENDING; } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return verify; } /* *=========================================================================== * ipmipmn_if_ha_adv *=========================================================================== * Description: Process a home agent advertisement and update any * home agent values. * Parameters: data - Context * proc - Process * pbuf - Packet buffer. * Returns: * */ IP_GLOBAL void ipmipmn_if_ha_adv(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_pktbuf_t *pbuf) { Ipmipmn_api_coa_t *ha; Ip_bool change = IP_FALSE; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /* Find it it? */ ha = ipmipmn_if_coa_get_iflocked(data, proc, pbuf->api.iff.priv, pbuf->api.ipmip4.ipmip.srcinfo.src->sin.sin_addr.s_addr, IPMIPMN_ATTR_COA_HA, IP_FALSE); if (ha == IP_NULL) { ha = ipmipmn_if_coa_new_iflocked(data, proc, pbuf->api.iff.priv, pbuf->api.ipmip4.ipmip.srcinfo.src->sin.sin_addr.s_addr, IPMIPMN_ATTR_COA_HA, IP_TRUE, IP_NULL); if (ha != IP_NULL) change = IP_TRUE; } /**/ if (ha != IP_NULL) { if (change) { Ipmipmn_api_coa_t *coa; /* Go through and locate and Home Agents located _AFTER_ the foreign agent, have we done * a 'silent' move? */ for (coa = IPCOM_LIST_NEXT(&ha->next); coa != IP_NULL; coa = IPCOM_LIST_NEXT(&coa->next)) { if (coa->down) continue; /**/ if (coa->type == IPMIPMN_ATTR_COA_FA) { /* Need to do a directed solicitation on this HA; verify that * it's still available */ coa->unreliable = IP_TRUE; } } } /* Update params */ if (ipmip_lladdr_cmp(ha->params.ha.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr) == IP_FALSE) { change = IP_TRUE; /* We're using this one instead */ ipmip_util_dup(&ha->params.ha.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr, pbuf->api.ipmip4.ipmip.srcinfo.lladdr->len); } /* Verified */ ha->params.ha.verify = IPMIPMN_IFF_COA_HA_VERIFY_NONE; /* Always relink to first in list when seen */ ipmipmn_if_coa_relink_iflocked(data, proc, pbuf->api.iff.priv, ha, IP_TRUE); /* Update lifetime */ ha->params.ha.lifetime = pbuf->api.advinfo.lifetime; /* Reschedule timeout */ ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &ha->params.ha.timer); ipmip_timer_request(&data->api.ipmip4.ipmip, proc, &ha->params.ha.timer, data->iflock, (Ipmip_timer_func_t)ipmipmn_if_agent_timeout_iflocked, ha, pbuf->api.iff.priv, ha->params.ha.lifetime * 1000); } /**/ if (change) ipmipmn_if_notify_iflocked(data, proc, pbuf->api.iff.priv); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); } /* *=========================================================================== * ipmipmn_if_coa_fa_llh *=========================================================================== * Description: Turn a LLH coa into a usable directly connected one. * Parameters: data - Context * proc - Process * ifp - The interface * llh_coa - LLH CoA * Returns: A new freshly allocated direct CoA * */ IP_GLOBAL Ipmipmn_api_coa_t * ipmipmn_if_coa_fa_llh(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *llh_coa) { Ipmipmn_api_coa_t *fa; ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); /* Find it it? */ fa = ipmipmn_if_coa_get_iflocked(data, proc, ifp, llh_coa->addr, IPMIPMN_ATTR_COA_FA, IP_TRUE); if (fa == IP_NULL || fa->down) { if (fa == IP_NULL) fa = ipmipmn_if_coa_new_iflocked(data, proc, ifp, llh_coa->addr, IPMIPMN_ATTR_COA_FA, IP_TRUE, IP_NULL); if (fa != IP_NULL) { /* Update */ fa->params.fa.adv_seq = llh_coa->params.fa.adv_seq; fa->params.fa.adv_flags = llh_coa->params.fa.adv_flags; fa->params.fa.lifetime = llh_coa->params.fa.lifetime; /* We're using this one instead */ ipmip_util_dup(&fa->params.fa.lladdr, llh_coa->params.fa.lladdr_llh, llh_coa->params.fa.lladdr_llh->len); ipmip_timer_cancel(&data->api.ipmip4.ipmip, proc, &fa->params.fa.timer); ipmip_timer_request(&data->api.ipmip4.ipmip, proc, &fa->params.fa.timer, data->iflock, (Ipmip_timer_func_t)ipmipmn_if_agent_timeout_iflocked, fa, ifp, fa->params.fa.lifetime * 1000); if (fa->down) { fa->down = IP_FALSE; ipmipmn_if_coa_reference_iflocked(data, proc, fa, IP_TRUE, "LLH Completed"); ipmipmn_if_coa_relink_iflocked(data, proc, ifp, fa, IP_TRUE); } } } if (fa) { ipmipmn_if_coa_reference_iflocked(data, proc, fa, IP_FALSE, "LLH Done"); ipmipmn_if_coa_put_iflocked(data, proc, ifp, llh_coa, IP_FALSE, IP_FALSE, "LLH Done"); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return fa; } /* *=========================================================================== * ipmipmn_if_admin_down *=========================================================================== * Description: Bring an interface administratively down. * Parameters: data - Context * proc - Process * ifp - The interface * Returns: * */ IP_GLOBAL void ipmipmn_if_admin_down(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); if (!ifp->admin_down) { ifp->admin_down = IP_TRUE; ifp->references++; ipmipmn_if_ifstate_iflocked(data, proc, ifp); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); } /* *=========================================================================== * ipmipmn_if_admin_up *=========================================================================== * Description: Bring an interface administratively up * Parameters: data - Context * proc - Process * ifp - The interface * Returns: * */ IP_GLOBAL void ipmipmn_if_admin_up(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp) { ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); if (ifp->admin_down) { ifp->admin_down = IP_FALSE; ipmipmn_if_put_iflocked(data, proc, ifp); ipmipmn_if_ifstate_iflocked(data, proc, ifp); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); } /* *=========================================================================== * ipmipmn_if_coa_reference *=========================================================================== * Description: Reference the CoA * Parameters: data - Context * proc - Process * coa - CoA * Returns: The CoA * */ IP_GLOBAL Ipmipmn_api_coa_t * ipmipmn_if_coa_reference(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_api_coa_t *coa, const char *reason) { ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); ipmipmn_if_coa_reference_iflocked(data, proc, coa, IP_FALSE, reason); ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); return coa; } /* *=========================================================================== * ipmipmn_if_coa_ike_reconfigure *=========================================================================== * Description: Request a re-configuration of IKE if required. * Parameters: data - The context * proc - Process * ifp - Interface * coa - Care of Address. * Returns: * */ IP_GLOBAL Ip_bool ipmipmn_if_coa_ike_reconfigure(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa) { #ifdef IPIPSEC2 Ip_bool reconfigure = IP_FALSE; IPMIP_UNUSED_PARAMETER(ifp); ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); if (coa->type == IPMIPMN_ATTR_COA_CCOA) { if (coa->params.ccoa.ike_reconfigure == IP_FALSE) { reconfigure = IP_TRUE; coa->params.ccoa.ike_reconfigure = IP_TRUE; } } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); /* */ if (reconfigure) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Reconfigure IKE due to new primary CoA")); ipcom_ipd_send("ipike", IPCOM_IPD_MSGTYPE_RECONFIGURE); return IP_TRUE; } #else IPMIP_UNUSED_PARAMETER(ifp); IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); IPMIP_UNUSED_PARAMETER(coa); #endif return IP_FALSE; } /* *=========================================================================== * ipmipmn_if_coa_ike_mobility *=========================================================================== * Description: Request an IKE mobility movement * Parameters: data - The context * proc - Process * ifp - Interface * coa - Care of Address. * Returns: * */ IP_GLOBAL Ip_bool ipmipmn_if_coa_ike_mobility(struct Ipmipmn_context *data, struct Ipmip_proc *proc, Ipmipmn_iff_t *ifp, Ipmipmn_api_coa_t *coa) { #ifdef IPIKE Ip_u32 endpoints[4]; Ip_u32 endindex = 0; unsigned int i; unsigned int p; Ip_bool pending = IP_FALSE; IPMIP_UNUSED_PARAMETER(ifp); ipmip_timer_lock_acquire(&data->api.ipmip4.ipmip, proc, data->iflock); if (coa->type == IPMIPMN_ATTR_COA_CCOA) { struct Ip_ifaddrs *ifaddrs = IP_NULL; Ipmipmn_api_coa_t *scoa; /* Get interface addresses */ ipcom_getifaddrs(&ifaddrs); /**/ for (scoa = IPCOM_LIST_FIRST(&ifp->coa); endindex < 4 && scoa != IP_NULL; scoa = IPCOM_LIST_NEXT(&scoa->next)) { Ipmip_buffer_t reply; if (scoa->type != IPMIPMN_ATTR_COA_SCOA_DEAD) continue; pending = IP_TRUE; if (scoa->params.tia.mobiked) continue; ipmip_buffer_init(&reply); if (ipmip4_netlink_route_validate(&data->api.ipmip4, proc, ifp->iff.ipmip.ifindex, scoa->params.tia.ike_address, &reply) < 0) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Postponing mobike request, since %s is not yet routable", ipmip_ntop_inet(proc, scoa->params.tia.ike_address))); continue; } ipmip_buffer_release(&reply); #if 0 if (!ipmip4_usr_route_validate(&data->api.ipmip4, proc, &ifp->iff, 0, scoa->params.tia.ike_address)) { IPMIP4_EVENT_IF(INFO, proc, ipmip_log_event_add(&data->api.ipmip4.ipmip, proc, "Postponing mobike request, since %s is not yet routable", ipmip_ntop_inet(proc, scoa->params.tia.ike_address))); continue; } #endif for (p = 0; p < endindex; p++) { if (endpoints[p] == scoa->params.tia.local_address) break; } /**/ scoa->params.tia.mobiked = IP_TRUE; if (p == endindex) endpoints[endindex++] = scoa->params.tia.local_address; } /* Free the interface addresses */ ipcom_freeifaddrs(ifaddrs); } ipmip_timer_lock_release(&data->api.ipmip4.ipmip, proc, data->iflock); /* Move the IKE endpoints */ for (i = 0; i < endindex; i++) ipmipmn_if_ike_move(data, proc, ifp, coa, endpoints[i]); return pending || endindex > 0; #else IPMIP_UNUSED_PARAMETER(ifp); IPMIP_UNUSED_PARAMETER(data); IPMIP_UNUSED_PARAMETER(proc); IPMIP_UNUSED_PARAMETER(coa); return IP_FALSE; #endif } /* **************************************************************************** * 11 PUBLIC FUNCTIONS **************************************************************************** */ /* **************************************************************************** * END OF FILE **************************************************************************** */