diff options
author | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-17 17:53:57 +0200 |
---|---|---|
committer | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-18 16:31:59 +0200 |
commit | c28265764ec6ad9995eb0c761a376ffc9f141fcd (patch) | |
tree | 3ad899757480d47deb2be6011509a4243e8e0dc2 /drivers/interceptor/linux_iface.c | |
parent | 0ddbcb39c0dc0318f68d858f25a96a074142af2f (diff) | |
download | kernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.zip kernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.tar.gz kernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.tar.bz2 |
applied patches from i9305 jb sources, updated mali to r3p0
Change-Id: Iec4bc4e2fb59e2cf5b4d25568a644d4e3719565e
Diffstat (limited to 'drivers/interceptor/linux_iface.c')
-rw-r--r-- | drivers/interceptor/linux_iface.c | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_iface.c b/drivers/interceptor/linux_iface.c new file mode 100644 index 0000000..cc0c499 --- /dev/null +++ b/drivers/interceptor/linux_iface.c @@ -0,0 +1,863 @@ +/* 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_iface.c + * + * Linux interceptor network interface handling. + * + */ + +#include "linux_internal.h" + +extern SshInterceptor ssh_interceptor_context; + +/* Protect access to net_device list in kernel. */ +#define SSH_LOCK_LINUX_DEV_LIST() read_lock(&dev_base_lock) +#define SSH_UNLOCK_LINUX_DEV_LIST() read_unlock(&dev_base_lock); + +/**************************** Notification handlers *************************/ + +/* Interface notifier callback. This will notify when there has been a + change in the interface list; device is added or removed, or an + IPv4/IPv6 address has been added or removed from an device. */ + +int +ssh_interceptor_notifier_callback(struct notifier_block *block, + unsigned long type, void *arg) +{ + if (ssh_interceptor_context != NULL) + ssh_interceptor_receive_ifaces(ssh_interceptor_context); + + return NOTIFY_OK; +} + +/* 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->if_table_lock); + for (iface = interceptor->if_hash[ifnum % SSH_LINUX_IFACE_HASH_SIZE]; + iface && iface->ifindex != ifnum; + iface = iface->next) + ; + if (iface) + { + SSH_ASSERT(iface->generation == interceptor->if_generation); + dev = iface->dev; + SSH_ASSERT(dev != NULL); + dev_hold(dev); + if (context_return != NULL) + *context_return = iface->context; + } + read_unlock(&interceptor->if_table_lock); + + return dev; +} + +inline struct net_device * +ssh_interceptor_ifnum_to_netdev(SshInterceptor interceptor, + SshUInt32 ifnum) +{ + return ssh_interceptor_ifnum_to_netdev_ctx(interceptor, ifnum, NULL); +} + +/* Decrement the reference count of net_device 'dev'. */ +inline void +ssh_interceptor_release_netdev(struct net_device *dev) +{ + SSH_ASSERT(dev != NULL); + dev_put(dev); +} + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR +/* Map Linux media types (actually extensions of ARP hardware + address types) to platform independent denominations. */ +static SshInterceptorMedia +ssh_interceptor_media_type(unsigned short type) +{ + switch (type) + { + case ARPHRD_ETHER: + return SSH_INTERCEPTOR_MEDIA_ETHERNET; + case ARPHRD_IEEE802: + return SSH_INTERCEPTOR_MEDIA_FDDI; + default: + return SSH_INTERCEPTOR_MEDIA_PLAIN; + } + SSH_NOTREACHED; + return SSH_INTERCEPTOR_MEDIA_PLAIN; +} +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + +/* Sends the updated interface information to the engine. This is called + at the initialization of the interceptor and whenever the status of any + interface changes. + + This function grabs 'if_table_lock' (for reading) and 'interceptor_lock'. +*/ +static void +ssh_interceptor_send_interfaces(SshInterceptor interceptor) +{ + SshInterceptorInternalInterface iface; + SshInterceptorInterface *ifarray; + SshInterceptorInterfacesCB interfaces_callback; + struct net_device *dev; + struct in_device *inet_dev = NULL; + struct in_ifaddr *addr; + int num, n, count, i, hashvalue; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + struct inet6_dev *inet6_dev = NULL; + struct inet6_ifaddr *addr6, *next_addr6; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Grab 'if_table_lock' for reading the interface table. */ + read_lock(&interceptor->if_table_lock); + + num = interceptor->if_table_size; + if (!num) + { + read_unlock(&interceptor->if_table_lock); + SSH_DEBUG(4, ("No interfaces to report.")); + return; + } + + /* Allocate temporary memory for the table that is + passed to the engine. This is mallocated, since + we want to minimise stack usage. */ + ifarray = ssh_malloc(sizeof(*ifarray) * num); + if (ifarray == NULL) + { + read_unlock(&interceptor->if_table_lock); + return; + } + memset(ifarray, 0, sizeof(*ifarray) * num); + + /* Iterate over the slots of the iface hashtable. */ + n = 0; + for (hashvalue = 0; hashvalue < SSH_LINUX_IFACE_HASH_SIZE; hashvalue++) + { + + /* Iterate over the chain of iface entries in a hashtable slot. */ + for (iface = interceptor->if_hash[hashvalue]; + iface != NULL; + iface = iface->next) + { + /* Ignore devices that are not up. */ + dev = iface->dev; + if (dev == NULL || !(dev->flags & IFF_UP)) + continue; + + /* Disable net_device features that vpnclient does not support */ + if (dev->features & NETIF_F_TSO) + dev->features &= ~NETIF_F_TSO; + +#ifdef LINUX_HAS_NETIF_F_GSO + /* Disable net_device features that vpnclient does not support */ + if (dev->features & NETIF_F_GSO) + { + SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported " + "feature NETIF_F_GSO", + (unsigned long) iface->ifindex, + (dev->name ? dev->name : "<none>"))); + dev->features &= ~NETIF_F_GSO; + } +#endif /* LINUX_HAS_NETIF_F_GSO */ + +#ifdef LINUX_HAS_NETIF_F_TSO6 + /* Disable net_device features that vpnclient does not support */ + if (dev->features & NETIF_F_TSO6) + { + SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported " + "feature NETIF_F_TSO6", + (unsigned long) iface->ifindex, + (dev->name ? dev->name : "<none>"))); + dev->features &= ~NETIF_F_TSO6; + } +#endif /* LINUX_HAS_NETIF_F_TSO6 */ + +#ifdef LINUX_HAS_NETIF_F_TSO_ECN + /* Disable net_device features that vpnclient does not support */ + if (dev->features & NETIF_F_TSO_ECN) + { + SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported " + "feature NETIF_F_TSO_ECN", + (unsigned long) iface->ifindex, + (dev->name ? dev->name : "<none>"))); + dev->features &= ~NETIF_F_TSO_ECN; + } +#endif /* LINUX_HAS_NETIF_F_TSO_ECN */ + +#ifdef LINUX_HAS_NETIF_F_GSO_ROBUST + /* Disable net_device features that vpnclient does not support */ + if (dev->features & NETIF_F_GSO_ROBUST) + { + SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported " + "feature NETIF_F_GSO_ROBUST", + (unsigned long) iface->ifindex, + (dev->name ? dev->name : "<none>"))); + dev->features &= ~NETIF_F_GSO_ROBUST; + } +#endif /* LINUX_HAS_NETIF_F_GSO_ROBUST */ + +#ifdef LINUX_HAS_NETIF_F_UFO + if (dev->features & NETIF_F_UFO) + { + SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported " + "feature NETIF_F_UFO", + (unsigned long) iface->ifindex, + (dev->name ? dev->name : "<none>"))); + dev->features &= ~NETIF_F_UFO; + } +#endif /* LINUX_HAS_NETIF_F_UFO */ + + /* Count addresses */ + count = 0; + + /* Increment refcount to make sure the device does not disappear. */ + inet_dev = in_dev_get(dev); + if (inet_dev) + { + /* Count the device's IPv4 addresses */ + for (addr = inet_dev->ifa_list; addr != NULL; + addr = addr->ifa_next) + { + count++; + } + } + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + /* Increment refcount to make sure the device does not disappear. */ + inet6_dev = in6_dev_get(dev); + if (inet6_dev) + { + /* Count the device's IPv6 addresses */ + SSH_INET6_IFADDR_LIST_FOR_EACH(addr6, + next_addr6, + inet6_dev->addr_list) + { + count++; + } + } +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Fill interface entry. */ + ifarray[n].ifnum = iface->ifindex; + ifarray[n].to_protocol.flags = + SSH_INTERCEPTOR_MEDIA_INFO_NO_FRAGMENT; + ifarray[n].to_protocol.mtu_ipv4 = dev->mtu; + ifarray[n].to_adapter.flags = 0; + ifarray[n].to_adapter.mtu_ipv4 = dev->mtu; + +#ifdef WITH_IPV6 + ifarray[n].to_adapter.mtu_ipv6 = dev->mtu; + ifarray[n].to_protocol.mtu_ipv6 = dev->mtu; +#endif /* WITH_IPV6 */ + +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + ifarray[n].to_adapter.media = ssh_interceptor_media_type(dev->type); + ifarray[n].to_protocol.media = ssh_interceptor_media_type(dev->type); +#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + ifarray[n].to_adapter.media = SSH_INTERCEPTOR_MEDIA_PLAIN; + ifarray[n].to_protocol.media = SSH_INTERCEPTOR_MEDIA_PLAIN; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + strncpy(ifarray[n].name, dev->name, 15); + + /* Set interface type and link status. */ + if (dev->flags & IFF_POINTOPOINT) + ifarray[n].flags |= SSH_INTERFACE_FLAG_POINTOPOINT; + if (dev->flags & IFF_BROADCAST) + ifarray[n].flags |= SSH_INTERFACE_FLAG_BROADCAST; + if (!netif_carrier_ok(dev)) + ifarray[n].flags |= SSH_INTERFACE_FLAG_LINK_DOWN; + + ifarray[n].num_addrs = count; + ifarray[n].addrs = NULL; + + /* Add addresses to interface entry. */ + if (count > 0) + { + ifarray[n].addrs = ssh_malloc(sizeof(*ifarray[n].addrs) * count); + + if (ifarray[n].addrs == NULL) + { + /* Release INET/INET6 devices */ + if (inet_dev) + in_dev_put(inet_dev); +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (inet6_dev) + in6_dev_put(inet6_dev); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + read_unlock(&interceptor->if_table_lock); + goto out; + } + count = 0; + if (inet_dev) + { + /* Put the IPv4 addresses */ + for (addr = inet_dev->ifa_list; + addr != NULL; + addr = addr->ifa_next) + { + ifarray[n].addrs[count].protocol = SSH_PROTOCOL_IP4; + SSH_IP4_DECODE(&ifarray[n].addrs[count].addr.ip.ip, + &addr->ifa_local); + SSH_IP4_DECODE(&ifarray[n].addrs[count].addr.ip.mask, + &addr->ifa_mask); + +#if 0 + + + + if (dev->flags & IFF_POINTOPOINT) + SSH_IP4_DECODE(&ifarray[n].addrs[count]. + addr.ip.broadcast, + &addr->ifa_address); + else +#endif /* 0 */ + if (dev->flags & IFF_BROADCAST) + SSH_IP4_DECODE(&ifarray[n].addrs[count]. + addr.ip.broadcast, + &addr->ifa_broadcast); + else + SSH_IP_UNDEFINE(&ifarray[n].addrs[count]. + addr.ip.broadcast); + count++; + } + } + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (inet6_dev) + { + /* Put the IPv6 addresses */ + SSH_INET6_IFADDR_LIST_FOR_EACH(addr6, + next_addr6, + inet6_dev->addr_list) + { + ifarray[n].addrs[count].protocol = SSH_PROTOCOL_IP6; + + SSH_IP6_DECODE(&ifarray[n].addrs[count].addr.ip.ip, + &addr6->addr); + + /* Generate mask from prefix length and IPv6 addr */ + SSH_IP6_DECODE(&ifarray[n].addrs[count].addr.ip.mask, + "\xff\xff\xff\xff\xff\xff\xff\xff" + "\xff\xff\xff\xff\xff\xff\xff\xff"); + ssh_ipaddr_set_bits(&ifarray[n].addrs[count]. + addr.ip.mask, + &ifarray[n].addrs[count]. + addr.ip.mask, + addr6->prefix_len, 0); + + /* Set the broadcast address to the IPv6 + undefined address */ + SSH_IP6_DECODE(&ifarray[n].addrs[count]. + addr.ip.broadcast, + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00"); + + /* Copy the ifnum in case of ipv6 to scope_id, since + in linux scope_id == ifnum. */ + ifarray[n].addrs[count].addr.ip.ip. + scope_id.scope_id_union.ui32 = ifarray[n].ifnum; + count++; + } + } +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + } +#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR + /* Grab the MAC address */ + ifarray[n].media_addr_len = dev->addr_len; + SSH_ASSERT(dev->addr_len <= sizeof(ifarray[n].media_addr)); + memcpy(&ifarray[n].media_addr[0], dev->dev_addr, dev->addr_len); +#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + ifarray[n].media_addr[0] = 0; + ifarray[n].media_addr_len = 0; +#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */ + + /* Release INET/INET6 devices */ + if (inet_dev) + in_dev_put(inet_dev); + inet_dev = NULL; +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + if (inet6_dev) + in6_dev_put(inet6_dev); + inet6_dev = NULL; +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + for (i = 0; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + SshVirtualAdapter adapter = interceptor->virtual_adapters[i]; + if (adapter && adapter->dev->ifindex == ifarray[n].ifnum) + { + ifarray[n].flags |= SSH_INTERFACE_FLAG_VIP; + break; + } + } + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + + /* Done with the interface entry, + increase ifarray index and continue with next iface. */ + n++; + + } /* for (iface entry chain iteration) */ + } /* for (hashtable iteration) */ + + /* Release if_table lock. */ + read_unlock(&interceptor->if_table_lock); + + SSH_ASSERT(n <= num); + + /* 'interceptor_lock' protects 'num_interface_callbacks' and + 'interfaces_callback'. */ + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + interceptor->num_interface_callbacks++; + interfaces_callback = interceptor->interfaces_callback; + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + + /* Call the interface callback. */ + (interfaces_callback)(n, ifarray, interceptor->callback_context); + + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + interceptor->num_interface_callbacks--; + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + +out: + /* Free the array. */ + for (i = 0; i < n; i++) + { + if (ifarray[i].addrs != NULL) + ssh_free(ifarray[i].addrs); + } + ssh_free(ifarray); + + return; +} + +/* Caller must hold 'if_table_lock' for writing */ +static SshInterceptorInternalInterface +ssh_interceptor_alloc_iface(SshInterceptor interceptor) +{ + SshInterceptorInternalInterface iface, new_table; + SshUInt32 i, n, new_table_size, hashvalue; + + iface = NULL; + + relookup: + /* Find empty slot in the if_table */ + for (i = 0; i < interceptor->if_table_size; i++) + { + iface = &interceptor->if_table[i]; + if (iface->generation == 0) + { +#ifdef DEBUG_LIGHT + iface->dev = NULL; + iface->ifindex = SSH_INTERCEPTOR_INVALID_IFNUM; + iface->next = NULL; +#endif /* DEBUG_LIGHT */ + iface->context = NULL; + return iface; + } + } + + /* No free slots, reallocate if_table */ + if (interceptor->if_table_size >= SSH_LINUX_IFACE_TABLE_SIZE) + return NULL; /* Maximum table size reached. */ + + new_table_size = interceptor->if_table_size + 10; + if (new_table_size > SSH_LINUX_IFACE_TABLE_SIZE) + new_table_size = SSH_LINUX_IFACE_TABLE_SIZE; + + new_table = ssh_malloc(sizeof(*new_table) * new_table_size); + if (new_table == NULL) + return NULL; + + /* Copy existing entries from if_hash */ + n = 0; + for (i = 0; i < SSH_LINUX_IFACE_HASH_SIZE; i++) + { + for (iface = interceptor->if_hash[i]; iface; iface = iface->next) + { + new_table[n].next = NULL; + new_table[n].ifindex = iface->ifindex; + new_table[n].dev = iface->dev; + new_table[n].generation = iface->generation; + n++; + } + } + + /* Initialize the rest of the new interface table */ + for (; n < new_table_size; n++) + { + new_table[n].generation = 0; +#ifdef DEBUG_LIGHT + new_table[n].dev = NULL; + new_table[n].ifindex = SSH_INTERCEPTOR_INVALID_IFNUM; + new_table[n].next = NULL; +#endif /* DEBUG_LIGHT */ + new_table[n].context = NULL; + } + + /* Rebuild hashtable */ + memset(interceptor->if_hash, 0, sizeof(interceptor->if_hash)); + for (i = 0; i < new_table_size; i++) + { + if (new_table[i].generation == 0) + continue; + + hashvalue = new_table[i].ifindex % SSH_LINUX_IFACE_HASH_SIZE; + new_table[i].next = interceptor->if_hash[hashvalue]; + interceptor->if_hash[hashvalue] = &new_table[i]; + } + + /* Free old if_table */ + ssh_free(interceptor->if_table); + wmb(); + interceptor->if_table = new_table; + interceptor->if_table_size = new_table_size; + + goto relookup; +} + +/* 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->if_table_lock); + + /* Increment 'if_generation' */ + interceptor->if_generation++; + if (interceptor->if_generation == 0) + interceptor->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_DEBUG(2, ("Interface index collides with " + "SSH_INTERCEPTOR_INVALID_IFNUM, " + "ignoring interface %lu[%s]", + (unsigned long) 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->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 %lu[%s]", + (unsigned long) ifindex, + (dev->name ? dev->name : "<none>"))); + + /* Mark up-to-date. */ + iface->generation = interceptor->if_generation; + } + + /* Interface index matches, but net_device has changed. */ + else + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Changed interface %lu[%s] (from %lu[%s])", + (unsigned long) ifindex, + (dev->name ? dev->name : "<none>"), + (unsigned long) 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->if_generation; + } + } + + /* Interface not found */ + else + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("New interface %lu[%s]", + (unsigned long) 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->if_generation; + /* Add entry to hashtable. */ + hashvalue = iface->ifindex % SSH_LINUX_IFACE_HASH_SIZE; + iface->next = interceptor->if_hash[hashvalue]; + wmb(); + interceptor->if_hash[hashvalue] = iface; + } + else + { + SSH_DEBUG(SSH_D_FAIL, + ("Could not allocate memory for new interface %lu[%s]", + (unsigned long) 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->if_hash[i]; + iface != NULL; + iface = iface_next) + { + if (iface->generation != 0 && + iface->generation != interceptor->if_generation) + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Disappeared interface %lu[%s]", + (unsigned long) 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->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->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; +} + + +/* Callback for interface and address notifier. */ +void +ssh_interceptor_receive_ifaces(SshInterceptor interceptor) +{ + local_bh_disable(); + ssh_interceptor_update_interfaces(interceptor); + local_bh_enable(); + + return; +} + + +/* Release all refcounts and free the 'ifnum->net_device' map cache. */ +void +ssh_interceptor_clear_ifaces(SshInterceptor interceptor) +{ + int i; + SshInterceptorInternalInterface iface; + + /* At this point. All hooks must have completed running! */ + + SSH_LOCK_LINUX_DEV_LIST(); + write_lock(&interceptor->if_table_lock); + + /* Release net_devices and free if_table. */ + if (interceptor->if_table != NULL) + { + for (i = 0; i < interceptor->if_table_size; i++) + { + iface = &interceptor->if_table[i]; + if (iface->generation != 0) + { + SSH_ASSERT(iface->dev != NULL); + dev_put(iface->dev); +#ifdef DEBUG_LIGHT + wmb(); + iface->dev = NULL; +#endif /* DEBUG_LIGHT */ + iface->generation = 0; + } + } + + ssh_free(interceptor->if_table); + interceptor->if_table = NULL; + } + + /* Clear interface hashtable. */ + memset(interceptor->if_hash, 0, sizeof(interceptor->if_hash)); + + write_unlock(&interceptor->if_table_lock); + SSH_UNLOCK_LINUX_DEV_LIST(); +} + + +/*************************** Module init / uninit **************************/ + +Boolean ssh_interceptor_iface_init(SshInterceptor interceptor) +{ + /* This will register notifier that notifies about bringing the + interface up and down. */ + interceptor->notifier_netdev.notifier_call = + ssh_interceptor_notifier_callback; + interceptor->notifier_netdev.priority = 1; + interceptor->notifier_netdev.next = NULL; + register_netdevice_notifier(&interceptor->notifier_netdev); + + /* This will register notifier that notifies when address of the + interface changes without bringing the interface down. */ + interceptor->notifier_inetaddr.notifier_call = + ssh_interceptor_notifier_callback; + interceptor->notifier_inetaddr.priority = 1; + interceptor->notifier_inetaddr.next = NULL; + register_inetaddr_notifier(&interceptor->notifier_inetaddr); + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + interceptor->notifier_inet6addr.notifier_call = + ssh_interceptor_notifier_callback; + interceptor->notifier_inet6addr.priority = 1; + interceptor->notifier_inet6addr.next = NULL; + register_inet6addr_notifier(&interceptor->notifier_inet6addr); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + interceptor->iface_notifiers_installed = TRUE; + + /* Send interface information to engine. This causes the interceptor + to grab reference to each net_device. On error cases + ssh_interceptor_clear_ifaces() or ssh_interceptor_iface_uninit() + must be called to release the references. */ + ssh_interceptor_receive_ifaces(interceptor); + + return TRUE; +} + +void ssh_interceptor_iface_uninit(SshInterceptor interceptor) +{ + if (interceptor->iface_notifiers_installed) + { + local_bh_enable(); + + /* Unregister notifier callback */ + unregister_netdevice_notifier(&interceptor->notifier_netdev); + unregister_inetaddr_notifier(&interceptor->notifier_inetaddr); +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + unregister_inet6addr_notifier(&interceptor->notifier_inet6addr); +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + local_bh_disable(); + } + + /* Clear interface table and release net_device references. */ + ssh_interceptor_clear_ifaces(interceptor); + + /* Due to lack of proper locking in linux kernel notifier code, + the unregister_*_notifier functions might leave the notifier + blocks out of sync, resulting in that the kernel may call an + unregistered notifier function. + + Sleep for a while to decrease the possibility of the bug causing + trouble (crash during module unloading). */ + mdelay(500); + + interceptor->iface_notifiers_installed = FALSE; +} |