diff options
Diffstat (limited to 'drivers/interceptor/linux_main.c')
-rw-r--r-- | drivers/interceptor/linux_main.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_main.c b/drivers/interceptor/linux_main.c new file mode 100644 index 0000000..ba92502 --- /dev/null +++ b/drivers/interceptor/linux_main.c @@ -0,0 +1,533 @@ +/* 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_main.c + * + * Linux interceptor kernel module main. + * + */ + +#include "linux_internal.h" +#include "sshinet.h" + +#include <linux/kernel.h> + +#ifdef DEBUG_LIGHT +unsigned int ssh_debug_level = 0; +MODULE_PARM_DESC(ssh_debug_level, "Debug level"); +module_param(ssh_debug_level, uint, 0444); +#endif /* DEBUG_LIGHT */ + +/* Global interceptor object */ +SshInterceptor ssh_interceptor_context = NULL; + +/* Preallocated interceptor object */ +static SshInterceptorStruct interceptor_struct; + +/******************************** Utility functions *************************/ + +void +ssh_interceptor_notify_ipm_open(SshInterceptor interceptor) +{ + local_bh_disable(); + + /* Tell engine the PM connection is open. */ + ssh_engine_notify_ipm_open(interceptor->engine); + + local_bh_enable(); +} + +void +ssh_interceptor_notify_ipm_close(SshInterceptor interceptor) +{ + local_bh_disable(); + + /* Tell engine the PM connection is closed. */ + ssh_engine_notify_ipm_close(interceptor->engine); + + /* Disable packet interception now that ipm has disconnected. */ + interceptor->enable_interception = FALSE; + ssh_interceptor_dst_entry_cache_flush(interceptor); + + local_bh_enable(); +} + +/******************************* Interceptor API ****************************/ + +/* Opens the packet interceptor. This must be called before using any + other interceptor functions. This registers the callbacks that the + interceptor will use to notify the higher levels of received packets + or changes in the interface list. The interface callback will be called + once either during this call or soon after this has returned. + The `packet_cb' callback will be called whenever a packet is received + from either a network adapter or a protocol stack. It is guaranteed that + this will not be called until from the bottom of the event loop after the + open call has returned. + + The `interfaces_cb' callback will be called once soon after opening the + interceptor, however earliest from the bottom of the event loop after the + open call has returned. From then on, it will be called whenever there is + a change in the interface list (e.g., the IP address of an interface is + changed, or a PPP interface goes up or down). + + The `callback_context' argument is passed to the callbacks. */ + +Boolean +ssh_interceptor_open(void *machine_context, + SshInterceptorPacketCB packet_cb, + SshInterceptorInterfacesCB interfaces_cb, + SshInterceptorRouteChangeCB route_cb, + void *callback_context, + SshInterceptor * interceptor_return) +{ + SshInterceptor interceptor; + + interceptor = ssh_interceptor_context; + if (interceptor->engine_open) + return FALSE; + + local_bh_disable(); + + SSH_DEBUG(2, ("interceptor opened")); + + interceptor->engine_open = TRUE; + interceptor->packet_callback = packet_cb; + interceptor->interfaces_callback = interfaces_cb; + interceptor->route_callback = route_cb; + interceptor->callback_context = callback_context; + + /* Return the global interceptor object. */ + *interceptor_return = (void *) interceptor; + + local_bh_enable(); + return TRUE; +} + +/* Closes the packet interceptor. No more packet or interface callbacks + will be received from the interceptor after this returns. Destructors + may still get called even after this has returned. + + It is illegal to call any packet interceptor functions (other than + ssh_interceptor_open) after this call. It is, however, legal to call + destructors for any previously returned packets even after calling this. + Destructors for any packets previously supplied to one of the send + functions will get called before this function returns. */ + +void +ssh_interceptor_close(SshInterceptor interceptor) +{ + /* all closing is done in ssh_interceptor_uninit() */ + interceptor->engine_open = FALSE; + return; +} + +/* Dummy function callback after interceptor has been stopped */ +static void +ssh_interceptor_dummy_interface_cb(SshUInt32 num_interfaces, + SshInterceptorInterface *ifs, + void *context) +{ + /* Do nothing */ + return; +} + +/* Dummy function which packets get routed to after ssh_interceptor_stop() + has been called. */ +static void +ssh_interceptor_dummy_packet_cb(SshInterceptorPacket pp, void *ctx) +{ + ssh_interceptor_packet_free(pp); +} + +/* Stops the packet interceptor. After this call has returned, no new + calls to the packet and interfaces callbacks will be made. The + interceptor keeps track of how many threads are processing packet, + interface, or have pending route callbacks, and this function + returns TRUE if there are no callbacks/pending calls to those functions. + This returns FALSE if threads are still executing in those callbacks + or routing callbacks are pending. + + After calling this function, the higher-level code should wait for + packet processing to continue, free all packet structures received + from that interceptor, and then close ssh_interceptor_close. It is + not an error to call this multiple times (the latter calls are + ignored). */ + +Boolean +ssh_interceptor_stop(SshInterceptor interceptor) +{ + SSH_DEBUG(2, ("interceptor stopping")); + + /* 'interceptor_lock protects the 'interfaces_callback' + and 'num_interface_callbacks'. */ + ssh_kernel_mutex_lock(interceptor->interceptor_lock); + + if (interceptor->num_interface_callbacks) + { + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + SSH_DEBUG(SSH_D_ERROR, + ("%d interface callbacks pending, can't stop", + interceptor->num_interface_callbacks)); + return FALSE; + } + + /* No more interfaces are delivered to the engine after this. */ + interceptor->interfaces_callback = ssh_interceptor_dummy_interface_cb; + + /* Route callback is currently not used. */ + interceptor->route_callback = NULL_FNPTR; + + ssh_kernel_mutex_unlock(interceptor->interceptor_lock); + + /* After this the engine will receive no more packets from + the interceptor, although the netfilter hooks are still + installed. */ + + /* Set packet_callback to point to our dummy_db */ + rcu_assign_pointer(interceptor->packet_callback, + ssh_interceptor_dummy_packet_cb); + + /* Wait for state synchronization. */ + local_bh_enable(); + synchronize_rcu(); + local_bh_disable(); + + /* Callback context can now be safely zeroed, as both + the interface_callback and the packet_callback point to + our dummy_cb, and all kernel threads have returned from + the engine. */ + interceptor->callback_context = NULL; + + SSH_DEBUG(2, ("interceptor stopped")); + + return TRUE; +} + +/************** Interceptor uninitialization break-down. *******************/ + +static void +ssh_interceptor_uninit_external_interfaces(SshInterceptor interceptor) +{ + ssh_interceptor_virtual_adapter_uninit(interceptor); + + /* Remove netfilter hooks */ + ssh_interceptor_ip_glue_uninit(interceptor); +} + +static void +ssh_interceptor_uninit_engine(SshInterceptor interceptor) +{ + /* Stop packet processing engine */ + if (interceptor->engine != NULL) + { + while (ssh_engine_stop(interceptor->engine) == FALSE) + { + local_bh_enable(); + schedule(); + mdelay(300); + local_bh_disable(); + } + interceptor->engine = NULL; + } + + /* Free packet data structure */ + ssh_interceptor_packet_freelist_uninit(interceptor); +} + +static void +ssh_interceptor_uninit_kernel_services(void) +{ + /* Remove interface event handlers and free interface table. */ + ssh_interceptor_iface_uninit(ssh_interceptor_context); + + ssh_interceptor_dst_entry_cache_uninit(ssh_interceptor_context); + + /* Uninitialize ipm channel */ + ssh_interceptor_ipm_uninit(ssh_interceptor_context); + + /* Free locks */ + ssh_kernel_mutex_free(ssh_interceptor_context->interceptor_lock); + ssh_interceptor_context->interceptor_lock = NULL; + ssh_kernel_mutex_free(ssh_interceptor_context->packet_lock); + ssh_interceptor_context->packet_lock = NULL; + + ssh_interceptor_context = NULL; +} + +/* Interceptor uninitialization. Called by cleanup_module() with + softirqs disabled. */ +static int +ssh_interceptor_uninit(void) +{ + /* Uninitialize external interfaces. We leave softirqs enabled for + this as we have to make calls into the netfilter API that will + execute scheduling in Linux 2.6. */ + ssh_interceptor_uninit_external_interfaces(ssh_interceptor_context); + + /* Uninitialize engine. Via ssh_interceptor_stop() this + function makes sure that no callouts to the interceptor + are in progress after it returns. ssh_interceptor_stop() + _WILL_ grab the interceptor_lock, so make sure that it + is not held.*/ + local_bh_disable(); + ssh_interceptor_uninit_engine(ssh_interceptor_context); + + /* Uninitialize basic kernel services to the engine and the + interceptor. This frees all remaining memory. Note that all locks + are also freed here, so none of them can be held. */ + ssh_interceptor_uninit_kernel_services(); + local_bh_enable(); + + return 0; +} + + +/************** Interceptor initialization break-down. *********************/ + + +int +ssh_interceptor_init_kernel_services(void) +{ + /* Interceptor object is always preallocated. */ + SSH_ASSERT(ssh_interceptor_context == NULL); + memset(&interceptor_struct, 0, sizeof(interceptor_struct)); + ssh_interceptor_context = &interceptor_struct; + +#ifdef DEBUG_LIGHT + spin_lock_init(&ssh_interceptor_context->statistics_lock); +#endif /* DEBUG_LIGHT */ + + /* General init */ + ssh_interceptor_context->interceptor_lock = ssh_kernel_mutex_alloc(); + ssh_interceptor_context->packet_lock = ssh_kernel_mutex_alloc(); + + if (ssh_interceptor_context->interceptor_lock == NULL + || ssh_interceptor_context->packet_lock == NULL) + goto error; + + rwlock_init(&ssh_interceptor_context->if_table_lock); + + /* Init packet data structure */ + if (!ssh_interceptor_packet_freelist_init(ssh_interceptor_context)) + { + printk(KERN_ERR + "VPNClient packet processing engine failed to start " + "(out of memory).\n"); + goto error; + } + + if (ssh_interceptor_dst_entry_cache_init(ssh_interceptor_context) == FALSE) + { + printk(KERN_ERR "VPNClient packet processing engine " + "failed to start, dst cache initialization failed."); + goto error; + } + + /* Initialize ipm channel */ + if (!ssh_interceptor_ipm_init(ssh_interceptor_context)) + { + printk(KERN_ERR + "VPNClient packet processing engine failed to start " + "(proc filesystem initialization error)\n"); + goto error1; + } + + return 0; + + error1: + local_bh_disable(); + ssh_interceptor_packet_freelist_uninit(ssh_interceptor_context); + local_bh_enable(); + + error: + ssh_interceptor_dst_entry_cache_uninit(ssh_interceptor_context); + ssh_kernel_mutex_free(ssh_interceptor_context->interceptor_lock); + ssh_interceptor_context->interceptor_lock = NULL; + + ssh_kernel_mutex_free(ssh_interceptor_context->packet_lock); + ssh_interceptor_context->packet_lock = NULL; + + ssh_interceptor_context = NULL; + + return -ENOMEM; +} + +int +ssh_interceptor_init_external_interfaces(SshInterceptor interceptor) +{ + /* Register interface notifiers. */ + if (!ssh_interceptor_iface_init(interceptor)) + { + printk(KERN_ERR + "VPNClient packet processing engine failed to start " + "(interface notifier installation error).\n"); + goto error0; + } + + /* Register the firewall hooks. */ + if (!ssh_interceptor_ip_glue_init(interceptor)) + { + printk(KERN_ERR + "VPNClient packet processing engine failed to start " + "(firewall glue installation error).\n"); + goto error1; + } + + return 0; + + error1: + + local_bh_disable(); + ssh_interceptor_iface_uninit(interceptor); + local_bh_enable(); + error0: + + return -EBUSY; +} + +int +ssh_interceptor_init_engine(SshInterceptor interceptor) +{ + int start_cnt; + + /* Initialize the IPsec engine */ + + interceptor->engine = NULL; + for (start_cnt = 0; + start_cnt < 3 && interceptor->engine == NULL; + start_cnt++) + { + /* In theory, it would be nice and proper to disable softirqs + here and enable them after we exit engine_start(), but then + we could not allocate memory without GFP_ATOMIC in the + engine initialization, which would not be nice. Therefore + we leave softirqs open here, and disable them for the + duration of ssh_interceptor_open(). */ + interceptor->engine = ssh_engine_start(ssh_interceptor_send_to_ipm, + interceptor, + SSH_LINUX_ENGINE_FLAGS); + if (interceptor->engine == NULL) + { + schedule(); + mdelay(500); + } + } + + if (interceptor->engine == NULL) + { + printk(KERN_ERR + "VPNClient packet processing engine failed to start " + "(engine start error).\n"); + goto error; + } + + return 0; + + error: + if (interceptor->engine != NULL) + { + local_bh_disable(); + while (ssh_engine_stop(interceptor->engine) == FALSE) + { + local_bh_enable(); + schedule(); + mdelay(300); + local_bh_disable(); + } + local_bh_enable(); + interceptor->engine = NULL; + } + + return -EBUSY; +} + +/* Interceptor initialization. Called by init_module(). */ +int ssh_interceptor_init(void) +{ + int ret; + + /* Print version info for log files */ + printk(KERN_INFO "VPNClient built on " __DATE__ " " __TIME__ "\n"); + + ret = ssh_interceptor_init_kernel_services(); + if (ret != 0) + goto error0; + + SSH_ASSERT(ssh_interceptor_context != NULL); + + ret = ssh_interceptor_hook_magic_init(); + if (ret != 0) + goto error1; + + ret = ssh_interceptor_virtual_adapter_init(ssh_interceptor_context); + if (ret != 0) + goto error1; + + ret = ssh_interceptor_init_engine(ssh_interceptor_context); + if (ret != 0) + goto error4; + + ret = ssh_interceptor_init_external_interfaces(ssh_interceptor_context); + if (ret != 0) + goto error5; + + return 0; + + error5: + local_bh_disable(); + ssh_interceptor_uninit_engine(ssh_interceptor_context); + local_bh_enable(); + + error4: + ssh_interceptor_clear_ifaces(ssh_interceptor_context); + ssh_interceptor_virtual_adapter_uninit(ssh_interceptor_context); + + error1: + local_bh_disable(); + ssh_interceptor_uninit_kernel_services(); + local_bh_enable(); + + error0: + return ret; +} + +MODULE_DESCRIPTION(SSH_LINUX_INTERCEPTOR_MODULE_DESCRIPTION); + +int __init ssh_init_module(void) +{ + if (ssh_interceptor_init() != 0) + return -EIO; + return 0; +} + +void __exit ssh_cleanup_module(void) +{ + if (ssh_interceptor_uninit() != 0) + { + printk("ssh_interceptor: module can't be removed."); + return; + } +} + +void ssh_linux_module_dec_use_count() +{ + module_put(THIS_MODULE); +} + +int +ssh_linux_module_inc_use_count() +{ + return try_module_get(THIS_MODULE); +} + +MODULE_LICENSE("GPL"); +module_init(ssh_init_module); +module_exit(ssh_cleanup_module); |