diff options
Diffstat (limited to 'drivers/interceptor/linux_virtual_adapter.c')
-rw-r--r-- | drivers/interceptor/linux_virtual_adapter.c | 1073 |
1 files changed, 1073 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_virtual_adapter.c b/drivers/interceptor/linux_virtual_adapter.c new file mode 100644 index 0000000..8196d0a --- /dev/null +++ b/drivers/interceptor/linux_virtual_adapter.c @@ -0,0 +1,1073 @@ +/* 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_virtual_adapter.c + * + */ + +#include "linux_internal.h" + +#include "virtual_adapter.h" +#include "linux_virtual_adapter_internal.h" +#include "sshencode.h" +#include "sshinetencode.h" + +extern SshInterceptor ssh_interceptor_context; + +/* ************************ Types and definitions ***************************/ + +#define SSH_DEBUG_MODULE "SshInterceptorVirtualAdapter" + +static int num_virtual_adapters = 1; /* Default to 1 virtual adapter. */ +MODULE_PARM_DESC(num_virtual_adapters, "Number of virtual adapters to create"); +module_param(num_virtual_adapters, int, 0444); + + +#ifdef LINUX_HAS_NET_DEVICE_PRIV +#define SSH_LINUX_NET_DEVICE_PRIV(netdev) ((netdev)->priv) +#else +#define SSH_LINUX_NET_DEVICE_PRIV(netdev) (*((void **) netdev_priv(netdev))) +#endif + + +/* *********************** Internal Utility Functions ***********************/ + +static inline SshVirtualAdapter +ssh_virtual_adapter_ifnum_to_adapter(SshInterceptor interceptor, + SshInterceptorIfnum adapter_ifnum) +{ + SshVirtualAdapter adapter = NULL; + SshUInt32 i; + + ssh_kernel_mutex_assert_is_locked(interceptor->interceptor_lock); + + for (i = 0; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + adapter = interceptor->virtual_adapters[i]; + if (adapter && adapter->dev->ifindex == adapter_ifnum) + return adapter; + } + return NULL; +} + + +/* ******************* Public Virtual Adapter Operations *********************/ + +void ssh_virtual_adapter_get_status(SshInterceptor interceptor, + SshInterceptorIfnum adapter_ifnum, + SshVirtualAdapterStatusCB status_cb, + void *context) +{ + SshVirtualAdapter adapter; + unsigned char adapter_name[SSH_INTERCEPTOR_IFNAME_SIZE]; + SshVirtualAdapterState adapter_state; + unsigned int dev_flags; + void *adapter_context; + SshUInt32 i; + + if (adapter_ifnum == SSH_INTERCEPTOR_INVALID_IFNUM) + { + i = 0; + restart: + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + for (; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + adapter = interceptor->virtual_adapters[i]; + if (adapter == NULL) + continue; + + SSH_ASSERT(adapter->dev != NULL); + adapter_ifnum = (SshInterceptorIfnum) adapter->dev->ifindex; + snprintf(adapter_name, SSH_INTERCEPTOR_IFNAME_SIZE, + "%s", adapter->dev->name); + dev_flags = SSH_LINUX_DEV_GET_FLAGS(adapter->dev); + adapter_state = SSH_VIRTUAL_ADAPTER_STATE_DOWN; + if (dev_flags & IFF_UP) + adapter_state = SSH_VIRTUAL_ADAPTER_STATE_UP; + adapter_context = adapter->adapter_context; + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + (*status_cb)(SSH_VIRTUAL_ADAPTER_ERROR_OK_MORE, + adapter_ifnum, adapter_name, adapter_state, + adapter_context, context); + i++; + goto restart; + } + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + (*status_cb)(SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT, + SSH_INTERCEPTOR_INVALID_IFNUM, NULL, + SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED, + NULL, context); + return; + } + else + { + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + adapter = ssh_virtual_adapter_ifnum_to_adapter(interceptor, + adapter_ifnum); + if (adapter == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + (*status_cb)(SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT, + adapter_ifnum, NULL, + SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED, + NULL, context); + return; + } + + SSH_ASSERT(adapter->dev != NULL); + snprintf(adapter_name, SSH_INTERCEPTOR_IFNAME_SIZE, + "%s", adapter->dev->name); + dev_flags = SSH_LINUX_DEV_GET_FLAGS(adapter->dev); + adapter_state = SSH_VIRTUAL_ADAPTER_STATE_DOWN; + if (dev_flags & IFF_UP) + adapter_state = SSH_VIRTUAL_ADAPTER_STATE_UP; + adapter_context = adapter->adapter_context; + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + (*status_cb)(SSH_VIRTUAL_ADAPTER_ERROR_OK, + adapter_ifnum, adapter_name, adapter_state, + adapter_context, context); + return; + } +} + +void ssh_virtual_adapter_attach(SshInterceptor interceptor, + SshInterceptorIfnum adapter_ifnum, + SshVirtualAdapterPacketCB packet_cb, + SshVirtualAdapterDetachCB detach_cb, + void *adapter_context, + SshVirtualAdapterStatusCB callback, + void *context) +{ + SshVirtualAdapter adapter; + unsigned char adapter_name[SSH_INTERCEPTOR_IFNAME_SIZE]; + SshVirtualAdapterState adapter_state = SSH_VIRTUAL_ADAPTER_STATE_DOWN; + unsigned int dev_flags; + SshVirtualAdapterDetachCB old_detach_cb; + void *old_adapter_context; + + restart: + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + adapter = ssh_virtual_adapter_ifnum_to_adapter(interceptor, adapter_ifnum); + if (adapter == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, ("Attach failed for virtual adapter %d", + (int) adapter_ifnum)); + if (callback) + (*callback)(SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT, + adapter_ifnum, NULL, SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED, + NULL, context); + return; + } + + /* Destroy old adapter_context. */ + if (adapter->detach_cb != NULL) + { + old_detach_cb = adapter->detach_cb; + old_adapter_context = adapter->adapter_context; + adapter->detach_cb = NULL; + adapter->adapter_context = NULL; + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + (*old_detach_cb)(old_adapter_context); + goto restart; + } + + /* Fill in new adapter_context and callbacks. */ + adapter->adapter_context = adapter_context; + adapter->packet_cb = packet_cb; + adapter->detach_cb = detach_cb; + adapter->attached = 1; + + /* Gather info for callback. */ + if (callback) + { + snprintf(adapter_name, SSH_INTERCEPTOR_IFNAME_SIZE, + "%s", adapter->dev->name); + dev_flags = SSH_LINUX_DEV_GET_FLAGS(adapter->dev); + if (dev_flags & IFF_UP) + adapter_state = SSH_VIRTUAL_ADAPTER_STATE_UP; + } + + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + if (callback) + (*callback)(SSH_VIRTUAL_ADAPTER_ERROR_OK, + adapter_ifnum, adapter_name, adapter_state, + adapter_context, context); +} + +/* Clear virtual adapter callbacks, context, and configured IPv6 addresses. */ +static void ssh_virtual_adapter_clear(SshVirtualAdapter adapter) +{ + /* Clear adapter_context and callbacks. */ + adapter->detach_cb = NULL; + adapter->adapter_context = NULL; + adapter->packet_cb = NULL; +} + +void ssh_virtual_adapter_detach(SshInterceptor interceptor, + SshInterceptorIfnum adapter_ifnum, + SshVirtualAdapterStatusCB callback, + void *context) +{ + SshVirtualAdapter adapter; + unsigned char adapter_name[SSH_INTERCEPTOR_IFNAME_SIZE]; + SshVirtualAdapterDetachCB detach_cb = NULL; + void *adapter_context = NULL; + + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + adapter = ssh_virtual_adapter_ifnum_to_adapter(interceptor, adapter_ifnum); + if (adapter == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + if (callback) + (*callback)(SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT, + adapter_ifnum, NULL, SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED, + NULL, context); + return; + } + + if (adapter->detach_cb) + { + detach_cb = adapter->detach_cb; + adapter_context = adapter->adapter_context; + } + + ssh_virtual_adapter_clear(adapter); + adapter->attached = 0; + + /* Gather info for callback. */ + if (callback) + { + snprintf(adapter_name, SSH_INTERCEPTOR_IFNAME_SIZE, + "%s", adapter->dev->name); + } + + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + /* Destroy adapter_context. */ + if (detach_cb) + (*detach_cb)(adapter_context); + + if (callback) + (*callback)(SSH_VIRTUAL_ADAPTER_ERROR_OK, + adapter_ifnum, adapter_name, + SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED, + NULL, context); +} + +void ssh_virtual_adapter_detach_all(SshInterceptor interceptor) +{ + SshVirtualAdapter adapter; + SshUInt32 i = 0; + SshVirtualAdapterDetachCB detach_cb; + void *adapter_context; + + restart: + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + for (; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + adapter = interceptor->virtual_adapters[i]; + if (adapter == NULL) + continue; + + detach_cb = NULL; + adapter_context = NULL; + if (adapter->detach_cb != NULL) + { + detach_cb = adapter->detach_cb; + adapter_context = adapter->adapter_context; + } + + ssh_virtual_adapter_clear(adapter); + adapter->attached = 0; + + if (detach_cb != NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + /* Destroy adapter_context. */ + (*detach_cb)(adapter_context); + + goto restart; + } + } + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); +} + +/* ****************************** Platform stuff *****************************/ + +/* Netdevice low level statistics callback function. */ +static struct net_device_stats * +ssh_virtual_adapter_get_stats(struct net_device *dev) +{ + SshVirtualAdapter adapter = + (SshVirtualAdapter) SSH_LINUX_NET_DEVICE_PRIV(dev); + + if (adapter) + return &adapter->low_level_stats; + + return NULL; +} + +/* 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; +} + +#ifdef HAVE_NET_DEVICE_OPS +static const struct net_device_ops ssh_virtual_adapter_ops = { + .ndo_start_xmit = ssh_virtual_adapter_xmit, + .ndo_get_stats = ssh_virtual_adapter_get_stats, +}; +#endif + +/* **************** Netdevice registration / unregistration ******************/ + +static void +ssh_virtual_adapter_low_level_setup(struct net_device *dev) +{ + /* Empty at the moment, but some netdevice specific settings could be done + here. */ +} + +static void +ssh_virtual_adapter_destructor(struct net_device *dev) +{ + /* Free netdevice object. */ + free_netdev(dev); +} + +/* Attach virtual adapter to system. Returns FALSE on error, TRUE otherwise. */ +static Boolean +ssh_virtual_adapter_attach_low_level(SshVirtualAdapter adapter, + unsigned char *adapter_name, + unsigned char *adapter_enaddr) +{ + int i, result; + + /* + After priv member has disappeared from struct net_device, store + the "priv" pointer to private data area following the structure. + */ +#ifdef LINUX_HAS_NET_DEVICE_PRIV + const unsigned int private_data_size = 0; +#else + const unsigned int private_data_size = sizeof(void *); +#endif + + SSH_ASSERT(adapter->dev == NULL); + SSH_ASSERT(adapter_name != NULL); + SSH_ASSERT(adapter_enaddr != NULL); + + + /* Allocate net_device. */ + adapter->dev = alloc_netdev(private_data_size, adapter_name, + ssh_virtual_adapter_low_level_setup); + if (adapter->dev == NULL) + { + SSH_DEBUG(SSH_D_ERROR, ("alloc_netdev failed")); + return FALSE; + } + + /* Copy ethernet address. */ + for (i = 0; i < ETH_ALEN; i++) + adapter->dev->dev_addr[i] = adapter_enaddr[i]; + + /* Initialize the device structure. */ + /* Set device private data to point to virtual adapter structure. */ + SSH_LINUX_NET_DEVICE_PRIV(adapter->dev) = (void *) adapter; + +#ifdef HAVE_NET_DEVICE_OPS + adapter->dev->netdev_ops = &ssh_virtual_adapter_ops; +#else + adapter->dev->hard_start_xmit = ssh_virtual_adapter_xmit; + adapter->dev->get_stats = ssh_virtual_adapter_get_stats; +#endif + + memset(&adapter->low_level_stats, 0, sizeof(struct net_device_stats)); + adapter->dev->tx_queue_len = 0; /* no transmit queue */ + adapter->dev->destructor = ssh_virtual_adapter_destructor; + + /* Fill in the fields of the device structure with ethernet-generic + values. */ + ether_setup(adapter->dev); + + adapter->dev->flags |= IFF_POINTOPOINT; + + /* Set default MTU. */ + adapter->dev->mtu -= 100; + + /* Register the network device. This call assigns a valid ifindex to + virtual adapter and it triggers an interface event. */ + result = register_netdev(adapter->dev); + + if (result != 0) + { + /* register_netdev() failed. Free net_device. */ + SSH_LINUX_NET_DEVICE_PRIV(adapter->dev) = NULL; + ssh_virtual_adapter_destructor(adapter->dev); + adapter->dev = NULL; + SSH_DEBUG(SSH_D_ERROR, ("register_netdev failed: %d", result)); + return FALSE; + } + + /* All ok. */ + SSH_DEBUG(SSH_D_MIDOK, ("Virtual adapter %d [%s] attached successfully", + adapter->dev->ifindex, adapter->dev->name)); + return TRUE; +} + +static void +ssh_virtual_adapter_detach_low_level(SshVirtualAdapter adapter) +{ +#ifdef DEBUG_LIGHT + unsigned char adapter_name[SSH_INTERCEPTOR_IFNAME_SIZE]; + SshInterceptorIfnum adapter_ifnum; +#endif /* DEBUG_LIGHT */ + + /* Virtual adapter is not attached to system. */ + if (adapter->dev == NULL) + return; + +#ifdef DEBUG_LIGHT + memcpy(adapter_name, adapter->dev->name, IFNAMSIZ); + adapter_ifnum = (SshInterceptorIfnum) adapter->dev->ifindex; +#endif /* DEBUG_LIGHT */ + + /* Remove the network device. This call triggers an interface event. */ + unregister_netdev(adapter->dev); + + /* Unlink netdevice structure, it will be freed in the device destructor. */ + adapter->dev = NULL; + + /* All ok. */ + SSH_DEBUG(SSH_D_NICETOKNOW, ("Virtual adapter %d [%s] detached", + (int)adapter_ifnum, adapter_name)); +} + +/* *************** Creating and Destroying Virtual Adapters *****************/ + +/* Workhorse for destroy_all and error handler for create. `adapter' must + not be in the adapter table when this function is called. */ +static Boolean +ssh_virtual_adapter_destroy(SshInterceptor interceptor, + SshVirtualAdapter adapter) +{ + SSH_ASSERT(adapter != NULL); + + ssh_kernel_mutex_assert_is_locked(interceptor->interceptor_lock); + + SSH_DEBUG(SSH_D_HIGHOK, + ("Destroying virtual adapter %d [%s]", + (adapter->dev ? adapter->dev->ifindex : -1), + (adapter->dev ? adapter->dev->name : "unknown"))); + + if (adapter->dev) + { + /* Detach and destroy net_device. */ + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + /* This call will produce an interface event. */ + ssh_virtual_adapter_detach_low_level(adapter); + + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + adapter->dev = NULL; + } + + /* Free adapter */ + ssh_free(adapter); + + /* All ok. */ + return TRUE; +} + +/* This function is called during module loading. */ +static Boolean +ssh_virtual_adapter_create(SshInterceptor interceptor, + unsigned char *adapter_name) +{ + SshVirtualAdapter adapter; + SshUInt32 i; + Boolean error = FALSE; + unsigned char created_adapter_name[SSH_INTERCEPTOR_IFNAME_SIZE]; + unsigned char adapter_enaddr[SSH_MAX_VIRTUAL_ADAPTER_HWADDR_LEN]; + + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + + /* Find a slot for this virtual adapter. Slot index will be used in + adapter name and ethernet address creation. */ + for (i = 0; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + if (interceptor->virtual_adapters[i] == NULL) + break; + } + if (i >= SSH_LINUX_MAX_VIRTUAL_ADAPTERS) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, ("Maximum number of virtual adapters reached")); + return FALSE; + } + + /* Allocate the virtual adapter and store it in to the table. */ + adapter = ssh_calloc(1, sizeof(*adapter)); + if (adapter == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, ("Could not allocate virtual adapter")); + return FALSE; + } + interceptor->virtual_adapters[i] = adapter; + + /* Create a name for the virtual adapter. */ + if (adapter_name == NULL) + { + snprintf(created_adapter_name, SSH_INTERCEPTOR_IFNAME_SIZE, + "%s%d", SSH_ADAPTER_NAME_PREFIX, (int)i); + adapter_name = created_adapter_name; + } + + /* Create ethernet hardware address for virtual adapter. */ + ssh_virtual_adapter_interface_ether_address(i, adapter_enaddr); + + /* We can't have lock when attaching the adapter, as it will create an + interface event. It is guaranteed that the adapter will not disappear + under us, as it not yet marked initialized. */ + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + /* Initialize net_device structure and attach it to the system. */ + if (!ssh_virtual_adapter_attach_low_level(adapter, + adapter_name, adapter_enaddr)) + { + SSH_DEBUG(SSH_D_ERROR, + ("Could not attach virtual adapter %s to system.", + adapter_name)); + error = TRUE; + } + else + { + /* We are attached to the system. */ + SSH_DEBUG(SSH_D_MIDOK, + ("Virtual adapter %lu [%s] created", + (unsigned long)adapter->dev->ifindex, adapter->dev->name)); + } + + /* Lock for finalizing adapter creation. */ + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + + /* Mark adapter initialized. */ + adapter->initialized = 1; + + /* Adapter low level attach failed or adapter was destroyed under us. + Free it now. */ + if (error || adapter->destroyed) + { + interceptor->virtual_adapters[i] = NULL; + ssh_virtual_adapter_destroy(interceptor, adapter); + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + return FALSE; + } + + /* All ok. */ + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + return TRUE; +} + +/* This function is called during module unload. */ +static void +ssh_virtual_adapter_destroy_all(SshInterceptor interceptor) +{ + SshVirtualAdapter adapter; + Boolean check_again; + SshUInt32 i; + + restart: + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + + check_again = FALSE; + for (i = 0; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++) + { + adapter = interceptor->virtual_adapters[i]; + if (adapter == NULL) + continue; + + if (!adapter->initialized) + { + /* Initialization is underway, mark adapter to be destroyed. */ + adapter->destroyed = 1; + check_again = TRUE; + continue; + } + + /* Remove adapter from table. */ + interceptor->virtual_adapters[i] = NULL; + + /* Detach and destroy adapter. */ + ssh_virtual_adapter_destroy(interceptor, adapter); + + /* Unlock and restart. */ + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + goto restart; + } + + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + if (check_again) + goto restart; +} + +/* ************ Interceptor Side Virtual Adapter Init / Uninit ***************/ + +/* Initialize virtual adapters. This function is called from linux_main.c + during module loading. */ +int ssh_interceptor_virtual_adapter_init(SshInterceptor interceptor) +{ + SshUInt32 i; + + SSH_ASSERT(!in_softirq()); + + if (num_virtual_adapters > SSH_LINUX_MAX_VIRTUAL_ADAPTERS) + { + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Maximum value for num_virtual_adapters is %d", + SSH_LINUX_MAX_VIRTUAL_ADAPTERS)); + return -1; + } + + for (i = 0; i < num_virtual_adapters; i++) + { + if (!ssh_virtual_adapter_create(interceptor, NULL)) + goto error; + } + + return 0; + + error: + ssh_virtual_adapter_destroy_all(interceptor); + return -1; +} + +/* Uninitialize virtual adapters. This function is called from linux_main.c + during module unloading. */ +int ssh_interceptor_virtual_adapter_uninit(SshInterceptor interceptor) +{ + SSH_ASSERT(!in_softirq()); + + /* Detach all virtual adapters from the system. */ + ssh_virtual_adapter_destroy_all(interceptor); + + return 0; +} + +Boolean +ssh_virtual_adapter_param_decode(SshVirtualAdapterParams params, + const unsigned char *data, size_t len) +{ + unsigned char *dns; + size_t dns_len; + unsigned char *wins; + size_t wins_len; + unsigned char *win_domain; + size_t win_domain_len; + SshUInt32 netbios_node_type; + SshUInt32 i; + size_t decode_len; + + SSH_ASSERT(params != NULL); + SSH_ASSERT(data != NULL); + SSH_ASSERT(len > 0); + + memset(params, 0, sizeof(*params)); + + if (ssh_decode_array(data, len, + SSH_DECODE_UINT32(¶ms->mtu), + SSH_DECODE_UINT32(¶ms->dns_ip_count), + SSH_DECODE_UINT32_STR_NOCOPY(&dns, &dns_len), + SSH_DECODE_UINT32(¶ms->wins_ip_count), + SSH_DECODE_UINT32_STR_NOCOPY(&wins, &wins_len), + SSH_DECODE_UINT32_STR_NOCOPY( + &win_domain, &win_domain_len), + SSH_DECODE_UINT32(&netbios_node_type), + SSH_FORMAT_END) != len) + return FALSE; + + /* DNS. */ + if (params->dns_ip_count) + { + params->dns_ip = ssh_calloc(params->dns_ip_count, + sizeof(*params->dns_ip)); + if (params->dns_ip == NULL) + goto error; + + for (i = 0; i < params->dns_ip_count; i++) + { + decode_len = ssh_decode_ipaddr_array(dns, dns_len, + ¶ms->dns_ip[i]); + if (decode_len == 0) + goto error; + dns += decode_len; + dns_len -= decode_len; + } + } + + /* WINS. */ + if (params->wins_ip_count) + { + params->wins_ip = ssh_calloc(params->wins_ip_count, + sizeof(*params->wins_ip)); + if (params->wins_ip == NULL) + goto error; + + for (i = 0; i < params->wins_ip_count; i++) + { + decode_len = ssh_decode_ipaddr_array(wins, wins_len, + ¶ms->wins_ip[i]); + if (decode_len == 0) + goto error; + wins += decode_len; + wins_len -= decode_len; + } + } + + if (win_domain_len) + { + params->win_domain = ssh_memdup(win_domain, win_domain_len); + if (params->win_domain == NULL) + goto error; + } + + params->netbios_node_type = (SshUInt8) netbios_node_type; + + return TRUE; + + error: + ssh_free(params->dns_ip); + ssh_free(params->wins_ip); + ssh_free(params->win_domain); + memset(params, 0, sizeof(*params)); + return FALSE; +} + +void +ssh_virtual_adapter_interface_ether_address(SshInterceptorIfnum adapter_ifnum, + unsigned char *buffer) +{ + memset(buffer, 0, SSH_ETHERH_ADDRLEN); + buffer[1] = 1; + SSH_PUT_32BIT(buffer + 2, (SshUInt32) adapter_ifnum + 1); +} + +void +ssh_virtual_adapter_ip_ether_address(SshIpAddr ip, unsigned char *buffer) +{ + memset(buffer, 0, SSH_ETHERH_ADDRLEN); + + if (SSH_IP_IS4(ip)) + { + buffer[1] = 2; + SSH_IP4_ENCODE(ip, buffer + 2); + } +#if defined (WITH_IPV6) + else + { + SshUInt32 value; + + value = SSH_IP6_WORD0_TO_INT(ip); + value ^= SSH_IP6_WORD1_TO_INT(ip); + value ^= SSH_IP6_WORD2_TO_INT(ip); + value ^= SSH_IP6_WORD3_TO_INT(ip); + + buffer[1] = 2; + SSH_PUT_32BIT(buffer + 2, value); + } +#endif /* WITH_IPV6 */ +} + + +/* ****************** Sending Packets to Local Stack ************************/ + +void +ssh_virtual_adapter_send(SshInterceptor interceptor, + SshInterceptorPacket pp) +{ + SshVirtualAdapter adapter; + SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket) pp; + struct net_device_stats *stats; + struct sk_buff *skb; + + local_bh_disable(); + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + adapter = ssh_virtual_adapter_ifnum_to_adapter(interceptor, pp->ifnum_out); + if (adapter == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, + ("Virtual adapter %d does not exist", (int)pp->ifnum_out)); + goto error; + } + + /* Check the type of the source packet. */ + if (pp->protocol == SSH_PROTOCOL_ETHERNET) + { + /* We can send this directly. */ + } + else if (pp->protocol == SSH_PROTOCOL_IP4 +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + || pp->protocol == SSH_PROTOCOL_IP6 +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + ) + { + unsigned char ether_hdr[SSH_ETHERH_HDRLEN]; + SshIpAddrStruct src; + SshUInt16 ethertype = SSH_ETHERTYPE_IP; + unsigned char *cp = NULL; + size_t packet_len; + + /* Add ethernet framing. */ + + /* Destination is virtual adapter's ethernet address. */ + memcpy(ether_hdr + SSH_ETHERH_OFS_DST, adapter->dev->dev_addr, + SSH_ETHERH_ADDRLEN); + + /* Resolve packet's source and the ethernet type to use. */ + packet_len = ssh_interceptor_packet_len(pp); + + /* IPv4 */ + if (pp->protocol == SSH_PROTOCOL_IP4) + { + if (packet_len < SSH_IPH4_HDRLEN) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, + ("Packet is too short to contain IPv4 header")); + goto error; + } + + /* Pullup requests data from the header of a writable skb. */ + if (likely(skb_headlen(ipp->skb) >= SSH_IPH4_HDRLEN + && !skb_shared(ipp->skb) && + SSH_SKB_WRITABLE(ipp->skb, SSH_IPH4_HDRLEN))) + cp = ipp->skb->data; + + if (cp == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + goto error_already_freed; + } + + SSH_IPH4_SRC(&src, cp); + } + +#ifdef SSH_LINUX_INTERCEPTOR_IPV6 + /* IPv6 */ + else + { + if (packet_len < SSH_IPH6_HDRLEN) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, + ("Packet too short to contain IPv6 header")); + goto error; + } + + if (likely(skb_headlen(ipp->skb) >= SSH_IPH6_HDRLEN + && !skb_shared(ipp->skb) && + SSH_SKB_WRITABLE(ipp->skb, SSH_IPH6_HDRLEN))) + cp = ipp->skb->data; + + if (cp == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + goto error_already_freed; + } + + SSH_IPH6_SRC(&src, cp); + ethertype = SSH_ETHERTYPE_IPv6; + } +#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ + + /* Finalize ethernet header. */ + ssh_virtual_adapter_ip_ether_address(&src, + ether_hdr + SSH_ETHERH_OFS_SRC); + SSH_PUT_16BIT(ether_hdr + SSH_ETHERH_OFS_TYPE, ethertype); + + /* Insert header to the packet. */ + cp = NULL; + if (likely((skb_headroom(ipp->skb) >= + (SSH_ETHERH_HDRLEN + SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM)) + && !skb_shared(ipp->skb) && SSH_SKB_WRITABLE(ipp->skb, 0))) + cp = skb_push(ipp->skb, SSH_ETHERH_HDRLEN); + + if (cp == NULL) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + goto error_already_freed; + } + memcpy(cp, ether_hdr, SSH_ETHERH_HDRLEN); + + /* Just to be pedantic. */ + pp->protocol = SSH_PROTOCOL_ETHERNET; + } + else + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + SSH_DEBUG(SSH_D_ERROR, ("Can not handle protocol %d", pp->protocol)); + goto error; + } + + /* Tear off the internal packet from the generic SshInterceptorPacket. */ + skb = ipp->skb; + ipp->skb = NULL; + + /* (re-)receive the packet via the interface; this should + make the packet go back up the stack */ + skb->protocol = eth_type_trans(skb, adapter->dev); + skb->dev = adapter->dev; + + /* Update per virtual adapter statistics. */ + stats = &adapter->low_level_stats; + stats->rx_packets++; + stats->rx_bytes += skb->len; + + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + local_bh_enable(); + + /* Send the skb up towards stack. If it is IP (or ARP), it will be + intercepted by ssh_interceptor_packet_in. */ + netif_rx(skb); + + /* Put the packet header on freelist. */ + ssh_interceptor_packet_free((SshInterceptorPacket) ipp); + return; + + error: + ssh_interceptor_packet_free(pp); + + error_already_freed: + return; +} |