diff options
Diffstat (limited to 'drivers/interceptor/linux_route.c')
-rw-r--r-- | drivers/interceptor/linux_route.c | 1136 |
1 files changed, 1136 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_route.c b/drivers/interceptor/linux_route.c new file mode 100644 index 0000000..ab93c6a --- /dev/null +++ b/drivers/interceptor/linux_route.c @@ -0,0 +1,1136 @@ +/* Netfilter Driver for IPSec VPN Client + * + * Copyright(c) 2012 Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * linux_route.c + * + * Linux interceptor implementation of interceptor API routing functions. + * + */ + +#include "linux_internal.h" + +/****************** Internal data types and declarations ********************/ + +typedef struct SshInterceptorRouteResultRec +{ + SshIpAddrStruct gw[1]; + SshInterceptorIfnum ifnum; + SshUInt16 mtu; +} SshInterceptorRouteResultStruct, *SshInterceptorRouteResult; + +/****************** Internal Utility Functions ******************************/ + +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#if (defined LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING) \ + || (defined LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING) + +/* Create a child dst_entry with locked interface MTU, and attach it to `dst'. + This is needed on newer linux kernels and IP_ONLY_INTERCEPTOR builds, + where the IP stack fragments packets to path MTU after ssh_interceptor_send. +*/ +static struct dst_entry * +interceptor_route_create_child_dst(struct dst_entry *dst, Boolean ipv6) +{ + struct dst_entry *child; +#ifdef LINUX_HAS_DST_COPY_METRICS + SshUInt32 set; + struct rt6_info *rt6; + struct rtable *rt; +#endif /* LINUX_HAS_DST_COPY_METRICS */ + + /* Allocate a dst_entry and copy relevant fields from dst. */ + child = SSH_DST_ALLOC(dst); + if (child == NULL) + return NULL; + + child->input = dst->input; + child->output = dst->output; + + /* Child is not added to dst hash, and linux native IPsec is disabled. */ + child->flags |= (DST_NOHASH | DST_NOPOLICY | DST_NOXFRM); + + /* Copy route metrics and lock MTU to interface MTU. */ +#ifdef LINUX_HAS_DST_COPY_METRICS + if (ipv6 == TRUE) + { + rt6 = (struct rt6_info *)child; + memset(&rt6->rt6i_table, 0, sizeof(*rt6) - sizeof(struct dst_entry)); + } + else + { + rt = (struct rtable *)child; + memset(&SSH_RTABLE_FIRST_MEMBER(rt), 0, + sizeof(*rt) - sizeof(struct dst_entry)); + } + + dst_copy_metrics(child, dst); + set = dst_metric(child, RTAX_LOCK); + set |= 1 << RTAX_MTU; + dst_metric_set(child, RTAX_LOCK, set); +#else /* LINUX_HAS_DST_COPY_METRICS */ + memcpy(child->metrics, dst->metrics, sizeof(child->metrics)); + child->metrics[RTAX_LOCK-1] |= 1 << RTAX_MTU; +#endif /* LINUX_HAS_DST_COPY_METRICS */ + +#ifdef CONFIG_NET_CLS_ROUTE + child->tclassid = dst->tclassid; +#endif /* CONFIG_NET_CLS_ROUTE */ + +#ifdef CONFIG_XFRM + child->xfrm = NULL; +#endif /* CONFIG_XFRM */ + +#ifdef LINUX_HAS_HH_CACHE + if (dst->hh) + { + atomic_inc(&dst->hh->hh_refcnt); + child->hh = dst->hh; + } +#endif /* LINUX_HAS_HH_CACHE */ + +#ifdef LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS + if (dst_get_neighbour(dst) != NULL) + dst_set_neighbour(child, neigh_clone(dst_get_neighbour(dst))); +#else /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */ + if (dst->neighbour != NULL) + child->neighbour = neigh_clone(dst->neighbour); +#endif /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */ + + if (dst->dev) + { + dev_hold(dst->dev); + child->dev = dst->dev; + } + + SSH_ASSERT(dst->child == NULL); + dst->child = dst_clone(child); + + SSH_DEBUG(SSH_D_MIDOK, ("Allocated child %p dst_entry for dst %p mtu %d", + child, dst, dst_mtu(dst))); + + return child; +} + +#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING + || LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +static inline int interceptor_ip4_route_output(struct rtable **rt, + struct flowi *fli) +{ + int rval = 0; +#ifdef LINUX_USE_NF_FOR_ROUTE_OUTPUT + struct dst_entry *dst = NULL; + const struct nf_afinfo *afinfo = NULL; + unsigned short family = AF_INET; + + rcu_read_lock(); + afinfo = nf_get_afinfo(family); + if (!afinfo) + { + rcu_read_unlock(); + SSH_DEBUG(SSH_D_FAIL, ("Failed to get nf afinfo")); + return -1; + } + + rval = afinfo->route(&init_net, &dst, fli, TRUE); + rcu_read_unlock(); + + *rt = (struct rtable *)dst; + +#else /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */ + +#ifdef LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT + rval = ip_route_output_key(&init_net, rt, fli); +#else /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ + rval = ip_route_output_key(rt, fli); +#endif /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ +#endif /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */ + + if (rval > 0) + rval = -rval; + + return rval; +} + +static inline struct dst_entry *interceptor_ip6_route_output(struct flowi *fli) +{ + struct dst_entry *dst = NULL; + + /* Perform route lookup */ + /* we do not need a socket, only fake flow */ +#ifdef LINUX_USE_NF_FOR_ROUTE_OUTPUT + const struct nf_afinfo *afinfo = NULL; + unsigned short family = AF_INET6; + int rval = 0; + + rcu_read_lock(); + afinfo = nf_get_afinfo(family); + if (!afinfo) + { + rcu_read_unlock(); + SSH_DEBUG(SSH_D_FAIL, ("Failed to get nf afinfo")); + return NULL; + } + + rval = afinfo->route(&init_net, &dst, fli, FALSE); + rcu_read_unlock(); + if (rval != 0) + { + SSH_DEBUG(SSH_D_FAIL, ("Failed to get route from IPv6 NF")); + return NULL; + } +#else /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */ + +#ifdef LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT + dst = ip6_route_output(&init_net, NULL, fli); +#else /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ + dst = ip6_route_output(NULL, fli); +#endif /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */ +#endif /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */ + + return dst; +} + +/****************** Implementation of ssh_interceptor_route *****************/ + +/* Perform route lookup using linux ip_route_output_key. + + The route lookup will use the following selectors: + dst, src, outbound ifnum, ip protocol, tos, and fwmark. + The source address is expected to be local or undefined. + + The following selectors are ignored: + dst port, src port, icmp type, icmp code, ipsec spi. */ +Boolean +ssh_interceptor_route_output_ipv4(SshInterceptor interceptor, + SshInterceptorRouteKey key, + SshUInt16 selector, + SshInterceptorRouteResult result) +{ + u32 daddr; + struct rtable *rt; + int rval; + struct flowi rt_key; + u16 rt_type; +#ifdef DEBUG_LIGHT + unsigned char *rt_type_str; + u32 fwmark = 0; + 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 rt_key with zero values */ + memset(&rt_key, 0, sizeof(rt_key)); + + rt_key.fl4_dst = daddr; + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) + SSH_IP4_ENCODE(&key->src, (unsigned char *) &rt_key.fl4_src); + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) + { + SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum); + rt_key.oif = key->ifnum; + } + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) + rt_key.proto = key->ipproto; + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) + rt_key.fl4_tos = key->nh.ip4.tos; + rt_key.fl4_scope = RT_SCOPE_UNIVERSE; + +#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) + { +#ifdef LINUX_HAS_SKB_MARK + rt_key.mark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR]; +#else /* LINUX_HAS_SKB_MARK */ +#ifdef CONFIG_IP_ROUTE_FWMARK + rt_key.fl4_fwmark = + key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR]; +#endif /* CONFIG_IP_ROUTE_FWMARK */ +#endif /* LINUX_HAS_SKB_MARK */ +#ifdef DEBUG_LIGHT + fwmark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR]; +#endif /* DEBUG_LIGHT */ + } + +#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ +#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ + + SSH_DEBUG(SSH_D_LOWOK, + ("Route lookup: " + "dst %s src %s ifnum %d ipproto %d tos 0x%02x fwmark 0x%lx", + 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), + (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)? + key->ifnum : -1), + (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ? + key->ipproto : -1), + ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) ? + key->nh.ip4.tos : 0), + (unsigned long) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION)? + fwmark : 0))); + + /* Perform route lookup */ + + rval = interceptor_ip4_route_output(&rt, &rt_key); + if (rval < 0) + { + goto fail; + } + + /* Get the gateway, mtu and ifnum */ + + SSH_IP4_DECODE(result->gw, &rt->rt_gateway); + result->mtu = SSH_DST_MTU(&SSH_RT_DST(rt)); + result->ifnum = SSH_RT_DST(rt).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, + (SSH_RT_DST(rt).dev->name ? SSH_RT_DST(rt).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_RT_DST(rt).child == NULL) + { + if (interceptor_route_create_child_dst(&SSH_RT_DST(rt), FALSE) == NULL) + SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p", + &SSH_RT_DST(rt))); + } +#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. */ + ip_rt_put(rt); + + /* 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_LINUX_ASSERT_VALID_IFNUM(result->ifnum); + return TRUE; + } + + fail: + /* Fail route lookup for other route types */ + SSH_DEBUG(SSH_D_FAIL, ("Route lookup for %s failed with code %d", + dst_buf, rval)); + + return FALSE; +} + + +/* 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 (dev == NULL) + return FALSE; + + 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->ifindex, dev->name, + 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, + (SSH_RT_DST(rt).dev->name ? SSH_RT_DST(rt).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), FALSE) == 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: + 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; +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + +/* Perform route lookup using linux ip6_route_output. + + The route lookup will use the following selectors: + dst, src, outbound ifnum. + + The following selectors are ignored: + ipv6 priority, flowlabel, ip protocol, dst port, src port, + icmp type, icmp code, ipsec spi, and fwmark. */ +Boolean +ssh_interceptor_route_output_ipv6(SshInterceptor interceptor, + SshInterceptorRouteKey key, + SshUInt16 selector, + SshInterceptorRouteResult result) +{ + struct flowi rt_key; + struct dst_entry *dst; + struct rt6_info *rt; + u32 rt6i_flags; + int error = 0; + struct neighbour *neigh; +#ifdef DEBUG_LIGHT + unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE]; + unsigned char src_buf[SSH_IP_ADDR_STRING_SIZE]; +#endif /* DEBUG_LIGHT */ + + memset(&rt_key, 0, sizeof(rt_key)); + + SSH_IP6_ENCODE(&key->dst, rt_key.fl6_dst.s6_addr); + + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) + SSH_IP6_ENCODE(&key->src, rt_key.fl6_src.s6_addr); + + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) + { + SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum); + rt_key.oif = key->ifnum; + } + + SSH_DEBUG(SSH_D_LOWOK, + ("Route lookup: dst %s src %s ifnum %d", + 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), + (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ? + key->ifnum : -1))); + + dst = interceptor_ip6_route_output(&rt_key); + if (dst == NULL) + { + goto fail; + } + else if (dst->error != 0) + { + error = dst->error; + /* Release dst_entry */ + dst_release(dst); + goto fail; + } + + rt = (struct rt6_info *) dst; + + /* Get the gateway, mtu and ifnum */ + + /* For an example of retrieving routing information for IPv6 + within Linux kernel (2.4.19) see inet6_rtm_getroute() + in /usr/src/linux/net/ipv6/route.c */ +#ifdef LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS + neigh = dst_get_neighbour(&rt->dst); +#else /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */ + neigh = rt->rt6i_nexthop; +#endif /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */ + + if (neigh != NULL) + SSH_IP6_DECODE(result->gw, &neigh->primary_key); + else + SSH_IP6_DECODE(result->gw, &rt_key.fl6_dst.s6_addr); + + result->mtu = SSH_DST_MTU(&SSH_RT_DST(rt)); + + /* The interface number might not be ok, but that is a problem + for the recipient of the routing information. */ + result->ifnum = dst->dev->ifindex; + + rt6i_flags = rt->rt6i_flags; + + SSH_DEBUG(SSH_D_LOWOK, + ("Route result: %s via %s ifnum %d[%s] mtu %d flags 0x%08x[%s%s]", + dst_buf, ssh_ipaddr_print(result->gw, src_buf, sizeof(src_buf)), + (int) result->ifnum, (dst->dev ? dst->dev->name : "none"), + result->mtu, rt6i_flags, ((rt6i_flags & RTF_UP) ? "up " : ""), + ((rt6i_flags & RTF_REJECT) ? "reject" : ""))); + + +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING + /* Check if need to create a child dst_entry with interface MTU. */ + if (selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED) + { + if (dst->child == NULL) + if (interceptor_route_create_child_dst(dst, TRUE) == NULL) + SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p", + dst)); + } +#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + /* Release dst_entry */ + dst_release(dst); + + /* 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 valid routes */ + if ((rt6i_flags & RTF_UP) + && (rt6i_flags & RTF_REJECT) == 0) + { + SSH_LINUX_ASSERT_VALID_IFNUM(result->ifnum); + return TRUE; + } + + /* Fail route lookup for reject and unknown routes */ + fail: + SSH_DEBUG(SSH_D_FAIL, ("Route lookup for %s failed with code %d", + dst_buf, error)); + + return FALSE; +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +/* Performs a route lookup for the given destination address. + This also always calls a callback function. */ + +void +ssh_interceptor_route(SshInterceptor interceptor, + SshInterceptorRouteKey key, + SshInterceptorRouteCompletion callback, + void *cb_context) +{ + SshInterceptorRouteResultStruct result; + + /* It is a fatal error to call ssh_interceptor_route with + a routing key that does not specify the destination address. */ + SSH_ASSERT(SSH_IP_DEFINED(&key->dst)); + + if (SSH_IP_IS4(&key->dst)) + { + /* Key specifies non-local src address and inbound ifnum + -> use ssh_interceptor_route_input_ipv4 */ + if ((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) + && (key->selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0 + && (key->selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM)) + { + /* Assert that all mandatory selectors are present. */ + SSH_ASSERT(key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC); + SSH_ASSERT(SSH_IP_IS4(&key->src)); + SSH_ASSERT(key->selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM); + + if (!ssh_interceptor_route_input_ipv4(interceptor, key, + key->selector, &result)) + goto fail; + } + + /* Otherwise use ssh_interceptor_route_output_ipv4 */ + else + { + SshUInt16 selector = key->selector; + + /* Assert that all mandatory selectors are present. */ + SSH_ASSERT((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) == 0 + || SSH_IP_IS4(&key->src)); + + /* Key specifies non-local src address. + Linux ip_route_output_key will fail such route lookups, + so we must clear the src address selector and do the + route lookup as if src was one of local addresses. + For example, locally generated TCP RST packets are + such packets. */ + if ((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0) + selector &= ~SSH_INTERCEPTOR_ROUTE_KEY_SRC; + + if (!ssh_interceptor_route_output_ipv4(interceptor, key, selector, + &result)) + goto fail; + } + + (*callback)(TRUE, result.gw, result.ifnum, result.mtu, cb_context); + return; + } + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (SSH_IP_IS6(&key->dst)) + { + /* Assert that all mandatory selectors are present. */ + SSH_ASSERT((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) == 0 + || SSH_IP_IS6(&key->src)); + + /* Always use ip6_route_output for IPv6 */ + if (!ssh_interceptor_route_output_ipv6(interceptor, key, key->selector, + &result)) + goto fail; + + (*callback)(TRUE, result.gw, result.ifnum, result.mtu, cb_context); + return; + } +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Fallback to error */ + fail: + SSH_DEBUG(SSH_D_FAIL, ("Route lookup failed, unknown dst address type")); + (*callback) (FALSE, NULL, 0, 0, cb_context); +} + + +/**************************** Rerouting of Packets **************************/ + + +/* 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); + if (dev == NULL) + return FALSE; + + /* 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 */ + 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 */ + 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) */ + + rval = interceptor_ip4_route_output(&rt, &rt_key); + 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(&SSH_RT_DST(rt))); + + /* 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), FALSE) + == 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. */ +#ifdef LINUX_DST_POP_IS_SKB_DST_POP + SSH_SKB_DST_SET(skbp, dst_clone(skb_dst_pop(skbp))); +#else /* LINUX_DST_POP_IS_SKB_DST_POP */ + SSH_SKB_DST_SET(skbp, dst_pop(SSH_SKB_DST(skbp))); +#endif /*LINUX_DST_POP_IS_SKB_DST_POP */ + } +#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + return TRUE; +} + + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + +/* 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); + } + + dst = interceptor_ip6_route_output(&rt_key); + if (dst == NULL || dst->error != 0) + { + if (dst != NULL) + dst_release(dst); + + 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)); + +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING + if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED) + { + SSH_DEBUG(SSH_D_LOWOK, + ("Creating a new child entry for dst %p, child %p %lu", + dst, dst->child, skbp->_skb_refdst)); + + /* Check if need to create a child dst_entry with interface MTU. */ + if (dst->child == NULL) + { + if (interceptor_route_create_child_dst(dst, TRUE) == NULL) + { + SSH_DEBUG(SSH_D_ERROR, + ("Could not create child dst_entry for dst %p", + dst)); + + dst_release(dst); + return FALSE; + } + } + + SSH_SKB_DST_SET(skbp, dst_clone(dst->child)); + dst_release(dst); + } + else +#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + { + /* No need to clone the dst as ip6_route_output has already taken + one for us. */ + SSH_SKB_DST_SET(skbp, dst); + } + + return TRUE; +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ |