diff options
author | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-17 17:53:57 +0200 |
---|---|---|
committer | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-18 16:31:59 +0200 |
commit | c28265764ec6ad9995eb0c761a376ffc9f141fcd (patch) | |
tree | 3ad899757480d47deb2be6011509a4243e8e0dc2 /drivers/interceptor | |
parent | 0ddbcb39c0dc0318f68d858f25a96a074142af2f (diff) | |
download | kernel_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')
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(¶ms->mtu), + SSH_DECODE_UINT32(¶ms->dns_ip_count), + SSH_DECODE_UINT32_STR_NOCOPY(&dns, &dns_len), + SSH_DECODE_UINT32(¶ms->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, + ¶ms->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, + ¶ms->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, ¶m_ptr, ¶m_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 */ |