/* Route IPv6 packet 'skbp', using the route key selectors in 'route_selector' and the interface number 'ifnum_in'. */ Boolean ssh_interceptor_reroute_skb_ipv6(SshInterceptor interceptor, struct sk_buff *skbp, SshUInt16 route_selector, SshUInt32 ifnum_in) { /* we do not need a socket, only fake flow */ struct flowi rt_key; struct dst_entry *dst; struct ipv6hdr *iph6; iph6 = (struct ipv6hdr *) SSH_SKB_GET_NETHDR(skbp); if (iph6 == NULL) { SSH_DEBUG(SSH_D_ERROR, ("Could not access IPv6 header")); return FALSE; } memset(&rt_key, 0, sizeof(rt_key)); rt_key.fl6_dst = iph6->daddr; if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) rt_key.fl6_src = iph6->saddr; if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) { rt_key.oif = (skbp->dev ? skbp->dev->ifindex : 0); SSH_LINUX_ASSERT_IFNUM(rt_key.oif); } #ifdef LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT dst = ip6_route_output(&init_net, NULL, &rt_key); #else /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ dst = ip6_route_output(NULL, &rt_key); #endif /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ if (dst == NULL || dst->error != 0) { SSH_DEBUG(SSH_D_FAIL, ("ip6_route_output failed.")); SSH_DEBUG_HEXDUMP(SSH_D_NICETOKNOW, ("dst "), (unsigned char *) &iph6->daddr, sizeof(iph6->daddr)); SSH_DEBUG_HEXDUMP(SSH_D_NICETOKNOW, ("src "), (unsigned char *) &iph6->saddr, sizeof(iph6->saddr)); SSH_DEBUG(SSH_D_NICETOKNOW, ("oif %d[%s]", (skbp->dev ? skbp->dev->ifindex : -1), (skbp->dev ? skbp->dev->name : "none"))); return FALSE; } if (SSH_SKB_DST(skbp)) dst_release(SSH_SKB_DST(skbp)); SSH_SKB_DST_SET(skbp, dst_clone(dst)); return TRUE; }
void ssh_interceptor_packet_return_dst_entry(SshInterceptor interceptor, SshUInt32 dst_entry_id, SshInterceptorPacket pp, Boolean remove_only) { SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket)pp; SshUInt32 slot = dst_entry_id % SSH_DST_ENTRY_TBL_SIZE; SshDstEntry tmp, prev = NULL; SSH_DEBUG(SSH_D_MIDOK, ("Returning dst entry ID %lu, pp 0x%p, %lu items in cache, " "update %s", (unsigned long)dst_entry_id, pp, (unsigned long)interceptor->dst_entry_cached_items, remove_only == TRUE ? "no" : "yes")); /* Special case, 'real' engine created packets. */ if (dst_entry_id == 0) return; SSH_ASSERT(slot < SSH_DST_ENTRY_TBL_SIZE); ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); for (tmp = interceptor->dst_entry_table[slot]; tmp != NULL; tmp = tmp->next) { /* Do we have a match? */ if (tmp->dst_entry_id == dst_entry_id) { /* Head of list. */ if (tmp == interceptor->dst_entry_table[slot]) { interceptor->dst_entry_table[slot] = tmp->next; interceptor->dst_entry_cached_items--; ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); if (remove_only == FALSE && pp != NULL) SSH_SKB_DST_SET(ipp->skb, tmp->dst_entry); else dst_release(tmp->dst_entry); ssh_free(tmp); SSH_DEBUG(SSH_D_NICETOKNOW, ("Removed cache ID %lu, left %lu items in dst cache", (unsigned long)dst_entry_id, (unsigned long)interceptor->dst_entry_cached_items)); return; } /* Any other place in the list. */ else { prev->next = tmp->next; interceptor->dst_entry_cached_items--; ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); if (remove_only == FALSE) SSH_SKB_DST_SET(ipp->skb, tmp->dst_entry); else dst_release(tmp->dst_entry); ssh_free(tmp); SSH_DEBUG(SSH_D_NICETOKNOW, ("Removed cache ID %lu, left %lu items in dst cache", (unsigned long)dst_entry_id, (unsigned long)interceptor->dst_entry_cached_items)); return; } } prev = tmp; } SSH_DEBUG(SSH_D_NICETOKNOW, ("Cache ID %lu was not found, left %lu items in dst cache", (unsigned long)dst_entry_id, (unsigned long)interceptor->dst_entry_cached_items)); ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); }
/* Route IPv4 packet 'skbp', using the route key selectors in 'route_selector' and the interface number 'ifnum_in'. */ Boolean ssh_interceptor_reroute_skb_ipv4(SshInterceptor interceptor, struct sk_buff *skbp, SshUInt16 route_selector, SshUInt32 ifnum_in) { struct iphdr *iph; int rval = 0; /* Recalculate the route info as the engine might have touched the destination address. This can happen for example if we are in tunnel mode. */ iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp); if (iph == NULL) { SSH_DEBUG(SSH_D_ERROR, ("Could not access IP header")); return FALSE; } /* Release old dst_entry */ if (SSH_SKB_DST(skbp)) dst_release(SSH_SKB_DST(skbp)); SSH_SKB_DST_SET(skbp, NULL); if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) && (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0 && (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM)) { u32 saddr = 0; u8 ipproto = 0; u8 tos = 0; #if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) #ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR u32 fwmark = 0; #endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ #endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ struct net_device *dev; SSH_ASSERT(skbp->protocol == __constant_htons(ETH_P_IP)); if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) saddr = iph->saddr; /* Map 'ifnum_in' to a net_device. */ SSH_LINUX_ASSERT_VALID_IFNUM(ifnum_in); dev = ssh_interceptor_ifnum_to_netdev(interceptor, ifnum_in); /* Clear the IP protocol, if selector does not define it. */ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) == 0) { ipproto = iph->protocol; iph->protocol = 0; } if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) tos = RT_TOS(iph->tos); #if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) #ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR /* Clear the nfmark, if selector does not define it. */ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) == 0) { fwmark = SSH_SKB_MARK(skbp); SSH_SKB_MARK(skbp) = 0; } #endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ #endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ /* Call ip_route_input */ if (ip_route_input(skbp, iph->daddr, saddr, tos, dev) < 0) { SSH_DEBUG(SSH_D_FAIL, ("ip_route_input failed. (0x%08x -> 0x%08x)", iph->saddr, iph->daddr)); SSH_DEBUG(SSH_D_NICETOKNOW, ("dst 0x%08x src 0x%08x iif %d[%s] proto %d tos 0x%02x " "fwmark 0x%x", iph->daddr, saddr, (dev ? dev->ifindex : -1), (dev ? dev->name : "none"), iph->protocol, tos, SSH_SKB_MARK(skbp))); /* Release netdev reference */ if (dev) ssh_interceptor_release_netdev(dev); return FALSE; } /* Write original IP protocol back to skb */ if (ipproto) iph->protocol = ipproto; #if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) #ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR /* Write original fwmark back to skb */ if (fwmark) SSH_SKB_MARK(skbp) = fwmark; #endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ #endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ /* Release netdev reference */ if (dev) ssh_interceptor_release_netdev(dev); } else { struct rtable *rt; struct flowi rt_key; if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0) route_selector &= ~SSH_INTERCEPTOR_ROUTE_KEY_SRC; memset(&rt_key, 0, sizeof(rt_key)); rt_key.fl4_dst = iph->daddr; if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) rt_key.fl4_src = iph->saddr; if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) rt_key.oif = (skbp->dev ? skbp->dev->ifindex : 0); if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) rt_key.proto = iph->protocol; if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) rt_key.fl4_tos = RT_TOS(iph->tos); rt_key.fl4_scope = RT_SCOPE_UNIVERSE; #if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) #ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) { #ifdef LINUX_HAS_SKB_MARK rt_key.mark = SSH_SKB_MARK(skbp); #else /* LINUX_HAS_SKB_MARK */ #ifdef CONFIG_IP_ROUTE_FWMARK rt_key.fl4_fwmark = SSH_SKB_MARK(skbp); #endif /* CONFIG_IP_ROUTE_FWMARK */ #endif /* LINUX_HAS_SKB_MARK */ } #endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ #endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ /* Call ip_route_output */ #ifdef LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT rval = ip_route_output_key(&init_net, &rt, &rt_key); #else /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ rval = ip_route_output_key(&rt, &rt_key); #endif /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ if (rval < 0) { SSH_DEBUG(SSH_D_FAIL, ("ip_route_output_key failed (0x%08x -> 0x%08x): %d", iph->saddr, iph->daddr, rval)); SSH_DEBUG(SSH_D_NICETOKNOW, ("dst 0x%08x src 0x%08x oif %d[%s] proto %d tos 0x%02x" "fwmark 0x%x", iph->daddr, ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ? iph->saddr : 0), ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ? rt_key.oif : -1), ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ? (skbp->dev ? skbp->dev->name : "none") : "none"), ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ? iph->protocol : -1), ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) ? iph->tos : 0), ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) ? SSH_SKB_MARK(skbp) : 0))); return FALSE; } /* Make a new dst because we just rechecked the route. */ SSH_SKB_DST_SET(skbp, dst_clone(&rt->u.dst)); /* Release the routing table entry ; otherwise a memory leak occurs in the route entry table. */ ip_rt_put(rt); } SSH_ASSERT(SSH_SKB_DST(skbp) != NULL); #ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR #ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED) { /* Check if need to create a child dst_entry with interface MTU. */ if (SSH_SKB_DST(skbp)->child == NULL) { if (interceptor_route_create_child_dst(SSH_SKB_DST(skbp)) == NULL) { SSH_DEBUG(SSH_D_ERROR, ("Could not create child dst_entry for dst %p", SSH_SKB_DST(skbp))); return FALSE; } } /* Pop dst stack and use the child entry with interface MTU for sending the packet. */ SSH_SKB_DST_SET(skbp, dst_pop(SSH_SKB_DST(skbp))); } #endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */ #endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ return TRUE; }
/* Perform route lookup using linux ip_route_input. The route lookup will use the following selectors: dst, src, inbound ifnum, ip protocol, tos, and fwmark. The source address is expected to be non-local and it must be defined. The following selectors are ignored: dst port, src port, icmp type, icmp code, ipsec spi. */ Boolean ssh_interceptor_route_input_ipv4(SshInterceptor interceptor, SshInterceptorRouteKey key, SshUInt16 selector, SshInterceptorRouteResult result) { u32 daddr, saddr; u8 ipproto; u8 tos; u32 fwmark; struct sk_buff *skbp; struct net_device *dev; struct rtable *rt; int rval = 0; u16 rt_type; struct iphdr *iph = NULL; #ifdef DEBUG_LIGHT unsigned char *rt_type_str; unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE]; unsigned char src_buf[SSH_IP_ADDR_STRING_SIZE]; #endif /* DEBUG_LIGHT */ SSH_IP4_ENCODE(&key->dst, (unsigned char *) &daddr); /* Initialize */ saddr = 0; ipproto = 0; tos = 0; fwmark = 0; dev = NULL; if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) SSH_IP4_ENCODE(&key->src, (unsigned char *) &saddr); if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM) { SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum); dev = ssh_interceptor_ifnum_to_netdev(interceptor, key->ifnum); } if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ipproto = key->ipproto; if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) tos = key->nh.ip4.tos; #if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) #ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR /* Use linux fw_mark in routing */ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) fwmark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR]; #endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ #endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ /* Build dummy skb */ skbp = alloc_skb(SSH_IPH4_HDRLEN, GFP_ATOMIC); if (skbp == NULL) goto fail; SSH_SKB_RESET_MACHDR(skbp); iph = (struct iphdr *) skb_put(skbp, SSH_IPH4_HDRLEN); if (iph == NULL) { dev_kfree_skb(skbp); goto fail; } SSH_SKB_SET_NETHDR(skbp, (unsigned char *) iph); SSH_SKB_DST_SET(skbp, NULL); skbp->protocol = __constant_htons(ETH_P_IP); SSH_SKB_MARK(skbp) = fwmark; iph->protocol = ipproto; SSH_DEBUG(SSH_D_LOWOK, ("Route lookup: " "dst %s src %s ifnum %d[%s] ipproto %d tos 0x%02x fwmark 0x%x", ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ? ssh_ipaddr_print(&key->src, src_buf, sizeof(src_buf)) : NULL), (dev ? dev->ifindex : -1), (dev ? dev->name : "none"), ipproto, tos, fwmark)); /* Perform route lookup */ rval = ip_route_input(skbp, daddr, saddr, tos, dev); if (rval < 0 || SSH_SKB_DST(skbp) == NULL) { dev_kfree_skb(skbp); goto fail; } /* Get the gateway, mtu and ifnum */ rt = (struct rtable *) SSH_SKB_DST(skbp); SSH_IP4_DECODE(result->gw, &rt->rt_gateway); result->mtu = SSH_DST_MTU(SSH_SKB_DST(skbp)); result->ifnum = SSH_SKB_DST(skbp)->dev->ifindex; rt_type = rt->rt_type; #ifdef DEBUG_LIGHT switch (rt_type) { case RTN_UNSPEC: rt_type_str = "unspec"; break; case RTN_UNICAST: rt_type_str = "unicast"; break; case RTN_LOCAL: rt_type_str = "local"; break; case RTN_BROADCAST: rt_type_str = "broadcast"; break; case RTN_ANYCAST: rt_type_str = "anycast"; break; case RTN_MULTICAST: rt_type_str = "multicast"; break; case RTN_BLACKHOLE: rt_type_str = "blackhole"; break; case RTN_UNREACHABLE: rt_type_str = "unreachable"; break; case RTN_PROHIBIT: rt_type_str = "prohibit"; break; case RTN_THROW: rt_type_str = "throw"; break; case RTN_NAT: rt_type_str = "nat"; break; case RTN_XRESOLVE: rt_type_str = "xresolve"; break; default: rt_type_str = "unknown"; } #endif /* DEBUG_LIGHT */ SSH_DEBUG(SSH_D_LOWOK, ("Route result: dst %s via %s ifnum %d[%s] mtu %d type %s [%d]", dst_buf, ssh_ipaddr_print(result->gw, src_buf, sizeof(src_buf)), (int) result->ifnum, (rt->u.dst.dev->name ? rt->u.dst.dev->name : "none"), result->mtu, rt_type_str, rt_type)); #ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR #ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING /* Check if need to create a child dst_entry with interface MTU. */ if ((selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED) && SSH_SKB_DST(skbp)->child == NULL) { if (interceptor_route_create_child_dst(SSH_SKB_DST(skbp)) == NULL) SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p", SSH_SKB_DST(skbp))); } #endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */ #endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ /* Release the routing table entry ; otherwise a memory leak occurs in the route entry table. */ dst_release(SSH_SKB_DST(skbp)); SSH_SKB_DST_SET(skbp, NULL); dev_kfree_skb(skbp); /* Assert that ifnum fits into the SshInterceptorIfnum data type. */ SSH_LINUX_ASSERT_IFNUM(result->ifnum); /* Check that ifnum does not collide with SSH_INTERCEPTOR_INVALID_IFNUM. */ if (result->ifnum == SSH_INTERCEPTOR_INVALID_IFNUM) goto fail; /* Accept only unicast, broadcast, anycast, multicast and local routes. */ if (rt_type == RTN_UNICAST || rt_type == RTN_BROADCAST || rt_type == RTN_ANYCAST || rt_type == RTN_MULTICAST || rt_type == RTN_LOCAL) { ssh_interceptor_release_netdev(dev); SSH_LINUX_ASSERT_VALID_IFNUM(result->ifnum); return TRUE; } /* Fail route lookup for other route types. */ fail: if (dev) ssh_interceptor_release_netdev(dev); SSH_DEBUG(SSH_D_FAIL, ("Route lookup for %s failed with code %d", ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)), rval)); return FALSE; }