/* Internal mapping from ifnum to net_device. This function will assert that 'ifnum' is a valid SshInterceptorIfnum and that the corresponding net_device exists in the interface hashtable. This function will dev_hold() the net_device. The caller of this function must release it by calling ssh_interceptor_release_netdev(). If `context_return' is not NULL then this sets it to point to the interface context. */ inline struct net_device * ssh_interceptor_ifnum_to_netdev_ctx(SshInterceptor interceptor, SshUInt32 ifnum, void **context_return) { SshInterceptorInternalInterface iface; struct net_device *dev = NULL; SSH_LINUX_ASSERT_VALID_IFNUM(ifnum); read_lock(&interceptor->nf->if_table_lock); for (iface = interceptor->nf->if_hash[ifnum % SSH_LINUX_IFACE_HASH_SIZE]; iface && iface->ifindex != ifnum; iface = iface->next) ; if (iface) { SSH_ASSERT(iface->generation == interceptor->nf->if_generation); dev = iface->dev; SSH_ASSERT(dev != NULL); dev_hold(dev); if (context_return != NULL) *context_return = iface->context; } read_unlock(&interceptor->nf->if_table_lock); return dev; }
/* Set the interface context. */ inline Boolean ssh_interceptor_iface_set_context(SshInterceptor interceptor, SshUInt32 ifnum, void *context) { SshInterceptorInternalInterface iface; Boolean ret = FALSE; SSH_LINUX_ASSERT_VALID_IFNUM(ifnum); read_lock(&interceptor->nf->if_table_lock); for (iface = interceptor->nf->if_hash[ifnum % SSH_LINUX_IFACE_HASH_SIZE]; iface && iface->ifindex != ifnum; iface = iface->next) ; if (iface) { SSH_ASSERT(iface->generation == interceptor->nf->if_generation); iface->context = context; ret = TRUE; } read_unlock(&interceptor->nf->if_table_lock); return ret; }
/* Netdevice low level transmit callback function. */ static int ssh_virtual_adapter_xmit(struct sk_buff *skbp, struct net_device *dev) { struct net_device_stats *stats; SshInterceptorInternalPacket ipp; SshInterceptor interceptor; SshVirtualAdapter adapter; SshInterceptorIfnum ifnum_in; SshVirtualAdapterPacketCB packet_cb; void *adapter_context; SSH_ASSERT(skbp != NULL && dev != NULL); interceptor = ssh_interceptor_context; ssh_kernel_mutex_lock(interceptor->interceptor_lock); adapter = (SshVirtualAdapter) SSH_LINUX_NET_DEVICE_PRIV(dev); if (adapter == NULL) { /* Virtual adapter is not attached. */ ssh_kernel_mutex_unlock(interceptor->interceptor_lock); SSH_DEBUG(SSH_D_NICETOKNOW, ("Device %d [%s] is not attached to a SshVirtualAdapter", dev->ifindex, dev->name)); discard: /* Silently discard the packet. */ dev_kfree_skb_any(skbp); return NET_XMIT_SUCCESS; } /* Update statistics */ stats = ssh_virtual_adapter_get_stats(dev); SSH_ASSERT(stats != NULL); stats->tx_packets++; if (!adapter->initialized || !adapter->packet_cb) { /* This is not very uncommon. Packets end up here if the virtual adapter is set up when policymanager is not running. We discard the packets silently, as otherwise the stack will attempt to transmit IPv6 IGMP messages indefinitely. */ ssh_kernel_mutex_unlock(interceptor->interceptor_lock); SSH_DEBUG(SSH_D_LOWOK, ("Virtual adapter %d [%s] not initialized / no cb.", adapter->dev->ifindex, adapter->dev->name)); goto discard; } ifnum_in = (SshInterceptorIfnum) dev->ifindex; SSH_LINUX_ASSERT_VALID_IFNUM(ifnum_in); /* Pass the packet to the packet callback. */ ipp = ssh_interceptor_packet_alloc_header(interceptor, SSH_PACKET_FROMPROTOCOL, SSH_PROTOCOL_ETHERNET, ifnum_in, SSH_INTERCEPTOR_INVALID_IFNUM, skbp, FALSE, FALSE, TRUE); if (ipp == NULL) { SSH_DEBUG(SSH_D_NICETOKNOW, ("Could not allocate packet header, virtual adapter %d [%s]", adapter->dev->ifindex, adapter->dev->name)); stats->tx_errors++; ssh_kernel_mutex_unlock(interceptor->interceptor_lock); return NET_XMIT_DROP; } stats->tx_bytes += ipp->skb->len; packet_cb = adapter->packet_cb; adapter_context = adapter->adapter_context; ssh_kernel_mutex_unlock(interceptor->interceptor_lock); SSH_DEBUG(SSH_D_NICETOKNOW, ("Passing skb %p from virtual adapter %d [%s] to engine", ipp->skb, (int)ifnum_in, dev->name)); /* Call the callback. This will eventually free `pp'. */ (*packet_cb)(interceptor, (SshInterceptorPacket) ipp, adapter_context); return NET_XMIT_SUCCESS; }
/* 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 (skb_dst(skbp)) dst_release(skb_dst(skbp)); 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. Ugly, but necessary to make sure the skb gets rerouted like engine expects. */ 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. Ugly, but necessary to make sure the skb gets rerouted like engine expects. */ 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); /* Note, skb modifications are not un-done as the caller frees the skb. If this is changed then the modifications should be un-done here before returning. */ 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))); /* Note, skb modifications are not un-done as the caller frees the skb. If this is changed then the modifications should be un-done here before returning. */ return FALSE; } /* Make a new dst because we just rechecked the route. */ 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(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 (skb_dst(skbp)->child == NULL) { if (interceptor_route_create_child_dst(skb_dst(skbp)) == NULL) { SSH_DEBUG(SSH_D_ERROR, ("Could not create child dst_entry for dst %p", skb_dst(skbp))); return FALSE; } } /* Pop dst stack and use the child entry with interface MTU for sending the packet. */ skb_dst_set(skbp, dst_pop(skb_dst(skbp))); } #endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */ #endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ return TRUE; }
/* 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; SSH_INTERCEPTOR_STACK_MARK(); 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 %@ src %@ ifnum %d", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ? &key->src : NULL), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ? key->ifnum : -1))); /* Perform route lookup */ /* we do not need a socket, only fake flow */ #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) { goto fail; } else if (dst->error != 0) { error = dst->error; 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 */ if (rt->rt6i_nexthop) SSH_IP6_DECODE(result->gw, &rt->rt6i_nexthop->primary_key); else SSH_IP6_DECODE(result->gw, &rt_key.fl6_dst.s6_addr); result->mtu = SSH_LINUX_DST_MTU(&rt->u.dst); /* 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: %@ via %@ ifnum %d[%s] mtu %d flags 0x%08x[%s%s]", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, result->gw, result->ifnum, (dst->dev ? dst->dev->name : "none"), result->mtu, rt6i_flags, ((rt6i_flags & RTF_UP) ? "up " : ""), ((rt6i_flags & RTF_REJECT) ? "reject" : "") )); /* 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 %@ failed with code %d", ssh_ipaddr_render, &key->dst, error)); 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; #endif /* DEBUG_LIGHT */ SSH_INTERCEPTOR_STACK_MARK(); 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); 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 %@ src %@ ifnum %d[%s] ipproto %d tos 0x%02x fwmark 0x%x", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ? &key->src : 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 || skb_dst(skbp) == NULL) { dev_kfree_skb(skbp); goto fail; } /* Get the gateway, mtu and ifnum */ rt = (struct rtable *) skb_dst(skbp); SSH_IP4_DECODE(result->gw, &rt->rt_gateway); result->mtu = SSH_LINUX_DST_MTU(skb_dst(skbp)); result->ifnum = 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 %@ via %@ ifnum %d[%s] mtu %d type %s [%d]", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, result->gw, 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) && skb_dst(skbp)->child == NULL) { if (interceptor_route_create_child_dst(skb_dst(skbp)) == NULL) SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p", 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(skb_dst(skbp)); 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 %@ failed with code %d", ssh_ipaddr_render, &key->dst, rval)); return FALSE; }
/* 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; #endif /* DEBUG_LIGHT */ SSH_INTERCEPTOR_STACK_MARK(); 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 %@ src %@ ifnum %d ipproto %d tos 0x%02x fwmark 0x%x", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ? &key->src : NULL), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ? key->ifnum : -1), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ? key->ipproto : -1), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) ? key->nh.ip4.tos : 0), ((selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) ? fwmark : 0))); /* Perform route lookup */ #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) { goto fail; } /* Get the gateway, mtu and ifnum */ SSH_IP4_DECODE(result->gw, &rt->rt_gateway); result->mtu = SSH_LINUX_DST_MTU(&rt->u.dst); result->ifnum = rt->u.dst.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 %@ via %@ ifnum %d[%s] mtu %d type %s [%d]", ssh_ipaddr_render, &key->dst, ssh_ipaddr_render, result->gw, 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) && rt->u.dst.child == NULL) { if (interceptor_route_create_child_dst(&rt->u.dst) == NULL) SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p", &rt->u.dst)); } #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 %@ failed with code %d", ssh_ipaddr_render, &key->dst, rval)); return FALSE; }
/* The interceptor_update_interfaces() function traverses the kernels list of interfaces, grabs a refcnt for each one, and updates the interceptors 'ifnum->net_device' cache (optimizing away the need to grab a lock, traverse dev_base linked list, unlock, for each packet). This function grabs 'if_table_lock' (for writing) and dev_base lock. */ static void ssh_interceptor_update_interfaces(SshInterceptor interceptor) { SshInterceptorInternalInterface iface, iface_prev, iface_next; struct net_device *dev; SshUInt32 i, hashvalue, ifindex; /* WARNING: TWO LOCKS HELD AT THE SAME TIME. BE CAREFUL! dev_base_lock MUST be held to ensure integrity during traversal of list of interfaces in kernel. */ SSH_LOCK_LINUX_DEV_LIST(); /* Grab 'if_table_lock' for modifying the interface table. */ write_lock(&interceptor->nf->if_table_lock); /* Increment 'if_generation' */ interceptor->nf->if_generation++; if (interceptor->nf->if_generation == 0) interceptor->nf->if_generation++; /* Handle wrapping */ /* Traverse net_device list, add new interfaces to hashtable, and mark existing entries up-to-date. */ for (dev = SSH_FIRST_NET_DEVICE(); dev != NULL; dev = SSH_NEXT_NET_DEVICE(dev)) { ifindex = (SshUInt32) dev->ifindex; /* Ignore the loopback device. */ if (dev->flags & IFF_LOOPBACK) continue; /* Ignore interfaces that collide with SSH_INTERCEPTOR_INVALID_IFNUM */ if (ifindex == SSH_INTERCEPTOR_INVALID_IFNUM) { ssh_warning("Interface index collides with " "SSH_INTERCEPTOR_INVALID_IFNUM, " "ignoring interface %d[%s]", ifindex, (dev->name ? dev->name : "<none>")); continue; } /* Assert that 'dev->ifindex' is otherwise valid. */ SSH_LINUX_ASSERT_VALID_IFNUM(ifindex); /* Lookup interface from the hashtable. */ for (iface = interceptor->nf->if_hash[ifindex % SSH_LINUX_IFACE_HASH_SIZE]; iface != NULL && iface->ifindex != ifindex; iface = iface->next) ; /* Interface found */ if (iface) { if (iface->dev == dev) { SSH_DEBUG(SSH_D_NICETOKNOW, ("Old interface %d[%s]", ifindex, (dev->name ? dev->name : "<none>"))); /* Mark up-to-date. */ iface->generation = interceptor->nf->if_generation; } /* Interface index matches, but net_device has changed. */ else { SSH_DEBUG(SSH_D_NICETOKNOW, ("Changed interface %d[%s] (from %d[%s])", ifindex, (dev->name ? dev->name : "<none>"), iface->ifindex, (iface->dev->name ? iface->dev->name : "<none>"))); /* Release old net_device. */ SSH_ASSERT(iface->dev != NULL); dev_put(iface->dev); /* Hold new net_device. */ dev_hold(dev); wmb(); /* Make sure assignments are not reordered. */ iface->dev = dev; iface->ifindex = ifindex; iface->context = NULL; /* Mark up-to-date. */ iface->generation = interceptor->nf->if_generation; } } /* Interface not found */ else { SSH_DEBUG(SSH_D_NICETOKNOW, ("New interface %d[%s]", ifindex, (dev->name ? dev->name : "<none>"))); /* Allocate new interface entry */ iface = ssh_interceptor_alloc_iface(interceptor); if (iface) { /* Hold new net_device. */ dev_hold(dev); /* Fill interface entry. */ iface->ifindex = ifindex; iface->dev = dev; iface->context = NULL; /* Mark up-to-date */ iface->generation = interceptor->nf->if_generation; /* Add entry to hashtable. */ hashvalue = iface->ifindex % SSH_LINUX_IFACE_HASH_SIZE; iface->next = interceptor->nf->if_hash[hashvalue]; wmb(); interceptor->nf->if_hash[hashvalue] = iface; } else { SSH_DEBUG(SSH_D_FAIL, ("Could not allocate memory for new interface %d[%s]", ifindex, (dev->name ? dev->name : "<none>"))); } } } /* Remove old interfaces from the table */ for (i = 0; i < SSH_LINUX_IFACE_HASH_SIZE; i++) { iface_prev = NULL; for (iface = interceptor->nf->if_hash[i]; iface != NULL; iface = iface_next) { if (iface->generation != 0 && iface->generation != interceptor->nf->if_generation) { SSH_DEBUG(SSH_D_NICETOKNOW, ("Disappeared interface %d[%s]", iface->ifindex, (iface->dev->name ? iface->dev->name : "<none>"))); /* Release old netdevice */ SSH_ASSERT(iface->dev != NULL); dev_put(iface->dev); #ifdef DEBUG_LIGHT wmb(); iface->dev = NULL; iface->ifindex = SSH_INTERCEPTOR_INVALID_IFNUM; #endif /* DEBUG_LIGHT */ /* Mark entry freed. */ iface->generation = 0; /* Remove entry from hashtable. */ if (iface_prev) iface_prev->next = iface->next; else interceptor->nf->if_hash[i] = iface->next; iface_next = iface->next; iface->next = NULL; } else { iface_prev = iface; iface_next = iface->next; } } } /* Unlock if_table_lock */ write_unlock(&interceptor->nf->if_table_lock); /* Release dev_base_lock. */ SSH_UNLOCK_LINUX_DEV_LIST(); /* Notify changes to engine */ if (interceptor->engine != NULL && interceptor->engine_open == TRUE) { local_bh_disable(); ssh_interceptor_send_interfaces(interceptor); local_bh_enable(); } return; }