/* ****************************************************************************** * INTERPEAK SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipnet_dst_cache.c,v $ $Revision: 1.16.2.28 $ * $Source: /home/interpeak/CVSRoot/ipnet2/src/ipnet_dst_cache.c,v $ * $Author: jren $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Kenneth Jonsson ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** * * Implementation of the destination cache for IPNET. The hash table * and all the elements in it stored in memory only visable to the * local CPU core when the system is running as a NAE. Hence, there is * no requirement for locking. */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ #include "ipnet_config.h" /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #define IPCOM_USE_CLIB_PROTO #include #include #include #include #include #include #include #include "ipnet_dst_cache.h" #include "ipnet_h.h" #include "ipnet_neigh.h" #include "ipnet_peer_info.h" #include "ipnet_route.h" /* **************************************************************************** * 4 DEFINES **************************************************************************** */ #define IPNET_FLOW_SPEC_ZERO(fs) (fs = ipnet_flow_spec_zero) #ifdef IPCOM_USE_INET6 #define IPNET_FLOW_SPEC_DOMAIN(fs) \ (IP_BIT_ISSET((fs)->flags, IPNET_FSF_IPV6) ? IP_AF_INET6 : IP_AF_INET) #else #define IPNET_FLOW_SPEC_DOMAIN(fs) IP_AF_INET #endif /* **************************************************************************** * 5 TYPES **************************************************************************** */ /* * Context data when travering all destination cache entries in order * to locate the true path MTU to a specific node. */ struct Ipnet_dst_cache_pmtu { int domain; union Ip_in_addr_union to; Ip_u32 path_mtu; }; /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ IP_STATIC void ipnet_dst_cache_delete(Ipnet_dst_cache *dst); IP_STATIC int ipnet_dst_cache_check_limit(void); /* **************************************************************************** * 8 DATA **************************************************************************** */ /* * Hash table where all the destination cache entries are stored. */ Ipcom_hash *ipnet_dst_cache; /* * Memory cache where all the destination cache entries are allocated * from. */ static Ipcom_memory_cache *ipnet_dst_slab; /* * Destination cache entry that will discard any packets transmitted * or received using that flow. */ static Ipnet_dst_cache ipnet_dst_cache_blackhole_singleton; /* * A destination cache entry with all zeros. It is used to quickly * zero out all fields when creating a new flow specification. */ static const Ipnet_flow_spec ipnet_flow_spec_zero; static Ipcom_hash *ipnet_pmtus; static Ipcom_memory_cache *ipnet_pmtu_slab; unsigned long ipnet_pmtu_limit = 0; #define INET_DEFAULT_PATHMTU_LIVE_TIME 600 unsigned long ipnet_pmtu_live_time = 0; union Ipnet_pmtu_addr_union { struct Ip_in_addr in; #ifdef IPCOM_USE_INET6 struct Ip_in6_addr in6; #endif }; typedef struct Ipnet_pmtu_lookup_key_struct { /* The ID of the virtual router this interface is assigned to */ Ip_u16 vr_index; /* * IP_AF_INET or IP_AF_INET6 */ int domain; /* * IP address of the peer. Points to a struct Ip_in_addr if * domain==IP_AF_INET and to a struct Ip_in6_addr if * domain==IP_AF_INET6. */ union Ipnet_pmtu_addr_union addr; } Ipnet_pmtu_lookup_key; typedef struct Ipnet_pmtu_struct { Ipnet_pmtu_lookup_key key; /* The key for this entry */ Ip_u32 path_mtu; /* The path-MTU of this flow */ Ipnet_timeout *tmo; /* Timer for this entry */ } Ipnet_pmtu; IP_STATIC unsigned ipnet_pmtu_key_hash(Ipnet_pmtu_lookup_key *key); IP_STATIC unsigned ipnet_pmtu_obj_hash(Ipnet_pmtu *pmtu); IP_STATIC unsigned ipnet_pmtu_cmp_hash(Ipnet_pmtu *pmtu, Ipnet_pmtu_lookup_key *key); IP_STATIC void ipnet_pmtu_update(Ipnet_pmtu *pmtu, Ip_u32 new_path_mtu); IP_STATIC void ipnet_pmtu_timeout(Ipnet_pmtu *pmtu); IP_STATIC void ipnet_pmtu_delete(Ipnet_pmtu *pmtu); IP_GLOBAL Ipnet_pmtu *ipnet_pmtu_get(int vr, int domain, IP_CONST void *addr); IP_STATIC Ipnet_pmtu *ipnet_pmtu_new(int vr, int domain, IP_CONST void *addr, int pmtu_value); #ifdef IPCOM_USE_INET IP_STATIC void ipnet_pmtu_proc_inet(Ipcom_pkt *pkt); #endif #ifdef IPCOM_USE_INET6 IP_STATIC void ipnet_pmtu_proc_inet6(Ipcom_pkt *pkt); #endif typedef void (*Ipnet_pmtu_foreach_func)(struct Ipnet_pmtu_struct *pmtu, void *user_data); /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipnet_dst_cache_hash_key *=========================================================================== * Description: Returns a hash value for a flow specification. * Parameters: flow_spec - a flow specification. * Returns: * */ IP_STATIC unsigned ipnet_dst_cache_hash_key(const Ipnet_flow_spec *flow_spec) { unsigned h; int domain = IPNET_FLOW_SPEC_DOMAIN(flow_spec); h = (((Ip_u32)flow_spec->ds << 22) + ((Ip_u32)flow_spec->vr << 13) + (flow_spec->egress_ifindex << 8) + flow_spec->ingress_ifindex + (flow_spec->zone_id << 15)); #ifdef IPCOM_USE_INET if (domain == IP_AF_INET) h = ipcom_hash_update(&flow_spec->to.in, sizeof(flow_spec->to.in), h ^ flow_spec->from.in.s_addr); #endif #ifdef IPCOM_USE_INET6 if (domain == IP_AF_INET6) h = ipcom_hash_update(&flow_spec->to.in6, sizeof(flow_spec->from.in6) * 2, h); #endif return h; } /* *=========================================================================== * ipnet_dst_cache_hash_obj *=========================================================================== * Description: Returns a hash value for the destination cache entry * Parameters: dst - a destination cache entry * Returns: * */ IP_STATIC unsigned ipnet_dst_cache_hash_obj(const Ipnet_dst_cache *dst) { return ipnet_dst_cache_hash_key(&dst->flow_spec); } /* *=========================================================================== * ipnet_dst_cache_hash_cmp *=========================================================================== * Description: Checks if a destination cache matches a specific flow * specification * Parameters: dst - a destination cache entry * flow_spec - a flow specification * Returns: IP_TRUE if the flow specification matches the * destination cache entry * IP_FALSE otherwise * */ IP_STATIC Ip_bool ipnet_dst_cache_hash_cmp(const Ipnet_dst_cache *dst, const Ipnet_flow_spec *flow_spec) { Ip_size_t alen; /* * memcmp() over the Ipnet_flow_spec structure would work * here. However, that function is implemented as a slow * byte-to-byte comparison on serveral platforms. */ if (dst->flow_spec.flags != flow_spec->flags || dst->flow_spec.ds != flow_spec->ds || dst->flow_spec.vr != flow_spec->vr || dst->flow_spec.egress_ifindex != flow_spec->egress_ifindex || dst->flow_spec.ingress_ifindex != flow_spec->ingress_ifindex || dst->flow_spec.zone_id != flow_spec->zone_id #ifdef IPMIP || (dst->flow_spec.mip.mask.u64[0] != 0 && dst->flow_spec.mip.key.u64[0] != (flow_spec->mip.key.u64[0] & dst->flow_spec.mip.mask.u64[0])) || (dst->flow_spec.mip.mask.u64[1] != 0 && dst->flow_spec.mip.key.u64[1] != (flow_spec->mip.key.u64[1] & dst->flow_spec.mip.mask.u64[1])) || (dst->flow_spec.mip.mask.u64[2] != 0 && dst->flow_spec.mip.key.u64[2] != (flow_spec->mip.key.u64[2] & dst->flow_spec.mip.mask.u64[2])) #endif ) return IP_FALSE; alen = ipnet_ipaddrlen(IPNET_FLOW_SPEC_DOMAIN(flow_spec)); return ipcom_memcmp(&dst->flow_spec.to, &flow_spec->to, alen) == 0 && ipcom_memcmp(&dst->flow_spec.from, &flow_spec->from, alen) == 0; } /* *=========================================================================== * ipnet_dst_cache_flush_cb *=========================================================================== * Description: Deletes the flow cache entry if it matches the flush * filter. * Parameters: dst - a destination cache entry * filter - only entries matching this filter will be * deleted * Returns: * */ IP_STATIC void ipnet_dst_cache_flush_cb(Ipnet_dst_cache *dst, const Ipnet_flow_spec *filter) { if ((filter->vr == IPCOM_VR_ANY || filter->vr == dst->flow_spec.vr) && (IPNET_FLOW_SPEC_DOMAIN(filter) == IPNET_FLOW_SPEC_DOMAIN(&dst->flow_spec))) { /* * Delete this entry. */ ipnet_dst_cache_delete(dst); } /* else: keep this entry since it didn't match the flush filter */ } /* *=========================================================================== * ipnet_dst_cache_flush_where_neigh_is_cb *=========================================================================== * Description: Visitor callback for each destination cache entry, * will delete all destination cache entries using * 'neigh' as their next-hop. * Parameters: dst - a destination cache entry * neigh - neighbor entry to match * Returns: * */ IP_STATIC void ipnet_dst_cache_flush_where_neigh_is_cb(Ipnet_dst_cache *dst, const Ipnet_neigh *neigh) { if (dst->neigh == neigh) /* * Delete this entry. */ ipnet_dst_cache_delete(dst); } /* *=========================================================================== * ipnet_dst_cache_reset_timer *=========================================================================== * Description: Starts a timer on a destination cache entry * Parameters: dst - a destination cache entry. * transient - IP_TRUE if this entry should be considered * short lived for now * Returns: * */ IP_STATIC void ipnet_dst_cache_reset_timer(Ipnet_dst_cache *dst, Ip_bool transient) { Ip_s64 msec; Ipnet_netif *netif = dst->neigh->netif; #if defined(IPCOM_USE_INET) && defined(IPCOM_USE_INET6) int domain = IPNET_FLOW_SPEC_DOMAIN(&dst->flow_spec); #endif int transient_cnt = IPNET_NETIF_CONF(domain, netif, dst_cache_transient_cnt); /* * The 'dst_cache_to_live' parameter specified in seconds */ if (transient && transient_cnt > 0) { dst->transient_cnt = transient_cnt; msec = IPNET_NETIF_CONF(domain, netif, dst_cache_transient_to_live_msec); } else { dst->transient_cnt = 0; msec = IPNET_NETIF_CONF(domain, netif, dst_cache_to_live) * 1000; } ipnet_dst_cache_set_tx_handler(dst, dst->real_tx); ipnet_dst_cache_set_rx_handler(dst, dst->real_rx); ipnet_timeout_cancel(dst->tmo); ipnet_timeout_schedule(msec, (Ipnet_timeout_handler) ipnet_dst_cache_delete, dst, &dst->tmo); } /* *=========================================================================== * ipnet_dst_cache_blackhole_action *=========================================================================== * Description: Discards all packets. * Parameters: dst - not used. * pkt - packet to discard * Returns: 0 = success * */ IP_STATIC int ipnet_dst_cache_blackhole_action(Ipnet_dst_cache *dst, Ipcom_pkt *pkt) { IPCOM_UNUSED_ARG(dst); ipcom_pkt_free(pkt); return 0; } /* *=========================================================================== * ipnet_dst_cache_delete *=========================================================================== * Description: Timeout handler for a destination cache entry. * Parameters: dst - the destination cache entry that had its timer * expired. * Returns: * */ IP_STATIC void ipnet_dst_cache_delete(Ipnet_dst_cache *dst) { /* * This entry has expired, lets delete it. It is not allowed * to keep references to a destination cache entry between * processing of two packets. The timeout handler is run on * the same CPU core as packet processing is run, so it is * safe to free this entry directly here. */ (void)ipcom_hash_remove(ipnet_dst_cache, dst); #ifdef IPMIP if (IP_UNLIKELY(dst->mip.flow_handle != IP_NULL)) (*dst->mip.flow_handle) (IP_FALSE, dst); #endif if (dst->peer_info) ipnet_peer_info_release(dst->peer_info); ipnet_neigh_release(dst->neigh); ipnet_timeout_cancel(dst->tmo); ipcom_slab_free(ipnet_dst_slab, dst); } /* *=========================================================================== * ipnet_dst_cache_check_limit *=========================================================================== * Description: check the dst cache num reach the limit * Parameters: * Returns: IP_TRUE check pass IP_FALSE check failed * */ IP_STATIC int ipnet_dst_cache_check_limit(void) { if(0 == ipnet_dst_cache_number_limit) return IP_TRUE; if(ipnet_dst_cache_number_limit > ipnet_dst_cache->elem) return IP_TRUE; return IP_FALSE; } /* *=========================================================================== * ipnet_dst_cache_tmo_to_string *=========================================================================== * Description: Creates a string representation of a destination cache entry * Parameters: dst - a destination cache entry * buf - buffer where the string representation should be * stored * buf_len - lenth of 'buf' * Returns: Number of characters written to 'buf' * */ #if defined(IPNET_DEBUG) IP_STATIC int ipnet_dst_cache_tmo_to_string(const Ipnet_dst_cache *dst, char *buf, Ip_size_t buf_len) { char from[IP_INET6_ADDRSTRLEN]; char to[IP_INET6_ADDRSTRLEN]; int domain = IPNET_FLOW_SPEC_DOMAIN(&dst->flow_spec); return ipcom_snprintf(buf, buf_len, "Dst cache %c%16s -> %-16s VR=%d, DS=%d, zone_id=%d, egress if=%u, ingress if=%u", IP_BIT_ISSET(dst->flow_spec.flags, IPNET_FSF_DONTROUTE) ? '*' : ' ', ipcom_inet_ntop(domain, &dst->flow_spec.from, from, sizeof(from)), ipcom_inet_ntop(domain, &dst->flow_spec.to, to, sizeof(to)), dst->flow_spec.vr, dst->flow_spec.ds, dst->flow_spec.zone_id, dst->flow_spec.egress_ifindex, dst->flow_spec.ingress_ifindex); } #endif /* IPNET_DEBUG */ /* *=========================================================================== * ipnet_dst_cache_ip4_dont_route_netif *=========================================================================== * Description: Tries to find the egress interface to use for a don't route * transmission. * Parameters: flow_spec - flow specification. * Returns: * */ #ifdef IPCOM_USE_INET IP_STATIC Ipnet_netif * ipnet_dst_cache_ip4_dont_route_netif(const Ipnet_flow_spec *flow_spec) { Ipnet_netif *netif; Ipnet_netif *best_netif = IP_NULL; int best_prefixlen = 0; unsigned i; /* * IPNET_NETIF_FOR_EACH_OR_SPECIFIC_ON_VR sets netif to NULL after the for loop, * this is a performance optimized defination */ /* coverity[result_independent_of_operands] */ IPNET_NETIF_FOR_EACH_OR_SPECIFIC_ON_VR(netif, flow_spec->vr, flow_spec->egress_ifindex, i) { Ipnet_ip4_addr_entry *addr; if (IP_BIT_ISFALSE(netif->ipcom.flags, IP_IFF_UP)) continue; for (addr = netif->inet4_addr_list; addr != IP_NULL && addr->type == IPNET_ADDR_TYPE_UNICAST; addr = addr->next) { if (ipcom_route_key_cmp(32, &flow_spec->to.in, &addr->ipaddr_n, &addr->netmask_n) != IPCOM_ROUTE_KEY_CMP_NO_MATCH) { int this_prefixlen = ipcom_mask_to_prefixlen(&addr->netmask_n, 32); /* * Select the network interface that is attached to a * subnet that closest matches the 'to' address. */ if (best_prefixlen < this_prefixlen) { best_prefixlen = this_prefixlen; best_netif = netif; } } } } return best_netif; } #endif /* IPCOM_USE_INET */ /* *=========================================================================== * ipnet_dst_cache_ip6_dont_route_netif *=========================================================================== * Description: Tries to find the egress interface to use for a don't route * transmission. * Parameters: flow_spec - flow specification. * Returns: * */ #ifdef IPCOM_USE_INET6 IP_STATIC Ipnet_netif * ipnet_dst_cache_ip6_dont_route_netif(const Ipnet_flow_spec *flow_spec) { Ipnet_netif *netif; Ipnet_netif *best_netif = IP_NULL; unsigned best_prefixlen = 0; unsigned i; /* * IPNET_NETIF_FOR_EACH_OR_SPECIFIC_ON_VR sets netif to NULL after the for loop, * this is a performance optimized defination */ /* coverity[result_independent_of_operands] */ IPNET_NETIF_FOR_EACH_OR_SPECIFIC_ON_VR(netif, flow_spec->vr, flow_spec->egress_ifindex, i) { Ipnet_ip6_addr_entry *addr; struct Ip_in6_addr mask; if (IP_BIT_ISFALSE(netif->ipcom.flags, IP_IFF_UP)) continue; for (addr = netif->inet6_addr_list; addr != IP_NULL && addr->type == IPNET_ADDR_TYPE_UNICAST; addr = addr->next) { if (addr->prefixlen == 0) continue; ipcom_memset(&mask, 0, sizeof(mask)); ipnet_route_create_mask(&mask, addr->prefixlen); if (ipcom_route_key_cmp(128, &flow_spec->to.in6, &addr->addr, &mask) != IPCOM_ROUTE_KEY_CMP_NO_MATCH) { if (best_prefixlen < addr->prefixlen) { best_prefixlen = addr->prefixlen; best_netif = netif; } } } } return best_netif; } #endif /* IPCOM_USE_INET6 */ /* *=========================================================================== * ipnet_dst_cache_select_best_rt *=========================================================================== * Description: Selects routes with next-hop that is known to be reachable * over routes where the connectivity of the next-hop is * unknown. * Parameters: domain - IP_AF_INET or IP_AF_INET6 * flow_spec - flow specification that will use the route * rt - route returned from the FIB, must not be IP_NULL * Returns: Best route, will never be IP_NULL * */ IP_GLOBAL Ipnet_route_entry * ipnet_dst_cache_select_best_rt(int domain, const Ipnet_flow_spec *flow_spec, Ipnet_route_entry *rt) { Ipnet_route_entry *re = rt; Ipnet_neigh *neigh; if (IP_BIT_ISFALSE(rt->hdr.flags, IPNET_RTF_GATEWAY) || (rt->prev == IP_NULL && rt->next == IP_NULL)) return rt; /* * RFC 1122 section 3.3.1.2 - WIND00314264 * The IP layer should choose the next "default" route if the * first one is not unreachable. * * RFC 4861 - default router switch */ /* * Prefer next-hops that are known to be reachable. */ do { Ipnet_netif *netif = re->netif; neigh = netif->neigh_create(domain, netif, flow_spec, re); if (neigh) { if (ipnet_neigh_is_complete(neigh)) { /* This next-hop is reachable */ ipnet_neigh_release(neigh); break; } ipnet_neigh_release(neigh); } re = re->next; if (re == IP_NULL) { re = rt; while (re->prev) re = re->prev; } } while (re != rt); return re; } /* *=========================================================================== * ipnet_dst_cache_pmtu_cb *=========================================================================== * Description: Callback that checks if the destination cache entry matches * the specified domain and 'to' address and if so, adjust * the path MTU. * Parameters: dst - a destination cache entry * p - matching domain and 'to' address * Returns: * */ IP_STATIC void ipnet_dst_cache_pmtu_cb(Ipnet_dst_cache *dst, struct Ipnet_dst_cache_pmtu *p) { if (p->path_mtu > dst->path_mtu && IPNET_FLOW_SPEC_DOMAIN(&dst->flow_spec) == p->domain && ipcom_memcmp(&dst->flow_spec.to, &p->to, ipnet_ipaddrlen(p->domain)) == 0) { p->path_mtu = dst->path_mtu; } } /* *=========================================================================== * ipnet_dst_cache_get_pmtu *=========================================================================== * Description: Checks all entries that ends at on the same remote node * and returns the smallest path MTU used to that host. * Parameters: dst - the destination entry to match. * Returns: Path MTU * */ IP_STATIC Ip_u32 ipnet_dst_cache_get_pmtu(const Ipnet_dst_cache *dst) { struct Ipnet_dst_cache_pmtu p; p.domain = IPNET_FLOW_SPEC_DOMAIN(&dst->flow_spec); p.to = dst->flow_spec.to; p.path_mtu = dst->path_mtu; ipcom_hash_for_each(ipnet_dst_cache, (Ipcom_hash_foreach_cb_func) ipnet_dst_cache_pmtu_cb, &p); return p.path_mtu; } /* **************************************************************************** * 10 GLOBAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipnet_dst_cache_new *=========================================================================== * Description: Creates a new destination cache entry. * Parameters: flow_spec - fields that uniquely indentify a flow of * packets * domain_ctor - domain specific constructor for this * entry. * pdst - pointer to where the new entry should be stored * Returns: 0 = success * <0 = error code * */ IP_GLOBAL int ipnet_dst_cache_new(const Ipnet_flow_spec *flow_spec, Ipnet_dst_cache_domain_ctor domain_ctor, Ipnet_dst_cache **pdst) { Ipnet_route_entry *rt; Ipnet_route_entry rt_buf; Ipnet_netif *netif = IP_NULL; int ret = 0; int rt_ret = 0; Ipnet_dst_cache *dst; int domain; #if defined(IPCOM_USE_INET) && defined(IPCOM_USE_INET6) Ipnet_flow_spec v4mapped_flow_spec; #endif Ipnet_pmtu *store_pathmtu; domain = IPNET_FLOW_SPEC_DOMAIN(flow_spec); ip_assert(ipnet_dst_cache_get(flow_spec) == IP_NULL); ip_assert(domain == IP_AF_INET || domain == IP_AF_INET6); if(IP_TRUE != ipnet_dst_cache_check_limit()) return -IP_ERRNO_ENOMEM; dst = ipcom_slab_alloc(ipnet_dst_slab); if (dst == IP_NULL) { /* * This is kind of bad, out of memory. */ ipnet_try_free_mem(IP_FALSE); dst = ipcom_slab_alloc(ipnet_dst_slab); if (dst == IP_NULL) return -IP_ERRNO_ENOMEM; } /* * Copy the flow specification */ ipcom_memcpy(&dst->flow_spec, flow_spec, sizeof(*flow_spec)); /* * Fetch the pointer to the ingress interface if an ingress * interface is specified. */ if (flow_spec->ingress_ifindex) dst->ingress_netif = ipnet_if_indextonetif(flow_spec->vr, flow_spec->ingress_ifindex); if (IP_BIT_ISSET(flow_spec->flags, IPNET_FSF_DONTROUTE)) { /* * Don't route. Create a route entry with the bare minimum of * fields initialized. */ ipcom_memset(&rt_buf, 0, sizeof(rt_buf)); rt = &rt_buf; rt->hdr.key = (void*) &flow_spec->to; if (flow_spec->egress_ifindex == 0) { switch (domain) { #ifdef IPCOM_USE_INET case IP_AF_INET: rt->netif = ipnet_dst_cache_ip4_dont_route_netif(flow_spec); break; #endif #ifdef IPCOM_USE_INET6 case IP_AF_INET6: rt->netif = ipnet_dst_cache_ip6_dont_route_netif(flow_spec); break; #endif default: IP_PANIC(); break; } if (rt->netif == IP_NULL) { ret = -IP_ERRNO_EHOSTUNREACH; goto errout; } } /* * else: use the specified egress interface */ } else { /* * Locate the FIB entry to the destination */ rt_ret = ipnet_route_lookup_ecmp(domain, flow_spec->vr, 0, &flow_spec->to, flow_spec->zone_id, flow_spec->egress_ifindex, dst, &dst->flow_spec, &rt); if (IP_UNLIKELY(0 > rt_ret || IP_BIT_ISSET(rt->hdr.flags, IPNET_RTF_BLACKHOLE))) { if (IP_BIT_ISSET(flow_spec->flags, IPNET_FSF_DONTDISCARD)) { /* The flow spec does not allow discard management */ ret = rt_ret < 0? rt_ret : -IP_ERRNO_EHOSTUNREACH; goto errout; } /* Setup an 'error' neigh using the discard netif */ ipcom_memset(&rt_buf, 0, sizeof(rt_buf)); rt = &rt_buf; rt->hdr.key = (void*) &flow_spec->to; rt->netif = netif = ipnet_neigh_netif_discard(); rt->data = &rt_ret; } #if defined(IPCOM_USE_INET) && defined(IPCOM_USE_INET6) if (rt_ret >= 0 && domain == IP_AF_INET6 && IP_IN6_IS_ADDR_V4MAPPED(&flow_spec->to.in6)) { /* 'rt' is an IPv4 route in this case */ domain = IP_AF_INET; ipcom_memcpy(&v4mapped_flow_spec, flow_spec, sizeof(Ipnet_flow_spec)); IP_BIT_CLR(v4mapped_flow_spec.flags, IPNET_FSF_IPV6); v4mapped_flow_spec.to.in.s_addr = flow_spec->to.in6.in6.addr32[3]; v4mapped_flow_spec.to.in6.in6.addr32[1] = 0; v4mapped_flow_spec.to.in6.in6.addr32[2] = 0; v4mapped_flow_spec.to.in6.in6.addr32[3] = 0; flow_spec = &v4mapped_flow_spec; } #endif /* IPCOM_USE_INET && IPCOM_USE_INET6 */ if (rt_ret >= 0) rt = ipnet_dst_cache_select_best_rt(domain, flow_spec, rt); } /* * Get the peer information entry. */ dst->peer_info = ipnet_peer_info_get(flow_spec->vr, domain, &flow_spec->to); if (dst->peer_info == IP_NULL) { /* out of memory */ ret = -IP_ERRNO_ENOMEM; goto errout; } /* * Do the domain specific initialization of the entry. The most * important part of this is the initialization of the * transmission/reception handlers and selection of local * address (dst->laddr). */ ret = domain_ctor(dst, rt); if (ret < 0) goto errout; if (IP_LIKELY(netif == IP_NULL)) { if (flow_spec->egress_ifindex) { /* * Multicast routes might have * rt->netif->ipcom.ifindex!=flow_spec->egress_ifindex. */ netif = ipnet_if_indextonetif(flow_spec->vr, flow_spec->egress_ifindex); } else netif = rt->netif; if (IP_UNLIKELY(IP_NULL == netif)) { ret = -IP_ERRNO_ENXIO; goto errout; } } /* * Set the initial path-MTU for this flow. Might be lowered by * ICMP need frag. or ICMPv6 packet too big messages later on. */ dst->path_mtu = netif->ipcom.mtu; if (dst->peer_info != IP_NULL && IP_BIT_ISSET(dst->peer_info->flags, IPNET_PEER_INFO_CHECKPMTU)) { /* * Has already received need frag / too big message when * communicating with this host, figure out the PMTU to use. */ dst->path_mtu = ipnet_dst_cache_get_pmtu(dst); } store_pathmtu = ipnet_pmtu_get(flow_spec->vr, domain, &flow_spec->to); if(IP_NULL != store_pathmtu) { if(store_pathmtu->path_mtu < dst->path_mtu) dst->path_mtu = store_pathmtu->path_mtu; #ifdef IPCOM_USE_INET if ((domain == IP_AF_INET) && (dst->path_mtu < ipnet_conf_ip4_min_mtu)) { dst->path_mtu = ipnet_conf_ip4_min_mtu; } #endif #ifdef IPCOM_USE_INET6 if ((domain == IP_AF_INET6) && (dst->path_mtu < ipnet_conf_ip6_min_mtu)) { dst->path_mtu = ipnet_conf_ip6_min_mtu; } #endif } ret = -IP_ERRNO_ENOMEM; if (dst->real_rx == ipnet_dst_cache_blackhole()->rx) { #ifdef IPNET_IS_ROUTER enum Ipnet_addr_type addr_type; #ifdef IPCOM_USE_INET if (domain == IP_AF_INET && (IP_BIT_ISFALSE(ipnet_flags, IPNET_FLAG_IPV4_FORWARD_PKT) || IP_BIT_ISSET(netif->flags2, IPNET_IFF2_NO_IPV4_FORWARD) || IP_BIT_ISTRUE(flow_spec->flags, IPNET_FSF_LINK_BROADCAST))) { addr_type = ipnet_ip4_get_addr_type(flow_spec->from.in.s_addr, flow_spec->vr, netif); if (addr_type != IPNET_ADDR_TYPE_ANY && addr_type != IPNET_ADDR_TYPE_UNICAST) { ret = -IP_ERRNO_EHOSTUNREACH; goto errout; } } #endif #ifdef IPCOM_USE_INET6 if (domain == IP_AF_INET6 && (IP_BIT_ISFALSE(ipnet_flags, IPNET_FLAG_IPV6_FORWARD_PKT) || IP_BIT_ISSET(netif->flags2, IPNET_IFF2_NO_IPV6_FORWARD) || IP_BIT_ISTRUE(flow_spec->flags, IPNET_FSF_LINK_BROADCAST))) { addr_type = ipnet_ip6_get_addr_type(&flow_spec->from.in6, flow_spec->vr, netif); if (addr_type != IPNET_ADDR_TYPE_ANY && addr_type != IPNET_ADDR_TYPE_UNICAST) { ret = -IP_ERRNO_EHOSTUNREACH; goto errout; } } #endif #endif } /* Create the neighbor */ dst->neigh = netif->neigh_create(domain, netif, flow_spec, rt); if (dst->neigh == IP_NULL) { /* out of memory */ goto errout; } #ifdef IPMIP if (IP_UNLIKELY(dst->mip.flow_handle != IP_NULL)) (*dst->mip.flow_handle) (IP_TRUE, dst); #endif /* * Finally add this entry into the hash destination cache table */ if (ipcom_hash_add(ipnet_dst_cache, dst) != IPCOM_SUCCESS) /* out of memory */ goto errout; ipnet_dst_cache_reset_timer(dst, IP_TRUE); ++rt->use; *pdst = dst; return 0; errout: ipnet_dst_cache_delete(dst); return ret; } /* *=========================================================================== * ipnet_dst_cache_transient_rx *=========================================================================== * Description: Reception handler during the transient state * Parameters: dst - ingress destination cache entry * pkt - a packet * Returns: The return value of the real RX handler. * */ IP_STATIC int ipnet_dst_cache_transient_rx(Ipnet_dst_cache *dst, Ipcom_pkt *pkt) { if (dst->transient_cnt-- <= 0) { dst->rx = dst->real_rx; ipnet_dst_cache_reset_timer(dst, IP_FALSE); } return dst->real_rx(dst, pkt); } /* *=========================================================================== * ipnet_dst_cache_set_rx_handler *=========================================================================== * Description: Sets the reception handler. * Parameters: dst - a destination cache entry * rx_func - reception handler to use * Returns: * */ IP_GLOBAL void ipnet_dst_cache_set_rx_handler(Ipnet_dst_cache *dst, Ipnet_dst_cache_rx_func rx_func) { if (dst->transient_cnt > 0) dst->rx = ipnet_dst_cache_transient_rx; else dst->rx = rx_func; dst->real_rx = rx_func; } /* *=========================================================================== * ipnet_dst_cache_transient_tx *=========================================================================== * Description: Transmission handler during the transient state * Parameters: dst - egress destination cache entry * pkt - a packet * Returns: The return value of the real TX handler. * */ IP_STATIC int ipnet_dst_cache_transient_tx(Ipnet_dst_cache *dst, Ipcom_pkt *pkt) { if (dst->transient_cnt-- <= 0) { dst->tx = dst->real_tx; ipnet_dst_cache_reset_timer(dst, IP_FALSE); } return dst->real_tx(dst, pkt); } /* *=========================================================================== * ipnet_dst_cache_set_tx_handler *=========================================================================== * Description: Sets the transmission handler. * Parameters: dst - a destination cache entry * tx_func - transmission handler to use * Returns: * */ IP_GLOBAL void ipnet_dst_cache_set_tx_handler(Ipnet_dst_cache *dst, Ipnet_dst_cache_tx_func tx_func) { if (dst->transient_cnt > 0) dst->tx = ipnet_dst_cache_transient_tx; else dst->tx = tx_func; dst->real_tx = tx_func; } /* *=========================================================================== * ipnet_dst_cache_set_path_mtu *=========================================================================== * Description: Sets the path MTU for this entry. * Parameters: dst - a destination cache entry * mtu - new path mtu * Returns: * */ IP_GLOBAL void ipnet_dst_cache_set_path_mtu(Ipnet_dst_cache *dst, Ip_u32 mtu) { Ip_u32 path_mtu_min = 0; #ifdef IPCOM_USE_INET if (ipnet_flow_spec_domain(&dst->flow_spec) == IP_AF_INET) { path_mtu_min = ipnet_conf_ip4_min_mtu; if (mtu < path_mtu_min) { if (mtu == 0) IP_BIT_SET(dst->peer_info->flags, IPNET_PEER_INFO_ZEROPMTU); mtu = path_mtu_min; } } #endif #ifdef IPCOM_USE_INET6 if (ipnet_flow_spec_domain(&dst->flow_spec) == IP_AF_INET6) { path_mtu_min = ipnet_conf_ip6_min_mtu; } #endif if (mtu < dst->path_mtu && mtu >= path_mtu_min) { dst->path_mtu = mtu; IP_BIT_SET(dst->peer_info->flags, IPNET_PEER_INFO_CHECKPMTU); ipnet_dst_cache_reset_timer(dst, IP_FALSE); } } IP_GLOBAL Ipnet_dst_cache * ipnet_dst_cache_blackhole_flow_spec(const Ipnet_flow_spec *flow_spec) { ipnet_dst_cache_blackhole_singleton.flow_spec = *flow_spec; return &ipnet_dst_cache_blackhole_singleton; } /* *=========================================================================== * ipnet_dst_cache_blackhole *=========================================================================== * Description: Returns a destination cache entry that will discard * any packets transmitted or received. * Parameters: * Returns: * */ IP_GLOBAL Ipnet_dst_cache * ipnet_dst_cache_blackhole(void) { return &ipnet_dst_cache_blackhole_singleton; } /* *=========================================================================== * ipnet_dst_cache_flush *=========================================================================== * Description: Flushes all matching destination cache entries * Parameters: vr - index of virtual router to flush or IP_VR_ANY for all * domain - IP_AF_INET/IP_AF_INET6 or 0 to flush all domains * Returns: * */ IP_GLOBAL void ipnet_dst_cache_flush(Ip_u16 vr, int domain) { Ipnet_flow_spec flush_filter; flush_filter.vr = vr; #ifdef IPCOM_USE_INET if (domain == 0 || domain == IP_AF_INET) { flush_filter.flags = 0; ipcom_hash_for_each(ipnet_dst_cache, (Ipcom_hash_foreach_cb_func) ipnet_dst_cache_flush_cb, &flush_filter); } #endif #ifdef IPCOM_USE_INET6 if (domain == 0 || domain == IP_AF_INET6) { flush_filter.flags = IPNET_FSF_IPV6; ipcom_hash_for_each(ipnet_dst_cache, (Ipcom_hash_foreach_cb_func) ipnet_dst_cache_flush_cb, &flush_filter); } #endif ipnet_route_cache_invalidate(); } /* *=========================================================================== * ipnet_dst_cache_flush_where_neigh_is *=========================================================================== * Description: Flush all entries using a specific neighbor as first hop. * Parameters: neigh - the neighbor to match * Returns: * */ IP_GLOBAL void ipnet_dst_cache_flush_where_neigh_is(Ipnet_neigh *neigh) { ipcom_hash_for_each(ipnet_dst_cache, (Ipcom_hash_foreach_cb_func) ipnet_dst_cache_flush_where_neigh_is_cb, neigh); } /* *=========================================================================== * ipnet_dst_cache_foreach *=========================================================================== * Description: Calls a user defined function for every destination * cache entry. * Parameters: foreach_func - * user_data - * Returns: * */ IP_GLOBAL void ipnet_dst_cache_foreach(Ipnet_dst_cache_foreach_func foreach_func, void *user_data) { ipcom_hash_for_each(ipnet_dst_cache, (Ipcom_hash_foreach_cb_func) foreach_func, user_data); } /* *=========================================================================== * ipnet_dst_cache_init *=========================================================================== * Description: Initializes the destination cache module. * Parameters: * Returns: 0 = success * -IP_ERRNO_ENOMEM = out of memory * */ IP_GLOBAL int ipnet_dst_cache_init(void) { ipnet_dst_slab = ipcom_slab_new("IPNET destination cache", IPCOM_SLAB_F_ZERO, sizeof(Ipnet_dst_cache), 0, IP_NULL, IP_NULL, ipnet->memory_pool); ipnet_dst_cache = ipcom_hash_new((Ipcom_hash_obj_func) ipnet_dst_cache_hash_obj, (Ipcom_hash_key_func) ipnet_dst_cache_hash_key, (Ipcom_hash_cmp_func) ipnet_dst_cache_hash_cmp); if (ipnet_dst_slab == IP_NULL || ipnet_dst_cache == IP_NULL) return -IP_ERRNO_ENOMEM; /**/ ipnet_dst_cache_blackhole_singleton.tx = ipnet_dst_cache_blackhole_action; ipnet_dst_cache_blackhole_singleton.rx = ipnet_dst_cache_blackhole_action; ipnet_dst_cache_blackhole_singleton.path_mtu = 0x7ffffff; #ifdef IPNET_DEBUG ipnet_timeout_to_string((Ipnet_timeout_handler) ipnet_dst_cache_delete, (Ipnet_timeout_to_string_f) ipnet_dst_cache_tmo_to_string); #endif return 0; } /* *=========================================================================== * ipnet_flow_spec_domain *=========================================================================== * Description: Returns the address domain for a flow specification * Parameters: flow_spec - a neighbor entry. * Returns: A domain, IP_AF_INET or IP_AF_INET6 * */ IP_GLOBAL int ipnet_flow_spec_domain(const Ipnet_flow_spec *flow_spec) { /* * Destination cache entries created for "IP_MSG_DONTROUTE" * transmissions has their domain value multiplied by -1. */ return IPNET_FLOW_SPEC_DOMAIN(flow_spec); } #ifdef IPMIP IP_GLOBAL void ipnet_flow_spec_mobility_from_pkt(Ipnet_flow_spec *flow_spec, const Ipcom_pkt *pkt, Ip_u8 proto) { Ip_u8 *encap; Ip_size_t size; ipcom_memset(&flow_spec->mip, 0, sizeof(flow_spec->mip)); /* Store protocol */ flow_spec->mip.key.u8[1] = proto; /* */ if ((encap = ipcom_pkt_get_info_and_size(pkt, IPNET_PKT_INFO_ENCAP, &size)) != IP_NULL) { /* ENCAP */ ipcom_memcpy(&flow_spec->mip.key.u32[1], encap, size); /* GRE KEY */ flow_spec->mip.key.u32[5] = (Ip_u32)pkt->link_cookie; /* ENCAP SIZE */ flow_spec->mip.key.u8[0] = (Ip_u8)size; } else { /* ENCAP SIZE */ flow_spec->mip.key.u8[0] = (Ip_u8)pkt->lla_size; /* ENCAP */ ipcom_memcpy(&flow_spec->mip.key.u32[1], &pkt->data[pkt->lla_off], pkt->lla_size); } } IP_GLOBAL void ipnet_flow_spec_mobility_from_sock(Ipnet_flow_spec *flow_spec, Ipnet_socket *sock) { #ifdef IPNET_USE_RFC2890 if (IP_BIT_ISSET(sock->flags, IPNET_SOCKET_FLAG_GRE_KEY)) flow_spec->mip.key.u32[5] = (Ip_u32)sock->gre_key; #endif /* PROTOCOL */ flow_spec->mip.key.u8[1] = (Ip_u8) sock->proto; } #endif /* *=========================================================================== * ipnet_flow_spec_from_sock *=========================================================================== * Description: Returns a flow specification created from a socket. * Parameters: flow_spec - pointer to where the flow information will * be stored. * sock - an IP_AF_INET or IP_AF_INET6 socket. * domain_data - domain specific data. * domain_func - function that will initialize the domain * specific part of the flow specification. * Returns: 0 = success * <0 = error code * */ IP_GLOBAL int ipnet_flow_spec_from_sock(Ipnet_flow_spec *flow_spec, Ipnet_socket *sock, const void *domain_data, Ipnet_flow_spec_from_sock_domain_func domain_func) { int ret; IPNET_FLOW_SPEC_ZERO(*flow_spec); flow_spec->vr = sock->vr_index; if (sock->ipcom.domain == IP_AF_INET6) flow_spec->flags = IPNET_FSF_IPV6; if (IP_LIKELY((ret = domain_func(flow_spec, sock, domain_data)) == 0)) { #ifdef IPMIP ipnet_flow_spec_mobility_from_sock(flow_spec, sock); #endif } return ret; } IP_GLOBAL int ipnet_flow_spec_from_info(Ipnet_flow_spec *flow_spec, Ip_u16 vr, const void *src, const void *dst, Ip_u32 zone_id, Ip_u8 tos_tclass, Ipnet_netif *iif, Ipnet_netif *oif, Ipnet_flow_spec_from_info_domain_func domain_func) { IPNET_FLOW_SPEC_ZERO(*flow_spec); flow_spec->vr = vr; flow_spec->ds = tos_tclass; flow_spec->ingress_ifindex = iif? iif->ipcom.ifindex : 0; flow_spec->egress_ifindex = oif? oif->ipcom.ifindex : 0; return domain_func(flow_spec, src, dst); } #ifdef IPNET_USE_SHOW /* *=========================================================================== * ipnet_dst_cache_show *=========================================================================== * Description: * Parameters: dst - * bucket - * ideal_bucket - * Returns: * */ IP_STATIC void ipnet_dst_cache_show(Ipnet_dst_cache *dst) { char addr_str[IP_INET6_ADDRSTRLEN]; Ipnet_flow_spec *flow_spec = &dst->flow_spec; ipcom_printf("%-20s ", ipcom_inet_ntop(IPNET_FLOW_SPEC_DOMAIN(flow_spec), &flow_spec->to, addr_str, sizeof(addr_str))); ipcom_printf("%-20s ", ipcom_inet_ntop(IPNET_FLOW_SPEC_DOMAIN(flow_spec), &flow_spec->from, addr_str, sizeof(addr_str))); ipcom_printf("%-4u %-4u %-3u %-4u %-4u %-6u %-6s %-20s\n", flow_spec->ingress_ifindex, flow_spec->egress_ifindex, flow_spec->ds, flow_spec->zone_id, flow_spec->vr, dst->path_mtu, IP_BIT_ISSET(flow_spec->flags, IPNET_FSF_DONTROUTE) ? "Yes" : "", ipcom_inet_ntop(IPNET_FLOW_SPEC_DOMAIN(flow_spec), &dst->laddr, addr_str, sizeof(addr_str))); } /* *=========================================================================== * dstShow *=========================================================================== * Description: Dumps all destination cache entries to the console. * Parameters: * Returns: * */ unsigned dstShow(void); unsigned dstShow(void) { unsigned i; ipcom_printf("%-20s %-20s %-4s %-4s %-3s %-4s %-4s %-6s %-6s %-20s\n", "Dst", "Src", "I-If", "E-If", "DS", "Zone", "VR", "PMTU", "DontRt", "Laddr"); for (i = 0; i < ipnet_dst_cache->size; i++) if (ipnet_dst_cache->table[i]) ipnet_dst_cache_show(ipnet_dst_cache->table[i]); /* * Return the number of elements printed */ return ipnet_dst_cache->elem; } #endif /* IPNET_USE_SHOW */ IP_INLINE Ip_size_t ipnet_pmtu_keylen(int domain) { #ifdef IPCOM_USE_INET6 if (domain == IP_AF_INET6) return sizeof(struct Ip_in6_addr); #endif return sizeof(struct Ip_in_addr); } /* *=========================================================================== * ipnet_pmtu_key_hash *=========================================================================== * Description: Generates a hash value for the pmtu * Parameters: neigh - a pmtu key. * Returns: Hash value. Will always be the same for the same key content. * */ IP_STATIC unsigned ipnet_pmtu_key_hash(Ipnet_pmtu_lookup_key *key) { return ipcom_hash_update(&(key->addr), (int)ipnet_pmtu_keylen(key->domain), key->domain); } /* *=========================================================================== * ipnet_pmtu_obj_hash *=========================================================================== * Description: Generates a hash value for the pmtu. * Parameters: neigh - a neighbor entry. * Returns: Hash value. Will always be the same for the same pmtu. * */ IP_STATIC unsigned ipnet_pmtu_obj_hash(Ipnet_pmtu *pmtu) { Ipnet_pmtu_lookup_key key; (void)ipcom_memcpy(&key, &(pmtu->key), (Ip_size_t) sizeof(Ipnet_pmtu_lookup_key)); return ipnet_pmtu_key_hash(&key); } /* *=========================================================================== * ipnet_pmtu_cmp_hash *=========================================================================== * Description: Checks if the lookup key matches the pmtu entry. * Parameters: pmtu - a pmtu node * key - a lookup key * Returns: IP_TRUE if the key matches the node, IP_FALSE otherwise * */ IP_STATIC unsigned ipnet_pmtu_cmp_hash(Ipnet_pmtu *pmtu, Ipnet_pmtu_lookup_key *key) { return (key->vr_index== pmtu->key.vr_index) && (key->domain == pmtu->key.domain) && (ipcom_memcmp(&(key->addr), &(pmtu->key.addr), ipnet_pmtu_keylen(key->domain)) == 0); } IP_GLOBAL int ipnet_pmtu_init(void) { ipnet_pmtu_slab = ipcom_slab_new("IPNET pmtus", IPCOM_SLAB_F_ZERO, sizeof(Ipnet_pmtu), 0, IP_NULL, IP_NULL, ipnet->memory_pool); ipnet_pmtus = ipcom_hash_new((Ipcom_hash_obj_func) ipnet_pmtu_obj_hash, (Ipcom_hash_key_func) ipnet_pmtu_key_hash, (Ipcom_hash_cmp_func) ipnet_pmtu_cmp_hash); ipnet_pmtu_live_time = INET_DEFAULT_PATHMTU_LIVE_TIME; if (ipnet_pmtu_slab == IP_NULL || ipnet_pmtus == IP_NULL) { /* * Boot error, this system is too memory contrained to support * networking */ IP_PANIC(); return -IP_ERRNO_ENOMEM; } return 0; } /* *=========================================================================== * ipnet_dstcache_delete_by_pmtu *=========================================================================== * Description: Called for each destination cache entry. Entries that matches * the key. * Parameters: dst - a destination cache entry * pmtu - the pmtu node * Returns: * */ IP_STATIC void ipnet_dstcache_delete_by_pmtu(Ipnet_dst_cache *dst, void *pmtu_ptr) { Ipnet_pmtu *pmtu = (Ipnet_pmtu *)pmtu_ptr; if (dst->flow_spec.vr == pmtu->key.vr_index) { if((IP_AF_INET == pmtu->key.domain) && (pmtu->key.addr.in.s_addr == dst->flow_spec.to.in.s_addr)) { ipnet_dst_cache_delete(dst); } #ifdef IPCOM_USE_INET6 else if((IP_AF_INET6 == pmtu->key.domain) && IP_IN6_ARE_ADDR_EQUAL(&pmtu->key.addr.in6, &dst->flow_spec.to.in6)) { ipnet_dst_cache_delete(dst); } #endif } } /* *=========================================================================== * ipnet_pmtu_update *=========================================================================== * Description: update a pmtu entry. * Parameters: pmtu - the pmtu that will be deleted * Returns: * */ IP_STATIC void ipnet_pmtu_update(Ipnet_pmtu *pmtu, Ip_u32 new_path_mtu) { Ip_u32 path_mtu_min = 0; Ip_s64 msec; #ifdef IPCOM_USE_INET if (pmtu->key.domain == IP_AF_INET) { path_mtu_min = ipnet_conf_ip4_min_mtu; if (new_path_mtu < path_mtu_min) { new_path_mtu = path_mtu_min; } } #endif #ifdef IPCOM_USE_INET6 if (pmtu->key.domain == IP_AF_INET6) { path_mtu_min = ipnet_conf_ip6_min_mtu; if (new_path_mtu < path_mtu_min) { new_path_mtu = path_mtu_min; } } #endif if (pmtu->path_mtu > new_path_mtu) { pmtu->path_mtu = new_path_mtu; msec = ipnet_pmtu_live_time * 1000; ipnet_timeout_cancel(pmtu->tmo); (void)ipnet_timeout_schedule(msec, (Ipnet_timeout_handler) ipnet_pmtu_timeout, pmtu, &pmtu->tmo); } } /* *=========================================================================== * ipnet_pmtu_timeout *=========================================================================== * Description: Timeout handler for a pmtu entry. * Parameters: pmtu - the pmtu entry that had its timer * expired. * Returns: * */ IP_STATIC void ipnet_pmtu_timeout(Ipnet_pmtu *pmtu) { ipnet_pmtu_delete(pmtu); } /* *=========================================================================== * ipnet_pmtu_delete *=========================================================================== * Description: delete a pmtu entry. * Parameters: pmtu - the pmtu entry that will be deleted * * Returns: * */ IP_STATIC void ipnet_pmtu_delete(Ipnet_pmtu *pmtu) { ip_assert(pmtu->tmo == IP_NULL); /*ipnet_flow_spec_from_pmtu(&flow_spec, pmtu);*/ /* Delete relevant DST cache */ ipnet_dst_cache_foreach(ipnet_dstcache_delete_by_pmtu, (void *)pmtu); (void)ipcom_hash_remove(ipnet_pmtus, pmtu); ipnet_timeout_cancel(pmtu->tmo); ipcom_slab_free(ipnet_pmtu_slab, pmtu); } /* *=========================================================================== * ipnet_pmtu_new *=========================================================================== * Description: Allocates and initializes a new pmtu entry. * Parameters: vr - virtual router * domain - IP_AF_INET or IP_AF_INET6 . * addr - address of the peer * Returns: * */ IP_STATIC Ipnet_pmtu * ipnet_pmtu_new(int vr, int domain, IP_CONST void *addr, int pmtu_value) { Ipnet_pmtu *pmtu = IP_NULL; Ip_s64 msec; if((0 != ipnet_pmtu_limit) &&(ipnet_pmtus->elem >= ipnet_pmtu_limit)) { IPCOM_LOG2(WARNING, "Pmtu node number exceed limit(peer %s; limit %u)", ipcom_inet_ntop(domain, addr, ipnet->log_buf, sizeof(ipnet->log_buf)), ipnet_pmtu_limit); return IP_NULL; } pmtu = ipcom_slab_alloc(ipnet_pmtu_slab); if (pmtu == IP_NULL) { IPCOM_LOG1(WARNING, "Failed to allocate pmtu memory for peer %s", ipcom_inet_ntop(domain, addr, ipnet->log_buf, sizeof(ipnet->log_buf))); ipnet_try_free_mem(IP_TRUE); return IP_NULL; } (void)ipcom_memset(pmtu, 0, sizeof(Ipnet_pmtu)); pmtu->key.vr_index = vr; pmtu->key.domain = domain; (void)ipcom_memcpy(&(pmtu->key.addr), addr, ipnet_pmtu_keylen(domain)); pmtu->path_mtu = pmtu_value; msec = ipnet_pmtu_live_time * 1000; ipnet_timeout_cancel(pmtu->tmo); (void)ipnet_timeout_schedule(msec, (Ipnet_timeout_handler) ipnet_pmtu_timeout, pmtu, &pmtu->tmo); if (ipcom_hash_add(ipnet_pmtus, pmtu) != IPCOM_SUCCESS) { ipnet_pmtu_delete(pmtu); return IP_NULL; } return pmtu; } /* *=========================================================================== * ipnet_pmtu_get *=========================================================================== * Description: Retrieves pmtu entry. * Parameters: vr - virtual router * domain - IP_AF_INET or IP_AF_INET6 . * addr - address of the peer * Returns: * */ IP_GLOBAL Ipnet_pmtu * ipnet_pmtu_get(int vr, int domain, IP_CONST void *addr) { Ipnet_pmtu_lookup_key key; Ipnet_pmtu *pmtu = IP_NULL; key.vr_index= vr; key.domain= domain; (void)ipcom_memcpy(&(key.addr), addr, (Ip_size_t)ipnet_pmtu_keylen(domain)); pmtu = ipcom_hash_get(ipnet_pmtus, &key); if (pmtu == IP_NULL) { return IP_NULL; } return pmtu; } #ifdef IPCOM_USE_INET IP_STATIC void ipnet_pmtu_proc_inet(Ipcom_pkt *pkt) { Ipnet_pkt_icmp *icmp; Ipnet_pkt_ip *exceeding_ip; Ipnet_pmtu *pmtu; Ip_u32 new_path_mtu; icmp = ipcom_pkt_get_data(pkt, 0); exceeding_ip = (Ipnet_pkt_ip *) icmp->data.failing_pkt.ip; new_path_mtu = IP_GET_NTOHL(icmp->data.failing_pkt.next_hop_mtu); if (new_path_mtu < ipnet_conf_ip4_min_mtu) { IPCOM_LOG2(WARNING, "Pmtu value less than minimum MTU value(pmtu %u; min %u)", new_path_mtu, ipnet_conf_ip4_min_mtu); } pmtu = ipnet_pmtu_get(pkt->vr_index, IP_AF_INET, &(exceeding_ip->dst)); if(IP_NULL == pmtu) { pmtu = ipnet_pmtu_new(pkt->vr_index, IP_AF_INET, &(exceeding_ip->dst), new_path_mtu); return; } ipnet_pmtu_update(pmtu, new_path_mtu); return; } #endif #ifdef IPCOM_USE_INET6 IP_STATIC void ipnet_pmtu_proc_inet6(Ipcom_pkt *pkt) { Ipnet_pkt_ip6 *exceeding_ip6; Ipnet_pkt_icmp6 *icmp6; struct Ip_in6_addr in6; Ipnet_pmtu *pmtu; Ip_u32 new_path_mtu; exceeding_ip6 = ipcom_pkt_get_data(pkt, IPNET_ICMPV6_HDR_SIZE); icmp6 = ipcom_pkt_get_data(pkt, 0); new_path_mtu = IP_GET_NTOHL(&icmp6->ipnet_icmp6_mtu); (void)ipcom_memcpy(&in6, &(exceeding_ip6->dst), (Ip_size_t) sizeof(struct Ip_in6_addr)); if (new_path_mtu < ipnet_conf_ip6_min_mtu) { IPCOM_LOG2(WARNING, "Pmtu value less than minimum MTU value(pmtu %u; min %u)", new_path_mtu, ipnet_conf_ip6_min_mtu); } pmtu = ipnet_pmtu_get(pkt->vr_index, IP_AF_INET6, &in6); if(IP_NULL == pmtu) { pmtu = ipnet_pmtu_new(pkt->vr_index, IP_AF_INET6, &in6, new_path_mtu); return; } ipnet_pmtu_update(pmtu, new_path_mtu); return; } #endif /* *=========================================================================== * ipnet_pmtu_proc *=========================================================================== * Description: try proc pmtu. * Parameters: vr - virtual router * domain - IP_AF_INET or IP_AF_INET6 . * addr - address of the peer * Returns: * */ IP_GLOBAL void ipnet_pmtu_proc(Ipcom_pkt *pkt, int domain) { if(IP_AF_INET == domain) { #ifdef IPCOM_USE_INET ipnet_pmtu_proc_inet(pkt); IPCOM_LOG0(DEBUG, "Process INET PMTU"); #endif } #ifdef IPCOM_USE_INET6 else if(IP_AF_INET6 == domain) { ipnet_pmtu_proc_inet6(pkt); IPCOM_LOG0(DEBUG, "Process INET6 PMTU"); } #endif else IPCOM_LOG1(WARNING, "Domain %d is illegal.", domain); return; } /* *=========================================================================== * ipnet_pmtu_foreach *=========================================================================== * Description: Runs a function for each pmtu entry currently in * the pmtu cache. * Parameters: pmtu_foreach - function to call for each entry * user_data - user defined data passed to pmtu_foreach * as 2:nd argument * Returns: * */ IP_GLOBAL void ipnet_pmtu_foreach(Ipnet_pmtu_foreach_func pmtu_foreach, void *user_data) { ipcom_hash_for_each(ipnet_pmtus, (Ipcom_hash_foreach_cb_func)pmtu_foreach, user_data); } /* *=========================================================================== * ipnet_pmtu_show *=========================================================================== * Description: Dumps a single pmtu entry to the console. * Parameters: pmtu - a pmtu entry. * unused - unused. * Returns: * */ IP_STATIC void ipnet_pmtu_show(Ipnet_pmtu *pmtu, void *domain) { char addr_str[IP_INET6_ADDRSTRLEN]; int show_domain; show_domain = (int)domain; if(show_domain == pmtu->key.domain) (void)ipcom_printf("%-48s %-10u %-10u %-10u\n", ipcom_inet_ntop(show_domain, &pmtu->key.addr, addr_str, sizeof(addr_str)), pmtu->key.vr_index, show_domain, pmtu->path_mtu); } /* *=========================================================================== * int pmtuShow *=========================================================================== * Description: Dumps all pmtu cache entries to the console * Parameters: * Returns: Number of entries dumped. * */ int pmtuShow(void) { #ifdef IPCOM_USE_INET (void)ipcom_printf("%-48s %-10s %-10s %-10s\n", "Address", "VR", "Domain", "PMTU"); (void)ipcom_printf("\nINET:\n"); ipnet_pmtu_foreach(ipnet_pmtu_show, (void *)IP_AF_INET); #endif #ifdef IPCOM_USE_INET6 (void)ipcom_printf("\nINET6:\n"); ipnet_pmtu_foreach(ipnet_pmtu_show, (void *)IP_AF_INET6); #endif return ipnet_pmtus->elem; } /* **************************************************************************** * 11 PUBLIC FUNCTIONS **************************************************************************** */ /* **************************************************************************** * END OF FILE **************************************************************************** */