diff options
Diffstat (limited to 'drivers/interceptor/linux_usermode.c')
-rw-r--r-- | drivers/interceptor/linux_usermode.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/drivers/interceptor/linux_usermode.c b/drivers/interceptor/linux_usermode.c new file mode 100644 index 0000000..4a6401d --- /dev/null +++ b/drivers/interceptor/linux_usermode.c @@ -0,0 +1,384 @@ +/* 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_usermode.c + * + */ + +#include "linux_internal.h" +#include "linux_packet_internal.h" + +#define SSH_DEBUG_MODULE "SshInterceptorPacketDstCache" + +extern SshInterceptor ssh_interceptor_context; + +Boolean +ssh_interceptor_dst_entry_cache_init(SshInterceptor interceptor) +{ + SSH_DEBUG(SSH_D_MIDOK, ("Initialising dst entry cache")); + + /* When the IPM is open, we cache dst entries with usermode engine. */ + interceptor->dst_entry_cache_lock = ssh_kernel_mutex_alloc(); + if (interceptor->dst_entry_cache_lock == NULL) + return FALSE; + + interceptor->dst_entry_cache_timeout_registered = FALSE; + memset(interceptor->dst_entry_table, 0x0, + sizeof(SshDstEntry) * SSH_DST_ENTRY_TBL_SIZE); + + interceptor->dst_entry_id = 1; + interceptor->dst_entry_cached_items = 0; + + return TRUE; +} + +/* How long the dst entry can live in the cache. */ +#define DST_ENTRY_MAX_CACHE_TIME 15 +static void +ssh_interceptor_dst_entry_cache_timeout(unsigned long data) +{ + SshInterceptor interceptor = ssh_interceptor_context; + SshUInt32 slot; + SshDstEntry tmp, prev = NULL; + struct timeval tv; + unsigned long time_now; + unsigned long expiry; + + ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); + + SSH_DEBUG(SSH_D_MIDOK, + ("Dst entry cache timeout %lu items in cache", + (unsigned long)interceptor->dst_entry_cached_items)); + SSH_ASSERT(interceptor->dst_entry_cache_timeout_registered == TRUE); + + if (interceptor->dst_entry_cached_items == 0) + { + interceptor->dst_entry_cache_timeout_registered = FALSE; + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); + return; + } + + tv.tv_sec = DST_ENTRY_MAX_CACHE_TIME; + tv.tv_usec = 0; + time_now = jiffies; + expiry = timeval_to_jiffies(&tv); + + for (slot = 0; slot < SSH_DST_ENTRY_TBL_SIZE; slot++) + { + restart: + prev = NULL; + for (tmp = interceptor->dst_entry_table[slot]; + tmp != NULL; + tmp = tmp->next) + { + /* Do we have a match? */ + if ((tmp->allocation_time + expiry) < time_now || + (time_now - tmp->allocation_time) > expiry) + { + /* Head of list. */ + if (tmp == interceptor->dst_entry_table[slot]) + { + SSH_DEBUG(SSH_D_MIDOK, + ("Dst entry cache timeout freeing head ID %lu", + (unsigned long)tmp->dst_entry_id)); + interceptor->dst_entry_table[slot] = tmp->next; + + interceptor->dst_entry_cached_items--; + + dst_release(tmp->dst_entry); + ssh_free(tmp); + + goto restart; + } + + /* Any other place in the list. */ + else + { + prev->next = tmp->next; + + interceptor->dst_entry_cached_items--; + + SSH_DEBUG(SSH_D_MIDOK, + ("Dst entry cache timeout freeing ID %lu", + (unsigned long)tmp->dst_entry_id)); + + dst_release(tmp->dst_entry); + ssh_free(tmp); + + goto restart; + } + } + + prev = tmp; + } + } + + if (interceptor->dst_entry_cached_items > 0) + { + struct timeval tv; + + tv.tv_sec = DST_ENTRY_MAX_CACHE_TIME; + tv.tv_usec = 0; + + interceptor->dst_cache_timer.expires = jiffies + timeval_to_jiffies(&tv); + interceptor->dst_cache_timer.data = (unsigned long)interceptor; + interceptor->dst_cache_timer.function = + ssh_interceptor_dst_entry_cache_timeout; + + mod_timer(&interceptor->dst_cache_timer, + interceptor->dst_cache_timer.expires); + } + else + { + interceptor->dst_entry_cache_timeout_registered = FALSE; + } + + SSH_DEBUG(SSH_D_NICETOKNOW, ("Left %lu items in dst cache", + (unsigned long) + interceptor->dst_entry_cached_items)); + + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); +} + +void +ssh_interceptor_dst_entry_cache_flush(SshInterceptor interceptor) +{ + SshUInt32 slot; + SshDstEntry tmp; + + SSH_DEBUG(SSH_D_MIDOK, ("Dst entry cache flush, %lu items in cache", + (unsigned long)interceptor->dst_entry_cached_items)); + + ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); + if (interceptor->dst_entry_cache_timeout_registered == TRUE) + { + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); + del_timer_sync(&interceptor->dst_cache_timer); + ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); + } + + interceptor->dst_entry_cache_timeout_registered = FALSE; + + /* Free all entries that are left in the table. */ + for (slot = 0; slot < SSH_DST_ENTRY_TBL_SIZE; slot++) + { + tmp = interceptor->dst_entry_table[slot]; + while (tmp != NULL) + { + SshDstEntry next = tmp->next; + interceptor->dst_entry_table[slot] = next; + + interceptor->dst_entry_cached_items--; + + SSH_DEBUG(SSH_D_NICETOKNOW, ("Releasing dst cache entry")); + + dst_release(tmp->dst_entry); + ssh_free(tmp); + + tmp = next; + } + } + + SSH_ASSERT(interceptor->dst_entry_cached_items == 0); + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); +} + +void +ssh_interceptor_dst_entry_cache_uninit(SshInterceptor interceptor) +{ + /* Something failed during initialization. */ + if (interceptor->dst_entry_cache_lock == NULL) + return; + + SSH_DEBUG(SSH_D_MIDOK, ("Dst entry cache uninit, %lu items in cache", + (unsigned long)interceptor->dst_entry_cached_items)); + + ssh_interceptor_dst_entry_cache_flush(interceptor); + ssh_kernel_mutex_uninit(interceptor->dst_entry_cache_lock); + ssh_kernel_mutex_free(interceptor->dst_entry_cache_lock); +} + +/* Cache a dst entry for later purposes. This is required by the + pass unmodified to work. If we lose the dst entry, we basically + cannot return the packet as unmodified to the linux. Return 0 + if the caching fails. If it succeeds, return a valid cache ID. */ +SshUInt32 +ssh_interceptor_packet_cache_dst_entry(SshInterceptor interceptor, + SshInterceptorPacket pp) +{ + SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket)pp; + SshDstEntry cache_dst; + SshDstEntry tmp; + SshUInt32 slot; + + SSH_DEBUG(SSH_D_MIDOK, + ("Dst entry cache, caching dst for pp 0x%p, %lu items in cache", + pp, (unsigned long)interceptor->dst_entry_cached_items)); + + if (ipp->skb == NULL || SSH_SKB_DST(ipp->skb) == NULL) + return 0; + + cache_dst = ssh_calloc(1, sizeof(SshDstEntryStruct)); + if (cache_dst == NULL) + return 0; + + cache_dst->allocation_time = jiffies; + cache_dst->next = NULL; + + cache_dst->dst_entry = SSH_SKB_DST(ipp->skb); + dst_hold(cache_dst->dst_entry); + + ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); + + cache_dst->dst_entry_id = interceptor->dst_entry_id++; + slot = cache_dst->dst_entry_id % SSH_DST_ENTRY_TBL_SIZE; + + interceptor->dst_entry_cached_items++; + + SSH_ASSERT(slot < SSH_DST_ENTRY_TBL_SIZE); + + /* Head of list. */ + if (interceptor->dst_entry_table[slot] == NULL) + { + interceptor->dst_entry_table[slot] = cache_dst; + } + else + { + /* We do not care about potential collisions. These are highly unlikely + to happen and in the end */ + for (tmp = interceptor->dst_entry_table[slot]; + tmp->next != NULL; + tmp = tmp->next) + SSH_ASSERT(cache_dst->dst_entry_id != tmp->dst_entry_id); + + tmp->next = cache_dst; + } + + /* Handle special case, the id is overflowing. 0 is used for special + purposes, i.e. for 'real' engine created packets. */ + if (interceptor->dst_entry_id == 0) + interceptor->dst_entry_id = 1; + + if (interceptor->dst_entry_cache_timeout_registered == FALSE) + { + struct timeval tv; + + SSH_ASSERT(interceptor->dst_entry_cached_items > 0); + + tv.tv_sec = DST_ENTRY_MAX_CACHE_TIME; + tv.tv_usec = 0; + + init_timer(&interceptor->dst_cache_timer); + interceptor->dst_cache_timer.expires = jiffies + timeval_to_jiffies(&tv); + interceptor->dst_cache_timer.data = (unsigned long)interceptor; + interceptor->dst_cache_timer.function = + ssh_interceptor_dst_entry_cache_timeout; + add_timer(&interceptor->dst_cache_timer); + + interceptor->dst_entry_cache_timeout_registered = TRUE; + } + + SSH_DEBUG(SSH_D_NICETOKNOW, ("Cache ID %lu, left %lu items in dst cache", + (unsigned long)cache_dst->dst_entry_id, + (unsigned long) + interceptor->dst_entry_cached_items)); + + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); + + return cache_dst->dst_entry_id; +} + +void +ssh_interceptor_packet_return_dst_entry(SshInterceptor interceptor, + SshUInt32 dst_entry_id, + SshInterceptorPacket pp, + Boolean remove_only) +{ + SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket)pp; + SshUInt32 slot = dst_entry_id % SSH_DST_ENTRY_TBL_SIZE; + SshDstEntry tmp, prev = NULL; + + SSH_DEBUG(SSH_D_MIDOK, + ("Returning dst entry ID %lu, pp 0x%p, %lu items in cache, " + "update %s", + (unsigned long)dst_entry_id, pp, + (unsigned long)interceptor->dst_entry_cached_items, + remove_only == TRUE ? "no" : "yes")); + + /* Special case, 'real' engine created packets. */ + if (dst_entry_id == 0) + return; + + SSH_ASSERT(slot < SSH_DST_ENTRY_TBL_SIZE); + + ssh_kernel_mutex_lock(interceptor->dst_entry_cache_lock); + for (tmp = interceptor->dst_entry_table[slot]; tmp != NULL; tmp = tmp->next) + { + /* Do we have a match? */ + if (tmp->dst_entry_id == dst_entry_id) + { + /* Head of list. */ + if (tmp == interceptor->dst_entry_table[slot]) + { + interceptor->dst_entry_table[slot] = tmp->next; + + interceptor->dst_entry_cached_items--; + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); + + if (remove_only == FALSE && pp != NULL) + SSH_SKB_DST_SET(ipp->skb, tmp->dst_entry); + else + dst_release(tmp->dst_entry); + + ssh_free(tmp); + + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Removed cache ID %lu, left %lu items in dst cache", + (unsigned long)dst_entry_id, + (unsigned long)interceptor->dst_entry_cached_items)); + + return; + } + + /* Any other place in the list. */ + else + { + prev->next = tmp->next; + + interceptor->dst_entry_cached_items--; + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); + + if (remove_only == FALSE) + SSH_SKB_DST_SET(ipp->skb, tmp->dst_entry); + else + dst_release(tmp->dst_entry); + + ssh_free(tmp); + + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Removed cache ID %lu, left %lu items in dst cache", + (unsigned long)dst_entry_id, + (unsigned long)interceptor->dst_entry_cached_items)); + + return; + } + } + + prev = tmp; + } + + SSH_DEBUG(SSH_D_NICETOKNOW, + ("Cache ID %lu was not found, left %lu items in dst cache", + (unsigned long)dst_entry_id, + (unsigned long)interceptor->dst_entry_cached_items)); + + ssh_kernel_mutex_unlock(interceptor->dst_entry_cache_lock); +} |