diff options
Diffstat (limited to 'drivers/interceptor/linux_ip_glue.c')
-rw-r--r-- | drivers/interceptor/linux_ip_glue.c | 1841 |
1 files changed, 1841 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_ip_glue.c b/drivers/interceptor/linux_ip_glue.c new file mode 100644 index 0000000..7638c08 --- /dev/null +++ b/drivers/interceptor/linux_ip_glue.c @@ -0,0 +1,1841 @@ +/* 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_ip_glue.c + * + * Linux interceptor packet interception using Netfilter hooks. + * + */ + +#include "linux_internal.h" + +extern SshInterceptor ssh_interceptor_context; + +/********************* Prototypes for packet handling hooks *****************/ + +static unsigned int +ssh_interceptor_packet_in_ipv4(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static unsigned int +ssh_interceptor_packet_in_ipv6(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR +static unsigned int +ssh_interceptor_packet_in_arp(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +static unsigned int +ssh_interceptor_packet_out(int pf, + unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); + +static unsigned int +ssh_interceptor_packet_out_ipv4(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static unsigned int +ssh_interceptor_packet_out_ipv6(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + +/********** Definition of netfilter hooks to register **********************/ + +struct SshLinuxHooksRec +{ + const char *name; /* Name of hook */ + Boolean is_registered; /* Has this hook been registered? */ + Boolean is_mandatory; /* If the registration fails, + abort initialization? */ + int pf; /* Protocol family */ + int hooknum; /* Hook id */ + int priority; /* Netfilter priority of hook */ + + /* Actual hook function */ + unsigned int (*hookfn)(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn)(struct sk_buff *)); + + struct nf_hook_ops *ops; /* Pointer to storage for nf_hook_ops + to store the netfilter hook configuration + and state */ +}; + +struct nf_hook_ops ssh_nf_in4, ssh_nf_out4; + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR +struct nf_hook_ops ssh_nf_in_arp; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +struct nf_hook_ops ssh_nf_in6, ssh_nf_out6; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +static struct SshLinuxHooksRec ssh_nf_hooks[] = +{ + { "ipv4 in", + FALSE, TRUE, PF_INET, SSH_NF_IP_PRE_ROUTING, SSH_NF_IP_PRI_FIRST, + ssh_interceptor_packet_in_ipv4, &ssh_nf_in4 }, + { "ipv4 out", + FALSE, TRUE, PF_INET, SSH_NF_IP_POST_ROUTING, SSH_NF_IP_PRI_FIRST, + ssh_interceptor_packet_out_ipv4, &ssh_nf_out4 }, + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + { "arp in", + FALSE, TRUE, SSH_NFPROTO_ARP, NF_ARP_IN, 1, + ssh_interceptor_packet_in_arp, &ssh_nf_in_arp }, +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + { "ipv6 in", + FALSE, TRUE, PF_INET6, SSH_NF_IP6_PRE_ROUTING, SSH_NF_IP6_PRI_FIRST, + ssh_interceptor_packet_in_ipv6, &ssh_nf_in6 }, + { "ipv6 out", + FALSE, TRUE, PF_INET6, SSH_NF_IP6_POST_ROUTING, SSH_NF_IP6_PRI_FIRST, + ssh_interceptor_packet_out_ipv6, &ssh_nf_out6 }, +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + { NULL, + 0, 0, 0, 0, 0, + NULL_FNPTR, NULL }, +}; + + +/******************************** Module parameters *************************/ + +/* Module parameters. Default values. These can be overrided at the + loading of the module from the command line. These set the priority + for netfilter hooks. */ + +static int in_priority = SSH_NF_IP_PRI_FIRST; +static int out_priority = SSH_NF_IP_PRI_FIRST; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static int in6_priority = SSH_NF_IP6_PRI_FIRST; +static int out6_priority = SSH_NF_IP6_PRI_FIRST; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +MODULE_PARM_DESC(in_priority, "Netfilter hook priority at IPv4 PREROUTING"); +MODULE_PARM_DESC(out_priority, "Netfilter hook priority at IPv4 POSTROUTING"); +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +MODULE_PARM_DESC(in6_priority, "Netfilter hook priority at IPv6 PREROUTING"); +MODULE_PARM_DESC(out6_priority, "Netfilter hook priority at IPv6 POSTROUTING"); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +module_param(in_priority, int, 0444); +module_param(out_priority, int, 0444); +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +module_param(in6_priority, int, 0444); +module_param(out6_priority, int, 0444); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + +/************* Utility functions ********************************************/ + +/* Map a SshInterceptorProtocol to a skbuff protocol id */ +static unsigned short +ssh_proto_to_skb_proto(SshInterceptorProtocol protocol) +{ + /* If support for other than IPv6, IPv4 and ARP + inside the engine on Linux are to be supported, their + protocol types must be added here. */ + switch (protocol) + { +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case SSH_PROTOCOL_IP6: + return __constant_htons(ETH_P_IPV6); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + case SSH_PROTOCOL_ARP: + return __constant_htons(ETH_P_ARP); + + case SSH_PROTOCOL_IP4: + return __constant_htons(ETH_P_IP); + + default: + SSH_DEBUG(SSH_D_ERROR, ("Unknown protocol %d", protocol)); + return 0; + } +} + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + +/* Map ethernet type to a skbuff protocol id */ +static unsigned short +ssh_ethertype_to_skb_proto(SshInterceptorProtocol protocol, + size_t media_header_len, + unsigned char *media_header) +{ + SshUInt16 ethertype; + + if (protocol != SSH_PROTOCOL_ETHERNET) + return ssh_proto_to_skb_proto(protocol); + + SSH_ASSERT(media_header_len >= SSH_ETHERH_HDRLEN); + ethertype = SSH_GET_16BIT(media_header + SSH_ETHERH_OFS_TYPE); + + /* If support for other than IPv6, IPv4 and ARP + inside the engine on Linux are to be supported, their + ethernet types must be added here. */ + switch (ethertype) + { + case SSH_ETHERTYPE_IPv6: + return __constant_htons(ETH_P_IPV6); + + case SSH_ETHERTYPE_ARP: + return __constant_htons(ETH_P_ARP); + + case SSH_ETHERTYPE_IP: + return __constant_htons(ETH_P_IP); + + default: + SSH_DEBUG(SSH_D_ERROR, ("Unknown ethertype 0x%x", ethertype)); + return 0; + } +} + +/* Return the pointer to start of ethernet header */ +static struct ethhdr *ssh_get_eth_hdr(const struct sk_buff *skb) +{ +#ifdef LINUX_HAS_ETH_HDR + return eth_hdr(skb); +#else /* LINUX_HAS_ETH_HDR */ + return skb->mac.ethernet; +#endif /* LINUX_HAS_ETH_HDR */ +} + +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + +/**************** Packet reception and sending *****************************/ + +/**************** Inbound packet interception ******************************/ + + +/* Common code for ssh_interceptor_packet_in_finish_ipv4() + and ssh_interceptor_packet_in_finish_ipv6(). + + If SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE is set, then + this function is called as the okfn() from the netfilter + infrastructure after inbound netfilter hook iteration. + Otherwise this function is called directly from the inbound + netfilter hookfn(). +*/ + +static inline int +ssh_interceptor_packet_in_finish(struct sk_buff *skbp, + SshInterceptorProtocol protocol) +{ + SshInterceptorInternalPacket packet; + SshInterceptor interceptor; + int ifnum_in; + + interceptor = ssh_interceptor_context; + + SSH_ASSERT(skbp->dev != NULL); + ifnum_in = skbp->dev->ifindex; + + SSH_DEBUG(SSH_D_HIGHSTART, + ("incoming packet 0x%p, len %d proto 0x%x [%s] iface %d [%s]", + skbp, skbp->len, ntohs(skbp->protocol), + (protocol == SSH_PROTOCOL_IP4 ? "ipv4" : + (protocol == SSH_PROTOCOL_IP6 ? "ipv6" : + (protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" : "unknown"))), + ifnum_in, skbp->dev->name)); + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + if (unlikely(protocol == SSH_PROTOCOL_ETHERNET)) + { + /* Unwrap ethernet header. This is needed by the engine currently + due to some sillyness in the ARP handling. */ + if (SSH_SKB_GET_MACHDR(skbp)) + { + size_t media_header_len = skbp->data - SSH_SKB_GET_MACHDR(skbp); + if (media_header_len) + skb_push(skbp, media_header_len); + } + } +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + /* Encapsulate the skb into a new packet. Note that ip_rcv() + performs IP header checksum computation, so we do not + need to. */ + packet = ssh_interceptor_packet_alloc_header(interceptor, + SSH_PACKET_FROMADAPTER + |SSH_PACKET_IP4HDRCKSUMOK, + protocol, + ifnum_in, + SSH_INTERCEPTOR_INVALID_IFNUM, + skbp, + FALSE, TRUE, TRUE); + + if (unlikely(packet == NULL)) + { + SSH_DEBUG(SSH_D_FAIL, ("encapsulation failed, packet dropped")); + /* Free sk_buff and return error */ + dev_kfree_skb_any(skbp); + SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; }); + return -EPERM; + } + +#ifdef DEBUG_LIGHT + packet->skb->dev = NULL; +#endif /* DEBUG_LIGHT */ + + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, + ("incoming packet, len=%d flags=0x%08lx%s", + packet->skb->len, + (unsigned long) packet->packet.flags, + ((packet->packet.flags & SSH_PACKET_HWCKSUM) ? + " [hwcsum]" : "")), + packet->skb->data, packet->skb->len); + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_fastpath_bytes_in += (SshUInt64) packet->skb->len; + interceptor->stats.num_fastpath_packets_in++; + }); + + /* Pass the packet to then engine. Which eventually will call + ssh_interceptor_send. */ + SSH_LINUX_INTERCEPTOR_PACKET_CALLBACK(interceptor, + (SshInterceptorPacket) packet); + + /* Return ok */ + return 0; +} + + +#ifdef SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE + +static inline int +ssh_interceptor_packet_in_finish_ipv4(struct sk_buff *skbp) +{ + return ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP4); +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static inline int +ssh_interceptor_packet_in_finish_ipv6(struct sk_buff *skbp) +{ + return ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP6); +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#endif /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */ + + +/* ssh_interceptor_packet_in() is the common code for + inbound netfilter hooks ssh_interceptor_packet_in_ipv4(), + ssh_interceptor_packet_in_ipv6(), and ssh_interceptor_packet_in_arp(). + + This function must only be called from softirq context, or + with softirqs disabled. This function MUST NOT be called + from a hardirq (as then it could pre-empt itself on the same CPU). */ + +static inline unsigned int +ssh_interceptor_packet_in(int pf, + unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + SshInterceptor interceptor; + struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb); +#ifdef DEBUG_LIGHT + struct iphdr *iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp); +#endif /* DEBUG_LIGHT */ + + SSH_DEBUG(SSH_D_PCKDMP, + ("IN 0x%04x/0x%x (%s[%d]->%s[%d]) len %d " + "type=0x%08x %08x -> %08x dst 0x%p dev (%s[%d])", + htons(skbp->protocol), + pf, + (in ? in->name : "<none>"), + (in ? in->ifindex : -1), + (out ? out->name : "<none>"), + (out ? out->ifindex : -1), + skbp->len, + skbp->pkt_type, + iph->saddr, iph->daddr, + SSH_SKB_DST(skbp), + (skbp->dev ? skbp->dev->name : "<none>"), + (skbp->dev ? skbp->dev->ifindex : -1) + )); + + interceptor = ssh_interceptor_context; + + if (interceptor->enable_interception == FALSE) + { + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + SSH_DEBUG(11, ("packet passed through")); + return NF_ACCEPT; + } + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_bytes_in += (SshUInt64) skbp->len; + interceptor->stats.num_packets_in++; + }); + + /* If the device is to loopback, pass the packet through. */ + if (in->flags & IFF_LOOPBACK) + { + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } + + /* The linux stack makes a copy of each locally generated + broadcast / multicast packet. The original packet will + be sent to network as any packet. The copy will be marked + as PACKET_LOOPBACK and looped back to local stack. + So we let the copy continue back to local stack. */ + if (skbp->pkt_type == PACKET_LOOPBACK) + { + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } + + /* VPNClient code relies on skb->dev to be set. + If packet has been processed by AF_PACKET (what tcpdump uses), + then skb->dev has been cleared, and we must reset it here. */ + SSH_ASSERT(skbp->dev == NULL || skbp->dev == in); + if (skbp->dev == NULL) + { + skbp->dev = (struct net_device *) in; + /* Increment refcount of skbp->dev. */ + dev_hold(skbp->dev); + } + +#ifdef SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE + + /* Traverse lower priority netfilter hooks. */ + switch (pf) + { + case PF_INET: + SSH_ASSERT(hooknum == SSH_NF_IP_PRE_ROUTING); + NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING, skbp, + (struct net_device *) in, (struct net_device *) out, + ssh_interceptor_packet_in_finish_ipv4, + ssh_nf_in4.priority + 1); + return NF_STOLEN; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + SSH_ASSERT(hooknum == SSH_NF_IP6_PRE_ROUTING); + NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, skbp, + (struct net_device *) in, (struct net_device *) out, + ssh_interceptor_packet_in_finish_ipv6, + ssh_nf_in6.priority + 1); + return NF_STOLEN; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + case SSH_NFPROTO_ARP: + /* There is no point in looping ARP packets, + just continue packet processing, and return NF_STOLEN. */ + ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_ETHERNET); + return NF_STOLEN; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + default: + SSH_NOTREACHED; + return NF_DROP; + } + +#else /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */ + + /* Continue packet processing ssh_interceptor_packet_in_finish() */ + switch (pf) + { + case PF_INET: + ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP4); + return NF_STOLEN; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP6); + return NF_STOLEN; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + case SSH_NFPROTO_ARP: + ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_ETHERNET); + return NF_STOLEN; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + default: + break; + } + +#endif /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */ + + SSH_NOTREACHED; + return NF_DROP; +} + +/* Netfilter hookfn() wrapper function for IPv4 packets. */ +static unsigned int +ssh_interceptor_packet_in_ipv4(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + return ssh_interceptor_packet_in(PF_INET, hooknum, skb, in, out, okfn); +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +/* Netfilter nf_hookfn() wrapper function for IPv6 packets. */ +static unsigned int +ssh_interceptor_packet_in_ipv6(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + return ssh_interceptor_packet_in(PF_INET6, hooknum, skb, in, out, okfn); +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR +/* Netfilter nf_hookfn() wrapper function for ARP packets. */ +static unsigned int +ssh_interceptor_packet_in_arp(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + return ssh_interceptor_packet_in(SSH_NFPROTO_ARP, hooknum, skb, + in, out, okfn); +} +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + +/**************** Outbound packet interception *****************************/ + + +/* Common code for ssh_interceptor_packet_out_finish_ipv4() + and ssh_interceptor_packet_out_finish_ipv6(). + + If SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is set, then + this function is called as the okfn() function from the + netfilter infrastructure after the outbound hook iteration. + Otherwise, this function is called directly from the outbound + netfilter hookfn(). + + This function must only be called from softirq context or + from an exception. It will disable softirqs for the engine + processing. This function MUST NOT be called + from a hardirq (as then it could pre-empt itself + on the same CPU). */ +static inline int +ssh_interceptor_packet_out_finish(struct sk_buff *skbp, + SshInterceptorProtocol protocol) +{ + SshInterceptorInternalPacket packet; + SshInterceptor interceptor; + int ifnum_in; + SshUInt32 flags; + + SSH_ASSERT(skbp->dev != NULL); + ifnum_in = skbp->dev->ifindex; + + interceptor = ssh_interceptor_context; + + SSH_DEBUG(SSH_D_HIGHSTART, + ("outgoing packet 0x%p, len %d proto 0x%x [%s] iface %d [%s]", + skbp, skbp->len, ntohs(skbp->protocol), + (protocol == SSH_PROTOCOL_IP4 ? "ipv4" : + (protocol == SSH_PROTOCOL_IP6 ? "ipv6" : + (protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" : "unknown"))), + ifnum_in, skbp->dev->name)); + + local_bh_disable(); + + flags = SSH_PACKET_FROMPROTOCOL; + +#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING + /* Is this a local packet which is allowed to be fragmented? */ + if (protocol == SSH_PROTOCOL_IP6 && skbp->local_df == 1) + { + SSH_DEBUG(SSH_D_NICETOKNOW, ("Local packet, fragmentation allowed.")); + flags |= SSH_PACKET_FRAGMENTATION_ALLOWED; + } +#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */ + +#ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING + /* Is this a local packet which is allowed to be fragmented? */ + if (protocol == SSH_PROTOCOL_IP4 && skbp->local_df == 1) + { + SSH_DEBUG(SSH_D_NICETOKNOW, ("Local packet, fragmentation allowed.")); + flags |= SSH_PACKET_FRAGMENTATION_ALLOWED; + } +#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */ + + /* Encapsulate the skb into a new packet. This function + holds packet_lock during freelist manipulation. */ + packet = ssh_interceptor_packet_alloc_header(interceptor, + flags, + protocol, + ifnum_in, + SSH_INTERCEPTOR_INVALID_IFNUM, + skbp, + FALSE, TRUE, TRUE); + if (unlikely(packet == NULL)) + { + SSH_DEBUG(SSH_D_FAIL, ("encapsulation failed, packet dropped")); + local_bh_enable(); + /* Free sk_buff and return error */ + dev_kfree_skb_any(skbp); + SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; }); + return -EPERM; + } + +#ifdef DEBUG_LIGHT + packet->skb->dev = NULL; +#endif /* DEBUG_LIGHT */ + + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, + ("outgoing packet, len=%d flags=0x%08lx%s", + packet->skb->len, + (unsigned long) packet->packet.flags, + ((packet->packet.flags & SSH_PACKET_HWCKSUM) ? + " [hwcsum]" : "")), + packet->skb->data, packet->skb->len); + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_fastpath_bytes_out += (SshUInt64) packet->skb->len; + interceptor->stats.num_fastpath_packets_out++; + }); + + /* Pass the packet to engine. */ + SSH_LINUX_INTERCEPTOR_PACKET_CALLBACK(interceptor, + (SshInterceptorPacket) packet); + + local_bh_enable(); + + /* Return ok */ + return 0; +} + +#ifdef SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE + +static inline int +ssh_interceptor_packet_out_finish_ipv4(struct sk_buff *skbp) +{ + return ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP4); +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static inline int +ssh_interceptor_packet_out_finish_ipv6(struct sk_buff *skbp) +{ + return ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP6); +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ +#endif /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */ + +/* ssh_interceptor_packet_out() is the common code for + outbound netfilter hook ssh_interceptor_packet_out_ipv4() + and ssh_interceptor_packet_out_ipv6(). + + Netfilter does not provide a clean way of intercepting ALL packets + being sent via an output chain after all other filters are processed. + Therefore this hook is registered first, and then if + SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is set the packet is + sent back to SSH_NF_IP_POST_ROUTING hook with (*okfn)() + pointing to the actual interception function. If + SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is not set, then + all following netfilter hook functions in SSH_NF_IP_POST_ROUTING hook + are skipped. + + This function must only be called from softirq context or + from an exception. It will disable softirqs for the engine + processing. This function MUST NOT be called + from a hardirq (as then it could pre-empt itself + on the same CPU). */ + +static inline unsigned int +ssh_interceptor_packet_out(int pf, + unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + SshInterceptor interceptor; + struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb); +#ifdef DEBUG_LIGHT + struct iphdr *iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp); +#endif /* DEBUG_LIGHT */ + + SSH_DEBUG(SSH_D_PCKDMP, + ("OUT 0x%04x/0x%x (%s[%d]->%s[%d]) len %d " + "type=0x%08x %08x -> %08x dst 0x%p dev (%s[%d])", + htons(skbp->protocol), + pf, + (in ? in->name : "<none>"), + (in ? in->ifindex : -1), + (out ? out->name : "<none>"), + (out ? out->ifindex : -1), + skbp->len, + skbp->pkt_type, + iph->saddr, iph->daddr, + SSH_SKB_DST(skbp), + (skbp->dev ? skbp->dev->name : "<none>"), + (skbp->dev ? skbp->dev->ifindex : -1) + )); + + interceptor = ssh_interceptor_context; + + if (interceptor->enable_interception == FALSE) + { + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + SSH_DEBUG(11, ("packet passed through")); + return NF_ACCEPT; + } + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_packets_out++; + interceptor->stats.num_bytes_out += (SshUInt64) skbp->len; + }); + + /* If the device is to loopback, pass the packet through. */ + if (out->flags & IFF_LOOPBACK) + { + SSH_DEBUG(SSH_D_NICETOKNOW, ("loopback packet passed through")); + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len), + skbp->data, skbp->len); + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } + + /* Linux network stack creates a copy of locally generated broadcast + and multicast packets, and sends the copies to local stack using + 'ip_dev_loopback_xmit' or 'ip6_dev_loopback_xmit' as the NFHOOK + okfn. Intercept the original packets and let the local copies go + through. */ + if (pf == PF_INET && + okfn != interceptor->linux_fn.ip_finish_output) + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("local IPv4 broadcast loopback packet passed through")); + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len), + skbp->data, skbp->len); + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (pf == PF_INET6 && + okfn != interceptor->linux_fn.ip6_output_finish) + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("local IPv6 broadcast loopback packet passed through")); + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len), + skbp->data, skbp->len); + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } + +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#ifdef LINUX_IP_ONLY_PASSTHROUGH_NDISC + if (pf == PF_INET6 && + skbp->sk == dev_net(skbp->dev)->ipv6.ndisc_sk) + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Neighbour discovery packet passed through")); + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, + ("length %d dumping %d bytes", + (int) skbp->len, (int) skb_headlen(skbp)), + skbp->data, skb_headlen(skbp)); + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + return NF_ACCEPT; + } +#endif /* LINUX_IP_ONLY_PASSTHROUGH_NDISC */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Assert that we are about to intercept the packet from + the correct netfilter hook on the correct path. */ +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + SSH_ASSERT(okfn == interceptor->linux_fn.ip_finish_output || + okfn == interceptor->linux_fn.ip6_output_finish); +#else /* SSH_LINUX_INTERCEPTOR_IPV6 */ + SSH_ASSERT(okfn == interceptor->linux_fn.ip_finish_output); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifdef SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE + + /* Traverse lower priority netfilter hooks. */ + switch (pf) + { + case PF_INET: + SSH_ASSERT(hooknum == SSH_NF_IP_POST_ROUTING); + NF_HOOK_THRESH(PF_INET, SSH_NF_IP_POST_ROUTING, skbp, + (struct net_device *) in, (struct net_device *) out, + ssh_interceptor_packet_out_finish_ipv4, + ssh_nf_out4.priority + 1); + return NF_STOLEN; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + SSH_ASSERT(hooknum == SSH_NF_IP6_POST_ROUTING); + NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_POST_ROUTING, skbp, + (struct net_device *) in, (struct net_device *) out, + ssh_interceptor_packet_out_finish_ipv6, + ssh_nf_out6.priority + 1); + return NF_STOLEN; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + SSH_NOTREACHED; + return NF_DROP; + } + +#else /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */ + + /* Continue packet processing in ssh_interceptor_packet_out_finish() */ + switch (pf) + { + case PF_INET: + SSH_ASSERT(hooknum == SSH_NF_IP_POST_ROUTING); + ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP4); + return NF_STOLEN; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + SSH_ASSERT(hooknum == SSH_NF_IP6_POST_ROUTING); + ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP6); + return NF_STOLEN; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + break; + } +#endif /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */ + + SSH_NOTREACHED; + return NF_DROP; +} + +/* Netfilter nf_hookfn() wrapper function for IPv4 packets. */ +static unsigned int +ssh_interceptor_packet_out_ipv4(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + return ssh_interceptor_packet_out(PF_INET, hooknum, skb, in, out, okfn); +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +/* Netfilter nf_hookfn() wrapper function for IPv6 packets. */ +static unsigned int +ssh_interceptor_packet_out_ipv6(unsigned int hooknum, + SshHookSkb *skb, + const struct net_device *in, + const struct net_device *out, + int (*okfn) (struct sk_buff *)) +{ + struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb); + if (skbp->dev == NULL) + skbp->dev = SSH_SKB_DST(skbp)->dev; + + return ssh_interceptor_packet_out(PF_INET6, hooknum, skb, in, out, okfn); +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + +/**************** Sending packets ******************************************/ + +/* Netfilter okfn() for sending packets to network after + SSH_NF_IP_FORWARD hook traversal. */ + +static inline int +ssh_interceptor_send_to_network(int pf, struct sk_buff *skbp) +{ + skbp->pkt_type = PACKET_OUTGOING; + +#ifdef CONFIG_NETFILTER_DEBUG +#ifdef LINUX_HAS_SKB_NFDEBUG + if (pf == PF_INET) + { + /* Mark SSH_NF_IP_LOCAL_OUT chains visited */ + if (skbp->sk) + skbp->nf_debug = ((1 << SSH_NF_IP_LOCAL_OUT) + | (1 << SSH_NF_IP_POST_ROUTING)); + + /* skbp is unowned, netfilter thinks this is a forwarded skb. + Mark SSH_NF_IP_PRE_ROUTING, SSH_NF_IP_FORWARD, + and SSH_NF_IP_POST_ROUTING + chains visited */ + else + skbp->nf_debug = ((1 << SSH_NF_IP_PRE_ROUTING) + | (1 << SSH_NF_IP_FORWARD) + | (1 << SSH_NF_IP_POST_ROUTING)); + } +#endif /* LINUX_HAS_SKB_NFDEBUG */ +#endif /* CONFIG_NETFILTER_DEBUG */ + + SSH_LINUX_STATISTICS(ssh_interceptor_context, + { + ssh_interceptor_context->stats.num_packets_sent++; + ssh_interceptor_context->stats.num_bytes_sent += (SshUInt64) skbp->len; + }); + +#ifdef SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE + /* Traverse lower priority netfilter hooks. */ + switch (pf) + { + case PF_INET: + return NF_HOOK_THRESH(PF_INET, SSH_NF_IP_POST_ROUTING, skbp, + NULL, skbp->dev, + ssh_interceptor_context-> + linux_fn.ip_finish_output, + ssh_nf_out4.priority + 1); + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + return NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_POST_ROUTING, skbp, + NULL, skbp->dev, + ssh_interceptor_context-> + linux_fn.ip6_output_finish, + ssh_nf_out6.priority + 1); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + break; + } + +#else /* SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE */ + /* Pass packet to output path. */ + switch (pf) + { + case PF_INET: + return (*ssh_interceptor_context->linux_fn.ip_finish_output)(skbp); + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case PF_INET6: + return (*ssh_interceptor_context->linux_fn.ip6_output_finish)(skbp); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + break; + } +#endif /* SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE */ + + SSH_NOTREACHED; + dev_kfree_skb_any(skbp); + return -EPERM; +} + +static inline int +ssh_interceptor_send_to_network_ipv4(struct sk_buff *skbp) +{ + return ssh_interceptor_send_to_network(PF_INET, skbp); +} + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 +static inline int +ssh_interceptor_send_to_network_ipv6(struct sk_buff *skbp) +{ + return ssh_interceptor_send_to_network(PF_INET6, skbp); +} +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +/* ssh_interceptor_send() sends a packet to the network or to the + protocol stacks. This will eventually free the packet by calling + ssh_interceptor_packet_free. The packet header should not be + touched once this function has been called. + + ssh_interceptor_send() function for both media level and IP level + interceptor. This grabs a packet with media layer headers attached + and sends it to the interface defined by 'pp->ifnum_out'. */ +void +ssh_interceptor_send(SshInterceptor interceptor, + SshInterceptorPacket pp, + size_t media_header_len) +{ + SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket) pp; + struct net_device *dev; +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE + struct net_device *in_dev = NULL; +#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */ +#endif /*SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + unsigned char *neth; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + size_t offset; + SshUInt8 ipproto; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + SSH_DEBUG(SSH_D_HIGHSTART, + ("sending packet to %s, " + "len=%d flags=0x%08lx ifnum_out=%lu protocol=%s[0x%x]", + ((pp->flags & SSH_PACKET_FROMPROTOCOL) ? "network" : + ((pp->flags & SSH_PACKET_FROMADAPTER) ? "stack" : + "nowhere")), + ipp->skb->len, (unsigned long) pp->flags, + (unsigned long) pp->ifnum_out, + (pp->protocol == SSH_PROTOCOL_IP4 ? "ipv4" : + (pp->protocol == SSH_PROTOCOL_IP6 ? "ipv6" : + (pp->protocol == SSH_PROTOCOL_ARP ? "arp" : + (pp->protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" : + "unknown")))), + pp->protocol)); + + SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("packet, len %d", ipp->skb->len), + ipp->skb->data, ipp->skb->len); + + /* Require that any references to previous devices + were released by the entrypoint hooks. */ + SSH_ASSERT(ipp->skb->dev == NULL); + + /* Map 'pp->ifnum_out' to a net_device. + This will dev_hold() the net_device. */ + dev = ssh_interceptor_ifnum_to_netdev(interceptor, pp->ifnum_out); + if (dev == NULL) + { + SSH_DEBUG(SSH_D_UNCOMMON, + ("Interface %lu has disappeared, dropping packet 0x%p", + (unsigned long) pp->ifnum_out, ipp->skb)); + goto error; + } + ipp->skb->dev = dev; + + /* Verify that packet has enough headroom to be sent out via `skb->dev'. */ + ipp->skb = + ssh_interceptor_packet_verify_headroom(ipp->skb, media_header_len); + if (ipp->skb == NULL) + { + SSH_DEBUG(SSH_D_UNCOMMON, + ("Could not add headroom to skbp, dropping packet 0x%p", + ipp->skb)); + goto error; + } + +#ifdef INTERCEPTOR_IP_ALIGNS_PACKETS + /* Align IP header to word boundary. */ + if (!ssh_interceptor_packet_align(pp, media_header_len)) + { + pp = NULL; + goto error; + } +#endif /* INTERCEPTOR_IP_ALIGNS_PACKETS */ + +#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) +#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR + /* Copy the linux nfmark from the extension slot indexed by + SSH_LINUX_FWMARK_EXTENSION_SELECTOR. */ + SSH_SKB_MARK(ipp->skb) = pp->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR]; +#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */ +#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */ + + /* Check if the engine has cleared the SSH_PACKET_HWCKSUM flag. */ + if ((pp->flags & SSH_PACKET_HWCKSUM) == 0) + ipp->skb->ip_summed = CHECKSUM_NONE; + + /* Clear control buffer, as packet contents might have changed. */ + if ((pp->flags & SSH_PACKET_UNMODIFIED) == 0) + memset(ipp->skb->cb, 0, sizeof(ipp->skb->cb)); + + /* Send to network */ + if (pp->flags & SSH_PACKET_FROMPROTOCOL) + { + /* Network header pointer is required by tcpdump. */ + SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data + media_header_len); + + /* Let unmodified packets pass on as if they were never intercepted. + Note that this expects that skb->dst has not been cleared or modified + during Engine processing. */ + if (pp->flags & SSH_PACKET_UNMODIFIED) + { + SSH_DEBUG(SSH_D_HIGHOK, ("Passing unmodified packet to network")); + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + /* Remove the media header that was prepended to the packet + in the inbound netfilter hook. Update skb->protocol and + pp->protocol. */ + if (media_header_len > 0) + { + SSH_ASSERT(ipp->skb->len >= media_header_len); + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol, + media_header_len, + ipp->skb->data); + skb_pull(ipp->skb, media_header_len); + if (ntohs(ipp->skb->protocol) == ETH_P_IP) + pp->protocol = SSH_PROTOCOL_IP4; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + else if (ntohs(ipp->skb->protocol) == ETH_P_IPV6) + pp->protocol = SSH_PROTOCOL_IP6; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + else + { + SSH_DEBUG(SSH_D_FAIL, + ("Invalid skb protocol %d, dropping packet", + ntohs(ipp->skb->protocol))); + goto error; + } + } +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + if (SSH_SKB_DST(ipp->skb) == NULL) + { + SSH_DEBUG(SSH_D_FAIL, ("Invalid skb->dst, dropping packet")); + goto error; + } + + switch (pp->protocol) + { + case SSH_PROTOCOL_IP4: + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + ssh_interceptor_send_to_network_ipv4(ipp->skb); + break; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case SSH_PROTOCOL_IP6: + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); + ssh_interceptor_send_to_network_ipv6(ipp->skb); + break; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + SSH_DEBUG(SSH_D_ERROR, + ("pp->protocol 0x%x ipp->skb->protocol 0x%x", + pp->protocol, ipp->skb->protocol)); + SSH_NOTREACHED; + goto error; + } + + /* All done. */ + goto sent; + } + +#ifdef DEBUG_LIGHT + if ( +#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS + ipp->skb->ip_summed == CHECKSUM_PARTIAL +#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */ + ipp->skb->ip_summed == CHECKSUM_HW +#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */ + ) + SSH_DEBUG(SSH_D_LOWOK, ("Hardware performs checksumming.")); + else if (ipp->skb->ip_summed == CHECKSUM_NONE) + SSH_DEBUG(SSH_D_LOWOK, ("Checksum calculated in software.")); + else + SSH_DEBUG(SSH_D_LOWOK, ("No checksumming required.")); +#endif /* DEBUG_LIGHT */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + + /* Media level */ + SSH_ASSERT(media_header_len <= ipp->skb->len); + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + + /* Set ipp->skb->protocol */ + SSH_ASSERT(skb_headlen(ipp->skb) >= media_header_len); + ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol, + media_header_len, + ipp->skb->data); + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_packets_sent++; + interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len; + }); + + /* Pass packet to network device driver. */ + dev_queue_xmit(ipp->skb); + + /* All done. */ + goto sent; + +#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + /* IP level */ + + /* Set ipp->skb->protocol */ + ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol); + +#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE + + /* Prepare to pass forwarded packets through + SSH_NF_IP_FORWARD netfilter hook. */ + if (pp->flags & SSH_PACKET_FORWARDED) + { + /* Map 'pp->ifnum_in' to a net_device. */ + in_dev = ssh_interceptor_ifnum_to_netdev(interceptor, pp->ifnum_in); + + SSH_DEBUG(SSH_D_PCKDMP, + ("FWD 0x%04x/%d (%s[%d]->%s[%d]) len %d " + "type=0x%08x dst 0x%08x", + ntohs(ipp->skb->protocol), pp->protocol, + (in_dev ? in_dev->name : "<none>"), + (in_dev ? in_dev->ifindex : -1), + (ipp->skb->dev ? ipp->skb->dev->name : "<none>"), + (ipp->skb->dev ? ipp->skb->dev->ifindex : -1), + ipp->skb->len, ipp->skb->pkt_type, SSH_SKB_DST(ipp->skb))); + + SSH_DEBUG(SSH_D_HIGHSTART, + ("forwarding packet 0x%08x, len %d proto 0x%x [%s]", + ipp->skb, ipp->skb->len, ntohs(ipp->skb->protocol), + (pp->protocol == SSH_PROTOCOL_IP4 ? "ipv4" : + (pp->protocol == SSH_PROTOCOL_IP6 ? "ipv6" : + (pp->protocol == SSH_PROTOCOL_ARP ? "arp" : + "unknown"))))); + + /* Change pkt_type to PACKET_HOST, which is expected + in the SSH_NF_IP_FORWARD hook. It is set to PACKET_OUTGOING + in ssh_interceptor_send_to_network_*() */ + ipp->skb->pkt_type = PACKET_HOST; + } +#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */ + + SSH_ASSERT(media_header_len == 0); + + switch (pp->protocol) + { + case SSH_PROTOCOL_IP4: + /* Set ipp->skb->dst */ + if (!ssh_interceptor_reroute_skb_ipv4(interceptor, + ipp->skb, + pp->route_selector, + pp->ifnum_in)) + { + SSH_DEBUG(SSH_D_UNCOMMON, + ("Unable to reroute skb 0x%p", ipp->skb)); + goto error; + } + SSH_ASSERT(SSH_SKB_DST(ipp->skb) != NULL); +#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE + /* Pass forwarded packets to SSH_NF_IP_FORWARD netfilter hook */ + if (pp->flags & SSH_PACKET_FORWARDED) + { + NF_HOOK(PF_INET, SSH_NF_IP_FORWARD, ipp->skb, + in_dev, ipp->skb->dev, + ssh_interceptor_send_to_network_ipv4); + } + /* Send local packets directly to network. */ + else +#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */ + ssh_interceptor_send_to_network_ipv4(ipp->skb); + break; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case SSH_PROTOCOL_IP6: + /* Set ipp->skb->dst */ + if (!ssh_interceptor_reroute_skb_ipv6(interceptor, + ipp->skb, + pp->route_selector, + pp->ifnum_in)) + { + SSH_DEBUG(SSH_D_UNCOMMON, + ("Unable to reroute skb 0x%p", ipp->skb)); + goto error; + } + SSH_ASSERT(SSH_SKB_DST(ipp->skb) != NULL); +#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE + /* Pass forwarded packets to SSH_NF_IP6_FORWARD netfilter hook */ + if (pp->flags & SSH_PACKET_FORWARDED) + { + NF_HOOK(PF_INET6, SSH_NF_IP6_FORWARD, ipp->skb, + in_dev, ipp->skb->dev, + ssh_interceptor_send_to_network_ipv6); + } + /* Send local packets directly to network. */ + else +#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */ + ssh_interceptor_send_to_network_ipv6(ipp->skb); + break; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + SSH_DEBUG(SSH_D_ERROR, + ("pp->protocol 0x%x ipp->skb->protocol 0x%x", + pp->protocol, ipp->skb->protocol)); + SSH_NOTREACHED; + goto error; + } + + /* All done. */ + goto sent; + +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + } + + /* Send to stack */ + else if (pp->flags & SSH_PACKET_FROMADAPTER) + { +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + SshUInt32 pkt_len4; +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + SshUInt32 pkt_len6; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Network header pointer is required by tcpdump. */ + SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data + media_header_len); + + /* Let unmodified packets pass on as if they were never intercepted. + Note that this expects that SSH_PACKET_UNMODIFIED packets are either + IPv4 or IPv6. Currently there is no handling for ARP, as the Engine + never sets SSH_PACKET_UNMODIFIED for ARP packets. */ + if (pp->flags & SSH_PACKET_UNMODIFIED) + { + SSH_DEBUG(SSH_D_HIGHOK, ("Passing unmodified packet to stack")); + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + /* Remove the media header that was prepended to the packet + in the inbound netfilter hook. Update skb->protocol and + pp->protocol. */ + if (media_header_len > 0) + { + SSH_ASSERT(ipp->skb->len >= media_header_len); + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol, + media_header_len, + ipp->skb->data); + skb_pull(ipp->skb, media_header_len); + if (ntohs(ipp->skb->protocol) == ETH_P_IP) + pp->protocol = SSH_PROTOCOL_IP4; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + else if (ntohs(ipp->skb->protocol) == ETH_P_IPV6) + pp->protocol = SSH_PROTOCOL_IP6; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + else + { + SSH_DEBUG(SSH_D_FAIL, + ("Invalid skb protocol %d, dropping packet", + ntohs(ipp->skb->protocol))); + goto error; + } + } +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + switch (pp->protocol) + { + case SSH_PROTOCOL_IP4: + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); +#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE + SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to NF_IP_PRE_ROUTING", + ipp->skb)); + NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING, + ipp->skb, ipp->skb->dev, NULL, + interceptor->nf->linux_fn.ip_rcv_finish, + ssh_nf_in4.priority + 1); +#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + /* Call SSH_NF_IP_PREROUTING okfn() directly */ + SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to ip_rcv_finish", + ipp->skb)); + (*interceptor->linux_fn.ip_rcv_finish)(ipp->skb); +#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + break; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case SSH_PROTOCOL_IP6: + SSH_LINUX_STATISTICS(interceptor, + { interceptor->stats.num_passthrough++; }); +#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE + SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to NF_IP6_PRE_ROUTING", + ipp->skb)); + NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, ipp->skb, + ipp->skb->dev, NULL, + interceptor->nf->linux_fn.ip6_rcv_finish, + ssh_nf_out6.priority + 1); +#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + /* Call SSH_NF_IP6_PREROUTING okfn() directly */ + SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to ip6_rcv_finish", + ipp->skb)); + (*interceptor->linux_fn.ip6_rcv_finish)(ipp->skb); +#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + break; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + default: + SSH_DEBUG(SSH_D_ERROR, + ("pp->protocol 0x%x ipp->skb->protocol 0x%x", + pp->protocol, ipp->skb->protocol)); + SSH_NOTREACHED; + goto error; + } + + /* All done. */ + goto sent; + } + + /* If we do not wish to keep the broadcast state of + the packet, then reset the pkt_type to PACKET_HOST. */ + if (!((ipp->skb->pkt_type == PACKET_MULTICAST + || ipp->skb->pkt_type == PACKET_BROADCAST) + && (pp->flags & SSH_PACKET_MEDIABCAST) != 0)) + ipp->skb->pkt_type = PACKET_HOST; + + /* Clear old routing decision */ + if (SSH_SKB_DST(ipp->skb)) + { + dst_release(SSH_SKB_DST(ipp->skb)); + SSH_SKB_DST_SET(ipp->skb, NULL); + } + + /* If the packet has an associated SKB and that SKB is associated + with a socket, orphan the skb from it's owner. These situations + may arise when sending packets towards the protocol when + the packet has been turned around by the engine. */ + skb_orphan(ipp->skb); + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + + /* Media level */ + + /* If the packet does not include a media level header (for + example in case of pppoe), calling eth_type_trans() will + corrupt the beginning of packet. Instead skb->protocol must + be set from pp->protocol. */ + if (media_header_len == 0) + { + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol); + } + else + { + /* Workaround for 802.2Q VLAN interfaces. + Calling eth_type_trans() would corrupt these packets, + as dev->hard_header_len includes the VLAN tag, but the + packet does not. */ + if (ipp->skb->dev->priv_flags & IFF_802_1Q_VLAN) + { + struct ethhdr *ethernet; + + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + ethernet = ssh_get_eth_hdr(ipp->skb); + ipp->skb->protocol = ethernet->h_proto; + skb_pull(ipp->skb, media_header_len); + } + + /* For all other packets, call eth_type_trans() to + set the protocol and the skb pointers. */ + else + ipp->skb->protocol = eth_type_trans(ipp->skb, ipp->skb->dev); + } +#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + /* IP level */ + + /* Assert that the media_header_len is always zero. */ + SSH_ASSERT(media_header_len == 0); + SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data); + ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol); + +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + +#ifdef DEBUG_LIGHT + if (ipp->skb->ip_summed == CHECKSUM_NONE) + SSH_DEBUG(SSH_D_LOWOK, ("Checksum is verified in software")); + else if (ipp->skb->ip_summed == CHECKSUM_UNNECESSARY) + SSH_DEBUG(SSH_D_LOWOK, ("Hardware claims to have verified checksum")); + else if ( +#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS + ipp->skb->ip_summed == CHECKSUM_COMPLETE +#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */ + ipp->skb->ip_summed == CHECKSUM_HW +#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */ + ) + SSH_DEBUG(SSH_D_LOWOK, ("Hardware has verified checksum, csum 0x%x", + SSH_SKB_CSUM(ipp->skb))); + /* ip_summed is CHECKSUM_PARTIAL, this should never happen. */ + else + SSH_DEBUG(SSH_D_ERROR, ("Invalid HW checksum flag %d", + ipp->skb->ip_summed)); +#endif /* DEBUG_LIGHT */ + + /* Set nh pointer */ + SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data); + switch(ntohs(ipp->skb->protocol)) + { + case ETH_P_IP: + neth = SSH_SKB_GET_NETHDR(ipp->skb); + if (neth == NULL) + { + SSH_DEBUG(SSH_D_ERROR, ("Could not access IP header")); + goto error; + } + SSH_SKB_SET_TRHDR(ipp->skb, neth + SSH_IPH4_HLEN(neth) * 4); + +#ifdef CONFIG_NETFILTER_DEBUG +#ifdef LINUX_HAS_SKB_NFDEBUG + /* Mark SSH_NF_IP_PRE_ROUTING visited */ + ipp->skb->nf_debug = (1 << SSH_NF_IP_PRE_ROUTING); +#endif /* LINUX_HAS_SKB_NFDEBUG */ +#endif /* CONFIG_NETFILTER_DEBUG */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + /* Remove padding from packet. */ + pkt_len4 = SSH_IPH4_LEN(neth); + SSH_ASSERT(pkt_len4 >= SSH_IPH4_HDRLEN && pkt_len4 <= 0xffff); + if (pkt_len4 != ipp->skb->len) + { + SSH_DEBUG(SSH_D_NICETOKNOW, ("Trimming skb down from %d to %lu", + ipp->skb->len, + (unsigned long) pkt_len4)); + skb_trim(ipp->skb, pkt_len4); + } +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_packets_sent++; + interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len; + }); + +#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE + NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING, + ipp->skb, ipp->skb->dev, NULL, + interceptor->linux_fn.ip_rcv_finish, + ssh_nf_in4.priority + 1); +#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + /* Call SSH_NF_IP_PREROUTING okfn() directly */ + (*interceptor->linux_fn.ip_rcv_finish)(ipp->skb); +#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + break; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + case ETH_P_IPV6: + neth = SSH_SKB_GET_NETHDR(ipp->skb); + if (neth == NULL) + { + SSH_DEBUG(SSH_D_ERROR, ("Could not access IPv6 header")); + goto error; + } + + ipproto = SSH_IPH6_NH(neth); + pkt_len6 = SSH_IPH6_LEN(neth) + SSH_IPH6_HDRLEN; + + /* Parse hop-by-hop options and update IPv6 control buffer. */ + SSH_LINUX_IP6CB(ipp->skb)->iif = ipp->skb->dev->ifindex; + SSH_LINUX_IP6CB(ipp->skb)->hop = 0; + SSH_LINUX_IP6CB(ipp->skb)->ra = 0; +#ifdef LINUX_HAS_IP6CB_NHOFF + SSH_LINUX_IP6CB(ipp->skb)->nhoff = SSH_IPH6_OFS_NH; +#endif /* LINUX_HAS_IP6CB_NHOFF */ + + offset = SSH_IPH6_HDRLEN; + /* Ipproto == HOPOPT */ + if (ipproto == 0) + { + unsigned char *opt_ptr = neth + offset + 2; + int opt_len; + + ipproto = SSH_IP6_EXT_COMMON_NH(neth + offset); + offset += SSH_IP6_EXT_COMMON_LENB(neth + offset); + + while (opt_ptr < neth + offset) + { + opt_len = opt_ptr[1] + 2; + switch (opt_ptr[0]) + { + /* PAD0 */ + case 0: + opt_len = 1; + break; + + /* PADN */ + case 1: + break; + + /* Jumbogram */ + case 194: + /* Take packet len from option (skb->len is zero). */ + pkt_len6 = SSH_GET_32BIT(&opt_ptr[2]) + + sizeof(struct ipv6hdr); + break; + + /* Router alert */ + case 5: + SSH_LINUX_IP6CB(ipp->skb)->ra = opt_ptr - neth; + break; + + /* Unknown / unsupported */ + default: + /* Just skip unknown options. */ + break; + } + opt_ptr += opt_len; + } + SSH_LINUX_IP6CB(ipp->skb)->hop = sizeof(struct ipv6hdr); + +#ifdef LINUX_HAS_IP6CB_NHOFF + SSH_LINUX_IP6CB(ipp->skb)->nhoff = sizeof(struct ipv6hdr); +#endif /* LINUX_HAS_IP6CB_NHOFF */ + } + SSH_SKB_SET_TRHDR(ipp->skb, neth + offset); + + /* Remove padding from packet. */ + SSH_ASSERT(pkt_len6 >= sizeof(struct ipv6hdr)); + if (pkt_len6 != ipp->skb->len) + { + SSH_DEBUG(SSH_D_NICETOKNOW, ("Trimming skb down from %d to %lu", + ipp->skb->len, + (unsigned long) pkt_len6)); + skb_trim(ipp->skb, pkt_len6); + } + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_packets_sent++; + interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len; + }); + +#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE + NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, ipp->skb, + ipp->skb->dev, NULL, + interceptor->linux_fn.ip6_rcv_finish, + ssh_nf_out6.priority + 1); +#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + /* Call SSH_NF_IP6_PREROUTING okfn() directly */ + (*interceptor->linux_fn.ip6_rcv_finish)(ipp->skb); +#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + break; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + case ETH_P_ARP: + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.num_packets_sent++; + interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len; + }); +#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE + NF_HOOK_THRESH(SSH_NFPROTO_ARP, NF_ARP_IN, + ipp->skb, ipp->skb->dev, NULL, + interceptor->linux_fn.arp_process, + ssh_nf_in_arp.priority + 1); +#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + /* Call NF_ARP_IN okfn() directly */ + (*interceptor->linux_fn.arp_process)(ipp->skb); +#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */ + break; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + default: + SSH_DEBUG(SSH_D_ERROR, + ("skb->protocol 0x%x", htons(ipp->skb->protocol))); + SSH_NOTREACHED; + goto error; + } + + /* All done. */ + goto sent; + } /* SSH_PACKET_FROMADAPTER */ + + else + { + /* Not SSH_PACKET_FROMPROTOCOL or SSH_PACKET_FROMADAPTER. */ + SSH_DEBUG(SSH_D_ERROR, ("Invalid packet direction flags")); + SSH_NOTREACHED; + goto error; + } + + sent: + ipp->skb = NULL; + + out: +#ifdef INTERCEPTOR_IP_ALIGNS_PACKETS + /* pp can go NULL only with packet aligning. */ + + if (pp) +#endif /* INTERCEPTOR_IP_ALIGNS_PACKETS */ + ssh_interceptor_packet_free(pp); + + /* Release net_device */ + if (dev) + ssh_interceptor_release_netdev(dev); + +#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR +#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE + /* Release inbound net_device that was used for + FORWARD NF_HOOK traversal. */ + if (in_dev) + ssh_interceptor_release_netdev(in_dev); +#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */ +#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + return; + + error: + SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; }); + goto out; +} + + +/******************************************************* General init/uninit */ + +void ssh_interceptor_enable_interception(SshInterceptor interceptor, + Boolean enable) +{ + + SSH_DEBUG(SSH_D_LOWOK, ("%s packet interception", + (enable ? "Enabling" : "Disabling"))); + interceptor->enable_interception = enable; +} + +/* Interceptor hook init. Utility function to initialize + individual hooks. */ +static Boolean +ssh_interceptor_hook_init(struct SshLinuxHooksRec *hook) +{ + int rval; + + SSH_ASSERT(hook->is_registered == FALSE); + + if (hook->pf == PF_INET && hook->hooknum == SSH_NF_IP_PRE_ROUTING) + hook->priority = in_priority; + + if (hook->pf == PF_INET && hook->hooknum == SSH_NF_IP_POST_ROUTING) + hook->priority = out_priority; + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (hook->pf == PF_INET6 && hook->hooknum == SSH_NF_IP6_PRE_ROUTING) + hook->priority = in6_priority; + + if (hook->pf == PF_INET6 && hook->hooknum == SSH_NF_IP6_POST_ROUTING) + hook->priority = out6_priority; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + hook->ops->hook = hook->hookfn; + hook->ops->pf = hook->pf; + hook->ops->hooknum = hook->hooknum; + hook->ops->priority = hook->priority; + + rval = nf_register_hook(hook->ops); + if (rval < 0) + { + if (hook->is_mandatory) + { + printk(KERN_ERR + "VPNClient netfilter %s hook failed to install.\n", + hook->name); + return FALSE; + } + return TRUE; + } + + hook->is_registered=TRUE; + return TRUE; +} + +/* Utility function for uninstalling a single netfilter hook. */ +static void +ssh_interceptor_hook_uninit(struct SshLinuxHooksRec *hook) +{ + if (hook->is_registered == FALSE) + return; + + nf_unregister_hook(hook->ops); + + hook->is_registered = FALSE; +} + +/* IP/Network glue initialization. This must be called only + after the engine has "opened" the interceptor, and packet_callback() + has been set to a valid value. */ +Boolean +ssh_interceptor_ip_glue_init(SshInterceptor interceptor) +{ + int i; + + /* Verify that the hooks haven't been initialized yet. */ + if (interceptor->hooks_installed) + { + SSH_DEBUG(2, ("init called when hooks are initialized already.\n")); + return TRUE; + } + + /* Register all hooks */ + for (i = 0; ssh_nf_hooks[i].name != NULL; i++) + { + if (ssh_interceptor_hook_init(&ssh_nf_hooks[i]) == FALSE) + goto fail; + } + + interceptor->hooks_installed = TRUE; + return TRUE; + + fail: + for (i = 0; ssh_nf_hooks[i].name != NULL; i++) + ssh_interceptor_hook_uninit(&ssh_nf_hooks[i]); + return FALSE; +} + +/* Uninitialization of netfilter glue. */ +Boolean +ssh_interceptor_ip_glue_uninit(SshInterceptor interceptor) +{ + int i; + + /* Note that we do not perform concurrency control here! + We expect that we are essentially running single-threaded + in init/shutdown! */ + + if (interceptor->hooks_installed == FALSE) + return TRUE; + + /* Unregister netfilter hooks */ + for (i = 0; ssh_nf_hooks[i].name != NULL; i++) + ssh_interceptor_hook_uninit(&ssh_nf_hooks[i]); + + interceptor->hooks_installed = FALSE; + + return TRUE; +} |