]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/core/flow_dissector.c
Merge branch 'x86-microcode-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.git] / net / core / flow_dissector.c
index 5f362c1d03322692da59509c7d594f72255330b8..45084938c403b82b23d45ed66d9871348baeadc7 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/if_pppox.h>
 #include <linux/ppp_defs.h>
 #include <net/flow_keys.h>
+#include <scsi/fc/fc_fcoe.h>
 
 /* copy saddr & daddr, possibly using 64bit load/store
  * Equivalent to :     flow->src = iph->saddr;
@@ -26,36 +27,61 @@ static void iph_to_flow_copy_addrs(struct flow_keys *flow, const struct iphdr *i
 }
 
 /**
- * skb_flow_get_ports - extract the upper layer ports and return them
- * @skb: buffer to extract the ports from
+ * __skb_flow_get_ports - extract the upper layer ports and return them
+ * @skb: sk_buff to extract the ports from
  * @thoff: transport header offset
  * @ip_proto: protocol for which to get port offset
+ * @data: raw buffer pointer to the packet, if NULL use skb->data
+ * @hlen: packet header length, if @data is NULL use skb_headlen(skb)
  *
  * The function will try to retrieve the ports at offset thoff + poff where poff
  * is the protocol port offset returned from proto_ports_offset
  */
-__be32 skb_flow_get_ports(const struct sk_buff *skb, int thoff, u8 ip_proto)
+__be32 __skb_flow_get_ports(const struct sk_buff *skb, int thoff, u8 ip_proto,
+                           void *data, int hlen)
 {
        int poff = proto_ports_offset(ip_proto);
 
+       if (!data) {
+               data = skb->data;
+               hlen = skb_headlen(skb);
+       }
+
        if (poff >= 0) {
                __be32 *ports, _ports;
 
-               ports = skb_header_pointer(skb, thoff + poff,
-                                          sizeof(_ports), &_ports);
+               ports = __skb_header_pointer(skb, thoff + poff,
+                                            sizeof(_ports), data, hlen, &_ports);
                if (ports)
                        return *ports;
        }
 
        return 0;
 }
-EXPORT_SYMBOL(skb_flow_get_ports);
+EXPORT_SYMBOL(__skb_flow_get_ports);
 
-bool skb_flow_dissect(const struct sk_buff *skb, struct flow_keys *flow)
+/**
+ * __skb_flow_dissect - extract the flow_keys struct and return it
+ * @skb: sk_buff to extract the flow from, can be NULL if the rest are specified
+ * @data: raw buffer pointer to the packet, if NULL use skb->data
+ * @proto: protocol for which to get the flow, if @data is NULL use skb->protocol
+ * @nhoff: network header offset, if @data is NULL use skb_network_offset(skb)
+ * @hlen: packet header length, if @data is NULL use skb_headlen(skb)
+ *
+ * The function will try to retrieve the struct flow_keys from either the skbuff
+ * or a raw buffer specified by the rest parameters
+ */
+bool __skb_flow_dissect(const struct sk_buff *skb, struct flow_keys *flow,
+                       void *data, __be16 proto, int nhoff, int hlen)
 {
-       int nhoff = skb_network_offset(skb);
        u8 ip_proto;
-       __be16 proto = skb->protocol;
+
+       if (!data) {
+               data = skb->data;
+               proto = skb->protocol;
+               nhoff = skb_network_offset(skb);
+               hlen = skb_headlen(skb);
+       }
 
        memset(flow, 0, sizeof(*flow));
 
@@ -65,7 +91,7 @@ again:
                const struct iphdr *iph;
                struct iphdr _iph;
 ip:
-               iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
+               iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
                if (!iph || iph->ihl < 5)
                        return false;
                nhoff += iph->ihl * 4;
@@ -74,6 +100,13 @@ ip:
                if (ip_is_fragment(iph))
                        ip_proto = 0;
 
+               /* skip the address processing if skb is NULL.  The assumption
+                * here is that if there is no skb we are not looking for flow
+                * info but lengths and protocols.
+                */
+               if (!skb)
+                       break;
+
                iph_to_flow_copy_addrs(flow, iph);
                break;
        }
@@ -83,14 +116,19 @@ ip:
                __be32 flow_label;
 
 ipv6:
-               iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
+               iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
                if (!iph)
                        return false;
 
                ip_proto = iph->nexthdr;
+               nhoff += sizeof(struct ipv6hdr);
+
+               /* see comment above in IPv4 section */
+               if (!skb)
+                       break;
+
                flow->src = (__force __be32)ipv6_addr_hash(&iph->saddr);
                flow->dst = (__force __be32)ipv6_addr_hash(&iph->daddr);
-               nhoff += sizeof(struct ipv6hdr);
 
                flow_label = ip6_flowlabel(iph);
                if (flow_label) {
@@ -113,7 +151,7 @@ ipv6:
                const struct vlan_hdr *vlan;
                struct vlan_hdr _vlan;
 
-               vlan = skb_header_pointer(skb, nhoff, sizeof(_vlan), &_vlan);
+               vlan = __skb_header_pointer(skb, nhoff, sizeof(_vlan), data, hlen, &_vlan);
                if (!vlan)
                        return false;
 
@@ -126,7 +164,7 @@ ipv6:
                        struct pppoe_hdr hdr;
                        __be16 proto;
                } *hdr, _hdr;
-               hdr = skb_header_pointer(skb, nhoff, sizeof(_hdr), &_hdr);
+               hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
                if (!hdr)
                        return false;
                proto = hdr->proto;
@@ -140,6 +178,9 @@ ipv6:
                        return false;
                }
        }
+       case htons(ETH_P_FCOE):
+               flow->thoff = (u16)(nhoff + FCOE_HEADER_LEN);
+               /* fall through */
        default:
                return false;
        }
@@ -151,7 +192,7 @@ ipv6:
                        __be16 proto;
                } *hdr, _hdr;
 
-               hdr = skb_header_pointer(skb, nhoff, sizeof(_hdr), &_hdr);
+               hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
                if (!hdr)
                        return false;
                /*
@@ -171,8 +212,9 @@ ipv6:
                                const struct ethhdr *eth;
                                struct ethhdr _eth;
 
-                               eth = skb_header_pointer(skb, nhoff,
-                                                        sizeof(_eth), &_eth);
+                               eth = __skb_header_pointer(skb, nhoff,
+                                                          sizeof(_eth),
+                                                          data, hlen, &_eth);
                                if (!eth)
                                        return false;
                                proto = eth->h_proto;
@@ -194,12 +236,16 @@ ipv6:
 
        flow->n_proto = proto;
        flow->ip_proto = ip_proto;
-       flow->ports = skb_flow_get_ports(skb, nhoff, ip_proto);
        flow->thoff = (u16) nhoff;
 
+       /* unless skb is set we don't need to record port info */
+       if (skb)
+               flow->ports = __skb_flow_get_ports(skb, nhoff, ip_proto,
+                                                  data, hlen);
+
        return true;
 }
-EXPORT_SYMBOL(skb_flow_dissect);
+EXPORT_SYMBOL(__skb_flow_dissect);
 
 static u32 hashrnd __read_mostly;
 static __always_inline void __flow_hash_secret_init(void)
@@ -286,34 +332,27 @@ u16 __skb_tx_hash(const struct net_device *dev, struct sk_buff *skb,
                qcount = dev->tc_to_txq[tc].count;
        }
 
-       return (u16) (((u64)skb_get_hash(skb) * qcount) >> 32) + qoffset;
+       return (u16) reciprocal_scale(skb_get_hash(skb), qcount) + qoffset;
 }
 EXPORT_SYMBOL(__skb_tx_hash);
 
-/* __skb_get_poff() returns the offset to the payload as far as it could
- * be dissected. The main user is currently BPF, so that we can dynamically
- * truncate packets without needing to push actual payload to the user
- * space and can analyze headers only, instead.
- */
-u32 __skb_get_poff(const struct sk_buff *skb)
+u32 __skb_get_poff(const struct sk_buff *skb, void *data,
+                  const struct flow_keys *keys, int hlen)
 {
-       struct flow_keys keys;
-       u32 poff = 0;
+       u32 poff = keys->thoff;
 
-       if (!skb_flow_dissect(skb, &keys))
-               return 0;
-
-       poff += keys.thoff;
-       switch (keys.ip_proto) {
+       switch (keys->ip_proto) {
        case IPPROTO_TCP: {
-               const struct tcphdr *tcph;
-               struct tcphdr _tcph;
+               /* access doff as u8 to avoid unaligned access */
+               const u8 *doff;
+               u8 _doff;
 
-               tcph = skb_header_pointer(skb, poff, sizeof(_tcph), &_tcph);
-               if (!tcph)
+               doff = __skb_header_pointer(skb, poff + 12, sizeof(_doff),
+                                           data, hlen, &_doff);
+               if (!doff)
                        return poff;
 
-               poff += max_t(u32, sizeof(struct tcphdr), tcph->doff * 4);
+               poff += max_t(u32, sizeof(struct tcphdr), (*doff & 0xF0) >> 2);
                break;
        }
        case IPPROTO_UDP:
@@ -343,6 +382,21 @@ u32 __skb_get_poff(const struct sk_buff *skb)
        return poff;
 }
 
+/* skb_get_poff() returns the offset to the payload as far as it could
+ * be dissected. The main user is currently BPF, so that we can dynamically
+ * truncate packets without needing to push actual payload to the user
+ * space and can analyze headers only, instead.
+ */
+u32 skb_get_poff(const struct sk_buff *skb)
+{
+       struct flow_keys keys;
+
+       if (!skb_flow_dissect(skb, &keys))
+               return 0;
+
+       return __skb_get_poff(skb, skb->data, &keys, skb_headlen(skb));
+}
+
 static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb)
 {
 #ifdef CONFIG_XPS
@@ -359,9 +413,8 @@ static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb)
                        if (map->len == 1)
                                queue_index = map->queues[0];
                        else
-                               queue_index = map->queues[
-                                   ((u64)skb_get_hash(skb) * map->len) >> 32];
-
+                               queue_index = map->queues[reciprocal_scale(skb_get_hash(skb),
+                                                                          map->len)];
                        if (unlikely(queue_index >= dev->real_num_tx_queues))
                                queue_index = -1;
                }