aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/interceptor/linux_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/interceptor/linux_main.c')
-rw-r--r--drivers/interceptor/linux_main.c533
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);