net: rmnet_data: add support for DL MAP based checksum offload

Add DL checksum offload routines for MAPv3. Can bypass checksum software
for IPv4/IPv6 TCP/UDP protocols.

CRs-fixed: 692334
Change-Id: Ic13a5d9a1ebfdc57b6eb53ee93da92c3aee547b1
Signed-off-by: Sivan Reinstein <sivanr@codeaurora.org>
This commit is contained in:
Sivan Reinstein 2014-07-10 16:22:41 +03:00
parent 83dd8ec0cb
commit 0db8fc47f0
6 changed files with 347 additions and 10 deletions

View File

@ -29,6 +29,7 @@
#define RMNET_INGRESS_FORMAT_DEAGGREGATION (1<<2)
#define RMNET_INGRESS_FORMAT_DEMUXING (1<<3)
#define RMNET_INGRESS_FORMAT_MAP_COMMANDS (1<<4)
#define RMNET_INGRESS_FORMAT_MAP_CKSUMV3 (1<<5)
/* ***************** Netlink API ******************************************** */
#define RMNET_NETLINK_PROTO 31

View File

@ -255,6 +255,7 @@ static rx_handler_result_t _rmnet_map_ingress_handler(struct sk_buff *skb,
struct rmnet_logical_ep_conf_s *ep;
uint8_t mux_id;
uint16_t len;
int ckresult;
mux_id = RMNET_MAP_GET_MUX_ID(skb);
len = RMNET_MAP_GET_LENGTH(skb)
@ -281,6 +282,21 @@ static rx_handler_result_t _rmnet_map_ingress_handler(struct sk_buff *skb,
if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEMUXING)
skb->dev = ep->egress_dev;
if (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV3) {
ckresult = rmnet_map_checksum_downlink_packet(skb);
trace_rmnet_map_checksum_downlink_packet(skb, ckresult);
if (likely(ckresult == RMNET_MAP_CHECKSUM_OK))
skb->ip_summed |= CHECKSUM_UNNECESSARY;
else if (ckresult != RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION
&& ckresult != RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT
&& ckresult != RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET
&& ckresult != RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET) {
rmnet_kfree_skb(skb,
RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM);
return RX_HANDLER_CONSUMED;
}
}
/* Subtract MAP header */
skb_pull(skb, sizeof(struct rmnet_map_header_s));
skb_trim(skb, len);

View File

@ -38,6 +38,7 @@ enum rmnet_skb_free_e {
RMNET_STATS_SKBFREE_DEAGG_CLONE_FAIL,
RMNET_STATS_SKBFREE_DEAGG_UNKOWN_IP_TYP,
RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0,
RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM,
RMNET_STATS_SKBFREE_MAX
};

View File

@ -200,6 +200,26 @@ TRACE_EVENT(rmnet_end_deaggregation,
__get_str(name), __entry->num)
)
TRACE_EVENT(rmnet_map_checksum_downlink_packet,
TP_PROTO(struct sk_buff *skb, int ckresult),
TP_ARGS(skb, ckresult),
TP_STRUCT__entry(
__string(name, skb->dev->name)
__field(int, res)
),
TP_fast_assign(
__assign_str(name, skb->dev->name);
__entry->res = ckresult;
),
TP_printk("DL checksum on dev=%s, res: %d",
__get_str(name), __entry->res)
)
#endif /* _RMNET_DATA_TRACE_H_ */
/* This part must be outside protection */

View File

@ -48,6 +48,22 @@ struct rmnet_map_control_command_s {
};
} __aligned(1);
struct rmnet_map_dl_checksum_trailer_s {
unsigned char reserved_h;
#if defined(__LITTLE_ENDIAN_BITFIELD)
unsigned char valid:1;
unsigned char reserved_l:7;
#elif defined(__BIG_ENDIAN_BITFIELD)
unsigned char reserved_l:7;
unsigned char valid:1;
#else
#error "Please fix <asm/byteorder.h>"
#endif
unsigned short checksum_start_offset;
unsigned short checksum_length;
unsigned short checksum_value;
} __aligned(1);
enum rmnet_map_results_e {
RMNET_MAP_SUCCESS,
RMNET_MAP_CONSUMED,
@ -70,11 +86,12 @@ enum rmnet_map_checksum_errors_e {
RMNET_MAP_CHECKSUM_OK,
RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET,
RMNET_MAP_CHECKSUM_VALIDATION_FAILED,
RMNET_MAP_CHECKSUM_ERROR_UNKOWN,
RMNET_MAP_CHECKSUM_ERROR_NOT_DATA_PACKET,
RMNET_MAP_CHECKSUM_ERROR_BAD_BUFFER,
RMNET_MAP_CHECKSUM_ERROR_UNKNOWN_IP_VERSION,
RMNET_MAP_CHECKSUM_ERROR_UNKNOWN_TRANSPORT,
RMNET_MAP_CHECKSUM_ERR_UNKOWN,
RMNET_MAP_CHECKSUM_ERR_NOT_DATA_PACKET,
RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER,
RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION,
RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT,
RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET,
/* This should always be the last element */
RMNET_MAP_CHECKSUM_ENUM_LENGTH
};
@ -93,11 +110,6 @@ enum rmnet_map_agg_state_e {
RMNET_MAP_TXFER_SCHEDULED
};
#define RMNET_MAP_P_ICMP4 0x01
#define RMNET_MAP_P_TCP 0x06
#define RMNET_MAP_P_UDP 0x11
#define RMNET_MAP_P_ICMP6 0x3a
#define RMNET_MAP_COMMAND_REQUEST 0
#define RMNET_MAP_COMMAND_ACK 1
#define RMNET_MAP_COMMAND_UNSUPPORTED 2
@ -114,4 +126,7 @@ rx_handler_result_t rmnet_map_command(struct sk_buff *skb,
void rmnet_map_aggregate(struct sk_buff *skb,
struct rmnet_phys_ep_conf_s *config);
int rmnet_map_checksum_downlink_packet(struct sk_buff *skb);
#endif /* _RMNET_MAP_H_ */

View File

@ -22,6 +22,14 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/net_map.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <net/ip.h>
#include <net/checksum.h>
#include <net/ip6_checksum.h>
#include "rmnet_data_config.h"
#include "rmnet_map.h"
#include "rmnet_data_private.h"
@ -283,3 +291,279 @@ schedule:
return;
}
/* ***************** Checksum Offload ************************************** */
static inline uint16_t *rmnet_map_get_checksum_field(unsigned char protocol,
const void *txporthdr)
{
uint16_t *check = 0;
switch (protocol) {
case IPPROTO_TCP:
check = &(((struct tcphdr *)txporthdr)->check);
break;
case IPPROTO_UDP:
check = &(((struct udphdr *)txporthdr)->check);
break;
default:
check = 0;
break;
}
return check;
}
static inline uint16_t rmnet_map_add_checksums(uint16_t val1, uint16_t val2)
{
int sum = val1+val2;
sum = (((sum&0xFFFF0000)>>16) + sum) & 0x0000FFFF;
return (uint16_t) (sum&0x0000FFFF);
}
static inline uint16_t rmnet_map_subtract_checksums(uint16_t val1,
uint16_t val2)
{
return rmnet_map_add_checksums(val1, ~val2);
}
/**
* rmnet_map_validate_ipv4_packet_checksum() - Validates TCP/UDP checksum
* value for IPv4 packet
* @map_payload: Pointer to the beginning of the map payload
* @cksum_trailer: Pointer to the checksum trailer
*
* Validates the TCP/UDP checksum for the packet using the checksum value
* from the checksum trailer added to the packet.
* The validation formula is the following:
* 1. Performs 1's complement over the checksum value from the trailer
* 2. Computes 1's complement checksum over IPv4 header and subtracts it from
* the value from step 1
* 3. Computes 1's complement checksum over IPv4 pseudo header and adds it to
* the value from step 2
* 4. Subtracts the checksum value from the TCP/UDP header from the value from
* step 3
* 5. Compares the value from step 4 to the checksum value from the TCP/UDP
* header
*
* Fragmentation and tunneling are not supported.
*
* Return: 0 is validation succeeded.
*/
static int rmnet_map_validate_ipv4_packet_checksum(unsigned char *map_payload,
struct rmnet_map_dl_checksum_trailer_s *cksum_trailer)
{
struct iphdr *ip4h;
uint16_t *checksum_field;
void *txporthdr;
uint16_t pseudo_checksum;
uint16_t ip_hdr_checksum;
uint16_t checksum_value;
uint16_t ip_payload_checksum;
uint16_t ip_pseudo_payload_checksum;
uint16_t checksum_value_final;
ip4h = (struct iphdr *) map_payload;
if ((ntohs(ip4h->frag_off) & IP_MF)
|| ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0))
return RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET;
txporthdr = map_payload + ip4h->ihl*4;
checksum_field = rmnet_map_get_checksum_field(ip4h->protocol,
txporthdr);
if (unlikely(!checksum_field))
return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT;
checksum_value = ~ntohs(cksum_trailer->checksum_value);
ip_hdr_checksum = ~ip_fast_csum(ip4h, (int)ip4h->ihl);
ip_payload_checksum = rmnet_map_subtract_checksums(checksum_value,
ip_hdr_checksum);
pseudo_checksum = ~ntohs(csum_tcpudp_magic(ip4h->saddr, ip4h->daddr,
(uint16_t)(ntohs(ip4h->tot_len) - ip4h->ihl*4),
(uint16_t)ip4h->protocol, 0));
ip_pseudo_payload_checksum = rmnet_map_add_checksums(
ip_payload_checksum, pseudo_checksum);
checksum_value_final = ~rmnet_map_subtract_checksums(
ip_pseudo_payload_checksum, ntohs(*checksum_field));
if (unlikely(checksum_value_final == 0)) {
switch (ip4h->protocol) {
case IPPROTO_UDP:
/* RFC 768 */
LOGD("DL4 1's complement rule for UDP checksum 0");
checksum_value_final = ~checksum_value_final;
break;
case IPPROTO_TCP:
if (*checksum_field == 0xFFFF) {
LOGD(
"DL4 Non-RFC compliant TCP checksum found");
checksum_value_final = ~checksum_value_final;
}
break;
}
}
LOGD(
"DL4 cksum: ~HW: %04X, field: %04X, pseudo header: %04X, final: %04X",
~ntohs(cksum_trailer->checksum_value), ntohs(*checksum_field),
pseudo_checksum, checksum_value_final);
if (checksum_value_final == ntohs(*checksum_field))
return RMNET_MAP_CHECKSUM_OK;
else
return RMNET_MAP_CHECKSUM_VALIDATION_FAILED;
}
/**
* rmnet_map_validate_ipv6_packet_checksum() - Validates TCP/UDP checksum
* value for IPv6 packet
* @map_payload: Pointer to the beginning of the map payload
* @cksum_trailer: Pointer to the checksum trailer
*
* Validates the TCP/UDP checksum for the packet using the checksum value
* from the checksum trailer added to the packet.
* The validation formula is the following:
* 1. Performs 1's complement over the checksum value from the trailer
* 2. Computes 1's complement checksum over IPv6 header and subtracts it from
* the value from step 1
* 3. Computes 1's complement checksum over IPv6 pseudo header and adds it to
* the value from step 2
* 4. Subtracts the checksum value from the TCP/UDP header from the value from
* step 3
* 5. Compares the value from step 4 to the checksum value from the TCP/UDP
* header
*
* Fragmentation, extension headers and tunneling are not supported.
*
* Return: 0 is validation succeeded.
*/
static int rmnet_map_validate_ipv6_packet_checksum(unsigned char *map_payload,
struct rmnet_map_dl_checksum_trailer_s *cksum_trailer)
{
struct ipv6hdr *ip6h;
uint16_t *checksum_field;
void *txporthdr;
uint16_t pseudo_checksum;
uint16_t ip_hdr_checksum;
uint16_t checksum_value;
uint16_t ip_payload_checksum;
uint16_t ip_pseudo_payload_checksum;
uint16_t checksum_value_final;
uint32_t length;
ip6h = (struct ipv6hdr *) map_payload;
txporthdr = map_payload + sizeof(struct ipv6hdr);
checksum_field = rmnet_map_get_checksum_field(ip6h->nexthdr,
txporthdr);
if (unlikely(!checksum_field))
return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT;
checksum_value = ~ntohs(cksum_trailer->checksum_value);
ip_hdr_checksum = ~ntohs(ip_compute_csum(ip6h,
(int)(txporthdr - (void *)map_payload)));
ip_payload_checksum = rmnet_map_subtract_checksums(checksum_value,
ip_hdr_checksum);
length = (ip6h->nexthdr == IPPROTO_UDP) ?
ntohs(((struct udphdr *)txporthdr)->len) :
ntohs(ip6h->payload_len);
pseudo_checksum = ~ntohs(csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
length, ip6h->nexthdr, 0));
ip_pseudo_payload_checksum = rmnet_map_add_checksums(
ip_payload_checksum, pseudo_checksum);
checksum_value_final = ~rmnet_map_subtract_checksums(
ip_pseudo_payload_checksum, ntohs(*checksum_field));
if (unlikely(checksum_value_final == 0)) {
switch (ip6h->nexthdr) {
case IPPROTO_UDP:
/* RFC 2460 section 8.1 */
LOGD("DL6 One's complement rule for UDP checksum 0");
checksum_value_final = ~checksum_value_final;
break;
case IPPROTO_TCP:
if (*checksum_field == 0xFFFF) {
LOGD(
"DL6 Non-RFC compliant TCP checksum found");
checksum_value_final = ~checksum_value_final;
}
break;
}
}
LOGD(
"DL6 cksum: ~HW: %04X, field: %04X, pseudo header: %04X, final: %04X",
~ntohs(cksum_trailer->checksum_value), ntohs(*checksum_field),
pseudo_checksum, checksum_value_final);
if (checksum_value_final == ntohs(*checksum_field))
return RMNET_MAP_CHECKSUM_OK;
else
return RMNET_MAP_CHECKSUM_VALIDATION_FAILED;
}
/**
* rmnet_map_checksum_downlink_packet() - Validates checksum on
* a downlink packet
* @skb: Pointer to the packet's skb.
*
* Validates packet checksums. Function takes a pointer to
* the beginning of a buffer which contains the entire MAP
* frame: MAP header + IP payload + padding + checksum trailer.
* Currently, only IPv4 and IPv6 are supported along with
* TCP & UDP. Fragmented or tunneled packets are not supported.
*
* Return:
* - RMNET_MAP_CHECKSUM_OK: Validation of checksum succeeded.
* - RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER: Skb buffer given is corrupted.
* - RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET: Valid flag is not set in the
* checksum trailer.
* - RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET: The packet is a fragment.
* - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT: The transport header is
* not TCP/UDP.
* - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION: Unrecognized IP header.
* - RMNET_MAP_CHECKSUM_VALIDATION_FAILED: In case the validation failed.
*/
int rmnet_map_checksum_downlink_packet(struct sk_buff *skb)
{
struct rmnet_map_dl_checksum_trailer_s *cksum_trailer;
unsigned int data_len;
unsigned char *map_payload;
unsigned char ip_version;
data_len = RMNET_MAP_GET_LENGTH(skb);
if (unlikely(skb->len < (sizeof(struct rmnet_map_header_s) + data_len +
sizeof(struct rmnet_map_dl_checksum_trailer_s))))
return RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER;
cksum_trailer = (struct rmnet_map_dl_checksum_trailer_s *)
(skb->data + data_len
+ sizeof(struct rmnet_map_header_s));
if (unlikely(!ntohs(cksum_trailer->valid)))
return RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET;
map_payload = (unsigned char *)(skb->data
+ sizeof(struct rmnet_map_header_s));
ip_version = (*map_payload & 0xF0) >> 4;
if (ip_version == 0x04)
return rmnet_map_validate_ipv4_packet_checksum(map_payload,
cksum_trailer);
else if (ip_version == 0x06)
return rmnet_map_validate_ipv6_packet_checksum(map_payload,
cksum_trailer);
return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION;
}