aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/interceptor
diff options
context:
space:
mode:
authorcodeworkx <codeworkx@cyanogenmod.com>2012-09-17 17:53:57 +0200
committercodeworkx <codeworkx@cyanogenmod.com>2012-09-18 16:31:59 +0200
commitc28265764ec6ad9995eb0c761a376ffc9f141fcd (patch)
tree3ad899757480d47deb2be6011509a4243e8e0dc2 /drivers/interceptor
parent0ddbcb39c0dc0318f68d858f25a96a074142af2f (diff)
downloadkernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.zip
kernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.tar.gz
kernel_samsung_smdk4412-c28265764ec6ad9995eb0c761a376ffc9f141fcd.tar.bz2
applied patches from i9305 jb sources, updated mali to r3p0
Change-Id: Iec4bc4e2fb59e2cf5b4d25568a644d4e3719565e
Diffstat (limited to 'drivers/interceptor')
-rw-r--r--drivers/interceptor/Kconfig3
-rw-r--r--drivers/interceptor/Makefile26
-rw-r--r--drivers/interceptor/engine.h114
-rw-r--r--drivers/interceptor/engine_alloc.h32
-rw-r--r--drivers/interceptor/interceptor.h720
-rw-r--r--drivers/interceptor/kernel_alloc.c124
-rw-r--r--drivers/interceptor/kernel_alloc.h44
-rw-r--r--drivers/interceptor/kernel_encode.c580
-rw-r--r--drivers/interceptor/kernel_includes.h152
-rw-r--r--drivers/interceptor/kernel_mutex.h67
-rw-r--r--drivers/interceptor/linux_hook_magic.c842
-rw-r--r--drivers/interceptor/linux_iface.c863
-rw-r--r--drivers/interceptor/linux_internal.h819
-rw-r--r--drivers/interceptor/linux_ip_glue.c1841
-rw-r--r--drivers/interceptor/linux_ipm.c364
-rw-r--r--drivers/interceptor/linux_kernel_alloc.c182
-rw-r--r--drivers/interceptor/linux_main.c533
-rw-r--r--drivers/interceptor/linux_mutex.c115
-rw-r--r--drivers/interceptor/linux_mutex_internal.h49
-rw-r--r--drivers/interceptor/linux_packet.c846
-rw-r--r--drivers/interceptor/linux_packet_internal.h84
-rw-r--r--drivers/interceptor/linux_params.h84
-rw-r--r--drivers/interceptor/linux_procfs.c800
-rw-r--r--drivers/interceptor/linux_route.c1136
-rw-r--r--drivers/interceptor/linux_usermode.c384
-rw-r--r--drivers/interceptor/linux_versions.h190
-rw-r--r--drivers/interceptor/linux_virtual_adapter.c1073
-rw-r--r--drivers/interceptor/linux_virtual_adapter_internal.h73
-rw-r--r--drivers/interceptor/platform_interceptor.h34
-rw-r--r--drivers/interceptor/platform_kernel_mutex.h54
-rw-r--r--drivers/interceptor/sshconf.h88
-rw-r--r--drivers/interceptor/sshdebug.h175
-rw-r--r--drivers/interceptor/sshencode.h202
-rw-r--r--drivers/interceptor/sshgetput.h108
-rw-r--r--drivers/interceptor/sshincludes.h53
-rw-r--r--drivers/interceptor/sshinet.h233
-rw-r--r--drivers/interceptor/sshinetbits.c45
-rw-r--r--drivers/interceptor/sshinetencode.c130
-rw-r--r--drivers/interceptor/sshinetencode.h43
-rw-r--r--drivers/interceptor/sshinetprint.c136
-rw-r--r--drivers/interceptor/usermodeforwarder.c1363
-rw-r--r--drivers/interceptor/usermodeforwarder.h90
-rw-r--r--drivers/interceptor/virtual_adapter.h296
43 files changed, 15190 insertions, 0 deletions
diff --git a/drivers/interceptor/Kconfig b/drivers/interceptor/Kconfig
new file mode 100644
index 0000000..8d8a30a
--- /dev/null
+++ b/drivers/interceptor/Kconfig
@@ -0,0 +1,3 @@
+config AUTHENTEC_VPNCLIENT_INTERCEPTOR
+ tristate "AuthenTec VPNClient Interceptor"
+ default n
diff --git a/drivers/interceptor/Makefile b/drivers/interceptor/Makefile
new file mode 100644
index 0000000..28c4223
--- /dev/null
+++ b/drivers/interceptor/Makefile
@@ -0,0 +1,26 @@
+obj-$(CONFIG_AUTHENTEC_VPNCLIENT_INTERCEPTOR) := vpnclient.o
+
+vpnclient-y := \
+ kernel_alloc.o \
+ kernel_encode.o \
+ linux_hook_magic.o \
+ linux_iface.o \
+ linux_ip_glue.o \
+ linux_ipm.o \
+ linux_kernel_alloc.o \
+ linux_main.o \
+ linux_usermode.o \
+ linux_virtual_adapter.o \
+ linux_mutex.o \
+ linux_packet.o \
+ linux_procfs.o \
+ linux_route.o \
+ sshinetbits.o \
+ sshinetencode.o \
+ sshinetprint.o \
+ usermodeforwarder.o
+
+override EXTRA_CFLAGS += \
+ -DKERNEL \
+ -D_KERNEL \
+ -DWITH_IPV6
diff --git a/drivers/interceptor/engine.h b/drivers/interceptor/engine.h
new file mode 100644
index 0000000..e1bf35b
--- /dev/null
+++ b/drivers/interceptor/engine.h
@@ -0,0 +1,114 @@
+/* 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.
+ */
+
+/*
+ * engine.h
+ *
+ * Engine API specifies the Engine side interface between the Interceptor
+ * and the Engine components.
+ *
+ */
+
+#ifndef ENGINE_H
+#define ENGINE_H
+
+/******************************** Data types ********************************/
+
+/* Definition of the type for the engine object. */
+typedef struct SshEngineRec *SshEngine;
+
+/* A function of this type is used to send messages from the engine to
+ the policy manager. The function should return TRUE if the message
+ was actually sent, and FALSE otherwise. This should always
+ eventually free `data' with ssh_free. The packet in the buffer
+ starts with a 32-bit length MSB first. If the connection to the
+ policy manager is not open, this should return FALSE and free
+ `data' using ssh_free. Warning: this function is called from
+ ssh_debug and ssh_warning; thus, this is not allowed to emit
+ debugging or warning messages. This function can be called
+ concurrently, and must perform appropriate locking. */
+typedef Boolean (*SshEngineSendProc)(unsigned char *data, size_t len,
+ Boolean reliable,
+ void *machine_context);
+
+/***************************************************************************
+ * Functions called by the machine-dependent main program
+ ***************************************************************************/
+
+/* Flags for the ssh_engine_start function. */
+#define SSH_ENGINE_DROP_IF_NO_IPM 0x00000001
+#define SSH_ENGINE_NO_FORWARDING 0x00000002
+
+/* Creates the engine object. Among other things, this opens the
+ interceptor, initializes filters to default values, and arranges to send
+ messages to the policy manager using the send procedure. The send
+ procedure will not be called until from the bottom of the event loop.
+ The `machine_context' argument is passed to the interceptor and the
+ `send' callback, but is not used otherwise. This function can be
+ called concurrently for different machine contexts, but not otherwise.
+ The first packet and interface callbacks may arrive before this has
+ returned. */
+SshEngine ssh_engine_start(SshEngineSendProc send,
+ void *machine_context,
+ SshUInt32 flags);
+
+/* Stops the engine, closes the interceptor, and destroys the
+ engine object. This does not notify IPM interface of the close;
+ that must be done by the caller before calling this. This returns
+ TRUE if the engine was successfully stopped (and the object freed),
+ and FALSE if the engine cannot yet be freed because there are
+ threads inside the engine or uncancellable callbacks expected to
+ arrive. When this returns FALSE, the engine has started stopping,
+ and this should be called again after a while. This function can
+ be called concurrently with packet/interface callbacks or timeouts
+ for this engine, or any functions for other engines.*/
+Boolean ssh_engine_stop(SshEngine engine);
+
+/* The machine-specific main program should call this when the policy
+ manager has opened the connection to the engine. This also
+ sends the version packet to the policy manager. This function can
+ be called concurrently with packet/interface callbacks or timeouts. */
+void ssh_engine_notify_ipm_open(SshEngine engine);
+
+/* This function is called whenever the policy manager closes the
+ connection to the engine. This is also called when the engine is
+ stopped. This function can be called concurrently with
+ packet/interface callbacks or timeouts. */
+void ssh_engine_notify_ipm_close(SshEngine engine);
+
+/* This function should be called by the machine-dependent main
+ program whenever a packet for this engine is received from
+ the policy manager. The data should not contain the 32-bit length
+ or the type (they have already been processed at this stage, to
+ check for possible machine-specific packets). The `data' argument
+ remains valid until this function returns; it should not be freed
+ by this function. This function can be called concurrently. */
+void ssh_engine_packet_from_ipm(SshEngine engine,
+ SshUInt32 type,
+ const unsigned char *data, size_t len);
+
+/******************************** Version global ****************************/
+
+/* This is statically (compile-time) initialized to SSH_ENGINE_VERSION */
+extern const char ssh_engine_version[];
+
+/* This is statically (compile-time) initialized to a value containing
+ information about the SSH_ENGINE_VERSION, compilation time,
+ compiler etc. etc. etc. It can be used by interceptors, usermode
+ engine etc. for startup output or somesuch. Debug information,
+ basically, and can vary quite much depending on the compilation
+ environment. */
+extern const char ssh_engine_compile_version[];
+
+/* Suffix to append to the device name. This is defined by the
+ engine. */
+extern const char ssh_device_suffix[];
+
+#endif /* ENGINE_H */
diff --git a/drivers/interceptor/engine_alloc.h b/drivers/interceptor/engine_alloc.h
new file mode 100644
index 0000000..f4a11c7
--- /dev/null
+++ b/drivers/interceptor/engine_alloc.h
@@ -0,0 +1,32 @@
+/* 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.
+ */
+
+/*
+ * engine_alloc.h
+ *
+ * Engine memory allocation API.
+ *
+ */
+
+#ifndef ENGINE_ALLOC_H
+#define ENGINE_ALLOC_H
+
+void *ssh_malloc(size_t size);
+void *ssh_malloc_flags(size_t size, SshUInt32 flags);
+void *ssh_realloc(void *ptr, size_t old_size, size_t new_size);
+void *ssh_realloc_flags(void *ptr, size_t old_size, size_t new_size,
+ SshUInt32 flags);
+void *ssh_calloc(size_t nitems, size_t size);
+void *ssh_calloc_flags(size_t nitems, size_t size, SshUInt32 flags);
+void *ssh_strdup(const void *p);
+void *ssh_memdup(const void *p, size_t len);
+void ssh_free(void *ptr);
+
+#endif /* ENGINE_ALLOC_H */
diff --git a/drivers/interceptor/interceptor.h b/drivers/interceptor/interceptor.h
new file mode 100644
index 0000000..054daf1
--- /dev/null
+++ b/drivers/interceptor/interceptor.h
@@ -0,0 +1,720 @@
+/* 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.
+ */
+
+/*
+ * interceptor.h
+ *
+ * Interceptor API specifies the Interceptor side interface between
+ * the Interceptor and Engine components. This API contains functions
+ * for Interceptor initialization, packet allocation, packet data access,
+ * routing and packet sending.
+ *
+ */
+
+#ifndef INTERCEPTOR_H
+#define INTERCEPTOR_H
+
+#include "sshinet.h"
+
+/** The amount of space to reserve in packet header for the IPsec engine. */
+#define SSH_INTERCEPTOR_UPPER_DATA_SIZE 192
+
+/** The number of available extension selectors for platform-specific
+ extensions. */
+#define SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS 1
+
+/** Size of interface system name. */
+#define SSH_INTERCEPTOR_IFNAME_SIZE 64
+
+/** Data type for the interceptor context. */
+typedef struct SshInterceptorRec *SshInterceptor;
+
+/** This type defines the type of a network interface interface. The
+ type is supposed to be sufficient to determine how to process the
+ packets. It is not expected to provide maximal detail to the
+ user. Regardless of the interface type, the higher-level code
+ should not assume that incoming packets have been defragmented,
+ nor will it assume that the interceptor will perform fragmentation
+ for outgoing packets. Thus, it will perform these functions
+ itself. It will not assume the underlying interceptor will do it.
+ Routing will also be done by the higher level to determine which
+ interface a packet should go out from. */
+typedef enum {
+ SSH_INTERCEPTOR_MEDIA_NONEXISTENT, /** The interface is not available */
+ SSH_INTERCEPTOR_MEDIA_PLAIN, /** Any interceptor w/o media headers*/
+ SSH_INTERCEPTOR_MEDIA_ETHERNET, /** Ethernet (rfc894) framing */
+ SSH_INTERCEPTOR_MEDIA_FDDI, /** FDDI (rfc1042/rfc1103) framing */
+ SSH_INTERCEPTOR_MEDIA_TOKENRING, /** Tokenring (rfc1042/rfc1469) framing */
+ /** New types may be added here. Look for one of the existing types
+ to find all places in code that should be updated. */
+ SSH_INTERCEPTOR_NUM_MEDIAS /** Must be the last entry! */
+} SshInterceptorMedia;
+
+/** Protocol identifiers. These identify recognized protocols (packet
+ formats) in a portable manner. This enumeration includes media
+ types, but also all recognized higher-level protocols. */
+typedef enum
+{
+ SSH_PROTOCOL_IP4, /** IPv4 frame */
+ SSH_PROTOCOL_IP6, /** IPv6 frame */
+ SSH_PROTOCOL_IPX, /** IPX frame */
+ SSH_PROTOCOL_ETHERNET, /** Ethernet frame */
+ SSH_PROTOCOL_FDDI, /** FDDI frame */
+ SSH_PROTOCOL_TOKENRING, /** Token Ring frame */
+ SSH_PROTOCOL_ARP, /** ARP frame */
+ SSH_PROTOCOL_OTHER, /** some other type frame */
+ SSH_PROTOCOL_NUM_PROTOCOLS /** must be the last entry! */
+} SshInterceptorProtocol;
+
+/** Data type for an interface number. This type must be atleast as big as
+ the system interface index. */
+typedef SshUInt32 SshInterceptorIfnum;
+
+/** Maximum value of interface number.
+ All valid interface numbers must be smaller than this value. */
+#define SSH_INTERCEPTOR_MAX_IFNUM ((SshInterceptorIfnum) 0xffffffff)
+
+/** Reserved value for invalid interface number. */
+#define SSH_INTERCEPTOR_INVALID_IFNUM SSH_INTERCEPTOR_MAX_IFNUM
+
+/** Data structure for representing an address for a network interface. */
+typedef struct SshInterfaceAddressRec
+{
+ /** Protocol for which the address is. */
+ SshInterceptorProtocol protocol;
+
+ /** The address itself. */
+ union
+ {
+ /** IPv4 and IPv6. */
+ struct
+ {
+ SshIpAddrStruct ip;
+ SshIpAddrStruct mask;
+ SshIpAddrStruct broadcast;
+ } ip;
+
+ /** IPX */
+ struct
+ {
+ SshUInt32 net;
+ unsigned char host[6];
+ } ns;
+ } addr;
+} *SshInterfaceAddress, SshInterfaceAddressStruct;
+
+/** Flags for the media direction information. */
+
+/** Do not fragment before sending from engine. */
+#define SSH_INTERCEPTOR_MEDIA_INFO_NO_FRAGMENT 0x0001
+
+/** Accessor for mtu member in SshInterceptorMediaDirectionInfo. */
+#ifdef WITH_IPV6
+#define SSH_INTERCEPTOR_MEDIA_INFO_MTU(info, is_ipv6) \
+ ((is_ipv6) ? (info)->mtu_ipv6 : (info)->mtu_ipv4)
+#else /* WITH_IPV6 */
+#define SSH_INTERCEPTOR_MEDIA_INFO_MTU(info, is_ipv6) \
+ ((info)->mtu_ipv4)
+#endif /* WITH_IPV6 */
+
+/** Media direction information. This information is used when engine
+ is sending packets to the interceptor. */
+typedef struct SshInterceptorMediaDirectionInfoRec
+{
+ SshInterceptorMedia media; /* media type */
+ SshUInt32 flags; /* flags */
+ size_t mtu_ipv4; /* mtu for the direction (ipv4) */
+#ifdef WITH_IPV6
+ size_t mtu_ipv6; /* mtu for the direction (ipv6) */
+#endif /* WITH_IPV6 */
+} *SshInterceptorMediaDirectionInfo, SshInterceptorMediaDirectionInfoStruct;
+
+/** Flag values for flags in SshInterceptorInterface */
+/* Interface type */
+#define SSH_INTERFACE_FLAG_VIP 0x0001
+#define SSH_INTERFACE_FLAG_POINTOPOINT 0x0002
+#define SSH_INTERFACE_FLAG_BROADCAST 0x0004
+/* Interface link status */
+#define SSH_INTERFACE_FLAG_LINK_DOWN 0x0100
+
+/** Data structure for providing information about a network
+ interface. */
+typedef struct
+{
+ SshInterceptorMediaDirectionInfoStruct to_protocol;
+ SshInterceptorMediaDirectionInfoStruct to_adapter;
+ char name[SSH_INTERCEPTOR_IFNAME_SIZE]; /** system name for the interface */
+ SshInterceptorIfnum ifnum; /** Interface number */
+ SshUInt32 num_addrs; /** Number of addresses for the interface. */
+ SshInterfaceAddress addrs; /** mallocated array of address structures. */
+ unsigned char media_addr[16]; /** MAC address, medium size and format */
+ size_t media_addr_len; /** Length of the MAC address. */
+
+ SshUInt32 flags; /** Flags for the interface. */
+
+ /** Context pointer for owner/user for this SshInterceptorInterface.
+ Can be used for e.g. storing interface-specific
+ NAT-configuration. */
+ void *ctx_user;
+} SshInterceptorInterface;
+
+/* Packet flag bits. */
+#define SSH_PACKET_FROMPROTOCOL 0x00000001U /** Packet from the protocol. */
+#define SSH_PACKET_FROMADAPTER 0x00000002U /** Packet from the adapter. */
+#define SSH_PACKET_IP4HDRCKSUMOK 0x00000004U /** IPv4 header cksum checked. */
+#define SSH_PACKET_FORWARDED 0x00000008U /** Packet was forwarded. */
+#define SSH_PACKET_HWCKSUM 0x00000010U /** TCP/UDP cksum done by NIC. */
+#define SSH_PACKET_MEDIABCAST 0x00000020U /** Packet was media broadcast. */
+#define SSH_PACKET_UNMODIFIED 0x00000200U /** Unmodified packet. */
+
+/* This flag specifies that the engine is allowed to fragment the packet if
+ the packet is too large to fit into interafce MTU. Some operating
+ system handle the fragmentation after us and therefore this flag
+ may be set for some outbound data packets. The packet is guaranteed
+ to have been originated from local stack and stack has indicated
+ that this packet can be fragmented. */
+#define SSH_PACKET_FRAGMENTATION_ALLOWED 0x00000400U
+
+/* Flag bits with mask 0x00000fff are reserved for interceptor. */
+/* Flag bits with mask 0xfffff000 are reserved for IPSEC engine. */
+
+/** Macro to access upper-level data in the packet header. */
+#define SSH_INTERCEPTOR_PACKET_DATA(packet, type) \
+ ((type)(&(packet)->upper_data))
+
+/** Data structure for a packet. These data structures can only be
+ allocated by the interceptor; higher-level code must never
+ directly allocate these (the interceptor implementation may
+ actually use a larger structure containing this public
+ structure). */
+typedef struct SshInterceptorPacketRec
+{
+ /** Flags for the packet. The SSH_PACKET_* bitmasks are used. Code
+ above the interceptor is not allowed to modify flags 0x001-0x800;
+ they may be used internally by the interceptor to pass
+ information from packet_cb/ssh_interceptor_packet_alloc to
+ ssh_interceptor_send/ssh_interceptor_packet_free. During
+ certain times, such when applying asynchronous IPSEC
+ transformations, this field may be changed concurrently by
+ another thread (or interrupt) even when another thread is
+ processing the packet. Care should be taken with locking in
+ those situations. */
+ SshUInt32 flags;
+
+ /** Number of the interface that this packet arrived from */
+ SshInterceptorIfnum ifnum_in;
+
+ /** Number of the interface that this packet going out */
+ SshInterceptorIfnum ifnum_out;
+
+ /** Format of the packet (protocol identifier). */
+ SshInterceptorProtocol protocol;
+
+ /** Path MTU stored for this packet, which must be respected at
+ media send (if interface MTU is smaller than this value, then
+ the media send routine must send ICMP PMTU message and discard
+ the packet). If 0, means use the interface MTU only. */
+ SshUInt32 pmtu;
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ /** Platform-dependent extension selectors for things like user id,
+ virtual network identifier, etc. */
+ SshUInt32 extension[SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS];
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /** Route key selector that was used in the route lookup */
+ SshUInt16 route_selector;
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /** Buffer that can be used by higher level software to store its
+ data (such as cached media addresses). This field can only be
+ accessed using the SSH_INTERCEPTOR_PACKET_DATA macro. */
+ union {
+ /** Contents of this union are private (they are there to
+ guarantee proper alignment for any data structure stored
+ here). */
+ long force_alignment_l; int force_alignment_i; short force_alignment_s;
+ void *force_alignment_v;
+ SshUInt16 force_alignment_i16; SshUInt32 force_alignment_i32;
+ SshUInt64 force_alignment_i64;
+ char force_size_dont_use_this_directly[SSH_INTERCEPTOR_UPPER_DATA_SIZE];
+ /* Should have double here if floating point was allowed. */
+ } upper_data;
+
+ /** Next pointer on freelist. This can also be used by higher-level
+ code to put the packet on a list. */
+ struct SshInterceptorPacketRec *next;
+} *SshInterceptorPacket, SshInterceptorPacketStruct;
+
+/** Error codes for route add / remove functions. */
+typedef enum {
+ SSH_INTERCEPTOR_ROUTE_ERROR_OK = 0,
+ SSH_INTERCEPTOR_ROUTE_ERROR_NONEXISTENT = 1,
+ SSH_INTERCEPTOR_ROUTE_ERROR_OUT_OF_MEMORY = 2,
+ SSH_INTERCEPTOR_ROUTE_ERROR_ALREADY_EXISTS = 3,
+ SSH_INTERCEPTOR_ROUTE_ERROR_UNDEFINED = 255
+} SshInterceptorRouteError;
+
+/** Flag values for the route add / remove functions. */
+
+/** Ignore non-existent routes when attempting to remove the route. */
+#define SSH_INTERCEPTOR_ROUTE_FLAG_IGNORE_NONEXISTENT 0x0001
+
+/** Data structure for routing key, used in route lookups and routing table
+ manipulation.
+
+ The route lookup is performed for the destination address, using other
+ fields in the routing key to enforce routing policies. It is a fatal
+ error to call ssh_interceptor_route using a SshInterceptorRouteKey
+ which does not have the destination address set.
+
+ Use the provided macros for setting fields in SshInterceptorRouteKey.
+
+ Note that on platforms that do not support policy routing, the route lookup
+ uses only the destination address. On other platforms other fields of the
+ SshInterceptorRouteKey may be used in the route lookup. */
+typedef struct SshInterceptorRouteKeyRec
+{
+ /** Destination address, mandatory */
+ SshIpAddrStruct dst;
+ /** Source address, optional */
+ SshIpAddrStruct src;
+ /** IP protocol identifier, optional */
+ SshUInt32 ipproto;
+ /** Interface number, optional.
+ Note that this field specifies either the inbound interface number
+ or the outbound interface number, depending on the value of the
+ 'selector' field. */
+ SshUInt32 ifnum;
+ /** Network layer fields */
+ union
+ {
+ /** IPv4 TOS, optional */
+ struct
+ {
+ SshUInt8 tos;
+ } ip4;
+ /** IPv6 priority and flow label, optional */
+ struct
+ {
+ SshUInt8 priority;
+ SshUInt32 flow;
+ } ip6;
+ /** For encoding / decoding */
+ unsigned char raw[5];
+ } nh;
+ /** Transport layer fields */
+ union
+ {
+ /** TCP / UDP ports, optional */
+ struct
+ {
+ SshUInt16 dst_port;
+ SshUInt16 src_port;
+ } tcp;
+ /** ICMP type and code, optional */
+ struct
+ {
+ SshUInt8 type;
+ SshUInt8 code;
+ } icmp;
+ /** ESP / AH spi, optional */
+ struct
+ {
+ SshUInt32 spi;
+ } ipsec;
+ /** For encoding / decoding */
+ unsigned char raw[4];
+ } th;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ /** Platform-dependent extension selectors, optional */
+ SshUInt32 extension[SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS];
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ /** Bitmap of selectors that are to be used in the route lookup.
+ Use the provided macros to add selectors to the routing key,
+ do not access this field directly. The highest 3 bits of
+ 'selector' are reserved for flags defined below. */
+ SshUInt16 selector;
+} *SshInterceptorRouteKey, SshInterceptorRouteKeyStruct;
+
+/* Selector values for 'selector' bitmap */
+
+/** Source address */
+#define SSH_INTERCEPTOR_ROUTE_KEY_SRC 0x0001
+/** IP protocol identifier */
+#define SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO 0x0002
+/** Inbound interface number */
+#define SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM 0x0004
+/** Outbound interface number */
+#define SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM 0x0008
+/** IPv4 type of service */
+#define SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS 0x0010
+/** Platform-dependent Extension selectors */
+#define SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION 0x1000
+
+/* Flag values for 'selector' bitmap */
+
+/** Packets going to this destination are transformed by the engine and the
+ resulting packet may be larger than the path MTU reported to the IP stack
+ by the engine. */
+#define SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED 0x2000
+/** Source address belongs to one of the local interfaces. */
+#define SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC 0x4000
+
+/** A callback function of this type will be called when the
+ interceptor is first opened, and from then on whenever there is a
+ change in the interface list (e.g., a new interface goes up or
+ down or the address of an interface changes). This function will
+ be given a list of the interfaces. The supplied array will only
+ be valid for the duration of this call; the implementation of this
+ function is supposed to copy the information if it is going to
+ need it later. Note that this function may be called
+ asynchronously, concurrently with any other functions. */
+typedef void (*SshInterceptorInterfacesCB)(SshUInt32 num_interfaces,
+ SshInterceptorInterface *ifs,
+ void *context);
+
+/** Callback functions of this type are called whenever a packet is
+ received from the network or from a protocol. This function must
+ eventually free the packet, either by calling
+ ssh_interceptor_packet_free on the packet or by passing it to the
+ ssh_interceptor_send function. Note that this function may be
+ called asynchronously, concurrently with any other functions.
+
+ When a packet is passed to this callback, the `pp->flags' field
+ may contain arbitrary flags in the bits reserved for the
+ interceptor (mask 0x00000fff). This callback is not allowed to
+ modify any of those bits; they must be passed intact to
+ ssh_interceptor_send or ssh_interceptor_packet_free. Any other
+ bits (mask 0xfffff000) will be zero when the packet is sent to
+ this callback; those bits may be used freely by this callback.
+ They are not used by the interceptor. */
+typedef void (*SshInterceptorPacketCB)(SshInterceptorPacket pp, void *context);
+
+/** A callback function of this type is used to notify code above the
+ interceptor that routing information has changed, and any cached
+ routing data should be thrown away and refreshed by a new route
+ lookup. */
+typedef void (*SshInterceptorRouteChangeCB)(void *context);
+
+/** 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 `machine_context' argument is intended to be meaningful only
+ to machine-specific code. It is passed through from the
+ machine-specific main program. One example of its possible uses
+ is to identify a virtual router in systems that implement multiple
+ virtual routers in a single software environment. Most
+ implementations will ignore this argument.
+
+ The `packet_cb' callback will be called whenever a packet is
+ received from either a network adapter or a protocol stack. The
+ first calls may arrive already before this function has returned.
+
+ The `interfaces_cb' callback will be called once soon after
+ opening the interceptor (possibly before this call returns). 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 `route_change_cb' callback should be called whenever there is
+ a change in routing information. Implementing this callback is
+ optional but beneficial in e.g. router environments (the
+ information is not easily available on all systems).
+
+ The `context' argument is passed to the callbacks.
+
+ This function returns TRUE if opening the interceptor was
+ successful. The interceptor object is returned in the
+ `interceptor_return' argument. Most systems will only allow a
+ single interceptor to be opened; however, some systems may support
+ multiple interceptors identified by the `machine_context'
+ argument. This returns FALSE if an error occurs (e.g., no
+ interceptor kernel module is loaded on this system, or the
+ interceptor is already open).
+
+ Care must be taken regarding concurrency control in systems that have
+ multithreaded IP stacks. In particular:
+ - packet_cb and interfaces_cb may get called before this function
+ returns. It is, however, guaranteed that `*interceptor_return'
+ has been set before the first call to either of them.
+ - the interceptor cannot be closed while there are calls or packets
+ out. The ssh_interceptor_stop function must be used.
+ In such systems, additional concurrency may be introduced by timeouts
+ and actions from the policy manager connection. */
+Boolean ssh_interceptor_open(void *machine_context,
+ SshInterceptorPacketCB packet_cb,
+ SshInterceptorInterfacesCB interfaces_cb,
+ SshInterceptorRouteChangeCB route_change_cb,
+ void *context,
+ SshInterceptor *interceptor_return);
+
+/** Sends a packet to the network or to the protocol stacks. This
+ will eventually free the packet by calling
+ ssh_interceptor_packet_free. The `media_header_len' argument
+ specifies how many bytes from the start of the packet are media
+ (link-level) headers. It will be 0 if the interceptor operates
+ directly at protocol level. If the configure define
+ INTERCEPTOR_IP_ALIGNS_PACKETS is set, this function must ensure
+ that the IP header of the packet is aligned to a word boundary.
+
+ This function relies on 'pp->ifnum_out' being an 'ifnum' which has
+ previously been reported via a SshInterceptorInterfacesCB. It does
+ not have to be valid at that precise point in time. If 'pp->ifnum_out'
+ is an ifnum which may have been previously reported to the
+ SshInterceptorInterfacesCB, then ssh_interceptor_send() MUST check
+ that it is valid, or otherwise discard the packet. Also,
+ pp->protocol (and the corresponding encapsulation) may be
+ incorrect for the interface denoted by 'pp->ifnum_out'. The packet
+ should be dropped also in this case.
+
+ This function can be called concurrently from multiple threads,
+ but only for one packet at a time. It is ok to call this even
+ before ssh_interceptor_open has actually returned (from a
+ `packet_cb' or `interface_cb' callback). */
+
+void ssh_interceptor_send(SshInterceptor interceptor,
+ SshInterceptorPacket pp,
+ size_t media_header_len);
+
+/** Enables or disables packet interception. */
+void ssh_interceptor_enable_interception(SshInterceptor interceptor,
+ Boolean enable);
+
+/** 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 call ssh_interceptor_close. It is
+ not an error to call this multiple times (the latter calls are
+ ignored).
+
+ It is forbidden to hold ANY locks when calling
+ ssh_interceptor_stop(). */
+
+Boolean ssh_interceptor_stop(SshInterceptor interceptor);
+
+/** Closes the packet interceptor. This function can only be called when
+ - ssh_interceptor_stop has been called
+ - all packet and interface callbacks from this interceptor have
+ returned.
+ - all ssh_interceptor_route completions have been called
+ - all packets received from the packet callbacks from this interceptor
+ have been freed.
+
+ It is illegal to call any packet interceptor functions (other than
+ ssh_interceptor_open) after this call. This function cannot be
+ called from an interceptor callback.
+
+ This function can be called from one thread only for any particular
+ interceptor. If multiple interceptors are supported, then this may be
+ called for different interceptors asynchronously. */
+void ssh_interceptor_close(SshInterceptor interceptor);
+
+/** Completion function for route lookup. This function is called when the
+ route lookup is complete.
+ reachable FALSE if the destination cannot be reached, TRUE otherwise
+ next_hop_gw IP address of next hop gw or destination
+ ifnum network interface to which to send the packet
+ mtu path mtu, or 0 if not known (= should use link mtu)
+ context context argument supplied to route request.
+
+ This function may get called concurrently from multiple threads. */
+typedef void (*SshInterceptorRouteCompletion)(Boolean reachable,
+ SshIpAddr next_hop_gw,
+ SshInterceptorIfnum ifnum,
+ size_t mtu,
+ void *context);
+
+/** Looks up routing information for the routing key specified
+ by `key'. Calls the callback function either during this
+ call or some time later. The purpose of the callback function is
+ to allow this function to perform asynchronous operations, such as
+ forwarding the routing request to a user-level process. This
+ function will not be very efficient on some systems, and calling
+ this on a per-packet basis should be avoided if possible.
+ This function expects that 'key' is valid only for the duration
+ of the call, and will take a local copy of it, if necessary.
+
+ Note that if this function is implemented by forwarding the
+ request to a user-level process, care must be taken to never lose
+ replies. The completion function must *always* be called. For
+ example, if the policy manager interface is used to pass the
+ requests to the policy manager process, and the interface is
+ closed, the completion function must still be called for all
+ requests. Code may be needed to keep track of which requests are
+ waiting for replies from the user-level process.
+
+ This function can be called concurrently from multiple threads.
+ While legal, new calls to this function after calling
+ ssh_interceptor_stop should be avoided, because it is not possible
+ to call ssh_interceptor_close until all route lookup completions
+ have been called. */
+void ssh_interceptor_route(SshInterceptor interceptor,
+ SshInterceptorRouteKey key,
+ SshInterceptorRouteCompletion completion,
+ void *context);
+
+/** Allocates a packet of at least the given size. Packets can only
+ be allocated using this function (either internally by the
+ interceptor or by other code by calling this function).
+ Typically, this takes a packet header from a free list, stores a
+ pointer to a platform-specific packet object, and returns the
+ packet header. This should be re-entrant and support concurrent
+ operations if the IPSEC engine is re-entrant on the target
+ platform. Other functions in this interface should be re-entrant
+ for different packet objects, but only one operation will be in
+ progress at any given time for a single packet object. This
+ returns NULL if no more packets can be allocated. On systems that
+ support concurrency, this can be called from multiple threads
+ concurrently.
+
+ This sets initial values for the mandatory fields of the packet
+ that always need to be initialized. However, any of these fields
+ can be modified later. */
+SshInterceptorPacket ssh_interceptor_packet_alloc(SshInterceptor interceptor,
+ SshUInt32 flags,
+ SshInterceptorProtocol proto,
+ SshInterceptorIfnum ifnum_in,
+ SshInterceptorIfnum ifnum_out,
+ size_t total_len);
+
+
+/** Frees the packet.
+
+ All packets allocated by ssh_interceptor_packet_alloc must
+ eventually be freed using this function by either calling this
+ explicitly or by passing the packet to the ssh_interceptor_send
+ function. Typically, this calls a suitable function to
+ free/release the platform-specific packet object, and puts the
+ packet header on a free list. This function should be re-entrant,
+ so if a free list is used, it should be protected by a lock in
+ systems that implement concurrency in the IPSEC Engine. Multiple
+ threads may call this function concurrently for different packets,
+ but not for the same packet. */
+void ssh_interceptor_packet_free(SshInterceptorPacket pp);
+
+/** Returns the total length of the packet in bytes. Multiple threads may
+ call this function concurrently, but not for the same packet. */
+size_t ssh_interceptor_packet_len(SshInterceptorPacket pp);
+
+
+/** Copies data into the packet. Space for the new data must already
+ have been allocated. It is a fatal error to attempt to copy beyond
+ the allocated packet. Multiple threads may call this function
+ concurrently, but not for the same packet. Returns TRUE if
+ successfull and FALSE otherwise. If error occurs then the pp is
+ already freed by this function, and the caller must not refer to
+ it anymore.
+
+ There is a generic version of this function inside the engine, in
+ case interceptor does not want to implement this. If interceptor
+ implements this function it must define the
+ INTERCEPTOR_HAS_PACKET_COPYIN pre-processor symbol. */
+Boolean ssh_interceptor_packet_copyin(SshInterceptorPacket pp, size_t offset,
+ const unsigned char *buf, size_t len);
+
+/** Copies data out from the packet. Space for the new data must
+ already have been allocated. It is a fatal error to attempt to
+ copy beyond the allocated packet. Multiple threads may call this
+ function concurrently, but not for the same packet.
+
+ There is a generic version of this function inside the engine, in
+ case interceptor does not want to implement this. If interceptor
+ implements this function it must define the
+ INTERCEPTOR_HAS_PACKET_COPYOUT pre-processor symbol. */
+void ssh_interceptor_packet_copyout(SshInterceptorPacket pp, size_t offset,
+ unsigned char *buf, size_t len);
+
+
+/** These two routines provide way to export and import
+ interceptor-specific internal packet data as an opaque binary data
+ block.
+
+ If the export routine returns FALSE, the packet `pp' is
+ invalidated. If it returnes TRUE, but *data_ret is NULL, then no
+ internal data was exported. If *data_ret is non-NULL, then
+ *len_return contains the length of *data_ret in bytes. The caller
+ must free the *data_ret value using ssh_free.
+
+ The import routine returns TRUE if the data was imported
+ successfully, otherwise it returns FALSE and the packet `pp' is
+ invalidated. It is a fatal error to call import routine on the
+ same packet more than once.
+
+ Notice: If the interceptor does not define these routines, then
+ the engine provides dummy versions.
+
+ Notice: This routine overlaps a bit with
+ ssh_interceptor_packet_alloc_and_copy_ext_data, as it could be
+ implemented as:
+
+ new_pp = ssh_interceptor_packet_alloc(...);
+ ssh_interceptor_packet_copy(pp, 0, ..., new_pp, 0);
+ ssh_interceptor_packet_export_internal_data(pp, &data, &len);
+ ssh_interceptor_packet_import_internal_data(new_pp, data, len);
+ ssh_free(data);
+
+ The main purpose of these routines is to allow some per-packet
+ interceptor-specific data to be transported to the usermode
+ engine. Under the kernel IPSec Engine, these routines are not
+ actually used at all. */
+Boolean ssh_interceptor_packet_export_internal_data(SshInterceptorPacket pp,
+ unsigned char **data_ret,
+ size_t *len_return);
+
+Boolean ssh_interceptor_packet_import_internal_data(SshInterceptorPacket pp,
+ const unsigned char *data,
+ size_t len);
+
+void ssh_interceptor_packet_discard_internal_data(unsigned char *data,
+ size_t data_len);
+
+
+/** Copy data from one packet to another. Start from the
+ `source_offset' and copy `bytes_to_copy' bytes to
+ `destination_offset' in the destination packet. If the destination
+ packet cannot be written then return FALSE, and the destination
+ packet has been freed by this function. The source packet is not
+ freed even in case of error. If data copying was successfull then
+ return TRUE.
+
+ This function can also be implemented so that it will simply
+ increment the reference counts in the source packet and share the
+ actual data without copying it at all. There is a generic version
+ of this function inside the engine, in case interceptor does not
+ want to implement this. If interceptor implements this function it
+ must define INTERCEPTOR_HAS_PACKET_COPY pre-processor symbol. */
+Boolean ssh_interceptor_packet_copy(SshInterceptorPacket source_pp,
+ size_t source_offset,
+ size_t bytes_to_copy,
+ SshInterceptorPacket destination_pp,
+ size_t destination_offset);
+
+#ifdef DEBUG_LIGHT
+#define KERNEL_INTERCEPTOR_USE_FUNCTIONS
+#endif /* DEBUG_LIGHT */
+
+#ifdef INTERCEPTOR_HAS_PLATFORM_INCLUDE
+#include "platform_interceptor.h"
+#endif /* INTERCEPTOR_HAS_PLATFORM_INCLUDE */
+
+#endif /* INTERCEPTOR_H */
diff --git a/drivers/interceptor/kernel_alloc.c b/drivers/interceptor/kernel_alloc.c
new file mode 100644
index 0000000..21af9ba
--- /dev/null
+++ b/drivers/interceptor/kernel_alloc.c
@@ -0,0 +1,124 @@
+/* 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.
+ */
+
+/*
+ * kernel_alloc.
+ *
+ * Engine memory allocation API implementation for kernel space.
+ *
+ */
+
+#include "sshincludes.h"
+#include "kernel_alloc.h"
+#include "kernel_mutex.h"
+
+void *
+ssh_malloc_flags(size_t size, SshUInt32 flags)
+{
+ return ssh_kernel_alloc(size, flags);
+}
+
+void *
+ssh_malloc(size_t size)
+{
+ return ssh_malloc_flags(size, SSH_KERNEL_ALLOC_NOWAIT);
+}
+
+void *
+ssh_realloc_flags(void *oldptr, size_t oldsize, size_t newsize,
+ SshUInt32 flags)
+{
+ void * newptr;
+
+ if (oldptr == NULL)
+ return ssh_kernel_alloc(newsize, flags);
+
+ if (newsize <= oldsize)
+ return oldptr;
+
+ if ((newptr = ssh_kernel_alloc(newsize, flags)) == NULL)
+ return NULL;
+
+ /* newsize > oldsize, see above */
+ if (oldsize > 0)
+ memcpy(newptr, oldptr, oldsize);
+
+ /* Success, thus we can release the old memory */
+ ssh_kernel_free(oldptr);
+
+ return newptr;
+}
+
+void *
+ssh_realloc(void * oldptr, size_t oldsize, size_t newsize)
+{
+ return ssh_realloc_flags(oldptr, oldsize, newsize, SSH_KERNEL_ALLOC_NOWAIT);
+}
+
+/* coverity[ -tainted_data_sink : arg-0 ] */
+void ssh_free (void * ptr)
+{
+ if (ptr != NULL)
+ ssh_kernel_free(ptr);
+}
+
+void*
+ssh_calloc_flags (size_t nitems, size_t isize, SshUInt32 flags)
+{
+ void * ptr;
+ unsigned long size;
+
+ size = isize * nitems;
+
+ if ((ptr = ssh_malloc_flags(size ? size : 1, flags)) == NULL)
+ return NULL;
+
+ if (size > 0)
+ memset(ptr, 0, size);
+
+ return ptr;
+}
+
+void *
+ssh_calloc(size_t nitems, size_t isize)
+{
+ return ssh_calloc_flags(nitems, isize, SSH_KERNEL_ALLOC_NOWAIT);
+}
+
+void *ssh_strdup (const void * p)
+{
+ const char * str;
+ char * cp;
+
+ SSH_PRECOND(p != NULL);
+
+ str = (const char *) p;
+
+ if ((cp = (char *) ssh_malloc(strlen(str) + 1)) == NULL)
+ return NULL;
+
+ strcpy(cp, str);
+
+ return (void *) cp;
+}
+
+void *ssh_memdup(const void * p, size_t len)
+{
+ void * cp;
+
+ if ((cp = ssh_malloc(len + 1)) == NULL)
+ return NULL;
+
+ memcpy(cp, p, (size_t)len);
+
+ ((unsigned char *) cp)[len] = '\0';
+
+ return cp;
+}
diff --git a/drivers/interceptor/kernel_alloc.h b/drivers/interceptor/kernel_alloc.h
new file mode 100644
index 0000000..c722bf8
--- /dev/null
+++ b/drivers/interceptor/kernel_alloc.h
@@ -0,0 +1,44 @@
+/* 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.
+ */
+
+/*
+ * kernel_alloc.h
+ *
+ * Kernel memory allocation API.
+ *
+ */
+
+#ifndef KERNEL_ALLOC_H
+#define KERNEL_ALLOC_H
+
+/* Allocate 'size' amount of memory, with the 'flag'
+ parameters. Returns a NULL value if the allocation request cannot
+ be satisfied for some reason.
+
+ Notice: 'flag' is nothing more than a hint to the allocator. The
+ allocator is free to ignore 'flag'. The allocatee is free to
+ specify flag as ssh_rand() number, and the returned memory must still
+ have the same semantics as any other memory block allocated. */
+void *ssh_kernel_alloc(size_t size, SshUInt32 flag);
+
+/* Flag is or-ed together of the following flags. */
+#define SSH_KERNEL_ALLOC_NOWAIT 0x0000 /* allocation/use atomic. */
+#define SSH_KERNEL_ALLOC_WAIT 0x0001 /* allow sleeping alloc/use. */
+#define SSH_KERNEL_ALLOC_DMA 0x0002 /* allow DMA use. */
+/* Other bits are usable for other purposes? */
+
+/* Frees a previously allocated block of memory. */
+void ssh_kernel_free(void *ptr);
+
+#ifdef DEBUG_LIGHT
+#define KERNEL_ALLOC_USE_FUNCTIONS
+#endif /* DEBUG_LIGHT */
+
+#endif /* KERNEL_ALLOC_H */
diff --git a/drivers/interceptor/kernel_encode.c b/drivers/interceptor/kernel_encode.c
new file mode 100644
index 0000000..f13694d
--- /dev/null
+++ b/drivers/interceptor/kernel_encode.c
@@ -0,0 +1,580 @@
+/* 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.
+ */
+
+/*
+ * kernel_encode.c
+ *
+ * Encode/decode API implementation for kernel space.
+ *
+ */
+
+#include "sshincludes.h"
+#include "sshgetput.h"
+#include "sshencode.h"
+
+/* Encodes data into the buffer. Returns the number of bytes added, or
+ 0 if the buffer is too small. The data to be encoded is specified by the
+ variable-length argument list. Each element must start with a
+ SshEncodingFormat type, be followed by arguments of the appropriate
+ type, and the list must end with SSH_FORMAT_END. */
+size_t ssh_encode_array_va(unsigned char *buf, size_t bufsize, va_list ap)
+{
+ SshEncodingFormat format;
+ unsigned int intvalue;
+ SshUInt16 u16;
+ SshUInt32 u32;
+ SshUInt64 u64;
+ size_t len, offset;
+ Boolean b;
+ const unsigned char *p;
+
+ offset = 0;
+ for (;;)
+ {
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+ case SSH_FORMAT_UINT32_STR:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ if (bufsize - offset < 4 + len)
+ return 0;
+ SSH_PUT_32BIT(buf + offset, len);
+ memcpy(buf + offset + 4, p, len);
+ offset += 4 + len;
+ break;
+
+ case SSH_FORMAT_BOOLEAN:
+ b = va_arg(ap, Boolean);
+ if (bufsize - offset < 1)
+ return 0;
+ buf[offset++] = (unsigned char)b;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32 = va_arg(ap, SshUInt32);
+ if (bufsize - offset < 4)
+ return 0;
+ SSH_PUT_32BIT(buf + offset, u32);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ intvalue = va_arg(ap, unsigned int);
+ u16 = (SshUInt16) intvalue;
+ if (bufsize - offset < 2)
+ return 0;
+ SSH_PUT_16BIT(buf + offset, u16);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ intvalue = va_arg(ap, unsigned int);
+ if (bufsize - offset < 1)
+ return 0;
+ buf[offset++] = (unsigned char)intvalue;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ if (bufsize - offset < len)
+ return 0;
+ memcpy(buf + offset, p, len);
+ offset += len;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64 = va_arg(ap, SshUInt64);
+ if (bufsize - offset < 8)
+ return 0;
+ SSH_PUT_64BIT(buf + offset, u64);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_SPECIAL:
+ {
+ SshEncodeDatum fn;
+ void *datum;
+ size_t space, size;
+
+ fn = va_arg(ap, SshEncodeDatum);
+ datum = va_arg(ap, void *);
+
+ space = bufsize - offset;
+ size = (*fn)(buf, space, datum);
+ if (size > space)
+ return 0;
+
+ offset += size;
+ }
+ break;
+
+ case SSH_FORMAT_END:
+ /* Return the number of bytes added. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_encode_array_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+}
+
+/* Appends data at the end of the buffer as specified by the variable-length
+ argument list. Each element must start with a SshEncodingFormat type,
+ be followed by arguments of the appropriate type, and the list must end
+ with SSH_FORMAT_END. This returns the number of bytes added to the
+ buffer, or 0 if the buffer is too small. */
+size_t ssh_encode_array(unsigned char *buf, size_t bufsize, ...)
+{
+ size_t bytes;
+ va_list ap;
+
+ va_start(ap, bufsize);
+ bytes = ssh_encode_array_va(buf, bufsize, ap);
+ va_end(ap);
+ return bytes;
+}
+
+/* Doubles the size of a buffer. Copies the contents from the old one
+ to the new one and frees the old one. Returns new buffer size.
+ If realloc fails, frees the old buffer and returns 0. */
+size_t ssh_encode_array_enlarge_buffer(unsigned char **buf, size_t bufsize)
+{
+ unsigned int newsize;
+ unsigned char *newbuf;
+
+ SSH_ASSERT(buf != NULL);
+
+ newsize = bufsize * 2;
+ SSH_ASSERT(newsize < 10000000);
+
+ if (newsize == 0)
+ newsize = 100;
+
+ newbuf = ssh_realloc(*buf, bufsize, newsize);
+ if (newbuf == NULL)
+ {
+ ssh_free(*buf);
+ return 0;
+ }
+ *buf = newbuf;
+
+ return newsize;
+}
+
+/* Encodes the given data. Returns the length of encoded data in bytes, and
+ if `buf_return' is non-NULL, it is set to a memory area allocated by
+ ssh_malloc that contains the data. The caller should free the data when
+ no longer needed.
+
+ Duplicates functionality in ssh_encode_array_va, could combine
+ some. Note that cycling va_list with va_arg is not supported in all
+ environments, so repeatedly trying ssh_encode_array_va with bigger
+ buffers is not possible. */
+size_t ssh_encode_array_alloc_va(unsigned char **buf_return, va_list ap)
+{
+ size_t bufsize;
+ unsigned char *buf = NULL;
+ SshEncodingFormat format;
+ unsigned int intvalue;
+ SshUInt16 u16;
+ SshUInt32 u32;
+ SshUInt64 u64;
+ size_t len, offset;
+ Boolean b;
+ const unsigned char *p;
+
+ /* Prepare return value in case of later failure. */
+ if (buf_return != NULL)
+ *buf_return = NULL;
+
+ /* Allocate new buffer. */
+ bufsize = ssh_encode_array_enlarge_buffer(&buf, 0);
+
+ offset = 0;
+ for (;;)
+ {
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+ case SSH_FORMAT_UINT32_STR:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ while (bufsize - offset < 4 + len)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_32BIT(buf + offset, len);
+ memcpy(buf + offset + 4, p, len);
+ offset += 4 + len;
+ break;
+
+ case SSH_FORMAT_BOOLEAN:
+ b = va_arg(ap, Boolean);
+ while (bufsize - offset < 1)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ buf[offset++] = (unsigned char)b;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32 = va_arg(ap, SshUInt32);
+ while (bufsize - offset < 4)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_32BIT(buf + offset, u32);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ intvalue = va_arg(ap, unsigned int);
+ u16 = (SshUInt16) intvalue;
+ while (bufsize - offset < 2)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_16BIT(buf + offset, u16);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ intvalue = va_arg(ap, unsigned int);
+ while (bufsize - offset < 1)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ buf[offset++] = (unsigned char)intvalue;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ while (bufsize - offset < len)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ memcpy(buf + offset, p, len);
+ offset += len;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64 = va_arg(ap, SshUInt64);
+ while (bufsize - offset < 8)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_64BIT(buf + offset, u64);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_END:
+ if (buf_return != NULL)
+ *buf_return = buf;
+ else
+ /* They only want to know the size of the buffer */
+ ssh_free(buf);
+
+ /* Return the number of bytes added. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_encode_array_alloc_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+}
+
+/* Encodes the given data. Returns the length of encoded data in bytes, and
+ if `buf_return' is non-NULL, it is set to a memory area allocated by
+ ssh_malloc that contains the data. The caller should free the data when
+ no longer needed. It is an error to call this with an argument list that
+ would result in zero bytes being encoded. */
+size_t ssh_encode_array_alloc(unsigned char **buf_return, ...)
+{
+ size_t bytes;
+ va_list ap;
+
+ va_start(ap, buf_return);
+ bytes = ssh_encode_array_alloc_va(buf_return, ap);
+ va_end(ap);
+
+ return bytes;
+}
+
+/* Allocates a buffer of the given size with ssh_malloc. However,
+ the buffer is also recorded in *num_allocs_p and *allocs_p, so that
+ they can all be easily freed later if necessary. */
+static unsigned char *ssh_decode_array_alloc(unsigned int *num_allocs_p,
+ unsigned char ***allocsp,
+ size_t size)
+{
+ unsigned char *p;
+
+ /* Check if we need to enlarge the pointer array. We enlarge it in chunks
+ of 16 pointers. */
+ if (*num_allocs_p == 0)
+ *allocsp = NULL;
+
+ /* This will also match *num_allocs_p == 0, and it is valid to pass
+ NULL to krealloc, so this works. */
+ if (*num_allocs_p % 16 == 0)
+ {
+ unsigned char ** nallocsp;
+
+ nallocsp = ssh_realloc(*allocsp,
+ *num_allocs_p
+ * sizeof(unsigned char *),
+ (*num_allocs_p + 16)
+ * sizeof(unsigned char *));
+
+ /* If we fail in allocation, return NULL but leave *allocsp intact. */
+ if (!nallocsp)
+ return NULL;
+
+ *allocsp = nallocsp;
+ }
+
+ /* Allocate the memory block. */
+ if (!(p = ssh_malloc(size)))
+ return NULL;
+
+ /* Store it in the array. */
+ (*allocsp)[*num_allocs_p] = p;
+ (*num_allocs_p)++;
+
+ return p;
+}
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocated data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array_va(const unsigned char *buf, size_t len,
+ va_list ap)
+{
+ SshEncodingFormat format;
+ unsigned long longvalue;
+ SshUInt16 *u16p;
+ SshUInt32 *u32p;
+ SshUInt64 *u64p;
+ Boolean *bp;
+ size_t size, *sizep;
+ unsigned int *uip;
+ unsigned char *p, **pp;
+ const unsigned char **cpp;
+ size_t offset;
+ unsigned int i, num_allocs;
+ unsigned char **allocs = NULL;
+
+ offset = 0;
+ num_allocs = 0;
+
+ for (;;)
+ {
+ /* Get the next format code. */
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+
+ case SSH_FORMAT_UINT32_STR:
+ /* Get length and data pointers. */
+ pp = va_arg(ap, unsigned char **);
+ sizep = va_arg(ap, size_t *);
+
+ /* Check if the length of the string is there. */
+ if (len - offset < 4)
+ goto fail;
+
+ /* Get the length of the string. */
+ longvalue = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+
+ /* Check that the string is all in the buffer. */
+ if (longvalue > len - offset)
+ goto fail;
+
+ /* Store length if requested. */
+ if (sizep != NULL)
+ *sizep = longvalue;
+
+ /* Retrieve the data if requested. */
+ if (pp != NULL)
+ {
+ *pp = ssh_decode_array_alloc(&num_allocs, &allocs,
+ (size_t)longvalue + 1);
+
+ if (!*pp)
+ goto fail;
+
+ memcpy(*pp, buf + offset, (size_t)longvalue);
+ (*pp)[longvalue] = '\0';
+ }
+
+ /* Consume the data. */
+ offset += longvalue;
+ break;
+
+ case SSH_FORMAT_UINT32_STR_NOCOPY:
+
+ /* Get length and data pointers. */
+ cpp = va_arg(ap, const unsigned char **);
+ sizep = va_arg(ap, size_t *);
+
+ /* Decode string length and skip the length. */
+
+ if (len - offset < 4)
+ goto fail;
+
+ longvalue = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+
+ /* Check that the string is all in the buffer. */
+ if (longvalue > len - offset)
+ goto fail;
+
+ /* Store length if requested. */
+ if (sizep != NULL)
+ *sizep = longvalue;
+
+ /* Retrieve the data if requested. */
+ if (cpp != NULL)
+ *cpp = buf + offset;
+
+ /* Consume the data. */
+ offset += longvalue;
+ break;
+
+
+ case SSH_FORMAT_BOOLEAN:
+ bp = va_arg(ap, Boolean *);
+ if (len - offset < 1)
+ goto fail;
+ if (bp != NULL)
+ *bp = buf[offset] != 0;
+ offset++;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32p = va_arg(ap, SshUInt32 *);
+ if (len - offset < 4)
+ goto fail;
+ if (u32p)
+ *u32p = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ u16p = va_arg(ap, SshUInt16 *);
+ if (len - offset < 2)
+ goto fail;
+ if (u16p)
+ *u16p = SSH_GET_16BIT(buf + offset);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ uip = va_arg(ap, unsigned int *);
+ if (len - offset < 1)
+ goto fail;
+ if (uip)
+ *uip = buf[offset];
+ offset++;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ size = va_arg(ap, size_t);
+ if (len - offset < size)
+ goto fail;
+ if (p)
+ memcpy(p, buf + offset, size);
+ offset += size;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64p = va_arg(ap, SshUInt64 *);
+ if (len - offset < 8)
+ goto fail;
+ if (u64p)
+ *u64p = SSH_GET_64BIT(buf + offset);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_SPECIAL:
+ {
+ SshDecodeDatum fn;
+ void **datump;
+
+ fn = va_arg(ap, SshDecodeDatum);
+ datump = va_arg(ap, void **);
+ size = (*fn)(buf + offset, len - offset, datump);
+ if (size > len - offset)
+ goto fail;
+ offset += size;
+ }
+ break;
+
+ case SSH_FORMAT_END:
+ /* Free the allocs array. */
+ if (num_allocs > 0)
+ ssh_free(allocs);
+ /* Return the number of bytes consumed. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_decode_array_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+
+fail:
+ /* An error was encountered. Free all allocated memory and return zero. */
+ for (i = 0; i < num_allocs; i++)
+ ssh_free(allocs[i]);
+ if (i > 0)
+ ssh_free(allocs);
+ return 0;
+}
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocates data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array(const unsigned char *buf, size_t len, ...)
+{
+ va_list ap;
+ size_t bytes;
+
+ va_start(ap, len);
+ bytes = ssh_decode_array_va(buf, len, ap);
+ va_end(ap);
+
+ return bytes;
+}
diff --git a/drivers/interceptor/kernel_includes.h b/drivers/interceptor/kernel_includes.h
new file mode 100644
index 0000000..93eeea8
--- /dev/null
+++ b/drivers/interceptor/kernel_includes.h
@@ -0,0 +1,152 @@
+/* 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.
+ */
+
+/*
+ * kernel_includes.h
+ *
+ * Common include file for kernel space components.
+ *
+ */
+
+#ifndef KERNEL_INCLUDES_H
+#define KERNEL_INCLUDES_H
+
+#ifdef KERNEL
+# undef _KERNEL
+# define _KERNEL
+#endif /* KERNEL */
+
+#include "sshconf.h"
+
+/* Set SIZEOF_* defines to point to kernel definitions of those. */
+#ifndef SIZEOF_INT
+# define SIZEOF_INT KERNEL_SIZEOF_INT
+#endif /* SIZEOF_INT */
+
+#ifndef SIZEOF_LONG
+# define SIZEOF_LONG KERNEL_SIZEOF_LONG
+#endif /* SIZEOF_LONG */
+
+#ifndef SIZEOF_LONG_LONG
+# define SIZEOF_LONG_LONG KERNEL_SIZEOF_LONG_LONG
+#endif /* SIZEOF_LONG_LONG */
+
+#ifndef SIZEOF_SHORT
+# define SIZEOF_SHORT KERNEL_SIZEOF_SHORT
+#endif /* SIZEOF_SHORT */
+
+#ifndef SIZEOF_VOID_P
+# define SIZEOF_VOID_P KERNEL_SIZEOF_VOID_P
+#endif /* SIZEOF_VOID_P */
+
+/* Set HAVE_ */
+#ifdef HAVE_KERNEL_SHORT
+# define HAVE_SHORT
+#endif
+#ifdef HAVE_KERNEL_INT
+# define HAVE_INT
+#endif
+#ifdef HAVE_KERNEL_LONG
+# define HAVE_LONG
+#endif
+#ifdef HAVE_KERNEL_LONG_LONG
+# define HAVE_LONG_LONG
+#endif
+#ifdef HAVE_KERNEL_VOID_P
+# define HAVE_VOID_P
+#endif
+
+typedef unsigned char SshUInt8; /* At least 8 bits. */
+typedef signed char SshInt8; /* At least 8 bits. */
+
+typedef unsigned short SshUInt16; /* At least 16 bits. */
+typedef short SshInt16; /* At least 16 bits. */
+
+#if SIZEOF_LONG == 4
+typedef unsigned long SshUInt32; /* At least 32 bits. */
+typedef long SshInt32; /* At least 32 bits. */
+#else /* SIZEOF_LONG != 4 */
+#if SIZEOF_INT == 4
+typedef unsigned int SshUInt32; /* At least 32 bits. */
+typedef int SshInt32; /* At least 32 bits. */
+#else /* SIZEOF_INT != 4 */
+#if SIZEOF_SHORT >= 4
+typedef unsigned short SshUInt32; /* At least 32 bits. */
+typedef short SshInt32; /* At least 32 bits. */
+#else /* SIZEOF_SHORT < 4 */
+# error "Autoconfig error, your compiler doesn't support any 32 bit type"
+#endif /* SIZEOF_SHORT < 4 */
+#endif /* SIZEOF_INT != 4 */
+#endif /* SIZEOF_LONG != 4 */
+
+#if SIZEOF_LONG >= 8
+typedef unsigned long SshUInt64;
+typedef long SshInt64;
+# define SSHUINT64_IS_64BITS
+# define SSH_C64(x) (x##lu)
+# define SSH_S64(x) (x##l)
+#else /* SIZEOF_LONG < 8 */
+#if SIZEOF_LONG_LONG >= 8
+typedef unsigned long long SshUInt64;
+typedef long long SshInt64;
+# define SSHUINT64_IS_64BITS
+# define SSH_C64(x) (x##llu)
+# define SSH_S64(x) (x##ll)
+#else /* SIZEOF_LONG_LONG < 8 */
+/* No 64 bit type; SshUInt64 and SshInt64 will be 32 bits. */
+typedef unsigned long SshUInt64;
+typedef long SshInt64;
+# define SSH_C64(x) SSH_FATAL(ERROR_NO_64_BIT_ON_THIS_SYSTEM)
+#endif /* SIZEOF_LONG_LONG < 8 */
+#endif /* SIZEOF_LONG < 8 */
+
+typedef SshInt64 SshTime;
+
+#include <linux/types.h>
+
+#ifdef HAVE_MACHINE_ENDIAN_H
+# include <sys/param.h>
+# include <machine/endian.h>
+#endif
+
+#include <stddef.h>
+
+#include <stdarg.h>
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+typedef unsigned int Boolean;
+
+/* Platform-specific kernel-mode definitions follow. */
+
+/****************************** LINUX *****************************/
+/* Sanity checks about module support and that we are supporting it really */
+#ifndef MODULE
+# error "MODULE must be defined when compiling for Linux"
+#endif
+
+#include <linux/string.h>
+
+/* Including linux/skbuff.h here allows us to cleanly make
+ ssh_interceptor_packet*() macros for it. It really ought to
+ be in the the "platform_interceptor.h" header file, but unfortunately
+ this would require generic engine code to set SSH_ALLOW_SYSTEM_SPRINTFS,
+ which is why it is here. */
+#include "linux/skbuff.h"
+
+/****************************** LINUX (END) *****************************/
+
+#endif /* KERNEL_INCLUDES_H */
diff --git a/drivers/interceptor/kernel_mutex.h b/drivers/interceptor/kernel_mutex.h
new file mode 100644
index 0000000..e966d8d
--- /dev/null
+++ b/drivers/interceptor/kernel_mutex.h
@@ -0,0 +1,67 @@
+/* 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.
+ */
+
+/*
+ * kernel_mutex.h
+ *
+ * Kernel mutex API.
+ *
+ */
+
+#ifndef KERNEL_MUTEX_H
+#define KERNEL_MUTEX_H
+
+typedef struct SshKernelMutexRec *SshKernelMutex;
+
+/* Allocates and initializes a simple mutex. This should be as fast as
+ possible, but work between different processors in a multiprocessor
+ machine. Also, it is a fatal error for a thread to attempt to lock
+ this twice (i.e., this need not check whether it is actually held
+ by the same thread). The recommended implementation is a spinlock. */
+SshKernelMutex ssh_kernel_mutex_alloc(void);
+
+/* Initializes a mutex allocated from the stack. Returns TRUE on success
+ and FALSE on failure. */
+Boolean ssh_kernel_mutex_init(SshKernelMutex mutex);
+
+/* Frees the given mutex. The mutex must not be locked when it is freed. */
+void ssh_kernel_mutex_free(SshKernelMutex mutex);
+
+/* Uninitializes the given mutex. The mutex must not be locked when it is
+ uninitialized. */
+void ssh_kernel_mutex_uninit(SshKernelMutex mutex);
+
+/* Locks the mutex. Only one thread of execution can have a mutex locked
+ at a time. This will block until execution can continue. One should
+ not keep mutexes locked for extended periods of time. */
+void ssh_kernel_mutex_lock(SshKernelMutex mutex);
+
+/* Unlocks the mutex. The mutex must be unlocked from the same thread
+ from which it was locked. If other threads are waiting to lock the mutex,
+ one of them will get the lock and continue execution. */
+void ssh_kernel_mutex_unlock(SshKernelMutex mutex);
+
+#ifdef DEBUG_LIGHT
+/* Check that the mutex is locked. It is a fatal error if it is not. */
+void ssh_kernel_mutex_assert_is_locked(SshKernelMutex mutex);
+#else /* DEBUG_LIGHT */
+#define ssh_kernel_mutex_assert_is_locked(mutex)
+#endif /* DEBUG_LIGHT */
+
+#ifdef DEBUG_LIGHT
+#define KERNEL_MUTEX_USE_FUNCTIONS
+#endif /* DEBUG_LIGHT */
+
+/* This must be in the -I path of the machine-dependent interceptor
+ dir. It defines any platform-dependent things (such as the inline
+ functions, if KERNEL_MUTEX_USE_FUNCTIONS is not defined). */
+#include "platform_kernel_mutex.h"
+
+#endif /* KERNEL_MUTEX_H */
diff --git a/drivers/interceptor/linux_hook_magic.c b/drivers/interceptor/linux_hook_magic.c
new file mode 100644
index 0000000..20a6edb
--- /dev/null
+++ b/drivers/interceptor/linux_hook_magic.c
@@ -0,0 +1,842 @@
+/* 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_hook_magic.c
+ *
+ * Linux netfilter hook probing.
+ *
+ */
+
+#include "linux_internal.h"
+
+#include <net/checksum.h>
+#include <linux/udp.h>
+
+extern SshInterceptor ssh_interceptor_context;
+
+/***************************** Probe hooks ***********************************/
+
+#define SSH_LINUX_HOOK_MAGIC_NUM_PROBES 10
+
+typedef struct SshInterceptorHookMagicOpsRec
+{
+ struct nf_hook_ops ops;
+ struct sk_buff *skbp[SSH_LINUX_HOOK_MAGIC_NUM_PROBES];
+ int probe_count;
+} SshInterceptorHookMagicOpsStruct;
+
+static SshInterceptorHookMagicOpsStruct hook_magic_in4;
+static SshInterceptorHookMagicOpsStruct hook_magic_out4;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static SshInterceptorHookMagicOpsStruct hook_magic_in6;
+static SshInterceptorHookMagicOpsStruct hook_magic_out6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+static SshInterceptorHookMagicOpsStruct hook_magic_arp_in;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+
+static unsigned int
+ssh_interceptor_hook_magic_hookfn(int pf,
+ unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb);
+ int i;
+
+ /* Grab lock */
+ local_bh_disable();
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+
+ switch (pf)
+ {
+ case PF_INET:
+ switch (hooknum)
+ {
+ case SSH_NF_IP_PRE_ROUTING:
+ /* Check if this is our hook_magic probe */
+ for (i = 0; i < hook_magic_in4.probe_count; i++)
+ {
+ if (hook_magic_in4.skbp[i] == skbp)
+ {
+ SSH_ASSERT(okfn != NULL);
+
+ /* Check if we have probed this hook */
+ if (ssh_interceptor_context->linux_fn.ip_rcv_finish == NULL)
+ {
+ /* Make sure the assignments do not get reordered */
+ barrier();
+
+ ssh_interceptor_context->linux_fn.ip_rcv_finish = okfn;
+ SSH_DEBUG(SSH_D_LOWOK, ("ip_rcv_finish 0x%p", okfn));
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Double probe received for "
+ "NF_IP_PRE_ROUTING"));
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip_rcv_finish == okfn);
+ }
+
+ /* Mark packet as being handled */
+ hook_magic_in4.skbp[i] = NULL;
+ goto drop;
+ }
+ }
+
+ /* Not our probe packet, let through */
+ SSH_DEBUG(10, ("Not our skbp 0x%p", skbp));
+ goto accept;
+
+ case SSH_NF_IP_POST_ROUTING:
+ /* Check if this is our hook_magic probe */
+ for (i = 0; i < hook_magic_out4.probe_count; i++)
+ {
+ if (hook_magic_out4.skbp[i] == skbp)
+ {
+ SSH_ASSERT(okfn != NULL);
+
+ /* Check if we have probed this hook */
+ if (ssh_interceptor_context->linux_fn.ip_finish_output
+ == NULL)
+ {
+ /* Make sure the assignments do not get reordered */
+ barrier();
+
+ ssh_interceptor_context->linux_fn.ip_finish_output = okfn;
+ SSH_DEBUG(SSH_D_LOWOK, ("ip_finish_output 0x%p", okfn));
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Double probe received for "
+ "NF_IP_POST_ROUTING"));
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip_finish_output == okfn);
+ }
+
+ /* Mark packet as being handled */
+ hook_magic_out4.skbp[i] = NULL;
+ goto drop;
+ }
+ }
+
+ /* Not our probe packet, let through */
+ SSH_DEBUG(10, ("Not our skbp 0x%p", skbp));
+ goto accept;
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ switch (hooknum)
+ {
+ case SSH_NF_IP6_PRE_ROUTING:
+ /* Check if this is our hook_magic probe */
+ for (i = 0; i < hook_magic_in6.probe_count; i++)
+ {
+ if (hook_magic_in6.skbp[i] == skbp)
+ {
+ SSH_ASSERT(okfn != NULL);
+
+ /* Check if we have probed this hook */
+ if (ssh_interceptor_context->linux_fn.ip6_rcv_finish == NULL)
+ {
+ /* Make sure the assignments do not get reordered */
+ barrier();
+
+ ssh_interceptor_context->linux_fn.ip6_rcv_finish = okfn;
+ SSH_DEBUG(SSH_D_LOWOK, ("ip6_rcv_finish 0x%p", okfn));
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Double probe received for "
+ "NF_IP6_PRE_ROUTING"));
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip6_rcv_finish == okfn);
+ }
+
+ /* Mark packet as being handled */
+ hook_magic_in6.skbp[i] = NULL;
+ goto drop;
+ }
+ }
+
+ /* Not our probe packet, let through */
+ SSH_DEBUG(10, ("Not our skbp 0x%p", skbp));
+ goto accept;
+
+ case SSH_NF_IP6_POST_ROUTING:
+ /* Check if this is our hook_magic probe */
+ for (i = 0; i < hook_magic_out6.probe_count; i++)
+ {
+ if (hook_magic_out6.skbp[i] == skbp)
+ {
+ SSH_ASSERT(okfn != NULL);
+
+ /* Check if we have not yet probed this hook */
+ if (ssh_interceptor_context->linux_fn.ip6_output_finish
+ == NULL)
+ {
+ /* Make sure the assignments do not get reordered */
+ barrier();
+
+ ssh_interceptor_context->linux_fn.ip6_output_finish =
+ okfn;
+ SSH_DEBUG(SSH_D_LOWOK, ("ip6_output_finish 0x%p", okfn));
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Double probe received for "
+ "NF_IP6_POST_ROUTING"));
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip6_output_finish == okfn);
+ }
+
+ /* Mark packet as being handled */
+ hook_magic_out6.skbp[i] = NULL;
+ goto drop;
+ }
+ }
+
+ /* Not our probe packet, let through */
+ SSH_DEBUG(10, ("Not our skbp 0x%p", skbp));
+ goto accept;
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ case SSH_NFPROTO_ARP:
+ /* Check if this is our hook_magic probe */
+ for (i = 0; i < hook_magic_arp_in.probe_count; i++)
+ {
+ if (hook_magic_arp_in.skbp[i] == skbp)
+ {
+ SSH_ASSERT(hooknum == NF_ARP_IN);
+ SSH_ASSERT(okfn != NULL);
+
+ /* Check if we have probed this hook */
+ if (ssh_interceptor_context->linux_fn.arp_process == NULL)
+ {
+ /* Make sure the assignments do not get reordered */
+ barrier();
+
+ ssh_interceptor_context->linux_fn.arp_process = okfn;
+ SSH_DEBUG(SSH_D_LOWOK, ("arp_process 0x%p", okfn));
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Double probe received for NF_ARP_IN"));
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.arp_process
+ == okfn);
+ }
+
+ /* Mark packet as being handled */
+ hook_magic_arp_in.skbp[i] = NULL;
+ goto drop;
+ }
+ }
+
+ /* Not our probe packet, let through */
+ SSH_DEBUG(10, ("Not our skbp 0x%p", skbp));
+ goto accept;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+
+ /* Unlock and drop the packet */
+ drop:
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+ return NF_DROP;
+
+ accept:
+ /* Unlock and let packet continue */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+ return NF_ACCEPT;
+}
+
+static unsigned int
+ssh_interceptor_hook_magic_hookfn_ipv4(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_hook_magic_hookfn(PF_INET, hooknum, skb,
+ in, out, okfn);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static unsigned int
+ssh_interceptor_hook_magic_hookfn_ipv6(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_hook_magic_hookfn(PF_INET6, hooknum, skb,
+ in, out, okfn);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+static unsigned int
+ssh_interceptor_hook_magic_hookfn_arp(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_hook_magic_hookfn(SSH_NFPROTO_ARP, hooknum, skb,
+ in, out, okfn);
+}
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+
+/***************************** Probe packet sending **************************/
+
+/* Caller holds 'interceptor_lock'.
+ This function will release it when calling the network stack functions,
+ and will grab it back after the function has returned. */
+
+static int
+ssh_interceptor_hook_magic_send(SshInterceptorHookMagicOpsStruct *hook_magic)
+{
+ struct sk_buff *skbp;
+ struct net_device *dev;
+ struct in_device *inet_dev = NULL;
+ struct iphdr *iph;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ struct inet6_dev *inet6_dev = NULL;
+ struct ipv6hdr *ip6h;
+ struct in6_addr addr6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ unsigned char *ptr;
+ struct arphdr *arph;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ struct udphdr *udph;
+
+ /* Assert that we have the lock */
+ ssh_kernel_mutex_assert_is_locked(ssh_interceptor_context->interceptor_lock);
+
+ /* Find a suitable device to put on the probe skb */
+ read_lock(&dev_base_lock);
+
+ for (dev = SSH_FIRST_NET_DEVICE();
+ dev != NULL;
+ dev = SSH_NEXT_NET_DEVICE(dev))
+ {
+ if (!(dev->flags & IFF_UP))
+ continue;
+
+ inet_dev = in_dev_get(dev);
+ if (inet_dev == NULL || inet_dev->ifa_list == NULL)
+ goto next_dev;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ /* The device must have an IPv6 address. */
+ inet6_dev = in6_dev_get(dev);
+ memset(&addr6, 0, sizeof(addr6));
+
+ if (inet6_dev != NULL)
+ {
+ struct inet6_ifaddr *ifaddr6, *next_ifaddr6;
+ SSH_INET6_IFADDR_LIST_FOR_EACH(ifaddr6,
+ next_ifaddr6,
+ inet6_dev->addr_list)
+ {
+ addr6 = ifaddr6->addr;
+ break;
+ }
+ }
+
+ if (inet6_dev == NULL ||
+ memcmp(addr6.s6_addr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0)
+ goto next_dev;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* The device must must support ARP. */
+ if ((dev->flags & IFF_NOARP) || dev->addr_len != 6)
+ goto next_dev;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* This device is suitable for probing. */
+ break;
+
+ next_dev:
+ if (inet_dev)
+ in_dev_put(inet_dev);
+ inet_dev = NULL;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (inet6_dev)
+ in6_dev_put(inet6_dev);
+ inet6_dev = NULL;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ }
+ read_unlock(&dev_base_lock);
+ if (dev == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, could not find a suitable device"));
+ goto out;
+ }
+
+ /* Allocate probe skb */
+ /* Maximum size: IPv6 + UDP = 48 bytes
+ ( + headroom for ethernet header + 2 bytes for proper alignment) */
+ skbp = alloc_skb(48 + ETH_HLEN + 2, GFP_ATOMIC);
+ if (skbp == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, could not allocate skbuff"));
+ goto out;
+ }
+
+ /* Reserve headroom for ethernet header.
+ Make sure IP header will be properly aligned. */
+ skb_reserve(skbp, ETH_HLEN + 2);
+
+ SSH_SKB_SET_MACHDR(skbp, skbp->data);
+
+#ifdef LINUX_HAS_SKB_MAC_LEN
+ skbp->mac_len = 0;
+#endif /* LINUX_HAS_SKB_MAC_LEN */
+ skbp->pkt_type = PACKET_HOST;
+
+ skbp->dev = dev;
+ SSH_SKB_DST_SET(skbp, NULL);
+
+ /* Store skb pointer to a free slot. */
+ SSH_ASSERT(hook_magic->probe_count < SSH_LINUX_HOOK_MAGIC_NUM_PROBES);
+ hook_magic->skbp[hook_magic->probe_count] = skbp;
+ hook_magic->probe_count++;
+
+ switch (hook_magic->ops.pf)
+ {
+ case PF_INET:
+
+ /* Build an IP + UDP packet for probing */
+ skbp->protocol = __constant_htons(ETH_P_IP);
+
+ iph = (struct iphdr *) skb_put(skbp, sizeof(struct iphdr));
+ if (iph == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, no space for IPv4 header"));
+ goto out;
+ }
+ SSH_SKB_SET_NETHDR(skbp, (unsigned char *) iph);
+
+ memset(iph, 0, sizeof(struct iphdr));
+ iph->version = 4;
+ iph->ihl = 5;
+ iph->ttl = 1;
+ iph->protocol = IPPROTO_UDP;
+ iph->tot_len = htons(28);
+
+ /* Use the device addresses */
+ SSH_ASSERT(inet_dev != NULL && inet_dev->ifa_list != NULL);
+ iph->saddr = inet_dev->ifa_list->ifa_local;
+ iph->daddr = inet_dev->ifa_list->ifa_local;
+
+ /* Add UDP header */
+ udph = (struct udphdr *) skb_put(skbp, sizeof(struct udphdr));
+ if (udph == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, no space for UDP header"));
+ goto out;
+ }
+ SSH_SKB_SET_TRHDR(skbp, (unsigned char *) udph);
+
+ udph->source = htons(9);
+ udph->dest = htons(9);
+ udph->len = 0;
+ udph->check = 0;
+
+ /* Checksum */
+ iph->check = 0;
+ iph->check = ip_fast_csum((unsigned char *) iph, iph->ihl);
+
+ switch (hook_magic->ops.hooknum)
+ {
+ case SSH_NF_IP_PRE_ROUTING:
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip_rcv_finish == NULL);
+ SSH_DEBUG(SSH_D_HIGHOK,
+ ("Probing SSH_NF_IP_PRE_ROUTING with skbp 0x%p", skbp));
+ /* Release lock for duration of netif_rx() */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+ netif_rx(skbp);
+ local_bh_disable();
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+ break;
+
+ case SSH_NF_IP_POST_ROUTING:
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip_finish_output == NULL);
+ SSH_DEBUG(SSH_D_HIGHOK,
+ ("Probing SSH_NF_IP_POST_ROUTING with skbp 0x%p", skbp));
+ if (ssh_interceptor_reroute_skb_ipv4(ssh_interceptor_context,
+ skbp, 0, 0))
+ {
+ /* Release lock for the duration of dst->output() */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->
+ interceptor_lock);
+ dst_output(skbp);
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+ }
+ break;
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+
+ /* Build an IPv6 + UDP packet for probing */
+ skbp->protocol = __constant_htons(ETH_P_IPV6);
+ ip6h = (struct ipv6hdr *) skb_put(skbp, sizeof(struct ipv6hdr));
+ if (ip6h == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, no space for IPv6 header"));
+ goto out;
+ }
+ SSH_SKB_SET_NETHDR(skbp, (unsigned char *) ip6h);
+
+ memset(ip6h, 0, sizeof(struct ipv6hdr));
+ ip6h->version = 6;
+ ip6h->hop_limit = 1;
+ ip6h->nexthdr = NEXTHDR_UDP;
+
+ /* Use the device addresses */
+ SSH_ASSERT(memcmp(addr6.s6_addr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)
+ != 0);
+
+ ip6h->saddr = addr6;
+ ip6h->daddr = addr6;
+
+ /* Add UDP header */
+ udph = (struct udphdr *) skb_put(skbp, sizeof(struct udphdr));
+ if (udph == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, no space for UDP header"));
+ goto out;
+ }
+ SSH_SKB_SET_TRHDR(skbp, (unsigned char *) udph);
+
+ udph->source = htons(9);
+ udph->dest = htons(9);
+ udph->len = 0;
+ udph->check = 0;
+
+ switch (hook_magic->ops.hooknum)
+ {
+ case SSH_NF_IP6_PRE_ROUTING:
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip6_rcv_finish == NULL);
+ SSH_DEBUG(SSH_D_HIGHOK,
+ ("Probing SSH_NF_IP6_PRE_ROUTING with skbp 0x%p", skbp));
+ /* Release lock for the duration of netif_rx() */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+ netif_rx(skbp);
+ local_bh_disable();
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+ break;
+
+ case SSH_NF_IP6_POST_ROUTING:
+ SSH_ASSERT(ssh_interceptor_context->
+ linux_fn.ip6_output_finish == NULL);
+ SSH_DEBUG(SSH_D_HIGHOK,
+ ("Probing SSH_NF_IP6_POST_ROUTING with skbp 0x%p", skbp));
+ if (ssh_interceptor_reroute_skb_ipv6(ssh_interceptor_context,
+ skbp, 0, 0))
+ {
+ /* Release lock for the duration of dst->output() */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->
+ interceptor_lock);
+ dst_output(skbp);
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+ }
+ break;
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ case SSH_NFPROTO_ARP:
+ SSH_ASSERT(hook_magic->ops.hooknum == NF_ARP_IN);
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.arp_process == NULL);
+
+ /* Build an ARP REQ packet for probing */
+ skbp->protocol = __constant_htons(ETH_P_ARP);
+
+ SSH_ASSERT(dev->addr_len == 6);
+ SSH_ASSERT((sizeof(struct arphdr) + (2 * dev->addr_len) + 8) <= 48);
+
+ arph = (struct arphdr *) skb_put(skbp, sizeof(struct arphdr) +
+ (2 * dev->addr_len) + 8);
+ if (arph == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Hook magic failed, no space for ARP header"));
+ goto out;
+ }
+ SSH_SKB_SET_NETHDR(skbp, (unsigned char *) arph);
+
+ arph->ar_hrd = htons(ARPHRD_ETHER);
+ arph->ar_pro = htons(ETH_P_IP);
+ arph->ar_hln = dev->addr_len;
+ arph->ar_pln = 4;
+ arph->ar_op = htons(ARPOP_REQUEST);
+
+ ptr = skbp->data + sizeof(*arph);
+
+ /* Zero sender HA */
+ memset(ptr, 0, dev->addr_len);
+ ptr += dev->addr_len;
+
+ /* Take sender IP from device */
+ memcpy(ptr, &inet_dev->ifa_list->ifa_local, 4);
+ ptr += 4;
+
+ /* All f's target HA */
+ memset(ptr, 0xff, dev->addr_len);
+ ptr += dev->addr_len;
+
+ /* Take target IP from device */
+ memcpy(ptr, &inet_dev->ifa_list->ifa_local, 4);
+
+ SSH_DEBUG(SSH_D_HIGHOK,
+ ("Probing NF_ARP_IN with skbp 0x%p", skbp));
+ /* Release the lock for the duration of dst->output() */
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+ netif_rx(skbp);
+ local_bh_disable();
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+ break;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ default:
+ SSH_NOTREACHED;
+ break;
+ }
+
+ out:
+ if (inet_dev)
+ in_dev_put(inet_dev);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (inet6_dev)
+ in6_dev_put(inet6_dev);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ return 1;
+}
+
+static void
+ssh_interceptor_hook_magic_uninit(void)
+{
+ /* Unregister probe hooks */
+
+ if (hook_magic_in4.ops.pf == PF_INET)
+ {
+ nf_unregister_hook(&hook_magic_in4.ops);
+ hook_magic_in4.ops.pf = 0;
+ }
+ if (hook_magic_out4.ops.pf == PF_INET)
+ {
+ nf_unregister_hook(&hook_magic_out4.ops);
+ hook_magic_out4.ops.pf = 0;
+ }
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (hook_magic_in6.ops.pf == PF_INET6)
+ {
+ nf_unregister_hook(&hook_magic_in6.ops);
+ hook_magic_in6.ops.pf = 0;
+ }
+ if (hook_magic_out6.ops.pf == PF_INET6)
+ {
+ nf_unregister_hook(&hook_magic_out6.ops);
+ hook_magic_out6.ops.pf = 0;
+ }
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ if (hook_magic_arp_in.ops.pf == SSH_NFPROTO_ARP)
+ {
+ nf_unregister_hook(&hook_magic_arp_in.ops);
+ hook_magic_arp_in.ops.pf = 0;
+ }
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+}
+
+int
+ssh_interceptor_hook_magic_init()
+{
+ int ret;
+ int i;
+
+ /* Intialize and register probe hooks */
+
+ /* SSH_NF_IP_PRE_ROUTING */
+ ssh_interceptor_context->linux_fn.ip_rcv_finish = NULL;
+ memset(&hook_magic_in4, 0, sizeof(hook_magic_in4));
+ hook_magic_in4.ops.hook = ssh_interceptor_hook_magic_hookfn_ipv4;
+ hook_magic_in4.ops.pf = PF_INET;
+ hook_magic_in4.ops.hooknum = SSH_NF_IP_PRE_ROUTING;
+ hook_magic_in4.ops.priority = SSH_NF_IP_PRI_FIRST;
+ nf_register_hook(&hook_magic_in4.ops);
+
+ /* SSH_NF_IP_POST_ROUTING */
+ ssh_interceptor_context->linux_fn.ip_finish_output = NULL;
+ memset(&hook_magic_out4, 0, sizeof(hook_magic_out4));
+ hook_magic_out4.ops.hook = ssh_interceptor_hook_magic_hookfn_ipv4;
+ hook_magic_out4.ops.pf = PF_INET;
+ hook_magic_out4.ops.hooknum = SSH_NF_IP_POST_ROUTING;
+ hook_magic_out4.ops.priority = SSH_NF_IP_PRI_FIRST;
+ nf_register_hook(&hook_magic_out4.ops);
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ /* SSH_NF_IP6_PRE_ROUTING */
+ ssh_interceptor_context->linux_fn.ip6_rcv_finish = NULL;
+ memset(&hook_magic_in6, 0, sizeof(hook_magic_in6));
+ hook_magic_in6.ops.hook = ssh_interceptor_hook_magic_hookfn_ipv6;
+ hook_magic_in6.ops.pf = PF_INET6;
+ hook_magic_in6.ops.hooknum = SSH_NF_IP6_PRE_ROUTING;
+ hook_magic_in6.ops.priority = SSH_NF_IP6_PRI_FIRST;
+ nf_register_hook(&hook_magic_in6.ops);
+
+ /* SSH_NF_IP6_POST_ROUTING */
+ ssh_interceptor_context->linux_fn.ip6_output_finish = NULL;
+ memset(&hook_magic_out6, 0, sizeof(hook_magic_out6));
+ hook_magic_out6.ops.hook = ssh_interceptor_hook_magic_hookfn_ipv6;
+ hook_magic_out6.ops.pf = PF_INET6;
+ hook_magic_out6.ops.hooknum = SSH_NF_IP6_POST_ROUTING;
+ hook_magic_out6.ops.priority = SSH_NF_IP6_PRI_FIRST;
+ nf_register_hook(&hook_magic_out6.ops);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* NF_ARP_IN */
+ ssh_interceptor_context->linux_fn.arp_process = NULL;
+ memset(&hook_magic_arp_in, 0, sizeof(hook_magic_arp_in));
+ hook_magic_arp_in.ops.hook = ssh_interceptor_hook_magic_hookfn_arp;
+ hook_magic_arp_in.ops.pf = SSH_NFPROTO_ARP;
+ hook_magic_arp_in.ops.hooknum = NF_ARP_IN;
+ hook_magic_arp_in.ops.priority = 1;
+ nf_register_hook(&hook_magic_arp_in.ops);
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Send probe packets */
+
+ /* Try maximum SSH_LINUX_HOOK_MAGIC_NUM_PROBES times, and give up */
+ for (i = 0; i < SSH_LINUX_HOOK_MAGIC_NUM_PROBES; i++)
+ {
+ ret = 0;
+
+ local_bh_disable();
+ ssh_kernel_mutex_lock(ssh_interceptor_context->interceptor_lock);
+
+ if (ssh_interceptor_context->linux_fn.ip_rcv_finish == NULL)
+ ret += ssh_interceptor_hook_magic_send(&hook_magic_in4);
+ if (ssh_interceptor_context->linux_fn.ip_finish_output == NULL)
+ ret += ssh_interceptor_hook_magic_send(&hook_magic_out4);
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (ssh_interceptor_context->linux_fn.ip6_rcv_finish == NULL)
+ ret += ssh_interceptor_hook_magic_send(&hook_magic_in6);
+ if (ssh_interceptor_context->linux_fn.ip6_output_finish == NULL)
+ ret += ssh_interceptor_hook_magic_send(&hook_magic_out6);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ if (ssh_interceptor_context->linux_fn.arp_process == NULL)
+ ret += ssh_interceptor_hook_magic_send(&hook_magic_arp_in);
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ ssh_kernel_mutex_unlock(ssh_interceptor_context->interceptor_lock);
+ local_bh_enable();
+
+ if (ret == 0)
+ break;
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (i < 5)
+ schedule_timeout((i + 5) * HZ / 10);
+ else
+ schedule_timeout(HZ);
+ }
+
+ /* Remove probe hooks */
+ ssh_interceptor_hook_magic_uninit();
+
+#ifdef DEBUG_LIGHT
+ if (ret)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("ssh_hook_magic_init exits with return value %d", -ret));
+ }
+ else
+ {
+ /* Assert that all function pointers have been resolved. */
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.ip_rcv_finish != NULL);
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.ip_finish_output != NULL);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.ip6_rcv_finish != NULL);
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.ip6_output_finish != NULL);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ SSH_ASSERT(ssh_interceptor_context->linux_fn.arp_process != NULL);
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ }
+
+#endif /* DEBUG_LIGHT */
+
+ return -ret;
+}
diff --git a/drivers/interceptor/linux_iface.c b/drivers/interceptor/linux_iface.c
new file mode 100644
index 0000000..cc0c499
--- /dev/null
+++ b/drivers/interceptor/linux_iface.c
@@ -0,0 +1,863 @@
+/* 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_iface.c
+ *
+ * Linux interceptor network interface handling.
+ *
+ */
+
+#include "linux_internal.h"
+
+extern SshInterceptor ssh_interceptor_context;
+
+/* Protect access to net_device list in kernel. */
+#define SSH_LOCK_LINUX_DEV_LIST() read_lock(&dev_base_lock)
+#define SSH_UNLOCK_LINUX_DEV_LIST() read_unlock(&dev_base_lock);
+
+/**************************** Notification handlers *************************/
+
+/* Interface notifier callback. This will notify when there has been a
+ change in the interface list; device is added or removed, or an
+ IPv4/IPv6 address has been added or removed from an device. */
+
+int
+ssh_interceptor_notifier_callback(struct notifier_block *block,
+ unsigned long type, void *arg)
+{
+ if (ssh_interceptor_context != NULL)
+ ssh_interceptor_receive_ifaces(ssh_interceptor_context);
+
+ return NOTIFY_OK;
+}
+
+/* Internal mapping from ifnum to net_device.
+ This function will assert that 'ifnum' is a valid
+ SshInterceptorIfnum and that the corresponding net_device
+ exists in the interface hashtable. This function will dev_hold()
+ the net_device. The caller of this function must release it by
+ calling ssh_interceptor_release_netdev(). If `context_return' is
+ not NULL then this sets it to point to the interface context. */
+inline struct net_device *
+ssh_interceptor_ifnum_to_netdev_ctx(SshInterceptor interceptor,
+ SshUInt32 ifnum,
+ void **context_return)
+{
+ SshInterceptorInternalInterface iface;
+ struct net_device *dev = NULL;
+
+ SSH_LINUX_ASSERT_VALID_IFNUM(ifnum);
+
+ read_lock(&interceptor->if_table_lock);
+ for (iface = interceptor->if_hash[ifnum % SSH_LINUX_IFACE_HASH_SIZE];
+ iface && iface->ifindex != ifnum;
+ iface = iface->next)
+ ;
+ if (iface)
+ {
+ SSH_ASSERT(iface->generation == interceptor->if_generation);
+ dev = iface->dev;
+ SSH_ASSERT(dev != NULL);
+ dev_hold(dev);
+ if (context_return != NULL)
+ *context_return = iface->context;
+ }
+ read_unlock(&interceptor->if_table_lock);
+
+ return dev;
+}
+
+inline struct net_device *
+ssh_interceptor_ifnum_to_netdev(SshInterceptor interceptor,
+ SshUInt32 ifnum)
+{
+ return ssh_interceptor_ifnum_to_netdev_ctx(interceptor, ifnum, NULL);
+}
+
+/* Decrement the reference count of net_device 'dev'. */
+inline void
+ssh_interceptor_release_netdev(struct net_device *dev)
+{
+ SSH_ASSERT(dev != NULL);
+ dev_put(dev);
+}
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+/* Map Linux media types (actually extensions of ARP hardware
+ address types) to platform independent denominations. */
+static SshInterceptorMedia
+ssh_interceptor_media_type(unsigned short type)
+{
+ switch (type)
+ {
+ case ARPHRD_ETHER:
+ return SSH_INTERCEPTOR_MEDIA_ETHERNET;
+ case ARPHRD_IEEE802:
+ return SSH_INTERCEPTOR_MEDIA_FDDI;
+ default:
+ return SSH_INTERCEPTOR_MEDIA_PLAIN;
+ }
+ SSH_NOTREACHED;
+ return SSH_INTERCEPTOR_MEDIA_PLAIN;
+}
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+
+/* Sends the updated interface information to the engine. This is called
+ at the initialization of the interceptor and whenever the status of any
+ interface changes.
+
+ This function grabs 'if_table_lock' (for reading) and 'interceptor_lock'.
+*/
+static void
+ssh_interceptor_send_interfaces(SshInterceptor interceptor)
+{
+ SshInterceptorInternalInterface iface;
+ SshInterceptorInterface *ifarray;
+ SshInterceptorInterfacesCB interfaces_callback;
+ struct net_device *dev;
+ struct in_device *inet_dev = NULL;
+ struct in_ifaddr *addr;
+ int num, n, count, i, hashvalue;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ struct inet6_dev *inet6_dev = NULL;
+ struct inet6_ifaddr *addr6, *next_addr6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Grab 'if_table_lock' for reading the interface table. */
+ read_lock(&interceptor->if_table_lock);
+
+ num = interceptor->if_table_size;
+ if (!num)
+ {
+ read_unlock(&interceptor->if_table_lock);
+ SSH_DEBUG(4, ("No interfaces to report."));
+ return;
+ }
+
+ /* Allocate temporary memory for the table that is
+ passed to the engine. This is mallocated, since
+ we want to minimise stack usage. */
+ ifarray = ssh_malloc(sizeof(*ifarray) * num);
+ if (ifarray == NULL)
+ {
+ read_unlock(&interceptor->if_table_lock);
+ return;
+ }
+ memset(ifarray, 0, sizeof(*ifarray) * num);
+
+ /* Iterate over the slots of the iface hashtable. */
+ n = 0;
+ for (hashvalue = 0; hashvalue < SSH_LINUX_IFACE_HASH_SIZE; hashvalue++)
+ {
+
+ /* Iterate over the chain of iface entries in a hashtable slot. */
+ for (iface = interceptor->if_hash[hashvalue];
+ iface != NULL;
+ iface = iface->next)
+ {
+ /* Ignore devices that are not up. */
+ dev = iface->dev;
+ if (dev == NULL || !(dev->flags & IFF_UP))
+ continue;
+
+ /* Disable net_device features that vpnclient does not support */
+ if (dev->features & NETIF_F_TSO)
+ dev->features &= ~NETIF_F_TSO;
+
+#ifdef LINUX_HAS_NETIF_F_GSO
+ /* Disable net_device features that vpnclient does not support */
+ if (dev->features & NETIF_F_GSO)
+ {
+ SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported "
+ "feature NETIF_F_GSO",
+ (unsigned long) iface->ifindex,
+ (dev->name ? dev->name : "<none>")));
+ dev->features &= ~NETIF_F_GSO;
+ }
+#endif /* LINUX_HAS_NETIF_F_GSO */
+
+#ifdef LINUX_HAS_NETIF_F_TSO6
+ /* Disable net_device features that vpnclient does not support */
+ if (dev->features & NETIF_F_TSO6)
+ {
+ SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported "
+ "feature NETIF_F_TSO6",
+ (unsigned long) iface->ifindex,
+ (dev->name ? dev->name : "<none>")));
+ dev->features &= ~NETIF_F_TSO6;
+ }
+#endif /* LINUX_HAS_NETIF_F_TSO6 */
+
+#ifdef LINUX_HAS_NETIF_F_TSO_ECN
+ /* Disable net_device features that vpnclient does not support */
+ if (dev->features & NETIF_F_TSO_ECN)
+ {
+ SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported "
+ "feature NETIF_F_TSO_ECN",
+ (unsigned long) iface->ifindex,
+ (dev->name ? dev->name : "<none>")));
+ dev->features &= ~NETIF_F_TSO_ECN;
+ }
+#endif /* LINUX_HAS_NETIF_F_TSO_ECN */
+
+#ifdef LINUX_HAS_NETIF_F_GSO_ROBUST
+ /* Disable net_device features that vpnclient does not support */
+ if (dev->features & NETIF_F_GSO_ROBUST)
+ {
+ SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported "
+ "feature NETIF_F_GSO_ROBUST",
+ (unsigned long) iface->ifindex,
+ (dev->name ? dev->name : "<none>")));
+ dev->features &= ~NETIF_F_GSO_ROBUST;
+ }
+#endif /* LINUX_HAS_NETIF_F_GSO_ROBUST */
+
+#ifdef LINUX_HAS_NETIF_F_UFO
+ if (dev->features & NETIF_F_UFO)
+ {
+ SSH_DEBUG(2, ("Warning: Interface %lu [%s], dropping unsupported "
+ "feature NETIF_F_UFO",
+ (unsigned long) iface->ifindex,
+ (dev->name ? dev->name : "<none>")));
+ dev->features &= ~NETIF_F_UFO;
+ }
+#endif /* LINUX_HAS_NETIF_F_UFO */
+
+ /* Count addresses */
+ count = 0;
+
+ /* Increment refcount to make sure the device does not disappear. */
+ inet_dev = in_dev_get(dev);
+ if (inet_dev)
+ {
+ /* Count the device's IPv4 addresses */
+ for (addr = inet_dev->ifa_list; addr != NULL;
+ addr = addr->ifa_next)
+ {
+ count++;
+ }
+ }
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ /* Increment refcount to make sure the device does not disappear. */
+ inet6_dev = in6_dev_get(dev);
+ if (inet6_dev)
+ {
+ /* Count the device's IPv6 addresses */
+ SSH_INET6_IFADDR_LIST_FOR_EACH(addr6,
+ next_addr6,
+ inet6_dev->addr_list)
+ {
+ count++;
+ }
+ }
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Fill interface entry. */
+ ifarray[n].ifnum = iface->ifindex;
+ ifarray[n].to_protocol.flags =
+ SSH_INTERCEPTOR_MEDIA_INFO_NO_FRAGMENT;
+ ifarray[n].to_protocol.mtu_ipv4 = dev->mtu;
+ ifarray[n].to_adapter.flags = 0;
+ ifarray[n].to_adapter.mtu_ipv4 = dev->mtu;
+
+#ifdef WITH_IPV6
+ ifarray[n].to_adapter.mtu_ipv6 = dev->mtu;
+ ifarray[n].to_protocol.mtu_ipv6 = dev->mtu;
+#endif /* WITH_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ ifarray[n].to_adapter.media = ssh_interceptor_media_type(dev->type);
+ ifarray[n].to_protocol.media = ssh_interceptor_media_type(dev->type);
+#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ ifarray[n].to_adapter.media = SSH_INTERCEPTOR_MEDIA_PLAIN;
+ ifarray[n].to_protocol.media = SSH_INTERCEPTOR_MEDIA_PLAIN;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ strncpy(ifarray[n].name, dev->name, 15);
+
+ /* Set interface type and link status. */
+ if (dev->flags & IFF_POINTOPOINT)
+ ifarray[n].flags |= SSH_INTERFACE_FLAG_POINTOPOINT;
+ if (dev->flags & IFF_BROADCAST)
+ ifarray[n].flags |= SSH_INTERFACE_FLAG_BROADCAST;
+ if (!netif_carrier_ok(dev))
+ ifarray[n].flags |= SSH_INTERFACE_FLAG_LINK_DOWN;
+
+ ifarray[n].num_addrs = count;
+ ifarray[n].addrs = NULL;
+
+ /* Add addresses to interface entry. */
+ if (count > 0)
+ {
+ ifarray[n].addrs = ssh_malloc(sizeof(*ifarray[n].addrs) * count);
+
+ if (ifarray[n].addrs == NULL)
+ {
+ /* Release INET/INET6 devices */
+ if (inet_dev)
+ in_dev_put(inet_dev);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (inet6_dev)
+ in6_dev_put(inet6_dev);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ read_unlock(&interceptor->if_table_lock);
+ goto out;
+ }
+ count = 0;
+ if (inet_dev)
+ {
+ /* Put the IPv4 addresses */
+ for (addr = inet_dev->ifa_list;
+ addr != NULL;
+ addr = addr->ifa_next)
+ {
+ ifarray[n].addrs[count].protocol = SSH_PROTOCOL_IP4;
+ SSH_IP4_DECODE(&ifarray[n].addrs[count].addr.ip.ip,
+ &addr->ifa_local);
+ SSH_IP4_DECODE(&ifarray[n].addrs[count].addr.ip.mask,
+ &addr->ifa_mask);
+
+#if 0
+
+
+
+ if (dev->flags & IFF_POINTOPOINT)
+ SSH_IP4_DECODE(&ifarray[n].addrs[count].
+ addr.ip.broadcast,
+ &addr->ifa_address);
+ else
+#endif /* 0 */
+ if (dev->flags & IFF_BROADCAST)
+ SSH_IP4_DECODE(&ifarray[n].addrs[count].
+ addr.ip.broadcast,
+ &addr->ifa_broadcast);
+ else
+ SSH_IP_UNDEFINE(&ifarray[n].addrs[count].
+ addr.ip.broadcast);
+ count++;
+ }
+ }
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (inet6_dev)
+ {
+ /* Put the IPv6 addresses */
+ SSH_INET6_IFADDR_LIST_FOR_EACH(addr6,
+ next_addr6,
+ inet6_dev->addr_list)
+ {
+ ifarray[n].addrs[count].protocol = SSH_PROTOCOL_IP6;
+
+ SSH_IP6_DECODE(&ifarray[n].addrs[count].addr.ip.ip,
+ &addr6->addr);
+
+ /* Generate mask from prefix length and IPv6 addr */
+ SSH_IP6_DECODE(&ifarray[n].addrs[count].addr.ip.mask,
+ "\xff\xff\xff\xff\xff\xff\xff\xff"
+ "\xff\xff\xff\xff\xff\xff\xff\xff");
+ ssh_ipaddr_set_bits(&ifarray[n].addrs[count].
+ addr.ip.mask,
+ &ifarray[n].addrs[count].
+ addr.ip.mask,
+ addr6->prefix_len, 0);
+
+ /* Set the broadcast address to the IPv6
+ undefined address */
+ SSH_IP6_DECODE(&ifarray[n].addrs[count].
+ addr.ip.broadcast,
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00");
+
+ /* Copy the ifnum in case of ipv6 to scope_id, since
+ in linux scope_id == ifnum. */
+ ifarray[n].addrs[count].addr.ip.ip.
+ scope_id.scope_id_union.ui32 = ifarray[n].ifnum;
+ count++;
+ }
+ }
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ }
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* Grab the MAC address */
+ ifarray[n].media_addr_len = dev->addr_len;
+ SSH_ASSERT(dev->addr_len <= sizeof(ifarray[n].media_addr));
+ memcpy(&ifarray[n].media_addr[0], dev->dev_addr, dev->addr_len);
+#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ ifarray[n].media_addr[0] = 0;
+ ifarray[n].media_addr_len = 0;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Release INET/INET6 devices */
+ if (inet_dev)
+ in_dev_put(inet_dev);
+ inet_dev = NULL;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (inet6_dev)
+ in6_dev_put(inet6_dev);
+ inet6_dev = NULL;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ ssh_kernel_mutex_lock(interceptor->interceptor_lock);
+ for (i = 0; i < SSH_LINUX_MAX_VIRTUAL_ADAPTERS; i++)
+ {
+ SshVirtualAdapter adapter = interceptor->virtual_adapters[i];
+ if (adapter && adapter->dev->ifindex == ifarray[n].ifnum)
+ {
+ ifarray[n].flags |= SSH_INTERFACE_FLAG_VIP;
+ break;
+ }
+ }
+ ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
+
+ /* Done with the interface entry,
+ increase ifarray index and continue with next iface. */
+ n++;
+
+ } /* for (iface entry chain iteration) */
+ } /* for (hashtable iteration) */
+
+ /* Release if_table lock. */
+ read_unlock(&interceptor->if_table_lock);
+
+ SSH_ASSERT(n <= num);
+
+ /* 'interceptor_lock' protects 'num_interface_callbacks' and
+ 'interfaces_callback'. */
+ ssh_kernel_mutex_lock(interceptor->interceptor_lock);
+ interceptor->num_interface_callbacks++;
+ interfaces_callback = interceptor->interfaces_callback;
+ ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
+
+ /* Call the interface callback. */
+ (interfaces_callback)(n, ifarray, interceptor->callback_context);
+
+ ssh_kernel_mutex_lock(interceptor->interceptor_lock);
+ interceptor->num_interface_callbacks--;
+ ssh_kernel_mutex_unlock(interceptor->interceptor_lock);
+
+out:
+ /* Free the array. */
+ for (i = 0; i < n; i++)
+ {
+ if (ifarray[i].addrs != NULL)
+ ssh_free(ifarray[i].addrs);
+ }
+ ssh_free(ifarray);
+
+ return;
+}
+
+/* Caller must hold 'if_table_lock' for writing */
+static SshInterceptorInternalInterface
+ssh_interceptor_alloc_iface(SshInterceptor interceptor)
+{
+ SshInterceptorInternalInterface iface, new_table;
+ SshUInt32 i, n, new_table_size, hashvalue;
+
+ iface = NULL;
+
+ relookup:
+ /* Find empty slot in the if_table */
+ for (i = 0; i < interceptor->if_table_size; i++)
+ {
+ iface = &interceptor->if_table[i];
+ if (iface->generation == 0)
+ {
+#ifdef DEBUG_LIGHT
+ iface->dev = NULL;
+ iface->ifindex = SSH_INTERCEPTOR_INVALID_IFNUM;
+ iface->next = NULL;
+#endif /* DEBUG_LIGHT */
+ iface->context = NULL;
+ return iface;
+ }
+ }
+
+ /* No free slots, reallocate if_table */
+ if (interceptor->if_table_size >= SSH_LINUX_IFACE_TABLE_SIZE)
+ return NULL; /* Maximum table size reached. */
+
+ new_table_size = interceptor->if_table_size + 10;
+ if (new_table_size > SSH_LINUX_IFACE_TABLE_SIZE)
+ new_table_size = SSH_LINUX_IFACE_TABLE_SIZE;
+
+ new_table = ssh_malloc(sizeof(*new_table) * new_table_size);
+ if (new_table == NULL)
+ return NULL;
+
+ /* Copy existing entries from if_hash */
+ n = 0;
+ for (i = 0; i < SSH_LINUX_IFACE_HASH_SIZE; i++)
+ {
+ for (iface = interceptor->if_hash[i]; iface; iface = iface->next)
+ {
+ new_table[n].next = NULL;
+ new_table[n].ifindex = iface->ifindex;
+ new_table[n].dev = iface->dev;
+ new_table[n].generation = iface->generation;
+ n++;
+ }
+ }
+
+ /* Initialize the rest of the new interface table */
+ for (; n < new_table_size; n++)
+ {
+ new_table[n].generation = 0;
+#ifdef DEBUG_LIGHT
+ new_table[n].dev = NULL;
+ new_table[n].ifindex = SSH_INTERCEPTOR_INVALID_IFNUM;
+ new_table[n].next = NULL;
+#endif /* DEBUG_LIGHT */
+ new_table[n].context = NULL;
+ }
+
+ /* Rebuild hashtable */
+ memset(interceptor->if_hash, 0, sizeof(interceptor->if_hash));
+ for (i = 0; i < new_table_size; i++)
+ {
+ if (new_table[i].generation == 0)
+ continue;
+
+ hashvalue = new_table[i].ifindex % SSH_LINUX_IFACE_HASH_SIZE;
+ new_table[i].next = interceptor->if_hash[hashvalue];
+ interceptor->if_hash[hashvalue] = &new_table[i];
+ }
+
+ /* Free old if_table */
+ ssh_free(interceptor->if_table);
+ wmb();
+ interceptor->if_table = new_table;
+ interceptor->if_table_size = new_table_size;
+
+ goto relookup;
+}
+
+/* The interceptor_update_interfaces() function traverses the kernels
+ list of interfaces, grabs a refcnt for each one, and updates the
+ interceptors 'ifnum->net_device' cache (optimizing away the need to
+ grab a lock, traverse dev_base linked list, unlock, for each
+ packet).
+
+ This function grabs 'if_table_lock' (for writing) and dev_base lock. */
+static void
+ssh_interceptor_update_interfaces(SshInterceptor interceptor)
+{
+ SshInterceptorInternalInterface iface, iface_prev, iface_next;
+ struct net_device *dev;
+ SshUInt32 i, hashvalue, ifindex;
+
+ /* WARNING: TWO LOCKS HELD AT THE SAME TIME. BE CAREFUL!
+ dev_base_lock MUST be held to ensure integrity during traversal
+ of list of interfaces in kernel. */
+ SSH_LOCK_LINUX_DEV_LIST();
+
+ /* Grab 'if_table_lock' for modifying the interface table. */
+ write_lock(&interceptor->if_table_lock);
+
+ /* Increment 'if_generation' */
+ interceptor->if_generation++;
+ if (interceptor->if_generation == 0)
+ interceptor->if_generation++; /* Handle wrapping */
+
+ /* Traverse net_device list, add new interfaces to hashtable,
+ and mark existing entries up-to-date. */
+ for (dev = SSH_FIRST_NET_DEVICE();
+ dev != NULL;
+ dev = SSH_NEXT_NET_DEVICE(dev))
+ {
+ ifindex = (SshUInt32) dev->ifindex;
+
+ /* Ignore the loopback device. */
+ if (dev->flags & IFF_LOOPBACK)
+ continue;
+
+ /* Ignore interfaces that collide with SSH_INTERCEPTOR_INVALID_IFNUM */
+ if (ifindex == SSH_INTERCEPTOR_INVALID_IFNUM)
+ {
+ SSH_DEBUG(2, ("Interface index collides with "
+ "SSH_INTERCEPTOR_INVALID_IFNUM, "
+ "ignoring interface %lu[%s]",
+ (unsigned long) ifindex,
+ (dev->name ? dev->name : "<none>")));
+ continue;
+ }
+
+ /* Assert that 'dev->ifindex' is otherwise valid. */
+ SSH_LINUX_ASSERT_VALID_IFNUM(ifindex);
+
+ /* Lookup interface from the hashtable. */
+ for (iface =
+ interceptor->if_hash[ifindex % SSH_LINUX_IFACE_HASH_SIZE];
+ iface != NULL && iface->ifindex != ifindex;
+ iface = iface->next)
+ ;
+
+ /* Interface found */
+ if (iface)
+ {
+ if (iface->dev == dev)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("Old interface %lu[%s]",
+ (unsigned long) ifindex,
+ (dev->name ? dev->name : "<none>")));
+
+ /* Mark up-to-date. */
+ iface->generation = interceptor->if_generation;
+ }
+
+ /* Interface index matches, but net_device has changed. */
+ else
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("Changed interface %lu[%s] (from %lu[%s])",
+ (unsigned long) ifindex,
+ (dev->name ? dev->name : "<none>"),
+ (unsigned long) iface->ifindex,
+ (iface->dev->name ? iface->dev->name : "<none>")));
+
+ /* Release old net_device. */
+ SSH_ASSERT(iface->dev != NULL);
+ dev_put(iface->dev);
+ /* Hold new net_device. */
+ dev_hold(dev);
+ wmb(); /* Make sure assignments are not reordered. */
+ iface->dev = dev;
+ iface->ifindex = ifindex;
+ iface->context = NULL;
+ /* Mark up-to-date. */
+ iface->generation = interceptor->if_generation;
+ }
+ }
+
+ /* Interface not found */
+ else
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("New interface %lu[%s]",
+ (unsigned long) ifindex,
+ (dev->name ? dev->name : "<none>")));
+
+ /* Allocate new interface entry */
+ iface = ssh_interceptor_alloc_iface(interceptor);
+ if (iface)
+ {
+ /* Hold new net_device. */
+ dev_hold(dev);
+ /* Fill interface entry. */
+ iface->ifindex = ifindex;
+ iface->dev = dev;
+ iface->context = NULL;
+ /* Mark up-to-date */
+ iface->generation = interceptor->if_generation;
+ /* Add entry to hashtable. */
+ hashvalue = iface->ifindex % SSH_LINUX_IFACE_HASH_SIZE;
+ iface->next = interceptor->if_hash[hashvalue];
+ wmb();
+ interceptor->if_hash[hashvalue] = iface;
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Could not allocate memory for new interface %lu[%s]",
+ (unsigned long) ifindex,
+ (dev->name ? dev->name : "<none>")));
+ }
+ }
+ }
+
+ /* Remove old interfaces from the table */
+ for (i = 0; i < SSH_LINUX_IFACE_HASH_SIZE; i++)
+ {
+ iface_prev = NULL;
+ for (iface = interceptor->if_hash[i];
+ iface != NULL;
+ iface = iface_next)
+ {
+ if (iface->generation != 0 &&
+ iface->generation != interceptor->if_generation)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("Disappeared interface %lu[%s]",
+ (unsigned long) iface->ifindex,
+ (iface->dev->name ? iface->dev->name : "<none>")));
+
+ /* Release old netdevice */
+ SSH_ASSERT(iface->dev != NULL);
+ dev_put(iface->dev);
+
+#ifdef DEBUG_LIGHT
+ wmb();
+ iface->dev = NULL;
+ iface->ifindex = SSH_INTERCEPTOR_INVALID_IFNUM;
+#endif /* DEBUG_LIGHT */
+
+ /* Mark entry freed. */
+ iface->generation = 0;
+
+ /* Remove entry from hashtable. */
+ if (iface_prev)
+ iface_prev->next = iface->next;
+ else
+ interceptor->if_hash[i] = iface->next;
+ iface_next = iface->next;
+ iface->next = NULL;
+ }
+ else
+ {
+ iface_prev = iface;
+ iface_next = iface->next;
+ }
+ }
+ }
+ /* Unlock if_table_lock */
+ write_unlock(&interceptor->if_table_lock);
+
+ /* Release dev_base_lock. */
+ SSH_UNLOCK_LINUX_DEV_LIST();
+
+ /* Notify changes to engine */
+ if (interceptor->engine != NULL && interceptor->engine_open == TRUE)
+ {
+ local_bh_disable();
+ ssh_interceptor_send_interfaces(interceptor);
+ local_bh_enable();
+ }
+
+ return;
+}
+
+
+/* Callback for interface and address notifier. */
+void
+ssh_interceptor_receive_ifaces(SshInterceptor interceptor)
+{
+ local_bh_disable();
+ ssh_interceptor_update_interfaces(interceptor);
+ local_bh_enable();
+
+ return;
+}
+
+
+/* Release all refcounts and free the 'ifnum->net_device' map cache. */
+void
+ssh_interceptor_clear_ifaces(SshInterceptor interceptor)
+{
+ int i;
+ SshInterceptorInternalInterface iface;
+
+ /* At this point. All hooks must have completed running! */
+
+ SSH_LOCK_LINUX_DEV_LIST();
+ write_lock(&interceptor->if_table_lock);
+
+ /* Release net_devices and free if_table. */
+ if (interceptor->if_table != NULL)
+ {
+ for (i = 0; i < interceptor->if_table_size; i++)
+ {
+ iface = &interceptor->if_table[i];
+ if (iface->generation != 0)
+ {
+ SSH_ASSERT(iface->dev != NULL);
+ dev_put(iface->dev);
+#ifdef DEBUG_LIGHT
+ wmb();
+ iface->dev = NULL;
+#endif /* DEBUG_LIGHT */
+ iface->generation = 0;
+ }
+ }
+
+ ssh_free(interceptor->if_table);
+ interceptor->if_table = NULL;
+ }
+
+ /* Clear interface hashtable. */
+ memset(interceptor->if_hash, 0, sizeof(interceptor->if_hash));
+
+ write_unlock(&interceptor->if_table_lock);
+ SSH_UNLOCK_LINUX_DEV_LIST();
+}
+
+
+/*************************** Module init / uninit **************************/
+
+Boolean ssh_interceptor_iface_init(SshInterceptor interceptor)
+{
+ /* This will register notifier that notifies about bringing the
+ interface up and down. */
+ interceptor->notifier_netdev.notifier_call =
+ ssh_interceptor_notifier_callback;
+ interceptor->notifier_netdev.priority = 1;
+ interceptor->notifier_netdev.next = NULL;
+ register_netdevice_notifier(&interceptor->notifier_netdev);
+
+ /* This will register notifier that notifies when address of the
+ interface changes without bringing the interface down. */
+ interceptor->notifier_inetaddr.notifier_call =
+ ssh_interceptor_notifier_callback;
+ interceptor->notifier_inetaddr.priority = 1;
+ interceptor->notifier_inetaddr.next = NULL;
+ register_inetaddr_notifier(&interceptor->notifier_inetaddr);
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ interceptor->notifier_inet6addr.notifier_call =
+ ssh_interceptor_notifier_callback;
+ interceptor->notifier_inet6addr.priority = 1;
+ interceptor->notifier_inet6addr.next = NULL;
+ register_inet6addr_notifier(&interceptor->notifier_inet6addr);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ interceptor->iface_notifiers_installed = TRUE;
+
+ /* Send interface information to engine. This causes the interceptor
+ to grab reference to each net_device. On error cases
+ ssh_interceptor_clear_ifaces() or ssh_interceptor_iface_uninit()
+ must be called to release the references. */
+ ssh_interceptor_receive_ifaces(interceptor);
+
+ return TRUE;
+}
+
+void ssh_interceptor_iface_uninit(SshInterceptor interceptor)
+{
+ if (interceptor->iface_notifiers_installed)
+ {
+ local_bh_enable();
+
+ /* Unregister notifier callback */
+ unregister_netdevice_notifier(&interceptor->notifier_netdev);
+ unregister_inetaddr_notifier(&interceptor->notifier_inetaddr);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ unregister_inet6addr_notifier(&interceptor->notifier_inet6addr);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ local_bh_disable();
+ }
+
+ /* Clear interface table and release net_device references. */
+ ssh_interceptor_clear_ifaces(interceptor);
+
+ /* Due to lack of proper locking in linux kernel notifier code,
+ the unregister_*_notifier functions might leave the notifier
+ blocks out of sync, resulting in that the kernel may call an
+ unregistered notifier function.
+
+ Sleep for a while to decrease the possibility of the bug causing
+ trouble (crash during module unloading). */
+ mdelay(500);
+
+ interceptor->iface_notifiers_installed = FALSE;
+}
diff --git a/drivers/interceptor/linux_internal.h b/drivers/interceptor/linux_internal.h
new file mode 100644
index 0000000..ebd0c9f
--- /dev/null
+++ b/drivers/interceptor/linux_internal.h
@@ -0,0 +1,819 @@
+/* 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_internal.h
+ *
+ * Internal header file for the linux interceptor.
+ *
+ */
+
+#ifndef LINUX_INTERNAL_H
+#define LINUX_INTERNAL_H
+
+#include "sshincludes.h"
+
+/* Parameters used to tune the interceptor. */
+#include "linux_versions.h"
+#include "linux_params.h"
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/pkt_sched.h>
+
+#include <linux/interrupt.h>
+#include <linux/inetdevice.h>
+
+#include <net/ip.h>
+#include <net/inet_common.h>
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#include <linux/if_arp.h>
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter_arp.h>
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+#include <linux/cpumask.h>
+#include <linux/rcupdate.h>
+
+#ifdef LINUX_NEED_IF_ADDR_H
+#include <linux/if_addr.h>
+#endif /* LINUX_NEED_IF_ADDR_H */
+
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/inet_common.h>
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#include <net/flow.h>
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#include <linux/threads.h>
+
+#include "engine.h"
+#include "kernel_includes.h"
+#include "kernel_mutex.h"
+#include "interceptor.h"
+#include "sshinet.h"
+#include "sshdebug.h"
+
+#include "linux_packet_internal.h"
+#include "linux_mutex_internal.h"
+#include "linux_virtual_adapter_internal.h"
+
+/****************************** Sanity checks ********************************/
+
+#ifndef MODULE
+#error "VPNClient can only be compiled as a MODULE"
+#endif /* MODULE */
+
+#ifndef CONFIG_NETFILTER
+#error "Kernel is not compiled with CONFIG_NETFILTER"
+#endif /* CONFIG_NETFILTER */
+
+/* Check that SSH_LINUX_FWMARK_EXTENSION_SELECTOR is in range. */
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#if (SSH_LINUX_FWMARK_EXTENSION_SELECTOR >= \
+ SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS)
+#error "Invalid value specified for SSH_LINUX_FWMARK_EXTENSION_SELECTOR"
+#endif
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+
+
+/****************************** Internal defines *****************************/
+
+#define SSH_LINUX_INTERCEPTOR_NR_CPUS NR_CPUS
+
+/* Flags for ssh_engine_start */
+#define SSH_LINUX_ENGINE_FLAGS 0
+
+#define SSH_LINUX_INTERCEPTOR_MODULE_DESCRIPTION "VPNClient"
+
+/********************** Kernel version specific wrapper macros ***************/
+
+#ifdef LINUX_HAS_DEV_GET_FLAGS
+#define SSH_LINUX_DEV_GET_FLAGS(dev) dev_get_flags(dev)
+#else /* LINUX_HAS_DEV_GET_FLAGS */
+#define SSH_LINUX_DEV_GET_FLAGS(dev) ((dev)->flags)
+#endif /* LINUX_HAS_DEV_GET_FLAGS */
+
+#ifdef LINUX_NF_HOOK_SKB_IS_POINTER
+typedef struct sk_buff SshHookSkb;
+#define SSH_HOOK_SKB_PTR(_skb) _skb
+#else /* LINUX_NF_HOOK_SKB_IS_POINTER */
+typedef struct sk_buff *SshHookSkb;
+#define SSH_HOOK_SKB_PTR(_skb) *_skb
+#endif /* LINUX_NF_HOOK_SKB_IS_POINTER */
+
+#ifdef LINUX_HAS_SKB_MARK
+#define SSH_SKB_MARK(__skb) ((__skb)->mark)
+#else /* LINUX_HAS_SKB_MARK */
+#define SSH_SKB_MARK(__skb) ((__skb)->nfmark)
+#endif /* LINUX_HAS_SKB_MARK */
+
+#ifdef LINUX_HAS_DST_MTU
+#define SSH_DST_MTU(__dst) (dst_mtu((__dst)))
+#else /* LINUX_HAS_DST_MTU */
+#define SSH_DST_MTU(__dst) (dst_pmtu((__dst)))
+#endif /* LINUX_HAS_DST_MTU */
+
+/* Before 2.6.22 kernels, the net devices were accessed
+ using directly global variables.
+ 2.6.22 -> 2.6.23 introduced new functions accessing
+ the net device list.
+ 2.6.24 -> these new functions started taking new
+ arguments. */
+#ifndef LINUX_HAS_NETDEVICE_ACCESSORS
+/* For 2.4.x -> 2.6.21 kernels */
+#define SSH_FIRST_NET_DEVICE() dev_base
+#define SSH_NEXT_NET_DEVICE(_dev) _dev->next
+
+#else /* LINUX_HAS_NETDEVICE_ACCESSORS */
+#ifndef LINUX_NET_DEVICE_HAS_ARGUMENT
+/* For 2.6.22 -> 2.6.23 kernels */
+#define SSH_FIRST_NET_DEVICE() first_net_device()
+#define SSH_NEXT_NET_DEVICE(_dev) next_net_device(_dev)
+
+#else /* LINUX_NET_DEVICE_HAS_ARGUMENT */
+/* For 2.6.24 -> kernels */
+#define SSH_FIRST_NET_DEVICE() first_net_device(&init_net)
+#define SSH_NEXT_NET_DEVICE(_dev) next_net_device(_dev)
+
+#endif /* LINUX_NET_DEVICE_HAS_ARGUMENT */
+#endif /* LINUX_HAS_NETDEVICE_ACCESSORS */
+
+/* This HAVE_NET_DEVICE_OPS was removed in 3.1.x */
+#ifdef LINUX_HAS_NET_DEVICE_OPS
+#ifndef HAVE_NET_DEVICE_OPS
+#define HAVE_NET_DEVICE_OPS 1
+#endif /* HAVE_NET_DEVICE_OPS */
+#endif /* LINUX_HAS_NET_DEVICE_OPS */
+
+#ifdef LINUX_NET_DEVICE_HAS_ARGUMENT
+#define SSH_DEV_GET_BY_INDEX(_i) dev_get_by_index(&init_net, (_i))
+#else /* LINUX_NET_DEVICE_HAS_ARGUMENT */
+#define SSH_DEV_GET_BY_INDEX(_i) dev_get_by_index((_i))
+#endif /* LINUX_NET_DEVICE_HAS_ARGUMENT */
+
+#ifdef LINUX_HAS_SKB_DATA_ACCESSORS
+/* On new kernel versions the skb->end, skb->tail, skb->network_header,
+ skb->mac_header, and skb->transport_header are either pointers to
+ skb->data (on 32bit platforms) or offsets from skb->data
+ (on 64bit platforms). */
+
+#define SSH_SKB_GET_END(__skb) (skb_end_pointer((__skb)))
+
+#define SSH_SKB_GET_TAIL(__skb) (skb_tail_pointer((__skb)))
+#define SSH_SKB_SET_TAIL(__skb, __ptr) \
+ (skb_set_tail_pointer((__skb), (__ptr) - (__skb)->data))
+#define SSH_SKB_RESET_TAIL(__skb) (skb_reset_tail_pointer((__skb)))
+
+#define SSH_SKB_GET_NETHDR(__skb) (skb_network_header((__skb)))
+#define SSH_SKB_SET_NETHDR(__skb, __ptr) \
+ (skb_set_network_header((__skb), (__ptr) - (__skb)->data))
+#define SSH_SKB_RESET_NETHDR(__skb) (skb_reset_network_header((__skb)))
+
+#define SSH_SKB_GET_MACHDR(__skb) (skb_mac_header((__skb)))
+#define SSH_SKB_SET_MACHDR(__skb, __ptr) \
+ (skb_set_mac_header((__skb), (__ptr) - (__skb)->data))
+#define SSH_SKB_RESET_MACHDR(__skb) (skb_reset_mac_header((__skb)))
+
+#define SSH_SKB_GET_TRHDR(__skb) (skb_transport_header((__skb)))
+#define SSH_SKB_SET_TRHDR(__skb, __ptr) \
+ (skb_set_transport_header((__skb), (__ptr) - (__skb)->data))
+#define SSH_SKB_RESET_TRHDR(__skb) (skb_reset_transport_header((__skb)))
+
+#else /* LINUX_HAS_SKB_DATA_ACCESSORS */
+
+#define SSH_SKB_GET_END(__skb) ((__skb)->end)
+
+#define SSH_SKB_GET_TAIL(__skb) ((__skb)->tail)
+#define SSH_SKB_SET_TAIL(__skb, __ptr) ((__skb)->tail = (__ptr))
+#define SSH_SKB_RESET_TAIL(__skb) ((__skb)->tail = NULL)
+
+#define SSH_SKB_GET_NETHDR(__skb) ((__skb)->nh.raw)
+#define SSH_SKB_SET_NETHDR(__skb, __ptr) ((__skb)->nh.raw = (__ptr))
+#define SSH_SKB_RESET_NETHDR(__skb) ((__skb)->nh.raw = NULL)
+
+#define SSH_SKB_GET_MACHDR(__skb) ((__skb)->mac.raw)
+#define SSH_SKB_SET_MACHDR(__skb, __ptr) ((__skb)->mac.raw = (__ptr))
+#define SSH_SKB_RESET_MACHDR(__skb) ((__skb)->mac.raw = NULL)
+
+#define SSH_SKB_GET_TRHDR(__skb) ((__skb)->h.raw)
+#define SSH_SKB_SET_TRHDR(__skb, __ptr) ((__skb)->h.raw = (__ptr))
+#define SSH_SKB_RESET_TRHDR(__skb) ((__skb)->h.raw = NULL)
+
+#endif /* LINUX_HAS_SKB_DATA_ACCESSORS */
+
+#ifdef LINUX_HAS_SKB_CSUM_OFFSET
+/* On linux-2.6.20 and later skb->csum is split into
+ a union of csum and csum_offset. */
+#define SSH_SKB_CSUM_OFFSET(__skb) ((__skb)->csum_offset)
+#define SSH_SKB_CSUM(__skb) ((__skb)->csum)
+#else /* LINUX_HAS_SKB_CSUM_OFFSET */
+#define SSH_SKB_CSUM_OFFSET(__skb) ((__skb)->csum)
+#define SSH_SKB_CSUM(__skb) ((__skb)->csum)
+#endif /* LINUX_HAS_SKB_CSUM_OFFSET */
+
+#ifdef LINUX_NF_INET_HOOKNUMS
+
+#define SSH_NF_IP_PRE_ROUTING NF_INET_PRE_ROUTING
+#define SSH_NF_IP_LOCAL_IN NF_INET_LOCAL_IN
+#define SSH_NF_IP_FORWARD NF_INET_FORWARD
+#define SSH_NF_IP_LOCAL_OUT NF_INET_LOCAL_OUT
+#define SSH_NF_IP_POST_ROUTING NF_INET_POST_ROUTING
+#define SSH_NF_IP_PRI_FIRST INT_MIN
+
+#define SSH_NF_IP6_PRE_ROUTING NF_INET_PRE_ROUTING
+#define SSH_NF_IP6_LOCAL_IN NF_INET_LOCAL_IN
+#define SSH_NF_IP6_FORWARD NF_INET_FORWARD
+#define SSH_NF_IP6_LOCAL_OUT NF_INET_LOCAL_OUT
+#define SSH_NF_IP6_POST_ROUTING NF_INET_POST_ROUTING
+#define SSH_NF_IP6_PRI_FIRST INT_MIN
+
+#else /* LINUX_UNIFIED_NETFILTER_IP_HOOKNUMS */
+
+#define SSH_NF_IP_PRE_ROUTING NF_IP_PRE_ROUTING
+#define SSH_NF_IP_LOCAL_IN NF_IP_LOCAL_IN
+#define SSH_NF_IP_FORWARD NF_IP_FORWARD
+#define SSH_NF_IP_LOCAL_OUT NF_IP_LOCAL_OUT
+#define SSH_NF_IP_POST_ROUTING NF_IP_POST_ROUTING
+#define SSH_NF_IP_PRI_FIRST NF_IP_PRI_FIRST
+
+#define SSH_NF_IP6_PRE_ROUTING NF_IP6_PRE_ROUTING
+#define SSH_NF_IP6_LOCAL_IN NF_IP6_LOCAL_IN
+#define SSH_NF_IP6_FORWARD NF_IP6_FORWARD
+#define SSH_NF_IP6_LOCAL_OUT NF_IP6_LOCAL_OUT
+#define SSH_NF_IP6_POST_ROUTING NF_IP6_POST_ROUTING
+#define SSH_NF_IP6_PRI_FIRST NF_IP6_PRI_FIRST
+
+#endif /* LINUX_NF_INET_HOOKNUMS */
+
+#ifdef LINUX_HAS_NFPROTO_ARP
+#define SSH_NFPROTO_ARP NFPROTO_ARP
+#else /* LINUX_HAS_NFPROTO_ARP */
+#define SSH_NFPROTO_ARP NF_ARP
+#endif /* LINUX_HAS_NFPROTO_ARP */
+
+/*
+ Since 2.6.31 there is now skb->dst pointer and
+ functions skb_dst() and skb_dst_set() should be used.
+
+ The code is modified to use the functions. For older
+ version corresponding macros are defined.
+ */
+#ifdef LINUX_HAS_SKB_DST_FUNCTIONS
+#define SSH_SKB_DST(__skb) skb_dst((__skb))
+#define SSH_SKB_DST_SET(__skb, __dst) skb_dst_set((__skb), (__dst))
+#else /* LINUX_HAS_SKB_DST_FUNCTIONS */
+#define SSH_SKB_DST(__skb) ((__skb)->dst)
+#define SSH_SKB_DST_SET(__skb, __dst) ((void)((__skb)->dst = (__dst)))
+#endif /* LINUX_HAS_SKB_DST_FUNCTIONS */
+
+#ifdef IP6CB
+#define SSH_LINUX_IP6CB(skbp) IP6CB(skbp)
+#else /* IP6CB */
+#define SSH_LINUX_IP6CB(skbp) ((struct inet6_skb_parm *) ((skbp)->cb))
+#endif /* IP6CB */
+
+/* Stating from linux 2.6.35 the IPv6 address list needs to be iterated
+ using the list_for_each_* macros. */
+#ifdef LINUX_RT_DST_IS_NOT_IN_UNION
+#define SSH_RT_DST(_rt) ((_rt)->dst)
+#else /* LINUX_RT_DST_IS_NOT_IN_UNION */
+#define SSH_RT_DST(_rt) ((_rt)->u.dst)
+#endif /* LINUX_RT_DST_IS_NOT_IN_UNION */
+
+/* Starting from linux 2.6.35 the IPv6 address list needs to be iterated
+ using the list_for_each_* macros. */
+#ifdef LINUX_HAS_INET6_IFADDR_LIST_HEAD
+#define SSH_INET6_IFADDR_LIST_FOR_EACH(item, next, list) \
+ list_for_each_entry_safe((item), (next), &(list), if_list)
+#else /* LINUX_HAS_INET6_IFADDR_LIST_HEAD */
+#define SSH_INET6_IFADDR_LIST_FOR_EACH(item, next, list) \
+ for ((item) = (list), (next) = NULL; \
+ (item) != NULL; \
+ (item) = (item)->if_next)
+#endif /* LINUX_HAS_INET6_IFADDR_LIST_HEAD */
+
+#if defined(LINUX_DST_ALLOC_HAS_MANY_ARGS)
+#define SSH_DST_ALLOC(_dst) dst_alloc((_dst)->ops, NULL, 0, 0, 0)
+#elif defined(LINUX_DST_ALLOC_HAS_REFCOUNT)
+#define SSH_DST_ALLOC(_dst) dst_alloc((_dst)->ops, 0)
+#else /* defined(LINUX_DST_ALLOC_HAS_REFCOUNT) */
+#define SSH_DST_ALLOC(_dst) dst_alloc((_dst)->ops)
+#endif /* defined(LINUX_DST_ALLOC_HAS_REFCOUNT) */
+
+#if defined(LINUX_SSH_RTABLE_FIRST_ELEMENT_NEEDED)
+#if LINUX_VERSION_CODE == KERNEL_VERSION(2,6,38)
+#define SSH_RTABLE_FIRST_MEMBER(_rt) ((_rt)->fl)
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+#define SSH_RTABLE_FIRST_MEMBER(_rt) ((_rt)->rt_key_dst)
+#endif /* LINUX_VERSION_CODE */
+#endif /* defined(LINUX_SSH_RTABLE_FIRST_ELEMENT_NEEDED) */
+
+/* Linux 2.6.39 removed fl4_dst and fl6_dst defines. We like to use
+ those so redefinig those for our purposes. */
+#ifdef LINUX_FLOWI_NO_FL4_ACCESSORS
+#define fl4_dst u.ip4.daddr
+#define fl4_src u.ip4.saddr
+#define fl4_tos u.ip4.__fl_common.flowic_tos
+#define oif u.ip4.__fl_common.flowic_oif
+#define proto u.ip4.__fl_common.flowic_proto
+#define fl4_scope u.ip4.__fl_common.flowic_scope
+#endif /* LINUX_FLOWI_NO_FL4_ACCESSORS */
+
+#ifdef LINUX_FLOWI_NO_FL6_ACCESSORS
+#define fl6_dst u.ip6.daddr
+#define fl6_src u.ip6.saddr
+#endif /* LINUX_FLOWI_NO_FL6_ACCESSORS */
+
+/****************************** Statistics helper macros *********************/
+
+#ifdef DEBUG_LIGHT
+
+#define SSH_LINUX_STATISTICS(interceptor, block) \
+do \
+ { \
+ if ((interceptor)) \
+ { \
+ spin_lock_bh(&(interceptor)->statistics_lock); \
+ block; \
+ spin_unlock_bh(&(interceptor)->statistics_lock); \
+ } \
+ } \
+while (0)
+
+#else /* DEBUG_LIGHT */
+
+#define SSH_LINUX_STATISTICS(interceptor, block)
+
+#endif /* DEBUG_LIGHT */
+
+/****************************** Interface handling ***************************/
+
+/* Sanity check that the interface index 'ifnum' fits into
+ the SshInterceptorIfnum data type. 'ifnum' may be equal to
+ SSH_INTERCEPTOR_INVALID_IFNUM. */
+#define SSH_LINUX_ASSERT_IFNUM(ifnum) \
+SSH_ASSERT(((SshUInt32) (ifnum)) < ((SshUInt32) SSH_INTERCEPTOR_MAX_IFNUM) \
+|| ((SshUInt32) (ifnum)) == ((SshUInt32) SSH_INTERCEPTOR_INVALID_IFNUM))
+
+/* Sanity check that the interface index 'ifnum' is a valid
+ SshInterceptorIfnum. */
+#define SSH_LINUX_ASSERT_VALID_IFNUM(ifnum) \
+SSH_ASSERT(((SshUInt32) (ifnum)) < ((SshUInt32) SSH_INTERCEPTOR_MAX_IFNUM) \
+&& ((SshUInt32) (ifnum)) != ((SshUInt32) SSH_INTERCEPTOR_INVALID_IFNUM))
+
+/* Interface structure for caching "ifindex->dev" mapping. */
+typedef struct SshInterceptorInternalInterfaceRec
+*SshInterceptorInternalInterface;
+
+struct SshInterceptorInternalInterfaceRec
+{
+ /* Next entry in the hashtable chain */
+ SshInterceptorInternalInterface next;
+ /* Interface index */
+ SshUInt32 ifindex;
+ /* Linux net_device structure */
+ struct net_device *dev;
+
+ /* This field is used to mark existing interfaces,
+ and to remove disappeared interfaces from the hashtable. */
+ SshUInt8 generation;
+
+ /* Pointer to private data. This is currently used only by Octeon. */
+ void *context;
+};
+
+/* Number of hashtable slots in the interface hashtable. */
+#define SSH_LINUX_IFACE_HASH_SIZE 256
+
+/* Maximum number of entries in the interface hashtable.
+ Currently equal to maximum interface number. */
+#define SSH_LINUX_IFACE_TABLE_SIZE SSH_INTERCEPTOR_MAX_IFNUM
+
+/****************************** Proc entries *********************************/
+
+#define SSH_PROC_ROOT "vpnclient"
+#define SSH_PROC_ENGINE "engine"
+#define SSH_PROC_VERSION "version"
+
+/* Ipm receive buffer size. This must be big enough to fit a maximum sized
+ IP packet + internal packet data + ipm message header. There is only
+ one receive buffer. */
+#define SSH_LINUX_IPM_RECV_BUFFER_SIZE 66000
+
+/* Ipm channel message structure. These structures are used for queueing
+ messages from kernel to userspace. The maximum number of allocated messages
+ is limited by SSH_LINUX_MAX_IPM_MESSAGES (in linux_params.h). */
+typedef struct SshInterceptorIpmMsgRec
+SshInterceptorIpmMsgStruct, *SshInterceptorIpmMsg;
+
+struct SshInterceptorIpmMsgRec
+{
+ /* Send queue is doubly linked, freelist uses only `next'. */
+ SshInterceptorIpmMsg next;
+ SshInterceptorIpmMsg prev;
+
+ SshUInt8 reliable : 1; /* message is reliable. */
+ SshUInt8 emergency_mallocated : 1; /* message is allocated from heap */
+
+ /* Offset for partially sent message */
+ size_t offset;
+
+ /* Message length and data. */
+ size_t len;
+ unsigned char *buf;
+};
+
+/* Ipm structure */
+typedef struct SshInterceptorIpmRec
+{
+ /* RW lock for protecting the send message queue and the message freelist. */
+ rwlock_t lock;
+
+ /* Is ipm channel open */
+ atomic_t open;
+
+ /* Message freelist and number of allocated messages. */
+ SshInterceptorIpmMsg msg_freelist;
+ SshUInt32 msg_allocated;
+
+ /* Output message queue */
+ SshInterceptorIpmMsg send_queue;
+ SshInterceptorIpmMsg send_queue_tail;
+
+ /* Number of unreliable messages in the sed queue. */
+ SshUInt32 send_queue_num_unreliable;
+
+} SshInterceptorIpmStruct, *SshInterceptorIpm;
+
+
+/* Structure for ipm channel /proc entry. */
+typedef struct SshInterceptorIpmProcEntryRec
+{
+ /* /proc filesystem inode */
+ struct proc_dir_entry *entry;
+
+ /* RW lock for protecting the proc entry */
+ rwlock_t lock;
+
+ /* Is an userspace application using this entry */
+ Boolean open;
+
+ /* Is another read ongoing? When this is TRUE
+ then `send_msg' is owned by the reader. */
+ Boolean read_active;
+
+ /* Is another write ongoing? When this is TRUE
+ then `recv_buf' is owned by the writer. */
+ Boolean write_active;
+
+ /* Wait queue for blocking mode reads and writes. */
+ wait_queue_head_t wait_queue;
+
+ /* Output message under processing. */
+ SshInterceptorIpmMsg send_msg;
+
+ /* Input message length */
+ size_t recv_len;
+
+ /* Input message buffer */
+ size_t recv_buf_size;
+ unsigned char *recv_buf;
+
+} SshInterceptorIpmProcEntryStruct, *SshInterceptorIpmProcEntry;
+
+
+/* Structure for other /proc entries. */
+typedef struct SshInterceptorProcEntryRec
+{
+ /* /proc filesystem entry */
+ struct proc_dir_entry *entry;
+
+ /* RW lock for protecting the proc entry */
+ rwlock_t lock;
+
+ /* Is an userspace application using this entry */
+ Boolean open;
+
+ /* Is another read or write ongoing? When this is TRUE
+ then `buf' is owned by the reader/writer. */
+ Boolean active;
+
+ /* Number of bytes returned to the userpace application */
+ size_t buf_len;
+
+ /* Preallocated buffer for read and write operations. */
+ char buf[1024];
+
+} SshInterceptorProcEntryStruct, *SshInterceptorProcEntry;
+
+/****************************** Dst entry cache ******************************/
+#define SSH_DST_ENTRY_TBL_SIZE 128
+typedef struct SshDstEntryRec
+{
+ struct dst_entry *dst_entry;
+
+ unsigned long allocation_time;
+ SshUInt32 dst_entry_id;
+
+ struct SshDstEntryRec *next;
+} *SshDstEntry, SshDstEntryStruct;
+
+
+/****************************** Interceptor Object ***************************/
+
+struct SshInterceptorRec
+{
+ /* Function pointers to netfilter infrastructure */
+ struct
+ {
+ int (*ip_rcv_finish) (struct sk_buff *);
+ int (*ip_finish_output) (struct sk_buff *);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ int (*ip6_rcv_finish) (struct sk_buff *);
+ int (*ip6_output_finish) (struct sk_buff *);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ int (*arp_process) (struct sk_buff *);
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ } linux_fn;
+
+ SshVirtualAdapter virtual_adapters[SSH_LINUX_MAX_VIRTUAL_ADAPTERS];
+
+ Boolean hooks_installed;
+
+ /* Interface information used in ssh_interceptor_send()
+ (and elsewhere obviously, but the aforementioned
+ is the reason it is here). 'if_hash', 'if_table_size',
+ and 'if_generation' are protected by 'if_table_lock' rwlock. */
+ SshInterceptorInternalInterface if_hash[SSH_LINUX_IFACE_HASH_SIZE];
+
+ SshInterceptorInternalInterface if_table;
+ SshUInt32 if_table_size;
+ SshUInt8 if_generation;
+
+ /* Protected by interceptor_lock */
+ int num_interface_callbacks;
+
+ /* Notifiers, notifies when interfaces change. */
+ Boolean iface_notifiers_installed;
+
+ struct notifier_block notifier_netdev;
+ struct notifier_block notifier_inetaddr;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ struct notifier_block notifier_inet6addr;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Reader Writer lock for interface table manipulation */
+ rwlock_t if_table_lock;
+
+ /* Registered callbacks */
+
+ /* Interface callback. Protected by 'interceptor_lock' */
+ SshInterceptorInterfacesCB interfaces_callback;
+
+ /* Unused and unprotected */
+ SshInterceptorRouteChangeCB route_callback;
+
+ /* Callback for packet. Protected by rcu. */
+ SshInterceptorPacketCB packet_callback;
+
+ /* Context for interface, route and packet callbacks. */
+ void *callback_context;
+
+ /* Engine context */
+ SshEngine engine;
+ Boolean engine_open;
+
+ /* Intercept packets. */
+ Boolean enable_interception;
+
+ /* Name of related engine-instance */
+ char *name;
+
+ /* Ipm channel */
+ SshInterceptorIpmStruct ipm;
+
+ /* /proc filesystem entries */
+ struct proc_dir_entry *proc_dir;
+
+ SshInterceptorIpmProcEntryStruct ipm_proc_entry;
+
+ SshInterceptorProcEntryStruct version_proc_entry;
+
+ /* Main mutex for interceptor use */
+ SshKernelMutex interceptor_lock;
+
+ /* Mutex for memory map manipulation */
+ SshKernelMutex memory_lock;
+
+ /* Mutex for packet handling */
+ SshKernelMutex packet_lock;
+
+ SshKernelMutex dst_entry_cache_lock;
+ Boolean dst_entry_cache_timeout_registered;
+ SshUInt32 dst_entry_id;
+ struct timer_list dst_cache_timer;
+ SshUInt32 dst_entry_cached_items;
+ SshDstEntry dst_entry_table[SSH_DST_ENTRY_TBL_SIZE];
+
+#ifdef DEBUG_LIGHT
+ /* Statistics spin lock */
+ spinlock_t statistics_lock;
+
+ struct {
+ /* Statistics */
+ SshUInt64 num_packets_out;
+ SshUInt64 num_packets_in;
+ SshUInt64 num_bytes_out;
+ SshUInt64 num_bytes_in;
+ SshUInt64 num_passthrough;
+ SshUInt64 num_fastpath_packets_in;
+ SshUInt64 num_fastpath_packets_out;
+ SshUInt64 num_fastpath_bytes_in;
+ SshUInt64 num_fastpath_bytes_out;
+ SshUInt64 num_errors;
+ SshUInt64 num_packets_sent;
+ SshUInt64 num_bytes_sent;
+ SshUInt64 allocated_memory;
+ SshUInt64 allocated_memory_max;
+ SshUInt64 num_allocations;
+ SshUInt64 num_allocations_large;
+ SshUInt64 num_allocations_total;
+ SshUInt64 num_allocated_packets;
+ SshUInt64 num_allocated_packets_total;
+ SshUInt64 num_copied_packets;
+ SshUInt64 num_failed_allocs;
+ SshUInt64 num_light_locks;
+ SshUInt64 num_light_interceptor_locks;
+ SshUInt64 num_heavy_locks;
+ SshUInt64 ipm_send_queue_len;
+ SshUInt64 ipm_send_queue_bytes;
+ } stats;
+#endif /* DEBUG_LIGHT */
+};
+
+typedef struct SshInterceptorRec SshInterceptorStruct;
+
+/****************************** Function prototypes **************************/
+
+/* Call packet_callback */
+#define SSH_LINUX_INTERCEPTOR_PACKET_CALLBACK(interceptor, pkt) \
+ do { \
+ rcu_read_lock(); \
+ (interceptor)->packet_callback((pkt), (interceptor)->callback_context); \
+ rcu_read_unlock(); \
+ } while (0)
+
+/* Proc entries */
+Boolean ssh_interceptor_proc_init(SshInterceptor interceptor);
+void ssh_interceptor_proc_uninit(SshInterceptor interceptor);
+
+/* Ipm channel */
+
+/* init / uninit */
+Boolean ssh_interceptor_ipm_init(SshInterceptor interceptor);
+void ssh_interceptor_ipm_uninit(SshInterceptor interceptor);
+
+/* open / close. These functions handle ipm message queue flushing. */
+void interceptor_ipm_open(SshInterceptor interceptor);
+void interceptor_ipm_close(SshInterceptor interceptor);
+
+/* open / close notifiers. These functions notify engine. */
+void ssh_interceptor_notify_ipm_open(SshInterceptor interceptor);
+void ssh_interceptor_notify_ipm_close(SshInterceptor interceptor);
+
+Boolean ssh_interceptor_send_to_ipm(unsigned char *data, size_t len,
+ Boolean reliable, void *machine_context);
+ssize_t ssh_interceptor_receive_from_ipm(unsigned char *data, size_t len);
+
+void interceptor_ipm_message_free(SshInterceptor interceptor,
+ SshInterceptorIpmMsg msg);
+
+/* Packet access and manipulation. */
+
+/* Header-only allocation.
+ This function will assert that the interface numbers will
+ fit into the data type SshInterceptorIfnum. */
+SshInterceptorInternalPacket
+ssh_interceptor_packet_alloc_header(SshInterceptor interceptor,
+ SshUInt32 flags,
+ SshInterceptorProtocol protocol,
+ SshUInt32 ifnum_in,
+ SshUInt32 ifnum_out,
+ struct sk_buff *skb,
+ Boolean force_copy_skbuff,
+ Boolean free_original_on_copy,
+ Boolean packet_from_system);
+
+
+
+/* Allocates new packet skb with copied data from original
+ + the extra free space reserved for extensions. */
+struct sk_buff *
+ssh_interceptor_packet_skb_dup(SshInterceptor interceptor,
+ struct sk_buff *skb,
+ size_t addbytes_active_ofs,
+ size_t addbytes_active);
+
+/* Align the interceptor packet at the data offset 'offset' to a word
+ boundary. On failure, 'pp' is freed and returns FALSE. */
+Boolean
+ssh_interceptor_packet_align(SshInterceptorPacket packet, size_t offset);
+
+/* Verify that `skbp' has enough headroom to be sent out through `skbp->dev'.
+ On failure this frees `skbp' and returns NULL. */
+struct sk_buff *
+ssh_interceptor_packet_verify_headroom(struct sk_buff *skbp,
+ size_t media_header_len);
+
+void
+ssh_interceptor_packet_return_dst_entry(SshInterceptor interceptor,
+ SshUInt32 dst_entry_id,
+ SshInterceptorPacket pp,
+ Boolean remove_only);
+SshUInt32
+ssh_interceptor_packet_cache_dst_entry(SshInterceptor interceptor,
+ SshInterceptorPacket pp);
+
+Boolean
+ssh_interceptor_dst_entry_cache_init(SshInterceptor interceptor);
+
+void
+ssh_interceptor_dst_entry_cache_flush(SshInterceptor interceptor);
+
+void
+ssh_interceptor_dst_entry_cache_uninit(SshInterceptor interceptor);
+
+/* Packet freelist init / uninit. */
+Boolean ssh_interceptor_packet_freelist_init(SshInterceptor interceptor);
+void ssh_interceptor_packet_freelist_uninit(SshInterceptor interceptor);
+
+int ssh_linux_module_inc_use_count(void);
+void ssh_linux_module_dec_use_count(void);
+
+
+Boolean ssh_interceptor_ip_glue_init(SshInterceptor interceptor);
+Boolean ssh_interceptor_ip_glue_uninit(SshInterceptor interceptor);
+
+int ssh_interceptor_hook_magic_init(void);
+
+struct net_device *
+ssh_interceptor_ifnum_to_netdev(SshInterceptor interceptor, SshUInt32 ifnum);
+struct net_device *
+ssh_interceptor_ifnum_to_netdev_ctx(SshInterceptor interceptor,
+ SshUInt32 ifnum, void **context_return);
+void ssh_interceptor_release_netdev(struct net_device *dev);
+void ssh_interceptor_receive_ifaces(SshInterceptor interceptor);
+void ssh_interceptor_clear_ifaces(SshInterceptor interceptor);
+Boolean ssh_interceptor_iface_init(SshInterceptor interceptor);
+void ssh_interceptor_iface_uninit(SshInterceptor interceptor);
+
+/* skb rerouting */
+Boolean ssh_interceptor_reroute_skb_ipv4(SshInterceptor interceptor,
+ struct sk_buff *skb,
+ SshUInt16 route_selector,
+ SshUInt32 ifnum_in);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+Boolean ssh_interceptor_reroute_skb_ipv6(SshInterceptor interceptor,
+ struct sk_buff *skb,
+ SshUInt16 route_selector,
+ SshUInt32 ifnum_in);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#endif /* LINUX_INTERNAL_H */
diff --git a/drivers/interceptor/linux_ip_glue.c b/drivers/interceptor/linux_ip_glue.c
new file mode 100644
index 0000000..7638c08
--- /dev/null
+++ b/drivers/interceptor/linux_ip_glue.c
@@ -0,0 +1,1841 @@
+/* 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_ip_glue.c
+ *
+ * Linux interceptor packet interception using Netfilter hooks.
+ *
+ */
+
+#include "linux_internal.h"
+
+extern SshInterceptor ssh_interceptor_context;
+
+/********************* Prototypes for packet handling hooks *****************/
+
+static unsigned int
+ssh_interceptor_packet_in_ipv4(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static unsigned int
+ssh_interceptor_packet_in_ipv6(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+static unsigned int
+ssh_interceptor_packet_in_arp(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+static unsigned int
+ssh_interceptor_packet_out(int pf,
+ unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+
+static unsigned int
+ssh_interceptor_packet_out_ipv4(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static unsigned int
+ssh_interceptor_packet_out_ipv6(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *));
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+
+/********** Definition of netfilter hooks to register **********************/
+
+struct SshLinuxHooksRec
+{
+ const char *name; /* Name of hook */
+ Boolean is_registered; /* Has this hook been registered? */
+ Boolean is_mandatory; /* If the registration fails,
+ abort initialization? */
+ int pf; /* Protocol family */
+ int hooknum; /* Hook id */
+ int priority; /* Netfilter priority of hook */
+
+ /* Actual hook function */
+ unsigned int (*hookfn)(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *));
+
+ struct nf_hook_ops *ops; /* Pointer to storage for nf_hook_ops
+ to store the netfilter hook configuration
+ and state */
+};
+
+struct nf_hook_ops ssh_nf_in4, ssh_nf_out4;
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+struct nf_hook_ops ssh_nf_in_arp;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+struct nf_hook_ops ssh_nf_in6, ssh_nf_out6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+static struct SshLinuxHooksRec ssh_nf_hooks[] =
+{
+ { "ipv4 in",
+ FALSE, TRUE, PF_INET, SSH_NF_IP_PRE_ROUTING, SSH_NF_IP_PRI_FIRST,
+ ssh_interceptor_packet_in_ipv4, &ssh_nf_in4 },
+ { "ipv4 out",
+ FALSE, TRUE, PF_INET, SSH_NF_IP_POST_ROUTING, SSH_NF_IP_PRI_FIRST,
+ ssh_interceptor_packet_out_ipv4, &ssh_nf_out4 },
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ { "arp in",
+ FALSE, TRUE, SSH_NFPROTO_ARP, NF_ARP_IN, 1,
+ ssh_interceptor_packet_in_arp, &ssh_nf_in_arp },
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ { "ipv6 in",
+ FALSE, TRUE, PF_INET6, SSH_NF_IP6_PRE_ROUTING, SSH_NF_IP6_PRI_FIRST,
+ ssh_interceptor_packet_in_ipv6, &ssh_nf_in6 },
+ { "ipv6 out",
+ FALSE, TRUE, PF_INET6, SSH_NF_IP6_POST_ROUTING, SSH_NF_IP6_PRI_FIRST,
+ ssh_interceptor_packet_out_ipv6, &ssh_nf_out6 },
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ { NULL,
+ 0, 0, 0, 0, 0,
+ NULL_FNPTR, NULL },
+};
+
+
+/******************************** Module parameters *************************/
+
+/* Module parameters. Default values. These can be overrided at the
+ loading of the module from the command line. These set the priority
+ for netfilter hooks. */
+
+static int in_priority = SSH_NF_IP_PRI_FIRST;
+static int out_priority = SSH_NF_IP_PRI_FIRST;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static int in6_priority = SSH_NF_IP6_PRI_FIRST;
+static int out6_priority = SSH_NF_IP6_PRI_FIRST;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+MODULE_PARM_DESC(in_priority, "Netfilter hook priority at IPv4 PREROUTING");
+MODULE_PARM_DESC(out_priority, "Netfilter hook priority at IPv4 POSTROUTING");
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+MODULE_PARM_DESC(in6_priority, "Netfilter hook priority at IPv6 PREROUTING");
+MODULE_PARM_DESC(out6_priority, "Netfilter hook priority at IPv6 POSTROUTING");
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+module_param(in_priority, int, 0444);
+module_param(out_priority, int, 0444);
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+module_param(in6_priority, int, 0444);
+module_param(out6_priority, int, 0444);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+
+/************* Utility functions ********************************************/
+
+/* Map a SshInterceptorProtocol to a skbuff protocol id */
+static unsigned short
+ssh_proto_to_skb_proto(SshInterceptorProtocol protocol)
+{
+ /* If support for other than IPv6, IPv4 and ARP
+ inside the engine on Linux are to be supported, their
+ protocol types must be added here. */
+ switch (protocol)
+ {
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case SSH_PROTOCOL_IP6:
+ return __constant_htons(ETH_P_IPV6);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ case SSH_PROTOCOL_ARP:
+ return __constant_htons(ETH_P_ARP);
+
+ case SSH_PROTOCOL_IP4:
+ return __constant_htons(ETH_P_IP);
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR, ("Unknown protocol %d", protocol));
+ return 0;
+ }
+}
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+
+/* Map ethernet type to a skbuff protocol id */
+static unsigned short
+ssh_ethertype_to_skb_proto(SshInterceptorProtocol protocol,
+ size_t media_header_len,
+ unsigned char *media_header)
+{
+ SshUInt16 ethertype;
+
+ if (protocol != SSH_PROTOCOL_ETHERNET)
+ return ssh_proto_to_skb_proto(protocol);
+
+ SSH_ASSERT(media_header_len >= SSH_ETHERH_HDRLEN);
+ ethertype = SSH_GET_16BIT(media_header + SSH_ETHERH_OFS_TYPE);
+
+ /* If support for other than IPv6, IPv4 and ARP
+ inside the engine on Linux are to be supported, their
+ ethernet types must be added here. */
+ switch (ethertype)
+ {
+ case SSH_ETHERTYPE_IPv6:
+ return __constant_htons(ETH_P_IPV6);
+
+ case SSH_ETHERTYPE_ARP:
+ return __constant_htons(ETH_P_ARP);
+
+ case SSH_ETHERTYPE_IP:
+ return __constant_htons(ETH_P_IP);
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR, ("Unknown ethertype 0x%x", ethertype));
+ return 0;
+ }
+}
+
+/* Return the pointer to start of ethernet header */
+static struct ethhdr *ssh_get_eth_hdr(const struct sk_buff *skb)
+{
+#ifdef LINUX_HAS_ETH_HDR
+ return eth_hdr(skb);
+#else /* LINUX_HAS_ETH_HDR */
+ return skb->mac.ethernet;
+#endif /* LINUX_HAS_ETH_HDR */
+}
+
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+
+/**************** Packet reception and sending *****************************/
+
+/**************** Inbound packet interception ******************************/
+
+
+/* Common code for ssh_interceptor_packet_in_finish_ipv4()
+ and ssh_interceptor_packet_in_finish_ipv6().
+
+ If SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE is set, then
+ this function is called as the okfn() from the netfilter
+ infrastructure after inbound netfilter hook iteration.
+ Otherwise this function is called directly from the inbound
+ netfilter hookfn().
+*/
+
+static inline int
+ssh_interceptor_packet_in_finish(struct sk_buff *skbp,
+ SshInterceptorProtocol protocol)
+{
+ SshInterceptorInternalPacket packet;
+ SshInterceptor interceptor;
+ int ifnum_in;
+
+ interceptor = ssh_interceptor_context;
+
+ SSH_ASSERT(skbp->dev != NULL);
+ ifnum_in = skbp->dev->ifindex;
+
+ SSH_DEBUG(SSH_D_HIGHSTART,
+ ("incoming packet 0x%p, len %d proto 0x%x [%s] iface %d [%s]",
+ skbp, skbp->len, ntohs(skbp->protocol),
+ (protocol == SSH_PROTOCOL_IP4 ? "ipv4" :
+ (protocol == SSH_PROTOCOL_IP6 ? "ipv6" :
+ (protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" : "unknown"))),
+ ifnum_in, skbp->dev->name));
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ if (unlikely(protocol == SSH_PROTOCOL_ETHERNET))
+ {
+ /* Unwrap ethernet header. This is needed by the engine currently
+ due to some sillyness in the ARP handling. */
+ if (SSH_SKB_GET_MACHDR(skbp))
+ {
+ size_t media_header_len = skbp->data - SSH_SKB_GET_MACHDR(skbp);
+ if (media_header_len)
+ skb_push(skbp, media_header_len);
+ }
+ }
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Encapsulate the skb into a new packet. Note that ip_rcv()
+ performs IP header checksum computation, so we do not
+ need to. */
+ packet = ssh_interceptor_packet_alloc_header(interceptor,
+ SSH_PACKET_FROMADAPTER
+ |SSH_PACKET_IP4HDRCKSUMOK,
+ protocol,
+ ifnum_in,
+ SSH_INTERCEPTOR_INVALID_IFNUM,
+ skbp,
+ FALSE, TRUE, TRUE);
+
+ if (unlikely(packet == NULL))
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("encapsulation failed, packet dropped"));
+ /* Free sk_buff and return error */
+ dev_kfree_skb_any(skbp);
+ SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; });
+ return -EPERM;
+ }
+
+#ifdef DEBUG_LIGHT
+ packet->skb->dev = NULL;
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP,
+ ("incoming packet, len=%d flags=0x%08lx%s",
+ packet->skb->len,
+ (unsigned long) packet->packet.flags,
+ ((packet->packet.flags & SSH_PACKET_HWCKSUM) ?
+ " [hwcsum]" : "")),
+ packet->skb->data, packet->skb->len);
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_fastpath_bytes_in += (SshUInt64) packet->skb->len;
+ interceptor->stats.num_fastpath_packets_in++;
+ });
+
+ /* Pass the packet to then engine. Which eventually will call
+ ssh_interceptor_send. */
+ SSH_LINUX_INTERCEPTOR_PACKET_CALLBACK(interceptor,
+ (SshInterceptorPacket) packet);
+
+ /* Return ok */
+ return 0;
+}
+
+
+#ifdef SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE
+
+static inline int
+ssh_interceptor_packet_in_finish_ipv4(struct sk_buff *skbp)
+{
+ return ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP4);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static inline int
+ssh_interceptor_packet_in_finish_ipv6(struct sk_buff *skbp)
+{
+ return ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP6);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#endif /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */
+
+
+/* ssh_interceptor_packet_in() is the common code for
+ inbound netfilter hooks ssh_interceptor_packet_in_ipv4(),
+ ssh_interceptor_packet_in_ipv6(), and ssh_interceptor_packet_in_arp().
+
+ This function must only be called from softirq context, or
+ with softirqs disabled. This function MUST NOT be called
+ from a hardirq (as then it could pre-empt itself on the same CPU). */
+
+static inline unsigned int
+ssh_interceptor_packet_in(int pf,
+ unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ SshInterceptor interceptor;
+ struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb);
+#ifdef DEBUG_LIGHT
+ struct iphdr *iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp);
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG(SSH_D_PCKDMP,
+ ("IN 0x%04x/0x%x (%s[%d]->%s[%d]) len %d "
+ "type=0x%08x %08x -> %08x dst 0x%p dev (%s[%d])",
+ htons(skbp->protocol),
+ pf,
+ (in ? in->name : "<none>"),
+ (in ? in->ifindex : -1),
+ (out ? out->name : "<none>"),
+ (out ? out->ifindex : -1),
+ skbp->len,
+ skbp->pkt_type,
+ iph->saddr, iph->daddr,
+ SSH_SKB_DST(skbp),
+ (skbp->dev ? skbp->dev->name : "<none>"),
+ (skbp->dev ? skbp->dev->ifindex : -1)
+ ));
+
+ interceptor = ssh_interceptor_context;
+
+ if (interceptor->enable_interception == FALSE)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ SSH_DEBUG(11, ("packet passed through"));
+ return NF_ACCEPT;
+ }
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_bytes_in += (SshUInt64) skbp->len;
+ interceptor->stats.num_packets_in++;
+ });
+
+ /* If the device is to loopback, pass the packet through. */
+ if (in->flags & IFF_LOOPBACK)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+
+ /* The linux stack makes a copy of each locally generated
+ broadcast / multicast packet. The original packet will
+ be sent to network as any packet. The copy will be marked
+ as PACKET_LOOPBACK and looped back to local stack.
+ So we let the copy continue back to local stack. */
+ if (skbp->pkt_type == PACKET_LOOPBACK)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+
+ /* VPNClient code relies on skb->dev to be set.
+ If packet has been processed by AF_PACKET (what tcpdump uses),
+ then skb->dev has been cleared, and we must reset it here. */
+ SSH_ASSERT(skbp->dev == NULL || skbp->dev == in);
+ if (skbp->dev == NULL)
+ {
+ skbp->dev = (struct net_device *) in;
+ /* Increment refcount of skbp->dev. */
+ dev_hold(skbp->dev);
+ }
+
+#ifdef SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE
+
+ /* Traverse lower priority netfilter hooks. */
+ switch (pf)
+ {
+ case PF_INET:
+ SSH_ASSERT(hooknum == SSH_NF_IP_PRE_ROUTING);
+ NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING, skbp,
+ (struct net_device *) in, (struct net_device *) out,
+ ssh_interceptor_packet_in_finish_ipv4,
+ ssh_nf_in4.priority + 1);
+ return NF_STOLEN;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ SSH_ASSERT(hooknum == SSH_NF_IP6_PRE_ROUTING);
+ NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, skbp,
+ (struct net_device *) in, (struct net_device *) out,
+ ssh_interceptor_packet_in_finish_ipv6,
+ ssh_nf_in6.priority + 1);
+ return NF_STOLEN;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ case SSH_NFPROTO_ARP:
+ /* There is no point in looping ARP packets,
+ just continue packet processing, and return NF_STOLEN. */
+ ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_ETHERNET);
+ return NF_STOLEN;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ default:
+ SSH_NOTREACHED;
+ return NF_DROP;
+ }
+
+#else /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */
+
+ /* Continue packet processing ssh_interceptor_packet_in_finish() */
+ switch (pf)
+ {
+ case PF_INET:
+ ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP4);
+ return NF_STOLEN;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_IP6);
+ return NF_STOLEN;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ case SSH_NFPROTO_ARP:
+ ssh_interceptor_packet_in_finish(skbp, SSH_PROTOCOL_ETHERNET);
+ return NF_STOLEN;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ default:
+ break;
+ }
+
+#endif /* SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE */
+
+ SSH_NOTREACHED;
+ return NF_DROP;
+}
+
+/* Netfilter hookfn() wrapper function for IPv4 packets. */
+static unsigned int
+ssh_interceptor_packet_in_ipv4(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_packet_in(PF_INET, hooknum, skb, in, out, okfn);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+/* Netfilter nf_hookfn() wrapper function for IPv6 packets. */
+static unsigned int
+ssh_interceptor_packet_in_ipv6(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_packet_in(PF_INET6, hooknum, skb, in, out, okfn);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+/* Netfilter nf_hookfn() wrapper function for ARP packets. */
+static unsigned int
+ssh_interceptor_packet_in_arp(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_packet_in(SSH_NFPROTO_ARP, hooknum, skb,
+ in, out, okfn);
+}
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+
+/**************** Outbound packet interception *****************************/
+
+
+/* Common code for ssh_interceptor_packet_out_finish_ipv4()
+ and ssh_interceptor_packet_out_finish_ipv6().
+
+ If SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is set, then
+ this function is called as the okfn() function from the
+ netfilter infrastructure after the outbound hook iteration.
+ Otherwise, this function is called directly from the outbound
+ netfilter hookfn().
+
+ This function must only be called from softirq context or
+ from an exception. It will disable softirqs for the engine
+ processing. This function MUST NOT be called
+ from a hardirq (as then it could pre-empt itself
+ on the same CPU). */
+static inline int
+ssh_interceptor_packet_out_finish(struct sk_buff *skbp,
+ SshInterceptorProtocol protocol)
+{
+ SshInterceptorInternalPacket packet;
+ SshInterceptor interceptor;
+ int ifnum_in;
+ SshUInt32 flags;
+
+ SSH_ASSERT(skbp->dev != NULL);
+ ifnum_in = skbp->dev->ifindex;
+
+ interceptor = ssh_interceptor_context;
+
+ SSH_DEBUG(SSH_D_HIGHSTART,
+ ("outgoing packet 0x%p, len %d proto 0x%x [%s] iface %d [%s]",
+ skbp, skbp->len, ntohs(skbp->protocol),
+ (protocol == SSH_PROTOCOL_IP4 ? "ipv4" :
+ (protocol == SSH_PROTOCOL_IP6 ? "ipv6" :
+ (protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" : "unknown"))),
+ ifnum_in, skbp->dev->name));
+
+ local_bh_disable();
+
+ flags = SSH_PACKET_FROMPROTOCOL;
+
+#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING
+ /* Is this a local packet which is allowed to be fragmented? */
+ if (protocol == SSH_PROTOCOL_IP6 && skbp->local_df == 1)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("Local packet, fragmentation allowed."));
+ flags |= SSH_PACKET_FRAGMENTATION_ALLOWED;
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */
+
+#ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING
+ /* Is this a local packet which is allowed to be fragmented? */
+ if (protocol == SSH_PROTOCOL_IP4 && skbp->local_df == 1)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("Local packet, fragmentation allowed."));
+ flags |= SSH_PACKET_FRAGMENTATION_ALLOWED;
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */
+
+ /* Encapsulate the skb into a new packet. This function
+ holds packet_lock during freelist manipulation. */
+ packet = ssh_interceptor_packet_alloc_header(interceptor,
+ flags,
+ protocol,
+ ifnum_in,
+ SSH_INTERCEPTOR_INVALID_IFNUM,
+ skbp,
+ FALSE, TRUE, TRUE);
+ if (unlikely(packet == NULL))
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("encapsulation failed, packet dropped"));
+ local_bh_enable();
+ /* Free sk_buff and return error */
+ dev_kfree_skb_any(skbp);
+ SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; });
+ return -EPERM;
+ }
+
+#ifdef DEBUG_LIGHT
+ packet->skb->dev = NULL;
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP,
+ ("outgoing packet, len=%d flags=0x%08lx%s",
+ packet->skb->len,
+ (unsigned long) packet->packet.flags,
+ ((packet->packet.flags & SSH_PACKET_HWCKSUM) ?
+ " [hwcsum]" : "")),
+ packet->skb->data, packet->skb->len);
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_fastpath_bytes_out += (SshUInt64) packet->skb->len;
+ interceptor->stats.num_fastpath_packets_out++;
+ });
+
+ /* Pass the packet to engine. */
+ SSH_LINUX_INTERCEPTOR_PACKET_CALLBACK(interceptor,
+ (SshInterceptorPacket) packet);
+
+ local_bh_enable();
+
+ /* Return ok */
+ return 0;
+}
+
+#ifdef SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE
+
+static inline int
+ssh_interceptor_packet_out_finish_ipv4(struct sk_buff *skbp)
+{
+ return ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP4);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static inline int
+ssh_interceptor_packet_out_finish_ipv6(struct sk_buff *skbp)
+{
+ return ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP6);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+#endif /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */
+
+/* ssh_interceptor_packet_out() is the common code for
+ outbound netfilter hook ssh_interceptor_packet_out_ipv4()
+ and ssh_interceptor_packet_out_ipv6().
+
+ Netfilter does not provide a clean way of intercepting ALL packets
+ being sent via an output chain after all other filters are processed.
+ Therefore this hook is registered first, and then if
+ SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is set the packet is
+ sent back to SSH_NF_IP_POST_ROUTING hook with (*okfn)()
+ pointing to the actual interception function. If
+ SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE is not set, then
+ all following netfilter hook functions in SSH_NF_IP_POST_ROUTING hook
+ are skipped.
+
+ This function must only be called from softirq context or
+ from an exception. It will disable softirqs for the engine
+ processing. This function MUST NOT be called
+ from a hardirq (as then it could pre-empt itself
+ on the same CPU). */
+
+static inline unsigned int
+ssh_interceptor_packet_out(int pf,
+ unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ SshInterceptor interceptor;
+ struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb);
+#ifdef DEBUG_LIGHT
+ struct iphdr *iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp);
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG(SSH_D_PCKDMP,
+ ("OUT 0x%04x/0x%x (%s[%d]->%s[%d]) len %d "
+ "type=0x%08x %08x -> %08x dst 0x%p dev (%s[%d])",
+ htons(skbp->protocol),
+ pf,
+ (in ? in->name : "<none>"),
+ (in ? in->ifindex : -1),
+ (out ? out->name : "<none>"),
+ (out ? out->ifindex : -1),
+ skbp->len,
+ skbp->pkt_type,
+ iph->saddr, iph->daddr,
+ SSH_SKB_DST(skbp),
+ (skbp->dev ? skbp->dev->name : "<none>"),
+ (skbp->dev ? skbp->dev->ifindex : -1)
+ ));
+
+ interceptor = ssh_interceptor_context;
+
+ if (interceptor->enable_interception == FALSE)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ SSH_DEBUG(11, ("packet passed through"));
+ return NF_ACCEPT;
+ }
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_packets_out++;
+ interceptor->stats.num_bytes_out += (SshUInt64) skbp->len;
+ });
+
+ /* If the device is to loopback, pass the packet through. */
+ if (out->flags & IFF_LOOPBACK)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("loopback packet passed through"));
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len),
+ skbp->data, skbp->len);
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+
+ /* Linux network stack creates a copy of locally generated broadcast
+ and multicast packets, and sends the copies to local stack using
+ 'ip_dev_loopback_xmit' or 'ip6_dev_loopback_xmit' as the NFHOOK
+ okfn. Intercept the original packets and let the local copies go
+ through. */
+ if (pf == PF_INET &&
+ okfn != interceptor->linux_fn.ip_finish_output)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("local IPv4 broadcast loopback packet passed through"));
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len),
+ skbp->data, skbp->len);
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (pf == PF_INET6 &&
+ okfn != interceptor->linux_fn.ip6_output_finish)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("local IPv6 broadcast loopback packet passed through"));
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("len=%d", skbp->len),
+ skbp->data, skbp->len);
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_IP_ONLY_PASSTHROUGH_NDISC
+ if (pf == PF_INET6 &&
+ skbp->sk == dev_net(skbp->dev)->ipv6.ndisc_sk)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("Neighbour discovery packet passed through"));
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP,
+ ("length %d dumping %d bytes",
+ (int) skbp->len, (int) skb_headlen(skbp)),
+ skbp->data, skb_headlen(skbp));
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ return NF_ACCEPT;
+ }
+#endif /* LINUX_IP_ONLY_PASSTHROUGH_NDISC */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Assert that we are about to intercept the packet from
+ the correct netfilter hook on the correct path. */
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ SSH_ASSERT(okfn == interceptor->linux_fn.ip_finish_output ||
+ okfn == interceptor->linux_fn.ip6_output_finish);
+#else /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ SSH_ASSERT(okfn == interceptor->linux_fn.ip_finish_output);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifdef SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE
+
+ /* Traverse lower priority netfilter hooks. */
+ switch (pf)
+ {
+ case PF_INET:
+ SSH_ASSERT(hooknum == SSH_NF_IP_POST_ROUTING);
+ NF_HOOK_THRESH(PF_INET, SSH_NF_IP_POST_ROUTING, skbp,
+ (struct net_device *) in, (struct net_device *) out,
+ ssh_interceptor_packet_out_finish_ipv4,
+ ssh_nf_out4.priority + 1);
+ return NF_STOLEN;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ SSH_ASSERT(hooknum == SSH_NF_IP6_POST_ROUTING);
+ NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_POST_ROUTING, skbp,
+ (struct net_device *) in, (struct net_device *) out,
+ ssh_interceptor_packet_out_finish_ipv6,
+ ssh_nf_out6.priority + 1);
+ return NF_STOLEN;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ SSH_NOTREACHED;
+ return NF_DROP;
+ }
+
+#else /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */
+
+ /* Continue packet processing in ssh_interceptor_packet_out_finish() */
+ switch (pf)
+ {
+ case PF_INET:
+ SSH_ASSERT(hooknum == SSH_NF_IP_POST_ROUTING);
+ ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP4);
+ return NF_STOLEN;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ SSH_ASSERT(hooknum == SSH_NF_IP6_POST_ROUTING);
+ ssh_interceptor_packet_out_finish(skbp, SSH_PROTOCOL_IP6);
+ return NF_STOLEN;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ break;
+ }
+#endif /* SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE */
+
+ SSH_NOTREACHED;
+ return NF_DROP;
+}
+
+/* Netfilter nf_hookfn() wrapper function for IPv4 packets. */
+static unsigned int
+ssh_interceptor_packet_out_ipv4(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ return ssh_interceptor_packet_out(PF_INET, hooknum, skb, in, out, okfn);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+/* Netfilter nf_hookfn() wrapper function for IPv6 packets. */
+static unsigned int
+ssh_interceptor_packet_out_ipv6(unsigned int hooknum,
+ SshHookSkb *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn) (struct sk_buff *))
+{
+ struct sk_buff *skbp = SSH_HOOK_SKB_PTR(skb);
+ if (skbp->dev == NULL)
+ skbp->dev = SSH_SKB_DST(skbp)->dev;
+
+ return ssh_interceptor_packet_out(PF_INET6, hooknum, skb, in, out, okfn);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+
+/**************** Sending packets ******************************************/
+
+/* Netfilter okfn() for sending packets to network after
+ SSH_NF_IP_FORWARD hook traversal. */
+
+static inline int
+ssh_interceptor_send_to_network(int pf, struct sk_buff *skbp)
+{
+ skbp->pkt_type = PACKET_OUTGOING;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#ifdef LINUX_HAS_SKB_NFDEBUG
+ if (pf == PF_INET)
+ {
+ /* Mark SSH_NF_IP_LOCAL_OUT chains visited */
+ if (skbp->sk)
+ skbp->nf_debug = ((1 << SSH_NF_IP_LOCAL_OUT)
+ | (1 << SSH_NF_IP_POST_ROUTING));
+
+ /* skbp is unowned, netfilter thinks this is a forwarded skb.
+ Mark SSH_NF_IP_PRE_ROUTING, SSH_NF_IP_FORWARD,
+ and SSH_NF_IP_POST_ROUTING
+ chains visited */
+ else
+ skbp->nf_debug = ((1 << SSH_NF_IP_PRE_ROUTING)
+ | (1 << SSH_NF_IP_FORWARD)
+ | (1 << SSH_NF_IP_POST_ROUTING));
+ }
+#endif /* LINUX_HAS_SKB_NFDEBUG */
+#endif /* CONFIG_NETFILTER_DEBUG */
+
+ SSH_LINUX_STATISTICS(ssh_interceptor_context,
+ {
+ ssh_interceptor_context->stats.num_packets_sent++;
+ ssh_interceptor_context->stats.num_bytes_sent += (SshUInt64) skbp->len;
+ });
+
+#ifdef SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE
+ /* Traverse lower priority netfilter hooks. */
+ switch (pf)
+ {
+ case PF_INET:
+ return NF_HOOK_THRESH(PF_INET, SSH_NF_IP_POST_ROUTING, skbp,
+ NULL, skbp->dev,
+ ssh_interceptor_context->
+ linux_fn.ip_finish_output,
+ ssh_nf_out4.priority + 1);
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ return NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_POST_ROUTING, skbp,
+ NULL, skbp->dev,
+ ssh_interceptor_context->
+ linux_fn.ip6_output_finish,
+ ssh_nf_out6.priority + 1);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ break;
+ }
+
+#else /* SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE */
+ /* Pass packet to output path. */
+ switch (pf)
+ {
+ case PF_INET:
+ return (*ssh_interceptor_context->linux_fn.ip_finish_output)(skbp);
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case PF_INET6:
+ return (*ssh_interceptor_context->linux_fn.ip6_output_finish)(skbp);
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ break;
+ }
+#endif /* SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE */
+
+ SSH_NOTREACHED;
+ dev_kfree_skb_any(skbp);
+ return -EPERM;
+}
+
+static inline int
+ssh_interceptor_send_to_network_ipv4(struct sk_buff *skbp)
+{
+ return ssh_interceptor_send_to_network(PF_INET, skbp);
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+static inline int
+ssh_interceptor_send_to_network_ipv6(struct sk_buff *skbp)
+{
+ return ssh_interceptor_send_to_network(PF_INET6, skbp);
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+/* ssh_interceptor_send() sends a packet to the network or to the
+ protocol stacks. This will eventually free the packet by calling
+ ssh_interceptor_packet_free. The packet header should not be
+ touched once this function has been called.
+
+ ssh_interceptor_send() function for both media level and IP level
+ interceptor. This grabs a packet with media layer headers attached
+ and sends it to the interface defined by 'pp->ifnum_out'. */
+void
+ssh_interceptor_send(SshInterceptor interceptor,
+ SshInterceptorPacket pp,
+ size_t media_header_len)
+{
+ SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket) pp;
+ struct net_device *dev;
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE
+ struct net_device *in_dev = NULL;
+#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */
+#endif /*SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ unsigned char *neth;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ size_t offset;
+ SshUInt8 ipproto;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ SSH_DEBUG(SSH_D_HIGHSTART,
+ ("sending packet to %s, "
+ "len=%d flags=0x%08lx ifnum_out=%lu protocol=%s[0x%x]",
+ ((pp->flags & SSH_PACKET_FROMPROTOCOL) ? "network" :
+ ((pp->flags & SSH_PACKET_FROMADAPTER) ? "stack" :
+ "nowhere")),
+ ipp->skb->len, (unsigned long) pp->flags,
+ (unsigned long) pp->ifnum_out,
+ (pp->protocol == SSH_PROTOCOL_IP4 ? "ipv4" :
+ (pp->protocol == SSH_PROTOCOL_IP6 ? "ipv6" :
+ (pp->protocol == SSH_PROTOCOL_ARP ? "arp" :
+ (pp->protocol == SSH_PROTOCOL_ETHERNET ? "ethernet" :
+ "unknown")))),
+ pp->protocol));
+
+ SSH_DEBUG_HEXDUMP(SSH_D_PCKDMP, ("packet, len %d", ipp->skb->len),
+ ipp->skb->data, ipp->skb->len);
+
+ /* Require that any references to previous devices
+ were released by the entrypoint hooks. */
+ SSH_ASSERT(ipp->skb->dev == NULL);
+
+ /* Map 'pp->ifnum_out' to a net_device.
+ This will dev_hold() the net_device. */
+ dev = ssh_interceptor_ifnum_to_netdev(interceptor, pp->ifnum_out);
+ if (dev == NULL)
+ {
+ SSH_DEBUG(SSH_D_UNCOMMON,
+ ("Interface %lu has disappeared, dropping packet 0x%p",
+ (unsigned long) pp->ifnum_out, ipp->skb));
+ goto error;
+ }
+ ipp->skb->dev = dev;
+
+ /* Verify that packet has enough headroom to be sent out via `skb->dev'. */
+ ipp->skb =
+ ssh_interceptor_packet_verify_headroom(ipp->skb, media_header_len);
+ if (ipp->skb == NULL)
+ {
+ SSH_DEBUG(SSH_D_UNCOMMON,
+ ("Could not add headroom to skbp, dropping packet 0x%p",
+ ipp->skb));
+ goto error;
+ }
+
+#ifdef INTERCEPTOR_IP_ALIGNS_PACKETS
+ /* Align IP header to word boundary. */
+ if (!ssh_interceptor_packet_align(pp, media_header_len))
+ {
+ pp = NULL;
+ goto error;
+ }
+#endif /* INTERCEPTOR_IP_ALIGNS_PACKETS */
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ /* Copy the linux nfmark from the extension slot indexed by
+ SSH_LINUX_FWMARK_EXTENSION_SELECTOR. */
+ SSH_SKB_MARK(ipp->skb) = pp->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR];
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Check if the engine has cleared the SSH_PACKET_HWCKSUM flag. */
+ if ((pp->flags & SSH_PACKET_HWCKSUM) == 0)
+ ipp->skb->ip_summed = CHECKSUM_NONE;
+
+ /* Clear control buffer, as packet contents might have changed. */
+ if ((pp->flags & SSH_PACKET_UNMODIFIED) == 0)
+ memset(ipp->skb->cb, 0, sizeof(ipp->skb->cb));
+
+ /* Send to network */
+ if (pp->flags & SSH_PACKET_FROMPROTOCOL)
+ {
+ /* Network header pointer is required by tcpdump. */
+ SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data + media_header_len);
+
+ /* Let unmodified packets pass on as if they were never intercepted.
+ Note that this expects that skb->dst has not been cleared or modified
+ during Engine processing. */
+ if (pp->flags & SSH_PACKET_UNMODIFIED)
+ {
+ SSH_DEBUG(SSH_D_HIGHOK, ("Passing unmodified packet to network"));
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* Remove the media header that was prepended to the packet
+ in the inbound netfilter hook. Update skb->protocol and
+ pp->protocol. */
+ if (media_header_len > 0)
+ {
+ SSH_ASSERT(ipp->skb->len >= media_header_len);
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+ ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol,
+ media_header_len,
+ ipp->skb->data);
+ skb_pull(ipp->skb, media_header_len);
+ if (ntohs(ipp->skb->protocol) == ETH_P_IP)
+ pp->protocol = SSH_PROTOCOL_IP4;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ else if (ntohs(ipp->skb->protocol) == ETH_P_IPV6)
+ pp->protocol = SSH_PROTOCOL_IP6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ else
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Invalid skb protocol %d, dropping packet",
+ ntohs(ipp->skb->protocol)));
+ goto error;
+ }
+ }
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ if (SSH_SKB_DST(ipp->skb) == NULL)
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("Invalid skb->dst, dropping packet"));
+ goto error;
+ }
+
+ switch (pp->protocol)
+ {
+ case SSH_PROTOCOL_IP4:
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ ssh_interceptor_send_to_network_ipv4(ipp->skb);
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case SSH_PROTOCOL_IP6:
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+ ssh_interceptor_send_to_network_ipv6(ipp->skb);
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR,
+ ("pp->protocol 0x%x ipp->skb->protocol 0x%x",
+ pp->protocol, ipp->skb->protocol));
+ SSH_NOTREACHED;
+ goto error;
+ }
+
+ /* All done. */
+ goto sent;
+ }
+
+#ifdef DEBUG_LIGHT
+ if (
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ ipp->skb->ip_summed == CHECKSUM_PARTIAL
+#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ ipp->skb->ip_summed == CHECKSUM_HW
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ )
+ SSH_DEBUG(SSH_D_LOWOK, ("Hardware performs checksumming."));
+ else if (ipp->skb->ip_summed == CHECKSUM_NONE)
+ SSH_DEBUG(SSH_D_LOWOK, ("Checksum calculated in software."));
+ else
+ SSH_DEBUG(SSH_D_LOWOK, ("No checksumming required."));
+#endif /* DEBUG_LIGHT */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+
+ /* Media level */
+ SSH_ASSERT(media_header_len <= ipp->skb->len);
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+
+ /* Set ipp->skb->protocol */
+ SSH_ASSERT(skb_headlen(ipp->skb) >= media_header_len);
+ ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol,
+ media_header_len,
+ ipp->skb->data);
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_packets_sent++;
+ interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len;
+ });
+
+ /* Pass packet to network device driver. */
+ dev_queue_xmit(ipp->skb);
+
+ /* All done. */
+ goto sent;
+
+#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* IP level */
+
+ /* Set ipp->skb->protocol */
+ ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol);
+
+#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE
+
+ /* Prepare to pass forwarded packets through
+ SSH_NF_IP_FORWARD netfilter hook. */
+ if (pp->flags & SSH_PACKET_FORWARDED)
+ {
+ /* Map 'pp->ifnum_in' to a net_device. */
+ in_dev = ssh_interceptor_ifnum_to_netdev(interceptor, pp->ifnum_in);
+
+ SSH_DEBUG(SSH_D_PCKDMP,
+ ("FWD 0x%04x/%d (%s[%d]->%s[%d]) len %d "
+ "type=0x%08x dst 0x%08x",
+ ntohs(ipp->skb->protocol), pp->protocol,
+ (in_dev ? in_dev->name : "<none>"),
+ (in_dev ? in_dev->ifindex : -1),
+ (ipp->skb->dev ? ipp->skb->dev->name : "<none>"),
+ (ipp->skb->dev ? ipp->skb->dev->ifindex : -1),
+ ipp->skb->len, ipp->skb->pkt_type, SSH_SKB_DST(ipp->skb)));
+
+ SSH_DEBUG(SSH_D_HIGHSTART,
+ ("forwarding packet 0x%08x, len %d proto 0x%x [%s]",
+ ipp->skb, ipp->skb->len, ntohs(ipp->skb->protocol),
+ (pp->protocol == SSH_PROTOCOL_IP4 ? "ipv4" :
+ (pp->protocol == SSH_PROTOCOL_IP6 ? "ipv6" :
+ (pp->protocol == SSH_PROTOCOL_ARP ? "arp" :
+ "unknown")))));
+
+ /* Change pkt_type to PACKET_HOST, which is expected
+ in the SSH_NF_IP_FORWARD hook. It is set to PACKET_OUTGOING
+ in ssh_interceptor_send_to_network_*() */
+ ipp->skb->pkt_type = PACKET_HOST;
+ }
+#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */
+
+ SSH_ASSERT(media_header_len == 0);
+
+ switch (pp->protocol)
+ {
+ case SSH_PROTOCOL_IP4:
+ /* Set ipp->skb->dst */
+ if (!ssh_interceptor_reroute_skb_ipv4(interceptor,
+ ipp->skb,
+ pp->route_selector,
+ pp->ifnum_in))
+ {
+ SSH_DEBUG(SSH_D_UNCOMMON,
+ ("Unable to reroute skb 0x%p", ipp->skb));
+ goto error;
+ }
+ SSH_ASSERT(SSH_SKB_DST(ipp->skb) != NULL);
+#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE
+ /* Pass forwarded packets to SSH_NF_IP_FORWARD netfilter hook */
+ if (pp->flags & SSH_PACKET_FORWARDED)
+ {
+ NF_HOOK(PF_INET, SSH_NF_IP_FORWARD, ipp->skb,
+ in_dev, ipp->skb->dev,
+ ssh_interceptor_send_to_network_ipv4);
+ }
+ /* Send local packets directly to network. */
+ else
+#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */
+ ssh_interceptor_send_to_network_ipv4(ipp->skb);
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case SSH_PROTOCOL_IP6:
+ /* Set ipp->skb->dst */
+ if (!ssh_interceptor_reroute_skb_ipv6(interceptor,
+ ipp->skb,
+ pp->route_selector,
+ pp->ifnum_in))
+ {
+ SSH_DEBUG(SSH_D_UNCOMMON,
+ ("Unable to reroute skb 0x%p", ipp->skb));
+ goto error;
+ }
+ SSH_ASSERT(SSH_SKB_DST(ipp->skb) != NULL);
+#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE
+ /* Pass forwarded packets to SSH_NF_IP6_FORWARD netfilter hook */
+ if (pp->flags & SSH_PACKET_FORWARDED)
+ {
+ NF_HOOK(PF_INET6, SSH_NF_IP6_FORWARD, ipp->skb,
+ in_dev, ipp->skb->dev,
+ ssh_interceptor_send_to_network_ipv6);
+ }
+ /* Send local packets directly to network. */
+ else
+#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */
+ ssh_interceptor_send_to_network_ipv6(ipp->skb);
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR,
+ ("pp->protocol 0x%x ipp->skb->protocol 0x%x",
+ pp->protocol, ipp->skb->protocol));
+ SSH_NOTREACHED;
+ goto error;
+ }
+
+ /* All done. */
+ goto sent;
+
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ }
+
+ /* Send to stack */
+ else if (pp->flags & SSH_PACKET_FROMADAPTER)
+ {
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ SshUInt32 pkt_len4;
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ SshUInt32 pkt_len6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Network header pointer is required by tcpdump. */
+ SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data + media_header_len);
+
+ /* Let unmodified packets pass on as if they were never intercepted.
+ Note that this expects that SSH_PACKET_UNMODIFIED packets are either
+ IPv4 or IPv6. Currently there is no handling for ARP, as the Engine
+ never sets SSH_PACKET_UNMODIFIED for ARP packets. */
+ if (pp->flags & SSH_PACKET_UNMODIFIED)
+ {
+ SSH_DEBUG(SSH_D_HIGHOK, ("Passing unmodified packet to stack"));
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* Remove the media header that was prepended to the packet
+ in the inbound netfilter hook. Update skb->protocol and
+ pp->protocol. */
+ if (media_header_len > 0)
+ {
+ SSH_ASSERT(ipp->skb->len >= media_header_len);
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+ ipp->skb->protocol = ssh_ethertype_to_skb_proto(pp->protocol,
+ media_header_len,
+ ipp->skb->data);
+ skb_pull(ipp->skb, media_header_len);
+ if (ntohs(ipp->skb->protocol) == ETH_P_IP)
+ pp->protocol = SSH_PROTOCOL_IP4;
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ else if (ntohs(ipp->skb->protocol) == ETH_P_IPV6)
+ pp->protocol = SSH_PROTOCOL_IP6;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+ else
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Invalid skb protocol %d, dropping packet",
+ ntohs(ipp->skb->protocol)));
+ goto error;
+ }
+ }
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ switch (pp->protocol)
+ {
+ case SSH_PROTOCOL_IP4:
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE
+ SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to NF_IP_PRE_ROUTING",
+ ipp->skb));
+ NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING,
+ ipp->skb, ipp->skb->dev, NULL,
+ interceptor->nf->linux_fn.ip_rcv_finish,
+ ssh_nf_in4.priority + 1);
+#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ /* Call SSH_NF_IP_PREROUTING okfn() directly */
+ SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to ip_rcv_finish",
+ ipp->skb));
+ (*interceptor->linux_fn.ip_rcv_finish)(ipp->skb);
+#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case SSH_PROTOCOL_IP6:
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_passthrough++; });
+#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE
+ SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to NF_IP6_PRE_ROUTING",
+ ipp->skb));
+ NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, ipp->skb,
+ ipp->skb->dev, NULL,
+ interceptor->nf->linux_fn.ip6_rcv_finish,
+ ssh_nf_out6.priority + 1);
+#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ /* Call SSH_NF_IP6_PREROUTING okfn() directly */
+ SSH_DEBUG(SSH_D_LOWOK, ("Passing skb 0x%p to ip6_rcv_finish",
+ ipp->skb));
+ (*interceptor->linux_fn.ip6_rcv_finish)(ipp->skb);
+#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR,
+ ("pp->protocol 0x%x ipp->skb->protocol 0x%x",
+ pp->protocol, ipp->skb->protocol));
+ SSH_NOTREACHED;
+ goto error;
+ }
+
+ /* All done. */
+ goto sent;
+ }
+
+ /* If we do not wish to keep the broadcast state of
+ the packet, then reset the pkt_type to PACKET_HOST. */
+ if (!((ipp->skb->pkt_type == PACKET_MULTICAST
+ || ipp->skb->pkt_type == PACKET_BROADCAST)
+ && (pp->flags & SSH_PACKET_MEDIABCAST) != 0))
+ ipp->skb->pkt_type = PACKET_HOST;
+
+ /* Clear old routing decision */
+ if (SSH_SKB_DST(ipp->skb))
+ {
+ dst_release(SSH_SKB_DST(ipp->skb));
+ SSH_SKB_DST_SET(ipp->skb, NULL);
+ }
+
+ /* If the packet has an associated SKB and that SKB is associated
+ with a socket, orphan the skb from it's owner. These situations
+ may arise when sending packets towards the protocol when
+ the packet has been turned around by the engine. */
+ skb_orphan(ipp->skb);
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+
+ /* Media level */
+
+ /* If the packet does not include a media level header (for
+ example in case of pppoe), calling eth_type_trans() will
+ corrupt the beginning of packet. Instead skb->protocol must
+ be set from pp->protocol. */
+ if (media_header_len == 0)
+ {
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+ ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol);
+ }
+ else
+ {
+ /* Workaround for 802.2Q VLAN interfaces.
+ Calling eth_type_trans() would corrupt these packets,
+ as dev->hard_header_len includes the VLAN tag, but the
+ packet does not. */
+ if (ipp->skb->dev->priv_flags & IFF_802_1Q_VLAN)
+ {
+ struct ethhdr *ethernet;
+
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+ ethernet = ssh_get_eth_hdr(ipp->skb);
+ ipp->skb->protocol = ethernet->h_proto;
+ skb_pull(ipp->skb, media_header_len);
+ }
+
+ /* For all other packets, call eth_type_trans() to
+ set the protocol and the skb pointers. */
+ else
+ ipp->skb->protocol = eth_type_trans(ipp->skb, ipp->skb->dev);
+ }
+#else /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* IP level */
+
+ /* Assert that the media_header_len is always zero. */
+ SSH_ASSERT(media_header_len == 0);
+ SSH_SKB_SET_MACHDR(ipp->skb, ipp->skb->data);
+ ipp->skb->protocol = ssh_proto_to_skb_proto(pp->protocol);
+
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#ifdef DEBUG_LIGHT
+ if (ipp->skb->ip_summed == CHECKSUM_NONE)
+ SSH_DEBUG(SSH_D_LOWOK, ("Checksum is verified in software"));
+ else if (ipp->skb->ip_summed == CHECKSUM_UNNECESSARY)
+ SSH_DEBUG(SSH_D_LOWOK, ("Hardware claims to have verified checksum"));
+ else if (
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ ipp->skb->ip_summed == CHECKSUM_COMPLETE
+#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ ipp->skb->ip_summed == CHECKSUM_HW
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ )
+ SSH_DEBUG(SSH_D_LOWOK, ("Hardware has verified checksum, csum 0x%x",
+ SSH_SKB_CSUM(ipp->skb)));
+ /* ip_summed is CHECKSUM_PARTIAL, this should never happen. */
+ else
+ SSH_DEBUG(SSH_D_ERROR, ("Invalid HW checksum flag %d",
+ ipp->skb->ip_summed));
+#endif /* DEBUG_LIGHT */
+
+ /* Set nh pointer */
+ SSH_SKB_SET_NETHDR(ipp->skb, ipp->skb->data);
+ switch(ntohs(ipp->skb->protocol))
+ {
+ case ETH_P_IP:
+ neth = SSH_SKB_GET_NETHDR(ipp->skb);
+ if (neth == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("Could not access IP header"));
+ goto error;
+ }
+ SSH_SKB_SET_TRHDR(ipp->skb, neth + SSH_IPH4_HLEN(neth) * 4);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#ifdef LINUX_HAS_SKB_NFDEBUG
+ /* Mark SSH_NF_IP_PRE_ROUTING visited */
+ ipp->skb->nf_debug = (1 << SSH_NF_IP_PRE_ROUTING);
+#endif /* LINUX_HAS_SKB_NFDEBUG */
+#endif /* CONFIG_NETFILTER_DEBUG */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ /* Remove padding from packet. */
+ pkt_len4 = SSH_IPH4_LEN(neth);
+ SSH_ASSERT(pkt_len4 >= SSH_IPH4_HDRLEN && pkt_len4 <= 0xffff);
+ if (pkt_len4 != ipp->skb->len)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("Trimming skb down from %d to %lu",
+ ipp->skb->len,
+ (unsigned long) pkt_len4));
+ skb_trim(ipp->skb, pkt_len4);
+ }
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_packets_sent++;
+ interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len;
+ });
+
+#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE
+ NF_HOOK_THRESH(PF_INET, SSH_NF_IP_PRE_ROUTING,
+ ipp->skb, ipp->skb->dev, NULL,
+ interceptor->linux_fn.ip_rcv_finish,
+ ssh_nf_in4.priority + 1);
+#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ /* Call SSH_NF_IP_PREROUTING okfn() directly */
+ (*interceptor->linux_fn.ip_rcv_finish)(ipp->skb);
+#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ break;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case ETH_P_IPV6:
+ neth = SSH_SKB_GET_NETHDR(ipp->skb);
+ if (neth == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("Could not access IPv6 header"));
+ goto error;
+ }
+
+ ipproto = SSH_IPH6_NH(neth);
+ pkt_len6 = SSH_IPH6_LEN(neth) + SSH_IPH6_HDRLEN;
+
+ /* Parse hop-by-hop options and update IPv6 control buffer. */
+ SSH_LINUX_IP6CB(ipp->skb)->iif = ipp->skb->dev->ifindex;
+ SSH_LINUX_IP6CB(ipp->skb)->hop = 0;
+ SSH_LINUX_IP6CB(ipp->skb)->ra = 0;
+#ifdef LINUX_HAS_IP6CB_NHOFF
+ SSH_LINUX_IP6CB(ipp->skb)->nhoff = SSH_IPH6_OFS_NH;
+#endif /* LINUX_HAS_IP6CB_NHOFF */
+
+ offset = SSH_IPH6_HDRLEN;
+ /* Ipproto == HOPOPT */
+ if (ipproto == 0)
+ {
+ unsigned char *opt_ptr = neth + offset + 2;
+ int opt_len;
+
+ ipproto = SSH_IP6_EXT_COMMON_NH(neth + offset);
+ offset += SSH_IP6_EXT_COMMON_LENB(neth + offset);
+
+ while (opt_ptr < neth + offset)
+ {
+ opt_len = opt_ptr[1] + 2;
+ switch (opt_ptr[0])
+ {
+ /* PAD0 */
+ case 0:
+ opt_len = 1;
+ break;
+
+ /* PADN */
+ case 1:
+ break;
+
+ /* Jumbogram */
+ case 194:
+ /* Take packet len from option (skb->len is zero). */
+ pkt_len6 = SSH_GET_32BIT(&opt_ptr[2])
+ + sizeof(struct ipv6hdr);
+ break;
+
+ /* Router alert */
+ case 5:
+ SSH_LINUX_IP6CB(ipp->skb)->ra = opt_ptr - neth;
+ break;
+
+ /* Unknown / unsupported */
+ default:
+ /* Just skip unknown options. */
+ break;
+ }
+ opt_ptr += opt_len;
+ }
+ SSH_LINUX_IP6CB(ipp->skb)->hop = sizeof(struct ipv6hdr);
+
+#ifdef LINUX_HAS_IP6CB_NHOFF
+ SSH_LINUX_IP6CB(ipp->skb)->nhoff = sizeof(struct ipv6hdr);
+#endif /* LINUX_HAS_IP6CB_NHOFF */
+ }
+ SSH_SKB_SET_TRHDR(ipp->skb, neth + offset);
+
+ /* Remove padding from packet. */
+ SSH_ASSERT(pkt_len6 >= sizeof(struct ipv6hdr));
+ if (pkt_len6 != ipp->skb->len)
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("Trimming skb down from %d to %lu",
+ ipp->skb->len,
+ (unsigned long) pkt_len6));
+ skb_trim(ipp->skb, pkt_len6);
+ }
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_packets_sent++;
+ interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len;
+ });
+
+#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE
+ NF_HOOK_THRESH(PF_INET6, SSH_NF_IP6_PRE_ROUTING, ipp->skb,
+ ipp->skb->dev, NULL,
+ interceptor->linux_fn.ip6_rcv_finish,
+ ssh_nf_out6.priority + 1);
+#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ /* Call SSH_NF_IP6_PREROUTING okfn() directly */
+ (*interceptor->linux_fn.ip6_rcv_finish)(ipp->skb);
+#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ case ETH_P_ARP:
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_packets_sent++;
+ interceptor->stats.num_bytes_sent += (SshUInt64) ipp->skb->len;
+ });
+#ifdef SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE
+ NF_HOOK_THRESH(SSH_NFPROTO_ARP, NF_ARP_IN,
+ ipp->skb, ipp->skb->dev, NULL,
+ interceptor->linux_fn.arp_process,
+ ssh_nf_in_arp.priority + 1);
+#else /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ /* Call NF_ARP_IN okfn() directly */
+ (*interceptor->linux_fn.arp_process)(ipp->skb);
+#endif /* SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE */
+ break;
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ default:
+ SSH_DEBUG(SSH_D_ERROR,
+ ("skb->protocol 0x%x", htons(ipp->skb->protocol)));
+ SSH_NOTREACHED;
+ goto error;
+ }
+
+ /* All done. */
+ goto sent;
+ } /* SSH_PACKET_FROMADAPTER */
+
+ else
+ {
+ /* Not SSH_PACKET_FROMPROTOCOL or SSH_PACKET_FROMADAPTER. */
+ SSH_DEBUG(SSH_D_ERROR, ("Invalid packet direction flags"));
+ SSH_NOTREACHED;
+ goto error;
+ }
+
+ sent:
+ ipp->skb = NULL;
+
+ out:
+#ifdef INTERCEPTOR_IP_ALIGNS_PACKETS
+ /* pp can go NULL only with packet aligning. */
+
+ if (pp)
+#endif /* INTERCEPTOR_IP_ALIGNS_PACKETS */
+ ssh_interceptor_packet_free(pp);
+
+ /* Release net_device */
+ if (dev)
+ ssh_interceptor_release_netdev(dev);
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef SSH_LINUX_NF_FORWARD_AFTER_ENGINE
+ /* Release inbound net_device that was used for
+ FORWARD NF_HOOK traversal. */
+ if (in_dev)
+ ssh_interceptor_release_netdev(in_dev);
+#endif /* SSH_LINUX_NF_FORWARD_AFTER_ENGINE */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ return;
+
+ error:
+ SSH_LINUX_STATISTICS(interceptor, { interceptor->stats.num_errors++; });
+ goto out;
+}
+
+
+/******************************************************* General init/uninit */
+
+void ssh_interceptor_enable_interception(SshInterceptor interceptor,
+ Boolean enable)
+{
+
+ SSH_DEBUG(SSH_D_LOWOK, ("%s packet interception",
+ (enable ? "Enabling" : "Disabling")));
+ interceptor->enable_interception = enable;
+}
+
+/* Interceptor hook init. Utility function to initialize
+ individual hooks. */
+static Boolean
+ssh_interceptor_hook_init(struct SshLinuxHooksRec *hook)
+{
+ int rval;
+
+ SSH_ASSERT(hook->is_registered == FALSE);
+
+ if (hook->pf == PF_INET && hook->hooknum == SSH_NF_IP_PRE_ROUTING)
+ hook->priority = in_priority;
+
+ if (hook->pf == PF_INET && hook->hooknum == SSH_NF_IP_POST_ROUTING)
+ hook->priority = out_priority;
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (hook->pf == PF_INET6 && hook->hooknum == SSH_NF_IP6_PRE_ROUTING)
+ hook->priority = in6_priority;
+
+ if (hook->pf == PF_INET6 && hook->hooknum == SSH_NF_IP6_POST_ROUTING)
+ hook->priority = out6_priority;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ hook->ops->hook = hook->hookfn;
+ hook->ops->pf = hook->pf;
+ hook->ops->hooknum = hook->hooknum;
+ hook->ops->priority = hook->priority;
+
+ rval = nf_register_hook(hook->ops);
+ if (rval < 0)
+ {
+ if (hook->is_mandatory)
+ {
+ printk(KERN_ERR
+ "VPNClient netfilter %s hook failed to install.\n",
+ hook->name);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ hook->is_registered=TRUE;
+ return TRUE;
+}
+
+/* Utility function for uninstalling a single netfilter hook. */
+static void
+ssh_interceptor_hook_uninit(struct SshLinuxHooksRec *hook)
+{
+ if (hook->is_registered == FALSE)
+ return;
+
+ nf_unregister_hook(hook->ops);
+
+ hook->is_registered = FALSE;
+}
+
+/* IP/Network glue initialization. This must be called only
+ after the engine has "opened" the interceptor, and packet_callback()
+ has been set to a valid value. */
+Boolean
+ssh_interceptor_ip_glue_init(SshInterceptor interceptor)
+{
+ int i;
+
+ /* Verify that the hooks haven't been initialized yet. */
+ if (interceptor->hooks_installed)
+ {
+ SSH_DEBUG(2, ("init called when hooks are initialized already.\n"));
+ return TRUE;
+ }
+
+ /* Register all hooks */
+ for (i = 0; ssh_nf_hooks[i].name != NULL; i++)
+ {
+ if (ssh_interceptor_hook_init(&ssh_nf_hooks[i]) == FALSE)
+ goto fail;
+ }
+
+ interceptor->hooks_installed = TRUE;
+ return TRUE;
+
+ fail:
+ for (i = 0; ssh_nf_hooks[i].name != NULL; i++)
+ ssh_interceptor_hook_uninit(&ssh_nf_hooks[i]);
+ return FALSE;
+}
+
+/* Uninitialization of netfilter glue. */
+Boolean
+ssh_interceptor_ip_glue_uninit(SshInterceptor interceptor)
+{
+ int i;
+
+ /* Note that we do not perform concurrency control here!
+ We expect that we are essentially running single-threaded
+ in init/shutdown! */
+
+ if (interceptor->hooks_installed == FALSE)
+ return TRUE;
+
+ /* Unregister netfilter hooks */
+ for (i = 0; ssh_nf_hooks[i].name != NULL; i++)
+ ssh_interceptor_hook_uninit(&ssh_nf_hooks[i]);
+
+ interceptor->hooks_installed = FALSE;
+
+ return TRUE;
+}
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);
+}
diff --git a/drivers/interceptor/linux_kernel_alloc.c b/drivers/interceptor/linux_kernel_alloc.c
new file mode 100644
index 0000000..5f4c57f
--- /dev/null
+++ b/drivers/interceptor/linux_kernel_alloc.c
@@ -0,0 +1,182 @@
+/* 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_kernel_alloc.c
+ *
+ * Linux interceptor implementation of kernel memory allocation API.
+ *
+ */
+#include "linux_internal.h"
+#include "kernel_alloc.h"
+
+/* Malloc overhead:
+ <uint32> size (highest bit used for vmalloc indication)
+ <uint32> magic
+*/
+#define SSH_MALLOC_OVERHEAD (sizeof(SshUInt32) * 2)
+
+#define SSH_MALLOC_FLAG_LARGE (1 << 31)
+#define SSH_MALLOC_MAGIC 0x32085242
+#define SSH_MALLOC_MAGIC_FREED 0x00420042
+
+/* The maximum allocation size of kmalloc is 128K, however because of
+ memory fragmentation it's still insecure to allocate areas of many
+ pages with kmalloc. So for areas bigger than one page (usually 4K)
+ vmalloc() should be used. */
+#define SSH_NORMAL_MALLOC_MAX (128 << 10)
+
+/* The maximum allocation size of __get_free_pages is 2M, that is the
+ maximum order of alloc_pages() is 9 (2^9 of 4K pages), sometimes
+ erroneous code can cause arbitarily long allocations so this is
+ good sanity check. In general, allocations should be fairly
+ small. */
+#define SSH_VMALLOC_MAX (2 << 20)
+
+/* When do we shift to using vmalloc? In truth this should be ~equal
+ to normal-alloc-max. */
+#define SSH_VMALLOC_THRESHOLD (SSH_NORMAL_MALLOC_MAX)
+
+#ifdef DEBUG_LIGHT
+extern SshInterceptor ssh_interceptor_context;
+#endif /* DEBUG_LIGHT */
+
+
+void *
+ssh_kernel_alloc(size_t size, SshUInt32 flag)
+{
+ void *ptr;
+ int malloc_flag;
+ int total_size = size + SSH_MALLOC_OVERHEAD;
+ Boolean is_vmalloc = FALSE;
+
+ if (flag & SSH_KERNEL_ALLOC_WAIT)
+ malloc_flag = GFP_KERNEL;
+ else
+ {
+ SSH_ASSERT((flag & SSH_KERNEL_ALLOC_NOWAIT) ==
+ SSH_KERNEL_ALLOC_NOWAIT);
+ malloc_flag = GFP_ATOMIC;
+ }
+ if (flag & SSH_KERNEL_ALLOC_DMA)
+ {
+ SSH_ASSERT(malloc_flag == GFP_ATOMIC);
+ malloc_flag |= GFP_DMA;
+
+ if (total_size > SSH_NORMAL_MALLOC_MAX)
+ return NULL;
+ }
+ else
+ if (total_size > SSH_VMALLOC_THRESHOLD)
+ {
+ if (total_size > SSH_VMALLOC_MAX)
+ return NULL;
+
+ is_vmalloc = TRUE;
+ }
+ if (is_vmalloc)
+ {
+ unsigned long order;
+
+ /* Round up to nearest page size. The pages allocated with
+ __get_free_pages() are guaranteed to be contiguous in memory,
+ but you get one only if the requested amount of contiguous
+ pages are available. Linux doesn't currently have any
+ mechanisms for smart defragmentation of physical memory and
+ even if we had them they couldn't be fully reliable. So be
+ careful what you ask for, if you ask for too large a chunk of
+ memory you might end up not getting any at all. */
+ total_size = ((total_size + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE;
+ for (order = 0; ((1 << order) * PAGE_SIZE) < total_size; order++);
+
+ if (order >= MAX_ORDER)
+ return NULL;
+
+ ptr = (char *)__get_free_pages(malloc_flag, order);
+ }
+ else
+ ptr = kmalloc(total_size, malloc_flag);
+
+ if (ptr == NULL)
+ return NULL;
+
+ /* Log all allocs to first entry */
+ SSH_LINUX_STATISTICS(ssh_interceptor_context,
+ {
+ ssh_interceptor_context->stats.allocated_memory += (SshUInt64) total_size;
+
+ if (ssh_interceptor_context->stats.allocated_memory >
+ ssh_interceptor_context->stats.allocated_memory_max)
+ ssh_interceptor_context->stats.allocated_memory_max =
+ (SshUInt64) ssh_interceptor_context->stats.allocated_memory;
+
+ if (is_vmalloc)
+ ssh_interceptor_context->stats.num_allocations_large++;
+ else
+ ssh_interceptor_context->stats.num_allocations++;
+
+ ssh_interceptor_context->stats.num_allocations_total++;
+ });
+
+ /* Fill in the size and magic */
+ ((SshUInt32 *) ptr)[0] = total_size
+ | (is_vmalloc ? SSH_MALLOC_FLAG_LARGE : 0);
+
+#ifdef DEBUG_LIGHT
+ ((SshUInt32 *) ptr)[1] = SSH_MALLOC_MAGIC;
+#endif /* DEBUG_LIGHT */
+
+ return ((char *) ptr) + SSH_MALLOC_OVERHEAD;
+}
+
+void
+ssh_kernel_free(void *ptr)
+{
+ Boolean is_vmalloc;
+ SshUInt32 size;
+
+#ifdef DEBUG_LIGHT
+ /* Sanity check */
+ if (((SshUInt32 *) ptr)[-1] != SSH_MALLOC_MAGIC)
+ SSH_FATAL("ssh_kernel_free: Malloc object corrupted (invalid magic %x)",
+ ((unsigned int *) ptr)[-1]);
+
+ /* Set this to distinctive value to make sure we detect double free. */
+ ((SshUInt32 *) ptr)[-1] = SSH_MALLOC_MAGIC_FREED;
+#endif /* DEBUG_LIGHT */
+
+ size = ((SshUInt32 *) ptr)[-2];
+ is_vmalloc = (size & SSH_MALLOC_FLAG_LARGE) != 0;
+
+ if (is_vmalloc)
+ size &= ~SSH_MALLOC_FLAG_LARGE;
+
+ /* Statistics */
+ SSH_LINUX_STATISTICS(ssh_interceptor_context,
+ {
+ ssh_interceptor_context->stats.allocated_memory -= (SshUInt64) size;
+ if (is_vmalloc)
+ ssh_interceptor_context->stats.num_allocations_large--;
+ else
+ ssh_interceptor_context->stats.num_allocations--;
+
+ ssh_interceptor_context->stats.num_allocations_total--;
+ });
+
+ /* Free the real block */
+ if (is_vmalloc)
+ {
+ SshUInt32 order;
+ for (order = 0; ((1 << order) * PAGE_SIZE) < size; order++);
+ free_pages((unsigned long)((char *) ptr - SSH_MALLOC_OVERHEAD), order);
+ }
+ else
+ kfree((char *) ptr - SSH_MALLOC_OVERHEAD);
+}
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);
diff --git a/drivers/interceptor/linux_mutex.c b/drivers/interceptor/linux_mutex.c
new file mode 100644
index 0000000..7a9fae7
--- /dev/null
+++ b/drivers/interceptor/linux_mutex.c
@@ -0,0 +1,115 @@
+/* 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_mutex.c
+ *
+ * Linux interceptor kernel mutex API implementation.
+ *
+ */
+
+#include "linux_internal.h"
+#include "linux_mutex_internal.h"
+
+extern SshInterceptor ssh_interceptor_context;
+
+Boolean ssh_kernel_mutex_init(SshKernelMutex mutex)
+{
+ spin_lock_init(&mutex->lock);
+
+#ifdef DEBUG_LIGHT
+ mutex->taken = FALSE;
+ mutex->jiffies = 0;
+#endif
+ return TRUE;
+}
+
+/* Allocates a simple mutex. This should be as fast as possible, but work
+ between different processors in a multiprocessor machine. This need
+ not work between different independent processes. */
+
+SshKernelMutex
+ssh_kernel_mutex_alloc(void)
+{
+ SshKernelMutex m;
+
+ m = ssh_calloc(1, sizeof(struct SshKernelMutexRec));
+ if (m == NULL)
+ return NULL;
+
+ if (!ssh_kernel_mutex_init(m))
+ {
+ ssh_free(m);
+ m = NULL;
+ }
+ return m;
+}
+
+/* Frees the given mutex. The mutex must not be locked when it is
+ freed. */
+
+void ssh_kernel_mutex_uninit(SshKernelMutex mutex)
+{
+ SSH_ASSERT(!mutex->taken);
+}
+
+void ssh_kernel_mutex_free(SshKernelMutex mutex)
+{
+ if (mutex)
+ {
+ ssh_kernel_mutex_uninit(mutex);
+ ssh_free(mutex);
+ }
+}
+
+#ifdef KERNEL_MUTEX_USE_FUNCTIONS
+/* Locks the mutex. Only one thread of execution can have a mutex locked
+ at a time. This will block until execution can continue. One should
+ not keep mutexes locked for extended periods of time. */
+void
+ssh_kernel_mutex_lock_i(SshKernelMutex mutex)
+{
+ SSH_LINUX_STATISTICS(ssh_interceptor_context,
+ { ssh_interceptor_context->stats.num_light_locks++; });
+
+ spin_lock(&mutex->lock);
+
+ SSH_ASSERT(!mutex->taken);
+
+#ifdef DEBUG_LIGHT
+ mutex->taken = TRUE;
+ mutex->jiffies = jiffies;
+#endif /* DEBUG_LIGHT */
+}
+
+/* Unlocks the mutex. If other threads are waiting to lock the mutex,
+ one of them will get the lock and continue execution. */
+
+void
+ssh_kernel_mutex_unlock_i(SshKernelMutex mutex)
+{
+ SSH_ASSERT(mutex->taken);
+#ifdef DEBUG_LIGHT
+ mutex->taken = FALSE;
+#endif /* DEBUG_LIGHT */
+
+ spin_unlock(&mutex->lock);
+}
+#endif /* KERNEL_MUTEX_USE_FUNCTIONS */
+
+#ifdef DEBUG_LIGHT
+/* Check that the mutex is locked. It is a fatal error if it is not. */
+
+void
+ssh_kernel_mutex_assert_is_locked(SshKernelMutex mutex)
+{
+ SSH_ASSERT(mutex->taken);
+}
+#endif /* DEBUG_LIGHT */
diff --git a/drivers/interceptor/linux_mutex_internal.h b/drivers/interceptor/linux_mutex_internal.h
new file mode 100644
index 0000000..954fbb6
--- /dev/null
+++ b/drivers/interceptor/linux_mutex_internal.h
@@ -0,0 +1,49 @@
+/* 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_mutex_internal.h
+ *
+ * Linux interceptor internal defines for kernel mutex API.
+ *
+ */
+
+#ifndef LINUX_MUTEX_INTERNAL_H
+#define LINUX_MUTEX_INTERNAL_H
+
+#include <linux/spinlock.h>
+#include <asm/current.h>
+
+typedef struct SshKernelMutexRec
+{
+ spinlock_t lock;
+ unsigned long flags;
+
+#ifdef DEBUG_LIGHT
+ Boolean taken;
+ unsigned long jiffies;
+#endif
+} SshKernelMutexStruct;
+
+#ifdef CONFIG_PREEMPT
+
+#include <linux/preempt.h>
+
+#define icept_preempt_enable() preempt_enable()
+#define icept_preempt_disable() preempt_disable()
+
+#else /* CONFIG_PREEMPT */
+
+#define icept_preempt_enable() do {;} while(0)
+#define icept_preempt_disable() do {;} while(0)
+
+#endif /* CONFIG_PREEMPT */
+
+#endif /* LINUX_MUTEX_INTERNAL_H */
diff --git a/drivers/interceptor/linux_packet.c b/drivers/interceptor/linux_packet.c
new file mode 100644
index 0000000..7a9649b
--- /dev/null
+++ b/drivers/interceptor/linux_packet.c
@@ -0,0 +1,846 @@
+/* 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_packet.c
+ *
+ * Linux interceptor implementation of interceptor API packet functions.
+ *
+ */
+
+#include "linux_internal.h"
+#include "linux_packet_internal.h"
+
+/* Packet context freelist head pointer array. There is a freelist entry for
+ each CPU and one freelist entry (with index SSH_LINUX_INTERCEPTOR_NR_CPUS)
+ shared among all CPU's. When getting a packet, the current CPU freelist is
+ searched, if that is empty the shared freelist is searched (which requires
+ taking a lock). When returning a packet, if the CPU is the same as when
+ the packet was allocated, the packet is returned to the CPU freelist, if
+ not it is returned to the shared freelist (which again requires taking
+ a lock). */
+struct SshInterceptorInternalPacketRec
+*ssh_packet_freelist_head[SSH_LINUX_INTERCEPTOR_NR_CPUS + 1] = {NULL};
+
+extern SshInterceptor ssh_interceptor_context;
+
+static inline SshInterceptorInternalPacket
+ssh_freelist_packet_get(SshInterceptor interceptor,
+ Boolean may_borrow)
+{
+ SshInterceptorInternalPacket p;
+ unsigned int cpu;
+
+ icept_preempt_disable();
+
+ cpu = smp_processor_id();
+
+ p = ssh_packet_freelist_head[cpu];
+ if (p)
+ {
+ p->cpu = cpu;
+
+ ssh_packet_freelist_head[p->cpu] =
+ (SshInterceptorInternalPacket) p->packet.next;
+ }
+ else
+ {
+ /* Try getting a packet from the shared freelist */
+ ssh_kernel_mutex_lock(interceptor->packet_lock);
+
+ p = ssh_packet_freelist_head[SSH_LINUX_INTERCEPTOR_NR_CPUS];
+ if (p)
+ {
+ p->cpu = cpu;
+
+ ssh_packet_freelist_head[SSH_LINUX_INTERCEPTOR_NR_CPUS] =
+ (SshInterceptorInternalPacket) p->packet.next;
+
+ ssh_kernel_mutex_unlock(interceptor->packet_lock);
+ goto done;
+ }
+ ssh_kernel_mutex_unlock(interceptor->packet_lock);
+
+ p = ssh_malloc(sizeof(*p));
+ if (!p)
+ goto done;
+
+ p->cpu = cpu;
+ }
+
+ done:
+ icept_preempt_enable();
+
+ return p;
+}
+
+static inline void
+ssh_freelist_packet_put(SshInterceptor interceptor,
+ SshInterceptorInternalPacket p)
+{
+ unsigned int cpu;
+
+ icept_preempt_disable();
+
+ cpu = p->cpu;
+
+ SSH_ASSERT(cpu < SSH_LINUX_INTERCEPTOR_NR_CPUS);
+
+#ifdef DEBUG_LIGHT
+ memset(p, 'F', sizeof(*p));
+#endif /* DEBUG_LIGHT */
+
+ if (likely(cpu == smp_processor_id()))
+ {
+ p->packet.next =
+ (SshInterceptorPacket) ssh_packet_freelist_head[cpu];
+ ssh_packet_freelist_head[cpu] = p;
+ }
+ else
+ {
+ cpu = SSH_LINUX_INTERCEPTOR_NR_CPUS;
+
+ /* The executing CPU is not the same as when the packet was
+ allocated. Put the packet back to the shared freelist */
+ ssh_kernel_mutex_lock(interceptor->packet_lock);
+
+ p->packet.next =
+ (SshInterceptorPacket) ssh_packet_freelist_head[cpu];
+ ssh_packet_freelist_head[cpu] = p;
+
+ ssh_kernel_mutex_unlock(interceptor->packet_lock);
+ }
+
+ icept_preempt_enable();
+}
+
+
+Boolean
+ssh_interceptor_packet_freelist_init(SshInterceptor interceptor)
+{
+ unsigned int i;
+
+ for (i = 0; i < SSH_LINUX_INTERCEPTOR_NR_CPUS + 1; i++)
+ ssh_packet_freelist_head[i] = NULL;
+
+ return TRUE;
+}
+
+void
+ssh_interceptor_packet_freelist_uninit(SshInterceptor interceptor)
+{
+ SshInterceptorInternalPacket p;
+ unsigned int i;
+
+ ssh_kernel_mutex_lock(interceptor->packet_lock);
+
+ for (i = 0; i < SSH_LINUX_INTERCEPTOR_NR_CPUS + 1; i++)
+ {
+ /* Traverse freelist and free allocated all packets. */
+ p = ssh_packet_freelist_head[i];
+ while (p != NULL)
+ {
+ ssh_packet_freelist_head[i] =
+ (SshInterceptorInternalPacket) p->packet.next;
+
+ ssh_free(p);
+
+ p = ssh_packet_freelist_head[i];
+ }
+ }
+
+ ssh_kernel_mutex_unlock(interceptor->packet_lock);
+}
+
+/******************************************* General packet allocation stuff */
+
+/* Allocates new packet skb with copied data from original
+ + the extra free space reserved for extensions. */
+struct sk_buff *
+ssh_interceptor_packet_skb_dup(SshInterceptor interceptor,
+ struct sk_buff *skb,
+ size_t addbytes_active_ofs,
+ size_t addbytes_active)
+{
+ struct sk_buff *new_skb;
+ ssize_t offset;
+ size_t addbytes_spare_start = SSH_INTERCEPTOR_PACKET_HEAD_ROOM;
+ size_t addbytes_spare_end = SSH_INTERCEPTOR_PACKET_TAIL_ROOM;
+ unsigned char *ptr;
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("skb dup: len %d extra %d offset %d headroom %d tailroom %d",
+ skb->len, (int) addbytes_active, (int) addbytes_active_ofs,
+ (int) addbytes_spare_start, (int) addbytes_spare_end));
+
+ /* Create new skb */
+ new_skb = alloc_skb(skb->len + addbytes_active +
+ addbytes_spare_start +
+ addbytes_spare_end,
+ SSH_LINUX_ALLOC_SKB_GFP_MASK);
+ if (!new_skb)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_failed_allocs++; });
+ return NULL;
+ }
+
+ /* Set the fields */
+ new_skb->len = skb->len + addbytes_active;
+ new_skb->data = new_skb->head + addbytes_spare_start;
+ SSH_SKB_SET_TAIL(new_skb, new_skb->data + new_skb->len);
+ new_skb->protocol = skb->protocol;
+ new_skb->dev = skb->dev;
+
+ new_skb->pkt_type = skb->pkt_type;
+#ifdef LINUX_HAS_SKB_STAMP
+ new_skb->stamp = skb->stamp;
+#endif /* LINUX_HAS_SKB_STAMP */
+ new_skb->destructor = NULL_FNPTR;
+
+ /* Set transport header offset. TX Checksum offloading relies this to be
+ set in the case that the checksum has to be calculated in software
+ in dev_queue_xmit(). */
+ ptr = SSH_SKB_GET_TRHDR(skb);
+ if (ptr != NULL)
+ {
+ offset = ptr - skb->data;
+ if (offset > addbytes_active_ofs)
+ offset += addbytes_active;
+ SSH_SKB_SET_TRHDR(new_skb, new_skb->data + offset);
+ }
+
+ /* Set mac header offset. This is set for convinience. Note that if
+ mac header has already been removed from the sk_buff then the mac
+ header data is not copied to the duplicate. */
+ ptr = SSH_SKB_GET_MACHDR(skb);
+ if (ptr != NULL)
+ {
+ offset = ptr - skb->data;
+ if (offset > addbytes_active_ofs)
+ offset += addbytes_active;
+ SSH_SKB_SET_MACHDR(new_skb, new_skb->data + offset);
+ }
+
+ /* Set network header offset. This one is maintained out of convenience
+ (so we need not do setting by hand unless we definitely want to,
+ i.e. sending packet out). */
+ ptr = SSH_SKB_GET_NETHDR(skb);
+ if (ptr != NULL)
+ {
+ offset = ptr - skb->data;
+ if (offset > addbytes_active_ofs)
+ offset += addbytes_active;
+ SSH_SKB_SET_NETHDR(new_skb, new_skb->data + offset);
+ }
+
+ /* not used by old interceptor. */
+ new_skb->sk = NULL; /* kernel does this.. copying might make more sense? */
+
+ /* not needed according to kernel (alloc_skb does this) */
+ atomic_set(&new_skb->users, 1);
+
+ /* Set csum fields. */
+ new_skb->ip_summed = skb->ip_summed;
+ if (
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ new_skb->ip_summed == CHECKSUM_COMPLETE
+#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ new_skb->ip_summed == CHECKSUM_HW
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ )
+ {
+ SSH_SKB_CSUM(new_skb) = SSH_SKB_CSUM(skb);
+ }
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ else if (new_skb->ip_summed == CHECKSUM_PARTIAL)
+ {
+ SSH_SKB_CSUM_OFFSET(new_skb) = SSH_SKB_CSUM_OFFSET(skb);
+#ifdef LINUX_HAS_SKB_CSUM_START
+ /* Set csum_start. */
+ offset = (skb->head + skb->csum_start) - skb->data;
+ if (offset > addbytes_active_ofs)
+ offset += addbytes_active;
+ new_skb->csum_start = (new_skb->data + offset) - new_skb->head;
+#endif /* LINUX_HAS_SKB_CSUM_START */
+ }
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ else
+ {
+ SSH_SKB_CSUM(new_skb) = 0;
+ }
+
+ new_skb->priority = skb->priority;
+ SSH_SKB_DST_SET(new_skb, dst_clone(SSH_SKB_DST(skb)));
+ memcpy(new_skb->cb, skb->cb, sizeof(skb->cb));
+
+#ifdef LINUX_HAS_SKB_SECURITY
+ new_skb->security = skb->security;
+#endif /* LINUX_HAS_SKB_SECURITY */
+
+#ifdef CONFIG_NETFILTER
+ SSH_SKB_MARK(new_skb) = SSH_SKB_MARK(skb);
+#ifdef LINUX_HAS_SKB_NFCACHE
+ new_skb->nfcache = NFC_UNKNOWN;
+#endif /* LINUX_HAS_SKB_NFCACHE */
+#ifdef CONFIG_NETFILTER_DEBUG
+#ifdef LINUX_HAS_SKB_NFDEBUG
+ new_skb->nf_debug = skb->nf_debug;
+#endif /* LINUX_HAS_SKB_NFDEBUG */
+#endif /* CONFIG_NETFILTER_DEBUG */
+#endif /* CONFIG_NETFILTER */
+
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_copied_packets++; });
+
+ /* Copy data from active_ofs+ => active_ofs+addbytes+ */
+ if ((skb->len - addbytes_active_ofs) > 0)
+ {
+ memcpy(new_skb->data + addbytes_active_ofs + addbytes_active,
+ skb->data + addbytes_active_ofs,
+ skb->len - addbytes_active_ofs);
+ }
+
+ /* Copy the 0+ => 0+ where value < ofs (header part that is left alone). */
+ if (addbytes_active_ofs > 0)
+ {
+ SSH_ASSERT(addbytes_active_ofs <= skb->len);
+ memcpy(new_skb->data,
+ skb->data,
+ addbytes_active_ofs);
+ }
+
+ return new_skb;
+}
+
+/* Allocates a packet header wrapping the given skbuff.
+ Packet headers can be allocated only using this function.
+
+ Note that the actual packet->skb is NULL after packet has been
+ returned.
+
+ This function returns NULL if the packet header cannot be
+ allocated. */
+
+SshInterceptorInternalPacket
+ssh_interceptor_packet_alloc_header(SshInterceptor interceptor,
+ SshUInt32 flags,
+ SshInterceptorProtocol protocol,
+ SshUInt32 ifnum_in,
+ SshUInt32 ifnum_out,
+ struct sk_buff *skb,
+ Boolean force_copy_skbuff,
+ Boolean free_original_on_copy,
+ Boolean packet_from_system)
+{
+ SshInterceptorInternalPacket p;
+
+ /* Linearize the packet in case it isn't already. */
+#ifdef LINUX_SKB_LINEARIZE_NEEDS_FLAGS
+ if (skb && skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0)
+ return NULL;
+#else /* LINUX_SKB_LINEARIZE_NEEDS_FLAGS */
+ if (skb && skb_is_nonlinear(skb) && skb_linearize(skb) != 0)
+ return NULL;
+#endif /* LINUX_SKB_LINEARIZE_NEEDS_FLAGS */
+
+ /* Allocate a wrapper structure */
+ p = ssh_freelist_packet_get(interceptor, !packet_from_system);
+ if (p == NULL)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_failed_allocs++; });
+ return NULL;
+ }
+
+ /* Initialize all the fields */
+ p->packet.flags = flags;
+
+ /* Assert that the interface number fits into SshInterceptorIfnum.
+ Note that both interface numbers may be equal to
+ SSH_INTERCEPTOR_INVALID_IFNUM. */
+ SSH_LINUX_ASSERT_IFNUM(ifnum_in);
+ SSH_LINUX_ASSERT_IFNUM(ifnum_out);
+
+ p->packet.ifnum_in = ifnum_in;
+ p->packet.ifnum_out = ifnum_out;
+ p->original_ifnum = ifnum_in;
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+ p->packet.route_selector = 0;
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ p->packet.pmtu = 0;
+ p->packet.protocol = protocol;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ memset(p->packet.extension, 0, sizeof(p->packet.extension));
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ /* Copy the linux fwmark to the extension slot indexed by
+ SSH_LINUX_FWMARK_EXTENSION_SELECTOR. */
+ if (skb)
+ p->packet.extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR] =
+ SSH_SKB_MARK(skb);
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ p->interceptor = interceptor;
+ p->skb = skb;
+
+ SSH_LINUX_STATISTICS(interceptor,
+ {
+ interceptor->stats.num_allocated_packets++;
+ interceptor->stats.num_allocated_packets_total++;
+ });
+
+ if (skb)
+ {
+ /* we have skb */
+ if (force_copy_skbuff || skb_cloned(skb))
+ {
+ /* The skb was already cloned, so make a new copy to be modified by
+ * the engine processing. */
+ p->skb = ssh_interceptor_packet_skb_dup(interceptor, skb, 0, 0);
+ if (p->skb == NULL)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_allocated_packets--; });
+ ssh_freelist_packet_put(interceptor, p);
+ return NULL;
+ }
+
+ if (free_original_on_copy)
+ {
+ /* Free the original buffer as we will not return it anymore */
+ dev_kfree_skb_any(skb);
+ }
+ }
+ else
+ {
+ /* No one else has cloned the original skb, so use it
+ without copying */
+ p->skb = skb;
+ }
+
+ /* If the packet is of media-broadcast persuasion, add it to the flags. */
+ if (p->skb->pkt_type == PACKET_BROADCAST)
+ p->packet.flags |= SSH_PACKET_MEDIABCAST;
+ if (p->skb->pkt_type == PACKET_MULTICAST)
+ p->packet.flags |= SSH_PACKET_MEDIABCAST;
+
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ if (p->skb->ip_summed == CHECKSUM_COMPLETE /* inbound */
+ || p->skb->ip_summed == CHECKSUM_UNNECESSARY /* inbound */
+ || p->skb->ip_summed == CHECKSUM_PARTIAL) /* outbound */
+ p->packet.flags |= SSH_PACKET_HWCKSUM;
+#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ if (p->skb->ip_summed == CHECKSUM_HW /* inbound/outbound */
+ || p->skb->ip_summed == CHECKSUM_UNNECESSARY) /* inbound */
+ p->packet.flags |= SSH_PACKET_HWCKSUM;
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("alloc header: skb len %d headroom %d tailroom %d",
+ p->skb->len, skb_headroom(p->skb), skb_tailroom(p->skb)));
+ }
+
+ return p;
+}
+
+/* Allocates a packet of at least the given size. Packets can only be
+ allocated using this function (either internally by the interceptor or
+ by other code by calling this function). This returns NULL if no more
+ packets can be allocated. */
+
+SshInterceptorPacket
+ssh_interceptor_packet_alloc(SshInterceptor interceptor,
+ SshUInt32 flags,
+ SshInterceptorProtocol protocol,
+ SshInterceptorIfnum ifnum_in,
+ SshInterceptorIfnum ifnum_out,
+ size_t total_len)
+{
+ SshInterceptorInternalPacket packet;
+ size_t len;
+ struct sk_buff *skb;
+
+ packet = (SshInterceptorInternalPacket)
+ ssh_interceptor_packet_alloc_header(interceptor,
+ flags,
+ protocol,
+ ifnum_in,
+ ifnum_out,
+ NULL,
+ FALSE,
+ FALSE,
+ FALSE);
+ if (!packet)
+ return NULL; /* header allocation failed */
+
+ /* Allocate actual kernel packet. Note that some overhead is calculated
+ so that media headers etc. fit without additional allocations or
+ copying. */
+ len = total_len + SSH_INTERCEPTOR_PACKET_HEAD_ROOM +
+ SSH_INTERCEPTOR_PACKET_TAIL_ROOM;
+ skb = alloc_skb(len, SSH_LINUX_ALLOC_SKB_GFP_MASK);
+ if (skb == NULL)
+ {
+ SSH_LINUX_STATISTICS(interceptor,
+ { interceptor->stats.num_failed_allocs++; });
+ ssh_freelist_packet_put(interceptor, packet);
+ return NULL;
+ }
+ packet->skb = skb;
+
+ /* Set data area inside the packet */
+ skb->len = total_len;
+ skb->data = skb->head + SSH_INTERCEPTOR_PACKET_HEAD_ROOM;
+ SSH_SKB_SET_TAIL(skb, skb->data + total_len);
+
+ /* Ensure the IP header offset is 16 byte aligned for ethernet frames. */
+ if (protocol == SSH_PROTOCOL_ETHERNET)
+ {
+ /* Assert that SSH_INTERCEPTOR_PACKET_TAIL_ROOM is large enough */
+ SSH_ASSERT(SSH_SKB_GET_END(skb) - SSH_SKB_GET_TAIL(skb) >= 2);
+ skb->data += 2;
+ /* This works on both pointers and offsets. */
+ skb->tail += 2;
+ }
+
+#ifdef LINUX_HAS_NEW_CHECKSUM_FLAGS
+ if (flags & SSH_PACKET_HWCKSUM)
+ {
+ if (flags & SSH_PACKET_FROMADAPTER)
+ skb->ip_summed = CHECKSUM_COMPLETE;
+ else if (flags & SSH_PACKET_FROMPROTOCOL)
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ }
+#else /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+ if (flags & SSH_PACKET_HWCKSUM)
+ skb->ip_summed = CHECKSUM_HW;
+#endif /* LINUX_HAS_NEW_CHECKSUM_FLAGS */
+
+ /* If support for other than IPv6, IPv4 and ARP
+ inside the engine on Linux are to be supported, their
+ protocol types must be added here. */
+ switch(protocol)
+ {
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ case SSH_PROTOCOL_IP6:
+ skb->protocol = __constant_htons(ETH_P_IPV6);
+ break;
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ case SSH_PROTOCOL_ARP:
+ skb->protocol = __constant_htons(ETH_P_ARP);
+ break;
+
+ case SSH_PROTOCOL_IP4:
+ default:
+ skb->protocol = __constant_htons(ETH_P_IP);
+ break;
+ }
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("alloc packet: skb len %d headroom %d tailroom %d",
+ packet->skb->len, skb_headroom(packet->skb),
+ skb_tailroom(packet->skb)));
+
+ return (SshInterceptorPacket) packet;
+}
+
+/* Frees the given packet. All packets allocated by
+ ssh_interceptor_packet_alloc must eventually be freed using this
+ function by either calling this explicitly or by passing the packet
+ to the interceptor send function. */
+
+void
+ssh_interceptor_packet_free(SshInterceptorPacket pp)
+{
+ SshInterceptorInternalPacket packet = (SshInterceptorInternalPacket) pp;
+
+ /* Free the packet buffer first */
+ if (packet->skb)
+ {
+ dev_kfree_skb_any(packet->skb);
+ packet->skb = NULL;
+ }
+
+ SSH_LINUX_STATISTICS(packet->interceptor,
+ { packet->interceptor->stats.num_allocated_packets--; });
+
+ /* Free the wrapper */
+ ssh_freelist_packet_put(packet->interceptor, packet);
+}
+
+#if defined(KERNEL_INTERCEPTOR_USE_FUNCTIONS) || !defined(KERNEL)
+/* Returns the length of the data packet. */
+size_t
+ssh_interceptor_packet_len(SshInterceptorPacket pp)
+{
+ SshInterceptorInternalPacket packet = (SshInterceptorInternalPacket) pp;
+ return packet->skb->len;
+}
+#endif /* defined(KERNEL_INTERCEPTOR_USE_FUNCTIONS) || !defined(KERNEL) */
+
+
+/* Copies data into the packet. Space for the new data must already have
+ been allocated. It is a fatal error to attempt to copy beyond the
+ allocated packet. Multiple threads may call this function concurrently,
+ but not for the same packet. This does not change the length of the
+ packet. */
+
+Boolean
+ssh_interceptor_packet_copyin(SshInterceptorPacket pp, size_t offset,
+ const unsigned char *buf, size_t len)
+{
+ SshInterceptorInternalPacket packet = (SshInterceptorInternalPacket) pp;
+
+ memmove(packet->skb->data + offset, buf, len);
+
+ return TRUE;
+}
+
+/* Copies data out from the packet. Space for the new data must
+ already have been allocated. It is a fatal error to attempt to
+ copy beyond the allocated packet. Multiple threads may call this
+ function concurrently, but not for the same packet. */
+
+void
+ssh_interceptor_packet_copyout(SshInterceptorPacket pp, size_t offset,
+ unsigned char *buf, size_t len)
+{
+ SshInterceptorInternalPacket packet = (SshInterceptorInternalPacket) pp;
+
+ memmove(buf, packet->skb->data + offset, len);
+}
+
+#define SSH_PACKET_IDATA_IFNUM_OFFSET 0
+#define SSH_PACKET_IDATA_DST_CACHE_ID_OFFSET sizeof(SshUInt32)
+#define SSH_PACKET_IDATA_PKT_TYPE_OFFSET (2 * sizeof(SshUInt32))
+#define SSH_PACKET_IDATA_TRHDR_OFFSET (3 * sizeof(SshUInt32))
+#define SSH_PACKET_IDATA_CP_OFFSET (4 * sizeof(SshUInt32))
+#define SSH_PACKET_IDATA_MINLEN SSH_PACKET_IDATA_CP_OFFSET
+
+Boolean ssh_interceptor_packet_export_internal_data(SshInterceptorPacket pp,
+ unsigned char **data_ret,
+ size_t *len_return)
+{
+ unsigned char *data;
+ SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket)pp;
+ SshUInt32 dst_cache_id = 0;
+ SshUInt32 transport_offset = 0;
+
+ if (ipp->skb == NULL)
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Unable to export internal packet data, sk buff"));
+ *data_ret = NULL;
+ *len_return = 0;
+ return FALSE;
+ }
+
+ data = ssh_calloc(1, SSH_PACKET_IDATA_MINLEN + sizeof(ipp->skb->cb));
+ if (data == NULL)
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("Unable to export internal packet data"));
+ *data_ret = NULL;
+ *len_return = 0;
+ return FALSE;
+ }
+
+ SSH_PUT_32BIT(data, SSH_PACKET_IDATA_IFNUM_OFFSET);
+
+ dst_cache_id =
+ ssh_interceptor_packet_cache_dst_entry(ssh_interceptor_context, pp);
+ SSH_PUT_32BIT(data + SSH_PACKET_IDATA_DST_CACHE_ID_OFFSET, dst_cache_id);
+
+ SSH_PUT_8BIT(data + SSH_PACKET_IDATA_PKT_TYPE_OFFSET, ipp->skb->pkt_type);
+
+ transport_offset = (SshUInt32)(SSH_SKB_GET_TRHDR(ipp->skb) - ipp->skb->data);
+
+ SSH_PUT_32BIT(data + SSH_PACKET_IDATA_TRHDR_OFFSET, transport_offset);
+ memcpy(data + SSH_PACKET_IDATA_CP_OFFSET, ipp->skb->cb,
+ sizeof(ipp->skb->cb));
+
+ *data_ret = data;
+ *len_return = SSH_PACKET_IDATA_MINLEN + sizeof(ipp->skb->cb);
+
+ return TRUE;
+}
+
+void
+ssh_interceptor_packet_discard_internal_data(unsigned char *data,
+ size_t data_len)
+{
+ SshUInt32 dst_cache_id;
+
+ if (data_len == 0)
+ return;
+
+ if (data == NULL || data_len < SSH_PACKET_IDATA_MINLEN)
+ {
+ /* Attempt to import corrupted data. */
+ SSH_DEBUG(SSH_D_FAIL, ("Unable to import internal packet data"));
+ return;
+ }
+
+ dst_cache_id = SSH_GET_32BIT(data + SSH_PACKET_IDATA_DST_CACHE_ID_OFFSET);
+
+ ssh_interceptor_packet_return_dst_entry(ssh_interceptor_context,
+ dst_cache_id, NULL, TRUE);
+}
+
+Boolean ssh_interceptor_packet_import_internal_data(SshInterceptorPacket pp,
+ const unsigned char *data,
+ size_t len)
+{
+ SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket)pp;
+ SshUInt32 orig_ifnum;
+ SshUInt32 dst_cache_id;
+ SshUInt32 transport_offset;
+ Boolean remove_only = FALSE;
+
+ if (ipp->skb == NULL)
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Unable to import internal packet data, no skb"));
+ return FALSE;
+ }
+
+ if (len == 0)
+ {
+ /* No data to import, i.e. packet created by engine. */
+ ipp->original_ifnum = SSH_INTERCEPTOR_INVALID_IFNUM;
+ ipp->skb->pkt_type = PACKET_HOST;
+ return TRUE;
+ }
+
+ if (data == NULL || len < (SSH_PACKET_IDATA_MINLEN + sizeof(ipp->skb->cb)))
+ {
+ /* Attempt to import corrupted data. */
+ SSH_DEBUG(SSH_D_FAIL, ("Unable to import internal packet data"));
+ return FALSE;
+ }
+
+ orig_ifnum = SSH_GET_32BIT(data + SSH_PACKET_IDATA_IFNUM_OFFSET);
+ ipp->original_ifnum = orig_ifnum;
+
+ dst_cache_id = SSH_GET_32BIT(data + SSH_PACKET_IDATA_DST_CACHE_ID_OFFSET);
+
+ if (pp->flags & SSH_PACKET_UNMODIFIED)
+ remove_only = FALSE;
+ ssh_interceptor_packet_return_dst_entry(ssh_interceptor_context,
+ dst_cache_id, pp, remove_only);
+
+ ipp->skb->pkt_type = SSH_GET_8BIT(data + SSH_PACKET_IDATA_PKT_TYPE_OFFSET);
+ transport_offset = SSH_GET_32BIT(data + SSH_PACKET_IDATA_TRHDR_OFFSET);
+
+ SSH_SKB_SET_TRHDR(ipp->skb, ipp->skb->data + transport_offset);
+
+ memcpy(ipp->skb->cb, data + SSH_PACKET_IDATA_CP_OFFSET,
+ sizeof(ipp->skb->cb));
+
+ return TRUE;
+}
+
+Boolean
+ssh_interceptor_packet_align(SshInterceptorPacket pp, size_t offset)
+{
+ SshInterceptorInternalPacket packet = (SshInterceptorInternalPacket) pp;
+ struct sk_buff *skb, *new_skb;
+ unsigned long addr;
+ size_t word_size, bytes;
+
+ word_size = sizeof(int *);
+ SSH_ASSERT(word_size < SSH_INTERCEPTOR_PACKET_TAIL_ROOM);
+
+ skb = packet->skb;
+
+ addr = (unsigned long) (skb->data + offset);
+
+ bytes = (size_t)((((addr + word_size - 1) / word_size) * word_size) - addr);
+ if (bytes == 0)
+ return TRUE;
+
+ if (SSH_SKB_GET_END(skb) - SSH_SKB_GET_TAIL(skb) >= bytes)
+ {
+ memmove(skb->data + bytes, skb->data, skb->len);
+ skb->data += bytes;
+ /* This works for both pointers and offsets. */
+ skb->tail += bytes;
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Aligning skb->data %p at offset %d to word "
+ "boundary (word_size %d), headroom %d tailroom %d",
+ skb->data, (int) offset, (int) word_size,
+ skb_headroom(skb), skb_tailroom(skb)));
+
+ return TRUE;
+ }
+ else if (skb->data - skb->head >= word_size - bytes)
+ {
+ bytes = word_size - bytes;
+ memmove(skb->data - bytes, skb->data, skb->len);
+ skb->data -= bytes;
+ skb->tail -= bytes;
+ return TRUE;
+ }
+ else
+ {
+ /* Allocate a new packet which has enough head/tail room to allow
+ alignment. */
+ new_skb = ssh_interceptor_packet_skb_dup(packet->interceptor, skb, 0, 0);
+
+ if (!new_skb)
+ {
+ ssh_interceptor_packet_free(pp);
+ return FALSE;
+ }
+ SSH_ASSERT(SSH_SKB_GET_END(new_skb) - SSH_SKB_GET_TAIL(new_skb)
+ >= word_size);
+
+ /* Free the old packet */
+ packet->skb = new_skb;
+ dev_kfree_skb_any(skb);
+ return ssh_interceptor_packet_align(pp, offset);
+ }
+}
+
+struct sk_buff *
+ssh_interceptor_packet_verify_headroom(struct sk_buff *skbp,
+ size_t media_header_len)
+{
+ SshUInt32 required_headroom;
+ struct sk_buff *skbp2;
+
+ SSH_ASSERT(skbp != NULL);
+ SSH_ASSERT(skbp->dev != NULL);
+
+ required_headroom = LL_RESERVED_SPACE(skbp->dev);
+#if (SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM > 0)
+ if (required_headroom < SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM)
+ required_headroom = SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM;
+#endif /* (SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM > 0) */
+
+ if (unlikely(required_headroom > media_header_len &&
+ skb_headroom(skbp) < (required_headroom - media_header_len)))
+ {
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("skb does not have enough headroom for device %d, "
+ "reallocating skb headroom",
+ skbp->dev->ifindex));
+ skbp2 = skb_realloc_headroom(skbp,
+ (required_headroom - media_header_len));
+ dev_kfree_skb_any(skbp);
+
+ return skbp2;
+ }
+
+ return skbp;
+}
diff --git a/drivers/interceptor/linux_packet_internal.h b/drivers/interceptor/linux_packet_internal.h
new file mode 100644
index 0000000..dd5c049
--- /dev/null
+++ b/drivers/interceptor/linux_packet_internal.h
@@ -0,0 +1,84 @@
+/* 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_packet_internal.h
+ *
+ * Linux interceptor specific defines for the packet portion of the
+ * Interceptor API.
+ *
+ */
+
+#ifndef LINUX_PACKET_INTERNAL_H
+#define LINUX_PACKET_INTERNAL_H
+
+#include "kernel_includes.h"
+
+/* Internal packet structure, used to encapsulate the kernel structure
+ for the generic packet processing engine. */
+
+typedef struct SshInterceptorInternalPacketRec
+{
+ /* Generic packet structure */
+ struct SshInterceptorPacketRec packet;
+
+ /* Backpointer to interceptor */
+ SshInterceptor interceptor;
+
+ /* Kernel skb structure. */
+ struct sk_buff *skb;
+
+ /* The processor from which this packet was allocated from the freelist */
+ unsigned int cpu;
+
+ size_t iteration_offset;
+ size_t iteration_bytes;
+
+ /* These are SshUInt32's for export/import */
+ SshUInt32 original_ifnum;
+
+ SshUInt16 borrowed : 1; /* From spare resource, free after use */
+} *SshInterceptorInternalPacket;
+
+
+/* Typical needed tailroom: ESP trailer (worstcase ~27B).
+ Typical needed headroom: media, IPIP, ESP (~60B for IPv4, ~80B for IPv6)
+ Worstcase headroom: media, UDP(8), NAT-T(12), IPIP(~20), ESP(22) */
+
+/* The amount of headroom reserved for network interface processing. The
+ interceptor ensures that all packets passed to NIC driver will have atleast
+ this much headroom. */
+#ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+/* With media level interceptor the SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM
+ includes the media header length. Let us use up the full skb if necessary.
+ This is important for reducing overhead in the forwarding case. */
+#define SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM 0
+#else
+/* Ensure that packet has always enough headroom for an aligned
+ ethernet header. */
+#define SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM 16
+#endif /* !SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+/* Amount of head- and tailroom to reserve when allocating or duplicating
+ a packet. These values are optimised for IPsec processing. */
+#define SSH_INTERCEPTOR_PACKET_HEAD_ROOM (80)
+#define SSH_INTERCEPTOR_PACKET_TAIL_ROOM (30)
+
+#define SSH_LINUX_ALLOC_SKB_GFP_MASK (GFP_ATOMIC)
+
+#ifdef LINUX_HAS_SKB_CLONE_WRITABLE
+#define SSH_SKB_WRITABLE(_skb, _len) \
+ (!skb_cloned((_skb)) || skb_clone_writable((_skb), (_len)))
+#else /* LINUX_HAS_SKB_CLONE_WRITABLE */
+#define SSH_SKB_WRITABLE(_skb, _len) \
+ (!skb_cloned((_skb)))
+#endif /* LINUX_HAS_SKB_CLONE_WRITABLE */
+
+#endif /* LINUX_PACKET_INTERNAL_H */
diff --git a/drivers/interceptor/linux_params.h b/drivers/interceptor/linux_params.h
new file mode 100644
index 0000000..29b264b
--- /dev/null
+++ b/drivers/interceptor/linux_params.h
@@ -0,0 +1,84 @@
+/* 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_params.h
+ *
+ * Linux interceptor tunable parameters.
+ *
+ */
+
+#ifndef LINUX_PARAMS_H
+#define LINUX_PARAMS_H
+
+/* Netfilter interoperability flag. This flags specifies the extension
+ selector slot which is used for storing the Linux 'skb->nfmark' firewall
+ mark. Note that in kernel versions before 2.6.20 the linux kernel
+ CONFIG_IP_ROUTE_FWMARK must be enabled if you wish to use `skb->nfmark'
+ in route lookups. This define is not used if
+ SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS is 0. */
+/* #define SSH_LINUX_FWMARK_EXTENSION_SELECTOR 0 */
+
+/* The upper treshold of queued messages from the engine to the policymanager.
+ If this treshold is passed, then "unreliable" messages (messages not
+ necessary for the correct operation of the engine/policymanager), are
+ discarded. Both existing queued messages or new messages can be
+ discarded. */
+#define SSH_LINUX_MAX_IPM_MESSAGES 2000
+
+/* Disable IPV6 support in the interceptor here, if explicitly desired.
+ Undefining SSH_LINUX_INTERCEPTOR_IPV6 results into excluding IPv6
+ specific code in the interceptor. The define does not affect the
+ size of any common data structures.
+ Currently it is disabled by default if IPv6 is not available in the
+ kernel. */
+#if defined (WITH_IPV6)
+#define SSH_LINUX_INTERCEPTOR_IPV6 1
+#ifndef CONFIG_IPV6
+#ifndef CONFIG_IPV6_MODULE
+#undef SSH_LINUX_INTERCEPTOR_IPV6
+#endif /* !CONFIG_IPV6_MODULE */
+#endif /* !CONFIG_IPV6 */
+#endif /* WITH_IPV6 */
+
+/* Netfilter interoperability flag. If this flag is set, then packets
+ intercepted at the PRE_ROUTING hook are passed to other netfilter modules
+ before the packet is given to the engine for processing. */
+/* #define SSH_LINUX_NF_PRE_ROUTING_BEFORE_ENGINE 1 */
+
+/* Netfilter interoperability flag. If this flag is set, then packets
+ sent to host stack are passed to other netfilter modules in the PRE_ROUTING
+ hook after the packet has been processed in the engine. */
+/* #define SSH_LINUX_NF_PRE_ROUTING_AFTER_ENGINE 1 */
+
+/* Netfilter interoperability flag. If this flag is set, then packets
+ intercepted at the POST_ROUTING hook are passed to other netfilter
+ modules before the packet is given to the engine for processing. */
+/* #define SSH_LINUX_NF_POST_ROUTING_BEFORE_ENGINE 1 */
+
+/* Netfilter interoperability flag. If this flag is set, then packets
+ sent to network are passed to other netfilter modules in the POST_ROUTING
+ hook after the packet has been processed in the engine. This flag is usable
+ only if SSH_IPSEC_IP_ONLY_INTERCEPTOR is defined. */
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+/* #define SSH_LINUX_NF_POST_ROUTING_AFTER_ENGINE 1 */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+/* Netfilter interoperability flag. If this flags is set, then forwarded
+ packets are passed to netfilter modules in the FORWARD hook after
+ the packet has been processed in the engine. This flag is usable
+ only if SSH_IPSEC_IP_ONLY_INTERCEPTOR defined, and if the engine performs
+ packet forwarding (that is, SSH_ENGINE_FLAGS does not include
+ SSH_ENGINE_NO_FORWARDING). */
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+/* #define SSH_LINUX_NF_FORWARD_AFTER_ENGINE 1 */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+#endif /* LINUX_PARAMS_H */
diff --git a/drivers/interceptor/linux_procfs.c b/drivers/interceptor/linux_procfs.c
new file mode 100644
index 0000000..6dff18c
--- /dev/null
+++ b/drivers/interceptor/linux_procfs.c
@@ -0,0 +1,800 @@
+/* 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_procfs.c
+ *
+ * Linux interceptor proc filesystem interface for kernel to user space
+ * messaging.
+ *
+ */
+
+#include "linux_internal.h"
+
+extern SshInterceptor ssh_interceptor_context;
+
+/************************ Module parameters *********************************/
+
+unsigned int ssh_procfs_uid = 0;
+MODULE_PARM_DESC(ssh_procfs_uid, "Procfs uid");
+module_param(ssh_procfs_uid, uint, 0444);
+
+unsigned int ssh_procfs_gid = 0;
+MODULE_PARM_DESC(ssh_procfs_gid, "Procfs gid");
+module_param(ssh_procfs_gid, uint, 0444);
+
+/************************ Internal utility functions ************************/
+
+/* Use printk instead of SSH_DEBUG macros. */
+#ifdef DEBUG_LIGHT
+#define SSH_LINUX_PROCFS_DEBUG(x...) if (net_ratelimit()) printk(KERN_INFO x)
+#define SSH_LINUX_PROCFS_WARN(x...) printk(KERN_EMERG x)
+#endif /* DEBUG_LIGHT */
+
+#ifndef SSH_LINUX_PROCFS_DEBUG
+#define SSH_LINUX_PROCFS_DEBUG(x...)
+#define SSH_LINUX_PROCFS_WARN(x...)
+#endif /* SSH_LINUX_PROCFS_DEBUG */
+
+/* Maximum number of bytes that can be written to the ipm proc entry
+ in one call. This limits the time spent softirqs disabled by forcing
+ the application to perform another write operation. */
+#define SSH_LINUX_PROCFS_IPM_WRITE_MAX_LENGTH \
+ (4 * SSH_LINUX_IPM_RECV_BUFFER_SIZE)
+
+static int
+interceptor_proc_entry_fop_open(SshInterceptorProcEntry entry,
+ struct file *file)
+{
+ /* Allow only one userspace application at a time. */
+ write_lock(&entry->lock);
+ if (entry->open == TRUE)
+ {
+ write_unlock(&entry->lock);
+ return -EBUSY;
+ }
+
+ /* Check that engine initialization has been completed. */
+ if (ssh_interceptor_context->engine_open == FALSE)
+ {
+ write_unlock(&entry->lock);
+ return -EBUSY;
+ }
+
+ /* Increment module ref to prohibit module unloading. */
+ if (!ssh_linux_module_inc_use_count())
+ {
+ write_unlock(&entry->lock);
+ return -EBUSY;
+ }
+
+ file->private_data = ssh_interceptor_context;
+ entry->buf_len = 0;
+ entry->open = TRUE;
+
+ write_unlock(&entry->lock);
+
+ return 0;
+}
+
+static int
+interceptor_proc_entry_fop_release(SshInterceptorProcEntry entry)
+{
+ write_lock(&entry->lock);
+
+ /* Release the module reference. */
+ ssh_linux_module_dec_use_count();
+
+ /* Mark proc entry closed */
+ entry->open = FALSE;
+
+ write_unlock(&entry->lock);
+
+ return 0;
+}
+
+static int interceptor_proc_entry_iop_permission(struct inode *inode,
+ int op
+#ifdef LINUX_INODE_OPERATION_PERMISSION_HAS_NAMEIDATA
+ , struct nameidata *nd
+#endif /* LINUX_INODE_OPERATION_PERMISSION_HAS_NAMEIDATA */
+#ifdef LINUX_INODE_OPERATION_PERMISSION_HAS_UINT
+ , unsigned int ed
+#endif /* LINUX_INODE_OPERATION_PERMISSION_HAS_UINT */
+ )
+{
+ if (op & MAY_EXEC)
+ return -EACCES;
+
+ if ((op & (MAY_READ | MAY_WRITE)) &&
+#ifdef LINUX_HAS_TASK_CRED_STRUCT
+ current_euid() == (uid_t) ssh_procfs_uid
+#else /* LINUX_HAS_TASK_CRED_STRUCT */
+ current->euid == (uid_t) ssh_procfs_uid
+#endif /* LINUX_HAS_TASK_CRED_STRUCT */
+ )
+ {
+ return 0;
+ }
+
+ return -EACCES;
+}
+
+static struct inode_operations common_proc_entry_iops =
+{
+ .permission = interceptor_proc_entry_iop_permission
+};
+
+
+/************************ Ipm proc entry ************************************/
+
+static ssize_t
+interceptor_ipm_proc_entry_fop_read(struct file *file,
+ char __user *buf,
+ size_t len,
+ loff_t *pos)
+{
+ SshInterceptor interceptor = file->private_data;
+ SshInterceptorIpmMsg msg = NULL;
+ ssize_t msg_len;
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+
+ /* Allow only one read at a time. */
+ if (interceptor->ipm_proc_entry.read_active)
+ {
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_read: -EBUSY\n");
+ return -EBUSY;
+ }
+
+ interceptor->ipm_proc_entry.read_active = TRUE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ /* Continue from the partial message. */
+ if (interceptor->ipm_proc_entry.send_msg != NULL)
+ msg = interceptor->ipm_proc_entry.send_msg;
+
+
+ retry:
+ if (msg == NULL)
+ {
+ /* Get the next message from send queue. */
+ local_bh_disable();
+ write_lock(&interceptor->ipm.lock);
+
+ /* Take next message from send queue. */
+ if (interceptor->ipm.send_queue != NULL)
+ {
+ msg = interceptor->ipm.send_queue;
+
+ interceptor->ipm.send_queue = msg->next;
+ if (msg->next)
+ msg->next->prev = NULL;
+ msg->next = NULL;
+
+ if (msg == interceptor->ipm.send_queue_tail)
+ interceptor->ipm.send_queue_tail = msg->next;
+
+ if (msg->reliable == 0)
+ {
+ SSH_ASSERT(interceptor->ipm.send_queue_num_unreliable > 0);
+ interceptor->ipm.send_queue_num_unreliable--;
+ }
+ }
+
+ write_unlock(&interceptor->ipm.lock);
+ local_bh_enable();
+ }
+
+ if (msg == NULL)
+ {
+ /* Non-blocking mode, fail read. */
+ if (file->f_flags & O_NONBLOCK)
+ {
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ return -EAGAIN;
+ }
+
+ /* Blocking mode, sleep until a message or a signal arrives. */
+ interruptible_sleep_on(&interceptor->ipm_proc_entry.wait_queue);
+
+ if (signal_pending(current))
+ {
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_read: "
+ "-ERESTARTSYS\n");
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ return -ERESTARTSYS;
+ }
+
+ goto retry;
+ }
+
+ interceptor->ipm_proc_entry.send_msg = msg;
+
+ /* Copy message to userspace. */
+ msg_len = msg->len - msg->offset;
+ if (len < msg_len)
+ msg_len = len;
+
+ if (copy_to_user(buf, msg->buf + msg->offset, msg_len))
+ {
+ SSH_LINUX_PROCFS_WARN("interceptor_ipm_proc_entry_fop_read: "
+ "copy_to_user failed, dropping message\n");
+
+ interceptor->ipm_proc_entry.send_msg = NULL;
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ interceptor_ipm_message_free(interceptor, msg);
+ return -EFAULT;
+ }
+
+ msg->offset += msg_len;
+
+ /* Whole message was sent. */
+ if (msg->offset >= msg->len)
+ {
+ interceptor->ipm_proc_entry.send_msg = NULL;
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ interceptor_ipm_message_free(interceptor, msg);
+ }
+ else
+ {
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+ }
+
+ return msg_len;
+}
+
+static ssize_t
+interceptor_ipm_proc_entry_fop_write(struct file *file,
+ const char __user *buf,
+ size_t len,
+ loff_t *pos)
+{
+ SshInterceptor interceptor = file->private_data;
+ size_t total_len, write_len, recv_len, consumed, msg_len;
+ char *user_buf, *recv_buf;
+
+ /* Limit the maximum write length to avoid running in softirq
+ context for long periods of time. */
+ if (len > SSH_LINUX_PROCFS_IPM_WRITE_MAX_LENGTH)
+ write_len = SSH_LINUX_PROCFS_IPM_WRITE_MAX_LENGTH;
+ else
+ write_len = len;
+
+ /* Refuse to receive any data if send queue is getting full.
+ Note this here checks if the IPM message freelist is empty,
+ which indicates that all IPM messages are in the send queue.
+
+ Allowing a new write in such condition could cause a number
+ of reply IPM messages to be queued for sending and this would
+ cause either unreliable IPM messages to be discarded from the
+ send queue or an emergency mallocation of a reliable IPM message.
+
+ A better way to solve this problem is to refuse this write
+ operation and force the application to read messages from the send
+ queue before allowing another write. */
+ local_bh_disable();
+ read_lock(&interceptor->ipm.lock);
+ if (interceptor->ipm.msg_freelist == NULL
+ && interceptor->ipm.msg_allocated >= SSH_LINUX_MAX_IPM_MESSAGES)
+ write_len = 0;
+ read_unlock(&interceptor->ipm.lock);
+ local_bh_enable();
+
+ if (write_len == 0)
+ return -EAGAIN;
+
+ /* Check if there is another write going on. */
+ retry:
+ write_lock(&interceptor->ipm_proc_entry.lock);
+
+ if (interceptor->ipm_proc_entry.write_active)
+ {
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ /* Non-blocking mode, fail write. */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ /* Blocking mode, wait until other writes are done. */
+ interruptible_sleep_on(&interceptor->ipm_proc_entry.wait_queue);
+
+ if (signal_pending(current))
+ {
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_write: "
+ "-ERESTARTSYS\n");
+ return -ERESTARTSYS;
+ }
+
+ goto retry;
+ }
+
+ interceptor->ipm_proc_entry.write_active = TRUE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ /* Receive data. */
+ total_len = 0;
+ user_buf = (char *) buf;
+ while (user_buf < (buf + write_len))
+ {
+ /* Copy data from user to receive buffer up to the maximum
+ allowed write size. */
+ user_buf = (char *) (buf + total_len);
+
+ recv_buf = (interceptor->ipm_proc_entry.recv_buf
+ + interceptor->ipm_proc_entry.recv_len);
+ recv_len = (interceptor->ipm_proc_entry.recv_buf_size
+ - interceptor->ipm_proc_entry.recv_len);
+
+ /* Break out of the loop if receive buffer is full. */
+ if (recv_len == 0)
+ break;
+
+ if (recv_len > (write_len - total_len))
+ recv_len = (write_len - total_len);
+
+ if (copy_from_user(recv_buf, user_buf, recv_len))
+ {
+ SSH_LINUX_PROCFS_WARN("interceptor_ipm_proc_entry_fop_write: "
+ "copy_from_user failed, dropping message\n");
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.write_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ wake_up_interruptible(&interceptor->ipm_proc_entry.wait_queue);
+ return -EFAULT;
+ }
+
+ total_len += recv_len;
+ interceptor->ipm_proc_entry.recv_len += recv_len;
+
+ /* Parse ipm messages. */
+ consumed = 0;
+ while (consumed < interceptor->ipm_proc_entry.recv_len)
+ {
+ msg_len =
+ ssh_interceptor_receive_from_ipm(interceptor->ipm_proc_entry.
+ recv_buf + consumed,
+ interceptor->ipm_proc_entry.
+ recv_len - consumed);
+
+ /* Need more data. */
+ if (msg_len == 0)
+ break;
+
+ /* Else continue parsing ipm messages. */
+ consumed += msg_len;
+ }
+
+ /* Move unparsed data to beginning of receive buffer. */
+ if (consumed > 0)
+ {
+ SSH_ASSERT(consumed <= interceptor->ipm_proc_entry.recv_len);
+
+ if (consumed < interceptor->ipm_proc_entry.recv_len)
+ memmove(interceptor->ipm_proc_entry.recv_buf,
+ interceptor->ipm_proc_entry.recv_buf + consumed,
+ interceptor->ipm_proc_entry.recv_len - consumed);
+
+ interceptor->ipm_proc_entry.recv_len -= consumed;
+ }
+
+ /* Continue receiving data from user. */
+ }
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.write_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ if (total_len == 0)
+ {
+ SSH_LINUX_PROCFS_WARN("interceptor_ipm_proc_entry_fop_write: "
+ "Out of receive buffer space\n");
+ return -ENOMEM;
+ }
+
+ SSH_ASSERT(total_len <= write_len);
+ return total_len;
+}
+
+static unsigned int
+interceptor_ipm_proc_entry_fop_poll(struct file *file,
+ struct poll_table_struct *table)
+{
+ unsigned int mask = 0;
+ SshInterceptor interceptor = file->private_data;
+
+ poll_wait(file, &interceptor->ipm_proc_entry.wait_queue, table);
+
+ /* Check if there are messages in the send queue. */
+ if (interceptor->ipm_proc_entry.send_msg != NULL)
+ mask |= (POLLIN | POLLRDNORM);
+
+ local_bh_disable();
+ read_lock(&interceptor->ipm.lock);
+
+ /* Check if there is a message pending for sending. */
+ if (interceptor->ipm.send_queue != NULL)
+ mask |= (POLLIN | POLLRDNORM);
+
+ /* /proc entry is always writable, unless send queue is too long. */
+ if (interceptor->ipm.msg_freelist != NULL
+ || interceptor->ipm.msg_allocated < SSH_LINUX_MAX_IPM_MESSAGES)
+ mask |= (POLLOUT | POLLWRNORM);
+
+ read_unlock(&interceptor->ipm.lock);
+ local_bh_enable();
+
+ return mask;
+}
+
+static int
+interceptor_ipm_proc_entry_fop_open(struct inode *inode,
+ struct file *file)
+{
+ write_lock(&ssh_interceptor_context->ipm_proc_entry.lock);
+
+ /* Allow only one read form userspace at a time. */
+ if (ssh_interceptor_context->ipm_proc_entry.open)
+ {
+ write_unlock(&ssh_interceptor_context->ipm_proc_entry.lock);
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_open: -EBUSY\n");
+ return -EBUSY;
+ }
+
+ /* Check that engine initialization has been completed. */
+ if (ssh_interceptor_context->engine_open == FALSE)
+ {
+ write_unlock(&ssh_interceptor_context->ipm_proc_entry.lock);
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_open: -EBUSY\n");
+ return -EBUSY;
+ }
+
+ /* Increment module ref to prohibit module unloading. */
+ if (!ssh_linux_module_inc_use_count())
+ {
+ write_unlock(&ssh_interceptor_context->ipm_proc_entry.lock);
+ SSH_LINUX_PROCFS_DEBUG("interceptor_ipm_proc_entry_fop_open: -EBUSY\n");
+ return -EBUSY;
+ }
+
+ file->private_data = ssh_interceptor_context;
+
+ ssh_interceptor_context->ipm_proc_entry.open = TRUE;
+
+ /* Clear receive buffer. */
+ ssh_interceptor_context->ipm_proc_entry.recv_len = 0;
+
+ write_unlock(&ssh_interceptor_context->ipm_proc_entry.lock);
+
+ interceptor_ipm_open(ssh_interceptor_context);
+ ssh_interceptor_notify_ipm_open(ssh_interceptor_context);
+
+ return 0;
+}
+
+static int
+interceptor_ipm_proc_entry_fop_release(struct inode *inode,
+ struct file *file)
+{
+ SshInterceptor interceptor = file->private_data;
+ SshInterceptorIpmMsg msg;
+
+ ssh_interceptor_notify_ipm_close(interceptor);
+ interceptor_ipm_close(interceptor);
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+
+ interceptor->ipm_proc_entry.open = FALSE;
+
+ /* Release the module reference. */
+ ssh_linux_module_dec_use_count();
+
+ /* Clear receive buffer. */
+ interceptor->ipm_proc_entry.recv_len = 0;
+
+ /* Free partial output message */
+ msg = interceptor->ipm_proc_entry.send_msg;
+ interceptor->ipm_proc_entry.send_msg = NULL;
+
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ if (msg)
+ interceptor_ipm_message_free(interceptor, msg);
+
+ return 0;
+}
+
+static struct file_operations ipm_proc_entry_fops =
+{
+ .read = interceptor_ipm_proc_entry_fop_read,
+ .write = interceptor_ipm_proc_entry_fop_write,
+ .poll = interceptor_ipm_proc_entry_fop_poll,
+ .open = interceptor_ipm_proc_entry_fop_open,
+ .release = interceptor_ipm_proc_entry_fop_release
+};
+
+static Boolean
+interceptor_ipm_proc_entry_init(SshInterceptor interceptor)
+{
+ /* Assert that parent dir exists. */
+ SSH_ASSERT(interceptor->proc_dir != NULL);
+
+ /* Initialize proc entry structure. */
+ init_waitqueue_head(&interceptor->ipm_proc_entry.wait_queue);
+ rwlock_init(&interceptor->ipm_proc_entry.lock);
+
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.open = TRUE;
+ interceptor->ipm_proc_entry.write_active = TRUE;
+ interceptor->ipm_proc_entry.read_active = TRUE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ interceptor->ipm_proc_entry.recv_buf_size = SSH_LINUX_IPM_RECV_BUFFER_SIZE;
+ interceptor->ipm_proc_entry.recv_buf =
+ ssh_malloc(interceptor->ipm_proc_entry.recv_buf_size);
+ if (interceptor->ipm_proc_entry.recv_buf == NULL)
+ return FALSE;
+
+ /* Create entry to procfs. */
+ interceptor->ipm_proc_entry.entry =
+ create_proc_entry(SSH_PROC_ENGINE, S_IFREG | S_IRUSR | S_IWUSR,
+ interceptor->proc_dir);
+
+ if (interceptor->ipm_proc_entry.entry == NULL)
+ return FALSE;
+
+#ifdef LINUX_HAS_PROC_DIR_ENTRY_OWNER
+ interceptor->ipm_proc_entry.entry->owner = THIS_MODULE;
+#endif
+
+ interceptor->ipm_proc_entry.entry->proc_iops = &common_proc_entry_iops;
+ interceptor->ipm_proc_entry.entry->proc_fops = &ipm_proc_entry_fops;
+ interceptor->ipm_proc_entry.entry->uid = (uid_t) ssh_procfs_uid;
+ interceptor->ipm_proc_entry.entry->gid = (uid_t) ssh_procfs_gid;
+ interceptor->ipm_proc_entry.entry->size = 0;
+
+ /* Mark proc entry ready for use. */
+ write_lock(&interceptor->ipm_proc_entry.lock);
+ interceptor->ipm_proc_entry.open = FALSE;
+ interceptor->ipm_proc_entry.write_active = FALSE;
+ interceptor->ipm_proc_entry.read_active = FALSE;
+ write_unlock(&interceptor->ipm_proc_entry.lock);
+
+ return TRUE;
+}
+
+static void
+interceptor_ipm_proc_entry_uninit(SshInterceptor interceptor)
+{
+ if (interceptor->ipm_proc_entry.recv_buf != NULL)
+ ssh_free(interceptor->ipm_proc_entry.recv_buf);
+ interceptor->ipm_proc_entry.recv_buf = NULL;
+
+ /* This should be safe to do without locking as interceptor code
+ does not refer `interceptor->ipm_proc_entry.entry' except in
+ init/uninit. */
+ if (interceptor->ipm_proc_entry.entry != NULL)
+ remove_proc_entry(interceptor->ipm_proc_entry.entry->name,
+ interceptor->proc_dir);
+ interceptor->ipm_proc_entry.entry = NULL;
+}
+
+/************************ Version proc_entry ********************************/
+
+static ssize_t
+interceptor_version_proc_entry_fop_read(struct file *file,
+ char __user *buf,
+ size_t len,
+ loff_t *pos)
+{
+ SshInterceptor interceptor = file->private_data;
+ ssize_t version_len;
+
+ /* Check if another read / write is ongoing */
+ write_lock(&interceptor->version_proc_entry.lock);
+ if (interceptor->version_proc_entry.active == TRUE)
+ {
+ write_unlock(&interceptor->version_proc_entry.lock);
+ return -EBUSY;
+ }
+ interceptor->version_proc_entry.active = TRUE;
+ write_unlock(&interceptor->version_proc_entry.lock);
+
+ if (*pos == 0)
+ {
+ version_len =
+ snprintf(interceptor->version_proc_entry.buf,
+ sizeof(interceptor->version_proc_entry.buf),
+ "VPNClient built on " __DATE__ " " __TIME__ "\n");
+ interceptor->version_proc_entry.buf_len = version_len;
+ }
+
+ else if (*pos >= interceptor->version_proc_entry.buf_len)
+ {
+ interceptor->version_proc_entry.buf_len = 0;
+ version_len = 0;
+ goto out;
+ }
+
+ else
+ {
+ version_len = interceptor->version_proc_entry.buf_len - *pos;
+ }
+
+ if (len < version_len)
+ version_len = len;
+
+ if (copy_to_user(buf, interceptor->version_proc_entry.buf + *pos,
+ version_len))
+ {
+ interceptor->version_proc_entry.buf_len = 0;
+ version_len = -EFAULT;
+ goto out;
+ }
+
+ *pos += version_len;
+
+ out:
+ write_lock(&interceptor->version_proc_entry.lock);
+ interceptor->version_proc_entry.active = FALSE;
+ write_unlock(&interceptor->version_proc_entry.lock);
+
+ return version_len;
+}
+
+static int
+interceptor_version_proc_entry_fop_open(struct inode *inode,
+ struct file *file)
+{
+ return interceptor_proc_entry_fop_open(&ssh_interceptor_context->
+ version_proc_entry,
+ file);
+}
+
+static int
+interceptor_version_proc_entry_fop_release(struct inode *inode,
+ struct file *file)
+{
+ SshInterceptor interceptor = file->private_data;
+ return interceptor_proc_entry_fop_release(&interceptor->version_proc_entry);
+}
+
+static struct file_operations version_proc_entry_fops =
+{
+ .read = interceptor_version_proc_entry_fop_read,
+ .open = interceptor_version_proc_entry_fop_open,
+ .release = interceptor_version_proc_entry_fop_release
+};
+
+static Boolean
+interceptor_version_proc_entry_init(SshInterceptor interceptor)
+{
+ /* Assert that parent dir exists. */
+ SSH_ASSERT(interceptor->proc_dir != NULL);
+
+ /* Initialize proc entry structure. */
+ rwlock_init(&interceptor->version_proc_entry.lock);
+ interceptor->version_proc_entry.buf_len = 0;
+
+ /* Take lock to use same variables always under lock to not confuse
+ static analysis. */
+ write_lock(&interceptor->version_proc_entry.lock);
+ interceptor->version_proc_entry.active = TRUE;
+ interceptor->version_proc_entry.open = TRUE;
+ write_unlock(&interceptor->version_proc_entry.lock);
+
+ /* Create entry to procfs. */
+ interceptor->version_proc_entry.entry =
+ create_proc_entry(SSH_PROC_VERSION, S_IFREG | S_IRUSR,
+ interceptor->proc_dir);
+
+ if (interceptor->version_proc_entry.entry == NULL)
+ return FALSE;
+
+#ifdef LINUX_HAS_PROC_DIR_ENTRY_OWNER
+ interceptor->version_proc_entry.entry->owner = THIS_MODULE;
+#endif
+
+ interceptor->version_proc_entry.entry->proc_iops = &common_proc_entry_iops;
+ interceptor->version_proc_entry.entry->proc_fops = &version_proc_entry_fops;
+ interceptor->version_proc_entry.entry->uid = (uid_t) ssh_procfs_uid;
+ interceptor->version_proc_entry.entry->gid = (uid_t) ssh_procfs_gid;
+ interceptor->version_proc_entry.entry->size = 0;
+
+ /* Mark proc entry ready for use. */
+ write_lock(&interceptor->version_proc_entry.lock);
+ interceptor->version_proc_entry.active = FALSE;
+ interceptor->version_proc_entry.open = FALSE;
+ write_unlock(&interceptor->version_proc_entry.lock);
+
+ return TRUE;
+}
+
+static void
+interceptor_version_proc_entry_uninit(SshInterceptor interceptor)
+{
+ if (interceptor->version_proc_entry.entry == NULL)
+ return;
+
+ /* This should be safe to do without locking as interceptor code
+ does not refer `interceptor->version_proc_entry.entry' except in
+ init/uninit. */
+ remove_proc_entry(interceptor->version_proc_entry.entry->name,
+ interceptor->proc_dir);
+ interceptor->version_proc_entry.entry = NULL;
+}
+
+
+/************************ Proc init / uninit ********************************/
+
+Boolean ssh_interceptor_proc_init(SshInterceptor interceptor)
+{
+ char name[128];
+
+ /* Create a directory under /proc/ */
+ snprintf(name, sizeof(name), "%s%s", SSH_PROC_ROOT, ssh_device_suffix);
+
+ interceptor->proc_dir = create_proc_entry(name, S_IFDIR, NULL);
+
+ if (interceptor->proc_dir == NULL)
+ goto error;
+
+ if (interceptor_ipm_proc_entry_init(interceptor) == FALSE)
+ goto error;
+
+ if (interceptor_version_proc_entry_init(interceptor) == FALSE)
+ goto error;
+
+ return TRUE;
+
+ error:
+ SSH_DEBUG(SSH_D_ERROR, ("Could not create /proc/%s", name));
+
+ interceptor_ipm_proc_entry_uninit(interceptor);
+ interceptor_version_proc_entry_uninit(interceptor);
+
+ if (interceptor->proc_dir)
+ remove_proc_entry(interceptor->proc_dir->name, NULL);
+ interceptor->proc_dir = NULL;
+
+ return FALSE;
+}
+
+void ssh_interceptor_proc_uninit(SshInterceptor interceptor)
+{
+ local_bh_enable();
+
+ interceptor_ipm_proc_entry_uninit(interceptor);
+ interceptor_version_proc_entry_uninit(interceptor);
+
+ if (interceptor->proc_dir)
+ remove_proc_entry(interceptor->proc_dir->name, NULL);
+ interceptor->proc_dir = NULL;
+
+ local_bh_disable();
+}
diff --git a/drivers/interceptor/linux_route.c b/drivers/interceptor/linux_route.c
new file mode 100644
index 0000000..ab93c6a
--- /dev/null
+++ b/drivers/interceptor/linux_route.c
@@ -0,0 +1,1136 @@
+/* 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_route.c
+ *
+ * Linux interceptor implementation of interceptor API routing functions.
+ *
+ */
+
+#include "linux_internal.h"
+
+/****************** Internal data types and declarations ********************/
+
+typedef struct SshInterceptorRouteResultRec
+{
+ SshIpAddrStruct gw[1];
+ SshInterceptorIfnum ifnum;
+ SshUInt16 mtu;
+} SshInterceptorRouteResultStruct, *SshInterceptorRouteResult;
+
+/****************** Internal Utility Functions ******************************/
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#if (defined LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING) \
+ || (defined LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING)
+
+/* Create a child dst_entry with locked interface MTU, and attach it to `dst'.
+ This is needed on newer linux kernels and IP_ONLY_INTERCEPTOR builds,
+ where the IP stack fragments packets to path MTU after ssh_interceptor_send.
+*/
+static struct dst_entry *
+interceptor_route_create_child_dst(struct dst_entry *dst, Boolean ipv6)
+{
+ struct dst_entry *child;
+#ifdef LINUX_HAS_DST_COPY_METRICS
+ SshUInt32 set;
+ struct rt6_info *rt6;
+ struct rtable *rt;
+#endif /* LINUX_HAS_DST_COPY_METRICS */
+
+ /* Allocate a dst_entry and copy relevant fields from dst. */
+ child = SSH_DST_ALLOC(dst);
+ if (child == NULL)
+ return NULL;
+
+ child->input = dst->input;
+ child->output = dst->output;
+
+ /* Child is not added to dst hash, and linux native IPsec is disabled. */
+ child->flags |= (DST_NOHASH | DST_NOPOLICY | DST_NOXFRM);
+
+ /* Copy route metrics and lock MTU to interface MTU. */
+#ifdef LINUX_HAS_DST_COPY_METRICS
+ if (ipv6 == TRUE)
+ {
+ rt6 = (struct rt6_info *)child;
+ memset(&rt6->rt6i_table, 0, sizeof(*rt6) - sizeof(struct dst_entry));
+ }
+ else
+ {
+ rt = (struct rtable *)child;
+ memset(&SSH_RTABLE_FIRST_MEMBER(rt), 0,
+ sizeof(*rt) - sizeof(struct dst_entry));
+ }
+
+ dst_copy_metrics(child, dst);
+ set = dst_metric(child, RTAX_LOCK);
+ set |= 1 << RTAX_MTU;
+ dst_metric_set(child, RTAX_LOCK, set);
+#else /* LINUX_HAS_DST_COPY_METRICS */
+ memcpy(child->metrics, dst->metrics, sizeof(child->metrics));
+ child->metrics[RTAX_LOCK-1] |= 1 << RTAX_MTU;
+#endif /* LINUX_HAS_DST_COPY_METRICS */
+
+#ifdef CONFIG_NET_CLS_ROUTE
+ child->tclassid = dst->tclassid;
+#endif /* CONFIG_NET_CLS_ROUTE */
+
+#ifdef CONFIG_XFRM
+ child->xfrm = NULL;
+#endif /* CONFIG_XFRM */
+
+#ifdef LINUX_HAS_HH_CACHE
+ if (dst->hh)
+ {
+ atomic_inc(&dst->hh->hh_refcnt);
+ child->hh = dst->hh;
+ }
+#endif /* LINUX_HAS_HH_CACHE */
+
+#ifdef LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS
+ if (dst_get_neighbour(dst) != NULL)
+ dst_set_neighbour(child, neigh_clone(dst_get_neighbour(dst)));
+#else /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */
+ if (dst->neighbour != NULL)
+ child->neighbour = neigh_clone(dst->neighbour);
+#endif /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */
+
+ if (dst->dev)
+ {
+ dev_hold(dst->dev);
+ child->dev = dst->dev;
+ }
+
+ SSH_ASSERT(dst->child == NULL);
+ dst->child = dst_clone(child);
+
+ SSH_DEBUG(SSH_D_MIDOK, ("Allocated child %p dst_entry for dst %p mtu %d",
+ child, dst, dst_mtu(dst)));
+
+ return child;
+}
+
+#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING
+ || LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+static inline int interceptor_ip4_route_output(struct rtable **rt,
+ struct flowi *fli)
+{
+ int rval = 0;
+#ifdef LINUX_USE_NF_FOR_ROUTE_OUTPUT
+ struct dst_entry *dst = NULL;
+ const struct nf_afinfo *afinfo = NULL;
+ unsigned short family = AF_INET;
+
+ rcu_read_lock();
+ afinfo = nf_get_afinfo(family);
+ if (!afinfo)
+ {
+ rcu_read_unlock();
+ SSH_DEBUG(SSH_D_FAIL, ("Failed to get nf afinfo"));
+ return -1;
+ }
+
+ rval = afinfo->route(&init_net, &dst, fli, TRUE);
+ rcu_read_unlock();
+
+ *rt = (struct rtable *)dst;
+
+#else /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */
+
+#ifdef LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT
+ rval = ip_route_output_key(&init_net, rt, fli);
+#else /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */
+ rval = ip_route_output_key(rt, fli);
+#endif /* LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */
+#endif /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */
+
+ if (rval > 0)
+ rval = -rval;
+
+ return rval;
+}
+
+static inline struct dst_entry *interceptor_ip6_route_output(struct flowi *fli)
+{
+ struct dst_entry *dst = NULL;
+
+ /* Perform route lookup */
+ /* we do not need a socket, only fake flow */
+#ifdef LINUX_USE_NF_FOR_ROUTE_OUTPUT
+ const struct nf_afinfo *afinfo = NULL;
+ unsigned short family = AF_INET6;
+ int rval = 0;
+
+ rcu_read_lock();
+ afinfo = nf_get_afinfo(family);
+ if (!afinfo)
+ {
+ rcu_read_unlock();
+ SSH_DEBUG(SSH_D_FAIL, ("Failed to get nf afinfo"));
+ return NULL;
+ }
+
+ rval = afinfo->route(&init_net, &dst, fli, FALSE);
+ rcu_read_unlock();
+ if (rval != 0)
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("Failed to get route from IPv6 NF"));
+ return NULL;
+ }
+#else /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */
+
+#ifdef LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT
+ dst = ip6_route_output(&init_net, NULL, fli);
+#else /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */
+ dst = ip6_route_output(NULL, fli);
+#endif /* LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT */
+#endif /* LINUX_USE_NF_FOR_ROUTE_OUTPUT */
+
+ return dst;
+}
+
+/****************** Implementation of ssh_interceptor_route *****************/
+
+/* Perform route lookup using linux ip_route_output_key.
+
+ The route lookup will use the following selectors:
+ dst, src, outbound ifnum, ip protocol, tos, and fwmark.
+ The source address is expected to be local or undefined.
+
+ The following selectors are ignored:
+ dst port, src port, icmp type, icmp code, ipsec spi. */
+Boolean
+ssh_interceptor_route_output_ipv4(SshInterceptor interceptor,
+ SshInterceptorRouteKey key,
+ SshUInt16 selector,
+ SshInterceptorRouteResult result)
+{
+ u32 daddr;
+ struct rtable *rt;
+ int rval;
+ struct flowi rt_key;
+ u16 rt_type;
+#ifdef DEBUG_LIGHT
+ unsigned char *rt_type_str;
+ u32 fwmark = 0;
+ unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE];
+ unsigned char src_buf[SSH_IP_ADDR_STRING_SIZE];
+#endif /* DEBUG_LIGHT */
+
+ SSH_IP4_ENCODE(&key->dst, (unsigned char *) &daddr);
+
+ /* Initialize rt_key with zero values */
+ memset(&rt_key, 0, sizeof(rt_key));
+
+ rt_key.fl4_dst = daddr;
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ SSH_IP4_ENCODE(&key->src, (unsigned char *) &rt_key.fl4_src);
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)
+ {
+ SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum);
+ rt_key.oif = key->ifnum;
+ }
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO)
+ rt_key.proto = key->ipproto;
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS)
+ rt_key.fl4_tos = key->nh.ip4.tos;
+ rt_key.fl4_scope = RT_SCOPE_UNIVERSE;
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+
+ /* Use linux fw_mark in routing */
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION)
+ {
+#ifdef LINUX_HAS_SKB_MARK
+ rt_key.mark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR];
+#else /* LINUX_HAS_SKB_MARK */
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rt_key.fl4_fwmark =
+ key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR];
+#endif /* CONFIG_IP_ROUTE_FWMARK */
+#endif /* LINUX_HAS_SKB_MARK */
+#ifdef DEBUG_LIGHT
+ fwmark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR];
+#endif /* DEBUG_LIGHT */
+ }
+
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route lookup: "
+ "dst %s src %s ifnum %d ipproto %d tos 0x%02x fwmark 0x%lx",
+ ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)),
+ ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ?
+ ssh_ipaddr_print(&key->src, src_buf, sizeof(src_buf)) : NULL),
+ (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)?
+ key->ifnum : -1),
+ (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ?
+ key->ipproto : -1),
+ ((selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) ?
+ key->nh.ip4.tos : 0),
+ (unsigned long) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION)?
+ fwmark : 0)));
+
+ /* Perform route lookup */
+
+ rval = interceptor_ip4_route_output(&rt, &rt_key);
+ if (rval < 0)
+ {
+ goto fail;
+ }
+
+ /* Get the gateway, mtu and ifnum */
+
+ SSH_IP4_DECODE(result->gw, &rt->rt_gateway);
+ result->mtu = SSH_DST_MTU(&SSH_RT_DST(rt));
+ result->ifnum = SSH_RT_DST(rt).dev->ifindex;
+ rt_type = rt->rt_type;
+
+#ifdef DEBUG_LIGHT
+ switch (rt_type)
+ {
+ case RTN_UNSPEC:
+ rt_type_str = "unspec";
+ break;
+ case RTN_UNICAST:
+ rt_type_str = "unicast";
+ break;
+ case RTN_LOCAL:
+ rt_type_str = "local";
+ break;
+ case RTN_BROADCAST:
+ rt_type_str = "broadcast";
+ break;
+ case RTN_ANYCAST:
+ rt_type_str = "anycast";
+ break;
+ case RTN_MULTICAST:
+ rt_type_str = "multicast";
+ break;
+ case RTN_BLACKHOLE:
+ rt_type_str = "blackhole";
+ break;
+ case RTN_UNREACHABLE:
+ rt_type_str = "unreachable";
+ break;
+ case RTN_PROHIBIT:
+ rt_type_str = "prohibit";
+ break;
+ case RTN_THROW:
+ rt_type_str = "throw";
+ break;
+ case RTN_NAT:
+ rt_type_str = "nat";
+ break;
+ case RTN_XRESOLVE:
+ rt_type_str = "xresolve";
+ break;
+ default:
+ rt_type_str = "unknown";
+ }
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route result: dst %s via %s ifnum %d[%s] mtu %d type %s [%d]",
+ dst_buf, ssh_ipaddr_print(result->gw, src_buf, sizeof(src_buf)),
+ (int) result->ifnum,
+ (SSH_RT_DST(rt).dev->name ? SSH_RT_DST(rt).dev->name : "none"),
+ result->mtu, rt_type_str, rt_type));
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING
+ /* Check if need to create a child dst_entry with interface MTU. */
+ if ((selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED)
+ && SSH_RT_DST(rt).child == NULL)
+ {
+ if (interceptor_route_create_child_dst(&SSH_RT_DST(rt), FALSE) == NULL)
+ SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p",
+ &SSH_RT_DST(rt)));
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Release the routing table entry ; otherwise a memory leak occurs
+ in the route entry table. */
+ ip_rt_put(rt);
+
+ /* Assert that ifnum fits into the SshInterceptorIfnum data type. */
+ SSH_LINUX_ASSERT_IFNUM(result->ifnum);
+
+ /* Check that ifnum does not collide with SSH_INTERCEPTOR_INVALID_IFNUM. */
+ if (result->ifnum == SSH_INTERCEPTOR_INVALID_IFNUM)
+ goto fail;
+
+ /* Accept only unicast, broadcast, anycast, multicast and local routes */
+ if (rt_type == RTN_UNICAST
+ || rt_type == RTN_BROADCAST
+ || rt_type == RTN_ANYCAST
+ || rt_type == RTN_MULTICAST
+ || rt_type == RTN_LOCAL)
+ {
+ SSH_LINUX_ASSERT_VALID_IFNUM(result->ifnum);
+ return TRUE;
+ }
+
+ fail:
+ /* Fail route lookup for other route types */
+ SSH_DEBUG(SSH_D_FAIL, ("Route lookup for %s failed with code %d",
+ dst_buf, rval));
+
+ return FALSE;
+}
+
+
+/* Perform route lookup using linux ip_route_input.
+
+ The route lookup will use the following selectors:
+ dst, src, inbound ifnum, ip protocol, tos, and fwmark.
+ The source address is expected to be non-local and it must be defined.
+
+ The following selectors are ignored:
+ dst port, src port, icmp type, icmp code, ipsec spi. */
+Boolean
+ssh_interceptor_route_input_ipv4(SshInterceptor interceptor,
+ SshInterceptorRouteKey key,
+ SshUInt16 selector,
+ SshInterceptorRouteResult result)
+{
+ u32 daddr, saddr;
+ u8 ipproto;
+ u8 tos;
+ u32 fwmark;
+ struct sk_buff *skbp;
+ struct net_device *dev;
+ struct rtable *rt;
+ int rval = 0;
+ u16 rt_type;
+ struct iphdr *iph = NULL;
+#ifdef DEBUG_LIGHT
+ unsigned char *rt_type_str;
+ unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE];
+ unsigned char src_buf[SSH_IP_ADDR_STRING_SIZE];
+#endif /* DEBUG_LIGHT */
+
+ SSH_IP4_ENCODE(&key->dst, (unsigned char *) &daddr);
+
+ /* Initialize */
+ saddr = 0;
+ ipproto = 0;
+ tos = 0;
+ fwmark = 0;
+ dev = NULL;
+
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ SSH_IP4_ENCODE(&key->src, (unsigned char *) &saddr);
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM)
+ {
+ SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum);
+ dev = ssh_interceptor_ifnum_to_netdev(interceptor, key->ifnum);
+ }
+
+ if (dev == NULL)
+ return FALSE;
+
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO)
+ ipproto = key->ipproto;
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS)
+ tos = key->nh.ip4.tos;
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ /* Use linux fw_mark in routing */
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION)
+ fwmark = key->extension[SSH_LINUX_FWMARK_EXTENSION_SELECTOR];
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Build dummy skb */
+ skbp = alloc_skb(SSH_IPH4_HDRLEN, GFP_ATOMIC);
+ if (skbp == NULL)
+ goto fail;
+
+ SSH_SKB_RESET_MACHDR(skbp);
+ iph = (struct iphdr *) skb_put(skbp, SSH_IPH4_HDRLEN);
+ if (iph == NULL)
+ {
+ dev_kfree_skb(skbp);
+ goto fail;
+ }
+ SSH_SKB_SET_NETHDR(skbp, (unsigned char *) iph);
+
+ SSH_SKB_DST_SET(skbp, NULL);
+ skbp->protocol = __constant_htons(ETH_P_IP);
+ SSH_SKB_MARK(skbp) = fwmark;
+ iph->protocol = ipproto;
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route lookup: "
+ "dst %s src %s ifnum %d[%s] ipproto %d tos 0x%02x fwmark 0x%x",
+ ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)),
+ ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ?
+ ssh_ipaddr_print(&key->src, src_buf, sizeof(src_buf)) : NULL),
+ dev->ifindex, dev->name,
+ ipproto, tos, fwmark));
+
+ /* Perform route lookup */
+
+ rval = ip_route_input(skbp, daddr, saddr, tos, dev);
+ if (rval < 0 || SSH_SKB_DST(skbp) == NULL)
+ {
+ dev_kfree_skb(skbp);
+ goto fail;
+ }
+
+ /* Get the gateway, mtu and ifnum */
+ rt = (struct rtable *) SSH_SKB_DST(skbp);
+ SSH_IP4_DECODE(result->gw, &rt->rt_gateway);
+ result->mtu = SSH_DST_MTU(SSH_SKB_DST(skbp));
+ result->ifnum = SSH_SKB_DST(skbp)->dev->ifindex;
+ rt_type = rt->rt_type;
+
+#ifdef DEBUG_LIGHT
+ switch (rt_type)
+ {
+ case RTN_UNSPEC:
+ rt_type_str = "unspec";
+ break;
+ case RTN_UNICAST:
+ rt_type_str = "unicast";
+ break;
+ case RTN_LOCAL:
+ rt_type_str = "local";
+ break;
+ case RTN_BROADCAST:
+ rt_type_str = "broadcast";
+ break;
+ case RTN_ANYCAST:
+ rt_type_str = "anycast";
+ break;
+ case RTN_MULTICAST:
+ rt_type_str = "multicast";
+ break;
+ case RTN_BLACKHOLE:
+ rt_type_str = "blackhole";
+ break;
+ case RTN_UNREACHABLE:
+ rt_type_str = "unreachable";
+ break;
+ case RTN_PROHIBIT:
+ rt_type_str = "prohibit";
+ break;
+ case RTN_THROW:
+ rt_type_str = "throw";
+ break;
+ case RTN_NAT:
+ rt_type_str = "nat";
+ break;
+ case RTN_XRESOLVE:
+ rt_type_str = "xresolve";
+ break;
+ default:
+ rt_type_str = "unknown";
+ }
+#endif /* DEBUG_LIGHT */
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route result: dst %s via %s ifnum %d[%s] mtu %d type %s [%d]",
+ dst_buf, ssh_ipaddr_print(result->gw, src_buf, sizeof(src_buf)),
+ (int) result->ifnum,
+ (SSH_RT_DST(rt).dev->name ? SSH_RT_DST(rt).dev->name : "none"),
+ result->mtu, rt_type_str, rt_type));
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING
+ /* Check if need to create a child dst_entry with interface MTU. */
+ if ((selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED)
+ && SSH_SKB_DST(skbp)->child == NULL)
+ {
+ if (interceptor_route_create_child_dst(SSH_SKB_DST(skbp), FALSE) == NULL)
+ SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p",
+ SSH_SKB_DST(skbp)));
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Release the routing table entry ; otherwise a memory leak occurs
+ in the route entry table. */
+ dst_release(SSH_SKB_DST(skbp));
+ SSH_SKB_DST_SET(skbp, NULL);
+ dev_kfree_skb(skbp);
+
+ /* Assert that ifnum fits into the SshInterceptorIfnum data type. */
+ SSH_LINUX_ASSERT_IFNUM(result->ifnum);
+
+ /* Check that ifnum does not collide with SSH_INTERCEPTOR_INVALID_IFNUM. */
+ if (result->ifnum == SSH_INTERCEPTOR_INVALID_IFNUM)
+ goto fail;
+
+ /* Accept only unicast, broadcast, anycast, multicast and local routes. */
+ if (rt_type == RTN_UNICAST
+ || rt_type == RTN_BROADCAST
+ || rt_type == RTN_ANYCAST
+ || rt_type == RTN_MULTICAST
+ || rt_type == RTN_LOCAL)
+ {
+ ssh_interceptor_release_netdev(dev);
+ SSH_LINUX_ASSERT_VALID_IFNUM(result->ifnum);
+ return TRUE;
+ }
+
+ /* Fail route lookup for other route types. */
+ fail:
+ ssh_interceptor_release_netdev(dev);
+
+ SSH_DEBUG(SSH_D_FAIL,
+ ("Route lookup for %s failed with code %d",
+ ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)), rval));
+
+ return FALSE;
+}
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+
+/* Perform route lookup using linux ip6_route_output.
+
+ The route lookup will use the following selectors:
+ dst, src, outbound ifnum.
+
+ The following selectors are ignored:
+ ipv6 priority, flowlabel, ip protocol, dst port, src port,
+ icmp type, icmp code, ipsec spi, and fwmark. */
+Boolean
+ssh_interceptor_route_output_ipv6(SshInterceptor interceptor,
+ SshInterceptorRouteKey key,
+ SshUInt16 selector,
+ SshInterceptorRouteResult result)
+{
+ struct flowi rt_key;
+ struct dst_entry *dst;
+ struct rt6_info *rt;
+ u32 rt6i_flags;
+ int error = 0;
+ struct neighbour *neigh;
+#ifdef DEBUG_LIGHT
+ unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE];
+ unsigned char src_buf[SSH_IP_ADDR_STRING_SIZE];
+#endif /* DEBUG_LIGHT */
+
+ memset(&rt_key, 0, sizeof(rt_key));
+
+ SSH_IP6_ENCODE(&key->dst, rt_key.fl6_dst.s6_addr);
+
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ SSH_IP6_ENCODE(&key->src, rt_key.fl6_src.s6_addr);
+
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)
+ {
+ SSH_LINUX_ASSERT_VALID_IFNUM(key->ifnum);
+ rt_key.oif = key->ifnum;
+ }
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route lookup: dst %s src %s ifnum %d",
+ ssh_ipaddr_print(&key->dst, dst_buf, sizeof(dst_buf)),
+ ((selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ?
+ ssh_ipaddr_print(&key->src, src_buf, sizeof(src_buf)) : NULL),
+ (int) ((selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ?
+ key->ifnum : -1)));
+
+ dst = interceptor_ip6_route_output(&rt_key);
+ if (dst == NULL)
+ {
+ goto fail;
+ }
+ else if (dst->error != 0)
+ {
+ error = dst->error;
+ /* Release dst_entry */
+ dst_release(dst);
+ goto fail;
+ }
+
+ rt = (struct rt6_info *) dst;
+
+ /* Get the gateway, mtu and ifnum */
+
+ /* For an example of retrieving routing information for IPv6
+ within Linux kernel (2.4.19) see inet6_rtm_getroute()
+ in /usr/src/linux/net/ipv6/route.c */
+#ifdef LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS
+ neigh = dst_get_neighbour(&rt->dst);
+#else /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */
+ neigh = rt->rt6i_nexthop;
+#endif /* LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS */
+
+ if (neigh != NULL)
+ SSH_IP6_DECODE(result->gw, &neigh->primary_key);
+ else
+ SSH_IP6_DECODE(result->gw, &rt_key.fl6_dst.s6_addr);
+
+ result->mtu = SSH_DST_MTU(&SSH_RT_DST(rt));
+
+ /* The interface number might not be ok, but that is a problem
+ for the recipient of the routing information. */
+ result->ifnum = dst->dev->ifindex;
+
+ rt6i_flags = rt->rt6i_flags;
+
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Route result: %s via %s ifnum %d[%s] mtu %d flags 0x%08x[%s%s]",
+ dst_buf, ssh_ipaddr_print(result->gw, src_buf, sizeof(src_buf)),
+ (int) result->ifnum, (dst->dev ? dst->dev->name : "none"),
+ result->mtu, rt6i_flags, ((rt6i_flags & RTF_UP) ? "up " : ""),
+ ((rt6i_flags & RTF_REJECT) ? "reject" : "")));
+
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING
+ /* Check if need to create a child dst_entry with interface MTU. */
+ if (selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED)
+ {
+ if (dst->child == NULL)
+ if (interceptor_route_create_child_dst(dst, TRUE) == NULL)
+ SSH_DEBUG(SSH_D_FAIL, ("Could not create child dst_entry for dst %p",
+ dst));
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ /* Release dst_entry */
+ dst_release(dst);
+
+ /* Assert that ifnum fits into the SshInterceptorIfnum data type. */
+ SSH_LINUX_ASSERT_IFNUM(result->ifnum);
+
+ /* Check that ifnum does not collide with SSH_INTERCEPTOR_INVALID_IFNUM. */
+ if (result->ifnum == SSH_INTERCEPTOR_INVALID_IFNUM)
+ goto fail;
+
+ /* Accept only valid routes */
+ if ((rt6i_flags & RTF_UP)
+ && (rt6i_flags & RTF_REJECT) == 0)
+ {
+ SSH_LINUX_ASSERT_VALID_IFNUM(result->ifnum);
+ return TRUE;
+ }
+
+ /* Fail route lookup for reject and unknown routes */
+ fail:
+ SSH_DEBUG(SSH_D_FAIL, ("Route lookup for %s failed with code %d",
+ dst_buf, error));
+
+ return FALSE;
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+/* Performs a route lookup for the given destination address.
+ This also always calls a callback function. */
+
+void
+ssh_interceptor_route(SshInterceptor interceptor,
+ SshInterceptorRouteKey key,
+ SshInterceptorRouteCompletion callback,
+ void *cb_context)
+{
+ SshInterceptorRouteResultStruct result;
+
+ /* It is a fatal error to call ssh_interceptor_route with
+ a routing key that does not specify the destination address. */
+ SSH_ASSERT(SSH_IP_DEFINED(&key->dst));
+
+ if (SSH_IP_IS4(&key->dst))
+ {
+ /* Key specifies non-local src address and inbound ifnum
+ -> use ssh_interceptor_route_input_ipv4 */
+ if ((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ && (key->selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0
+ && (key->selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM))
+ {
+ /* Assert that all mandatory selectors are present. */
+ SSH_ASSERT(key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC);
+ SSH_ASSERT(SSH_IP_IS4(&key->src));
+ SSH_ASSERT(key->selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM);
+
+ if (!ssh_interceptor_route_input_ipv4(interceptor, key,
+ key->selector, &result))
+ goto fail;
+ }
+
+ /* Otherwise use ssh_interceptor_route_output_ipv4 */
+ else
+ {
+ SshUInt16 selector = key->selector;
+
+ /* Assert that all mandatory selectors are present. */
+ SSH_ASSERT((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) == 0
+ || SSH_IP_IS4(&key->src));
+
+ /* Key specifies non-local src address.
+ Linux ip_route_output_key will fail such route lookups,
+ so we must clear the src address selector and do the
+ route lookup as if src was one of local addresses.
+ For example, locally generated TCP RST packets are
+ such packets. */
+ if ((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0)
+ selector &= ~SSH_INTERCEPTOR_ROUTE_KEY_SRC;
+
+ if (!ssh_interceptor_route_output_ipv4(interceptor, key, selector,
+ &result))
+ goto fail;
+ }
+
+ (*callback)(TRUE, result.gw, result.ifnum, result.mtu, cb_context);
+ return;
+ }
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+ if (SSH_IP_IS6(&key->dst))
+ {
+ /* Assert that all mandatory selectors are present. */
+ SSH_ASSERT((key->selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) == 0
+ || SSH_IP_IS6(&key->src));
+
+ /* Always use ip6_route_output for IPv6 */
+ if (!ssh_interceptor_route_output_ipv6(interceptor, key, key->selector,
+ &result))
+ goto fail;
+
+ (*callback)(TRUE, result.gw, result.ifnum, result.mtu, cb_context);
+ return;
+ }
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
+
+ /* Fallback to error */
+ fail:
+ SSH_DEBUG(SSH_D_FAIL, ("Route lookup failed, unknown dst address type"));
+ (*callback) (FALSE, NULL, 0, 0, cb_context);
+}
+
+
+/**************************** Rerouting of Packets **************************/
+
+
+/* Route IPv4 packet 'skbp', using the route key selectors in
+ 'route_selector' and the interface number 'ifnum_in'. */
+Boolean
+ssh_interceptor_reroute_skb_ipv4(SshInterceptor interceptor,
+ struct sk_buff *skbp,
+ SshUInt16 route_selector,
+ SshUInt32 ifnum_in)
+{
+ struct iphdr *iph;
+ int rval = 0;
+
+ /* Recalculate the route info as the engine might have touched the
+ destination address. This can happen for example if we are in
+ tunnel mode. */
+
+ iph = (struct iphdr *) SSH_SKB_GET_NETHDR(skbp);
+ if (iph == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("Could not access IP header"));
+ return FALSE;
+ }
+
+ /* Release old dst_entry */
+ if (SSH_SKB_DST(skbp))
+ dst_release(SSH_SKB_DST(skbp));
+
+ SSH_SKB_DST_SET(skbp, NULL);
+
+ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ && (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0
+ && (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IN_IFNUM))
+ {
+ u32 saddr = 0;
+ u8 ipproto = 0;
+ u8 tos = 0;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ u32 fwmark = 0;
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ struct net_device *dev;
+
+ SSH_ASSERT(skbp->protocol == __constant_htons(ETH_P_IP));
+
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ saddr = iph->saddr;
+
+ /* Map 'ifnum_in' to a net_device. */
+ SSH_LINUX_ASSERT_VALID_IFNUM(ifnum_in);
+ dev = ssh_interceptor_ifnum_to_netdev(interceptor, ifnum_in);
+ if (dev == NULL)
+ return FALSE;
+
+ /* Clear the IP protocol, if selector does not define it. */
+ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) == 0)
+ {
+ ipproto = iph->protocol;
+ iph->protocol = 0;
+ }
+
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS)
+ tos = RT_TOS(iph->tos);
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ /* Clear the nfmark, if selector does not define it. */
+ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) == 0)
+ {
+ fwmark = SSH_SKB_MARK(skbp);
+ SSH_SKB_MARK(skbp) = 0;
+ }
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Call ip_route_input */
+ if (ip_route_input(skbp, iph->daddr, saddr, tos, dev) < 0)
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("ip_route_input failed. (0x%08x -> 0x%08x)",
+ iph->saddr, iph->daddr));
+
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("dst 0x%08x src 0x%08x iif %d[%s] proto %d tos 0x%02x "
+ "fwmark 0x%x",
+ iph->daddr, saddr, (dev ? dev->ifindex : -1),
+ (dev ? dev->name : "none"), iph->protocol, tos,
+ SSH_SKB_MARK(skbp)));
+
+ /* Release netdev reference */
+ ssh_interceptor_release_netdev(dev);
+
+ return FALSE;
+ }
+
+ /* Write original IP protocol back to skb */
+ if (ipproto)
+ iph->protocol = ipproto;
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ /* Write original fwmark back to skb */
+ if (fwmark)
+ SSH_SKB_MARK(skbp) = fwmark;
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Release netdev reference */
+ ssh_interceptor_release_netdev(dev);
+ }
+
+ else
+ {
+ struct rtable *rt;
+ struct flowi rt_key;
+
+ if ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_LOCAL_SRC) == 0)
+ route_selector &= ~SSH_INTERCEPTOR_ROUTE_KEY_SRC;
+
+ memset(&rt_key, 0, sizeof(rt_key));
+
+ rt_key.fl4_dst = iph->daddr;
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ rt_key.fl4_src = iph->saddr;
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)
+ rt_key.oif = (skbp->dev ? skbp->dev->ifindex : 0);
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO)
+ rt_key.proto = iph->protocol;
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS)
+ rt_key.fl4_tos = RT_TOS(iph->tos);
+ rt_key.fl4_scope = RT_SCOPE_UNIVERSE;
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+#ifdef SSH_LINUX_FWMARK_EXTENSION_SELECTOR
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION)
+ {
+#ifdef LINUX_HAS_SKB_MARK
+ rt_key.mark = SSH_SKB_MARK(skbp);
+#else /* LINUX_HAS_SKB_MARK */
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rt_key.fl4_fwmark = SSH_SKB_MARK(skbp);
+#endif /* CONFIG_IP_ROUTE_FWMARK */
+#endif /* LINUX_HAS_SKB_MARK */
+ }
+#endif /* SSH_LINUX_FWMARK_EXTENSION_SELECTOR */
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ rval = interceptor_ip4_route_output(&rt, &rt_key);
+ if (rval < 0)
+ {
+ SSH_DEBUG(SSH_D_FAIL,
+ ("ip_route_output_key failed (0x%08x -> 0x%08x): %d",
+ iph->saddr, iph->daddr, rval));
+
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("dst 0x%08x src 0x%08x oif %d[%s] proto %d tos 0x%02x"
+ "fwmark 0x%x",
+ iph->daddr,
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC) ?
+ iph->saddr : 0),
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ?
+ rt_key.oif : -1),
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM) ?
+ (skbp->dev ? skbp->dev->name : "none") : "none"),
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IPPROTO) ?
+ iph->protocol : -1),
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_IP4_TOS) ?
+ iph->tos : 0),
+ ((route_selector & SSH_INTERCEPTOR_ROUTE_KEY_EXTENSION) ?
+ SSH_SKB_MARK(skbp) : 0)));
+
+ return FALSE;
+ }
+
+ /* Make a new dst because we just rechecked the route. */
+ SSH_SKB_DST_SET(skbp, dst_clone(&SSH_RT_DST(rt)));
+
+ /* Release the routing table entry ; otherwise a memory leak occurs
+ in the route entry table. */
+ ip_rt_put(rt);
+ }
+
+ SSH_ASSERT(SSH_SKB_DST(skbp) != NULL);
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED)
+ {
+ /* Check if need to create a child dst_entry with interface MTU. */
+ if (SSH_SKB_DST(skbp)->child == NULL)
+ {
+ if (interceptor_route_create_child_dst(SSH_SKB_DST(skbp), FALSE)
+ == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Could not create child dst_entry for dst %p",
+ SSH_SKB_DST(skbp)));
+ return FALSE;
+ }
+ }
+
+ /* Pop dst stack and use the child entry with interface MTU
+ for sending the packet. */
+#ifdef LINUX_DST_POP_IS_SKB_DST_POP
+ SSH_SKB_DST_SET(skbp, dst_clone(skb_dst_pop(skbp)));
+#else /* LINUX_DST_POP_IS_SKB_DST_POP */
+ SSH_SKB_DST_SET(skbp, dst_pop(SSH_SKB_DST(skbp)));
+#endif /*LINUX_DST_POP_IS_SKB_DST_POP */
+ }
+#endif /* LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+
+ return TRUE;
+}
+
+
+#ifdef SSH_LINUX_INTERCEPTOR_IPV6
+
+/* Route IPv6 packet 'skbp', using the route key selectors in
+ 'route_selector' and the interface number 'ifnum_in'. */
+Boolean
+ssh_interceptor_reroute_skb_ipv6(SshInterceptor interceptor,
+ struct sk_buff *skbp,
+ SshUInt16 route_selector,
+ SshUInt32 ifnum_in)
+{
+ /* we do not need a socket, only fake flow */
+ struct flowi rt_key;
+ struct dst_entry *dst;
+ struct ipv6hdr *iph6;
+
+ iph6 = (struct ipv6hdr *) SSH_SKB_GET_NETHDR(skbp);
+ if (iph6 == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("Could not access IPv6 header"));
+ return FALSE;
+ }
+
+ memset(&rt_key, 0, sizeof(rt_key));
+
+ rt_key.fl6_dst = iph6->daddr;
+
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_SRC)
+ rt_key.fl6_src = iph6->saddr;
+
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_OUT_IFNUM)
+ {
+ rt_key.oif = (skbp->dev ? skbp->dev->ifindex : 0);
+ SSH_LINUX_ASSERT_IFNUM(rt_key.oif);
+ }
+
+ dst = interceptor_ip6_route_output(&rt_key);
+ if (dst == NULL || dst->error != 0)
+ {
+ if (dst != NULL)
+ dst_release(dst);
+
+ SSH_DEBUG(SSH_D_FAIL,
+ ("ip6_route_output failed."));
+
+ SSH_DEBUG_HEXDUMP(SSH_D_NICETOKNOW,
+ ("dst "),
+ (unsigned char *) &iph6->daddr, sizeof(iph6->daddr));
+ SSH_DEBUG_HEXDUMP(SSH_D_NICETOKNOW,
+ ("src "),
+ (unsigned char *) &iph6->saddr, sizeof(iph6->saddr));
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("oif %d[%s]",
+ (skbp->dev ? skbp->dev->ifindex : -1),
+ (skbp->dev ? skbp->dev->name : "none")));
+ return FALSE;
+ }
+ if (SSH_SKB_DST(skbp))
+ dst_release(SSH_SKB_DST(skbp));
+
+#ifdef SSH_IPSEC_IP_ONLY_INTERCEPTOR
+#ifdef LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING
+ if (route_selector & SSH_INTERCEPTOR_ROUTE_KEY_FLAG_TRANSFORM_APPLIED)
+ {
+ SSH_DEBUG(SSH_D_LOWOK,
+ ("Creating a new child entry for dst %p, child %p %lu",
+ dst, dst->child, skbp->_skb_refdst));
+
+ /* Check if need to create a child dst_entry with interface MTU. */
+ if (dst->child == NULL)
+ {
+ if (interceptor_route_create_child_dst(dst, TRUE) == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("Could not create child dst_entry for dst %p",
+ dst));
+
+ dst_release(dst);
+ return FALSE;
+ }
+ }
+
+ SSH_SKB_DST_SET(skbp, dst_clone(dst->child));
+ dst_release(dst);
+ }
+ else
+#endif /* LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING */
+#endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */
+ {
+ /* No need to clone the dst as ip6_route_output has already taken
+ one for us. */
+ SSH_SKB_DST_SET(skbp, dst);
+ }
+
+ return TRUE;
+}
+#endif /* SSH_LINUX_INTERCEPTOR_IPV6 */
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);
+}
diff --git a/drivers/interceptor/linux_versions.h b/drivers/interceptor/linux_versions.h
new file mode 100644
index 0000000..426b5b1
--- /dev/null
+++ b/drivers/interceptor/linux_versions.h
@@ -0,0 +1,190 @@
+/* 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_versions.h
+ *
+ * Linux interceptor kernel version specific defines. When adding support
+ * for new kernel versions, add the kernel version specific block to the
+ * bottom of the file and undefine any removed feature defines inherited
+ * from earlier kernel version blocks.
+ *
+ */
+
+#ifndef LINUX_VERSION_H
+#define LINUX_VERSION_H
+
+#include <linux/version.h>
+
+#ifndef KERNEL_VERSION
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#endif /* KERNEL_VERSION */
+
+/* 3.1.10 is the highest version currently supported */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(3,1,10)
+#error "Kernel versions after 3.1.10 are not supported"
+#endif /* LINUX_VERSION_CODE > KERNEL_VERSION(3,1,10) */
+
+/* 2.4 is not supported */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
+#error "Kernel versions pre 2.6.0 are not supported"
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) */
+
+/* 2.6 series specific things */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+#define LINUX_HAS_SKB_SECURITY 1
+#define LINUX_HAS_SKB_STAMP 1
+#define LINUX_HAS_SKB_NFCACHE 1
+#define LINUX_HAS_SKB_NFDEBUG 1
+#define LINUX_SKB_LINEARIZE_NEEDS_FLAGS 1
+#define LINUX_INODE_OPERATION_PERMISSION_HAS_NAMEIDATA 1
+#define LINUX_HAS_PROC_DIR_ENTRY_OWNER 1
+#define LINUX_HAS_HH_CACHE 1
+#endif /* >= 2.6.0 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,4)
+#define LINUX_HAS_SKB_MAC_LEN 1
+#endif /* >= 2.6.4 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,9)
+#define LINUX_HAS_ETH_HDR 1
+#endif /* >= 2.6.9 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,12)
+#define LINUX_HAS_DST_MTU 1
+#define LINUX_HAS_DEV_GET_FLAGS 1
+#endif /* >= 2.6.12 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+#undef LINUX_HAS_SKB_SECURITY
+#undef LINUX_HAS_SKB_STAMP
+#undef LINUX_HAS_SKB_NFCACHE
+#undef LINUX_HAS_SKB_NFDEBUG
+#endif /* >= 2.6.13 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
+#define LINUX_HAS_NETIF_F_UFO 1
+#endif /* >= 2.6.15 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
+#define LINUX_HAS_IP6CB_NHOFF 1
+#define LINUX_FRAGMENTATION_AFTER_NF_POST_ROUTING 1
+#endif /* >= 2.6.16 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
+#undef LINUX_SKB_LINEARIZE_NEEDS_FLAGS
+#define LINUX_HAS_NETIF_F_GSO 1
+#define LINUX_HAS_NETIF_F_TSO6 1
+#define LINUX_HAS_NETIF_F_TSO_ECN 1
+#define LINUX_HAS_NETIF_F_GSO_ROBUST 1
+#endif /* >= 2.6.18 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+#define LINUX_HAS_NEW_CHECKSUM_FLAGS 1
+#define LINUX_NEED_IF_ADDR_H 1
+#endif /* >= 2.6.19 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
+#define LINUX_HAS_SKB_MARK 1
+#define LINUX_HAS_SKB_CSUM_OFFSET 1
+#endif /* >= 2.6.20 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+#define LINUX_HAS_NETDEVICE_ACCESSORS 1
+#define LINUX_HAS_SKB_DATA_ACCESSORS 1
+#define LINUX_HAS_SKB_CSUM_START 1
+#endif /* >= 2.6.22 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+#define LINUX_NET_DEVICE_HAS_ARGUMENT 1
+#define LINUX_NF_HOOK_SKB_IS_POINTER 1
+#endif /* >= 2.6.24 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
+#define LINUX_NF_INET_HOOKNUMS 1
+#define LINUX_IP_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT 1
+#endif /* >= 2.6.25 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#define LINUX_IP6_ROUTE_OUTPUT_KEY_HAS_NET_ARGUMENT 1
+#endif /* >= 2.6.26 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+#undef LINUX_INODE_OPERATION_PERMISSION_HAS_NAMEIDATA
+#endif /* >= 2.6.27 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+#define LINUX_HAS_NFPROTO_ARP 1
+#endif /* >= 2.6.28 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
+#define LINUX_HAS_TASK_CRED_STRUCT 1
+#endif /* >= 2.6.29 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+#undef LINUX_HAS_PROC_DIR_ENTRY_OWNER
+#endif /* >= 2.6.30 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
+#define LINUX_HAS_SKB_DST_FUNCTIONS 1
+#endif /* >= 2.6.31 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+#endif /* >= 2.6.32 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
+#endif /* >= 2.6.33 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)
+#endif /* >= 2.6.34 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)
+#define LINUX_HAS_INET6_IFADDR_LIST_HEAD 1
+#define LINUX_DST_POP_IS_SKB_DST_POP 1
+#define LINUX_IP_ONLY_PASSTHROUGH_NDISC 1
+#define LINUX_FRAGMENTATION_AFTER_NF6_POST_ROUTING 1
+#endif /* >= 2.6.35 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+#define LINUX_RT_DST_IS_NOT_IN_UNION 1
+#endif /* >= 2.6.36 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+#endif /* >= 2.6.37 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,38)
+#define LINUX_INODE_OPERATION_PERMISSION_HAS_UINT 1
+#define LINUX_HAS_DST_COPY_METRICS 1
+#define LINUX_SSH_RTABLE_FIRST_ELEMENT_NEEDED 1
+#endif /* >= 2.6.38 */
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+#define LINUX_USE_NF_FOR_ROUTE_OUTPUT 1
+#define LINUX_FLOWI_NO_FL4_ACCESSORS 1
+#define LINUX_FLOWI_NO_FL6_ACCESSORS 1
+#define LINUX_DST_ALLOC_HAS_REFCOUNT 1
+#endif /* >= 2.6.38 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
+#define LINUX_DST_ALLOC_HAS_MANY_ARGS 1
+#endif /* >= 3.0.0 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,29)
+#define LINUX_HAS_DST_NEIGHBOUR_FUNCTIONS 1
+#endif /* >= 3.1.0 */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,1,0)
+#undef LINUX_INODE_OPERATION_PERMISSION_HAS_UINT
+#define LINUX_HAS_NET_DEVICE_OPS 1
+#undef LINUX_HAS_HH_CACHE
+#endif /* >= 3.1.0 */
+
+#endif /* LINUX_VERSION_H */
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(&params->mtu),
+ SSH_DECODE_UINT32(&params->dns_ip_count),
+ SSH_DECODE_UINT32_STR_NOCOPY(&dns, &dns_len),
+ SSH_DECODE_UINT32(&params->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,
+ &params->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,
+ &params->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;
+}
diff --git a/drivers/interceptor/linux_virtual_adapter_internal.h b/drivers/interceptor/linux_virtual_adapter_internal.h
new file mode 100644
index 0000000..72f3b01
--- /dev/null
+++ b/drivers/interceptor/linux_virtual_adapter_internal.h
@@ -0,0 +1,73 @@
+/* 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_internal.h
+ *
+ * Internal declarations for linux virtual adapters.
+ *
+ */
+
+
+#ifndef LINUX_VIRTUAL_ADAPTER_INTERNAL_H
+#define LINUX_VIRTUAL_ADAPTER_INTERNAL_H
+
+#include "virtual_adapter.h"
+
+/* **************************** Types and Definitions ************************/
+
+/* Maximum number of virtual adapters. */
+#define SSH_LINUX_MAX_VIRTUAL_ADAPTERS 16
+
+/* Prefix for adapter names. */
+#define SSH_ADAPTER_NAME_PREFIX "vip"
+
+/* HW address lenght. */
+#define SSH_MAX_VIRTUAL_ADAPTER_HWADDR_LEN 6
+
+/* Maximum number of configured IPv6 addresses the virtual adapter saves
+ and restores on ifdown / ifup. */
+#define SSH_VIRTUAL_ADAPTER_MAX_IPV6_ADDRS 2
+
+/* Context for a virtual adapter. */
+typedef struct SshVirtualAdapterRec
+{
+ /* Is the adapter initialized. This is 0 until the
+ ssh_virtual_adapter_create() returns. */
+ SshUInt8 initialized : 1;
+
+ /* Is the adapter destroyed. */
+ SshUInt8 destroyed : 1;
+
+ /* Is the adapter attached to engine. */
+ SshUInt8 attached : 1;
+
+ /* Packet callback. */
+ SshVirtualAdapterPacketCB packet_cb;
+
+ /* Destructor for engine-level block */
+ SshVirtualAdapterDetachCB detach_cb;
+ void *adapter_context;
+
+ /* The low-level implementation of a virtual adapter. */
+
+ /* Platform dependant low-level implementation structure. */
+ struct net_device *dev;
+ struct net_device_stats low_level_stats;
+
+} SshVirtualAdapterStruct, *SshVirtualAdapter;
+
+/* ******************************* Functions *********************************/
+
+int ssh_interceptor_virtual_adapter_init(SshInterceptor interceptor);
+int ssh_interceptor_virtual_adapter_uninit(SshInterceptor interceptor);
+
+#endif /* LINUX_VIRTUAL_ADAPTER_INTERNAL_H */
diff --git a/drivers/interceptor/platform_interceptor.h b/drivers/interceptor/platform_interceptor.h
new file mode 100644
index 0000000..2813cca
--- /dev/null
+++ b/drivers/interceptor/platform_interceptor.h
@@ -0,0 +1,34 @@
+/* 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.
+ */
+
+/*
+ * platform_interceptor.h
+ *
+ * Linux interceptor specific defines for the Interceptor API.
+ *
+ */
+
+#ifndef SSH_PLATFORM_INTERCEPTOR_H
+
+#define SSH_PLATFORM_INTERCEPTOR_H 1
+
+#ifdef KERNEL
+#ifndef KERNEL_INTERCEPTOR_USE_FUNCTIONS
+
+#define ssh_interceptor_packet_len(pp) \
+ ((size_t)((SshInterceptorInternalPacket)(pp))->skb->len)
+
+#include "linux_params.h"
+#include "linux_packet_internal.h"
+
+#endif /* KERNEL_INTERCEPTOR_USE_FUNCTIONS */
+#endif /* KERNEL */
+
+#endif /* SSH_PLATFORM_INTERCEPTOR_H */
diff --git a/drivers/interceptor/platform_kernel_mutex.h b/drivers/interceptor/platform_kernel_mutex.h
new file mode 100644
index 0000000..2e9d080
--- /dev/null
+++ b/drivers/interceptor/platform_kernel_mutex.h
@@ -0,0 +1,54 @@
+/* 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.
+ */
+
+/*
+ * platform_kernel_mutex.h
+ *
+ * Linux interceptor specific defines for the kernel mutex API.
+ *
+ */
+
+#ifndef PLATFORM_KERNEL_MUTEX_H
+#define PLATFORM_KERNEL_MUTEX_H 1
+
+/* Sanity check to make sure that optimizations get done correcly
+ on a UP machine. The following block can be safely removed. */
+#ifndef CONFIG_SMP
+#ifdef __ASM_SPINLOCK_H
+#error "asm/spinlock.h" should not be included explicitly.
+#endif /* __ASM_SPINLOCK_H */
+#endif /* !CONFIG_SMP */
+
+#include "linux_params.h"
+#include "linux_mutex_internal.h"
+
+/* Directly map linux mutexes to macros. This causes significantly
+ less overhead in the non-preemptive UP case, where these macros
+ are empty. */
+#ifndef KERNEL_MUTEX_USE_FUNCTIONS
+
+/* This code should not be used unless DEBUG_LIGHT is disabled. */
+
+#define ssh_kernel_mutex_lock(a) spin_lock(&((a)->lock))
+#define ssh_kernel_mutex_unlock(b) spin_unlock(&((b)->lock))
+
+#else /* KERNEL_MUTEX_USE_FUNCTIONS */
+
+void ssh_kernel_mutex_lock_i(SshKernelMutex mutex);
+void ssh_kernel_mutex_unlock_i(SshKernelMutex mutex);
+
+#define ssh_kernel_mutex_lock(a) \
+ ssh_kernel_mutex_lock_i((a))
+#define ssh_kernel_mutex_unlock(a) \
+ ssh_kernel_mutex_unlock_i((a))
+
+#endif /* KERNEL_MUTEX_USE_FUNCTIONS */
+
+#endif /* PLATFORM_KERNEL_MUTEX_H */
diff --git a/drivers/interceptor/sshconf.h b/drivers/interceptor/sshconf.h
new file mode 100644
index 0000000..e198965
--- /dev/null
+++ b/drivers/interceptor/sshconf.h
@@ -0,0 +1,88 @@
+/* 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.
+ */
+
+/*
+ * sshconf.h
+ *
+ * Generic configuration defines.
+ *
+ */
+
+#ifndef SSHCONF_H
+#define SSHCONF_H
+
+/* Light debugging */
+#define DEBUG_LIGHT 1
+
+/* Interceptor has its own version of ssh_interceptor_packet_copy */
+/* #undef INTERCEPTOR_HAS_PACKET_COPY */
+
+/* Interceptor has its own version of ssh_interceptor_packet_copyin */
+#define INTERCEPTOR_HAS_PACKET_COPYIN 1
+
+/* Interceptor has its own version of ssh_interceptor_packet_copyout */
+#define INTERCEPTOR_HAS_PACKET_COPYOUT 1
+
+/* Interceptor has its own versions of
+ ssh_interceptor_export_internal_data and
+ ssh_interceptor_import_internal_data */
+#define INTERCEPTOR_HAS_PACKET_INTERNAL_DATA_ROUTINES 1
+
+/* Interceptor has "platform_interceptor.h" include file
+ to be included by interceptor.h. */
+#define INTERCEPTOR_HAS_PLATFORM_INCLUDE 1
+
+/* Should the interceptor align the IP header of packets to word boundary
+ when sending to the network or stack? */
+/* #undef INTERCEPTOR_IP_ALIGNS_PACKETS */
+
+/* Define this if the interceptor operates at IP level (that is, no
+ interface supplies or requires packets at ethernet level, or
+ generally media level). Generally there is no much difference in
+ performance whether the interceptor operates at ethernet level or
+ at IP level; however, some functionality (particularity the ability
+ to proxy arp so that the same subnet can be shared for both
+ external and DMZ interfaces) is not available without an ethernet
+ level interceptor. */
+#define SSH_IPSEC_IP_ONLY_INTERCEPTOR 1
+
+#define KERNEL_SIZEOF_SHORT 2
+#define KERNEL_SIZEOF_INT 4
+#define KERNEL_SIZEOF_LONG_LONG 8
+
+#ifdef __LP64__
+#define KERNEL_SIZEOF_LONG 8
+#define KERNEL_SIZEOF_SIZE_T 8
+#define KERNEL_SIZEOF_VOID_P 8
+#else
+#define KERNEL_SIZEOF_LONG 4
+#define KERNEL_SIZEOF_SIZE_T 4
+#define KERNEL_SIZEOF_VOID_P 4
+#endif
+
+#include <asm/byteorder.h>
+
+#if defined(__BIG_ENDIAN)
+#define WORDS_BIGENDIAN
+#elif !defined(__LITTLE_ENDIAN)
+#error cannot determine byte order
+#endif
+
+/* "Have" for kernel basic types */
+#define HAVE_KERNEL_INT 1
+#define HAVE_KERNEL_LONG 1
+#define HAVE_KERNEL_LONG_LONG 1
+#define HAVE_KERNEL_SHORT 1
+#define HAVE_KERNEL_VOID_P 1
+
+/* Define this to enable IPv6 support. */
+#define WITH_IPV6 1
+
+#endif /* SSHCONF_H */
diff --git a/drivers/interceptor/sshdebug.h b/drivers/interceptor/sshdebug.h
new file mode 100644
index 0000000..c1c0836
--- /dev/null
+++ b/drivers/interceptor/sshdebug.h
@@ -0,0 +1,175 @@
+/* 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.
+ */
+
+/*
+ * sshdebug.h
+ *
+ * Debug macros and assertions.
+ *
+ */
+
+#ifndef SSHDEBUG_H
+#define SSHDEBUG_H
+
+#ifdef DEBUG_LIGHT
+
+/* Debug level */
+extern unsigned int ssh_debug_level;
+
+#define SSH_FATAL(_fmt...) panic(_fmt)
+
+#define SSH_NOTREACHED \
+ panic("%s:%d: Unreachable code reached!", __FILE__, __LINE__)
+
+#define SSH_DEBUG(level, varcall) \
+ do { \
+ if ((level) <= ssh_debug_level) \
+ { \
+ printk("%s:%d: ", __FILE__, __LINE__); \
+ printk varcall; \
+ printk("\n"); \
+ } \
+ } while (0)
+
+#define SSH_ASSERT(cond) \
+ do { \
+ if (!(cond)) \
+ panic("%s:%d: Assertion failed: %s\n", __FILE__, __LINE__, "#cond"); \
+ } while (0)
+#define SSH_VERIFY(cond) SSH_ASSERT(cond)
+#define SSH_PRECOND(cond) SSH_ASSERT(cond)
+
+static inline void
+ssh_debug_hexdump(const unsigned char *buf, const size_t len)
+{
+ size_t i;
+
+ for (i = 0; (i + 16) < len; i += 16)
+ printk("%08x: %02x%02x %02x%02x %02x%02x %02x%02x "
+ "%02x%02x %02x%02x %02x%02x %02x%02x\n",
+ (unsigned int) i,
+ (unsigned int) buf[i+0], (unsigned int) buf[i+1],
+ (unsigned int) buf[i+2], (unsigned int) buf[i+3],
+ (unsigned int) buf[i+4], (unsigned int) buf[i+5],
+ (unsigned int) buf[i+6], (unsigned int) buf[i+7],
+ (unsigned int) buf[i+8], (unsigned int) buf[i+9],
+ (unsigned int) buf[i+10], (unsigned int) buf[i+11],
+ (unsigned int) buf[i+12], (unsigned int) buf[i+13],
+ (unsigned int) buf[i+14], (unsigned int) buf[i+15]);
+ if (i >= len)
+ return;
+
+ printk("%08x: ", (unsigned int) i);
+ for (; i < len; i++)
+ printk("%02x%s", (unsigned int) buf[i], ((i % 2) == 1 ? " " : ""));
+ printk("\n");
+}
+
+#define SSH_DEBUG_HEXDUMP(level, varcall, buf, len) \
+ do { \
+ if ((level) <= ssh_debug_level) \
+ { \
+ printk("%s:%d: ", __FILE__, __LINE__); \
+ printk varcall; \
+ printk("\n"); \
+ ssh_debug_hexdump((buf), (len)); \
+ } \
+ } while (0)
+
+#else /* !DEBUG_LIGHT */
+
+#define SSH_FATAL(_fmt...) panic(_fmt)
+
+#define SSH_NOTREACHED \
+ panic("%s:%d: Unreachable code reached!", __FILE__, __LINE__)
+
+#define SSH_DEBUG(level, varcall)
+
+#define SSH_VERIFY(cond) \
+ do { \
+ if (!(cond)) \
+ panic("%s:%d: Verify failed: %s\n", __FILE__, __LINE__, "#cond"); \
+ } while (0)
+
+#ifdef __COVERITY__
+#define SSH_ASSERT(cond) \
+ do { \
+ if (!(cond)) \
+ panic("%s:%d: Assertion failed: %s\n", __FILE__, __LINE__, "#cond"); \
+ } while (0)
+#else /* __COVERITY__ */
+#define SSH_ASSERT(cond)
+#endif /* __COVERITY__ */
+
+#define SSH_PRECOND(cond) SSH_ASSERT(cond)
+
+#define SSH_DEBUG_HEXDUMP(level, varcall, buf, len)
+
+#endif /* DEBUG_LIGHT */
+
+
+
+/* *********************************************************************
+ * DEBUG LEVELS
+ * *********************************************************************/
+
+/* *********************************************************************
+ * Debug type definitions for debug level mapping
+ * *********************************************************************/
+
+/* Use debug code definitions below, not the debug level numbers
+ (except 11-15). */
+
+/** Software malfunction. */
+#define SSH_D_ERROR 0
+
+/** Software failure, but caused by a packet coming from network. */
+#define SSH_D_NETFAULT 3
+
+/** Data formatted incorrectly coming from a network or other outside source.*/
+#define SSH_D_NETGARB 3
+
+/** Nonfatal failure in a high or middle-level operation. */
+#define SSH_D_FAIL 3
+
+/** Uncommon situation. */
+#define SSH_D_UNCOMMON 6
+
+/** Success in a high-level operation. */
+#define SSH_D_HIGHOK 4
+
+/** Success in a middle-level operation. */
+#define SSH_D_MIDOK 7
+
+/** Success in a low-level operation. */
+#define SSH_D_LOWOK 9
+
+/** Start of a high-level operation. */
+#define SSH_D_HIGHSTART 5
+
+/** Start of a middle-level operation. */
+#define SSH_D_MIDSTART 8
+
+/** Start of a low-level operation. */
+#define SSH_D_LOWSTART 10
+
+/** Nice-to-know information. */
+#define SSH_D_NICETOKNOW 7
+
+/** Data block dump. */
+#define SSH_D_DATADUMP 8
+
+/** Packet dump. */
+#define SSH_D_PCKDMP 9
+
+/** Middle result of an operation, loop-internal information. */
+#define SSH_D_MIDRESULT 10
+
+#endif /* SSHDEBUG_H */
diff --git a/drivers/interceptor/sshencode.h b/drivers/interceptor/sshencode.h
new file mode 100644
index 0000000..473e212
--- /dev/null
+++ b/drivers/interceptor/sshencode.h
@@ -0,0 +1,202 @@
+/* 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.
+ */
+
+/*
+ * sshencode.h
+ *
+ * Encode/decode API.
+ *
+ */
+
+#ifndef SSHENCODE_H
+#define SSHENCODE_H
+
+/* Encode object in `datum' into buffer `buf' whose size is `len' bytes.
+
+ Return values:
+ [0,len] number of bytes datums prentation took in the buffer.
+ ]len,inf[ amount of space writing datum would require.
+
+ If buf can be extented, this will be done by the driver, and the
+ encoder function will be called again. If buf can not be extented
+ (either it is static, or allocating space fails at the driver,
+ ssh_encode call will return error. */
+typedef int (*SshEncodeDatum)(unsigned char *buf, size_t len,
+ const void *datum);
+
+/* Decode */
+typedef int (*SshDecodeDatum)(const unsigned char *buf, size_t len,
+ void **datum);
+
+/* Decode, which does not alloc new datum to be returned, but assumes that
+ it gets preallocated datum of correct size in, and fills it up */
+typedef int (*SshDecodeDatumNoAlloc)(const unsigned char *buf, size_t len,
+ void *datum);
+
+/* The packet encoding/decoding functions take a variable number of
+ arguments, and decode data from a SshBuffer or a character array as
+ specified by a format. Each element of the format contains a type
+ specifier, and arguments depending on the type specifier. The list
+ must end with a SSH_FORMAT_END specifier. */
+
+typedef enum {
+ /* Specifies a string with uint32-coded length. This has two arguments.
+ For encoding,
+ const unsigned char *data
+ size_t len
+ For decoding,
+ unsigned char **data_return
+ size_t *len_return
+ When decoding, either or both arguments may be NULL, in which case they
+ are not stored. The returned data is allocated by ssh_xmalloc, and an
+ extra nul (\0) character is automatically added at the end to make it
+ easier to retrieve strings. */
+ SSH_FORMAT_UINT32_STR, /* Encode const unsigned char *, size_t */
+ /* Decode unsigned char *, size_t * */
+
+
+ /* This code can only be used while decoding. This specifies string with
+ uint32-coded length. This has two arguments:
+ unsigned char **data_return
+ size_t *len_return
+ Either argument may be NULL. *data_return is set to point to the data
+ in the packet, and *len_return is set to the length of the string.
+ No null character is stored, and the string remains in the original
+ buffer. This can only be used with ssh_decode_array. */
+ SSH_FORMAT_UINT32_STR_NOCOPY, /* unsigned char **, size_t */
+
+ /* An 32-bit MSB first integer value. */
+ SSH_FORMAT_UINT32, /* SshUInt32, note that if you encode constant
+ integer, you still must use (SshUInt32) cast
+ before it. Also enums must be casted to
+ SshUInt32 before encoding. */
+
+ /* A boolean value. For encoding, this has a single "Boolean" argument.
+ For decoding, this has a "Boolean *" argument, where the value will
+ be stored. The argument may be NULL in which case the value is not
+ stored. */
+ SSH_FORMAT_BOOLEAN, /* Boolean */
+
+ /* Application specific value given as `void *' argument is encoded
+ using SshEncodeDatum function, or decoded using SshDecodeDatum.
+ For information about renderers, see their definitions. */
+ SSH_FORMAT_SPECIAL, /* SshEncodeDatum, void * */
+ /* SshDecodeDatum, void ** */
+
+ /* A single one-byte character. The argument is of type "unsigned int"
+ when encoding, and of type "unsigned int *" when decoding. The value
+ may also be NULL when decoding, in which case the value is ignored. */
+ SSH_FORMAT_CHAR, /* unsigned int */
+
+ /* A fixed-length character array, without explicit length. When
+ encoding, the arguments are
+ const unsigned char *buf
+ size_t len
+ and when decoding,
+ unsigned char *buf
+ size_t len
+ The buffer must be preallocated when decoding; data is simply copied
+ there. `buf' may also be NULL, in which the value is ignored. */
+ SSH_FORMAT_DATA, /* char * (fixed length!), size_t */
+
+ /* A 64-bit MSB first integer value. For encoding, this has a single
+ "SshUInt64" argument (the value), and for decoding an
+ "SshUInt64 *" argument, where the value will be stored. The argument
+ may be NULL in which case the value is not stored. */
+ SSH_FORMAT_UINT64, /* SshUInt64 */
+
+ /* A 16-bit MSB first integer value. */
+ SSH_FORMAT_UINT16, /* SshUInt16 */
+
+ /* Marks end of the argument list. */
+ SSH_FORMAT_END = 0x0d0e0a0d
+} SshEncodingFormat;
+
+/* Encodes the given data to a given buffer as specified by the
+ variable-length argument list. If the given buffer cannot hold the
+ encoded data, 0 is returned and the given buffer is left in
+ undefined state. */
+size_t ssh_encode_array(unsigned char *buf, size_t bufsize,...);
+
+/* Encodes the given data to a given buffer as specified by the
+ variable-length argument list. If the given buffer cannot hold the
+ encoded data, 0 is returned and the given buffer is left in
+ undefined state. */
+size_t ssh_encode_array_va(unsigned char *buf, size_t bufsize, va_list ap);
+
+/* Encodes the given data. Returns the length of encoded data in
+ bytes, and if `buf_return' is non-NULL, it is set to a memory area
+ allocated by ssh_xmalloc that contains the data. The caller should
+ free the data when no longer needed. */
+size_t ssh_encode_array_alloc(unsigned char **buf_return, ...);
+
+/* Encodes the given data. Returns the length of encoded data in
+ bytes, and if `buf_return' is non-NULL, it is set to a memory area
+ allocated by ssh_xmalloc that contains the data. The caller should
+ free the data when no longer needed. */
+size_t ssh_encode_array_alloc_va(unsigned char **buf_return, va_list ap);
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocated data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array(const unsigned char *buf, size_t len, ...);
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocated data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array_va(const unsigned char *buf, size_t len, va_list ap);
+
+#define ssh_xxcode_unsigned_char_ptr(ptr) ((unsigned char *) (ptr))
+#define ssh_xxcode_const_unsigned_char_ptr(ptr) ((const unsigned char *) (ptr))
+#define ssh_xxcode_size_t(size) ((size_t) (size))
+#define ssh_xxcode_uint32(num) ((SshUInt32) (num))
+#define ssh_xxcode_uint32_ptr(ptr) ((SshUInt32 *) (ptr))
+#define ssh_xxcode_unsigned_int(num) ((unsigned int) num)
+#define ssh_xxcode_unsigned_int_ptr(ptr) ((unsigned int *) ptr)
+#define ssh_xxcode_unsigned_char_ptr_ptr(ptr) ((unsigned char **) (ptr))
+#define ssh_xxcode_size_t_ptr(size) ((size_t *) (size))
+
+#define SSH_ENCODE_UINT32(num) \
+ SSH_FORMAT_UINT32, \
+ ssh_xxcode_uint32(num)
+#define SSH_DECODE_UINT32(ptr) \
+ SSH_FORMAT_UINT32, \
+ ssh_xxcode_uint32_ptr(ptr)
+
+#define SSH_DECODE_UINT32_STR_NOCOPY(ptr,size) \
+ SSH_FORMAT_UINT32_STR_NOCOPY, \
+ ssh_xxcode_unsigned_char_ptr_ptr(ptr), \
+ ssh_xxcode_size_t_ptr(size)
+
+#define SSH_ENCODE_CHAR(num) \
+ SSH_FORMAT_CHAR, \
+ ssh_xxcode_unsigned_int(num)
+#define SSH_DECODE_CHAR(ptr) \
+ SSH_FORMAT_CHAR, \
+ ssh_xxcode_unsigned_int_ptr(ptr)
+
+#define SSH_ENCODE_DATA(ptr,size) \
+ SSH_FORMAT_DATA, \
+ ssh_xxcode_const_unsigned_char_ptr(ptr), \
+ ssh_xxcode_size_t(size)
+#define SSH_DECODE_DATA(ptr,size) \
+ SSH_FORMAT_DATA, \
+ ssh_xxcode_unsigned_char_ptr(ptr), \
+ ssh_xxcode_size_t(size)
+
+#endif /* SSHENCODE_H */
diff --git a/drivers/interceptor/sshgetput.h b/drivers/interceptor/sshgetput.h
new file mode 100644
index 0000000..9e009a4
--- /dev/null
+++ b/drivers/interceptor/sshgetput.h
@@ -0,0 +1,108 @@
+/* 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.
+ */
+
+/*
+ * sshgetput.h
+ *
+ * Macros for enconding/decoding of integers to character data.
+ *
+ */
+
+#ifndef SSHGETPUT_H
+#define SSHGETPUT_H
+
+
+#define SSH_GET_8BIT(cp) (*(unsigned char *)(cp))
+#define SSH_PUT_8BIT(cp, value) (*(unsigned char *)(cp)) = \
+ (unsigned char)(value)
+
+#define SSH_GET_4BIT_LOW(cp) (*(unsigned char *)(cp) & 0x0f)
+#define SSH_GET_4BIT_HIGH(cp) ((*(unsigned char *)(cp) >> 4) & 0x0f)
+#define SSH_PUT_4BIT_LOW(cp, value) (*(unsigned char *)(cp) = \
+ (unsigned char)((*(unsigned char *)(cp) & 0xf0) | ((value) & 0x0f)))
+#define SSH_PUT_4BIT_HIGH(cp, value) (*(unsigned char *)(cp) = \
+ (unsigned char)((*(unsigned char *)(cp) & 0x0f) | (((value) & 0x0f) << 4)))
+
+#ifdef SSHUINT64_IS_64BITS
+
+#define SSH_GET_64BIT(cp) (((SshUInt64)SSH_GET_32BIT((cp)) << 32) | \
+ ((SshUInt64)SSH_GET_32BIT((cp) + 4)))
+#define SSH_PUT_64BIT(cp, value) do { \
+ SSH_PUT_32BIT((cp), (SshUInt32)((SshUInt64)(value) >> 32)); \
+ SSH_PUT_32BIT((cp) + 4, (SshUInt32)(value)); } while (0)
+
+#else /* SSHUINT64_IS_64BITS */
+
+#define SSH_GET_64BIT(cp) ((SshUInt64)SSH_GET_32BIT((cp) + 4))
+#define SSH_PUT_64BIT(cp, value) do { \
+ SSH_PUT_32BIT((cp), 0L); \
+ SSH_PUT_32BIT((cp) + 4, (SshUInt32)(value)); } while (0)
+#define SSH_GET_64BIT_LSB_FIRST(cp) ((SshUInt64)SSH_GET_32BIT((cp)))
+#define SSH_PUT_64BIT_LSB_FIRST(cp, value) do { \
+ SSH_PUT_32BIT_LSB_FIRST((cp), (SshUInt32)(value)); \
+ SSH_PUT_32BIT_LSB_FIRST((cp) + 4, 0L); } while (0)
+
+#define SSH_GET_40BIT(cp) ((SshUInt64)SSH_GET_32BIT((cp) + 1))
+#define SSH_PUT_40BIT(cp, value) do { \
+ SSH_PUT_8BIT((cp), 0); \
+ SSH_PUT_32BIT((cp) + 1, (SshUInt32)(value)); } while (0)
+#define SSH_GET_40BIT_LSB_FIRST(cp) ((SshUInt64)SSH_GET_32BIT_LSB_FIRST((cp)))
+#define SSH_PUT_40BIT_LSB_FIRST(cp, value) do { \
+ SSH_PUT_32BIT_LSB_FIRST((cp), (SshUInt32)(value)); \
+ SSH_PUT_8BIT((cp) + 4, 0); } while (0)
+
+#endif /* SSHUINT64_IS_64BITS */
+
+
+/*------------ macros for storing/extracting msb first words -------------*/
+
+#define SSH_GET_32BIT(cp) \
+ ((((unsigned long)((unsigned char *)(cp))[0]) << 24) | \
+ (((unsigned long)((unsigned char *)(cp))[1]) << 16) | \
+ (((unsigned long)((unsigned char *)(cp))[2]) << 8) | \
+ ((unsigned long)((unsigned char *)(cp))[3]))
+
+#define SSH_GET_16BIT(cp) \
+ ((SshUInt16) ((((unsigned long)((unsigned char *)(cp))[0]) << 8) | \
+ ((unsigned long)((unsigned char *)(cp))[1])))
+
+#define SSH_PUT_32BIT(cp, value) do { \
+ ((unsigned char *)(cp))[0] = (unsigned char)((value) >> 24); \
+ ((unsigned char *)(cp))[1] = (unsigned char)((value) >> 16); \
+ ((unsigned char *)(cp))[2] = (unsigned char)((value) >> 8); \
+ ((unsigned char *)(cp))[3] = (unsigned char)(value); } while (0)
+
+#define SSH_PUT_16BIT(cp, value) do { \
+ ((unsigned char *)(cp))[0] = (unsigned char)((value) >> 8); \
+ ((unsigned char *)(cp))[1] = (unsigned char)(value); } while (0)
+
+/*------------ macros for storing/extracting lsb first words -------------*/
+
+#define SSH_GET_32BIT_LSB_FIRST(cp) \
+ (((unsigned long)((unsigned char *)(cp))[0]) | \
+ (((unsigned long)((unsigned char *)(cp))[1]) << 8) | \
+ (((unsigned long)((unsigned char *)(cp))[2]) << 16) | \
+ (((unsigned long)((unsigned char *)(cp))[3]) << 24))
+
+#define SSH_GET_16BIT_LSB_FIRST(cp) \
+ ((SshUInt16) (((unsigned long)((unsigned char *)(cp))[0]) | \
+ (((unsigned long)((unsigned char *)(cp))[1]) << 8)))
+
+#define SSH_PUT_32BIT_LSB_FIRST(cp, value) do { \
+ ((unsigned char *)(cp))[0] = (unsigned char)(value); \
+ ((unsigned char *)(cp))[1] = (unsigned char)((value) >> 8); \
+ ((unsigned char *)(cp))[2] = (unsigned char)((value) >> 16); \
+ ((unsigned char *)(cp))[3] = (unsigned char)((value) >> 24); } while (0)
+
+#define SSH_PUT_16BIT_LSB_FIRST(cp, value) do { \
+ ((unsigned char *)(cp))[0] = (unsigned char)(value); \
+ ((unsigned char *)(cp))[1] = (unsigned char)((value) >> 8); } while (0)
+
+#endif /* GETPUT_H */
diff --git a/drivers/interceptor/sshincludes.h b/drivers/interceptor/sshincludes.h
new file mode 100644
index 0000000..b0b59d8
--- /dev/null
+++ b/drivers/interceptor/sshincludes.h
@@ -0,0 +1,53 @@
+/* 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.
+ */
+
+/*
+ * sshincludes.h
+ *
+ * Common include file.
+ *
+ */
+
+#ifndef SSHINCLUDES_H
+#define SSHINCLUDES_H
+
+/* Defines related to segmented memory architectures. */
+#ifndef NULL_FNPTR
+#define NULL_FNPTR NULL
+#endif
+
+/* Macros for giving branch prediction hints to the compiler. The
+ result type of the expression must be an integral type. */
+#if __GNUC__ >= 3
+#define SSH_PREDICT_TRUE(expr) __builtin_expect(!!(expr), 1)
+#define SSH_PREDICT_FALSE(expr) __builtin_expect(!!(expr), 0)
+#else /* __GNUC__ >= 3 */
+#define SSH_PREDICT_TRUE(expr) (!!(expr))
+#define SSH_PREDICT_FALSE(expr) (!!(expr))
+#endif /* __GNUC__ >= 3 */
+
+/* Macros for marking functions to be placed in a special section. */
+#if __GNUC__ >= 3
+#define SSH_FASTTEXT __attribute__((__section__ (".text.fast")))
+#else /* __GNUC__ >= 3 */
+#define SSH_FASTTEXT
+#endif /* __GNUC__ >= 3 */
+
+/* Some generic pointer types. */
+typedef char *SshCharPtr;
+typedef void *SshVoidPtr;
+
+#include "kernel_includes.h"
+
+/* Some internal headers used in almost every file. */
+#include "sshdebug.h"
+#include "engine_alloc.h"
+
+#endif /* SSHINCLUDES_H */
diff --git a/drivers/interceptor/sshinet.h b/drivers/interceptor/sshinet.h
new file mode 100644
index 0000000..ac18c53
--- /dev/null
+++ b/drivers/interceptor/sshinet.h
@@ -0,0 +1,233 @@
+/* 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.
+ */
+
+/*
+ * sshinet.h
+ *
+ * Inet API.
+ *
+ */
+
+#ifndef SSHINET_H
+#define SSHINET_H
+
+#include "sshgetput.h"
+
+/*************************** Ethernet definitions ***************************/
+
+/* Etherenet header things we need */
+#define SSH_ETHERH_HDRLEN 14
+#define SSH_ETHERH_OFS_DST 0
+#define SSH_ETHERH_OFS_SRC 6
+#define SSH_ETHERH_OFS_TYPE 12
+#define SSH_ETHERH_ADDRLEN 6
+
+/* Known values for the ethernet type field. The same values are used for
+ both ethernet (rfc894) and IEEE 802 encapsulation (the type will just
+ be in a different position in the header). */
+#define SSH_ETHERTYPE_IP 0x0800 /* IPv4, as per rfc894 */
+#define SSH_ETHERTYPE_ARP 0x0806 /* ARP, as per rfc826 */
+#define SSH_ETHERTYPE_IPv6 0x86dd /* IPv6, as per rfc1972 */
+
+
+/***************************** SshIpAddr stuff ******************************/
+
+typedef enum {
+ SSH_IP_TYPE_NONE = 0,
+ SSH_IP_TYPE_IPV4 = 1,
+ SSH_IP_TYPE_IPV6 = 2
+} SshIpAddrType;
+
+#if defined(WITH_IPV6)
+/* An IPv6 link-local address scope ID. */
+struct SshScopeIdRec
+{
+ union
+ {
+ SshUInt32 ui32;
+ } scope_id_union;
+};
+
+typedef struct SshScopeIdRec SshScopeIdStruct;
+typedef struct SshScopeIdRec *SshScopeId;
+
+#endif /* WITH_IPV6 */
+
+#if !defined(WITH_IPV6)
+#define SSH_IP_ADDR_SIZE 4
+#define SSH_IP_ADDR_STRING_SIZE 32
+#else /* WITH_IPV6 */
+#define SSH_IP_ADDR_SIZE 16
+#define SSH_IP_ADDR_STRING_SIZE 64
+#endif /* !WITH_IPV6 */
+
+typedef struct SshIpAddrRec
+{
+ /* Note: All fields of this data structure are private, and should
+ not be accessed except using the macros and functions defined in
+ this header. They should never be accessed directly; the
+ internal definition of this structure is subject to change
+ without notice. */
+ SshUInt8 type; /* KEEP type first if changing rest of the contents */
+ SshUInt8 mask_len;
+
+ /* There is a hole of 16 bits here */
+
+ /* For optimised mask comparison routine _addr_data has to be 32-bit
+ aligned so it can be read as words on machines requiring
+ alignment */
+ union {
+ unsigned char _addr_data[SSH_IP_ADDR_SIZE];
+ SshUInt32 _addr_align;
+ } addr_union;
+
+#define addr_data addr_union._addr_data
+
+#if defined(WITH_IPV6)
+ SshScopeIdStruct scope_id;
+#endif /* WITH_IPV6 */
+
+} *SshIpAddr, SshIpAddrStruct;
+
+#define SSH_IP_DEFINED(ip_addr) ((ip_addr)->type != SSH_IP_TYPE_NONE)
+#define SSH_IP_IS4(ip_addr) ((ip_addr)->type == SSH_IP_TYPE_IPV4)
+#define SSH_IP_IS6(ip_addr) ((ip_addr)->type == SSH_IP_TYPE_IPV6)
+
+#define SSH_IP_ADDR_LEN(ip_addr) \
+ (SSH_PREDICT_TRUE(SSH_IP_IS4(ip_addr))\
+ ? (4) \
+ : (SSH_IP_IS6(ip_addr) \
+ ? (16) \
+ : 0))
+
+/* Make given IP address undefined. */
+#define SSH_IP_UNDEFINE(IPADDR) \
+do { \
+ (IPADDR)->type = SSH_IP_TYPE_NONE; \
+} while (0)
+
+
+#if defined(WITH_IPV6)
+/* Decode, that is fill given 'ipaddr', with given 'type', 'bytes' and
+ 'masklen' information. */
+#define __SSH_IP_MASK_DECODE(IPADDR,TYPE,BYTES,BYTELEN,MASKLEN) \
+ do { \
+ (IPADDR)->type = (TYPE); \
+ memmove((IPADDR)->addr_data, (BYTES), (BYTELEN)); \
+ memset(&(IPADDR)->scope_id, 0, sizeof((IPADDR)->scope_id)); \
+ (IPADDR)->mask_len = (MASKLEN); \
+ } while (0)
+#else /* WITH_IPV6 */
+#define __SSH_IP_MASK_DECODE(IPADDR,TYPE,BYTES,BYTELEN,MASKLEN) \
+ do { \
+ (IPADDR)->type = (TYPE); \
+ memmove((IPADDR)->addr_data, (BYTES), (BYTELEN)); \
+ (IPADDR)->mask_len = (MASKLEN); \
+ } while (0)
+#endif /* WITH_IPV6 */
+
+/* Encode, that is copy from 'ipaddr' into 'bytes' and 'maskptr'. The
+ input 'ipaddr' needs to be of given 'type'. It is an fatal error
+ to call this for invalid address type. */
+#define __SSH_IP_MASK_ENCODE(IPADDR,TYPE,BYTES,BYTELEN,MASKPTR) \
+ do { \
+ SSH_VERIFY((IPADDR)->type == (TYPE)); \
+ memmove((BYTES), (IPADDR)->addr_data, (BYTELEN)); \
+ if (SSH_PREDICT_FALSE(MASKPTR)) \
+ *((SshUInt32 *) (MASKPTR)) = (IPADDR)->mask_len; \
+ } while (0)
+
+/* IPv4 Address manipulation */
+#define SSH_IP4_ENCODE(ip_addr,bytes) \
+ __SSH_IP_MASK_ENCODE(ip_addr,SSH_IP_TYPE_IPV4,bytes,4,NULL)
+#define SSH_IP4_DECODE(ip_addr,bytes) \
+ __SSH_IP_MASK_DECODE(ip_addr,SSH_IP_TYPE_IPV4,bytes,4,32)
+
+/* IPv6 address manipulation */
+#define SSH_IP6_ENCODE(ip_addr,bytes) \
+ __SSH_IP_MASK_ENCODE(ip_addr,SSH_IP_TYPE_IPV6,bytes,16,NULL)
+
+#if !defined(WITH_IPV6)
+#define SSH_IP6_DECODE(ip_addr,bytes) SSH_IP_UNDEFINE(ip_addr)
+#else /* WITH_IPV6 */
+#define SSH_IP6_DECODE(ip_addr,bytes) \
+ __SSH_IP_MASK_DECODE(ip_addr,SSH_IP_TYPE_IPV6,bytes,16,128)
+#endif /* !WITH_IPV6 */
+
+#define SSH_IP_MASK_LEN(ip_addr) ((ip_addr)->mask_len)
+
+#if defined(WITH_IPV6)
+#define SSH_IP6_SCOPE_ID(ip_addr) ((ip_addr)->scope_id.scope_id_union.ui32)
+#endif /* WITH_IPV6 */
+
+/*********************** Definitions for IPv4 packets ***********************/
+
+
+/* IPv4 header lengths. */
+#define SSH_IPH4_HDRLEN 20
+#define SSH_IPH4_HLEN(ucp) SSH_GET_4BIT_LOW(ucp)
+#define SSH_IPH4_LEN(ucp) SSH_GET_16BIT((ucp) + 2)
+#define SSH_IPH4_SRC(ipaddr, ucp) SSH_IP4_DECODE((ipaddr), (ucp) + 12)
+
+/*********************** Definitions for IPv6 packets ***********************/
+
+/* IPv6 header length. Extension headers are not counted in IPv6
+ header */
+#define SSH_IPH6_HDRLEN 40
+
+#define SSH_IPH6_OFS_LEN 4
+#define SSH_IPH6_OFS_NH 6
+
+#define SSH_IPH6_ADDRLEN 16
+#define SSH_IPH6_OFS_SRC 8
+
+#define SSH_IP6_WORD0_TO_INT(ip_addr) SSH_GET_32BIT((ip_addr)->addr_data)
+#define SSH_IP6_WORD1_TO_INT(ip_addr) SSH_GET_32BIT((ip_addr)->addr_data + 4)
+#define SSH_IP6_WORD2_TO_INT(ip_addr) SSH_GET_32BIT((ip_addr)->addr_data + 8)
+#define SSH_IP6_WORD3_TO_INT(ip_addr) SSH_GET_32BIT((ip_addr)->addr_data + 12)
+
+#define SSH_IPH6_LEN(ucp) SSH_GET_16BIT((ucp) + SSH_IPH6_OFS_LEN)
+#define SSH_IPH6_NH(ucp) SSH_GET_8BIT((ucp) + SSH_IPH6_OFS_NH)
+#define SSH_IPH6_SRC(ipaddr, ucp) SSH_IP6_DECODE((ipaddr), \
+ (ucp) + SSH_IPH6_OFS_SRC)
+
+/****************** Definitions for IPv6 extension headers ******************/
+
+#define SSH_IP6_EXT_COMMON_NH(ucp) SSH_GET_8BIT((ucp))
+#define SSH_IP6_EXT_COMMON_LEN(ucp) SSH_GET_8BIT((ucp) + 1)
+#define SSH_IP6_EXT_COMMON_LENB(ucp) \
+ ((SSH_IP6_EXT_COMMON_LEN((ucp)) + 1) << 3)
+
+/*************************** Link definitions *******************************/
+
+/* Reserved value for invalid interface index. */
+#define SSH_INVALID_IFNUM 0xffffffff
+
+/***************************** Helper functions *****************************/
+
+/* Sets all rightmost bits after keeping `keep_bits' bits on the left
+ to the value specified by `value'. */
+void ssh_ipaddr_set_bits(SshIpAddr result, SshIpAddr ip,
+ unsigned int keep_bits, unsigned int value);
+
+/* Prints the IP address into the buffer in string format. If the buffer
+ is too short, the address is truncated. This returns `buf'. */
+unsigned char *ssh_ipaddr_print(const SshIpAddr ip, unsigned char *buf,
+ size_t buflen);
+
+/* Prints the IP address into the buffer in string format. If the buffer
+ is too short, the address is truncated. This returns `buf'. */
+void ssh_ipaddr_ipv4_print(const unsigned char *data,
+ unsigned char *buf, size_t buflen);
+void ssh_ipaddr_ipv6_print(const unsigned char *data,
+ unsigned char *buf, size_t buflen,
+ SshUInt32 scope);
+
+#endif /* SSHINET_H */
diff --git a/drivers/interceptor/sshinetbits.c b/drivers/interceptor/sshinetbits.c
new file mode 100644
index 0000000..6a77ef8
--- /dev/null
+++ b/drivers/interceptor/sshinetbits.c
@@ -0,0 +1,45 @@
+/* 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.
+ */
+
+/*
+ * sshinetbits.c
+ *
+ * Implementation of inet API IP address bit manipulation functions.
+ *
+ */
+
+#include "sshincludes.h"
+#include "sshinet.h"
+
+/* Sets all rightmost bits after keeping `keep_bits' bits on the left to
+ the value specified by `value'. */
+
+void ssh_ipaddr_set_bits(SshIpAddr result, SshIpAddr ip,
+ unsigned int keep_bits, unsigned int value)
+{
+ size_t len;
+ unsigned int i;
+
+ len = SSH_IP_IS6(ip) ? 16 : 4;
+
+ *result = *ip;
+ for (i = keep_bits / 8; i < len; i++)
+ {
+ if (8 * i >= keep_bits)
+ result->addr_data[i] = value ? 0xff : 0;
+ else
+ {
+ SSH_ASSERT(keep_bits - 8 * i < 8);
+ result->addr_data[i] &= (0xff << (8 - (keep_bits - 8 * i)));
+ if (value)
+ result->addr_data[i] |= (0xff >> (keep_bits - 8 * i));
+ }
+ }
+}
diff --git a/drivers/interceptor/sshinetencode.c b/drivers/interceptor/sshinetencode.c
new file mode 100644
index 0000000..35b406f
--- /dev/null
+++ b/drivers/interceptor/sshinetencode.c
@@ -0,0 +1,130 @@
+/* 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.
+ */
+
+/*
+ * sshinetencode.c
+ *
+ * Implementation of inet API IP address encoding and decoding functions.
+ *
+ */
+
+#include "sshincludes.h"
+#include "sshencode.h"
+#include "sshinetencode.h"
+
+size_t ssh_encode_ipaddr_array(unsigned char *buf, size_t bufsize,
+ const SshIpAddr ip)
+{
+ if (!ip || ip->type == SSH_IP_TYPE_NONE)
+ return ssh_encode_array(buf, bufsize,
+ SSH_ENCODE_CHAR(SSH_IP_TYPE_NONE),
+ SSH_FORMAT_END);
+ return ssh_encode_array(buf, bufsize,
+ SSH_ENCODE_CHAR(ip->type),
+ SSH_ENCODE_UINT32(ip->mask_len),
+ SSH_ENCODE_DATA(ip->addr_data,
+ SSH_IP_ADDR_LEN(ip)),
+#ifdef WITH_IPV6
+ SSH_ENCODE_UINT32(ip->scope_id.scope_id_union.ui32),
+#endif /* WITH_IPV6 */
+ SSH_FORMAT_END);
+}
+
+size_t ssh_encode_ipaddr_array_alloc(unsigned char **buf_return,
+ const SshIpAddr ip)
+{
+ size_t req, got;
+
+ if (ip->type == SSH_IP_TYPE_NONE)
+ req = 1;
+ else
+#ifdef WITH_IPV6
+ req = 1 + 8 + SSH_IP_ADDR_LEN(ip);
+#else /* WITH_IPV6 */
+ req = 1 + 4 + SSH_IP_ADDR_LEN(ip);
+#endif /* WITH_IPV6 */
+
+ if (buf_return == NULL)
+ return req;
+
+ if ((*buf_return = ssh_malloc(req)) == NULL)
+ return 0;
+
+ got = ssh_encode_ipaddr_array(*buf_return, req, ip);
+
+ if (got != req)
+ {
+ ssh_free(*buf_return);
+ *buf_return = NULL;
+ return 0;
+ }
+
+ return got;
+}
+
+int ssh_decode_ipaddr_array(const unsigned char *buf, size_t len,
+ void * ipaddr)
+{
+ size_t point, got;
+ SshUInt32 mask_len;
+#ifdef WITH_IPV6
+ SshUInt32 scope_id;
+#endif /* WITH_IPV6 */
+ unsigned int type;
+ SshIpAddr ip = (SshIpAddr)ipaddr;
+ point = 0;
+
+ if ((got = ssh_decode_array(buf + point, len - point,
+ SSH_DECODE_CHAR(&type),
+ SSH_FORMAT_END)) != 1)
+ return 0;
+
+ /* Make sure scope-id (that is not present at the kernel) is
+ zeroed */
+ memset(ip, 0, sizeof(*ip));
+
+ ip->type = (SshUInt8) type;
+
+ point += got;
+
+ if (ip->type == SSH_IP_TYPE_NONE)
+ return point;
+
+ if ((got = ssh_decode_array(buf + point, len - point,
+ SSH_DECODE_UINT32(&mask_len),
+ SSH_DECODE_DATA(ip->addr_data,
+ SSH_IP_ADDR_LEN(ip)),
+#ifdef WITH_IPV6
+ SSH_DECODE_UINT32(&scope_id),
+ SSH_FORMAT_END)) != ((2 * sizeof(SshUInt32))
+ + SSH_IP_ADDR_LEN(ip)))
+#else /* WITH_IPV6 */
+ SSH_FORMAT_END)) != (4 + SSH_IP_ADDR_LEN(ip)))
+#endif /* WITH_IPV6 */
+ return 0;
+
+ /* Sanity check */
+ if (mask_len > 255)
+ return 0;
+
+ ip->mask_len = (SshUInt8) mask_len;
+
+ point += got;
+
+#ifdef WITH_IPV6
+ ip->scope_id.scope_id_union.ui32 = scope_id;
+#endif /* WITH_IPV6 */
+
+ /* Sanity check */
+ if (!SSH_IP_IS4(ip) && !SSH_IP_IS6(ip))
+ return 0;
+
+ return point;
+}
diff --git a/drivers/interceptor/sshinetencode.h b/drivers/interceptor/sshinetencode.h
new file mode 100644
index 0000000..0c453ed
--- /dev/null
+++ b/drivers/interceptor/sshinetencode.h
@@ -0,0 +1,43 @@
+/* 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.
+ */
+
+/*
+ * sshinetencode.h
+ *
+ * Inet API: IP address encoding and decoding functions.
+ *
+ */
+
+#ifndef SSHINETENCODE_H
+#define SSHINETENCODE_H
+
+#include "sshinet.h"
+
+/* Decode IP-address from array. */
+int ssh_decode_ipaddr_array(const unsigned char *buf, size_t bufsize,
+ void *ip);
+
+/* Encode IP-address to array. Return 0 in case it does not fit to the buffer.
+ NOTE, this is NOT a SshEncodeDatum Encoder, as the return values are
+ different. */
+size_t ssh_encode_ipaddr_array(unsigned char *buf, size_t bufsize,
+ const SshIpAddr ip);
+size_t ssh_encode_ipaddr_array_alloc(unsigned char **buf_return,
+ const SshIpAddr ip);
+
+#ifdef WITH_IPV6
+/* type+mask+scopeid+content */
+#define SSH_MAX_IPADDR_ENCODED_LENGTH (1+4+4+16)
+#else /* WITH_IPV6 */
+/* type+mask+content */
+#define SSH_MAX_IPADDR_ENCODED_LENGTH (1+4+16)
+#endif /* WITH_IPV6 */
+
+#endif /* SSHINETENCODE_H */
diff --git a/drivers/interceptor/sshinetprint.c b/drivers/interceptor/sshinetprint.c
new file mode 100644
index 0000000..76bd877
--- /dev/null
+++ b/drivers/interceptor/sshinetprint.c
@@ -0,0 +1,136 @@
+/* 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.
+ */
+
+/*
+ * sshinetprint.c
+ *
+ * Implementation of inet API IP address printing functions.
+ *
+ */
+
+#include "sshincludes.h"
+#include "sshinet.h"
+
+/* Prints the IP address into the buffer in string format. If the buffer
+ is too short, the address is truncated. This returns `buf'. */
+
+void ssh_ipaddr_ipv4_print(const unsigned char *data,
+ unsigned char *buf, size_t buflen)
+{
+ snprintf(buf, buflen, "%d.%d.%d.%d", data[0], data[1], data[2], data[3]);
+}
+
+void ssh_ipaddr_ipv6_print(const unsigned char *data,
+ unsigned char *buf, size_t buflen,
+ SshUInt32 scope)
+{
+ int i, j;
+ unsigned char *cp;
+ int opt_start = 8;
+ int opt_len = 0, n, l;
+ SshUInt16 val;
+
+ /* Optimize the largest zero-block from the address. */
+ for (i = 0; i < 8; i++)
+ if (SSH_GET_16BIT(data + i * 2) == 0)
+ {
+ for (j = i + 1; j < 8; j++)
+ {
+ val = SSH_GET_16BIT(data + j * 2);
+ if (val != 0)
+ break;
+ }
+ if (j - i > opt_len)
+ {
+ opt_start = i;
+ opt_len = j - i;
+ }
+ i = j;
+ }
+
+ if (opt_len <= 1)
+ /* Disable optimization. */
+ opt_start = 8;
+
+ cp = buf;
+ n = buflen;
+
+ /* Format the result. */
+ for (i = 0; i < 8; i++)
+ {
+ if (i == opt_start)
+ {
+ if (i == 0)
+ {
+ *cp++ = ':';
+ n -= 1;
+ if (n <= 1)
+ break;
+ }
+
+ *cp++ = ':';
+ n -= 1;
+ if (n <= 1)
+ break;
+
+ i += opt_len - 1;
+ }
+ else
+ {
+ l = snprintf(cp, n, "%x",
+ (unsigned int) SSH_GET_16BIT(data + i * 2));
+ if (l == -1)
+ {
+ *cp = '\0';
+ return;
+ }
+
+ cp += l;
+ n -= l;
+ if (n <= 1)
+ break;
+
+ if (i + 1 < 8)
+ {
+ *cp = ':';
+ cp++;
+ n -= 1;
+ if (n <= 1)
+ break;
+ }
+ }
+ }
+
+ /* Put scope id there, if stored. */
+ if (scope != 0)
+ {
+ l = snprintf(cp, n, "%%%u", (unsigned int) scope);
+
+ if (l > 0)
+ cp += l;
+ }
+
+ *cp = '\0';
+}
+
+unsigned char *ssh_ipaddr_print(const SshIpAddr ip, unsigned char *buf,
+ size_t buflen)
+{
+ if (SSH_IP_IS4(ip))
+ ssh_ipaddr_ipv4_print(ip->addr_data, buf, buflen);
+#if defined(WITH_IPV6)
+ else if (SSH_IP_IS6(ip))
+ ssh_ipaddr_ipv6_print(ip->addr_data, buf, buflen, SSH_IP6_SCOPE_ID(ip));
+#endif /* WITH_IPV6 */
+ else if (buflen > 0)
+ buf[0] = '\0';
+
+ return buf;
+}
diff --git a/drivers/interceptor/usermodeforwarder.c b/drivers/interceptor/usermodeforwarder.c
new file mode 100644
index 0000000..0122302
--- /dev/null
+++ b/drivers/interceptor/usermodeforwarder.c
@@ -0,0 +1,1363 @@
+/* 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.
+ */
+
+/*
+ * usermodeforwarder.c
+ *
+ * Stub implementation of Engine API. The usermodeforwarder forwards packets
+ * and requests between the kernel space Interceptor and user space Engine
+ * components.
+ *
+ */
+#include "sshincludes.h"
+#include "interceptor.h"
+#include "engine.h"
+#include "sshencode.h"
+#include "kernel_mutex.h"
+#include "usermodeforwarder.h"
+#include "sshinetencode.h"
+#include "virtual_adapter.h"
+
+#define SSH_ENGINE_VERSION "User-Mode Forwarder 1.0"
+
+const char ssh_engine_version[] = SSH_ENGINE_VERSION;
+
+/* Suffix to add to the name of the device name used for communicating
+ with the kernel module in systems that have such a concept. This
+ is ignored on other systems. */
+const char ssh_device_suffix[] = "-usermode";
+
+/* Data structure for the user mode forwarder engine. */
+struct SshEngineRec
+{
+ /* Lock for the engine. */
+ SshKernelMutex lock;
+
+ /* Function and context for sending packets to the user mode code. */
+ SshEngineSendProc send;
+ void *machine_context;
+
+ /* Flag indicating that packets should be dropped if the user mode
+ code is not connected. Otherwise packets will be passed through in this
+ situation. */
+ Boolean drop_if_no_ipm;
+
+ /* Flag indicating whether the user mode connection is currently open. */
+ Boolean ipm_open;
+
+ /* Packet interceptor. */
+ SshInterceptor interceptor;
+
+ /* Saved interfaces message (to be sent when ipm opens). */
+ unsigned char *queued_interfaces_message;
+ size_t queued_interfaces_len;
+
+ /* List of registered control message handlers */
+ struct SshEngineControlHandlerRec * control_handlers;
+ size_t control_handlers_num;
+};
+
+/* Formats the message, and tries to send it to the policy manager. This
+ returns FALSE if sending the message fails (e.g., the queue is full).
+ Every argument list should start with SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR, type. The first integer will be set to the length
+ of the resulting packet. This function can be called concurrently. */
+Boolean ssh_engine_send(SshEngine engine, Boolean locked,
+ Boolean reliable, ...)
+{
+ va_list ap;
+ unsigned char *ucp;
+ size_t len;
+
+ if (!locked)
+ ssh_kernel_mutex_lock(engine->lock);
+
+ if (!engine->ipm_open)
+ {
+ if (!locked)
+ ssh_kernel_mutex_unlock(engine->lock);
+ return FALSE;
+ }
+ if (!locked)
+ ssh_kernel_mutex_unlock(engine->lock);
+
+ /* Construct the final packet to send to ipm. */
+ va_start(ap, reliable);
+ len = ssh_encode_array_alloc_va(&ucp, ap);
+ va_end(ap);
+ SSH_ASSERT(len >= 5); /* must have at least len+type */
+
+ /* Update the length of the packet. */
+ SSH_PUT_32BIT(ucp, len - 4);
+
+ /* Send and/or queue the packet to the ipm. This will free the buffer. */
+ return (*engine->send)(ucp, len, reliable, engine->machine_context);
+}
+
+/* Callback function called by the real interceptor whenever a packet
+ is received. This passes the packet to the user mode
+ interceptor. */
+
+void ssh_engine_packet_callback(SshInterceptorPacket pp, void *context)
+{
+ SshEngine engine = (SshEngine) context;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ unsigned char extbuf[4 * SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS];
+ SshUInt32 i;
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ unsigned char *packetbuf, *internalbuf;
+ size_t packet_len, internal_len, mediahdr_len;
+
+ /* Check if the user mode connection is open. */
+ ssh_kernel_mutex_lock(engine->lock);
+ if (!engine->ipm_open)
+ {
+ /* The user-mode connection is not open. Either
+ drop the packet or pass it through. */
+ ssh_kernel_mutex_unlock(engine->lock);
+ if (engine->drop_if_no_ipm)
+ ssh_interceptor_packet_free(pp);
+ else
+ {
+ /* Determine media header length. */
+ if (pp->protocol == SSH_PROTOCOL_ETHERNET)
+ mediahdr_len = SSH_ETHERH_HDRLEN;
+ else
+ if (pp->protocol == SSH_PROTOCOL_FDDI ||
+ pp->protocol == SSH_PROTOCOL_TOKENRING)
+ mediahdr_len = 22;
+ else
+ mediahdr_len = 0;
+
+ /* Send it through. */
+ /* Set 'pp->ifnum_out' to the inbound interface 'pp->ifnum_in'. */
+ pp->ifnum_out = pp->ifnum_in;
+
+ ssh_interceptor_send(engine->interceptor, pp, mediahdr_len);
+ }
+ return;
+ }
+ ssh_kernel_mutex_unlock(engine->lock);
+
+ /* Format the extension selectors into a buffer. */
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ for (i = 0; i < SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS; i++)
+ {
+ SSH_PUT_32BIT(extbuf + 4 * i, pp->extension[i]);
+ }
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Copy the packet into a linear buffer. This is not fast, but is easy. */
+ packet_len = ssh_interceptor_packet_len(pp);
+ if (!(packetbuf = ssh_malloc(packet_len)))
+ {
+ ssh_interceptor_packet_free(pp);
+ return;
+ }
+
+ ssh_interceptor_packet_copyout(pp, 0, packetbuf, packet_len);
+
+ if (!ssh_interceptor_packet_export_internal_data(pp, &internalbuf,
+ &internal_len))
+ {
+ ssh_free(packetbuf);
+ return;
+ }
+
+
+ /* Send a packet to the user mode code. */
+ ssh_engine_send(engine, FALSE, FALSE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_PACKET,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->flags,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->ifnum_in,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->ifnum_out,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->protocol,
+ SSH_FORMAT_UINT32_STR, packetbuf, packet_len,
+ SSH_FORMAT_UINT32_STR, internalbuf, internal_len,
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ SSH_FORMAT_DATA, extbuf, sizeof(extbuf),
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ SSH_FORMAT_END);
+
+ /* Free the buffers. */
+ ssh_free(packetbuf);
+ ssh_free(internalbuf);
+ ssh_interceptor_packet_free(pp);
+}
+
+/* This function is called whenever the interface list changes. */
+void ssh_engine_interfaces_callback(SshUInt32 num_interfaces,
+ SshInterceptorInterface *ifs,
+ void *context)
+{
+ SshEngine engine = (SshEngine) context;
+ unsigned char * packet, * packet_new;
+ size_t len, packet_len;
+ unsigned char *ucp;
+ SshUInt32 i, k;
+
+ /* Prepare the packet to send. Loop over all interfaces and create data
+ for each. */
+ packet = NULL;
+ packet_len = 0;
+ for (i = 0; i < num_interfaces; i++)
+ {
+ /* Complete the data for the interface. */
+ if (ifs[i].to_adapter.media == SSH_INTERCEPTOR_MEDIA_NONEXISTENT)
+ len = ssh_encode_array_alloc(
+ &ucp,
+
+ SSH_FORMAT_UINT32,
+ (SshUInt32) SSH_INTERCEPTOR_MEDIA_NONEXISTENT,
+
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+#ifdef WITH_IPV6
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+#endif /* WITH_IPV6 */
+
+ SSH_FORMAT_UINT32,
+ (SshUInt32) SSH_INTERCEPTOR_MEDIA_NONEXISTENT,
+
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+#ifdef WITH_IPV6
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+#endif /* WITH_IPV6 */
+
+ SSH_FORMAT_UINT32_STR, "", (size_t)0L,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].ifnum,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].flags,
+ SSH_FORMAT_UINT32_STR, "", (size_t)0L,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_END);
+ else
+ len = ssh_encode_array_alloc(
+ &ucp,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_protocol.media,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_protocol.flags,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_protocol.mtu_ipv4,
+#ifdef WITH_IPV6
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_protocol.mtu_ipv6,
+#endif /* WITH_IPV6 */
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_adapter.media,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_adapter.flags,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_adapter.mtu_ipv4,
+#ifdef WITH_IPV6
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].to_adapter.mtu_ipv6,
+#endif /* WITH_IPV6 */
+
+ SSH_FORMAT_UINT32_STR,
+ ifs[i].media_addr, ifs[i].media_addr_len,
+
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].ifnum,
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].flags,
+ SSH_FORMAT_UINT32_STR, ifs[i].name, strlen(ifs[i].name),
+ SSH_FORMAT_UINT32, (SshUInt32) ifs[i].num_addrs,
+ SSH_FORMAT_END);
+
+ if (!ucp)
+ {
+ ssh_free(packet);
+ return;
+ }
+
+ packet_new = ssh_realloc(packet, packet_len, packet_len + len);
+
+ if (!packet_new)
+ {
+ ssh_free(packet);
+ ssh_free(ucp);
+ return;
+ }
+
+ packet = packet_new;
+
+ memcpy(packet + packet_len, ucp, len);
+ packet_len += len;
+
+ ssh_free(ucp);
+
+ for (k = 0; k < ifs[i].num_addrs; k++)
+ {
+ unsigned char *addr;
+ size_t addr_size;
+
+ if (ifs[i].addrs[k].protocol == SSH_PROTOCOL_IP4 ||
+ ifs[i].addrs[k].protocol == SSH_PROTOCOL_IP6)
+ {
+ unsigned char *ip, *mask, *bcast;
+ size_t ip_size, mask_size, bcast_size;
+
+ ip_size =
+ ssh_encode_ipaddr_array_alloc(&ip,
+ &ifs[i].addrs[k].addr.ip.ip);
+
+ mask_size =
+ ssh_encode_ipaddr_array_alloc(&mask,
+ &ifs[i].addrs[k].addr.ip.mask);
+
+ bcast_size =
+ ssh_encode_ipaddr_array_alloc(&bcast,
+ &ifs[i].addrs[k].addr.ip.broadcast);
+
+ /* Out of memory */
+ if (!ip_size || !mask_size || !bcast_size)
+ {
+ failure:
+ ssh_free(ip);
+ ssh_free(mask);
+ ssh_free(bcast);
+ ssh_free(packet);
+ return;
+ }
+
+ addr_size = ssh_encode_array_alloc(&addr,
+ SSH_FORMAT_UINT32_STR,
+ ip, ip_size,
+ SSH_FORMAT_UINT32_STR,
+ mask, mask_size,
+ SSH_FORMAT_UINT32_STR,
+ bcast, bcast_size,
+ SSH_FORMAT_END);
+
+ if (!addr_size)
+ goto failure;
+
+ ssh_free(ip);
+ ssh_free(mask);
+ ssh_free(bcast);
+ }
+ else
+ {
+ SSH_DEBUG(SSH_D_ERROR,
+ ("ifs[i].addrs[%d].protocol == %d is not supported",
+ (int) k, ifs[i].addrs[k].protocol));
+
+ addr = ssh_strdup("");
+
+ if (!addr)
+ {
+ ssh_free(packet);
+ return;
+ }
+
+ addr_size = 0;
+ }
+
+ len = ssh_encode_array_alloc(&ucp,
+ SSH_FORMAT_UINT32,
+ ifs[i].addrs[k].protocol,
+ SSH_FORMAT_UINT32_STR, addr, addr_size,
+ SSH_FORMAT_END);
+
+ ssh_free(addr);
+
+ if (!ucp)
+ {
+ ssh_free(packet);
+ return;
+ }
+
+ packet_new = ssh_realloc(packet, packet_len, packet_len + len);
+
+ if (!packet_new)
+ {
+ ssh_free(packet);
+ ssh_free(ucp);
+ return;
+ }
+
+ packet = packet_new;
+ memcpy(packet + packet_len, ucp, len);
+ packet_len += len;
+
+ ssh_free(ucp);
+ }
+ }
+
+ /* Send the interfaces packet. */
+ len = ssh_encode_array_alloc(&ucp,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_INTERFACES,
+ SSH_FORMAT_UINT32, num_interfaces,
+ SSH_FORMAT_DATA, packet, packet_len,
+ SSH_FORMAT_END);
+
+ ssh_free(packet);
+
+ if (!ucp)
+ return;
+
+ /* Save the interfaces message so that we can send it again when the
+ ipm is next opened. */
+ ssh_kernel_mutex_lock(engine->lock);
+ if (engine->queued_interfaces_message)
+ ssh_free(engine->queued_interfaces_message);
+ engine->queued_interfaces_message = ucp;
+ engine->queued_interfaces_len = len;
+ ssh_kernel_mutex_unlock(engine->lock);
+
+ /* Send the message now (assuming the user mode connection is open). */
+ ssh_engine_send(engine, FALSE, TRUE, SSH_FORMAT_DATA, ucp, len,
+ SSH_FORMAT_END);
+
+ /* ucp is not freed here, since it is stored in queued_interfaces_message */
+}
+
+/* Function that is called whenever routing information changes. There
+ is no guarantee that this ever gets called. */
+void ssh_engine_route_change_callback(void *context)
+{
+ SshEngine engine = (SshEngine) context;
+
+ /* Send a simple notification. */
+ ssh_engine_send(engine, FALSE, FALSE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_ROUTECHANGE,
+ SSH_FORMAT_END);
+}
+
+/* Creates the engine object. Among other things, this opens the
+ interceptor, initializes filters to default values, and arranges to send
+ messages to the policy manager using the send procedure. The send
+ procedure will not be called until from the bottom of the event loop.
+ The `machine_context' argument is passed to the interceptor and the
+ `send' callback, but is not used otherwise. This function can be
+ called concurrently for different machine contexts, but not otherwise.
+ The first packet and interface callbacks may arrive before this has
+ returned. */
+SshEngine ssh_engine_start(SshEngineSendProc send,
+ void *machine_context,
+ SshUInt32 flags)
+{
+ SshEngine engine;
+
+ engine = ssh_calloc(1, sizeof(*engine));
+ if (engine == NULL)
+ {
+ SSH_DEBUG(SSH_D_FAIL, ("failed to allocate engine object"));
+ goto fail;
+ }
+
+ /* Transform data pointers are already all zero (assumed to equal NULL). */
+ /* Fragment magic data initialized to zero. */
+ engine->lock = ssh_kernel_mutex_alloc();
+ engine->send = send;
+ engine->machine_context = machine_context;
+ engine->drop_if_no_ipm = (flags & SSH_ENGINE_DROP_IF_NO_IPM) != 0;
+ engine->ipm_open = FALSE;
+ engine->interceptor = NULL;
+
+ /* Open the interceptor. */
+ if (!ssh_interceptor_open(machine_context,
+ ssh_engine_packet_callback,
+ ssh_engine_interfaces_callback,
+ ssh_engine_route_change_callback,
+ (void *) engine,
+ &engine->interceptor))
+ {
+ SSH_DEBUG(1, ("opening the real interceptor failed"));
+ goto fail;
+ }
+
+ SSH_DEBUG(1, ("SSH forwarder engine started"));
+ return engine;
+
+ fail:
+ if (engine != NULL)
+ {
+ if (engine->interceptor)
+ ssh_interceptor_close(engine->interceptor);
+ ssh_kernel_mutex_free(engine->lock);
+ ssh_free(engine);
+ }
+ return NULL;
+}
+
+/* Stops the engine, closes the interceptor, and destroys the
+ engine object. This does not notify IPM interface of the close;
+ that must be done by the caller before calling this. This returns
+ TRUE if the engine was successfully stopped (and the object freed),
+ and FALSE if the engine cannot yet be freed because there are
+ threads inside the engine or uncancellable callbacks expected to
+ arrive. When this returns FALSE, the engine has started stopping,
+ and this should be called again after a while. This function can
+ be called concurrently with packet/interface callbacks or timeouts
+ for this engine, or any functions for other engines.*/
+
+Boolean ssh_engine_stop(SshEngine engine)
+{
+ /* Stop the interceptor. This means that no more new callbacks will
+ arrive. */
+ if (!ssh_interceptor_stop(engine->interceptor))
+ return FALSE;
+
+ /* Close the packet interceptor. */
+ ssh_interceptor_close(engine->interceptor);
+
+ /* Free the engine data structures. */
+ ssh_free(engine->control_handlers);
+ ssh_free(engine->queued_interfaces_message);
+ ssh_kernel_mutex_free(engine->lock);
+ memset(engine, 'F', sizeof(*engine));
+ ssh_free(engine);
+ return TRUE;
+}
+
+/* The machine-specific main program should call this when the policy
+ manager has opened the connection to the engine. This also
+ sends the version packet to the policy manager. This function can
+ be called concurrently with packet/interface callbacks or timeouts. */
+
+void ssh_engine_notify_ipm_open(SshEngine engine)
+{
+ SSH_DEBUG(1, ("User level module opened connection."));
+
+ /* Update state information about the policy manager connection. */
+ ssh_kernel_mutex_lock(engine->lock);
+ SSH_ASSERT(!engine->ipm_open);
+ engine->ipm_open = TRUE;
+ ssh_kernel_mutex_unlock(engine->lock);
+
+ /* Send a version packet to the policy manager. */
+ ssh_engine_send(engine, FALSE, TRUE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_VERSION,
+ SSH_FORMAT_UINT32_STR,
+ SSH_ENGINE_VERSION, strlen(SSH_ENGINE_VERSION),
+ SSH_FORMAT_END);
+
+ /* If there is a saved interfaces message, send it now. */
+ if (engine->queued_interfaces_message)
+ {
+ ssh_engine_send(engine, FALSE, TRUE,
+ SSH_FORMAT_DATA,
+ engine->queued_interfaces_message,
+ engine->queued_interfaces_len,
+ SSH_FORMAT_END);
+
+ /* queued_interfaces_message is not freed, as it either will be
+ freed on _stop or next interfaces callback */
+ }
+}
+
+/* This function is called whenever the policy manager closes the
+ connection to the engine. This is also called when the engine is
+ stopped. This function can be called concurrently with
+ packet/interface callbacks or timeouts. */
+
+void ssh_engine_notify_ipm_close(SshEngine engine)
+{
+ SSH_DEBUG(1, ("User level module closed connection."));
+
+ /* Lock the engine. */
+ ssh_kernel_mutex_lock(engine->lock);
+
+ /* Mark the policy interface not open. */
+ engine->ipm_open = FALSE;
+
+ /* Unlock the engine. */
+ ssh_kernel_mutex_unlock(engine->lock);
+}
+
+/* Context structure for route lookups in the kernel. */
+
+typedef struct SshEngineFromIpmRouteRec
+{
+ SshEngine engine;
+ SshUInt32 id;
+} *SshEngineFromIpmRoute;
+
+/* Callback function to be called when a route lookup completes. This sends
+ a response to the user-mode interceptor. */
+
+void ssh_engine_route_completion(Boolean reachable,
+ SshIpAddr next_hop_gw,
+ SshInterceptorIfnum ifnum,
+ size_t mtu,
+ void *context)
+{
+ SshEngineFromIpmRoute rr = (SshEngineFromIpmRoute) context;
+ unsigned char *buf;
+ size_t len;
+ SshIpAddrStruct ip;
+#ifdef DEBUG_LIGHT
+ unsigned char next_hop_buf[SSH_IP_ADDR_STRING_SIZE];
+#endif /* DEBUG_LIGHT */
+
+ buf = NULL;
+ len = 0;
+
+ if (next_hop_gw)
+ {
+ len = ssh_encode_ipaddr_array_alloc(&buf, next_hop_gw);
+ }
+ else
+ {
+ SSH_IP_UNDEFINE(&ip);
+ len = ssh_encode_ipaddr_array_alloc(&buf, &ip);
+ }
+
+ if (reachable)
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("sending route reply id=%d reachable=%d ifnum=%d mtu=%d "
+ "next_hop=%s",
+ (int) rr->id, reachable, (int) ifnum, (int) mtu,
+ (next_hop_gw != NULL ?
+ ssh_ipaddr_print(next_hop_gw, next_hop_buf,
+ sizeof(next_hop_buf)) : NULL)));
+ else
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("sending route reply id=%d not reachable", (int) rr->id));
+
+ ssh_engine_send(rr->engine, FALSE, TRUE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_ROUTEREPLY,
+ SSH_FORMAT_UINT32, (SshUInt32) rr->id,
+ SSH_FORMAT_UINT32, (SshUInt32) reachable,
+ SSH_FORMAT_UINT32, (SshUInt32) ifnum,
+ SSH_FORMAT_UINT32, (SshUInt32) mtu,
+ SSH_FORMAT_UINT32_STR, buf, len,
+ SSH_FORMAT_END);
+
+ ssh_free(buf);
+ ssh_free(rr);
+}
+
+/* Processes a route lookup message received from the user-mode
+ interceptor. */
+
+void ssh_engine_from_ipm_route(SshEngine engine,
+ const unsigned char *data, size_t len)
+{
+ SshEngineFromIpmRoute rr;
+ SshUInt32 id;
+ SshInterceptorRouteKeyStruct key;
+ unsigned char *dst_ptr, *src_ptr;
+ SshUInt32 ipproto, ifnum, selector;
+ size_t dst_len, src_len;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ unsigned char extbuf[4 * SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS];
+ SshUInt32 i;
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+#ifdef DEBUG_LIGHT
+ unsigned char dst_buf[SSH_IP_ADDR_STRING_SIZE];
+#endif /* DEBUG_LIGHT */
+
+ /* Decode the packet. */
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &id,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &dst_ptr, &dst_len,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &src_ptr, &src_len,
+ SSH_FORMAT_UINT32, &ipproto,
+ SSH_FORMAT_UINT32, &ifnum,
+ SSH_FORMAT_DATA, key.nh.raw, sizeof(key.nh.raw),
+ SSH_FORMAT_DATA, key.th.raw, sizeof(key.th.raw),
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ SSH_FORMAT_DATA, extbuf, sizeof(extbuf),
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ SSH_FORMAT_UINT32, &selector,
+ SSH_FORMAT_END) != len || !dst_ptr)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("bad ipm route packet len=%d", (int) len),
+ data, len);
+ return;
+ }
+
+ /* Copy addresses to the structure. */
+ if (!ssh_decode_ipaddr_array(dst_ptr, dst_len, &key.dst))
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("bad ipaddr encoding"), dst_ptr, dst_len);
+ return;
+ }
+ if (!ssh_decode_ipaddr_array(src_ptr, src_len, &key.src))
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("bad ipaddr encoding"), src_ptr, src_len);
+ return;
+ }
+
+ /* Set ipproto, ifnum, and selector */
+ key.ipproto = ipproto;
+ key.ifnum = (SshInterceptorIfnum) ifnum;
+ key.selector = (SshUInt16) selector;
+
+ /* Copy extension selectors */
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ for (i = 0; i < SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS; i++)
+ {
+ key.extension[i] = SSH_GET_32BIT(extbuf + 4 * i);
+ }
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ SSH_DEBUG(SSH_D_NICETOKNOW,
+ ("route request to %s (id %d)",
+ ssh_ipaddr_print(&key.dst, dst_buf, sizeof(dst_buf)), (int) id));
+
+ /* Allocate and initialize a context structure. */
+ rr = ssh_calloc(1, sizeof(*rr));
+ rr->engine = engine;
+ rr->id = id;
+ ssh_interceptor_route(engine->interceptor, &key,
+ ssh_engine_route_completion, (void *)rr);
+}
+
+/* Processes a packet to send received from the user-mode interceptor. */
+
+void ssh_engine_from_ipm_packet(SshEngine engine,
+ const unsigned char *data, size_t len)
+{
+ SshInterceptorPacket pp;
+ SshUInt32 flags, ifnum_in, ifnum_out, protocol, media_header_len;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ SshUInt32 extensions[SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS];
+ SshUInt32 i;
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ unsigned char *packet_ptr, *internal_ptr;
+ size_t packet_len, internal_len, bytes;
+
+ /* Decode the packet. */
+ bytes = ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &flags,
+ SSH_FORMAT_UINT32, &ifnum_in,
+ SSH_FORMAT_UINT32, &ifnum_out,
+ SSH_FORMAT_UINT32, &protocol,
+ SSH_FORMAT_UINT32, &media_header_len,
+ SSH_FORMAT_UINT32_STR_NOCOPY,
+ &packet_ptr, &packet_len,
+ SSH_FORMAT_UINT32_STR_NOCOPY,
+ &internal_ptr, &internal_len,
+ SSH_FORMAT_END);
+ if (bytes == 0)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR, ("bad ipm_packet fixed part"), data, len);
+ return;
+ }
+ data += bytes;
+ len -= bytes;
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ for (i = 0; i < SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS; i++)
+ {
+ bytes = ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &extensions[i],
+ SSH_FORMAT_END);
+ if (bytes == 0)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("bad extension selector in ipm_packet"),
+ data, len);
+ return;
+ }
+ data += bytes;
+ len -= bytes;
+ }
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+ if (len != 0)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR, ("garbage at end of ipm_packet"),
+ data, len);
+ return;
+ }
+
+ /* Assert that interface numbers fit into SshInterceptorIfnum. */
+ SSH_ASSERT(((SshUInt32)ifnum_in) <= ((SshUInt32)SSH_INTERCEPTOR_MAX_IFNUM));
+ SSH_ASSERT(((SshUInt32)ifnum_out) <= ((SshUInt32)SSH_INTERCEPTOR_MAX_IFNUM));
+
+ /* Allocate a packet object and copy data into it. */
+ flags &= SSH_PACKET_FROMADAPTER | SSH_PACKET_FROMPROTOCOL;
+ pp = ssh_interceptor_packet_alloc(engine->interceptor,
+ flags, protocol,
+ ifnum_in, ifnum_out,
+ packet_len);
+ if (pp == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("failed to allocate packet"));
+ return;
+ }
+ if (!ssh_interceptor_packet_copyin(pp, 0, packet_ptr, packet_len))
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("copyin failed, dropping packet"));
+ return;
+ }
+ if (!ssh_interceptor_packet_import_internal_data(pp,
+ internal_ptr, internal_len))
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("internal import failed, dropping packet"));
+ return;
+ }
+
+#if (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0)
+ for (i = 0; i < SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS; i++)
+ {
+ pp->extension[i] = extensions[i];
+ }
+#endif /* (SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS > 0) */
+
+ /* Send the packet out. */
+ ssh_interceptor_send(engine->interceptor, pp, media_header_len);
+}
+
+/* Process enable interception request */
+void ssh_engine_from_ipm_enable_interception(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ size_t bytes;
+ SshUInt32 enable;
+
+ bytes = ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &enable,
+ SSH_FORMAT_END);
+ if (bytes == 0)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR, ("bad ipm_enable_interception packet"),
+ data, len);
+ return;
+ }
+
+ SSH_DEBUG(SSH_D_LOWOK, ("%s packet interception",
+ (enable == 0 ? "Disabling" : "Enabling")));
+
+ if (enable == 0)
+ ssh_interceptor_enable_interception(engine->interceptor, FALSE);
+ else
+ ssh_interceptor_enable_interception(engine->interceptor, TRUE);
+}
+
+void ssh_engine_from_ipm_set_debug(SshEngine engine,
+ const unsigned char *data, size_t len)
+{
+ unsigned char *s;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &s, NULL,
+ SSH_FORMAT_END) != len)
+ return;
+
+ SSH_DEBUG(SSH_D_NICETOKNOW, ("Setting debug level to \"%s\"", s));
+}
+
+
+void ssh_engine_from_ipm_internal_data_discarded(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ unsigned char *data_ptr;
+ size_t data_len, bytes;
+
+ /* Decode the packet. */
+ bytes = ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32_STR_NOCOPY,
+ &data_ptr, &data_len,
+ SSH_FORMAT_END);
+ if (bytes == 0)
+ {
+ SSH_DEBUG_HEXDUMP(0, ("bad ipm_packet fixed part"), data, len);
+ return;
+ }
+ data += bytes;
+ len -= bytes;
+
+ if (len != 0)
+ {
+ SSH_DEBUG_HEXDUMP(0, ("garbage at end of ipm_packet"), data, len);
+ return;
+ }
+
+ ssh_interceptor_packet_discard_internal_data(data_ptr, data_len);
+}
+
+/***************************** Virtual adapter things ************************/
+typedef struct SshEngineIpmVirtualAdapterOpCtxRec
+{
+ SshEngine engine;
+ SshUInt32 operation_id;
+ Boolean dynamic;
+} *SshEngineIpmVirtualAdapterOpCtx, SshEngineIpmVirtualAdapterOpCtxStruct;
+
+void
+ssh_engine_ipm_virtual_adapter_packet_cb(SshInterceptor interceptor,
+ SshInterceptorPacket pp,
+ void *adapter_context)
+{
+ SshEngine engine = adapter_context;
+ unsigned char *packet, *internal;
+ size_t packet_len, internal_len;
+
+ /* Copy the packet into a linear buffer. */
+ packet_len = ssh_interceptor_packet_len(pp);
+ packet = ssh_malloc(packet_len);
+ if (packet == NULL)
+ {
+ ssh_interceptor_packet_free(pp);
+ return;
+ }
+
+#ifdef INTERCEPTOR_HAS_PACKET_INTERNAL_DATA_ROUTINES
+ if (!ssh_interceptor_packet_export_internal_data(pp,
+ &internal, &internal_len))
+ {
+ ssh_free(packet);
+ return;
+ }
+#endif /* INTERCEPTOR_HAS_PACKET_INTERNAL_DATA_ROUTINES */
+
+ ssh_interceptor_packet_copyout(pp, 0, packet, packet_len);
+
+ /* Send the packet to the user-mode engine. */
+ ssh_engine_send(engine, FALSE, FALSE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0, /* reserved for length */
+
+ SSH_FORMAT_CHAR,
+ (unsigned int) SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_PACKET_CB,
+
+ SSH_FORMAT_UINT32, (SshUInt32) pp->flags,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->ifnum_in,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->ifnum_out,
+ SSH_FORMAT_UINT32, (SshUInt32) pp->protocol,
+ SSH_FORMAT_UINT32_STR, packet, packet_len,
+ SSH_FORMAT_UINT32_STR, internal, internal_len,
+
+ SSH_FORMAT_END);
+
+ /* Free the temporary buffer. */
+ ssh_free(packet);
+
+ /* Free internal data representation */
+ ssh_free(internal);
+
+ /* Free the interceptor packet. */
+ ssh_interceptor_packet_free(pp);
+}
+
+void
+ssh_engine_ipm_virtual_adapter_status_cb(SshVirtualAdapterError error,
+ SshInterceptorIfnum adapter_ifnum,
+ const unsigned char *adapter_name,
+ SshVirtualAdapterState adapter_state,
+ void *adapter_context,
+ void *context)
+{
+ SshEngineIpmVirtualAdapterOpCtx ctx =
+ (SshEngineIpmVirtualAdapterOpCtx) context;
+ size_t adapter_name_len = 0;
+
+ if (adapter_name != NULL)
+ adapter_name_len = strlen(adapter_name);
+
+ ssh_engine_send(ctx->engine, FALSE, TRUE,
+ SSH_FORMAT_UINT32, (SshUInt32) 0,
+ SSH_FORMAT_CHAR, (unsigned int)
+ SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_STATUS_CB,
+ SSH_FORMAT_UINT32, ctx->operation_id,
+ SSH_FORMAT_UINT32, error,
+ SSH_FORMAT_UINT32, adapter_ifnum,
+ SSH_FORMAT_UINT32_STR, adapter_name, adapter_name_len,
+ SSH_FORMAT_UINT32, adapter_state,
+ SSH_FORMAT_END);
+
+ /* Free the context if it is dynamically allocated and no more callbacks
+ are expected. */
+ if (error != SSH_VIRTUAL_ADAPTER_ERROR_OK_MORE && ctx->dynamic)
+ ssh_free(ctx);
+}
+
+void
+ssh_engine_from_ipm_virtual_adapter_send(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ SshUInt32 ifnum_in, ifnum_out;
+ SshUInt32 protocol;
+ const unsigned char *packet, *internal;
+ size_t packet_len, internal_len;
+ SshInterceptorPacket pp;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &ifnum_in,
+ SSH_FORMAT_UINT32, &ifnum_out,
+ SSH_FORMAT_UINT32, &protocol,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &packet, &packet_len,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &internal, &internal_len,
+ SSH_FORMAT_END) != len)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("Bad virtual adapter send request from PM"),
+ data, len);
+ return;
+ }
+
+ /* Allocate an interceptor packet. */
+ pp = ssh_interceptor_packet_alloc(engine->interceptor,
+ SSH_PACKET_FROMADAPTER,
+ protocol,
+ ifnum_in,
+ ifnum_out,
+ packet_len);
+ if (pp == NULL)
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("failed to allocate packet"));
+ return;
+ }
+
+ if (!ssh_interceptor_packet_copyin(pp, 0, packet, packet_len))
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("copyin failed, dropping packet"));
+ return;
+ }
+
+ if (!ssh_interceptor_packet_import_internal_data(pp, internal, internal_len))
+ {
+ SSH_DEBUG(SSH_D_ERROR, ("internal import failed, dropping packet"));
+ return;
+ }
+
+ ssh_virtual_adapter_send(engine->interceptor, pp);
+}
+
+void
+ssh_engine_from_ipm_virtual_adapter_attach(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ SshUInt32 operation_id;
+ SshUInt32 adapter_ifnum;
+ SshEngineIpmVirtualAdapterOpCtx ctx;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &operation_id,
+ SSH_FORMAT_UINT32, &adapter_ifnum,
+ SSH_FORMAT_END) != len)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("Bad virtual adapter attach request from PM"),
+ data, len);
+ return;
+ }
+
+ /* Attach virtual adapter. */
+ ctx = ssh_calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ {
+ SshEngineIpmVirtualAdapterOpCtxStruct ctx_struct;
+
+ ctx_struct.engine = engine;
+ ctx_struct.operation_id = operation_id;
+ ctx_struct.dynamic = FALSE;
+
+ ssh_engine_ipm_virtual_adapter_status_cb(
+ SSH_VIRTUAL_ADAPTER_ERROR_OUT_OF_MEMORY,
+ (SshInterceptorIfnum) adapter_ifnum,
+ NULL,
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED,
+ NULL, &ctx_struct);
+ return;
+ }
+
+ ctx->engine = engine;
+ ctx->operation_id = operation_id;
+ ctx->dynamic = TRUE;
+
+ ssh_virtual_adapter_attach(engine->interceptor,
+ (SshInterceptorIfnum) adapter_ifnum,
+ ssh_engine_ipm_virtual_adapter_packet_cb,
+ NULL_FNPTR,
+ engine,
+ ssh_engine_ipm_virtual_adapter_status_cb, ctx);
+}
+
+
+void
+ssh_engine_from_ipm_virtual_adapter_detach(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ SshUInt32 operation_id;
+ SshUInt32 adapter_ifnum;
+ SshEngineIpmVirtualAdapterOpCtx ctx;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &operation_id,
+ SSH_FORMAT_UINT32, &adapter_ifnum,
+ SSH_FORMAT_END) != len)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("Bad virtual adapter detach request from PM"),
+ data, len);
+ return;
+ }
+
+ /* Detach virtual adapter. */
+ ctx = ssh_calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ {
+ SshEngineIpmVirtualAdapterOpCtxStruct ctx_struct;
+
+ ctx_struct.engine = engine;
+ ctx_struct.operation_id = operation_id;
+ ctx_struct.dynamic = FALSE;
+
+ ssh_engine_ipm_virtual_adapter_status_cb(
+ SSH_VIRTUAL_ADAPTER_ERROR_OUT_OF_MEMORY,
+ (SshInterceptorIfnum) adapter_ifnum,
+ NULL,
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED,
+ NULL, &ctx_struct);
+ return;
+ }
+
+ ctx->engine = engine;
+ ctx->operation_id = operation_id;
+ ctx->dynamic = TRUE;
+
+ ssh_virtual_adapter_detach(engine->interceptor,
+ (SshInterceptorIfnum) adapter_ifnum,
+ ssh_engine_ipm_virtual_adapter_status_cb, ctx);
+}
+
+
+void
+ssh_engine_from_ipm_virtual_adapter_detach_all(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ ssh_virtual_adapter_detach_all(engine->interceptor);
+}
+
+
+void
+ssh_engine_from_ipm_virtual_adapter_configure(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ SshUInt32 operation_id;
+ SshUInt32 adapter_ifnum, adapter_state;
+ SshUInt32 num_addresses = 0;
+ SshIpAddrStruct addresses[16] = { { 0 } };
+ SshVirtualAdapterParamsStruct p;
+ const unsigned char *ip_ptr, *param_ptr;
+ size_t ip_len, param_len;
+ size_t decode_len;
+ SshUInt32 i;
+ SshEngineIpmVirtualAdapterOpCtxStruct ctx_struct;
+ SshEngineIpmVirtualAdapterOpCtx ctx;
+ SshVirtualAdapterError error = SSH_VIRTUAL_ADAPTER_ERROR_UNKNOWN_ERROR;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &operation_id,
+ SSH_FORMAT_UINT32, &adapter_ifnum,
+ SSH_FORMAT_UINT32, &adapter_state,
+ SSH_FORMAT_UINT32, &num_addresses,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &ip_ptr, &ip_len,
+ SSH_FORMAT_UINT32_STR_NOCOPY, &param_ptr, &param_len,
+ SSH_FORMAT_END) != len)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("Bad virtual adapter configure request from PM"),
+ data, len);
+ return;
+ }
+
+ /* Decode IP addresses. */
+ if (ip_len)
+ {
+ for (i = 0; i < num_addresses && i < 16; i++)
+ {
+ decode_len = ssh_decode_ipaddr_array(ip_ptr, ip_len, &addresses[i]);
+ if (decode_len == 0)
+ {
+ error = SSH_VIRTUAL_ADAPTER_ERROR_ADDRESS_FAILURE;
+ goto error;
+ }
+ ip_ptr += decode_len;
+ ip_len -= decode_len;
+ }
+ }
+
+ /* A single undefined address "means clear all addresses". */
+ if (num_addresses == 1 && !SSH_IP_DEFINED(&addresses[0]))
+ num_addresses = 0;
+
+ /* Decode params. */
+ memset(&p, 0, sizeof(p));
+ if (param_len)
+ {
+ if (!ssh_virtual_adapter_param_decode(&p, param_ptr, param_len))
+ goto error;
+ }
+
+ /* Create context. */
+ ctx = ssh_calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ {
+ error = SSH_VIRTUAL_ADAPTER_ERROR_OUT_OF_MEMORY;
+ goto error;
+ }
+
+ ctx->engine = engine;
+ ctx->operation_id = operation_id;
+ ctx->dynamic = TRUE;
+
+ /* This interceptor does not implement kernel level
+ virtual adapter configure. */
+ ssh_engine_ipm_virtual_adapter_status_cb(
+ SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT,
+ (SshInterceptorIfnum) adapter_ifnum,
+ NULL,
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED,
+ NULL, ctx);
+ return;
+
+ /* Error handling. */
+ error:
+ ctx_struct.engine = engine;
+ ctx_struct.operation_id = operation_id;
+ ctx_struct.dynamic = FALSE;
+
+ ssh_engine_ipm_virtual_adapter_status_cb(error,
+ (SshInterceptorIfnum) adapter_ifnum,
+ NULL,
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED,
+ NULL, &ctx_struct);
+}
+
+
+void
+ssh_engine_from_ipm_virtual_adapter_get_status(SshEngine engine,
+ const unsigned char *data,
+ size_t len)
+{
+ SshUInt32 operation_id;
+ SshUInt32 adapter_ifnum;
+ SshEngineIpmVirtualAdapterOpCtx ctx;
+
+ if (ssh_decode_array(data, len,
+ SSH_FORMAT_UINT32, &operation_id,
+ SSH_FORMAT_UINT32, &adapter_ifnum,
+ SSH_FORMAT_END) != len)
+ {
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("Bad virtual adapter get status request from PM"),
+ data, len);
+ return;
+ }
+
+ /* Get virtual adapter status. */
+ ctx = ssh_calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ {
+ SshEngineIpmVirtualAdapterOpCtxStruct ctx_struct;
+
+ ctx_struct.engine = engine;
+ ctx_struct.operation_id = operation_id;
+ ctx_struct.dynamic = FALSE;
+
+ ssh_engine_ipm_virtual_adapter_status_cb(
+ SSH_VIRTUAL_ADAPTER_ERROR_OUT_OF_MEMORY,
+ (SshInterceptorIfnum) adapter_ifnum,
+ NULL,
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED,
+ NULL, &ctx_struct);
+ return;
+ }
+
+ ctx->engine = engine;
+ ctx->operation_id = operation_id;
+ ctx->dynamic = TRUE;
+
+ ssh_virtual_adapter_get_status(engine->interceptor,
+ (SshInterceptorIfnum) adapter_ifnum,
+ ssh_engine_ipm_virtual_adapter_status_cb,
+ ctx);
+}
+
+
+/***************************** Packet Multiplexing ***************************/
+
+/* This function should be called by the machine-dependent main
+ program whenever a packet for this engine is received from
+ the policy manager. The data should not contain the 32-bit length
+ or the type (they have already been processed at this stage, to
+ check for possible machine-specific packets). The `data' argument
+ remains valid until this function returns; it should not be freed
+ by this function. This function can be called concurrently. */
+
+void ssh_engine_packet_from_ipm(SshEngine engine,
+ SshUInt32 type,
+ const unsigned char *data, size_t len)
+{
+ switch (type)
+ {
+ case SSH_ENGINE_IPM_FORWARDER_PACKET:
+ ssh_engine_from_ipm_packet(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_INTERNAL_DATA_DISCARDED:
+ ssh_engine_from_ipm_internal_data_discarded(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_ROUTEREQ:
+ ssh_engine_from_ipm_route(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_ENABLE_INTERCEPTION:
+ ssh_engine_from_ipm_enable_interception(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_SEND:
+ ssh_engine_from_ipm_virtual_adapter_send(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_ATTACH:
+ ssh_engine_from_ipm_virtual_adapter_attach(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_DETACH:
+ ssh_engine_from_ipm_virtual_adapter_detach(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_DETACH_ALL:
+ ssh_engine_from_ipm_virtual_adapter_detach_all(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_CONFIGURE:
+ ssh_engine_from_ipm_virtual_adapter_configure(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_GET_STATUS:
+ ssh_engine_from_ipm_virtual_adapter_get_status(engine, data, len);
+ break;
+
+ case SSH_ENGINE_IPM_FORWARDER_SET_DEBUG:
+ ssh_engine_from_ipm_set_debug(engine, data, len);
+ break;
+
+ default:
+ printk(KERN_EMERG "Unknown packet type %u", (unsigned int)type);
+ SSH_DEBUG(2, ("ssh_engine_packet_from_ipm: unexpected packet %u in "
+ "kernel; probably wrong policy manager",
+ (unsigned int) type));
+
+ SSH_DEBUG_HEXDUMP(SSH_D_ERROR,
+ ("invalid packet from engine, type=%u",
+ (unsigned int) type),
+ data, len);
+ break;
+ }
+}
diff --git a/drivers/interceptor/usermodeforwarder.h b/drivers/interceptor/usermodeforwarder.h
new file mode 100644
index 0000000..5df0a71
--- /dev/null
+++ b/drivers/interceptor/usermodeforwarder.h
@@ -0,0 +1,90 @@
+/* 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.
+ */
+
+/*
+ * usermodeforwarder.h
+ *
+ * Message types for kernel to userspace messaging.
+ *
+ */
+
+#ifndef USERMODEFORWARDER_H
+#define USERMODEFORWARDER_H
+
+/* Allocate message numbers from the platform-specific portion. */
+
+/** Received packet or packet to be sent.
+ - uint32 flags
+ - uint32 ifnum
+ - uint32 protocol
+ - uint32 media_header_len (0 for packets going up)
+ - uint16 route_selector (0 in media level interceptor builds)
+ - string packet data
+ - uint32 extension
+ repeats SSH_INTERCEPTOR_NUM_EXTENSION_SELECTORS times. */
+#define SSH_ENGINE_IPM_FORWARDER_PACKET 201
+
+/** Routing request from user mode.
+ - string destination
+ - uint32 request id */
+#define SSH_ENGINE_IPM_FORWARDER_ROUTEREQ 202
+
+/** Routing reply from kernel.
+ - uint32 id
+ - uint32 reachable
+ - uint32 ifnum
+ - uint32 mtu
+ - string next_hop_gw */
+#define SSH_ENGINE_IPM_FORWARDER_ROUTEREPLY 203
+
+/** Interfaces information from kernel:
+ - uint32 num_interfaces.
+
+ Repeats:
+ - uint32 media
+ - uint32 mtu
+ - string name
+ - string media_addr
+ - uint32 num_addrs
+ - string addrs array as binary data */
+#define SSH_ENGINE_IPM_FORWARDER_INTERFACES 204
+
+/** Route change notification. No data. */
+#define SSH_ENGINE_IPM_FORWARDER_ROUTECHANGE 205
+
+/** Kernel version string. */
+#define SSH_ENGINE_IPM_FORWARDER_VERSION 206
+
+#define SSH_ENGINE_IPM_FORWARDER_SET_DEBUG 208
+
+/** Enable / disable packet interception:
+ - uint32 enable (1 to enable, 0 to disable) */
+#define SSH_ENGINE_IPM_FORWARDER_ENABLE_INTERCEPTION 215
+
+#define SSH_ENGINE_IPM_FORWARDER_INTERNAL_DATA_DISCARDED 216
+
+/** Send a packet to local stack. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_SEND 220
+/** Attach a virtual adapter to Engine. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_ATTACH 221
+/** Detach a virtual adapter from Engine. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_DETACH 222
+/** Detach all virtual adapters from Engine. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_DETACH_ALL 223
+/** Configure virtual adapter. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_CONFIGURE 224
+/** Enumerate virtual adapters. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_GET_STATUS 225
+/** Virtual adapter status callback. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_STATUS_CB 226
+/** Virtual adapter packet callback. */
+#define SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_PACKET_CB 227
+
+#endif /* USERMODEFORWARDER_H */
diff --git a/drivers/interceptor/virtual_adapter.h b/drivers/interceptor/virtual_adapter.h
new file mode 100644
index 0000000..628b04a
--- /dev/null
+++ b/drivers/interceptor/virtual_adapter.h
@@ -0,0 +1,296 @@
+/* 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.
+ */
+
+/*
+ *
+ * Kernel-mode virtual adapter interface.
+ *
+ * File: virtual_adapter.h
+ *
+ */
+
+
+#ifndef VIRTUAL_ADAPTER_H
+#define VIRTUAL_ADAPTER_H
+
+#include "interceptor.h"
+
+/* ***************************** Data Types **********************************/
+
+/** Error codes for virtual adapter operations. */
+typedef enum {
+ /** Success */
+ SSH_VIRTUAL_ADAPTER_ERROR_OK = 0,
+ /** Success, status callback will be called again */
+ SSH_VIRTUAL_ADAPTER_ERROR_OK_MORE = 1,
+ /** Nonexistent adapter */
+ SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT = 2,
+ /** Address configuration error */
+ SSH_VIRTUAL_ADAPTER_ERROR_ADDRESS_FAILURE = 3,
+ /** Route configuration error */
+ SSH_VIRTUAL_ADAPTER_ERROR_ROUTE_FAILURE = 4,
+ /** Parameter configuration error */
+ SSH_VIRTUAL_ADAPTER_ERROR_PARAM_FAILURE = 5,
+ /** Memory allocation error */
+ SSH_VIRTUAL_ADAPTER_ERROR_OUT_OF_MEMORY = 6,
+ /** Undefined internal error */
+ SSH_VIRTUAL_ADAPTER_ERROR_UNKNOWN_ERROR = 255
+} SshVirtualAdapterError;
+
+/** Virtual adapter state. */
+typedef enum {
+ /** Invalid value */
+ SSH_VIRTUAL_ADAPTER_STATE_UNDEFINED = 0,
+ /** Up */
+ SSH_VIRTUAL_ADAPTER_STATE_UP = 1,
+ /** Down */
+ SSH_VIRTUAL_ADAPTER_STATE_DOWN = 2,
+ /** Keep existing state */
+ SSH_VIRTUAL_ADAPTER_STATE_KEEP_OLD = 3,
+} SshVirtualAdapterState;
+
+/** Optional parameters for a virtual adapter. */
+struct SshVirtualAdapterParamsRec
+{
+ /** Virtual adapter mtu. */
+ SshUInt32 mtu;
+
+ /** DNS server IP addresses. */
+ SshUInt32 dns_ip_count;
+ SshIpAddr dns_ip;
+
+ /** WINS server IP addresses. */
+ SshUInt32 wins_ip_count;
+ SshIpAddr wins_ip;
+
+ /** Windows domain name. */
+ char *win_domain;
+
+ /** Netbios node type. */
+ SshUInt8 netbios_node_type;
+};
+
+typedef struct SshVirtualAdapterParamsRec SshVirtualAdapterParamsStruct;
+typedef struct SshVirtualAdapterParamsRec *SshVirtualAdapterParams;
+
+/* ******************* Sending and Receiving Packets *************************/
+
+/** Send packet `pp' to the IP stack like it was received by the
+ virtual adapter indexed by `pp->ifnum_in'. The packet `pp' must be a
+ plain IP packet (IPv4 or IPv6) or it must be of the same media type
+ as the virtual adapter (most probably ethernet). This will free the
+ packet `pp'.
+
+ NOTE: LOCKS MUST NOT BE HELD DURING CALLS TO ssh_virtual_adapter_receive()!
+*/
+void ssh_virtual_adapter_send(SshInterceptor interceptor,
+ SshInterceptorPacket pp);
+
+/** Callback function of this type is called whenever a packet is
+ sent out via a virtual adapter. This function must eventually free the
+ packet by calling ssh_interceptor_packet_free.
+
+ This function is used for handling only those ARP, neighbor discovery,
+ and DHCP packets that are not handled by the generic engine packet
+ callback. This function must not be used for passing normal
+ IP packets.
+
+ Note that this function may be called asynchronously. */
+typedef void (*SshVirtualAdapterPacketCB)(SshInterceptor interceptor,
+ SshInterceptorPacket pp,
+ void *adapter_context);
+
+/* **************************** Generic Callbacks ****************************/
+
+/** A callback function of this type is called to notify about the
+ success of a virtual adapter operation.
+
+ - The argument `error' describes whether the operation was successful or
+ not. If `error' indicates success, the arguments `adapter_ifnum',
+ `adapter_name', and `state' contain information about the virtual adapter.
+
+ - The argument `adapter_ifnum' is an unique interface identifier for the
+ virtual adapter. You can use it configuring additional routes for
+ the adapter, and for configuring and destroying the adapter.
+
+ - The argument `adapter_name' contains the name of the created adapter.
+ The returned name is the same that identifies the interface in the
+ interceptor's interface callback.
+
+ - The argument `adapter_context' is the context attached to the virtual
+ adapter in the call to ssh_virtual_adapter_attach.
+
+ Arguments `adapter_ifnum', `adapter_name', `adapter_state', and
+ `adapter_context' are valid only for the duration of the callback. */
+typedef void (*SshVirtualAdapterStatusCB)(SshVirtualAdapterError error,
+ SshInterceptorIfnum adapter_ifnum,
+ const unsigned char *adapter_name,
+ SshVirtualAdapterState adapter_state,
+ void *adapter_context,
+ void *context);
+
+/* **************************** Attach and Detach ****************************/
+
+/** A callback function of this type is called to notify about detachment
+ of a virtual adapter from the engine. The callback is meant only as a
+ destructor of the virtual adapter context, and thus should be called
+ whenever there is no possibility of `packet_cb' being called again.
+ The argument `adapter_context' is the context attached to the virtual
+ adapter in the call to ssh_virtual_adapter_attach. */
+typedef void (*SshVirtualAdapterDetachCB)(void *adapter_context);
+
+/** Attaches the packet callback and context to a virtual adapter.
+
+ - The argument `packet_cb' specifies a callback function that is
+ called when a packet is received from the virtual adapter. The argument
+ `adapter_context' is the context for this callback.
+
+ - The optional argument `detach_cb' specifies an engine-level
+ destructor for `adapter_context'. It must be called when the
+ interceptor is done with virtual adapter, i.e. when the `packet_cb'
+ will not be called for this adapter again. Even in case of instant
+ failure, the `detach_cb' must be called.
+
+ - The operation completion callback `callback' must be called to notify
+ about success or failure of the attachment. In failure cases `callback'
+ must be called after `detach_cb'. The argument `context' is the context
+ data for this callback.
+
+ NOTE: LOCKS MUST NOT BE HELD DURING CALLS TO ssh_virtual_adapter_attach()!
+*/
+void ssh_virtual_adapter_attach(SshInterceptor interceptor,
+ SshInterceptorIfnum adapter_ifnum,
+ SshVirtualAdapterPacketCB packet_cb,
+ SshVirtualAdapterDetachCB detach_cb,
+ void *adapter_context,
+ SshVirtualAdapterStatusCB callback,
+ void *context);
+
+/** Detach the virtual adapter `adapter_ifnum'. The success of the
+ operation is notified by calling the callback function `callback'.
+
+ The detach operation will fail with the error code
+ SSH_VIRTUAL_ADAPTER_NONEXISTENT if the virtual adapter was never
+ attached. Detaching a virtual adapter must generate an interface callback
+ for the interceptor.
+
+ If a detach callback was specified in ssh_virtual_adapter_attach, then
+ this `detach_cb' must be called before calling the operation completion
+ callback `callback'.
+
+ NOTE: LOCKS MUST NOT BE HELD DURING CALLS TO ssh_virtual_adapter_detach()!
+*/
+void ssh_virtual_adapter_detach(SshInterceptor interceptor,
+ SshInterceptorIfnum adapter_ifnum,
+ SshVirtualAdapterStatusCB callback,
+ void *context);
+
+/** Detach all configured virtual adapters. This is called when the engine
+ is stopped during interceptor is unloading. This function must call
+ `detach_cb' for each detached virtual adapter that defines `detach_cb'.
+
+ NOTE: LOCKS MUST NOT BE HELD DURING CALLS TO
+ ssh_virtual_adapter_detach_all()! */
+void ssh_virtual_adapter_detach_all(SshInterceptor interceptor);
+
+/* *************************** Configuration *********************************/
+
+#ifdef INTERCEPTOR_IMPLEMENTS_VIRTUAL_ADAPTER_CONFIGURE
+
+/** Configures the virtual adapter `adapter_ifnum' with `state', `addresses',
+ and `params'. The argument `adapter_ifnum' must be the valid interface
+ index of a virtual adapter that has been attached to the engine during
+ pm connect.
+
+ The argument `adapter_state' specifies the state to configure for the
+ virtual adapter.
+
+ The arguments `num_addresses' and `addresses' specify the IP addresses
+ for the virtual adapter. The addresses must specify the netmask. If
+ `addresses' is NULL, the address configuration will not be changed.
+ Otherwise the existing addresses will be removed from the virtual adapter
+ and specified addresses will be added. To clear all addresses from the
+ virtual adapter, specify `addresses' as non-NULL and `num_addresses' as 0.
+
+ The argument `params' specifies optional parameters for the virtual
+ adapter. If `params' is non-NULL, then the existing params will be cleared
+ and the specified params will be set for the virtual adapter.
+
+ Any configured interface addresses must survive interface state changes.
+
+ If the define INTERCEPTOR_IMPLEMENTS_VIRTUAL_ADAPTER_CONFIGURE is not
+ defined then the interceptor does not support kernel level virtual
+ adapter configure.
+*/
+void ssh_virtual_adapter_configure(SshInterceptor interceptor,
+ SshInterceptorIfnum adapter_ifnum,
+ SshVirtualAdapterState adapter_state,
+ SshUInt32 num_addresses,
+ SshIpAddr addresses,
+ SshVirtualAdapterParams params,
+ SshVirtualAdapterStatusCB callback,
+ void *context);
+
+#endif /* INTERCEPTOR_IMPLEMENTS_VIRTUAL_ADAPTER_CONFIGURE */
+
+/* *************************** Virtual Adapter Status ************************/
+
+/** This function will call `callback' for virtual adapter `adapter_ifnum'.
+ If `adapter_ifnum' equals SSH_INTERCEPTOR_INVALID_IFNUM, then this
+ function calls `callback' once for each existing virtual adapter with
+ error code SSH_VIRTUAL_ADAPTER_ERROR_OK_MORE, and once with error code
+ SSH_VIRTUAL_ADAPTER_ERROR_NONEXISTENT after enumerating all virtual
+ adapters. */
+void ssh_virtual_adapter_get_status(SshInterceptor interceptor,
+ SshInterceptorIfnum adapter_ifnum,
+ SshVirtualAdapterStatusCB callback,
+ void *context);
+
+/* *************************** Utility functions *****************************/
+
+/** These generic utility functions that are implemented in files
+ virtual_adapter_misc.c and virtual_adapter_util.s, and they use the
+ virtual adapter API described above. */
+
+/** Initializes engine side virtual adapter contexts and attaches the existing
+ virtual adapters to engine. */
+SshVirtualAdapterError ssh_virtual_adapter_init(SshInterceptor interceptor);
+
+/** Unitializes engine side virtual adapter contexts and detaches virtual
+ adapters from the engine. */
+SshVirtualAdapterError ssh_virtual_adapter_uninit(SshInterceptor interceptor);
+
+/** Creates a pseudo Ethernet hardware address for the virtual adapter
+ `adapter_ifnum'. The address is formatted in the buffer `buffer' which
+ must have space for SSH_ETHERH_ADDRLEN bytes. */
+void
+ssh_virtual_adapter_interface_ether_address(SshInterceptorIfnum adapter_ifnum,
+ unsigned char *buffer);
+
+/** Create a pseudo Ethernet hardware address for the IP address `ip'.
+ The address `ip' can be an IPv4 or an IPv6 address. The address if
+ formatted in the buffer `buffer' which must have space for
+ SSH_ETHERH_ADDRLEN bytes. */
+void ssh_virtual_adapter_ip_ether_address(SshIpAddr ip, unsigned char *buffer);
+
+/** Encode virtual adapter params `params' to buffer `buffer'. This
+ function returns TRUE on success. The caller must ssh_free the allocated
+ memory in `data'. */
+Boolean
+ssh_virtual_adapter_param_encode(SshVirtualAdapterParams params,
+ unsigned char **data, size_t *len);
+
+/** Decode virtual adapter params from table `data' into `params'. This
+ function returns TRUE on success. */
+Boolean
+ssh_virtual_adapter_param_decode(SshVirtualAdapterParams params,
+ const unsigned char *data, size_t len);
+
+#endif /* not VIRTUAL_ADAPTER_H */