| /* | 
 |  * net/ife/ife.c - Inter-FE protocol based on ForCES WG InterFE LFB | 
 |  * Copyright (c) 2015 Jamal Hadi Salim <jhs@mojatatu.com> | 
 |  * Copyright (c) 2017 Yotam Gigi <yotamg@mellanox.com> | 
 |  * | 
 |  * Refer to: draft-ietf-forces-interfelfb-03 and netdev01 paper: | 
 |  * "Distributing Linux Traffic Control Classifier-Action Subsystem" | 
 |  * Authors: Jamal Hadi Salim and Damascene M. Joachimpillai | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation. | 
 |  */ | 
 |  | 
 | #include <linux/types.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/string.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/skbuff.h> | 
 | #include <linux/rtnetlink.h> | 
 | #include <linux/module.h> | 
 | #include <linux/init.h> | 
 | #include <net/net_namespace.h> | 
 | #include <net/netlink.h> | 
 | #include <net/pkt_sched.h> | 
 | #include <linux/etherdevice.h> | 
 | #include <net/ife.h> | 
 |  | 
 | struct ifeheadr { | 
 | 	__be16 metalen; | 
 | 	u8 tlv_data[]; | 
 | }; | 
 |  | 
 | void *ife_encode(struct sk_buff *skb, u16 metalen) | 
 | { | 
 | 	/* OUTERHDR:TOTMETALEN:{TLVHDR:Metadatum:TLVHDR..}:ORIGDATA | 
 | 	 * where ORIGDATA = original ethernet header ... | 
 | 	 */ | 
 | 	int hdrm = metalen + IFE_METAHDRLEN; | 
 | 	int total_push = hdrm + skb->dev->hard_header_len; | 
 | 	struct ifeheadr *ifehdr; | 
 | 	struct ethhdr *iethh;	/* inner ether header */ | 
 | 	int skboff = 0; | 
 | 	int err; | 
 |  | 
 | 	err = skb_cow_head(skb, total_push); | 
 | 	if (unlikely(err)) | 
 | 		return NULL; | 
 |  | 
 | 	iethh = (struct ethhdr *) skb->data; | 
 |  | 
 | 	__skb_push(skb, total_push); | 
 | 	memcpy(skb->data, iethh, skb->dev->hard_header_len); | 
 | 	skb_reset_mac_header(skb); | 
 | 	skboff += skb->dev->hard_header_len; | 
 |  | 
 | 	/* total metadata length */ | 
 | 	ifehdr = (struct ifeheadr *) (skb->data + skboff); | 
 | 	metalen += IFE_METAHDRLEN; | 
 | 	ifehdr->metalen = htons(metalen); | 
 |  | 
 | 	return ifehdr->tlv_data; | 
 | } | 
 | EXPORT_SYMBOL_GPL(ife_encode); | 
 |  | 
 | void *ife_decode(struct sk_buff *skb, u16 *metalen) | 
 | { | 
 | 	struct ifeheadr *ifehdr; | 
 | 	int total_pull; | 
 | 	u16 ifehdrln; | 
 |  | 
 | 	if (!pskb_may_pull(skb, skb->dev->hard_header_len + IFE_METAHDRLEN)) | 
 | 		return NULL; | 
 |  | 
 | 	ifehdr = (struct ifeheadr *) (skb->data + skb->dev->hard_header_len); | 
 | 	ifehdrln = ntohs(ifehdr->metalen); | 
 | 	total_pull = skb->dev->hard_header_len + ifehdrln; | 
 |  | 
 | 	if (unlikely(ifehdrln < 2)) | 
 | 		return NULL; | 
 |  | 
 | 	if (unlikely(!pskb_may_pull(skb, total_pull))) | 
 | 		return NULL; | 
 |  | 
 | 	ifehdr = (struct ifeheadr *)(skb->data + skb->dev->hard_header_len); | 
 | 	skb_set_mac_header(skb, total_pull); | 
 | 	__skb_pull(skb, total_pull); | 
 | 	*metalen = ifehdrln - IFE_METAHDRLEN; | 
 |  | 
 | 	return &ifehdr->tlv_data; | 
 | } | 
 | EXPORT_SYMBOL_GPL(ife_decode); | 
 |  | 
 | struct meta_tlvhdr { | 
 | 	__be16 type; | 
 | 	__be16 len; | 
 | }; | 
 |  | 
 | static bool __ife_tlv_meta_valid(const unsigned char *skbdata, | 
 | 				 const unsigned char *ifehdr_end) | 
 | { | 
 | 	const struct meta_tlvhdr *tlv; | 
 | 	u16 tlvlen; | 
 |  | 
 | 	if (unlikely(skbdata + sizeof(*tlv) > ifehdr_end)) | 
 | 		return false; | 
 |  | 
 | 	tlv = (const struct meta_tlvhdr *)skbdata; | 
 | 	tlvlen = ntohs(tlv->len); | 
 |  | 
 | 	/* tlv length field is inc header, check on minimum */ | 
 | 	if (tlvlen < NLA_HDRLEN) | 
 | 		return false; | 
 |  | 
 | 	/* overflow by NLA_ALIGN check */ | 
 | 	if (NLA_ALIGN(tlvlen) < tlvlen) | 
 | 		return false; | 
 |  | 
 | 	if (unlikely(skbdata + NLA_ALIGN(tlvlen) > ifehdr_end)) | 
 | 		return false; | 
 |  | 
 | 	return true; | 
 | } | 
 |  | 
 | /* Caller takes care of presenting data in network order | 
 |  */ | 
 | void *ife_tlv_meta_decode(void *skbdata, const void *ifehdr_end, u16 *attrtype, | 
 | 			  u16 *dlen, u16 *totlen) | 
 | { | 
 | 	struct meta_tlvhdr *tlv; | 
 |  | 
 | 	if (!__ife_tlv_meta_valid(skbdata, ifehdr_end)) | 
 | 		return NULL; | 
 |  | 
 | 	tlv = (struct meta_tlvhdr *)skbdata; | 
 | 	*dlen = ntohs(tlv->len) - NLA_HDRLEN; | 
 | 	*attrtype = ntohs(tlv->type); | 
 |  | 
 | 	if (totlen) | 
 | 		*totlen = nla_total_size(*dlen); | 
 |  | 
 | 	return skbdata + sizeof(struct meta_tlvhdr); | 
 | } | 
 | EXPORT_SYMBOL_GPL(ife_tlv_meta_decode); | 
 |  | 
 | void *ife_tlv_meta_next(void *skbdata) | 
 | { | 
 | 	struct meta_tlvhdr *tlv = (struct meta_tlvhdr *) skbdata; | 
 | 	u16 tlvlen = ntohs(tlv->len); | 
 |  | 
 | 	tlvlen = NLA_ALIGN(tlvlen); | 
 |  | 
 | 	return skbdata + tlvlen; | 
 | } | 
 | EXPORT_SYMBOL_GPL(ife_tlv_meta_next); | 
 |  | 
 | /* Caller takes care of presenting data in network order | 
 |  */ | 
 | int ife_tlv_meta_encode(void *skbdata, u16 attrtype, u16 dlen, const void *dval) | 
 | { | 
 | 	__be32 *tlv = (__be32 *) (skbdata); | 
 | 	u16 totlen = nla_total_size(dlen);	/*alignment + hdr */ | 
 | 	char *dptr = (char *) tlv + NLA_HDRLEN; | 
 | 	u32 htlv = attrtype << 16 | (dlen + NLA_HDRLEN); | 
 |  | 
 | 	*tlv = htonl(htlv); | 
 | 	memset(dptr, 0, totlen - NLA_HDRLEN); | 
 | 	memcpy(dptr, dval, dlen); | 
 |  | 
 | 	return totlen; | 
 | } | 
 | EXPORT_SYMBOL_GPL(ife_tlv_meta_encode); | 
 |  | 
 | MODULE_AUTHOR("Jamal Hadi Salim <jhs@mojatatu.com>"); | 
 | MODULE_AUTHOR("Yotam Gigi <yotam.gi@gmail.com>"); | 
 | MODULE_DESCRIPTION("Inter-FE LFB action"); | 
 | MODULE_LICENSE("GPL"); |