diff options
Diffstat (limited to 'drivers/interceptor/linux_ipm.c')
-rw-r--r-- | drivers/interceptor/linux_ipm.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_ipm.c b/drivers/interceptor/linux_ipm.c new file mode 100644 index 0000000..a9cb079 --- /dev/null +++ b/drivers/interceptor/linux_ipm.c @@ -0,0 +1,364 @@ +/* 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_ipm.c + * + * Linux interceptor kernel to user space messaging. + * + */ + +#include "linux_internal.h" + +extern SshInterceptor ssh_interceptor_context; + +/************************ Internal utility functions ************************/ + +/* Use printk instead of SSH_DEBUG macros. */ +#ifdef DEBUG_LIGHT +#define SSH_LINUX_IPM_DEBUG(x...) if (net_ratelimit()) printk(KERN_INFO x) +#define SSH_LINUX_IPM_WARN(x...) panic(x) +#endif /* DEBUG_LIGHT */ + +#ifndef SSH_LINUX_IPM_DEBUG +#define SSH_LINUX_IPM_DEBUG(x...) +#define SSH_LINUX_IPM_WARN(x...) printk(KERN_CRIT x) +#endif /* SSH_LINUX_IPM_DEBUG */ + +/************************* Ipm message alloc / free *************************/ + +static void interceptor_ipm_message_free_internal(SshInterceptor interceptor, + SshInterceptorIpmMsg msg) +{ + SSH_ASSERT(interceptor != NULL); + SSH_ASSERT(msg != NULL); + + if (msg->buf) + ssh_free(msg->buf); + msg->buf = NULL; + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.ipm_send_queue_len--; + interceptor->stats.ipm_send_queue_bytes -= (SshUInt64) msg->len; + }); + + if (msg->emergency_mallocated) + { + ssh_free(msg); + } + else + { + msg->next = interceptor->ipm.msg_freelist; + interceptor->ipm.msg_freelist = msg; + msg->prev = NULL; + } +} + +void interceptor_ipm_message_free(SshInterceptor interceptor, + SshInterceptorIpmMsg msg) +{ + local_bh_disable(); + write_lock(&interceptor->ipm.lock); + interceptor_ipm_message_free_internal(interceptor, msg); + write_unlock(&interceptor->ipm.lock); + local_bh_enable(); +} + +static SshInterceptorIpmMsg +interceptor_ipm_message_alloc(SshInterceptor interceptor, + Boolean reliable, + size_t len) +{ + SshInterceptorIpmMsg msg = NULL; + + SSH_ASSERT(interceptor != NULL); + + /* Try to take a message from freelist. */ + if (interceptor->ipm.msg_freelist) + { + msg = interceptor->ipm.msg_freelist; + interceptor->ipm.msg_freelist = msg->next; + } + +#if SSH_LINUX_MAX_IPM_MESSAGES < 2000 +#error "SSH_LINUX_MAX_IPM_MESSAGES is too low" +#endif /* SSH_LINUX_MAX_IPM_MESSAGES */ + + /* Try to allocate a new message. */ + else if (interceptor->ipm.msg_allocated < SSH_LINUX_MAX_IPM_MESSAGES) + { + msg = ssh_calloc(1, sizeof(*msg)); + if (msg != NULL) + { + interceptor->ipm.msg_allocated++; + } + } + + /* Try to reuse last unreliable message in send queue. */ + if (msg == NULL && reliable == TRUE) + { + /* This is a reliable message, reuse last unreliable message. */ + if (interceptor->ipm.send_queue_num_unreliable > 0) + { + for (msg = interceptor->ipm.send_queue_tail; + msg != NULL; + msg = msg->prev) + { + if (msg->reliable == 0) + { + if (msg->next != NULL) + msg->next->prev = msg->prev; + + if (msg->prev != NULL) + msg->prev->next = msg->next; + + if (msg == interceptor->ipm.send_queue) + interceptor->ipm.send_queue = msg->next; + + if (msg == interceptor->ipm.send_queue_tail) + interceptor->ipm.send_queue_tail = msg->prev; + + SSH_ASSERT(interceptor->ipm.send_queue_num_unreliable > 0); + interceptor->ipm.send_queue_num_unreliable--; + + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.ipm_send_queue_len--; + interceptor->stats.ipm_send_queue_bytes + -= (SshUInt64) msg->len; + }); + + ssh_free(msg->buf); + break; + } + } + } + + /* Last resort, malloc message. */ + if (msg == NULL) + { + msg = ssh_calloc(1, sizeof(*msg)); + if (msg != NULL) + msg->emergency_mallocated = 1; + } + } + + if (msg) + SSH_LINUX_STATISTICS(interceptor, + { + interceptor->stats.ipm_send_queue_len++; + interceptor->stats.ipm_send_queue_bytes += (SshUInt64) len; + }); + + return msg; +} + +void interceptor_ipm_message_freelist_uninit(SshInterceptor interceptor) +{ + SshInterceptorIpmMsg msg; + int freelist_len; + + local_bh_disable(); + write_lock(&interceptor->ipm.lock); + + SSH_ASSERT(atomic_read(&interceptor->ipm.open) == 0); + + while (interceptor->ipm.msg_freelist != NULL) + { + msg = interceptor->ipm.msg_freelist; + interceptor->ipm.msg_freelist = msg->next; + SSH_ASSERT(msg->buf == NULL); + ssh_free(msg); + interceptor->ipm.msg_allocated--; + } + + freelist_len = interceptor->ipm.msg_allocated; + + write_unlock(&interceptor->ipm.lock); + local_bh_enable(); + + if (freelist_len) + SSH_LINUX_IPM_WARN("Memory leak detected: %d ipm messages leaked!\n", + freelist_len); +} + + +/***************************** Process message from ipm ********************/ + +ssize_t ssh_interceptor_receive_from_ipm(unsigned char *data, size_t len) +{ + SshUInt32 msg_len; + SshUInt8 msg_type; + + /* Need a complete header. */ + if (len < 5) + return 0; + + /* Parse message header. */ + msg_len = SSH_GET_32BIT(data) - 1; + msg_type = SSH_GET_8BIT(data + 4); + + /* Need a complete message. */ + if (msg_len > (len - 5)) + return 0; + + /* Pass message to engine. */ + local_bh_disable(); + + ssh_engine_packet_from_ipm(ssh_interceptor_context->engine, + msg_type, data + 5, msg_len); + + local_bh_enable(); + + return msg_len + 5; +} + + +/***************************** Send to ipm *********************************/ + +Boolean ssh_interceptor_send_to_ipm(unsigned char *data, size_t len, + Boolean reliable, void *machine_context) +{ + SshInterceptorIpmMsg msg = NULL; + + local_bh_disable(); + write_lock(&ssh_interceptor_context->ipm.lock); + + /* Check ipm channel status */ + if (atomic_read(&ssh_interceptor_context->ipm.open) == 0) + { + write_unlock(&ssh_interceptor_context->ipm.lock); + local_bh_enable(); + ssh_free(data); + SSH_LINUX_IPM_DEBUG("ipm channel closed, dropping ipm message len %d\n", + (int) len); + return FALSE; + } + + /* Allocate a message. */ + msg = interceptor_ipm_message_alloc(ssh_interceptor_context, reliable, len); + if (msg == NULL) + { + write_unlock(&ssh_interceptor_context->ipm.lock); + local_bh_enable(); + + if (reliable) + SSH_LINUX_IPM_WARN("Dropping reliable ipm message type %d len %d\n", + (int) (len < 5 ? -1 : data[4]), (int) len); + else + SSH_LINUX_IPM_DEBUG("Dropping unreliable ipm message type %d " + "len %d\n", + (int) (len < 5 ? -1 : data[4]), (int) len); + ssh_free(data); + return FALSE; + } + + /* Fill message structure. */ + msg->buf = data; + msg->len = len; + msg->offset = 0; + if (reliable) + msg->reliable = 1; + + /* Append message to send queue tail. */ + msg->prev = ssh_interceptor_context->ipm.send_queue_tail; + ssh_interceptor_context->ipm.send_queue_tail = msg; + msg->next = NULL; + if (msg->prev) + msg->prev->next = msg; + + if (ssh_interceptor_context->ipm.send_queue == NULL) + ssh_interceptor_context->ipm.send_queue = msg; + + if (msg->reliable == 0) + { + ssh_interceptor_context->ipm.send_queue_num_unreliable++; + SSH_ASSERT(ssh_interceptor_context->ipm.send_queue_num_unreliable != 0); + } + + write_unlock(&ssh_interceptor_context->ipm.lock); + local_bh_enable(); + + /* Wake up reader. */ + wake_up_interruptible(&ssh_interceptor_context->ipm_proc_entry.wait_queue); + + return TRUE; +} + + +/**************************** Ipm channel open / close **********************/ + +void interceptor_ipm_open(SshInterceptor interceptor) +{ + + local_bh_disable(); + write_lock(&interceptor->ipm.lock); + + /* Assert that send queue is empty */ + SSH_ASSERT(interceptor->ipm.send_queue == NULL); + + /* Mark ipm channel open */ + atomic_set(&interceptor->ipm.open, 1); + + write_unlock(&interceptor->ipm.lock); + local_bh_enable(); +} + +void interceptor_ipm_close(SshInterceptor interceptor) +{ + SshInterceptorIpmMsg msg, list; + + local_bh_disable(); + write_lock(&interceptor->ipm.lock); + + /* Mark ipm channel closed */ + atomic_set(&interceptor->ipm.open, 0); + + /* Clear send queue */ + list = interceptor->ipm.send_queue; + interceptor->ipm.send_queue = NULL; + interceptor->ipm.send_queue_tail = NULL; + + write_unlock(&interceptor->ipm.lock); + local_bh_enable(); + + /* Free all ipm messages from send queue. */ + while (list != NULL) + { + msg = list; + list = msg->next; + interceptor_ipm_message_free(interceptor, msg); + } +} + + +/***************************** Init / uninit ********************************/ + +Boolean ssh_interceptor_ipm_init(SshInterceptor interceptor) +{ + /* Initialize ipm structure */ + atomic_set(&interceptor->ipm.open, 0); + rwlock_init(&interceptor->ipm.lock); + + /* Initialize /proc interface */ + return ssh_interceptor_proc_init(interceptor); +} + +void ssh_interceptor_ipm_uninit(SshInterceptor interceptor) +{ + /* Uninit /proc interface */ + ssh_interceptor_proc_uninit(interceptor); + + interceptor_ipm_close(interceptor); + + /* Free ipm messages.*/ + interceptor_ipm_message_freelist_uninit(interceptor); +} |