* Re: [PATCH] net: ipv4: Fix incorrect free in ICMP receive
From: Cong Wang @ 2015-01-20 19:42 UTC (permalink / raw)
To: Eric Dumazet; +Cc: subashab, David Miller, netdev
In-Reply-To: <1421725837.17892.6.camel@edumazet-glaptop2.roam.corp.google.com>
On Mon, Jan 19, 2015 at 7:50 PM, Eric Dumazet <eric.dumazet@gmail.com> wrote:
> On Tue, 2015-01-20 at 02:34 +0000, subashab@codeaurora.org wrote:
>> Thanks David and Eric for the insights. In order for me to steer this
>> debug in the right direction, can you please help me? Based on your input
>> I looked into this a little deeper to understand the refcnts for sockets
>> and skb's in this ping receive path.
>>
>> from ping_rcv()
>>
>> sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));
>> if (sk != NULL) {
>> pr_debug("rcv on socket %p\n", sk);
>> ping_queue_rcv_skb(sk, skb_get(skb));
>> sock_put(sk);
>> return;
>> }
>>
>> From my understanding I have made the following analysis, please correct
>> if I am wrong.
>>
>> 1) There is no guarantee that sock_put() in the above code snippet
>> will not drop the socket refcount to 0 and free the socket. This can
>> hypothetically happen if say
>> sock_close()->ping_close()->*->ping_unhash()->sock_put()
>> can happen between in a different context between ping_lookup() and
>> sock_put() in the above code snippet. Is this observation accurate?
>>
>> 2) Now since this socket is being freed in the ping receive path, I think
>> the following is what is happening with the skb.
>> alloc_skb()[skb->users=1] -> deliver_skb()[skb->users=2] -> * ->
>> icmp_rcv() -> ping_rcv() -> sk_free --> inet_sock_destruct()->
>> __skb_queue_purge()->kfree_skb()[dec ref cnt, skb->users=1]
>>
>> when stack unwinds to icmp_rcv(), refcnt actually hits zero and packet is
>> freed calling the destructor which tries to access the freed socket.
>>
>> If these observations are right, Can you please tell me what is the call
>> flow that is not supposed to happen but is happening in this issue? I am
>> trying to understand better to identify next steps to tackle this issue.
>
> This is why skb_get() is very often a bug.
>
> There is no guarantee the consume_skb() in icmp_rcv() is done before the
> skb_queue_purge().
>
Or let the socket layer drop the packet instead of unconditionally
dropping all icmp packets after success?
^ permalink raw reply
* Re: BW regression after "tcp: refine TSO autosizing"
From: Rick Jones @ 2015-01-20 19:44 UTC (permalink / raw)
To: Eric Dumazet
Cc: Dave Taht, Eyal Perry, Yuchung Cheng, Neal Cardwell, Eyal Perry,
Or Gerlitz, Linux Netdev List, Amir Vadai, Yevgeny Petrilin,
Saeed Mahameed, Ido Shamay, Amir Ancel
In-Reply-To: <1421781991.4832.8.camel@edumazet-glaptop2.roam.corp.google.com>
>> More stuff to pull from a TCP_INFO call I presume? Feel free to drop me
>> a patch, though I'd probably want it to be in the guise of the omni
>> output selectors.
>>
>
> It was something like :
I'd forgotten about dump_tcp_info() :)
Committed revision 673.
happy benchmarking,
rick
^ permalink raw reply
* [net-next PATCH v3 00/12] Flow API
From: John Fastabend @ 2015-01-20 20:26 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
I believe I addressed all the comments so far except for the integrate
with 'tc'. I plan to work on the integration pieces next.
v3:
- fixes from Simon Horman integrated see netdev mailing list
- converted synch rcu to call_rcu
- updated git commit messages to match code
- updated flow-api.html document to match latest updates
- also updated user space flow tool with a handful of fixes
v2:
- Use a software rhashtable to store add/del flows so we can skip
having to interrogate drivers for get_flow requests.
- Removed structures from UAPI this should make it easier to evolve
as needed.
- Added net_flow_lock around set/del rule ops.
- Alexei Starovoitov suggested renaming NET_FLOW -> NFL for
brevity/clarity. Seems reasonable to me so went ahead and changed
the UAPI enums. Also renamed flow types and calls to *_rule. Core
flow_table still using net_flow_* prefix.
- various fixes/suggestion from Simon Horman, Jiri Pirko, Scot
Feldman, Thomas Graf, et. al.
* SimonH: sent patch series of fixes to netdev
* JiriP: some naming issues, some helper funcs added, etc.
* ScottF: use ARRAY_SIZE, let compiler define array sizes, use
ETH_P_* macros. Various fixes.
* ThomasG: various suggestions
- fixed a few cases to catch invalid messages from user space
and dev_put errors.
---
This set creates a new netlink family and set of messages to configure
flow tables in hardware. I tried to make the commit messages
reasonably verbose at least in the flow_table patches possibly too
verbose.
What we get at the end of this series is a working API to get device
capabilities and program flows using the rocker switch.
I created a user space tool 'flow' that I use to configure and query
the devices it is posted here,
https://github.com/jrfastab/iprotue2-flow-tool
For now it is a stand-alone tool but once the kernel bits get sorted
out I would like to port it into the iproute2 package. This way we
can keep all of our tooling in one package.
As far as testing, I've tested various combinations of tables and
rules on the rocker switch and it seems to work.
For some examples and maybe a bit more illustrative description I
posted a set of notes on github io pages. Here we can show the
description along with images produced by the flow tool showing
the pipeline.
http://jrfastab.github.io/jekyll/update/2014/12/21/flow-api.html
After this base work is complete the next task is to integrate with
existing subsystems 'tc' and OVS for example. And provide more
example setups in the notes.
Thanks! Any comments/feedback always welcome.
And also thanks to everyone who helped with this flow API so
far. All the folks at Dusseldorf LPC, OVS summit Santa Clara, P4
authors for some inspiration, the collection of IETF FoRCES
documents I mulled over, Netfilter workshop where I started
to realize fixing ethtool was most likely not going to work,
etc.
---
John Fastabend (12):
net: flow_table: create interface for hw match/action tables
net: flow_table: add rule, delete rule
net: flow: implement flow cache for get routines
net: flow_table: create a set of common headers and actions
net: flow_table: add validation functions for rules
net: rocker: add pipeline model for rocker switch
net: rocker: add set rule ops
net: rocker: add group_id slices and drop explicit goto
net: rocker: add multicast path to bridging
net: rocker: add cookie to group acls and use flow_id to set cookie
net: rocker: have flow api calls set cookie value
net: rocker: implement delete flow routine
drivers/net/ethernet/rocker/rocker.c | 754 ++++++++++
drivers/net/ethernet/rocker/rocker_pipeline.h | 595 ++++++++
include/linux/if_flow.h | 231 +++
include/linux/if_flow_common.h | 257 +++
include/linux/netdevice.h | 48 +
include/uapi/linux/if_flow.h | 440 ++++++
net/Kconfig | 7
net/core/Makefile | 1
net/core/flow_table.c | 1915 +++++++++++++++++++++++++
9 files changed, 4231 insertions(+), 17 deletions(-)
create mode 100644 drivers/net/ethernet/rocker/rocker_pipeline.h
create mode 100644 include/linux/if_flow.h
create mode 100644 include/linux/if_flow_common.h
create mode 100644 include/uapi/linux/if_flow.h
create mode 100644 net/core/flow_table.c
--
Signature
^ permalink raw reply
* [net-next PATCH v3 01/12] net: flow_table: create interface for hw match/action tables
From: John Fastabend @ 2015-01-20 20:26 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Currently, we do not have an interface to query hardware and learn
the capabilities of the device. This makes it very difficult to use
hardware flow tables.
At the moment the only interface we have to work with hardware flow
tables is ethtool. This has many deficiencies, first its ioctl based
making it difficult to use in systems that need to monitor interfaces
because there is no support for multicast, notifiers, etc.
The next big gap is it doesn't support querying devices for
capabilities. The only way to learn hardware entries is by doing a
"try and see" operation. An error perhaps indicating the device can
not support your request but could be possibly for other reasons.
Maybe a table is full for example. The existing flow interface only
supports a single ingress table which is sufficient for some of the
existing NIC host interfaces but limiting for more advanced NIC
interfaces and switch devices.
Also it is not extensible without recompiling both drivers and core
interfaces. It may be possible to reprogram a device with additional
header types, new protocols, whatever and it would be great if the
flow table infrastructure can handle this.
So this patch scraps the ethtool flow classifier interface and
creates a new flow table interface. It is expected that device that
support the existing ethtool interface today can support both
interfaces without too much difficulty. I did a proof point on the
ixgbe driver. Only choosing ixgbe because I have a 82599 10Gbps
device in my development system. A more thorough implementation
was done for the rocker switch showing how to use the interface.
In this patch we create interfaces to get the headers a device
supports, the actions it supports, a header graph showing the
relationship between headers the device supports, the tables
supported by the device and how they are connected.
This patch _only_ provides the get routines in an attempt to
make the patch sequence manageable.
get_hdrs :
report a set of headers/fields the device supports. These
are specified as length/offsets so we can support standard
protocols or vendor specific headers. This is more flexible
then bitmasks of pre-defined packet types. In 'tc' for example
I may use u32 to match on proprietary or vendor specific fields.
A bitmask approach does not allow for this, but defining the
fields as a set of offsets and lengths allows for this.
A device that supports Openflow version 1.x for example could
provide the set of field/offsets that are equivelent to the
specification.
One property of this type of interface is I don't have to
rebuild my kernel/driver header interfaces, etc to support the
latest and greatest trendy protocol foo.
For some types of metadata the device understands we also
use header fields to represent these. One example of this is
we may have an ingress_port metadata field to report the
port a packet was received on. At the moment we expect the
metadata fields to be defined outside the interface. We can
standardize on common ones such "ingress_port" across devices.
Some examples of outside definitions specifying metadata
might be OVS, internal definitions like skb->mark, or some
FoRCES definitions.
get_hdr_graph :
Simply providing a header/field offset I support is not sufficient
to learn how many nested 802.1Q tags I can support and other
similar cases where the ordering of headers matters.
So we use this operation to query the device for a header
graph showing how the headers need to be related.
With this operation and the 'get_headers' operation you can
interrogate the driver with questions like "do you support
Q'in'Q?", "how many VLAN tags can I nest before the parser
breaks?", "Do you support MPLS?", "How about Foo Header in
a VXLAN tunnel?".
get_actions :
Report a list of actions supported by the device along with the
arguments they take. So "drop_packet" action takes no arguments
and "set_field" action takes two arguments a field and value.
This suffers again from being slightly opaque. Meaning if a device
reports back action "foo_bar" with three arguments how do I as a
consumer of this "know" what that action is? The easy thing to do
is punt on it and say it should be described outside the driver
somewhere. OVS for example defines a set of actions. If my FoRCeS
quick read is correct they define actions using text in the
messaging interface. A follow up patch series could use a
description language to describe actions. Possibly using something
from eBPF or nftables for example. This patch will not try to
solve the isuse now and expect actions are defined outside the API
or are well known.
get_tbls :
Hardware may support one or more tables. Each table supports a set
of matches and a set of actions. The match fields supported are
defined above by the 'get_headers' operations. Similarly the actions
supported are defined by the 'get_actions' operation.
This allows the hardware to report several tables all with distinct
capabilities. Tables also have table attributes used to describe
features of the table. Because netlink messages are TLV based we
can easily add new table attribues as needed.
Currently a table has two attributes size and source. The size
indicates how many "slots" are in the table for flow entries. One
caveat here is a rule in the flow table may consume multiple slots
in the table. We deal with this in a subsequent patch.
The source field is used to indicate table boundaries where actions
are applied. A table with the same source value will not "see"
actions from tables with the same source. An example where this is
relavent would be to have an action to re-write the destiniation
IP address of a packet. If you have a match rule in a table with
the same source that matches on the new IP address it will not be
hit. However if it is in a table with a different source value
_and_ in another table that gets applied the rule will be hit. See
the next operatoin for querying table ordering.
Some basic hardware may only support a single table which simplifies
some things. But even the simple 10/40Gbps NICs support multiple
tables and different tables depending on ingress/egress.
get_tbl_graph :
When a device supports multiple tables we need to identify how the
tables are connected when each table is executed.
To do this we provide a table graph which gives the pipeline of the
device. The graph gives nodes representing each table and the edges
indicate the criteria to progress to the next flow table. There are
examples of this type of thing in both FoRCES and OVS. OVS
prescribes a set of tables reachable with goto actions and FoRCES a
slightly more flexible arrangement. In software tc's u32 classifier
allows "linking" hash tables together. The OVS dataplane with the
support of 'goto' action is completely connected. Without the
'goto' action the tables are progressed linearly.
By querying the graph from hardware we can "learn" what table flows
are supported and map them into software.
We also provide a bit to indicate if the node is a root node of the
ingress pipeline or egress pipeline. This is used on devices that
have different pipelines for ingres and egress. This appears to be
fairly common for devices. The realtek chip presented at LPC in
Dusseldorf for example appeared to have a separate ingress/egress
pipeline.
With these five operations software can learn what types of fields
the hardware flow table supports and how they are arranged. Subsequent
patches will address programming the flow tables.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
include/linux/if_flow.h | 188 ++++++++
include/linux/netdevice.h | 38 ++
include/uapi/linux/if_flow.h | 389 +++++++++++++++++
net/Kconfig | 7
net/core/Makefile | 1
net/core/flow_table.c | 942 ++++++++++++++++++++++++++++++++++++++++++
6 files changed, 1565 insertions(+)
create mode 100644 include/linux/if_flow.h
create mode 100644 include/uapi/linux/if_flow.h
create mode 100644 net/core/flow_table.c
diff --git a/include/linux/if_flow.h b/include/linux/if_flow.h
new file mode 100644
index 0000000..7ce1e1d
--- /dev/null
+++ b/include/linux/if_flow.h
@@ -0,0 +1,188 @@
+/*
+ * include/linux/net/if_flow.h - Flow table interface for Switch devices
+ * Copyright (c) 2014 John Fastabend <john.r.fastabend@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Author: John Fastabend <john.r.fastabend@intel.com>
+ */
+
+#ifndef _IF_FLOW_H
+#define _IF_FLOW_H
+
+#include <uapi/linux/if_flow.h>
+
+/**
+ * @struct net_flow_fields
+ * @brief defines a field in a header
+ *
+ * @name string identifier for pretty printing
+ * @uid unique identifier for field
+ * @bitwidth length of field in bits
+ */
+struct net_flow_field {
+ char *name;
+ __u32 uid;
+ __u32 bitwidth;
+};
+
+/**
+ * @struct net_flow_hdr
+ * @brief defines a match (header/field) an endpoint can use
+ *
+ * @name string identifier for pretty printing
+ * @uid unique identifier for header
+ * @field_sz number of fields are in the set
+ * @fields the set of fields in the net_flow_hdr
+ */
+struct net_flow_hdr {
+ char *name;
+ __u32 uid;
+ __u32 field_sz;
+ struct net_flow_field *fields;
+};
+
+/**
+ * @struct net_flow_action_arg
+ * @brief encodes action arguments in structures one per argument
+ *
+ * @name string identifier for pretty printing
+ * @type type of argument either u8, u16, u32, u64
+ * @value_# indicate value/mask value type on of u8, u16, u32, or u64
+ */
+struct net_flow_action_arg {
+ char *name;
+ enum net_flow_action_arg_type type;
+ union {
+ __u8 value_u8;
+ __u16 value_u16;
+ __u32 value_u32;
+ __u64 value_u64;
+ };
+};
+
+/**
+ * @struct net_flow_action
+ * @brief a description of a endpoint defined action
+ *
+ * @name printable name
+ * @uid unique action identifier
+ * @args null terminated list of action arguments
+ */
+struct net_flow_action {
+ char *name;
+ __u32 uid;
+ struct net_flow_action_arg *args;
+};
+
+/**
+ * @struct net_flow_field_ref
+ * @brief uniquely identify field as instance:header:field tuple
+ *
+ * @instance identify unique instance of field reference
+ * @header identify unique header reference
+ * @field identify unique field in above header reference
+ * @mask_type indicate mask type
+ * @type indicate value/mask value type on of u8, u16, u32, or u64
+ * @value_u# value of field reference
+ * @mask_u# mask value of field reference
+ */
+struct net_flow_field_ref {
+ __u32 instance;
+ __u32 header;
+ __u32 field;
+ __u32 mask_type;
+ __u32 type;
+ union {
+ struct {
+ __u8 value_u8;
+ __u8 mask_u8;
+ };
+ struct {
+ __u16 value_u16;
+ __u16 mask_u16;
+ };
+ struct {
+ __u32 value_u32;
+ __u32 mask_u32;
+ };
+ struct {
+ __u64 value_u64;
+ __u64 mask_u64;
+ };
+ };
+};
+
+/**
+ * @struct net_flow_tbl
+ * @brief define flow table with supported match/actions
+ *
+ * @name string identifier for pretty printing
+ * @uid unique identifier for table
+ * @source uid of parent table
+ * @apply_action actions in the same apply group are applied in one step
+ * @size max number of entries for table or -1 for unbounded
+ * @matches null terminated set of supported match types given by match uid
+ * @actions null terminated set of supported action types given by action uid
+ */
+struct net_flow_tbl {
+ char *name;
+ __u32 uid;
+ __u32 source;
+ __u32 apply_action;
+ __u32 size;
+ struct net_flow_field_ref *matches;
+ __u32 *actions;
+};
+
+/**
+ * @struct net_flow_jump_table
+ * @brief encodes an edge of the table graph or header graph
+ *
+ * @field field reference must be true to follow edge
+ * @node node identifier to connect edge to
+ */
+
+struct net_flow_jump_table {
+ struct net_flow_field_ref field;
+ __u32 node; /* <0 is a parser error */
+};
+
+/* @struct net_flow_hdr_node
+ * @brief node in a header graph of header fields.
+ *
+ * @name string identifier for pretty printing
+ * @uid unique id of the graph node
+ * @hdrs null terminated list of hdrs identified by this node
+ * @jump encoding of graph structure as a case jump statement
+ */
+struct net_flow_hdr_node {
+ char *name;
+ __u32 uid;
+ __u32 *hdrs;
+ struct net_flow_jump_table *jump;
+};
+
+/* @struct net_flow_tbl_node
+ * @brief
+ *
+ * @uid unique id of the table node
+ * @flags bitmask of table attributes
+ * @jump encoding of graph structure as a case jump statement
+ */
+struct net_flow_tbl_node {
+ __u32 uid;
+ __u32 flags;
+ struct net_flow_jump_table *jump;
+};
+#endif
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 679e6e9..74481b9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -52,6 +52,10 @@
#include <linux/neighbour.h>
#include <uapi/linux/netdevice.h>
+#ifdef CONFIG_NET_FLOW_TABLES
+#include <linux/if_flow.h>
+#endif
+
struct netpoll_info;
struct device;
struct phy_device;
@@ -1030,6 +1034,33 @@ typedef u16 (*select_queue_fallback_t)(struct net_device *dev,
* int (*ndo_switch_port_stp_update)(struct net_device *dev, u8 state);
* Called to notify switch device port of bridge port STP
* state change.
+ *
+ * struct net_flow_action **(*ndo_flow_get_actions)(struct net_device *dev)
+ * Report a null terminated list of actions supported by the device along
+ * with the arguments they take.
+ *
+ * struct net_flow_tbl **(*ndo_flow_get_tbls)(struct net_device *dev)
+ * Report a null terminated list of tables supported by the device.
+ * Including the match fields and actions supported. The match fields
+ * are defined by the 'ndo_flow_get_hdrs' op and the actions are defined
+ * by 'ndo_flow_get_actions' op.
+ *
+ * struct net_flow_tbl_node **(*ndo_flow_get_tbl_graph)(struct net_device *dev)
+ * Report a null terminated list of nodes defining the table graph. When
+ * a device supports multiple tables we need to identify how the tables
+ * are connected and in what order are the tables traversed. The table
+ * nodes returned here provide the graph required to learn this.
+ *
+ * struct net_flow_hdr **(*ndo_flow_get_hdrs)(struct net_device *dev)
+ * Report a null terminated list of headers+fields supported by the
+ * device. See net_flow_hdr struct for details on header/field layout
+ * the basic logic is by giving the byte/length/offset of each field
+ * the device can define the protocols it supports.
+ *
+ * struct net_flow_hdr_node **(*ndo_flow_get_hdr_graph)(struct net_device *dev)
+ * Report a null terminated list of nodes defining the header graph. This
+ * provides the necessary graph to learn the ordering of headers supported
+ * by the device.
*/
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
@@ -1190,6 +1221,13 @@ struct net_device_ops {
int (*ndo_switch_port_stp_update)(struct net_device *dev,
u8 state);
#endif
+#ifdef CONFIG_NET_FLOW_TABLES
+ struct net_flow_action **(*ndo_flow_get_actions)(struct net_device *dev);
+ struct net_flow_tbl **(*ndo_flow_get_tbls)(struct net_device *dev);
+ struct net_flow_tbl_node **(*ndo_flow_get_tbl_graph)(struct net_device *dev);
+ struct net_flow_hdr **(*ndo_flow_get_hdrs)(struct net_device *dev);
+ struct net_flow_hdr_node **(*ndo_flow_get_hdr_graph)(struct net_device *dev);
+#endif
};
/**
diff --git a/include/uapi/linux/if_flow.h b/include/uapi/linux/if_flow.h
new file mode 100644
index 0000000..3314aa2
--- /dev/null
+++ b/include/uapi/linux/if_flow.h
@@ -0,0 +1,389 @@
+/*
+ * include/uapi/linux/if_flow.h - Flow table interface for Switch devices
+ * Copyright (c) 2014 John Fastabend <john.r.fastabend@intel.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Author: John Fastabend <john.r.fastabend@intel.com>
+ */
+
+/* Netlink description:
+ *
+ * Table definition used to describe running tables. The following
+ * describes the netlink format used by the flow API.
+ *
+ * Flow table definitions used to define tables.
+ *
+ * [NFL_TABLE_IDENTIFIER_TYPE]
+ * [NFL_TABLE_IDENTIFIER]
+ * [NFL_TABLE_TABLES]
+ * [NFL_TABLE]
+ * [NFL_TABLE_ATTR_NAME]
+ * [NFL_TABLE_ATTR_UID]
+ * [NFL_TABLE_ATTR_SOURCE]
+ * [NFL_TABLE_ATTR_APPLY]
+ * [NFL_TABLE_ATTR_SIZE]
+ * [NFL_TABLE_ATTR_MATCHES]
+ * [NFL_FIELD_REF]
+ * [NFL_FIELD_REF_INSTANCE]
+ * [NFL_FIELD_REF_HEADER]
+ * [NFL_FIELD_REF_FIELD]
+ * [NFL_FIELD_REF_MASK]
+ * [NFL_FIELD_REF_TYPE]
+ * [...]
+ * [NFL_TABLE_ATTR_ACTIONS]
+ * [NFL_ACTION_ATTR_UID]
+ * [...]
+ * [NFL_TABLE]
+ * [...]
+ *
+ * Header definitions used to define headers with user friendly
+ * names.
+ *
+ * [NFL_TABLE_HEADERS]
+ * [NFL_HEADER]
+ * [NFL_HEADER_ATTR_NAME]
+ * [NFL_HEADER_ATTR_UID]
+ * [NFL_HEADER_ATTR_FIELDS]
+ * [NFL_HEADER_ATTR_FIELD]
+ * [NFL_FIELD_ATTR_NAME]
+ * [NFL_FIELD_ATTR_UID]
+ * [NFL_FIELD_ATTR_BITWIDTH]
+ * [NFL_HEADER_ATTR_FIELD]
+ * [...]
+ * [...]
+ * [NFL_HEADER]
+ * [...]
+ * [...]
+ *
+ * Action definitions supported by tables
+ *
+ * [NFL_TABLE_ACTIONS]
+ * [NFL_TABLE_ATTR_ACTIONS]
+ * [NFL_ACTION]
+ * [NFL_ACTION_ATTR_NAME]
+ * [NFL_ACTION_ATTR_UID]
+ * [NFL_ACTION_ATTR_SIGNATURE]
+ * [NFL_ACTION_ARG]
+ * [NFL_ACTION_ARG_NAME]
+ * [NFL_ACTION_ARG_TYPE]
+ * [...]
+ * [NFL_ACTION]
+ * [...]
+ *
+ * Then two get definitions for the headers graph and the table graph
+ * The header graph gives an encoded graph to describe how the device
+ * parses the headers. Use this to learn if a specific protocol is
+ * supported in the current device configuration. The table graph
+ * reports how tables are traversed by packets.
+ *
+ * Get Headers Graph <Request> only requires msg preamble.
+ *
+ * Get Headers Graph <Reply> description
+ *
+ * [NFL_HEADER_GRAPH]
+ * [NFL_HEADER_GRAPH_NODE]
+ * [NFL_HEADER_NODE_NAME]
+ * [NFL_HEADER_NODE_HDRS]
+ * [NFL_HEADER_NODE_HDRS_VALUE]
+ * [...]
+ * [NFL_HEADER_NODE_JUMP]]
+ * [NFL_JUMP_ENTRY]
+ * [NFL_FIELD_REF_NEXT_NODE]
+ * [NFL_FIELD_REF_INSTANCE]
+ * [NFL_FIELD_REF_HEADER]
+ * [NFL_FIELD_REF_FIELD]
+ * [NFL_FIELD_REF_MASK]
+ * [NFL_FIELD_REF_TYPE]
+ * [NFL_FIELD_REF_VALUE]
+ * [NFL_FIELD_REF_MASK]
+ * [...]
+ * [NFL_HEADER_GRAPH_NODE]
+ * [
+ *
+ * Get Table Graph <Request> only requires msg preamble.
+ *
+ * Get Table Graph <Reply> description
+ *
+ * [NFL_TABLE_GRAPH]
+ * [NFL_TABLE_GRAPH_NODE]
+ * [NFL_TABLE_GRAPH_NODE_UID]
+ * [NFL_TABLE_GRAPH_NODE_JUMP]
+ * [NFL_JUMP_ENTRY]
+ * [NFL_FIELD_REF_NEXT_NODE]
+ * [NFL_FIELD_REF_INSTANCE]
+ * [NFL_FIELD_REF_HEADER]
+ * [NFL_FIELD_REF_FIELD]
+ * [NFL_FIELD_REF_MASK]
+ * [NFL_FIELD_REF_TYPE]
+ * [NFL_FIELD_REF_VALUE]
+ * [NFL_FIELD_REF_MASK]
+ * [...]
+ * [NFL_TABLE_GRAPH_NODE]
+ * [..]
+ */
+
+#ifndef _UAPI_LINUX_IF_FLOW
+#define _UAPI_LINUX_IF_FLOW
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/if.h>
+
+enum {
+ NFL_FIELD_UNSPEC,
+ NFL_FIELD,
+ __NFL_FIELD_MAX,
+};
+
+#define NFL_FIELD_MAX (__NFL_FIELD_MAX - 1)
+
+enum {
+ NFL_FIELD_ATTR_UNSPEC,
+ NFL_FIELD_ATTR_NAME,
+ NFL_FIELD_ATTR_UID,
+ NFL_FIELD_ATTR_BITWIDTH,
+ __NFL_FIELD_ATTR_MAX,
+};
+
+#define NFL_FIELD_ATTR_MAX (__NFL_FIELD_ATTR_MAX - 1)
+
+enum {
+ NFL_HEADER_UNSPEC,
+ NFL_HEADER,
+ __NFL_HEADER_MAX,
+};
+
+#define NFL_HEADER_MAX (__NFL_HEADER_MAX - 1)
+
+enum {
+ NFL_HEADER_ATTR_UNSPEC,
+ NFL_HEADER_ATTR_NAME,
+ NFL_HEADER_ATTR_UID,
+ NFL_HEADER_ATTR_FIELDS,
+ __NFL_HEADER_ATTR_MAX,
+};
+
+#define NFL_HEADER_ATTR_MAX (__NFL_HEADER_ATTR_MAX - 1)
+
+enum {
+ NFL_MASK_TYPE_UNSPEC,
+ NFL_MASK_TYPE_EXACT,
+ NFL_MASK_TYPE_LPM,
+ NFL_MASK_TYPE_MASK,
+};
+
+enum {
+ NFL_FIELD_REF_UNSPEC,
+ NFL_FIELD_REF_NEXT_NODE,
+ NFL_FIELD_REF_INSTANCE,
+ NFL_FIELD_REF_HEADER,
+ NFL_FIELD_REF_FIELD,
+ NFL_FIELD_REF_MASK_TYPE,
+ NFL_FIELD_REF_TYPE,
+ NFL_FIELD_REF_VALUE,
+ NFL_FIELD_REF_MASK,
+ __NFL_FIELD_REF_MAX,
+};
+
+#define NFL_FIELD_REF_MAX (__NFL_FIELD_REF_MAX - 1)
+
+enum {
+ NFL_FIELD_REFS_UNSPEC,
+ NFL_FIELD_REF,
+ __NFL_FIELD_REFS_MAX,
+};
+
+#define NFL_FIELD_REFS_MAX (__NFL_FIELD_REFS_MAX - 1)
+
+enum {
+ NFL_FIELD_REF_ATTR_TYPE_UNSPEC,
+ NFL_FIELD_REF_ATTR_TYPE_U8,
+ NFL_FIELD_REF_ATTR_TYPE_U16,
+ NFL_FIELD_REF_ATTR_TYPE_U32,
+ NFL_FIELD_REF_ATTR_TYPE_U64,
+};
+
+enum net_flow_action_arg_type {
+ NFL_ACTION_ARG_TYPE_NULL,
+ NFL_ACTION_ARG_TYPE_U8,
+ NFL_ACTION_ARG_TYPE_U16,
+ NFL_ACTION_ARG_TYPE_U32,
+ NFL_ACTION_ARG_TYPE_U64,
+ __NFL_ACTION_ARG_TYPE_VAL_MAX,
+};
+
+enum {
+ NFL_ACTION_ARG_UNSPEC,
+ NFL_ACTION_ARG_NAME,
+ NFL_ACTION_ARG_TYPE,
+ NFL_ACTION_ARG_VALUE,
+ __NFL_ACTION_ARG_MAX,
+};
+
+#define NFL_ACTION_ARG_MAX (__NFL_ACTION_ARG_MAX - 1)
+
+enum {
+ NFL_ACTION_ARGS_UNSPEC,
+ NFL_ACTION_ARG,
+ __NFL_ACTION_ARGS_MAX,
+};
+
+#define NFL_ACTION_ARGS_MAX (__NFL_ACTION_ARGS_MAX - 1)
+
+enum {
+ NFL_ACTION_UNSPEC,
+ NFL_ACTION,
+ __NFL_ACTION_MAX,
+};
+
+#define NFL_ACTION_MAX (__NFL_ACTION_MAX - 1)
+
+enum {
+ NFL_ACTION_ATTR_UNSPEC,
+ NFL_ACTION_ATTR_NAME,
+ NFL_ACTION_ATTR_UID,
+ NFL_ACTION_ATTR_SIGNATURE,
+ __NFL_ACTION_ATTR_MAX,
+};
+
+#define NFL_ACTION_ATTR_MAX (__NFL_ACTION_ATTR_MAX - 1)
+
+enum {
+ NFL_ACTION_SET_UNSPEC,
+ NFL_ACTION_SET_ACTIONS,
+ __NFL_ACTION_SET_MAX,
+};
+
+#define NFL_ACTION_SET_MAX (__NFL_ACTION_SET_MAX - 1)
+
+enum {
+ NFL_TABLE_UNSPEC,
+ NFL_TABLE,
+ __NFL_TABLE_MAX,
+};
+
+#define NFL_TABLE_MAX (__NFL_TABLE_MAX - 1)
+
+enum {
+ NFL_TABLE_ATTR_UNSPEC,
+ NFL_TABLE_ATTR_NAME,
+ NFL_TABLE_ATTR_UID,
+ NFL_TABLE_ATTR_SOURCE,
+ NFL_TABLE_ATTR_APPLY,
+ NFL_TABLE_ATTR_SIZE,
+ NFL_TABLE_ATTR_MATCHES,
+ NFL_TABLE_ATTR_ACTIONS,
+ __NFL_TABLE_ATTR_MAX,
+};
+
+#define NFL_TABLE_ATTR_MAX (__NFL_TABLE_ATTR_MAX - 1)
+
+#define NFL_JUMP_TABLE_DONE 0
+enum {
+ NFL_JUMP_ENTRY_UNSPEC,
+ NFL_JUMP_ENTRY,
+ __NFL_JUMP_ENTRY_MAX,
+};
+
+enum {
+ NFL_HEADER_NODE_HDRS_UNSPEC,
+ NFL_HEADER_NODE_HDRS_VALUE,
+ __NFL_HEADER_NODE_HDRS_MAX,
+};
+
+#define NFL_HEADER_NODE_HDRS_MAX (__NFL_HEADER_NODE_HDRS_MAX - 1)
+
+enum {
+ NFL_HEADER_NODE_UNSPEC,
+ NFL_HEADER_NODE_NAME,
+ NFL_HEADER_NODE_UID,
+ NFL_HEADER_NODE_HDRS,
+ NFL_HEADER_NODE_JUMP,
+ __NFL_HEADER_NODE_MAX,
+};
+
+#define NFL_HEADER_NODE_MAX (__NFL_HEADER_NODE_MAX - 1)
+
+enum {
+ NFL_HEADER_GRAPH_UNSPEC,
+ NFL_HEADER_GRAPH_NODE,
+ __NFL_HEADER_GRAPH_MAX,
+};
+
+#define NFL_HEADER_GRAPH_MAX (__NFL_HEADER_GRAPH_MAX - 1)
+
+#define NFL_TABLE_EGRESS_ROOT 1
+#define NFL_TABLE_INGRESS_ROOT 2
+
+enum {
+ NFL_TABLE_GRAPH_NODE_UNSPEC,
+ NFL_TABLE_GRAPH_NODE_UID,
+ NFL_TABLE_GRAPH_NODE_FLAGS,
+ NFL_TABLE_GRAPH_NODE_JUMP,
+ __NFL_TABLE_GRAPH_NODE_MAX,
+};
+
+#define NFL_TABLE_GRAPH_NODE_MAX (__NFL_TABLE_GRAPH_NODE_MAX - 1)
+
+enum {
+ NFL_TABLE_GRAPH_UNSPEC,
+ NFL_TABLE_GRAPH_NODE,
+ __NFL_TABLE_GRAPH_MAX,
+};
+
+#define NFL_TABLE_GRAPH_MAX (__NFL_TABLE_GRAPH_MAX - 1)
+
+enum {
+ NFL_NFL_UNSPEC,
+ NFL_FLOW,
+ __NFL_NFL_MAX,
+};
+
+#define NFL_NFL_MAX (__NFL_NFL_MAX - 1)
+
+enum {
+ NFL_IDENTIFIER_UNSPEC,
+ NFL_IDENTIFIER_IFINDEX, /* net_device ifindex */
+};
+
+enum {
+ NFL_UNSPEC,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER,
+
+ NFL_TABLES,
+ NFL_HEADERS,
+ NFL_ACTIONS,
+ NFL_HEADER_GRAPH,
+ NFL_TABLE_GRAPH,
+
+ __NFL_MAX,
+ NFL_MAX = (__NFL_MAX - 1),
+};
+
+enum {
+ NFL_TABLE_CMD_GET_TABLES,
+ NFL_TABLE_CMD_GET_HEADERS,
+ NFL_TABLE_CMD_GET_ACTIONS,
+ NFL_TABLE_CMD_GET_HDR_GRAPH,
+ NFL_TABLE_CMD_GET_TABLE_GRAPH,
+
+ __NFL_CMD_MAX,
+ NFL_CMD_MAX = (__NFL_CMD_MAX - 1),
+};
+
+#define NFL_GENL_NAME "net_flow_nl"
+#define NFL_GENL_VERSION 0x1
+#endif /* _UAPI_LINUX_IF_FLOW */
diff --git a/net/Kconfig b/net/Kconfig
index ff9ffc1..8380bfe 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -293,6 +293,13 @@ config NET_FLOW_LIMIT
with many clients some protection against DoS by a single (spoofed)
flow that greatly exceeds average workload.
+config NET_FLOW_TABLES
+ boolean "Support network flow tables"
+ ---help---
+ This feature provides an interface for device drivers to report
+ flow tables and supported matches and actions. If you do not
+ want to support hardware offloads for flow tables, say N here.
+
menu "Network testing"
config NET_PKTGEN
diff --git a/net/core/Makefile b/net/core/Makefile
index 235e6c5..1eea785 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += timestamping.o
obj-$(CONFIG_NET_PTP_CLASSIFY) += ptp_classifier.o
obj-$(CONFIG_CGROUP_NET_PRIO) += netprio_cgroup.o
obj-$(CONFIG_CGROUP_NET_CLASSID) += netclassid_cgroup.o
+obj-$(CONFIG_NET_FLOW_TABLES) += flow_table.o
diff --git a/net/core/flow_table.c b/net/core/flow_table.c
new file mode 100644
index 0000000..f994acb
--- /dev/null
+++ b/net/core/flow_table.c
@@ -0,0 +1,942 @@
+/*
+ * net/core/flow_table.c - Flow table interface for Switch devices
+ * Copyright (c) 2014 John Fastabend <john.r.fastabend@intel.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Author: John Fastabend <john.r.fastabend@intel.com>
+ */
+
+#include <uapi/linux/if_flow.h>
+#include <linux/if_flow.h>
+#include <linux/if_bridge.h>
+#include <linux/types.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include <net/rtnetlink.h>
+#include <linux/module.h>
+
+static struct genl_family net_flow_nl_family = {
+ .id = GENL_ID_GENERATE,
+ .name = NFL_GENL_NAME,
+ .version = NFL_GENL_VERSION,
+ .maxattr = NFL_MAX,
+ .netnsok = true,
+};
+
+static struct net_device *net_flow_get_dev(struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ int type, ifindex;
+
+ if (!info->attrs[NFL_IDENTIFIER_TYPE] ||
+ !info->attrs[NFL_IDENTIFIER])
+ return NULL;
+
+ type = nla_get_u32(info->attrs[NFL_IDENTIFIER_TYPE]);
+ switch (type) {
+ case NFL_IDENTIFIER_IFINDEX:
+ ifindex = nla_get_u32(info->attrs[NFL_IDENTIFIER]);
+ break;
+ default:
+ return NULL;
+ }
+
+ return dev_get_by_index(net, ifindex);
+}
+
+static int net_flow_put_act_types(struct sk_buff *skb,
+ struct net_flow_action_arg *args)
+{
+ struct nlattr *arg;
+ int i, err;
+
+ for (i = 0; args[i].type; i++) {
+ arg = nla_nest_start(skb, NFL_ACTION_ARG);
+ if (!arg)
+ return -EMSGSIZE;
+
+ if (args[i].name) {
+ err = nla_put_string(skb, NFL_ACTION_ARG_NAME,
+ args[i].name);
+ if (err)
+ goto out;
+ }
+
+ err = nla_put_u32(skb, NFL_ACTION_ARG_TYPE, args[i].type);
+ if (err)
+ goto out;
+
+ nla_nest_end(skb, arg);
+ }
+ return 0;
+out:
+ nla_nest_cancel(skb, arg);
+ return err;
+}
+
+static const
+struct nla_policy net_flow_action_policy[NFL_ACTION_ATTR_MAX + 1] = {
+ [NFL_ACTION_ATTR_NAME] = {.type = NLA_STRING },
+ [NFL_ACTION_ATTR_UID] = {.type = NLA_U32 },
+ [NFL_ACTION_ATTR_SIGNATURE] = {.type = NLA_NESTED },
+};
+
+static int net_flow_put_action(struct sk_buff *skb, struct net_flow_action *a)
+{
+ struct nlattr *nest;
+ int err;
+
+ if (a->name && nla_put_string(skb, NFL_ACTION_ATTR_NAME, a->name))
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, NFL_ACTION_ATTR_UID, a->uid))
+ return -EMSGSIZE;
+
+ if (a->args) {
+ nest = nla_nest_start(skb, NFL_ACTION_ATTR_SIGNATURE);
+ if (!nest)
+ return -EMSGSIZE;
+
+ err = net_flow_put_act_types(skb, a->args);
+ if (err) {
+ nla_nest_cancel(skb, nest);
+ return err;
+ }
+ nla_nest_end(skb, nest);
+ }
+
+ return 0;
+}
+
+static int net_flow_put_actions(struct sk_buff *skb,
+ struct net_flow_action **acts)
+{
+ struct nlattr *actions;
+ int i, err;
+
+ actions = nla_nest_start(skb, NFL_ACTIONS);
+ if (!actions)
+ return -EMSGSIZE;
+
+ for (i = 0; acts[i]; i++) {
+ struct nlattr *action = nla_nest_start(skb, NFL_ACTION);
+
+ if (!action)
+ goto action_put_failure;
+
+ err = net_flow_put_action(skb, acts[i]);
+ if (err)
+ goto action_put_failure;
+ nla_nest_end(skb, action);
+ }
+ nla_nest_end(skb, actions);
+
+ return 0;
+action_put_failure:
+ nla_nest_cancel(skb, actions);
+ return -EMSGSIZE;
+}
+
+static struct sk_buff *net_flow_build_actions_msg(struct net_flow_action **a,
+ struct net_device *dev,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ err = net_flow_put_actions(skb, a);
+ if (err < 0)
+ goto out;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static int net_flow_cmd_get_actions(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net_flow_action **a;
+ struct net_device *dev;
+ struct sk_buff *msg;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->netdev_ops->ndo_flow_get_actions) {
+ dev_put(dev);
+ return -EOPNOTSUPP;
+ }
+
+ a = dev->netdev_ops->ndo_flow_get_actions(dev);
+ if (!a) {
+ dev_put(dev);
+ return -EBUSY;
+ }
+
+ msg = net_flow_build_actions_msg(a, dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_ACTIONS);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+}
+
+static int net_flow_put_field_ref(struct sk_buff *skb,
+ struct net_flow_field_ref *ref)
+{
+ if (nla_put_u32(skb, NFL_FIELD_REF_INSTANCE, ref->instance) ||
+ nla_put_u32(skb, NFL_FIELD_REF_HEADER, ref->header) ||
+ nla_put_u32(skb, NFL_FIELD_REF_FIELD, ref->field) ||
+ nla_put_u32(skb, NFL_FIELD_REF_MASK_TYPE, ref->mask_type) ||
+ nla_put_u32(skb, NFL_FIELD_REF_TYPE, ref->type))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int net_flow_put_field_value(struct sk_buff *skb,
+ struct net_flow_field_ref *r)
+{
+ int err = -EINVAL;
+
+ switch (r->type) {
+ case NFL_FIELD_REF_ATTR_TYPE_UNSPEC:
+ err = 0;
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U8:
+ err = nla_put_u8(skb, NFL_FIELD_REF_VALUE, r->value_u8);
+ if (err)
+ break;
+ err = nla_put_u8(skb, NFL_FIELD_REF_MASK, r->mask_u8);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U16:
+ err = nla_put_u16(skb, NFL_FIELD_REF_VALUE, r->value_u16);
+ if (err)
+ break;
+ err = nla_put_u16(skb, NFL_FIELD_REF_MASK, r->mask_u16);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U32:
+ err = nla_put_u32(skb, NFL_FIELD_REF_VALUE, r->value_u32);
+ if (err)
+ break;
+ err = nla_put_u32(skb, NFL_FIELD_REF_MASK, r->mask_u32);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U64:
+ err = nla_put_u64(skb, NFL_FIELD_REF_VALUE, r->value_u64);
+ if (err)
+ break;
+ err = nla_put_u64(skb, NFL_FIELD_REF_MASK, r->mask_u64);
+ break;
+ default:
+ break;
+ }
+ return err;
+}
+
+static int net_flow_put_table(struct net_device *dev,
+ struct sk_buff *skb,
+ struct net_flow_tbl *t)
+{
+ struct nlattr *matches, *actions, *field;
+ int i, err;
+
+ if (nla_put_string(skb, NFL_TABLE_ATTR_NAME, t->name) ||
+ nla_put_u32(skb, NFL_TABLE_ATTR_UID, t->uid) ||
+ nla_put_u32(skb, NFL_TABLE_ATTR_SOURCE, t->source) ||
+ nla_put_u32(skb, NFL_TABLE_ATTR_APPLY, t->apply_action) ||
+ nla_put_u32(skb, NFL_TABLE_ATTR_SIZE, t->size))
+ return -EMSGSIZE;
+
+ matches = nla_nest_start(skb, NFL_TABLE_ATTR_MATCHES);
+ if (!matches)
+ return -EMSGSIZE;
+
+ for (i = 0; t->matches[i].instance; i++) {
+ field = nla_nest_start(skb, NFL_FIELD_REF);
+
+ err = net_flow_put_field_ref(skb, &t->matches[i]);
+ if (err) {
+ nla_nest_cancel(skb, matches);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, field);
+ }
+ nla_nest_end(skb, matches);
+
+ actions = nla_nest_start(skb, NFL_TABLE_ATTR_ACTIONS);
+ if (!actions)
+ return -EMSGSIZE;
+
+ for (i = 0; t->actions[i]; i++) {
+ if (nla_put_u32(skb,
+ NFL_ACTION_ATTR_UID,
+ t->actions[i])) {
+ nla_nest_cancel(skb, actions);
+ return -EMSGSIZE;
+ }
+ }
+ nla_nest_end(skb, actions);
+
+ return 0;
+}
+
+static int net_flow_put_tables(struct net_device *dev,
+ struct sk_buff *skb,
+ struct net_flow_tbl **tables)
+{
+ struct nlattr *nest, *t;
+ int i, err = 0;
+
+ nest = nla_nest_start(skb, NFL_TABLES);
+ if (!nest)
+ return -EMSGSIZE;
+
+ for (i = 0; tables[i]; i++) {
+ t = nla_nest_start(skb, NFL_TABLE);
+ if (!t) {
+ err = -EMSGSIZE;
+ goto errout;
+ }
+
+ err = net_flow_put_table(dev, skb, tables[i]);
+ if (err) {
+ nla_nest_cancel(skb, t);
+ goto errout;
+ }
+ nla_nest_end(skb, t);
+ }
+ nla_nest_end(skb, nest);
+ return 0;
+errout:
+ nla_nest_cancel(skb, nest);
+ return err;
+}
+
+static struct sk_buff *net_flow_build_tables_msg(struct net_flow_tbl **t,
+ struct net_device *dev,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ err = net_flow_put_tables(dev, skb, t);
+ if (err < 0)
+ goto out;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static int net_flow_cmd_get_tables(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net_flow_tbl **tables;
+ struct net_device *dev;
+ struct sk_buff *msg;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->netdev_ops->ndo_flow_get_tbls) {
+ dev_put(dev);
+ return -EOPNOTSUPP;
+ }
+
+ tables = dev->netdev_ops->ndo_flow_get_tbls(dev);
+ if (!tables) {
+ dev_put(dev);
+ return -EBUSY;
+ }
+
+ msg = net_flow_build_tables_msg(tables, dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_TABLES);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+}
+
+static
+int net_flow_put_fields(struct sk_buff *skb, const struct net_flow_hdr *h)
+{
+ struct net_flow_field *f;
+ int count = h->field_sz;
+ struct nlattr *field;
+
+ for (f = h->fields; count; count--, f++) {
+ field = nla_nest_start(skb, NFL_FIELD);
+ if (!field)
+ goto field_put_failure;
+
+ if (nla_put_string(skb, NFL_FIELD_ATTR_NAME, f->name) ||
+ nla_put_u32(skb, NFL_FIELD_ATTR_UID, f->uid) ||
+ nla_put_u32(skb, NFL_FIELD_ATTR_BITWIDTH, f->bitwidth))
+ goto out;
+
+ nla_nest_end(skb, field);
+ }
+
+ return 0;
+out:
+ nla_nest_cancel(skb, field);
+field_put_failure:
+ return -EMSGSIZE;
+}
+
+static int net_flow_put_headers(struct sk_buff *skb,
+ struct net_flow_hdr **headers)
+{
+ struct nlattr *nest, *hdr, *fields;
+ struct net_flow_hdr *h;
+ int i, err;
+
+ nest = nla_nest_start(skb, NFL_HEADERS);
+ if (!nest)
+ return -EMSGSIZE;
+
+ for (i = 0; headers[i]; i++) {
+ err = -EMSGSIZE;
+ h = headers[i];
+
+ hdr = nla_nest_start(skb, NFL_HEADER);
+ if (!hdr)
+ goto put_failure;
+
+ if (nla_put_string(skb, NFL_HEADER_ATTR_NAME, h->name) ||
+ nla_put_u32(skb, NFL_HEADER_ATTR_UID, h->uid))
+ goto put_failure;
+
+ fields = nla_nest_start(skb, NFL_HEADER_ATTR_FIELDS);
+ if (!fields)
+ goto put_failure;
+
+ err = net_flow_put_fields(skb, h);
+ if (err)
+ goto put_failure;
+
+ nla_nest_end(skb, fields);
+
+ nla_nest_end(skb, hdr);
+ }
+ nla_nest_end(skb, nest);
+
+ return 0;
+put_failure:
+ nla_nest_cancel(skb, nest);
+ return err;
+}
+
+static struct sk_buff *net_flow_build_headers_msg(struct net_flow_hdr **h,
+ struct net_device *dev,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ err = net_flow_put_headers(skb, h);
+ if (err < 0)
+ goto out;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static int net_flow_cmd_get_headers(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net_flow_hdr **h;
+ struct net_device *dev;
+ struct sk_buff *msg;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->netdev_ops->ndo_flow_get_hdrs) {
+ dev_put(dev);
+ return -EOPNOTSUPP;
+ }
+
+ h = dev->netdev_ops->ndo_flow_get_hdrs(dev);
+ if (!h) {
+ dev_put(dev);
+ return -EBUSY;
+ }
+
+ msg = net_flow_build_headers_msg(h, dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_HEADERS);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+}
+
+static int net_flow_put_header_node(struct sk_buff *skb,
+ struct net_flow_hdr_node *node)
+{
+ struct nlattr *hdrs, *jumps;
+ int i, err;
+
+ if (nla_put_string(skb, NFL_HEADER_NODE_NAME, node->name) ||
+ nla_put_u32(skb, NFL_HEADER_NODE_UID, node->uid))
+ return -EMSGSIZE;
+
+ /* Insert the set of headers that get extracted at this node */
+ hdrs = nla_nest_start(skb, NFL_HEADER_NODE_HDRS);
+ if (!hdrs)
+ return -EMSGSIZE;
+ for (i = 0; node->hdrs[i]; i++) {
+ if (nla_put_u32(skb, NFL_HEADER_NODE_HDRS_VALUE,
+ node->hdrs[i])) {
+ nla_nest_cancel(skb, hdrs);
+ return -EMSGSIZE;
+ }
+ }
+ nla_nest_end(skb, hdrs);
+
+ /* Then give the jump table to find next header node in graph */
+ jumps = nla_nest_start(skb, NFL_HEADER_NODE_JUMP);
+ if (!jumps)
+ return -EMSGSIZE;
+
+ for (i = 0; node->jump[i].node; i++) {
+ struct nlattr *entry;
+
+ entry = nla_nest_start(skb, NFL_JUMP_ENTRY);
+ if (!entry) {
+ nla_nest_cancel(skb, jumps);
+ return -EMSGSIZE;
+ }
+
+ err = nla_put_u32(skb, NFL_FIELD_REF_NEXT_NODE,
+ node->jump[i].node);
+ if (err) {
+ nla_nest_cancel(skb, jumps);
+ return err;
+ }
+
+ err = net_flow_put_field_ref(skb, &node->jump[i].field);
+ if (err) {
+ nla_nest_cancel(skb, jumps);
+ return err;
+ }
+
+ err = net_flow_put_field_value(skb, &node->jump[i].field);
+ if (err) {
+ nla_nest_cancel(skb, jumps);
+ return err;
+ }
+ nla_nest_end(skb, entry);
+ }
+ nla_nest_end(skb, jumps);
+
+ return 0;
+}
+
+static int net_flow_put_header_graph(struct sk_buff *skb,
+ struct net_flow_hdr_node **g)
+{
+ struct nlattr *nodes, *node;
+ int i, err;
+
+ nodes = nla_nest_start(skb, NFL_HEADER_GRAPH);
+ if (!nodes)
+ return -EMSGSIZE;
+
+ for (i = 0; g[i]; i++) {
+ node = nla_nest_start(skb, NFL_HEADER_GRAPH_NODE);
+ if (!node) {
+ err = -EMSGSIZE;
+ goto nodes_put_error;
+ }
+
+ err = net_flow_put_header_node(skb, g[i]);
+ if (err)
+ goto nodes_put_error;
+
+ nla_nest_end(skb, node);
+ }
+
+ nla_nest_end(skb, nodes);
+ return 0;
+nodes_put_error:
+ nla_nest_cancel(skb, nodes);
+ return err;
+}
+
+static
+struct sk_buff *net_flow_build_header_graph_msg(struct net_flow_hdr_node **g,
+ struct net_device *dev,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ err = net_flow_put_header_graph(skb, g);
+ if (err < 0)
+ goto out;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static int net_flow_cmd_get_header_graph(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net_flow_hdr_node **h;
+ struct net_device *dev;
+ struct sk_buff *msg;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->netdev_ops->ndo_flow_get_hdr_graph) {
+ dev_put(dev);
+ return -EOPNOTSUPP;
+ }
+
+ h = dev->netdev_ops->ndo_flow_get_hdr_graph(dev);
+ if (!h) {
+ dev_put(dev);
+ return -EBUSY;
+ }
+
+ msg = net_flow_build_header_graph_msg(h, dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_HDR_GRAPH);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+}
+
+static int net_flow_put_table_node(struct sk_buff *skb,
+ struct net_flow_tbl_node *node)
+{
+ struct nlattr *nest, *jump;
+ int i, err = -EMSGSIZE;
+
+ nest = nla_nest_start(skb, NFL_TABLE_GRAPH_NODE);
+ if (!nest)
+ return err;
+
+ if (nla_put_u32(skb, NFL_TABLE_GRAPH_NODE_UID, node->uid) ||
+ nla_put_u32(skb, NFL_TABLE_GRAPH_NODE_FLAGS, node->flags))
+ goto node_put_failure;
+
+ jump = nla_nest_start(skb, NFL_TABLE_GRAPH_NODE_JUMP);
+ if (!jump)
+ goto node_put_failure;
+
+ for (i = 0; node->jump[i].node; i++) {
+ struct nlattr *entry;
+
+ entry = nla_nest_start(skb, NFL_JUMP_ENTRY);
+ if (!entry)
+ goto node_put_failure;
+
+ err = nla_put_u32(skb, NFL_FIELD_REF_NEXT_NODE,
+ node->jump[i].node);
+ if (err) {
+ nla_nest_cancel(skb, jump);
+ return err;
+ }
+
+ err = net_flow_put_field_ref(skb, &node->jump[i].field);
+ if (err)
+ goto node_put_failure;
+
+ err = net_flow_put_field_value(skb, &node->jump[i].field);
+ if (err)
+ goto node_put_failure;
+
+ nla_nest_end(skb, entry);
+ }
+
+ nla_nest_end(skb, jump);
+ nla_nest_end(skb, nest);
+ return 0;
+node_put_failure:
+ nla_nest_cancel(skb, nest);
+ return err;
+}
+
+static int net_flow_put_table_graph(struct sk_buff *skb,
+ struct net_flow_tbl_node **nodes)
+{
+ struct nlattr *graph;
+ int i, err;
+
+ graph = nla_nest_start(skb, NFL_TABLE_GRAPH);
+ if (!graph)
+ return -EMSGSIZE;
+
+ for (i = 0; nodes[i]; i++) {
+ err = net_flow_put_table_node(skb, nodes[i]);
+ if (err) {
+ nla_nest_cancel(skb, graph);
+ return -EMSGSIZE;
+ }
+ }
+
+ nla_nest_end(skb, graph);
+ return 0;
+}
+
+static
+struct sk_buff *net_flow_build_graph_msg(struct net_flow_tbl_node **g,
+ struct net_device *dev,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *hdr;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ err = net_flow_put_table_graph(skb, g);
+ if (err < 0)
+ goto out;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static int net_flow_cmd_get_table_graph(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net_flow_tbl_node **g;
+ struct net_device *dev;
+ struct sk_buff *msg;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->netdev_ops->ndo_flow_get_tbl_graph) {
+ dev_put(dev);
+ return -EOPNOTSUPP;
+ }
+
+ g = dev->netdev_ops->ndo_flow_get_tbl_graph(dev);
+ if (!g) {
+ dev_put(dev);
+ return -EBUSY;
+ }
+
+ msg = net_flow_build_graph_msg(g, dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_TABLE_GRAPH);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+}
+
+static const struct nla_policy net_flow_cmd_policy[NFL_MAX + 1] = {
+ [NFL_IDENTIFIER_TYPE] = {.type = NLA_U32, },
+ [NFL_IDENTIFIER] = {.type = NLA_U32, },
+ [NFL_TABLES] = {.type = NLA_NESTED, },
+ [NFL_HEADERS] = {.type = NLA_NESTED, },
+ [NFL_ACTIONS] = {.type = NLA_NESTED, },
+ [NFL_HEADER_GRAPH] = {.type = NLA_NESTED, },
+ [NFL_TABLE_GRAPH] = {.type = NLA_NESTED, },
+};
+
+static const struct genl_ops net_flow_table_nl_ops[] = {
+ {
+ .cmd = NFL_TABLE_CMD_GET_TABLES,
+ .doit = net_flow_cmd_get_tables,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_GET_HEADERS,
+ .doit = net_flow_cmd_get_headers,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_GET_ACTIONS,
+ .doit = net_flow_cmd_get_actions,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_GET_HDR_GRAPH,
+ .doit = net_flow_cmd_get_header_graph,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_GET_TABLE_GRAPH,
+ .doit = net_flow_cmd_get_table_graph,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+};
+
+static int __init net_flow_nl_module_init(void)
+{
+ return genl_register_family_with_ops(&net_flow_nl_family,
+ net_flow_table_nl_ops);
+}
+
+static void net_flow_nl_module_fini(void)
+{
+ genl_unregister_family(&net_flow_nl_family);
+}
+
+module_init(net_flow_nl_module_init);
+module_exit(net_flow_nl_module_fini);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("John Fastabend <john.r.fastabend@intel.com>");
+MODULE_DESCRIPTION("Netlink interface to Flow Tables (Net Flow Netlink)");
+MODULE_ALIAS_GENL_FAMILY(NFL_GENL_NAME);
^ permalink raw reply related
* [net-next PATCH v3 02/12] net: flow_table: add rule, delete rule
From: John Fastabend @ 2015-01-20 20:27 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Now that the device capabilities are exposed we can add support to
add and delete rules from the tables. Rules are a set of matches
to run on packets and a set of actions to perform when the match
occurs.
The two operations are
set_rule :
The set rule operations is used to program a set of rules into a
hardware device table. The message is consumed via netlink encoded
message which is then decoded into a null terminated array of
rule entry structures. A rule entry structure is defined as
struct net_flow_rule {
__u32 table_id;
__u32 uid;
__u32 priority;
struct net_flow_field_ref *matches;
struct net_flow_action *actions;
}
The table id is the _uid_ returned from 'get_tables' operations.
Matches is a set of match criteria for packets with a logical AND
operation done on the set so packets match the entire criteria.
Actions provide a set of actions to perform when the flow rule is
hit. Both matches and actions are null terminated arrays.
The rules are configured in hardware using an ndo op. We do not
provide a commit operation at the moment and expect hardware
commits the rules one at a time. Future work may require a commit
operation to tell the hardware we are done loading rules. On some
hardware this will help bulk updates.
Its possible for hardware to return an error from a rule set
operation. This can occur for many reasons both transient and
resource constraints. We have different error handling strategies
built in and listed here,
*_ERROR_ABORT abort on first error with errmsg
*_ERROR_CONTINUE continue programming rules no errmsg
*_ERROR_ABORT_LOG abort on first error and return rule that
failed to user space in reply msg
*_ERROR_CONT_LOG continue programming rules and return a list
of rules that failed to user space in a reply
msg.
notably missing is a rollback error strategy. I don't have a
use for this in software yet but the strategy can be added with
*_ERROR_ROLLBACK for example.
del_rule :
The delete rule operation uses the same structures and error
handling strategies as the set_rule operation. Although delete
messges ommit the matches/actions arrays because they are not
needed to lookup the flow.
Also thanks to Simon Horman for fixes and other help.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
include/linux/if_flow.h | 21 +
include/linux/netdevice.h | 10 +
include/uapi/linux/if_flow.h | 51 +++
net/core/flow_table.c | 760 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 841 insertions(+), 1 deletion(-)
diff --git a/include/linux/if_flow.h b/include/linux/if_flow.h
index 7ce1e1d..712b54f 100644
--- a/include/linux/if_flow.h
+++ b/include/linux/if_flow.h
@@ -185,4 +185,23 @@ struct net_flow_tbl_node {
__u32 flags;
struct net_flow_jump_table *jump;
};
-#endif
+
+/**
+ * @struct net_flow_rule
+ * @brief describes the match/action entry
+ *
+ * @uid unique identifier for flow
+ * @priority priority to execute flow match/action in table
+ * @match null terminated set of match uids match criteria
+ * @action null terminated set of action uids to apply to match
+ *
+ * Flows must match all entries in match set.
+ */
+struct net_flow_rule {
+ __u32 table_id;
+ __u32 uid;
+ __u32 priority;
+ struct net_flow_field_ref *matches;
+ struct net_flow_action *actions;
+};
+#endif /* _IF_FLOW_H_ */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 74481b9..9d57f8b 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1061,6 +1061,12 @@ typedef u16 (*select_queue_fallback_t)(struct net_device *dev,
* Report a null terminated list of nodes defining the header graph. This
* provides the necessary graph to learn the ordering of headers supported
* by the device.
+ *
+ * int (*ndo_flow_set_rule)(struct net_device *dev, struct net_flow_rule *f)
+ * This is used to program a rule into a device table.
+ *
+ * int (*ndo_flow_del_rule)(struct net_device *dev, struct net_flow_rule *f)
+ * This is used to remove a rule from a device table.
*/
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
@@ -1227,6 +1233,10 @@ struct net_device_ops {
struct net_flow_tbl_node **(*ndo_flow_get_tbl_graph)(struct net_device *dev);
struct net_flow_hdr **(*ndo_flow_get_hdrs)(struct net_device *dev);
struct net_flow_hdr_node **(*ndo_flow_get_hdr_graph)(struct net_device *dev);
+ int (*ndo_flow_set_rule)(struct net_device *dev,
+ struct net_flow_rule *f);
+ int (*ndo_flow_del_rule)(struct net_device *dev,
+ struct net_flow_rule *f);
#endif
};
diff --git a/include/uapi/linux/if_flow.h b/include/uapi/linux/if_flow.h
index 3314aa2..989d019 100644
--- a/include/uapi/linux/if_flow.h
+++ b/include/uapi/linux/if_flow.h
@@ -149,6 +149,12 @@ enum {
#define NFL_FIELD_MAX (__NFL_FIELD_MAX - 1)
+/* Max length supported by kernel name strings only the first n characters
+ * will be used by the kernel API. This is to prevent arbitrarily long
+ * strings being passed from user space.
+ */
+#define NFL_MAX_NAME 80
+
enum {
NFL_FIELD_ATTR_UNSPEC,
NFL_FIELD_ATTR_NAME,
@@ -354,6 +360,44 @@ enum {
#define NFL_NFL_MAX (__NFL_NFL_MAX - 1)
enum {
+ NFL_TABLE_FLOWS_UNSPEC,
+ NFL_TABLE_FLOWS_TABLE,
+ NFL_TABLE_FLOWS_MINPRIO,
+ NFL_TABLE_FLOWS_MAXPRIO,
+ NFL_TABLE_FLOWS_FLOWS,
+ __NFL_TABLE_FLOWS_MAX,
+};
+
+#define NFL_TABLE_FLOWS_MAX (__NFL_TABLE_FLOWS_MAX - 1)
+
+enum {
+ /* Abort with normal errmsg */
+ NFL_FLOWS_ERROR_ABORT,
+ /* Ignore errors and continue without logging */
+ NFL_FLOWS_ERROR_CONTINUE,
+ /* Abort and reply with invalid flow fields */
+ NFL_FLOWS_ERROR_ABORT_LOG,
+ /* Continue and reply with list of invalid flows */
+ NFL_FLOWS_ERROR_CONT_LOG,
+ __NFLS_FLOWS_ERROR_MAX,
+};
+
+#define NFLS_FLOWS_ERROR_MAX (__NFLS_FLOWS_ERROR_MAX - 1)
+
+enum {
+ NFL_ATTR_UNSPEC,
+ NFL_ATTR_ERROR,
+ NFL_ATTR_TABLE,
+ NFL_ATTR_UID,
+ NFL_ATTR_PRIORITY,
+ NFL_ATTR_MATCHES,
+ NFL_ATTR_ACTIONS,
+ __NFL_ATTR_MAX,
+};
+
+#define NFL_ATTR_MAX (__NFL_ATTR_MAX - 1)
+
+enum {
NFL_IDENTIFIER_UNSPEC,
NFL_IDENTIFIER_IFINDEX, /* net_device ifindex */
};
@@ -369,6 +413,9 @@ enum {
NFL_HEADER_GRAPH,
NFL_TABLE_GRAPH,
+ NFL_FLOWS,
+ NFL_FLOWS_ERROR,
+
__NFL_MAX,
NFL_MAX = (__NFL_MAX - 1),
};
@@ -380,6 +427,10 @@ enum {
NFL_TABLE_CMD_GET_HDR_GRAPH,
NFL_TABLE_CMD_GET_TABLE_GRAPH,
+ NFL_TABLE_CMD_GET_FLOWS,
+ NFL_TABLE_CMD_SET_FLOWS,
+ NFL_TABLE_CMD_DEL_FLOWS,
+
__NFL_CMD_MAX,
NFL_CMD_MAX = (__NFL_CMD_MAX - 1),
};
diff --git a/net/core/flow_table.c b/net/core/flow_table.c
index f994acb..7b85e53 100644
--- a/net/core/flow_table.c
+++ b/net/core/flow_table.c
@@ -27,6 +27,18 @@
#include <net/rtnetlink.h>
#include <linux/module.h>
+static DEFINE_MUTEX(net_flow_mutex);
+
+void net_flow_lock(void)
+{
+ mutex_lock(&net_flow_mutex);
+}
+
+void net_flow_unlock(void)
+{
+ mutex_unlock(&net_flow_mutex);
+}
+
static struct genl_family net_flow_nl_family = {
.id = GENL_ID_GENERATE,
.name = NFL_GENL_NAME,
@@ -78,6 +90,34 @@ static int net_flow_put_act_types(struct sk_buff *skb,
if (err)
goto out;
+ switch (args[i].type) {
+ case NFL_ACTION_ARG_TYPE_NULL:
+ err = 0;
+ break;
+ case NFL_ACTION_ARG_TYPE_U8:
+ err = nla_put_u8(skb, NFL_ACTION_ARG_VALUE,
+ args[i].value_u8);
+ break;
+ case NFL_ACTION_ARG_TYPE_U16:
+ err = nla_put_u16(skb, NFL_ACTION_ARG_VALUE,
+ args[i].value_u16);
+ break;
+ case NFL_ACTION_ARG_TYPE_U32:
+ err = nla_put_u32(skb, NFL_ACTION_ARG_VALUE,
+ args[i].value_u32);
+ break;
+ case NFL_ACTION_ARG_TYPE_U64:
+ err = nla_put_u64(skb, NFL_ACTION_ARG_VALUE,
+ args[i].value_u64);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ if (err)
+ goto out;
+
nla_nest_end(skb, arg);
}
return 0;
@@ -879,6 +919,708 @@ static int net_flow_cmd_get_table_graph(struct sk_buff *skb,
return genlmsg_reply(msg, info);
}
+static int net_flow_put_flow_action(struct sk_buff *skb,
+ struct net_flow_action *a)
+{
+ struct nlattr *action, *sigs;
+ int err = 0;
+
+ action = nla_nest_start(skb, NFL_ACTION);
+ if (!action)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, NFL_ACTION_ATTR_UID, a->uid))
+ return -EMSGSIZE;
+
+ if (!a->args)
+ goto done;
+
+ sigs = nla_nest_start(skb, NFL_ACTION_ATTR_SIGNATURE);
+ if (!sigs) {
+ nla_nest_cancel(skb, action);
+ return -EMSGSIZE;
+ }
+
+ err = net_flow_put_act_types(skb, a->args);
+ if (err) {
+ nla_nest_cancel(skb, action);
+ return err;
+ }
+ nla_nest_end(skb, sigs);
+done:
+ nla_nest_end(skb, action);
+ return 0;
+}
+
+static int net_flow_put_rule(struct sk_buff *skb, struct net_flow_rule *rule)
+{
+ struct nlattr *flows, *actions, *matches;
+ int j, i = 0;
+ int err = -EMSGSIZE;
+
+ flows = nla_nest_start(skb, NFL_FLOW);
+ if (!flows)
+ goto put_failure;
+
+ if (nla_put_u32(skb, NFL_ATTR_TABLE, rule->table_id) ||
+ nla_put_u32(skb, NFL_ATTR_UID, rule->uid) ||
+ nla_put_u32(skb, NFL_ATTR_PRIORITY, rule->priority))
+ goto flows_put_failure;
+
+ if (rule->matches) {
+ matches = nla_nest_start(skb, NFL_ATTR_MATCHES);
+ if (!matches)
+ goto flows_put_failure;
+
+ for (j = 0; rule->matches && rule->matches[j].header; j++) {
+ struct net_flow_field_ref *f = &rule->matches[j];
+ struct nlattr *field;
+
+ field = nla_nest_start(skb, NFL_FIELD_REF);
+ if (!field) {
+ err = -EMSGSIZE;
+ goto flows_put_failure;
+ }
+
+ err = net_flow_put_field_ref(skb, f);
+ if (err)
+ goto flows_put_failure;
+
+ err = net_flow_put_field_value(skb, f);
+ if (err)
+ goto flows_put_failure;
+
+ nla_nest_end(skb, field);
+ }
+ nla_nest_end(skb, matches);
+ }
+
+ if (rule->actions) {
+ actions = nla_nest_start(skb, NFL_ATTR_ACTIONS);
+ if (!actions)
+ goto flows_put_failure;
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ err = net_flow_put_flow_action(skb, &rule->actions[i]);
+ if (err)
+ goto flows_put_failure;
+ }
+ nla_nest_end(skb, actions);
+ }
+
+ nla_nest_end(skb, flows);
+ return 0;
+
+flows_put_failure:
+ nla_nest_cancel(skb, flows);
+put_failure:
+ return err;
+}
+
+static struct sk_buff *net_flow_build_flows_msg(struct net_device *dev,
+ u32 portid, int seq, u8 cmd,
+ int min, int max, int table)
+{
+ struct genlmsghdr *hdr;
+ struct nlattr *flows;
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!hdr)
+ goto out;
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex)) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ flows = nla_nest_start(skb, NFL_FLOWS);
+ if (!flows) {
+ err = -EMSGSIZE;
+ goto out;
+ }
+
+ err = -EOPNOTSUPP;
+ if (err < 0)
+ goto out_cancel;
+
+ nla_nest_end(skb, flows);
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0)
+ goto out;
+
+ return skb;
+out_cancel:
+ nla_nest_cancel(skb, flows);
+out:
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+}
+
+static const
+struct nla_policy net_flow_table_flows_policy[NFL_TABLE_FLOWS_MAX + 1] = {
+ [NFL_TABLE_FLOWS_TABLE] = { .type = NLA_U32,},
+ [NFL_TABLE_FLOWS_MINPRIO] = { .type = NLA_U32,},
+ [NFL_TABLE_FLOWS_MAXPRIO] = { .type = NLA_U32,},
+ [NFL_TABLE_FLOWS_FLOWS] = { .type = NLA_NESTED,},
+};
+
+static int net_flow_table_cmd_get_flows(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct nlattr *tb[NFL_TABLE_FLOWS_MAX+1];
+ int table, min = -1, max = -1;
+ struct net_device *dev;
+ struct sk_buff *msg;
+ int err = -EINVAL;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ if (!info->attrs[NFL_IDENTIFIER_TYPE] ||
+ !info->attrs[NFL_IDENTIFIER] ||
+ !info->attrs[NFL_FLOWS])
+ goto out;
+
+ err = nla_parse_nested(tb, NFL_TABLE_FLOWS_MAX,
+ info->attrs[NFL_FLOWS],
+ net_flow_table_flows_policy);
+ if (err)
+ goto out;
+
+ if (!tb[NFL_TABLE_FLOWS_TABLE])
+ goto out;
+
+ table = nla_get_u32(tb[NFL_TABLE_FLOWS_TABLE]);
+
+ if (tb[NFL_TABLE_FLOWS_MINPRIO])
+ min = nla_get_u32(tb[NFL_TABLE_FLOWS_MINPRIO]);
+ if (tb[NFL_TABLE_FLOWS_MAXPRIO])
+ max = nla_get_u32(tb[NFL_TABLE_FLOWS_MAXPRIO]);
+
+ msg = net_flow_build_flows_msg(dev,
+ info->snd_portid,
+ info->snd_seq,
+ NFL_TABLE_CMD_GET_FLOWS,
+ min, max, table);
+ dev_put(dev);
+
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return genlmsg_reply(msg, info);
+out:
+ dev_put(dev);
+ return err;
+}
+
+static struct sk_buff *net_flow_start_errmsg(struct net_device *dev,
+ struct genlmsghdr **hdr,
+ u32 portid, int seq, u8 cmd)
+{
+ struct genlmsghdr *h;
+ struct sk_buff *skb;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return ERR_PTR(-EMSGSIZE);
+
+ h = genlmsg_put(skb, portid, seq, &net_flow_nl_family, 0, cmd);
+ if (!h)
+ return ERR_PTR(-EMSGSIZE);
+
+ if (nla_put_u32(skb,
+ NFL_IDENTIFIER_TYPE,
+ NFL_IDENTIFIER_IFINDEX) ||
+ nla_put_u32(skb, NFL_IDENTIFIER, dev->ifindex))
+ return ERR_PTR(-EMSGSIZE);
+
+ *hdr = h;
+ return skb;
+}
+
+static struct sk_buff *net_flow_end_flow_errmsg(struct sk_buff *skb,
+ struct genlmsghdr *hdr)
+{
+ int err;
+
+ err = genlmsg_end(skb, hdr);
+ if (err < 0) {
+ nlmsg_free(skb);
+ return ERR_PTR(err);
+ }
+
+ return skb;
+}
+
+const struct nla_policy net_flow_field_policy[NFL_FIELD_REF_MAX + 1] = {
+ [NFL_FIELD_REF_NEXT_NODE] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_INSTANCE] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_HEADER] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_FIELD] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_MASK_TYPE] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_TYPE] = { .type = NLA_U32,},
+ [NFL_FIELD_REF_VALUE] = { .type = NLA_BINARY,
+ .len = sizeof(u64)},
+ [NFL_FIELD_REF_MASK] = { .type = NLA_BINARY,
+ .len = sizeof(u64)},
+};
+
+static int net_flow_get_field(struct net_flow_field_ref *field,
+ struct nlattr *nla)
+{
+ struct nlattr *ref[NFL_FIELD_REF_MAX+1];
+ int err;
+
+ err = nla_parse_nested(ref, NFL_FIELD_REF_MAX,
+ nla, net_flow_field_policy);
+ if (err)
+ return err;
+
+ if (!ref[NFL_FIELD_REF_INSTANCE] ||
+ !ref[NFL_FIELD_REF_HEADER] ||
+ !ref[NFL_FIELD_REF_FIELD] ||
+ !ref[NFL_FIELD_REF_MASK_TYPE] ||
+ !ref[NFL_FIELD_REF_TYPE])
+ return -EINVAL;
+
+ field->instance = nla_get_u32(ref[NFL_FIELD_REF_INSTANCE]);
+ field->header = nla_get_u32(ref[NFL_FIELD_REF_HEADER]);
+ field->field = nla_get_u32(ref[NFL_FIELD_REF_FIELD]);
+ field->mask_type = nla_get_u32(ref[NFL_FIELD_REF_MASK_TYPE]);
+ field->type = nla_get_u32(ref[NFL_FIELD_REF_TYPE]);
+
+ if (!ref[NFL_FIELD_REF_VALUE])
+ return 0;
+
+ switch (field->type) {
+ case NFL_FIELD_REF_ATTR_TYPE_U8:
+ if (nla_len(ref[NFL_FIELD_REF_VALUE]) < sizeof(u8)) {
+ err = -EINVAL;
+ break;
+ }
+ field->value_u8 = nla_get_u8(ref[NFL_FIELD_REF_VALUE]);
+
+ if (!ref[NFL_FIELD_REF_MASK])
+ break;
+
+ if (nla_len(ref[NFL_FIELD_REF_MASK]) < sizeof(u8)) {
+ err = -EINVAL;
+ break;
+ }
+ field->mask_u8 = nla_get_u8(ref[NFL_FIELD_REF_MASK]);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U16:
+ if (nla_len(ref[NFL_FIELD_REF_VALUE]) < sizeof(u16)) {
+ err = -EINVAL;
+ break;
+ }
+ field->value_u16 = nla_get_u16(ref[NFL_FIELD_REF_VALUE]);
+
+ if (!ref[NFL_FIELD_REF_MASK])
+ break;
+
+ if (nla_len(ref[NFL_FIELD_REF_MASK]) < sizeof(u16)) {
+ err = -EINVAL;
+ break;
+ }
+ field->mask_u16 = nla_get_u16(ref[NFL_FIELD_REF_MASK]);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U32:
+ if (nla_len(ref[NFL_FIELD_REF_VALUE]) < sizeof(u32)) {
+ err = -EINVAL;
+ break;
+ }
+ field->value_u32 = nla_get_u32(ref[NFL_FIELD_REF_VALUE]);
+
+ if (!ref[NFL_FIELD_REF_MASK])
+ break;
+
+ if (nla_len(ref[NFL_FIELD_REF_MASK]) < sizeof(u32)) {
+ err = -EINVAL;
+ break;
+ }
+ field->mask_u32 = nla_get_u32(ref[NFL_FIELD_REF_MASK]);
+ break;
+ case NFL_FIELD_REF_ATTR_TYPE_U64:
+ if (nla_len(ref[NFL_FIELD_REF_VALUE]) < sizeof(u64)) {
+ err = -EINVAL;
+ break;
+ }
+ field->value_u64 = nla_get_u64(ref[NFL_FIELD_REF_VALUE]);
+
+ if (!ref[NFL_FIELD_REF_MASK])
+ break;
+
+ if (nla_len(ref[NFL_FIELD_REF_MASK]) < sizeof(u64)) {
+ err = -EINVAL;
+ break;
+ }
+ field->mask_u64 = nla_get_u64(ref[NFL_FIELD_REF_MASK]);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static void net_flow_free_actions(struct net_flow_action *actions)
+{
+ int i;
+
+ if (!actions)
+ return;
+
+ for (i = 0; actions[i].args; i++) {
+ kfree(actions[i].args->name);
+ kfree(actions[i].args);
+ }
+ kfree(actions);
+}
+
+static void net_flow_rule_free(struct net_flow_rule *rule)
+{
+ if (!rule)
+ return;
+
+ kfree(rule->matches);
+ net_flow_free_actions(rule->actions);
+ kfree(rule);
+}
+
+static const
+struct nla_policy net_flow_actarg_policy[NFL_ACTION_ARG_MAX + 1] = {
+ [NFL_ACTION_ARG_NAME] = { .type = NLA_STRING },
+ [NFL_ACTION_ARG_TYPE] = { .type = NLA_U32 },
+ [NFL_ACTION_ARG_VALUE] = { .type = NLA_BINARY, .len = sizeof(u64)},
+};
+
+static int net_flow_get_actarg(struct net_flow_action_arg *arg,
+ struct nlattr *attr)
+{
+ struct nlattr *r[NFL_ACTION_ARG_MAX+1];
+ int err;
+
+ err = nla_parse_nested(r, NFL_ACTION_ARG_MAX,
+ attr, net_flow_actarg_policy);
+ if (err)
+ return err;
+
+ if (!r[NFL_ACTION_ARG_TYPE] ||
+ !r[NFL_ACTION_ARG_VALUE])
+ return -EINVAL;
+
+ arg->type = nla_get_u32(r[NFL_ACTION_ARG_TYPE]);
+ switch (arg->type) {
+ case NFL_ACTION_ARG_TYPE_U8:
+ if (nla_len(r[NFL_ACTION_ARG_VALUE]) < sizeof(u8))
+ return -EINVAL;
+ arg->value_u8 = nla_get_u8(r[NFL_ACTION_ARG_VALUE]);
+ break;
+ case NFL_ACTION_ARG_TYPE_U16:
+ if (nla_len(r[NFL_ACTION_ARG_VALUE]) < sizeof(u16))
+ return -EINVAL;
+ arg->value_u16 = nla_get_u16(r[NFL_ACTION_ARG_VALUE]);
+ break;
+ case NFL_ACTION_ARG_TYPE_U32:
+ if (nla_len(r[NFL_ACTION_ARG_VALUE]) < sizeof(u32))
+ return -EINVAL;
+ arg->value_u32 = nla_get_u32(r[NFL_ACTION_ARG_VALUE]);
+ break;
+ case NFL_ACTION_ARG_TYPE_U64:
+ if (nla_len(r[NFL_ACTION_ARG_VALUE]) < sizeof(u64))
+ return -EINVAL;
+ arg->value_u64 = nla_get_u64(r[NFL_ACTION_ARG_VALUE]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (r[NFL_ACTION_ARG_NAME]) {
+ int max = nla_len(r[NFL_ACTION_ARG_NAME]);
+
+ if (max > NFL_MAX_NAME)
+ max = NFL_MAX_NAME;
+
+ arg->name = kzalloc(max, GFP_KERNEL);
+ if (!arg->name)
+ return -ENOMEM;
+ nla_strlcpy(arg->name, r[NFL_ACTION_ARG_NAME], max);
+ }
+
+ return 0;
+}
+
+static int net_flow_get_action(struct net_flow_action *a, struct nlattr *attr)
+{
+ struct nlattr *act[NFL_ACTION_ATTR_MAX+1];
+ struct nlattr *args;
+ int rem;
+ int err, count = 0;
+
+ if (nla_type(attr) != NFL_ACTION) {
+ pr_warn("%s: expected NFL_ACTION\n", __func__);
+ return 0;
+ }
+
+ err = nla_parse_nested(act, NFL_ACTION_ATTR_MAX,
+ attr, net_flow_action_policy);
+ if (err < 0)
+ return err;
+
+ if (!act[NFL_ACTION_ATTR_UID])
+ return -EINVAL;
+
+ a->uid = nla_get_u32(act[NFL_ACTION_ATTR_UID]);
+
+ /* Only need to parse signature if it is provided otherwise assume
+ * action does not need any arguments
+ */
+ if (!act[NFL_ACTION_ATTR_SIGNATURE])
+ return 0;
+
+ nla_for_each_nested(args, act[NFL_ACTION_ATTR_SIGNATURE], rem)
+ count++;
+
+ a->args = kcalloc(count + 1,
+ sizeof(struct net_flow_action_arg),
+ GFP_KERNEL);
+ count = 0;
+
+ nla_for_each_nested(args, act[NFL_ACTION_ATTR_SIGNATURE], rem) {
+ if (nla_type(args) != NFL_ACTION_ARG)
+ continue;
+
+ err = net_flow_get_actarg(&a->args[count], args);
+ if (err) {
+ kfree(a->args);
+ a->args = NULL;
+ return err;
+ }
+ count++;
+ }
+ return 0;
+}
+
+static const
+struct nla_policy net_flow_rule_policy[NFL_ATTR_MAX + 1] = {
+ [NFL_ATTR_TABLE] = { .type = NLA_U32 },
+ [NFL_ATTR_UID] = { .type = NLA_U32 },
+ [NFL_ATTR_PRIORITY] = { .type = NLA_U32 },
+ [NFL_ATTR_MATCHES] = { .type = NLA_NESTED },
+ [NFL_ATTR_ACTIONS] = { .type = NLA_NESTED },
+};
+
+static int net_flow_get_rule(struct net_flow_rule *rule, struct nlattr *attr)
+{
+ struct nlattr *f[NFL_ATTR_MAX+1];
+ struct nlattr *match, *act;
+ int rem, err;
+ int count = 0;
+
+ err = nla_parse_nested(f, NFL_ATTR_MAX,
+ attr, net_flow_rule_policy);
+ if (err < 0)
+ return -EINVAL;
+
+ if (!f[NFL_ATTR_TABLE] || !f[NFL_ATTR_UID] ||
+ !f[NFL_ATTR_PRIORITY])
+ return -EINVAL;
+
+ rule->table_id = nla_get_u32(f[NFL_ATTR_TABLE]);
+ rule->uid = nla_get_u32(f[NFL_ATTR_UID]);
+ rule->priority = nla_get_u32(f[NFL_ATTR_PRIORITY]);
+
+ rule->matches = NULL;
+ rule->actions = NULL;
+
+ if (f[NFL_ATTR_MATCHES]) {
+ nla_for_each_nested(match, f[NFL_ATTR_MATCHES], rem) {
+ if (nla_type(match) == NFL_FIELD_REF)
+ count++;
+ }
+
+ /* Null terminated list of matches */
+ rule->matches = kcalloc(count + 1,
+ sizeof(struct net_flow_field_ref),
+ GFP_KERNEL);
+ if (!rule->matches)
+ return -ENOMEM;
+
+ count = 0;
+ nla_for_each_nested(match, f[NFL_ATTR_MATCHES], rem) {
+ err = net_flow_get_field(&rule->matches[count], match);
+ if (err) {
+ kfree(rule->matches);
+ rule->matches = NULL;
+ return err;
+ }
+ count++;
+ }
+ }
+
+ if (f[NFL_ATTR_ACTIONS]) {
+ count = 0;
+ nla_for_each_nested(act, f[NFL_ATTR_ACTIONS], rem) {
+ if (nla_type(act) == NFL_ACTION)
+ count++;
+ }
+
+ /* Null terminated list of actions */
+ rule->actions = kcalloc(count + 1,
+ sizeof(struct net_flow_action),
+ GFP_KERNEL);
+ if (!rule->actions) {
+ kfree(rule->matches);
+ rule->matches = NULL;
+ return -ENOMEM;
+ }
+
+ count = 0;
+ nla_for_each_nested(act, f[NFL_ATTR_ACTIONS], rem) {
+ err = net_flow_get_action(&rule->actions[count], act);
+ if (err) {
+ kfree(rule->matches);
+ rule->matches = NULL;
+ net_flow_free_actions(rule->actions);
+ rule->actions = NULL;
+ return err;
+ }
+ count++;
+ }
+ }
+
+ return 0;
+}
+
+static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
+ struct genl_info *info)
+{
+ int rem, err_handle = NFL_FLOWS_ERROR_ABORT;
+ struct net_flow_rule *this = NULL;
+ struct sk_buff *skb = NULL;
+ struct genlmsghdr *hdr;
+ struct net_device *dev;
+ struct nlattr *flow, *flows;
+ int cmd = info->genlhdr->cmd;
+ int err = -EOPNOTSUPP;
+
+ dev = net_flow_get_dev(info);
+ if (!dev)
+ return -EINVAL;
+
+ switch (cmd) {
+ case NFL_TABLE_CMD_SET_FLOWS:
+ if (!dev->netdev_ops->ndo_flow_set_rule)
+ goto out;
+ break;
+ case NFL_TABLE_CMD_DEL_FLOWS:
+ if (!dev->netdev_ops->ndo_flow_del_rule)
+ goto out;
+ break;
+ default:
+ goto out;
+ }
+
+ if (!info->attrs[NFL_IDENTIFIER_TYPE] ||
+ !info->attrs[NFL_IDENTIFIER] ||
+ !info->attrs[NFL_FLOWS]) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (info->attrs[NFL_FLOWS_ERROR])
+ err_handle = nla_get_u32(info->attrs[NFL_FLOWS_ERROR]);
+
+ net_flow_lock();
+ nla_for_each_nested(flow, info->attrs[NFL_FLOWS], rem) {
+ if (nla_type(flow) != NFL_FLOW)
+ continue;
+
+ this = kzalloc(sizeof(*this), GFP_KERNEL);
+ if (!this) {
+ err = -ENOMEM;
+ goto skip;
+ }
+
+ /* If userspace is passing invalid messages so that we can not
+ * even build correct flow structures abort with an error. And
+ * do not try to proceed regardless of error structure.
+ */
+ err = net_flow_get_rule(this, flow);
+ if (err)
+ goto out_locked;
+
+ switch (cmd) {
+ case NFL_TABLE_CMD_SET_FLOWS:
+ err = dev->netdev_ops->ndo_flow_set_rule(dev, this);
+ break;
+ case NFL_TABLE_CMD_DEL_FLOWS:
+ err = dev->netdev_ops->ndo_flow_del_rule(dev, this);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+skip:
+ if (err && err_handle != NFL_FLOWS_ERROR_CONTINUE) {
+ if (!skb) {
+ skb = net_flow_start_errmsg(dev, &hdr,
+ info->snd_portid,
+ info->snd_seq,
+ cmd);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ goto out_locked;
+ }
+
+ flows = nla_nest_start(skb, NFL_FLOWS);
+ if (!flows) {
+ err = -EMSGSIZE;
+ goto out_locked;
+ }
+ }
+
+ net_flow_put_rule(skb, this);
+ }
+
+ net_flow_rule_free(this);
+
+ if (err && err_handle == NFL_FLOWS_ERROR_ABORT)
+ goto out_locked;
+ }
+ net_flow_unlock();
+ dev_put(dev);
+
+ if (skb) {
+ nla_nest_end(skb, flows);
+ net_flow_end_flow_errmsg(skb, hdr);
+ return genlmsg_reply(skb, info);
+ }
+ return 0;
+
+out_locked:
+ net_flow_unlock();
+out:
+ net_flow_rule_free(this);
+ nlmsg_free(skb);
+ dev_put(dev);
+ return err;
+}
+
static const struct nla_policy net_flow_cmd_policy[NFL_MAX + 1] = {
[NFL_IDENTIFIER_TYPE] = {.type = NLA_U32, },
[NFL_IDENTIFIER] = {.type = NLA_U32, },
@@ -920,6 +1662,24 @@ static const struct genl_ops net_flow_table_nl_ops[] = {
.policy = net_flow_cmd_policy,
.flags = GENL_ADMIN_PERM,
},
+ {
+ .cmd = NFL_TABLE_CMD_GET_FLOWS,
+ .doit = net_flow_table_cmd_get_flows,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_SET_FLOWS,
+ .doit = net_flow_table_cmd_flows,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NFL_TABLE_CMD_DEL_FLOWS,
+ .doit = net_flow_table_cmd_flows,
+ .policy = net_flow_cmd_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
};
static int __init net_flow_nl_module_init(void)
^ permalink raw reply related
* [net-next PATCH v3 03/12] net: flow: implement flow cache for get routines
From: John Fastabend @ 2015-01-20 20:27 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
I used rhashtable to implement a flow cache so software can track the
currently programmed rules without requiring every driver to
implement its own cache logic or fetch information from hardware.
I chose rhashtable to get the dynamic resizing.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
include/linux/if_flow.h | 24 +++++++
net/core/flow_table.c | 152 +++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 169 insertions(+), 7 deletions(-)
diff --git a/include/linux/if_flow.h b/include/linux/if_flow.h
index 712b54f..07d7bca 100644
--- a/include/linux/if_flow.h
+++ b/include/linux/if_flow.h
@@ -21,6 +21,7 @@
#define _IF_FLOW_H
#include <uapi/linux/if_flow.h>
+#include <linux/rhashtable.h>
/**
* @struct net_flow_fields
@@ -134,6 +135,7 @@ struct net_flow_field_ref {
* @size max number of entries for table or -1 for unbounded
* @matches null terminated set of supported match types given by match uid
* @actions null terminated set of supported action types given by action uid
+ * @cache software cache of hardware flows
*/
struct net_flow_tbl {
char *name;
@@ -143,6 +145,7 @@ struct net_flow_tbl {
__u32 size;
struct net_flow_field_ref *matches;
__u32 *actions;
+ struct rhashtable cache;
};
/**
@@ -190,6 +193,8 @@ struct net_flow_tbl_node {
* @struct net_flow_rule
* @brief describes the match/action entry
*
+ * @node node for resizable hash table used for software cache of rules
+ * @rcu used to support delayed freeing via call_rcu in software cache
* @uid unique identifier for flow
* @priority priority to execute flow match/action in table
* @match null terminated set of match uids match criteria
@@ -198,10 +203,29 @@ struct net_flow_tbl_node {
* Flows must match all entries in match set.
*/
struct net_flow_rule {
+ struct rhash_head node;
+ struct rcu_head rcu;
__u32 table_id;
__u32 uid;
__u32 priority;
struct net_flow_field_ref *matches;
struct net_flow_action *actions;
};
+
+#ifdef CONFIG_NET_FLOW_TABLES
+int net_flow_init_cache(struct net_flow_tbl *table);
+void net_flow_destroy_cache(struct net_flow_tbl *table);
+#else
+static inline int
+net_flow_init_cache(struct net_flow_tbl *table)
+{
+ return 0;
+}
+
+static inline void
+net_flow_destroy_cache(struct net_flow_tbl *table)
+{
+ return;
+}
+#endif /* CONFIG_NET_FLOW_TABLES */
#endif /* _IF_FLOW_H_ */
diff --git a/net/core/flow_table.c b/net/core/flow_table.c
index 7b85e53..c1a9716 100644
--- a/net/core/flow_table.c
+++ b/net/core/flow_table.c
@@ -26,6 +26,8 @@
#include <net/genetlink.h>
#include <net/rtnetlink.h>
#include <linux/module.h>
+#include <linux/rhashtable.h>
+#include <linux/jhash.h>
static DEFINE_MUTEX(net_flow_mutex);
@@ -919,6 +921,27 @@ static int net_flow_cmd_get_table_graph(struct sk_buff *skb,
return genlmsg_reply(msg, info);
}
+static struct net_flow_tbl *net_flow_get_table(struct net_device *dev,
+ int table_id)
+{
+ struct net_flow_tbl **tables;
+ int i;
+
+ if (!dev->netdev_ops->ndo_flow_get_tbls)
+ return NULL;
+
+ tables = dev->netdev_ops->ndo_flow_get_tbls(dev);
+ if (!tables)
+ return NULL;
+
+ for (i = 0; tables[i]; i++) {
+ if (tables[i]->uid == table_id)
+ return tables[i];
+ }
+
+ return NULL;
+}
+
static int net_flow_put_flow_action(struct sk_buff *skb,
struct net_flow_action *a)
{
@@ -1017,11 +1040,39 @@ put_failure:
return err;
}
+static int net_flow_get_rule_cache(struct sk_buff *skb,
+ struct net_flow_tbl *table,
+ int min, int max)
+{
+ const struct bucket_table *tbl;
+ struct net_flow_rule *he;
+ int i, err = 0;
+
+ rcu_read_lock();
+ tbl = rht_dereference_rcu(table->cache.tbl, &table->cache);
+
+ for (i = 0; i < tbl->size; i++) {
+ struct rhash_head *pos;
+
+ rht_for_each_entry_rcu(he, pos, tbl, i, node) {
+ if (he->uid < min || (max > 0 && he->uid > max))
+ continue;
+ err = net_flow_put_rule(skb, he);
+ if (err)
+ goto out;
+ }
+ }
+out:
+ rcu_read_unlock();
+ return err;
+}
+
static struct sk_buff *net_flow_build_flows_msg(struct net_device *dev,
u32 portid, int seq, u8 cmd,
int min, int max, int table)
{
struct genlmsghdr *hdr;
+ struct net_flow_tbl *t;
struct nlattr *flows;
struct sk_buff *skb;
int err = -ENOBUFS;
@@ -1042,15 +1093,23 @@ static struct sk_buff *net_flow_build_flows_msg(struct net_device *dev,
goto out;
}
+ t = net_flow_get_table(dev, table);
+ if (!t) {
+ err = -EINVAL;
+ goto out;
+ }
+
flows = nla_nest_start(skb, NFL_FLOWS);
if (!flows) {
err = -EMSGSIZE;
goto out;
}
- err = -EOPNOTSUPP;
- if (err < 0)
- goto out_cancel;
+ err = net_flow_get_rule_cache(skb, t, min, max);
+ if (err < 0) {
+ nla_nest_cancel(skb, flows);
+ goto out;
+ }
nla_nest_end(skb, flows);
@@ -1059,8 +1118,6 @@ static struct sk_buff *net_flow_build_flows_msg(struct net_device *dev,
goto out;
return skb;
-out_cancel:
- nla_nest_cancel(skb, flows);
out:
nlmsg_free(skb);
return ERR_PTR(err);
@@ -1300,6 +1357,13 @@ static void net_flow_rule_free(struct net_flow_rule *rule)
kfree(rule);
}
+static void net_flow_rule_free_rcu(struct rcu_head *head)
+{
+ struct net_flow_rule *r = container_of(head, struct net_flow_rule, rcu);
+
+ net_flow_rule_free(r);
+}
+
static const
struct nla_policy net_flow_actarg_policy[NFL_ACTION_ARG_MAX + 1] = {
[NFL_ACTION_ARG_NAME] = { .type = NLA_STRING },
@@ -1505,6 +1569,70 @@ static int net_flow_get_rule(struct net_flow_rule *rule, struct nlattr *attr)
return 0;
}
+#define NFL_TABLE_ELEM_HINT 10
+int net_flow_init_cache(struct net_flow_tbl *table)
+{
+ struct rhashtable_params params = {
+ .nelem_hint = NFL_TABLE_ELEM_HINT,
+ .head_offset = offsetof(struct net_flow_rule, node),
+ .key_offset = offsetof(struct net_flow_rule, uid),
+ .key_len = sizeof(__u32),
+ .hashfn = jhash,
+ .grow_decision = rht_grow_above_75,
+ .shrink_decision = rht_shrink_below_30
+ };
+
+ return rhashtable_init(&table->cache, ¶ms);
+}
+EXPORT_SYMBOL(net_flow_init_cache);
+
+void net_flow_destroy_cache(struct net_flow_tbl *table)
+{
+ struct rhashtable *cache = &table->cache;
+ const struct bucket_table *tbl;
+ struct net_flow_rule *he;
+ struct rhash_head *pos, *next;
+ unsigned int i;
+
+ /* Stop an eventual async resizing */
+ cache->being_destroyed = true;
+ mutex_lock(&cache->mutex);
+
+ tbl = rht_dereference(cache->tbl, cache);
+ for (i = 0; i < tbl->size; i++) {
+ rht_for_each_entry_safe(he, pos, next, tbl, i, node) {
+ rhashtable_remove(&table->cache, &he->node);
+ call_rcu(&he->rcu, net_flow_rule_free_rcu);
+ }
+ }
+
+ mutex_unlock(&cache->mutex);
+ rhashtable_destroy(cache);
+}
+EXPORT_SYMBOL(net_flow_destroy_cache);
+
+static void net_flow_add_rule_cache(struct net_flow_tbl *table,
+ struct net_flow_rule *this)
+{
+ rhashtable_insert(&table->cache, &this->node);
+}
+
+static int net_flow_del_rule_cache(struct net_flow_tbl *table,
+ struct net_flow_rule *this)
+{
+ struct net_flow_rule *he;
+
+ he = rhashtable_lookup(&table->cache, &this->uid);
+ if (he) {
+ rhashtable_remove(&table->cache, &he->node);
+ synchronize_rcu();
+ net_flow_rule_free(he);
+ return 0;
+ }
+
+ return -EEXIST;
+}
+
static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
struct genl_info *info)
{
@@ -1546,6 +1674,8 @@ static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
net_flow_lock();
nla_for_each_nested(flow, info->attrs[NFL_FLOWS], rem) {
+ struct net_flow_tbl *table;
+
if (nla_type(flow) != NFL_FLOW)
continue;
@@ -1563,12 +1693,22 @@ static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
if (err)
goto out_locked;
+ table = net_flow_get_table(dev, this->table_id);
+ if (!table) {
+ err = -EINVAL;
+ goto skip;
+ }
+
switch (cmd) {
case NFL_TABLE_CMD_SET_FLOWS:
err = dev->netdev_ops->ndo_flow_set_rule(dev, this);
+ if (!err)
+ net_flow_add_rule_cache(table, this);
break;
case NFL_TABLE_CMD_DEL_FLOWS:
err = dev->netdev_ops->ndo_flow_del_rule(dev, this);
+ if (!err)
+ err = net_flow_del_rule_cache(table, this);
break;
default:
err = -EOPNOTSUPP;
@@ -1597,8 +1737,6 @@ skip:
net_flow_put_rule(skb, this);
}
- net_flow_rule_free(this);
-
if (err && err_handle == NFL_FLOWS_ERROR_ABORT)
goto out_locked;
}
^ permalink raw reply related
* [net-next PATCH v3 04/12] net: flow_table: create a set of common headers and actions
From: John Fastabend @ 2015-01-20 20:27 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
This adds common headers and actions that drivers can use.
I have not yet moved the header graphs into the common header
because I'm not entirely convinced its re-usable. The devices
I have been looking at have different enough header graphs that
they wouldn't be re-usable. However possibly many 40Gbp NICs
for example could share a common header graph. When we get
multiple implementations we can move this into the common file
if it makes sense.
And table structures seem to be unique enough that there is
little value in putting each devices table layout into the
common file so its left for device specific implementation.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
include/linux/if_flow_common.h | 257 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 257 insertions(+)
create mode 100644 include/linux/if_flow_common.h
diff --git a/include/linux/if_flow_common.h b/include/linux/if_flow_common.h
new file mode 100644
index 0000000..ef2d66f
--- /dev/null
+++ b/include/linux/if_flow_common.h
@@ -0,0 +1,257 @@
+#ifndef _IF_FLOW_COMMON_H_
+#define _IF_FLOW_COMMON_H_
+
+#include <linux/if_flow.h>
+
+/* Common header definition this section provides a set of common or
+ * standard headers that device driver writers may use to simplify the
+ * driver creation. We do not want vendor or driver specific headers
+ * here though. Driver authors can keep these contained to their driver
+ *
+ * Driver authors may use unique IDs greater than HEADER_MAX_UID it is
+ * guaranteed to be larger than any unique IDs used here.
+ */
+#define HEADER_MAX_UID 100
+
+enum net_flow_headers {
+ HEADER_UNSPEC,
+ HEADER_ETHERNET,
+ HEADER_VLAN,
+ HEADER_IPV4,
+};
+
+enum net_flow_ethernet_fields_ids {
+ HEADER_ETHERNET_UNSPEC,
+ HEADER_ETHERNET_SRC_MAC,
+ HEADER_ETHERNET_DST_MAC,
+ HEADER_ETHERNET_ETHERTYPE,
+};
+
+struct net_flow_field net_flow_ethernet_fields[] = {
+ { .name = "src_mac", .uid = HEADER_ETHERNET_SRC_MAC, .bitwidth = 48},
+ { .name = "dst_mac", .uid = HEADER_ETHERNET_DST_MAC, .bitwidth = 48},
+ { .name = "ethertype",
+ .uid = HEADER_ETHERNET_ETHERTYPE,
+ .bitwidth = 16},
+};
+
+struct net_flow_hdr net_flow_ethernet = {
+ .name = "ethernet",
+ .uid = HEADER_ETHERNET,
+ .field_sz = ARRAY_SIZE(net_flow_ethernet_fields),
+ .fields = net_flow_ethernet_fields,
+};
+
+enum net_flow_vlan_fields_ids {
+ HEADER_VLAN_UNSPEC,
+ HEADER_VLAN_PCP,
+ HEADER_VLAN_CFI,
+ HEADER_VLAN_VID,
+ HEADER_VLAN_ETHERTYPE,
+};
+
+struct net_flow_field net_flow_vlan_fields[] = {
+ { .name = "pcp", .uid = HEADER_VLAN_PCP, .bitwidth = 3,},
+ { .name = "cfi", .uid = HEADER_VLAN_CFI, .bitwidth = 1,},
+ { .name = "vid", .uid = HEADER_VLAN_VID, .bitwidth = 12,},
+ { .name = "ethertype", .uid = HEADER_VLAN_ETHERTYPE, .bitwidth = 16,},
+};
+
+struct net_flow_hdr net_flow_vlan = {
+ .name = "vlan",
+ .uid = HEADER_VLAN,
+ .field_sz = ARRAY_SIZE(net_flow_vlan_fields),
+ .fields = net_flow_vlan_fields,
+};
+
+enum net_flow_ipv4_fields_ids {
+ HEADER_IPV4_UNSPEC,
+ HEADER_IPV4_VERSION,
+ HEADER_IPV4_IHL,
+ HEADER_IPV4_DSCP,
+ HEADER_IPV4_ECN,
+ HEADER_IPV4_LENGTH,
+ HEADER_IPV4_IDENTIFICATION,
+ HEADER_IPV4_FLAGS,
+ HEADER_IPV4_FRAGMENT_OFFSET,
+ HEADER_IPV4_TTL,
+ HEADER_IPV4_PROTOCOL,
+ HEADER_IPV4_CSUM,
+ HEADER_IPV4_SRC_IP,
+ HEADER_IPV4_DST_IP,
+ HEADER_IPV4_OPTIONS,
+};
+
+struct net_flow_field net_flow_ipv4_fields[] = {
+ { .name = "version",
+ .uid = HEADER_IPV4_VERSION,
+ .bitwidth = 4,},
+ { .name = "ihl",
+ .uid = HEADER_IPV4_IHL,
+ .bitwidth = 4,},
+ { .name = "dscp",
+ .uid = HEADER_IPV4_DSCP,
+ .bitwidth = 6,},
+ { .name = "ecn",
+ .uid = HEADER_IPV4_ECN,
+ .bitwidth = 2,},
+ { .name = "length",
+ .uid = HEADER_IPV4_LENGTH,
+ .bitwidth = 8,},
+ { .name = "identification",
+ .uid = HEADER_IPV4_IDENTIFICATION,
+ .bitwidth = 8,},
+ { .name = "flags",
+ .uid = HEADER_IPV4_FLAGS,
+ .bitwidth = 3,},
+ { .name = "fragment_offset",
+ .uid = HEADER_IPV4_FRAGMENT_OFFSET,
+ .bitwidth = 13,},
+ { .name = "ttl",
+ .uid = HEADER_IPV4_TTL,
+ .bitwidth = 1,},
+ { .name = "protocol",
+ .uid = HEADER_IPV4_PROTOCOL,
+ .bitwidth = 8,},
+ { .name = "csum",
+ .uid = HEADER_IPV4_CSUM,
+ .bitwidth = 8,},
+ { .name = "src_ip",
+ .uid = HEADER_IPV4_SRC_IP,
+ .bitwidth = 32,},
+ { .name = "dst_ip",
+ .uid = HEADER_IPV4_DST_IP,
+ .bitwidth = 32,},
+ { .name = "options",
+ .uid = HEADER_IPV4_OPTIONS,
+ .bitwidth = 0,},
+};
+
+struct net_flow_hdr net_flow_ipv4 = {
+ .name = "ipv4",
+ .uid = HEADER_IPV4,
+ .field_sz = ARRAY_SIZE(net_flow_ipv4_fields),
+ .fields = net_flow_ipv4_fields,
+};
+
+/* Common set of actions. Below are the list of actions that are or we expect
+ * are common enough to be supported by multiple devices.
+ *
+ * Driver authors may use unique IDs greater than ACTION_MAX_UID it is
+ * guaranteed to be larger than any unique IDs used here.
+ */
+#define ACTION_MAX_UID 100
+
+enum net_flow_action_ids {
+ ACTION_SET_UNSPEC,
+ ACTION_SET_VLAN_ID,
+ ACTION_COPY_TO_CPU,
+ ACTION_POP_VLAN,
+ ACTION_SET_ETH_SRC,
+ ACTION_SET_ETH_DST,
+ ACTION_SET_OUT_PORT,
+ ACTION_CHECK_TTL_DROP,
+};
+
+struct net_flow_action_arg net_flow_null_args[] = {
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+struct net_flow_action net_flow_null_action = {
+ .name = "", .uid = 0, .args = NULL,
+};
+
+struct net_flow_action_arg net_flow_set_vlan_id_args[] = {
+ {
+ .name = "vlan_id",
+ .type = NFL_ACTION_ARG_TYPE_U16,
+ .value_u16 = 0,
+ },
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+struct net_flow_action net_flow_set_vlan_id = {
+ .name = "set_vlan_id",
+ .uid = ACTION_SET_VLAN_ID,
+ .args = net_flow_set_vlan_id_args,
+};
+
+struct net_flow_action net_flow_copy_to_cpu = {
+ .name = "copy_to_cpu",
+ .uid = ACTION_COPY_TO_CPU,
+ .args = net_flow_null_args,
+};
+
+struct net_flow_action net_flow_pop_vlan = {
+ .name = "pop_vlan",
+ .uid = ACTION_POP_VLAN,
+ .args = net_flow_null_args,
+};
+
+struct net_flow_action_arg net_flow_set_eth_src_args[] = {
+ {
+ .name = "eth_src",
+ .type = NFL_ACTION_ARG_TYPE_U64,
+ .value_u64 = 0,
+ },
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+struct net_flow_action net_flow_set_eth_src = {
+ .name = "set_eth_src",
+ .uid = ACTION_SET_ETH_SRC,
+ .args = net_flow_set_eth_src_args,
+};
+
+struct net_flow_action_arg net_flow_set_eth_dst_args[] = {
+ {
+ .name = "eth_dst",
+ .type = NFL_ACTION_ARG_TYPE_U64,
+ .value_u64 = 0,
+ },
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+struct net_flow_action net_flow_set_eth_dst = {
+ .name = "set_eth_dst",
+ .uid = ACTION_SET_ETH_DST,
+ .args = net_flow_set_eth_dst_args,
+};
+
+struct net_flow_action_arg net_flow_set_out_port_args[] = {
+ {
+ .name = "set_out_port",
+ .type = NFL_ACTION_ARG_TYPE_U32,
+ .value_u32 = 0,
+ },
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+struct net_flow_action net_flow_set_out_port = {
+ .name = "set_out_port",
+ .uid = ACTION_SET_OUT_PORT,
+ .args = net_flow_set_out_port_args,
+};
+
+struct net_flow_action net_flow_check_ttl_drop = {
+ .name = "check_ttl_drop",
+ .uid = ACTION_CHECK_TTL_DROP,
+ .args = net_flow_null_args,
+};
+
+#endif
^ permalink raw reply related
* [net-next PATCH v3 05/12] net: flow_table: add validation functions for rules
From: John Fastabend @ 2015-01-20 20:28 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
This adds common validation functions that is used before
adding rules to verify they match the table spec returned
from driver.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
net/core/flow_table.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/net/core/flow_table.c b/net/core/flow_table.c
index c1a9716..4b4da2e 100644
--- a/net/core/flow_table.c
+++ b/net/core/flow_table.c
@@ -1633,6 +1633,78 @@ static int net_flow_del_rule_cache(struct net_flow_tbl *table,
return -EEXIST;
}
+static int net_flow_is_valid_action_arg(struct net_flow_action *a, int id)
+{
+ struct net_flow_action_arg *args = a->args;
+ int i;
+
+ /* Actions may not have any arguments */
+ if (!a->args)
+ return 0;
+
+ for (i = 0; args[i].type != NFL_ACTION_ARG_TYPE_NULL; i++) {
+ if (a->args[i].type == NFL_ACTION_ARG_TYPE_NULL ||
+ args[i].type != a->args[i].type)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int net_flow_is_valid_action(struct net_flow_action *a, int *actions)
+{
+ int i;
+
+ for (i = 0; actions[i]; i++) {
+ if (actions[i] == a->uid)
+ return net_flow_is_valid_action_arg(a, a->uid);
+ }
+ return -EINVAL;
+}
+
+static int net_flow_is_valid_match(struct net_flow_field_ref *f,
+ struct net_flow_field_ref *fields)
+{
+ int i;
+
+ for (i = 0; fields[i].header; i++) {
+ if (f->header == fields[i].header &&
+ f->field == fields[i].field)
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int net_flow_is_valid_rule(struct net_flow_tbl *table,
+ struct net_flow_rule *flow)
+{
+ struct net_flow_field_ref *fields = table->matches;
+ int *actions = table->actions;
+ int i, err;
+
+ /* Only accept rules with matches AND actions it does not seem
+ * correct to allow a match without actions or action chains
+ * that will never be hit
+ */
+ if (!flow->actions || !flow->matches)
+ return -EINVAL;
+
+ for (i = 0; flow->actions[i].uid; i++) {
+ err = net_flow_is_valid_action(&flow->actions[i], actions);
+ if (err)
+ return -EINVAL;
+ }
+
+ for (i = 0; flow->matches[i].header; i++) {
+ err = net_flow_is_valid_match(&flow->matches[i], fields);
+ if (err)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
struct genl_info *info)
{
@@ -1701,6 +1773,9 @@ static int net_flow_table_cmd_flows(struct sk_buff *recv_skb,
switch (cmd) {
case NFL_TABLE_CMD_SET_FLOWS:
+ err = net_flow_is_valid_rule(table, this);
+ if (err)
+ break;
err = dev->netdev_ops->ndo_flow_set_rule(dev, this);
if (!err)
net_flow_add_rule_cache(table, this);
^ permalink raw reply related
* [net-next PATCH v3 06/12] net: rocker: add pipeline model for rocker switch
From: John Fastabend @ 2015-01-20 20:28 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
This adds rocker support for the net_flow_get_* operations. With this
we can interrogate rocker.
Here we see that for static configurations enabling the get operations
is simply a matter of defining a pipeline model and returning the
structures for the core infrastructure to encapsulate into netlink
messages.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 65 ++++
drivers/net/ethernet/rocker/rocker_pipeline.h | 451 +++++++++++++++++++++++++
2 files changed, 516 insertions(+)
create mode 100644 drivers/net/ethernet/rocker/rocker_pipeline.h
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index 2f398fa..d2ea451 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -36,6 +36,7 @@
#include <generated/utsrelease.h>
#include "rocker.h"
+#include "rocker_pipeline.h"
static const char rocker_driver_name[] = "rocker";
@@ -3781,6 +3782,56 @@ static int rocker_port_switch_port_stp_update(struct net_device *dev, u8 state)
return rocker_port_stp_update(rocker_port, state);
}
+static void rocker_destroy_flow_tables(struct rocker_port *rocker_port)
+{
+ int i;
+
+ for (i = 0; rocker_table_list[i]; i++)
+ net_flow_destroy_cache(rocker_table_list[i]);
+}
+
+static int rocker_init_flow_tables(struct rocker_port *rocker_port)
+{
+ int i, err;
+
+ for (i = 0; rocker_table_list[i]; i++) {
+ err = net_flow_init_cache(rocker_table_list[i]);
+ if (err) {
+ rocker_destroy_flow_tables(rocker_port);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_NET_FLOW_TABLES
+static struct net_flow_tbl **rocker_get_tables(struct net_device *d)
+{
+ return rocker_table_list;
+}
+
+static struct net_flow_hdr **rocker_get_headers(struct net_device *d)
+{
+ return rocker_header_list;
+}
+
+static struct net_flow_action **rocker_get_actions(struct net_device *d)
+{
+ return rocker_action_list;
+}
+
+static struct net_flow_tbl_node **rocker_get_tgraph(struct net_device *d)
+{
+ return rocker_table_nodes;
+}
+
+static struct net_flow_hdr_node **rocker_get_hgraph(struct net_device *d)
+{
+ return rocker_header_nodes;
+}
+#endif
+
static const struct net_device_ops rocker_port_netdev_ops = {
.ndo_open = rocker_port_open,
.ndo_stop = rocker_port_stop,
@@ -3795,6 +3846,13 @@ static const struct net_device_ops rocker_port_netdev_ops = {
.ndo_bridge_getlink = rocker_port_bridge_getlink,
.ndo_switch_parent_id_get = rocker_port_switch_parent_id_get,
.ndo_switch_port_stp_update = rocker_port_switch_port_stp_update,
+#ifdef CONFIG_NET_FLOW_TABLES
+ .ndo_flow_get_tbls = rocker_get_tables,
+ .ndo_flow_get_hdrs = rocker_get_headers,
+ .ndo_flow_get_actions = rocker_get_actions,
+ .ndo_flow_get_tbl_graph = rocker_get_tgraph,
+ .ndo_flow_get_hdr_graph = rocker_get_hgraph,
+#endif
};
/********************
@@ -3960,6 +4018,7 @@ static void rocker_remove_ports(struct rocker *rocker)
rocker_port = rocker->ports[i];
rocker_port_ig_tbl(rocker_port, ROCKER_OP_FLAG_REMOVE);
unregister_netdev(rocker_port->dev);
+ rocker_destroy_flow_tables(rocker_port);
}
kfree(rocker->ports);
}
@@ -4023,6 +4082,12 @@ static int rocker_probe_port(struct rocker *rocker, unsigned int port_number)
goto err_port_ig_tbl;
}
+ err = rocker_init_flow_tables(rocker_port);
+ if (err) {
+ dev_err(&pdev->dev, "install flow table failed\n");
+ goto err_port_ig_tbl;
+ }
+
return 0;
err_port_ig_tbl:
diff --git a/drivers/net/ethernet/rocker/rocker_pipeline.h b/drivers/net/ethernet/rocker/rocker_pipeline.h
new file mode 100644
index 0000000..7136380
--- /dev/null
+++ b/drivers/net/ethernet/rocker/rocker_pipeline.h
@@ -0,0 +1,451 @@
+/*
+ * drivers/net/ethernet/rocker/rocker_pipeline.h - Rocker switch device driver
+ * Copyright (c) 2014 John Fastabend <john.r.fastabend@intel.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _ROCKER_PIPELINE_H_
+#define _ROCKER_PIPELINE_H_
+
+#include <linux/if_flow.h>
+#include <linux/if_flow_common.h>
+
+enum rocker_header_ids {
+ ROCKER_HEADER_UNSPEC = HEADER_MAX_UID,
+ ROCKER_HEADER_METADATA,
+};
+
+enum rocker_header_metadata_fields {
+ ROCKER_HEADER_METADATA_UNSPEC,
+ ROCKER_HEADER_METADATA_IN_LPORT,
+ ROCKER_HEADER_METADATA_GOTO_TBL,
+ ROCKER_HEADER_METADATA_GROUP_ID,
+};
+
+struct net_flow_field rocker_metadata_fields[] = {
+ { .name = "in_lport",
+ .uid = ROCKER_HEADER_METADATA_IN_LPORT,
+ .bitwidth = 32,},
+ { .name = "goto_tbl",
+ .uid = ROCKER_HEADER_METADATA_GOTO_TBL,
+ .bitwidth = 16,},
+ { .name = "group_id",
+ .uid = ROCKER_HEADER_METADATA_GROUP_ID,
+ .bitwidth = 32,},
+};
+
+struct net_flow_hdr rocker_metadata_t = {
+ .name = "metadata_t",
+ .uid = ROCKER_HEADER_METADATA,
+ .field_sz = ARRAY_SIZE(rocker_metadata_fields),
+ .fields = rocker_metadata_fields,
+};
+
+struct net_flow_hdr *rocker_header_list[] = {
+ &net_flow_ethernet,
+ &net_flow_vlan,
+ &net_flow_ipv4,
+ &rocker_metadata_t,
+ NULL,
+};
+
+/* rocker specific action definitions */
+struct net_flow_action_arg rocker_set_group_id_args[] = {
+ {
+ .name = "group_id",
+ .type = NFL_ACTION_ARG_TYPE_U32,
+ .value_u32 = 0,
+ },
+ {
+ .name = "",
+ .type = NFL_ACTION_ARG_TYPE_NULL,
+ },
+};
+
+enum rocker_action_ids {
+ ROCKER_ACTION_UNSPEC = ACTION_MAX_UID,
+ ROCKER_ACTION_SET_GROUP_ID,
+};
+
+struct net_flow_action rocker_set_group_id = {
+ .name = "set_group_id",
+ .uid = ROCKER_ACTION_SET_GROUP_ID,
+ .args = rocker_set_group_id_args,
+};
+
+struct net_flow_action *rocker_action_list[] = {
+ &net_flow_set_vlan_id,
+ &net_flow_copy_to_cpu,
+ &rocker_set_group_id,
+ &net_flow_pop_vlan,
+ &net_flow_set_eth_src,
+ &net_flow_set_eth_dst,
+ NULL,
+};
+
+/* headers graph */
+enum rocker_header_instance_ids {
+ ROCKER_HEADER_INSTANCE_UNSPEC,
+ ROCKER_HEADER_INSTANCE_ETHERNET,
+ ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ ROCKER_HEADER_INSTANCE_IPV4,
+ ROCKER_HEADER_INSTANCE_IN_LPORT,
+ ROCKER_HEADER_INSTANCE_GOTO_TABLE,
+ ROCKER_HEADER_INSTANCE_GROUP_ID,
+};
+
+struct net_flow_jump_table rocker_parse_ethernet[] = {
+ {
+ .field = {
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_ETHERTYPE,
+ .type = NFL_FIELD_REF_ATTR_TYPE_U16,
+ .value_u16 = ETH_P_IP,
+ },
+ .node = ROCKER_HEADER_INSTANCE_IPV4,
+ },
+ {
+ .field = {
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_ETHERTYPE,
+ .type = NFL_FIELD_REF_ATTR_TYPE_U16,
+ .value_u16 = ETH_P_8021Q,
+ },
+ .node = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ },
+ {
+ .field = {0},
+ .node = 0,
+ },
+};
+
+int rocker_ethernet_headers[] = {HEADER_ETHERNET, 0};
+
+struct net_flow_hdr_node rocker_ethernet_header_node = {
+ .name = "ethernet",
+ .uid = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .hdrs = rocker_ethernet_headers,
+ .jump = rocker_parse_ethernet,
+};
+
+struct net_flow_jump_table rocker_parse_vlan[] = {
+ {
+ .field = {
+ .header = HEADER_VLAN,
+ .field = HEADER_VLAN_ETHERTYPE,
+ .type = NFL_FIELD_REF_ATTR_TYPE_U16,
+ .value_u16 = ETH_P_IP,
+ },
+ .node = ROCKER_HEADER_INSTANCE_IPV4,
+ },
+ {
+ .field = {0},
+ .node = 0,
+ },
+};
+
+int rocker_vlan_headers[] = {HEADER_VLAN, 0};
+struct net_flow_hdr_node rocker_vlan_header_node = {
+ .name = "vlan",
+ .uid = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ .hdrs = rocker_vlan_headers,
+ .jump = rocker_parse_vlan,
+};
+
+struct net_flow_jump_table rocker_terminal_headers[] = {
+ {
+ .field = {0},
+ .node = NFL_JUMP_TABLE_DONE,
+ },
+ {
+ .field = {0},
+ .node = 0,
+ },
+};
+
+int rocker_ipv4_headers[] = {HEADER_IPV4, 0};
+struct net_flow_hdr_node rocker_ipv4_header_node = {
+ .name = "ipv4",
+ .uid = ROCKER_HEADER_INSTANCE_IPV4,
+ .hdrs = rocker_ipv4_headers,
+ .jump = rocker_terminal_headers,
+};
+
+int rocker_metadata_headers[] = {ROCKER_HEADER_METADATA, 0};
+struct net_flow_hdr_node rocker_in_lport_header_node = {
+ .name = "in_lport",
+ .uid = ROCKER_HEADER_INSTANCE_IN_LPORT,
+ .hdrs = rocker_metadata_headers,
+ .jump = rocker_terminal_headers,
+};
+
+struct net_flow_hdr_node rocker_group_id_header_node = {
+ .name = "group_id",
+ .uid = ROCKER_HEADER_INSTANCE_GROUP_ID,
+ .hdrs = rocker_metadata_headers,
+ .jump = rocker_terminal_headers,
+};
+
+struct net_flow_hdr_node *rocker_header_nodes[] = {
+ &rocker_ethernet_header_node,
+ &rocker_vlan_header_node,
+ &rocker_ipv4_header_node,
+ &rocker_in_lport_header_node,
+ &rocker_group_id_header_node,
+ NULL,
+};
+
+/* table definition */
+struct net_flow_field_ref rocker_matches_ig_port[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_IN_LPORT,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_IN_LPORT,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_vlan[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_IN_LPORT,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_IN_LPORT,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ .header = HEADER_VLAN,
+ .field = HEADER_VLAN_VID,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_term_mac[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_IN_LPORT,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_IN_LPORT,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_ETHERTYPE,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_DST_MAC,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ .header = HEADER_VLAN,
+ .field = HEADER_VLAN_VID,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_ucast_routing[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_ETHERTYPE,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = ROCKER_HEADER_INSTANCE_IPV4,
+ .header = HEADER_IPV4,
+ .field = HEADER_IPV4_DST_IP,
+ .mask_type = NFL_MASK_TYPE_LPM},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_bridge[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_DST_MAC,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ .header = HEADER_VLAN,
+ .field = HEADER_VLAN_VID,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_acl[] = {
+ { .instance = ROCKER_HEADER_INSTANCE_IN_LPORT,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_IN_LPORT,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_SRC_MAC,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_DST_MAC,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_ETHERTYPE,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = ROCKER_HEADER_INSTANCE_VLAN_OUTER,
+ .header = HEADER_VLAN,
+ .field = HEADER_VLAN_VID,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_IPV4,
+ .header = HEADER_IPV4,
+ .field = HEADER_IPV4_PROTOCOL,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = ROCKER_HEADER_INSTANCE_IPV4,
+ .header = HEADER_IPV4,
+ .field = HEADER_IPV4_DSCP,
+ .mask_type = NFL_MASK_TYPE_MASK},
+ { .instance = 0, .field = 0},
+};
+
+int rocker_actions_ig_port[] = {0};
+int rocker_actions_vlan[] = {ACTION_SET_VLAN_ID, 0};
+int rocker_actions_term_mac[] = {ACTION_COPY_TO_CPU, 0};
+int rocker_actions_ucast_routing[] = {ROCKER_ACTION_SET_GROUP_ID, 0};
+int rocker_actions_bridge[] = {ROCKER_ACTION_SET_GROUP_ID,
+ ACTION_COPY_TO_CPU, 0};
+int rocker_actions_acl[] = {ROCKER_ACTION_SET_GROUP_ID, 0};
+
+enum rocker_flow_table_id_space {
+ ROCKER_FLOW_TABLE_NULL,
+ ROCKER_FLOW_TABLE_ID_INGRESS_PORT,
+ ROCKER_FLOW_TABLE_ID_VLAN,
+ ROCKER_FLOW_TABLE_ID_TERMINATION_MAC,
+ ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING,
+ ROCKER_FLOW_TABLE_ID_MULTICAST_ROUTING,
+ ROCKER_FLOW_TABLE_ID_BRIDGING,
+ ROCKER_FLOW_TABLE_ID_ACL_POLICY,
+};
+
+struct net_flow_tbl rocker_ingress_port_table = {
+ .name = "ingress_port",
+ .uid = ROCKER_FLOW_TABLE_ID_INGRESS_PORT,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_ig_port,
+ .actions = rocker_actions_ig_port,
+ .cache = {0},
+};
+
+struct net_flow_tbl rocker_vlan_table = {
+ .name = "vlan",
+ .uid = ROCKER_FLOW_TABLE_ID_VLAN,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_vlan,
+ .actions = rocker_actions_vlan,
+ .cache = {0},
+};
+
+struct net_flow_tbl rocker_term_mac_table = {
+ .name = "term_mac",
+ .uid = ROCKER_FLOW_TABLE_ID_TERMINATION_MAC,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_term_mac,
+ .actions = rocker_actions_term_mac,
+ .cache = {0},
+};
+
+struct net_flow_tbl rocker_ucast_routing_table = {
+ .name = "ucast_routing",
+ .uid = ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_ucast_routing,
+ .actions = rocker_actions_ucast_routing,
+ .cache = {0},
+};
+
+struct net_flow_tbl rocker_bridge_table = {
+ .name = "bridge",
+ .uid = ROCKER_FLOW_TABLE_ID_BRIDGING,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_bridge,
+ .actions = rocker_actions_bridge,
+ .cache = {0},
+};
+
+struct net_flow_tbl rocker_acl_table = {
+ .name = "acl",
+ .uid = ROCKER_FLOW_TABLE_ID_ACL_POLICY,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_acl,
+ .actions = rocker_actions_acl,
+ .cache = {0},
+};
+
+struct net_flow_tbl *rocker_table_list[] = {
+ &rocker_ingress_port_table,
+ &rocker_vlan_table,
+ &rocker_term_mac_table,
+ &rocker_ucast_routing_table,
+ &rocker_bridge_table,
+ &rocker_acl_table,
+ NULL,
+};
+
+/* Define the table graph layout */
+struct net_flow_jump_table rocker_table_node_ig_port_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_VLAN},
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_ingress_port = {
+ .uid = ROCKER_FLOW_TABLE_ID_INGRESS_PORT,
+ .jump = rocker_table_node_ig_port_next};
+
+struct net_flow_jump_table rocker_table_node_vlan_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_TERMINATION_MAC},
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_vlan = {
+ .uid = ROCKER_FLOW_TABLE_ID_VLAN,
+ .jump = rocker_table_node_vlan_next};
+
+struct net_flow_jump_table rocker_table_node_term_mac_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING},
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_term_mac = {
+ .uid = ROCKER_FLOW_TABLE_ID_TERMINATION_MAC,
+ .jump = rocker_table_node_term_mac_next};
+
+struct net_flow_jump_table rocker_table_node_bridge_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_ACL_POLICY},
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_bridge = {
+ .uid = ROCKER_FLOW_TABLE_ID_BRIDGING,
+ .jump = rocker_table_node_bridge_next};
+
+struct net_flow_jump_table rocker_table_node_ucast_routing_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_ACL_POLICY},
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_ucast_routing = {
+ .uid = ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING,
+ .jump = rocker_table_node_ucast_routing_next};
+
+struct net_flow_jump_table rocker_table_node_acl_next[] = {
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_acl = {
+ .uid = ROCKER_FLOW_TABLE_ID_ACL_POLICY,
+ .jump = rocker_table_node_acl_next};
+
+struct net_flow_tbl_node *rocker_table_nodes[] = {
+ &rocker_table_node_ingress_port,
+ &rocker_table_node_vlan,
+ &rocker_table_node_term_mac,
+ &rocker_table_node_ucast_routing,
+ &rocker_table_node_bridge,
+ &rocker_table_node_acl,
+ NULL,
+};
+#endif /*_ROCKER_PIPELINE_H_*/
^ permalink raw reply related
* [net-next PATCH v3 07/12] net: rocker: add set rule ops
From: John Fastabend @ 2015-01-20 20:29 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Implement set rule operations for existing rocker tables.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 421 ++++++++++++++++++++++++++++++++++
1 file changed, 420 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index d2ea451..51290882 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -3830,6 +3830,422 @@ static struct net_flow_hdr_node **rocker_get_hgraph(struct net_device *d)
{
return rocker_header_nodes;
}
+
+static u32 rocker_goto_value(u32 id)
+{
+ switch (id) {
+ case ROCKER_FLOW_TABLE_ID_INGRESS_PORT:
+ return ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
+ case ROCKER_FLOW_TABLE_ID_VLAN:
+ return ROCKER_OF_DPA_TABLE_ID_VLAN;
+ case ROCKER_FLOW_TABLE_ID_TERMINATION_MAC:
+ return ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+ case ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING:
+ return ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
+ case ROCKER_FLOW_TABLE_ID_MULTICAST_ROUTING:
+ return ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
+ case ROCKER_FLOW_TABLE_ID_BRIDGING:
+ return ROCKER_OF_DPA_TABLE_ID_BRIDGING;
+ case ROCKER_FLOW_TABLE_ID_ACL_POLICY:
+ return ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+ default:
+ return 0;
+ }
+}
+
+static int rocker_flow_set_ig_port(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ enum rocker_of_dpa_table_id goto_tbl;
+ u32 in_lport_mask, in_lport;
+ int flags = 0;
+
+ /* ingress port table only supports one field/mask/action this
+ * simplifies the key construction and we can assume the values
+ * are the correct types/mask/action by valid check above. The
+ * user could pass multiple match/actions in a message with the
+ * same field multiple times currently the valid test does not
+ * catch this and we just use the first specified.
+ */
+ in_lport = rule->matches[0].value_u32;
+ in_lport_mask = rule->matches[0].mask_u32;
+ goto_tbl = rocker_goto_value(rule->actions[0].args[0].value_u16);
+
+ return rocker_flow_tbl_ig_port(rocker_port, flags,
+ in_lport, in_lport_mask,
+ goto_tbl);
+}
+
+static int rocker_flow_set_vlan(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ __be16 vlan_id, vlan_id_mask, new_vlan_id;
+ bool untagged, have_in_lport = false;
+ enum rocker_of_dpa_table_id goto_tbl;
+ int i, flags = 0;
+ u32 in_lport;
+
+ goto_tbl = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
+
+ /* If user does not specify vid match default to any */
+ vlan_id = htons(1);
+ vlan_id_mask = 0;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ switch (rule->matches[i].instance) {
+ case ROCKER_HEADER_INSTANCE_IN_LPORT:
+ in_lport = rule->matches[i].value_u32;
+ have_in_lport = true;
+ break;
+ case ROCKER_HEADER_INSTANCE_VLAN_OUTER:
+ if (rule->matches[i].field != HEADER_VLAN_VID)
+ break;
+
+ vlan_id = htons(rule->matches[i].value_u16);
+ vlan_id_mask = htons(rule->matches[i].mask_u16);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (!have_in_lport)
+ return -EINVAL;
+
+ /* If user does not specify a new vlan id use default vlan id */
+ new_vlan_id = rocker_port_vid_to_vlan(rocker_port, vlan_id, &untagged);
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ struct net_flow_action_arg *arg = &rule->actions[i].args[0];
+
+ switch (rule->actions[i].uid) {
+ case ACTION_SET_VLAN_ID:
+ new_vlan_id = htons(arg->value_u16);
+ if (new_vlan_id)
+ untagged = false;
+ break;
+ }
+ }
+
+ return rocker_flow_tbl_vlan(rocker_port, flags, in_lport,
+ vlan_id, vlan_id_mask, goto_tbl,
+ untagged, new_vlan_id);
+}
+
+static int rocker_flow_set_term_mac(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ __be16 vlan_id, vlan_id_mask, ethtype = 0;
+ const u8 *eth_dst, *eth_dst_mask;
+ u32 in_lport, in_lport_mask;
+ int i, flags = 0;
+ bool copy_to_cpu;
+
+ /* If user does not specify vid match default to any */
+ vlan_id = rocker_port->internal_vlan_id;
+ vlan_id_mask = 0;
+
+ /* If user does not specify in_lport match default to any */
+ in_lport = rocker_port->lport;
+ in_lport_mask = 0;
+
+ /* If user does not specify a mac address match any */
+ eth_dst = rocker_port->dev->dev_addr;
+ eth_dst_mask = zero_mac;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ switch (rule->matches[i].instance) {
+ case ROCKER_HEADER_INSTANCE_IN_LPORT:
+ in_lport = rule->matches[i].value_u32;
+ in_lport_mask = rule->matches[i].mask_u32;
+ break;
+ case ROCKER_HEADER_INSTANCE_VLAN_OUTER:
+ if (rule->matches[i].field != HEADER_VLAN_VID)
+ break;
+
+ vlan_id = htons(rule->matches[i].value_u16);
+ vlan_id_mask = htons(rule->matches[i].mask_u16);
+ break;
+ case ROCKER_HEADER_INSTANCE_ETHERNET:
+ switch (rule->matches[i].field) {
+ case HEADER_ETHERNET_DST_MAC:
+ eth_dst = (u8 *)&rule->matches[i].value_u64;
+ eth_dst_mask = (u8 *)&rule->matches[i].mask_u64;
+ break;
+ case HEADER_ETHERNET_ETHERTYPE:
+ ethtype = htons(rule->matches[i].value_u16);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (!ethtype)
+ return -EINVAL;
+
+ /* By default do not copy to cpu */
+ copy_to_cpu = false;
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ switch (rule->actions[i].uid) {
+ case ACTION_COPY_TO_CPU:
+ copy_to_cpu = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return rocker_flow_tbl_term_mac(rocker_port, in_lport, in_lport_mask,
+ ethtype, eth_dst, eth_dst_mask,
+ vlan_id, vlan_id_mask,
+ copy_to_cpu, flags);
+}
+
+static int rocker_flow_set_ucast_routing(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ return -EOPNOTSUPP;
+}
+
+static int rocker_flow_set_mcast_routing(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ return -EOPNOTSUPP;
+}
+
+static int rocker_flow_set_bridge(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ enum rocker_of_dpa_table_id goto_tbl;
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ u32 in_lport, in_lport_mask, group_id, tunnel_id;
+ __be16 vlan_id, vlan_id_mask;
+ const u8 *eth_dst, *eth_dst_mask;
+ int i, flags = 0;
+ bool copy_to_cpu;
+
+ goto_tbl = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
+
+ /* If user does not specify vid match default to any */
+ vlan_id = rocker_port->internal_vlan_id;
+ vlan_id_mask = 0;
+
+ /* If user does not specify in_lport match default to any */
+ in_lport = rocker_port->lport;
+ in_lport_mask = 0;
+
+ /* If user does not specify a mac address match any */
+ eth_dst = rocker_port->dev->dev_addr;
+ eth_dst_mask = NULL;
+
+ /* Do not support for tunnel_id yet. */
+ tunnel_id = 0;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ switch (rule->matches[i].instance) {
+ case ROCKER_HEADER_INSTANCE_IN_LPORT:
+ in_lport = rule->matches[i].value_u32;
+ in_lport_mask = rule->matches[i].mask_u32;
+ break;
+ case ROCKER_HEADER_INSTANCE_VLAN_OUTER:
+ if (rule->matches[i].field != HEADER_VLAN_VID)
+ break;
+
+ vlan_id = htons(rule->matches[i].value_u16);
+ vlan_id_mask = htons(rule->matches[i].mask_u16);
+ break;
+ case ROCKER_HEADER_INSTANCE_ETHERNET:
+ switch (rule->matches[i].field) {
+ case HEADER_ETHERNET_DST_MAC:
+ eth_dst = (u8 *)&rule->matches[i].value_u64;
+ eth_dst_mask = (u8 *)&rule->matches[i].mask_u64;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* By default do not copy to cpu and skip group assignment */
+ copy_to_cpu = false;
+ group_id = ROCKER_GROUP_NONE;
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ struct net_flow_action_arg *arg = &rule->actions[i].args[0];
+
+ switch (rule->actions[i].uid) {
+ case ACTION_COPY_TO_CPU:
+ copy_to_cpu = true;
+ break;
+ case ROCKER_ACTION_SET_GROUP_ID:
+ group_id = arg->value_u32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* Ignoring eth_dst_mask it seems to cause a EINVAL return code */
+ return rocker_flow_tbl_bridge(rocker_port, flags,
+ eth_dst, eth_dst_mask,
+ vlan_id, tunnel_id,
+ goto_tbl, group_id, copy_to_cpu);
+}
+
+static int rocker_flow_set_acl(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ u32 in_lport, in_lport_mask, group_id, tunnel_id;
+ __be16 vlan_id, vlan_id_mask, ethtype = 0;
+ const u8 *eth_dst, *eth_src, *eth_dst_mask, *eth_src_mask;
+ u8 protocol, protocol_mask, dscp, dscp_mask;
+ int i, flags = 0;
+
+ /* If user does not specify vid match default to any */
+ vlan_id = rocker_port->internal_vlan_id;
+ vlan_id_mask = 0;
+
+ /* If user does not specify in_lport match default to any */
+ in_lport = rocker_port->lport;
+ in_lport_mask = 0;
+
+ /* If user does not specify a mac address match any */
+ eth_dst = rocker_port->dev->dev_addr;
+ eth_src = zero_mac;
+ eth_dst_mask = NULL;
+ eth_src_mask = NULL;
+
+ /* If user does not set protocol/dscp mask them out */
+ protocol = 0;
+ dscp = 0;
+ protocol_mask = 0;
+ dscp_mask = 0;
+
+ /* Do not support for tunnel_id yet. */
+ tunnel_id = 0;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ switch (rule->matches[i].instance) {
+ case ROCKER_HEADER_INSTANCE_IN_LPORT:
+ in_lport = rule->matches[i].value_u32;
+ in_lport_mask = rule->matches[i].mask_u32;
+ break;
+ case ROCKER_HEADER_INSTANCE_VLAN_OUTER:
+ if (rule->matches[i].field != HEADER_VLAN_VID)
+ break;
+
+ vlan_id = htons(rule->matches[i].value_u16);
+ vlan_id_mask = htons(rule->matches[i].mask_u16);
+ break;
+ case ROCKER_HEADER_INSTANCE_ETHERNET:
+ switch (rule->matches[i].field) {
+ case HEADER_ETHERNET_SRC_MAC:
+ eth_src = (u8 *)&rule->matches[i].value_u64;
+ eth_src_mask = (u8 *)&rule->matches[i].mask_u64;
+ break;
+ case HEADER_ETHERNET_DST_MAC:
+ eth_dst = (u8 *)&rule->matches[i].value_u64;
+ eth_dst_mask = (u8 *)&rule->matches[i].mask_u64;
+ break;
+ case HEADER_ETHERNET_ETHERTYPE:
+ ethtype = htons(rule->matches[i].value_u16);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case ROCKER_HEADER_INSTANCE_IPV4:
+ switch (rule->matches[i].field) {
+ case HEADER_IPV4_PROTOCOL:
+ protocol = rule->matches[i].value_u8;
+ protocol_mask = rule->matches[i].mask_u8;
+ break;
+ case HEADER_IPV4_DSCP:
+ dscp = rule->matches[i].value_u8;
+ dscp_mask = rule->matches[i].mask_u8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* By default do not copy to cpu and skip group assignment */
+ group_id = ROCKER_GROUP_NONE;
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ switch (rule->actions[i].uid) {
+ case ROCKER_ACTION_SET_GROUP_ID:
+ group_id = rule->actions[i].args[0].value_u32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return rocker_flow_tbl_acl(rocker_port, flags,
+ in_lport, in_lport_mask,
+ eth_src, eth_src_mask,
+ eth_dst, eth_dst_mask, ethtype,
+ vlan_id, vlan_id_mask,
+ protocol, protocol_mask,
+ dscp, dscp_mask,
+ group_id);
+}
+
+static int rocker_set_rules(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ int err = -EINVAL;
+
+ switch (rule->table_id) {
+ case ROCKER_FLOW_TABLE_ID_INGRESS_PORT:
+ err = rocker_flow_set_ig_port(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_VLAN:
+ err = rocker_flow_set_vlan(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_TERMINATION_MAC:
+ err = rocker_flow_set_term_mac(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING:
+ err = rocker_flow_set_ucast_routing(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_MULTICAST_ROUTING:
+ err = rocker_flow_set_mcast_routing(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_BRIDGING:
+ err = rocker_flow_set_bridge(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_ACL_POLICY:
+ err = rocker_flow_set_acl(dev, rule);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static int rocker_del_rules(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ return -EOPNOTSUPP;
+}
#endif
static const struct net_device_ops rocker_port_netdev_ops = {
@@ -3852,6 +4268,9 @@ static const struct net_device_ops rocker_port_netdev_ops = {
.ndo_flow_get_actions = rocker_get_actions,
.ndo_flow_get_tbl_graph = rocker_get_tgraph,
.ndo_flow_get_hdr_graph = rocker_get_hgraph,
+
+ .ndo_flow_set_rule = rocker_set_rules,
+ .ndo_flow_del_rule = rocker_del_rules,
#endif
};
@@ -4084,7 +4503,7 @@ static int rocker_probe_port(struct rocker *rocker, unsigned int port_number)
err = rocker_init_flow_tables(rocker_port);
if (err) {
- dev_err(&pdev->dev, "install flow table failed\n");
+ dev_err(&pdev->dev, "install rule table failed\n");
goto err_port_ig_tbl;
}
^ permalink raw reply related
* [net-next PATCH v3 08/12] net: rocker: add group_id slices and drop explicit goto
From: John Fastabend @ 2015-01-20 20:29 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
This adds the group tables for l3_unicast, l2_rewrite and l2. In
addition to adding the tables we extend the metadata fields to
support three different group id lookups. One for each table and
drop the more generic one previously being used.
Finally we can also drop the goto action as it is not used anymore.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 174 ++++++++++++++++++++++++
drivers/net/ethernet/rocker/rocker_pipeline.h | 180 ++++++++++++++++++++++---
2 files changed, 328 insertions(+), 26 deletions(-)
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index 51290882..2be8f61 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -4088,8 +4088,8 @@ static int rocker_flow_set_bridge(struct net_device *dev,
case ACTION_COPY_TO_CPU:
copy_to_cpu = true;
break;
- case ROCKER_ACTION_SET_GROUP_ID:
- group_id = arg->value_u32;
+ case ROCKER_ACTION_SET_L3_UNICAST_GID:
+ group_id = ROCKER_GROUP_L3_UNICAST(arg->value_u32);
break;
default:
return -EINVAL;
@@ -4188,9 +4188,11 @@ static int rocker_flow_set_acl(struct net_device *dev,
group_id = ROCKER_GROUP_NONE;
for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ struct net_flow_action_arg *arg = &rule->actions[i].args[0];
+
switch (rule->actions[i].uid) {
- case ROCKER_ACTION_SET_GROUP_ID:
- group_id = rule->actions[i].args[0].value_u32;
+ case ROCKER_ACTION_SET_L3_UNICAST_GID:
+ group_id = ROCKER_GROUP_L3_UNICAST(arg->value_u32);
break;
default:
return -EINVAL;
@@ -4207,6 +4209,161 @@ static int rocker_flow_set_acl(struct net_device *dev,
group_id);
}
+static int rocker_flow_set_group_slice_l3_unicast(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ struct rocker_group_tbl_entry *entry;
+ int i, flags = 0;
+
+ entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags));
+ if (!entry)
+ return -ENOMEM;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ struct net_flow_field_ref *r = &rule->matches[i];
+
+ switch (r->instance) {
+ case ROCKER_HEADER_INSTANCE_L3_UNICAST_GID:
+ entry->group_id = ROCKER_GROUP_L3_UNICAST(r->value_u32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ struct net_flow_action_arg *arg = &rule->actions[i].args[0];
+
+ switch (rule->actions[i].uid) {
+ case ACTION_SET_ETH_SRC:
+ ether_addr_copy(entry->l3_unicast.eth_src,
+ (u8 *)&arg->value_u64);
+ break;
+ case ACTION_SET_ETH_DST:
+ ether_addr_copy(entry->l3_unicast.eth_dst,
+ (u8 *)&arg->value_u64);
+ break;
+ case ACTION_SET_VLAN_ID:
+ entry->l3_unicast.vlan_id = htons(arg->value_u16);
+ break;
+ case ACTION_CHECK_TTL_DROP:
+ entry->l3_unicast.ttl_check = true;
+ break;
+ case ROCKER_ACTION_SET_L2_REWRITE_GID:
+ entry->l3_unicast.group_id =
+ ROCKER_GROUP_L2_REWRITE(arg->value_u32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return rocker_group_tbl_do(rocker_port, flags, entry);
+}
+
+static int rocker_flow_set_group_slice_l2_rewrite(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ struct rocker_group_tbl_entry *entry;
+ int i, flags = 0;
+
+ entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags));
+ if (!entry)
+ return -ENOMEM;
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ struct net_flow_field_ref *r = &rule->matches[i];
+
+ switch (r->instance) {
+ case ROCKER_HEADER_INSTANCE_L2_REWRITE_GID:
+ entry->group_id = ROCKER_GROUP_L2_REWRITE(r->value_u32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ struct net_flow_action_arg *arg = &rule->actions[i].args[0];
+
+ switch (rule->actions[i].uid) {
+ case ACTION_SET_ETH_SRC:
+ ether_addr_copy(entry->l2_rewrite.eth_src,
+ (u8 *)&arg->value_u64);
+ break;
+ case ACTION_SET_ETH_DST:
+ ether_addr_copy(entry->l2_rewrite.eth_dst,
+ (u8 *)&arg->value_u64);
+ break;
+ case ACTION_SET_VLAN_ID:
+ entry->l2_rewrite.vlan_id = htons(arg->value_u16);
+ break;
+ case ROCKER_ACTION_SET_L2_GID:
+ entry->l2_rewrite.group_id =
+ ROCKER_GROUP_L2_INTERFACE(arg->value_u32,
+ rocker_port->lport);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return rocker_group_tbl_do(rocker_port, flags, entry);
+}
+
+static int rocker_flow_set_group_slice_l2(struct net_device *dev,
+ struct net_flow_rule *rule)
+{
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ struct rocker_group_tbl_entry *entry;
+ int i, flags = 0;
+ u32 lport;
+
+ entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags));
+ if (!entry)
+ return -ENOMEM;
+
+ lport = rocker_port->lport;
+
+ /* Use the dev lport if we don't have a specified lport instance
+ * from the user. We need to walk the list once before to extract
+ * any lport attribute.
+ */
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ switch (rule->matches[i].instance) {
+ case ROCKER_HEADER_METADATA_IN_LPORT:
+ lport = rule->matches[i].value_u32;
+ }
+ }
+
+ for (i = 0; rule->matches && rule->matches[i].instance; i++) {
+ struct net_flow_field_ref *r = &rule->matches[i];
+
+ switch (r->instance) {
+ case ROCKER_HEADER_INSTANCE_L2_GID:
+ entry->group_id =
+ ROCKER_GROUP_L2_INTERFACE(r->value_u32, lport);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; rule->actions && rule->actions[i].uid; i++) {
+ switch (rule->actions[i].uid) {
+ case ACTION_POP_VLAN:
+ entry->l2_interface.pop_vlan = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return rocker_group_tbl_do(rocker_port, flags, entry);
+}
+
static int rocker_set_rules(struct net_device *dev,
struct net_flow_rule *rule)
{
@@ -4234,6 +4391,15 @@ static int rocker_set_rules(struct net_device *dev,
case ROCKER_FLOW_TABLE_ID_ACL_POLICY:
err = rocker_flow_set_acl(dev, rule);
break;
+ case ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L3_UNICAST:
+ err = rocker_flow_set_group_slice_l3_unicast(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2_REWRITE:
+ err = rocker_flow_set_group_slice_l2_rewrite(dev, rule);
+ break;
+ case ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2:
+ err = rocker_flow_set_group_slice_l2(dev, rule);
+ break;
default:
break;
}
diff --git a/drivers/net/ethernet/rocker/rocker_pipeline.h b/drivers/net/ethernet/rocker/rocker_pipeline.h
index 7136380..6d1e2ee 100644
--- a/drivers/net/ethernet/rocker/rocker_pipeline.h
+++ b/drivers/net/ethernet/rocker/rocker_pipeline.h
@@ -22,19 +22,23 @@ enum rocker_header_ids {
enum rocker_header_metadata_fields {
ROCKER_HEADER_METADATA_UNSPEC,
ROCKER_HEADER_METADATA_IN_LPORT,
- ROCKER_HEADER_METADATA_GOTO_TBL,
- ROCKER_HEADER_METADATA_GROUP_ID,
+ ROCKER_HEADER_METADATA_L3_UNICAST_GID,
+ ROCKER_HEADER_METADATA_L2_REWRITE_GID,
+ ROCKER_HEADER_METADATA_L2_GID,
};
struct net_flow_field rocker_metadata_fields[] = {
{ .name = "in_lport",
.uid = ROCKER_HEADER_METADATA_IN_LPORT,
.bitwidth = 32,},
- { .name = "goto_tbl",
- .uid = ROCKER_HEADER_METADATA_GOTO_TBL,
- .bitwidth = 16,},
- { .name = "group_id",
- .uid = ROCKER_HEADER_METADATA_GROUP_ID,
+ { .name = "l3_unicast_group_id",
+ .uid = ROCKER_HEADER_METADATA_L3_UNICAST_GID,
+ .bitwidth = 32,},
+ { .name = "l2_rewrite_group_id",
+ .uid = ROCKER_HEADER_METADATA_L2_REWRITE_GID,
+ .bitwidth = 32,},
+ { .name = "l2_group_id",
+ .uid = ROCKER_HEADER_METADATA_L2_GID,
.bitwidth = 32,},
};
@@ -68,22 +72,39 @@ struct net_flow_action_arg rocker_set_group_id_args[] = {
enum rocker_action_ids {
ROCKER_ACTION_UNSPEC = ACTION_MAX_UID,
- ROCKER_ACTION_SET_GROUP_ID,
+ ROCKER_ACTION_SET_L3_UNICAST_GID,
+ ROCKER_ACTION_SET_L2_REWRITE_GID,
+ ROCKER_ACTION_SET_L2_GID,
+};
+
+struct net_flow_action rocker_set_l3_unicast_group_id = {
+ .name = "set_l3_unicast_group_id",
+ .uid = ROCKER_ACTION_SET_L3_UNICAST_GID,
+ .args = rocker_set_group_id_args,
+};
+
+struct net_flow_action rocker_set_l2_rewrite_group_id = {
+ .name = "set_l2_rewrite_group_id",
+ .uid = ROCKER_ACTION_SET_L2_REWRITE_GID,
+ .args = rocker_set_group_id_args,
};
-struct net_flow_action rocker_set_group_id = {
- .name = "set_group_id",
- .uid = ROCKER_ACTION_SET_GROUP_ID,
+struct net_flow_action rocker_set_l2_group_id = {
+ .name = "set_l2_group_id",
+ .uid = ROCKER_ACTION_SET_L2_GID,
.args = rocker_set_group_id_args,
};
struct net_flow_action *rocker_action_list[] = {
&net_flow_set_vlan_id,
&net_flow_copy_to_cpu,
- &rocker_set_group_id,
+ &rocker_set_l3_unicast_group_id,
+ &rocker_set_l2_rewrite_group_id,
+ &rocker_set_l2_group_id,
&net_flow_pop_vlan,
&net_flow_set_eth_src,
&net_flow_set_eth_dst,
+ &net_flow_check_ttl_drop,
NULL,
};
@@ -94,8 +115,9 @@ enum rocker_header_instance_ids {
ROCKER_HEADER_INSTANCE_VLAN_OUTER,
ROCKER_HEADER_INSTANCE_IPV4,
ROCKER_HEADER_INSTANCE_IN_LPORT,
- ROCKER_HEADER_INSTANCE_GOTO_TABLE,
- ROCKER_HEADER_INSTANCE_GROUP_ID,
+ ROCKER_HEADER_INSTANCE_L3_UNICAST_GID,
+ ROCKER_HEADER_INSTANCE_L2_REWRITE_GID,
+ ROCKER_HEADER_INSTANCE_L2_GID,
};
struct net_flow_jump_table rocker_parse_ethernet[] = {
@@ -183,9 +205,23 @@ struct net_flow_hdr_node rocker_in_lport_header_node = {
.jump = rocker_terminal_headers,
};
-struct net_flow_hdr_node rocker_group_id_header_node = {
- .name = "group_id",
- .uid = ROCKER_HEADER_INSTANCE_GROUP_ID,
+struct net_flow_hdr_node rocker_l2_group_id_header_node = {
+ .name = "l2_group_id",
+ .uid = ROCKER_HEADER_INSTANCE_L2_GID,
+ .hdrs = rocker_metadata_headers,
+ .jump = rocker_terminal_headers,
+};
+
+struct net_flow_hdr_node rocker_l2_rewrite_group_id_header_node = {
+ .name = "l2_rewrite_group_id",
+ .uid = ROCKER_HEADER_INSTANCE_L2_REWRITE_GID,
+ .hdrs = rocker_metadata_headers,
+ .jump = rocker_terminal_headers,
+};
+
+struct net_flow_hdr_node rocker_l3_unicast_group_id_header_node = {
+ .name = "l3_uniscast_group_id",
+ .uid = ROCKER_HEADER_INSTANCE_L3_UNICAST_GID,
.hdrs = rocker_metadata_headers,
.jump = rocker_terminal_headers,
};
@@ -195,7 +231,9 @@ struct net_flow_hdr_node *rocker_header_nodes[] = {
&rocker_vlan_header_node,
&rocker_ipv4_header_node,
&rocker_in_lport_header_node,
- &rocker_group_id_header_node,
+ &rocker_l3_unicast_group_id_header_node,
+ &rocker_l2_rewrite_group_id_header_node,
+ &rocker_l2_group_id_header_node,
NULL,
};
@@ -296,13 +334,48 @@ struct net_flow_field_ref rocker_matches_acl[] = {
{ .instance = 0, .field = 0},
};
+struct net_flow_field_ref rocker_matches_l3_unicast_group_slice[2] = {
+ { .instance = ROCKER_HEADER_INSTANCE_L3_UNICAST_GID,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_L3_UNICAST_GID,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_l2_rewrite_group_slice[2] = {
+ { .instance = ROCKER_HEADER_INSTANCE_L2_REWRITE_GID,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_L2_REWRITE_GID,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = 0, .field = 0},
+};
+
+struct net_flow_field_ref rocker_matches_l2_group_slice[2] = {
+ { .instance = ROCKER_HEADER_INSTANCE_L2_GID,
+ .header = ROCKER_HEADER_METADATA,
+ .field = ROCKER_HEADER_METADATA_L2_GID,
+ .mask_type = NFL_MASK_TYPE_EXACT},
+ { .instance = 0, .field = 0},
+};
+
int rocker_actions_ig_port[] = {0};
int rocker_actions_vlan[] = {ACTION_SET_VLAN_ID, 0};
int rocker_actions_term_mac[] = {ACTION_COPY_TO_CPU, 0};
-int rocker_actions_ucast_routing[] = {ROCKER_ACTION_SET_GROUP_ID, 0};
-int rocker_actions_bridge[] = {ROCKER_ACTION_SET_GROUP_ID,
+int rocker_actions_ucast_routing[] = {ROCKER_ACTION_SET_L3_UNICAST_GID, 0};
+int rocker_actions_bridge[] = {ROCKER_ACTION_SET_L3_UNICAST_GID,
ACTION_COPY_TO_CPU, 0};
-int rocker_actions_acl[] = {ROCKER_ACTION_SET_GROUP_ID, 0};
+int rocker_actions_acl[] = {ROCKER_ACTION_SET_L3_UNICAST_GID, 0};
+int rocker_actions_group_slice_l3_unicast[] = {ACTION_SET_ETH_SRC,
+ ACTION_SET_ETH_DST,
+ ACTION_SET_VLAN_ID,
+ ROCKER_ACTION_SET_L2_REWRITE_GID,
+ ACTION_CHECK_TTL_DROP, 0};
+int rocker_actions_group_slice_l2_rewrite[] = {ACTION_SET_ETH_SRC,
+ ACTION_SET_ETH_DST,
+ ACTION_SET_VLAN_ID,
+ ROCKER_ACTION_SET_L2_GID,
+ 0};
+int rocker_actions_group_slice_l2[] = {ACTION_POP_VLAN, 0};
enum rocker_flow_table_id_space {
ROCKER_FLOW_TABLE_NULL,
@@ -313,6 +386,9 @@ enum rocker_flow_table_id_space {
ROCKER_FLOW_TABLE_ID_MULTICAST_ROUTING,
ROCKER_FLOW_TABLE_ID_BRIDGING,
ROCKER_FLOW_TABLE_ID_ACL_POLICY,
+ ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L3_UNICAST,
+ ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2_REWRITE,
+ ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2,
};
struct net_flow_tbl rocker_ingress_port_table = {
@@ -375,6 +451,33 @@ struct net_flow_tbl rocker_acl_table = {
.cache = {0},
};
+struct net_flow_tbl rocker_group_slice_l3_unicast_table = {
+ .name = "group_slice_l3_unicast",
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L3_UNICAST,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_l3_unicast_group_slice,
+ .actions = rocker_actions_group_slice_l3_unicast,
+};
+
+struct net_flow_tbl rocker_group_slice_l2_rewrite_table = {
+ .name = "group_slice_l2_rewrite",
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2_REWRITE,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_l2_rewrite_group_slice,
+ .actions = rocker_actions_group_slice_l2_rewrite,
+};
+
+struct net_flow_tbl rocker_group_slice_l2_table = {
+ .name = "group_slice_l2",
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2,
+ .source = 1,
+ .size = -1,
+ .matches = rocker_matches_l2_group_slice,
+ .actions = rocker_actions_group_slice_l2,
+};
+
struct net_flow_tbl *rocker_table_list[] = {
&rocker_ingress_port_table,
&rocker_vlan_table,
@@ -382,6 +485,9 @@ struct net_flow_tbl *rocker_table_list[] = {
&rocker_ucast_routing_table,
&rocker_bridge_table,
&rocker_acl_table,
+ &rocker_group_slice_l3_unicast_table,
+ &rocker_group_slice_l2_rewrite_table,
+ &rocker_group_slice_l2_table,
NULL,
};
@@ -432,6 +538,7 @@ struct net_flow_tbl_node rocker_table_node_ucast_routing = {
.jump = rocker_table_node_ucast_routing_next};
struct net_flow_jump_table rocker_table_node_acl_next[] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L3_UNICAST},
{ .field = {0}, .node = 0},
};
@@ -439,6 +546,32 @@ struct net_flow_tbl_node rocker_table_node_acl = {
.uid = ROCKER_FLOW_TABLE_ID_ACL_POLICY,
.jump = rocker_table_node_acl_next};
+struct net_flow_jump_table rocker_table_node_group_l3_unicast_next[1] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2_REWRITE},
+};
+
+struct net_flow_tbl_node rocker_table_node_group_l3_unicast = {
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L3_UNICAST,
+ .jump = rocker_table_node_group_l3_unicast_next};
+
+struct net_flow_jump_table rocker_table_node_group_l2_rewrite_next[1] = {
+ { .field = {0}, .node = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2},
+};
+
+struct net_flow_tbl_node rocker_table_node_group_l2_rewrite = {
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2_REWRITE,
+ .jump = rocker_table_node_group_l2_rewrite_next};
+
+struct net_flow_jump_table rocker_table_node_group_l2_next[1] = {
+ { .field = {0}, .node = 0},
+};
+
+struct net_flow_tbl_node rocker_table_node_group_l2 = {
+ .uid = ROCKER_FLOW_TABLE_ID_GROUP_SLICE_L2,
+ .jump = rocker_table_node_group_l2_next};
+
+struct net_flow_tbl_node rocker_table_node_nil = {.uid = 0, .jump = NULL};
+
struct net_flow_tbl_node *rocker_table_nodes[] = {
&rocker_table_node_ingress_port,
&rocker_table_node_vlan,
@@ -446,6 +579,9 @@ struct net_flow_tbl_node *rocker_table_nodes[] = {
&rocker_table_node_ucast_routing,
&rocker_table_node_bridge,
&rocker_table_node_acl,
- NULL,
+ &rocker_table_node_group_l3_unicast,
+ &rocker_table_node_group_l2_rewrite,
+ &rocker_table_node_group_l2,
+ NULL
};
#endif /*_ROCKER_PIPELINE_H_*/
^ permalink raw reply related
* [net-next PATCH v3 09/12] net: rocker: add multicast path to bridging
From: John Fastabend @ 2015-01-20 20:30 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Add path in table graph to send packets to the bridge table.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker_pipeline.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/rocker/rocker_pipeline.h b/drivers/net/ethernet/rocker/rocker_pipeline.h
index 6d1e2ee..1a3dcdb 100644
--- a/drivers/net/ethernet/rocker/rocker_pipeline.h
+++ b/drivers/net/ethernet/rocker/rocker_pipeline.h
@@ -511,6 +511,14 @@ struct net_flow_tbl_node rocker_table_node_vlan = {
.jump = rocker_table_node_vlan_next};
struct net_flow_jump_table rocker_table_node_term_mac_next[] = {
+ { .field = {.instance = ROCKER_HEADER_INSTANCE_ETHERNET,
+ .header = HEADER_ETHERNET,
+ .field = HEADER_ETHERNET_DST_MAC,
+ .mask_type = NFL_MASK_TYPE_LPM,
+ .type = NFL_FIELD_REF_ATTR_TYPE_U64,
+ .value_u64 = (__u64)0x1,
+ .mask_u64 = (__u64)0x1,
+ }, .node = ROCKER_FLOW_TABLE_ID_BRIDGING},
{ .field = {0}, .node = ROCKER_FLOW_TABLE_ID_UNICAST_ROUTING},
{ .field = {0}, .node = 0},
};
^ permalink raw reply related
* [net-next PATCH v3 10/12] net: rocker: add cookie to group acls and use flow_id to set cookie
From: John Fastabend @ 2015-01-20 20:30 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Rocker uses a cookie value to identify flows however the flow API
already has a unique id for each flow. To help the translation
add support to set the cookie value through the internal rocker
flow API and then use the unique id in the cases where it is
available.
This patch extends the internal code paths to support the new
cookie value.
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 64 ++++++++++++++++++++++------------
1 file changed, 42 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index 2be8f61..5ba46f6 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -120,6 +120,7 @@ struct rocker_flow_tbl_entry {
struct rocker_group_tbl_entry {
struct hlist_node entry;
+ u64 cookie;
u32 cmd;
u32 group_id; /* key */
u16 group_count;
@@ -2233,7 +2234,8 @@ static int rocker_flow_tbl_add(struct rocker_port *rocker_port,
kfree(match);
} else {
found = match;
- found->cookie = rocker->flow_tbl_next_cookie++;
+ if (!found->cookie)
+ found->cookie = rocker->flow_tbl_next_cookie++;
hash_add(rocker->flow_tbl, &found->entry, found->key_crc32);
add_to_hw = true;
}
@@ -2311,7 +2313,7 @@ static int rocker_flow_tbl_do(struct rocker_port *rocker_port,
return rocker_flow_tbl_add(rocker_port, entry, nowait);
}
-static int rocker_flow_tbl_ig_port(struct rocker_port *rocker_port,
+static int rocker_flow_tbl_ig_port(struct rocker_port *rocker_port, u64 flow_id,
int flags, u32 in_lport, u32 in_lport_mask,
enum rocker_of_dpa_table_id goto_tbl)
{
@@ -2327,11 +2329,14 @@ static int rocker_flow_tbl_ig_port(struct rocker_port *rocker_port,
entry->key.ig_port.in_lport_mask = in_lport_mask;
entry->key.ig_port.goto_tbl = goto_tbl;
+ if (flow_id)
+ entry->cookie = flow_id;
+
return rocker_flow_tbl_do(rocker_port, flags, entry);
}
static int rocker_flow_tbl_vlan(struct rocker_port *rocker_port,
- int flags, u32 in_lport,
+ int flags, u64 flow_id, u32 in_lport,
__be16 vlan_id, __be16 vlan_id_mask,
enum rocker_of_dpa_table_id goto_tbl,
bool untagged, __be16 new_vlan_id)
@@ -2352,10 +2357,14 @@ static int rocker_flow_tbl_vlan(struct rocker_port *rocker_port,
entry->key.vlan.untagged = untagged;
entry->key.vlan.new_vlan_id = new_vlan_id;
+ if (flow_id)
+ entry->cookie = flow_id;
+
return rocker_flow_tbl_do(rocker_port, flags, entry);
}
static int rocker_flow_tbl_term_mac(struct rocker_port *rocker_port,
+ u64 flow_id,
u32 in_lport, u32 in_lport_mask,
__be16 eth_type, const u8 *eth_dst,
const u8 *eth_dst_mask, __be16 vlan_id,
@@ -2388,11 +2397,14 @@ static int rocker_flow_tbl_term_mac(struct rocker_port *rocker_port,
entry->key.term_mac.vlan_id_mask = vlan_id_mask;
entry->key.term_mac.copy_to_cpu = copy_to_cpu;
+ if (flow_id)
+ entry->cookie = flow_id;
+
return rocker_flow_tbl_do(rocker_port, flags, entry);
}
static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port,
- int flags,
+ int flags, u64 flow_id,
const u8 *eth_dst, const u8 *eth_dst_mask,
__be16 vlan_id, u32 tunnel_id,
enum rocker_of_dpa_table_id goto_tbl,
@@ -2442,11 +2454,14 @@ static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port,
entry->key.bridge.group_id = group_id;
entry->key.bridge.copy_to_cpu = copy_to_cpu;
+ if (flow_id)
+ entry->cookie = flow_id;
+
return rocker_flow_tbl_do(rocker_port, flags, entry);
}
static int rocker_flow_tbl_acl(struct rocker_port *rocker_port,
- int flags, u32 in_lport,
+ int flags, u64 flow_id, u32 in_lport,
u32 in_lport_mask,
const u8 *eth_src, const u8 *eth_src_mask,
const u8 *eth_dst, const u8 *eth_dst_mask,
@@ -2494,6 +2509,9 @@ static int rocker_flow_tbl_acl(struct rocker_port *rocker_port,
entry->key.acl.ip_tos_mask = ip_tos_mask;
entry->key.acl.group_id = group_id;
+ if (flow_id)
+ entry->cookie = flow_id;
+
return rocker_flow_tbl_do(rocker_port, flags, entry);
}
@@ -2604,7 +2622,7 @@ static int rocker_group_tbl_do(struct rocker_port *rocker_port,
}
static int rocker_group_l2_interface(struct rocker_port *rocker_port,
- int flags, __be16 vlan_id,
+ int flags, int flow_id, __be16 vlan_id,
u32 out_lport, int pop_vlan)
{
struct rocker_group_tbl_entry *entry;
@@ -2615,6 +2633,7 @@ static int rocker_group_l2_interface(struct rocker_port *rocker_port,
entry->group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_lport);
entry->l2_interface.pop_vlan = pop_vlan;
+ entry->cookie = flow_id;
return rocker_group_tbl_do(rocker_port, flags, entry);
}
@@ -2713,7 +2732,7 @@ static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port,
if (rocker_port->stp_state == BR_STATE_LEARNING ||
rocker_port->stp_state == BR_STATE_FORWARDING) {
out_lport = rocker_port->lport;
- err = rocker_group_l2_interface(rocker_port, flags,
+ err = rocker_group_l2_interface(rocker_port, flags, 0,
vlan_id, out_lport,
pop_vlan);
if (err) {
@@ -2739,7 +2758,7 @@ static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port,
return 0;
out_lport = 0;
- err = rocker_group_l2_interface(rocker_port, flags,
+ err = rocker_group_l2_interface(rocker_port, flags, 0,
vlan_id, out_lport,
pop_vlan);
if (err) {
@@ -2813,7 +2832,7 @@ static int rocker_port_ctrl_vlan_acl(struct rocker_port *rocker_port,
u32 group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_lport);
int err;
- err = rocker_flow_tbl_acl(rocker_port, flags,
+ err = rocker_flow_tbl_acl(rocker_port, flags, 0,
in_lport, in_lport_mask,
eth_src, eth_src_mask,
ctrl->eth_dst, ctrl->eth_dst_mask,
@@ -2842,7 +2861,7 @@ static int rocker_port_ctrl_vlan_bridge(struct rocker_port *rocker_port,
if (!rocker_port_is_bridged(rocker_port))
return 0;
- err = rocker_flow_tbl_bridge(rocker_port, flags,
+ err = rocker_flow_tbl_bridge(rocker_port, flags, 0,
ctrl->eth_dst, ctrl->eth_dst_mask,
vlan_id, tunnel_id,
goto_tbl, group_id, ctrl->copy_to_cpu);
@@ -2864,7 +2883,7 @@ static int rocker_port_ctrl_vlan_term(struct rocker_port *rocker_port,
if (ntohs(vlan_id) == 0)
vlan_id = rocker_port->internal_vlan_id;
- err = rocker_flow_tbl_term_mac(rocker_port,
+ err = rocker_flow_tbl_term_mac(rocker_port, 0,
rocker_port->lport, in_lport_mask,
ctrl->eth_type, ctrl->eth_dst,
ctrl->eth_dst_mask, vlan_id,
@@ -2978,7 +2997,7 @@ static int rocker_port_vlan(struct rocker_port *rocker_port, int flags,
return err;
}
- err = rocker_flow_tbl_vlan(rocker_port, flags,
+ err = rocker_flow_tbl_vlan(rocker_port, flags, 0,
in_lport, vlan_id, vlan_id_mask,
goto_tbl, untagged, internal_vlan_id);
if (err)
@@ -3003,7 +3022,7 @@ static int rocker_port_ig_tbl(struct rocker_port *rocker_port, int flags)
in_lport_mask = 0xffff0000;
goto_tbl = ROCKER_OF_DPA_TABLE_ID_VLAN;
- err = rocker_flow_tbl_ig_port(rocker_port, flags,
+ err = rocker_flow_tbl_ig_port(rocker_port, flags, 0,
in_lport, in_lport_mask,
goto_tbl);
if (err)
@@ -3053,7 +3072,7 @@ static int rocker_port_fdb_learn(struct rocker_port *rocker_port,
group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_lport);
if (!(flags & ROCKER_OP_FLAG_REFRESH)) {
- err = rocker_flow_tbl_bridge(rocker_port, flags, addr, NULL,
+ err = rocker_flow_tbl_bridge(rocker_port, flags, 0, addr, NULL,
vlan_id, tunnel_id, goto_tbl,
group_id, copy_to_cpu);
if (err)
@@ -3188,7 +3207,7 @@ static int rocker_port_router_mac(struct rocker_port *rocker_port,
vlan_id = rocker_port->internal_vlan_id;
eth_type = htons(ETH_P_IP);
- err = rocker_flow_tbl_term_mac(rocker_port,
+ err = rocker_flow_tbl_term_mac(rocker_port, 0,
rocker_port->lport, in_lport_mask,
eth_type, rocker_port->dev->dev_addr,
dst_mac_mask, vlan_id, vlan_id_mask,
@@ -3197,7 +3216,7 @@ static int rocker_port_router_mac(struct rocker_port *rocker_port,
return err;
eth_type = htons(ETH_P_IPV6);
- err = rocker_flow_tbl_term_mac(rocker_port,
+ err = rocker_flow_tbl_term_mac(rocker_port, 0,
rocker_port->lport, in_lport_mask,
eth_type, rocker_port->dev->dev_addr,
dst_mac_mask, vlan_id, vlan_id_mask,
@@ -3232,7 +3251,7 @@ static int rocker_port_fwding(struct rocker_port *rocker_port)
continue;
vlan_id = htons(vid);
pop_vlan = rocker_vlan_id_is_internal(vlan_id);
- err = rocker_group_l2_interface(rocker_port, flags,
+ err = rocker_group_l2_interface(rocker_port, flags, 0,
vlan_id, out_lport,
pop_vlan);
if (err) {
@@ -3872,7 +3891,7 @@ static int rocker_flow_set_ig_port(struct net_device *dev,
in_lport_mask = rule->matches[0].mask_u32;
goto_tbl = rocker_goto_value(rule->actions[0].args[0].value_u16);
- return rocker_flow_tbl_ig_port(rocker_port, flags,
+ return rocker_flow_tbl_ig_port(rocker_port, flags, 0,
in_lport, in_lport_mask,
goto_tbl);
}
@@ -3929,7 +3948,7 @@ static int rocker_flow_set_vlan(struct net_device *dev,
}
}
- return rocker_flow_tbl_vlan(rocker_port, flags, in_lport,
+ return rocker_flow_tbl_vlan(rocker_port, flags, 0, in_lport,
vlan_id, vlan_id_mask, goto_tbl,
untagged, new_vlan_id);
}
@@ -4003,7 +4022,8 @@ static int rocker_flow_set_term_mac(struct net_device *dev,
}
}
- return rocker_flow_tbl_term_mac(rocker_port, in_lport, in_lport_mask,
+ return rocker_flow_tbl_term_mac(rocker_port, 0,
+ in_lport, in_lport_mask,
ethtype, eth_dst, eth_dst_mask,
vlan_id, vlan_id_mask,
copy_to_cpu, flags);
@@ -4097,7 +4117,7 @@ static int rocker_flow_set_bridge(struct net_device *dev,
}
/* Ignoring eth_dst_mask it seems to cause a EINVAL return code */
- return rocker_flow_tbl_bridge(rocker_port, flags,
+ return rocker_flow_tbl_bridge(rocker_port, flags, 0,
eth_dst, eth_dst_mask,
vlan_id, tunnel_id,
goto_tbl, group_id, copy_to_cpu);
@@ -4199,7 +4219,7 @@ static int rocker_flow_set_acl(struct net_device *dev,
}
}
- return rocker_flow_tbl_acl(rocker_port, flags,
+ return rocker_flow_tbl_acl(rocker_port, flags, 0,
in_lport, in_lport_mask,
eth_src, eth_src_mask,
eth_dst, eth_dst_mask, ethtype,
^ permalink raw reply related
* [net-next PATCH v3 11/12] net: rocker: have flow api calls set cookie value
From: John Fastabend @ 2015-01-20 20:31 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index 5ba46f6..3ceb313 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -3891,7 +3891,7 @@ static int rocker_flow_set_ig_port(struct net_device *dev,
in_lport_mask = rule->matches[0].mask_u32;
goto_tbl = rocker_goto_value(rule->actions[0].args[0].value_u16);
- return rocker_flow_tbl_ig_port(rocker_port, flags, 0,
+ return rocker_flow_tbl_ig_port(rocker_port, flags, rule->uid,
in_lport, in_lport_mask,
goto_tbl);
}
@@ -3948,7 +3948,7 @@ static int rocker_flow_set_vlan(struct net_device *dev,
}
}
- return rocker_flow_tbl_vlan(rocker_port, flags, 0, in_lport,
+ return rocker_flow_tbl_vlan(rocker_port, flags, rule->uid, in_lport,
vlan_id, vlan_id_mask, goto_tbl,
untagged, new_vlan_id);
}
@@ -4022,7 +4022,7 @@ static int rocker_flow_set_term_mac(struct net_device *dev,
}
}
- return rocker_flow_tbl_term_mac(rocker_port, 0,
+ return rocker_flow_tbl_term_mac(rocker_port, rule->uid,
in_lport, in_lport_mask,
ethtype, eth_dst, eth_dst_mask,
vlan_id, vlan_id_mask,
@@ -4117,7 +4117,7 @@ static int rocker_flow_set_bridge(struct net_device *dev,
}
/* Ignoring eth_dst_mask it seems to cause a EINVAL return code */
- return rocker_flow_tbl_bridge(rocker_port, flags, 0,
+ return rocker_flow_tbl_bridge(rocker_port, flags, rule->uid,
eth_dst, eth_dst_mask,
vlan_id, tunnel_id,
goto_tbl, group_id, copy_to_cpu);
@@ -4219,7 +4219,7 @@ static int rocker_flow_set_acl(struct net_device *dev,
}
}
- return rocker_flow_tbl_acl(rocker_port, flags, 0,
+ return rocker_flow_tbl_acl(rocker_port, flags, rule->uid,
in_lport, in_lport_mask,
eth_src, eth_src_mask,
eth_dst, eth_dst_mask, ethtype,
@@ -4279,6 +4279,8 @@ static int rocker_flow_set_group_slice_l3_unicast(struct net_device *dev,
}
}
+ entry->cookie = rule->uid;
+
return rocker_group_tbl_do(rocker_port, flags, entry);
}
@@ -4330,6 +4332,8 @@ static int rocker_flow_set_group_slice_l2_rewrite(struct net_device *dev,
}
}
+ entry->cookie = rule->uid;
+
return rocker_group_tbl_do(rocker_port, flags, entry);
}
@@ -4381,6 +4385,8 @@ static int rocker_flow_set_group_slice_l2(struct net_device *dev,
}
}
+ entry->cookie = rule->uid;
+
return rocker_group_tbl_do(rocker_port, flags, entry);
}
^ permalink raw reply related
* [net-next PATCH v3 12/12] net: rocker: implement delete flow routine
From: John Fastabend @ 2015-01-20 20:31 UTC (permalink / raw)
To: tgraf, simon.horman, sfeldma; +Cc: netdev, jhs, davem, gerlitz.or, andy, ast
In-Reply-To: <20150120202404.1741.8658.stgit@nitbit.x32>
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
---
drivers/net/ethernet/rocker/rocker.c | 46 +++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c
index 3ceb313..ba48e88 100644
--- a/drivers/net/ethernet/rocker/rocker.c
+++ b/drivers/net/ethernet/rocker/rocker.c
@@ -4436,7 +4436,51 @@ static int rocker_set_rules(struct net_device *dev,
static int rocker_del_rules(struct net_device *dev,
struct net_flow_rule *rule)
{
- return -EOPNOTSUPP;
+ struct rocker_port *rocker_port = netdev_priv(dev);
+ struct rocker_flow_tbl_entry *entry;
+ struct rocker_group_tbl_entry *group;
+ struct hlist_node *tmp;
+ int bkt, err = -EEXIST;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rocker_port->rocker->flow_tbl_lock, flags);
+ hash_for_each_safe(rocker_port->rocker->flow_tbl,
+ bkt, tmp, entry, entry) {
+ if (rocker_goto_value(rule->table_id) != entry->key.tbl_id ||
+ rule->uid != entry->cookie)
+ continue;
+
+ hash_del(&entry->entry);
+ err = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&rocker_port->rocker->flow_tbl_lock, flags);
+
+ if (!err)
+ goto done;
+
+ spin_lock_irqsave(&rocker_port->rocker->group_tbl_lock, flags);
+ hash_for_each_safe(rocker_port->rocker->group_tbl,
+ bkt, tmp, group, entry) {
+ if (rocker_goto_value(rule->table_id) !=
+ ROCKER_GROUP_TYPE_GET(group->group_id) ||
+ rule->uid != group->cookie)
+ continue;
+
+ hash_del(&group->entry);
+ err = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&rocker_port->rocker->group_tbl_lock, flags);
+
+done:
+ if (!err) {
+ err = rocker_cmd_exec(rocker_port->rocker, rocker_port,
+ rocker_cmd_flow_tbl_del,
+ entry, NULL, NULL, true);
+ kfree(entry);
+ }
+ return err;
}
#endif
^ permalink raw reply related
* Re: Re: Re: [PATCH tip 0/9] tracing: attach eBPF programs to tracepoints/syscalls/kprobe
From: Alexei Starovoitov @ 2015-01-20 20:33 UTC (permalink / raw)
To: Masami Hiramatsu
Cc: Ingo Molnar, Steven Rostedt, Namhyung Kim,
Arnaldo Carvalho de Melo, Jiri Olsa, David S. Miller,
Daniel Borkmann, Hannes Frederic Sowa, Brendan Gregg, Linux API,
Network Development, LKML, yrl.pp-manager.tt@hitachi.com,
Jovi Zhangwei
On Tue, Jan 20, 2015 at 3:57 AM, Masami Hiramatsu
<masami.hiramatsu.pt@hitachi.com> wrote:
>
> Ok, BTW, would you think is it possible to use a reusable small scratchpad
> memory for passing arguments? (just a thought)
sure. doable, but what's the use case?
>> It's not usable for high frequency events which
>> need this in-kernel aggregation.
>> If events are rare, then just dumping everything
>> into trace buffer is just fine. No in-kernel program is needed.
>
> Hmm, let me ensure your point, the performance number is the reason why
> we need to do it in the kernel, right? Not mainly for the flexibility but speed.
if user space can do X at the same speed as kernel,
then user space is a better choice and more flexible.
In case of bpf programs two things user space cannot do:
- fast aggregation without adding penalty to things being traced
- access to in-kernel data structures
And often both used together.
Say, we want to monitor amount of network traffic per user.
So we'd use trace_net_dev_xmit() tracepoint and do
map[current_uid()] += skb_len
as part of the program.
Overhead will be tiny and users won't notice any slowdown.
Trying to do the same in user space by enabling
this tracepoint has two problems:
high overhead and events are hard to aggregate
per user, since trace has 'pid', but short lived
processes will have dead pids in trace output.
> - perf probe and kprobe-event gives us a complete understandable
> interface for what will be recorded at where.
> (we can see the event definitions via kprobe_events interface,
> without any tools)
> - kprobe-event gives a completely same interface as other tracepoint
> events.
> - it also doesn't require any build-binary parts :) nor special tools.
> We can play with ftrace on just a small busybox.
yeah, when debugging in busybox is the goal
and 'cat' and 'echo' are your only tools, then
debugfs interface is the only choice :)
> However, this does NOT interfere your patch upstreaming. I just said current
> ftrace method is also meaningful for some reasons :)
of course :)
To emphasize the point I was trying to make with tracex1:
The program is a filter/aggregator. The bpf maps
are not suitable for streaming the events. That's the job
of ring buffer/trace_pipe. The program may choose
to aggregate some events and discard them (by
returning 0 from the program), and the rest of
the events will be streamed to user space via
ring buffer in the format statically defined by tracepoint
or by kprobe arguments.
The tracex1 example loads the program and then
reads /sys/kernel/debugfs/tracing/trace_pipe...
That part I was trying to improve with bpf_trace_printk:
to give ability to programs to stream data in a format
different from the one statically defined by tracepoints.
But trace_printk has its disadvantages, so probably
something cleaner is needed.
Like in my earlier example of trace_net_dev_xmit,
if the program could add printing of uid to arguments
already printed, it would have helped user space.
> By the way, I concern about that bpf compiler can become another systemtap,
> especially if you build it on llvm.
> Would you plan to develop it on kernel
> tree? or apart from the kernel-side development?
I'm not sure I completely understand the concern.
perf is using a bunch of out-of-tree libraries.
mcjit of llvm or libgccjit are another libraries.
Or may be eventually eBPF can be generated
by something like libpcap.
Ideally I would like to see 'perf run script.txt'
where script.txt is a program in a language suited
for tracing. The tracing language not necessary
will fit networking use cases. Currently I'm
using C for both and it's the most convenient,
but some folks complained that 'restricted'
nature of this C is hard to grasp, so I can only
encourage Jovi to do ktap language to bpf
translator. If it generates bpf directly that's great,
if it uses gcc or llvm backend that's fine too.
> I think it is hard to sync the development if you do it out-of-tree.
I think some pieces would have to be out of tree.
I've kept standalone llvm backend across 3.2, 3.3 and 3.4
but it gets polluted with ifdefs and not really a long term
solution, so now I'm working on upstreaming it
and feedback/codereviews I got, definitely improved
the quality of the bpf backend.
In case of backends the only bit to sync is instruction
set itself, which is stable. New instructions may be
added, but that's not a concern.
llvm backend doesn't care what language is
used in front-end or how programs are attached
to tracepoints or what set of bpf helper
functions is available.
All such bits and the main interface for
dynamic tracer, imo, should be in perf binary.
What it does underneath and how
many times it calls into llvm/gcc lib, won't be visible.
In case of systemtap compile time, for whatever
reason, is slow to the point of being annoying,
but here it should be instant.
^ permalink raw reply
* [PATCH net-next] amd-xgbe: Let OS arch support adjust DMA mask as needed
From: Tom Lendacky @ 2015-01-20 20:47 UTC (permalink / raw)
To: netdev; +Cc: David Miller
Set the DMA mask to 64-bit and let the underlying arch support adjust
it as appropriate based on device tree DMA ranges, etc.
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
---
drivers/net/ethernet/amd/xgbe/xgbe-main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-main.c b/drivers/net/ethernet/amd/xgbe/xgbe-main.c
index 32dd651..080c3a8 100644
--- a/drivers/net/ethernet/amd/xgbe/xgbe-main.c
+++ b/drivers/net/ethernet/amd/xgbe/xgbe-main.c
@@ -377,7 +377,7 @@ static int xgbe_probe(struct platform_device *pdev)
/* Set the DMA mask */
if (!dev->dma_mask)
dev->dma_mask = &dev->coherent_dma_mask;
- ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(dev, "dma_set_mask_and_coherent failed\n");
goto err_io;
^ permalink raw reply related
* [PATCH net-next 0/3] openvswitch: Add STT support.
From: Pravin B Shelar @ 2015-01-20 20:25 UTC (permalink / raw)
To: davem; +Cc: netdev, Pravin B Shelar
Following patch series adds support for Stateless Transport
Tunneling protocol.
STT uses TCP segmentation offload available in most of NIC. On
packet xmit STT driver appends STT header along with TCP header
to the packet. For GSO packet GSO parameters are set according
to tunnel configuration and packet is handed over to networking
stack. This allows use of segmentation offload available in NICs
Netperf unidirectional test gives ~9.4 Gbits/s performance on 10Gbit
NIC with 1500 byte MTU with two TCP streams.
The protocol is documented at
http://www.ietf.org/archive/id/draft-davie-stt-06.txt
I will send out OVS userspace patch on ovs-dev mailing list.
Pravin B Shelar (3):
skbuff: Add skb_list_linearize()
net: Add STT tunneling protocol.
openvswitch: Add support for STT tunneling.
include/linux/skbuff.h | 2 +
include/net/stt.h | 55 ++
include/uapi/linux/openvswitch.h | 1 +
net/core/skbuff.c | 35 +
net/ipv4/Kconfig | 11 +
net/ipv4/Makefile | 1 +
net/ipv4/stt.c | 1386 ++++++++++++++++++++++++++++++++++++++
net/openvswitch/Kconfig | 10 +
net/openvswitch/Makefile | 1 +
net/openvswitch/vport-stt.c | 214 ++++++
10 files changed, 1716 insertions(+)
create mode 100644 include/net/stt.h
create mode 100644 net/ipv4/stt.c
create mode 100644 net/openvswitch/vport-stt.c
--
1.9.1
^ permalink raw reply
* [PATCH net-next 1/3] skbuff: Add skb_list_linearize()
From: Pravin B Shelar @ 2015-01-20 20:25 UTC (permalink / raw)
To: davem; +Cc: netdev, Pravin B Shelar
similar to skb_linearize(), this API takes skb list as arg and
linearize it into one big skb. STT driver patch will use this.
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
include/linux/skbuff.h | 2 ++
net/core/skbuff.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 85ab7d7..c9194c1 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -2532,6 +2532,8 @@ static inline int skb_linearize(struct sk_buff *skb)
return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
}
+int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask);
+
/**
* skb_has_shared_frag - can any frag be overwritten
* @skb: buffer to test
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 56db472..9ef2881 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2329,6 +2329,41 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
}
EXPORT_SYMBOL(skb_copy_and_csum_dev);
+int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask)
+{
+ struct sk_buff *skb;
+ int tlen = 0;
+ int err;
+
+ err = skb_linearize(head);
+ if (err)
+ return err;
+
+ skb = head->next;
+ while (skb) {
+ tlen += skb->len;
+ skb = skb->next;
+ }
+ err = pskb_expand_head(head, 0, tlen, gfp_mask);
+ if (err)
+ return err;
+
+ skb = head->next;
+ while (skb) {
+ err = skb_copy_bits(skb, 0, skb_tail_pointer(head), skb->len);
+ if (err)
+ return err;
+ head->tail += skb->len;
+ skb = skb->next;
+ }
+ kfree_skb_list(head->next);
+ head->next = NULL;
+ head->len += tlen;
+ head->truesize = SKB_TRUESIZE(skb_end_offset(head));
+ return 0;
+}
+EXPORT_SYMBOL(skb_list_linearize);
+
/**
* skb_dequeue - remove from the head of the queue
* @list: list to dequeue from
--
1.9.1
^ permalink raw reply related
* [PATCH net-next 2/3] net: Add STT tunneling protocol.
From: Pravin B Shelar @ 2015-01-20 20:25 UTC (permalink / raw)
To: davem; +Cc: netdev, Pravin B Shelar, Jesse Gross
This adds a device level support for Stateless TCP Tunnel (STT)
protocol encapsulation. NF-hook is used for receiving STT
packets from networking stack.
Open vSwitch can be used for configuring, set up and tear down
STT tunnels.
The protocol is documented at
http://www.ietf.org/archive/id/draft-davie-stt-06.txt
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: Jesse Gross <jesse@nicira.com>
---
include/net/stt.h | 55 +++
net/ipv4/Kconfig | 11 +
net/ipv4/Makefile | 1 +
net/ipv4/stt.c | 1386 +++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1453 insertions(+)
create mode 100644 include/net/stt.h
create mode 100644 net/ipv4/stt.c
diff --git a/include/net/stt.h b/include/net/stt.h
new file mode 100644
index 0000000..d3a9242
--- /dev/null
+++ b/include/net/stt.h
@@ -0,0 +1,55 @@
+#ifndef __NET_STT_H
+#define __NET_STT_H 1
+
+#include <net/ip_tunnels.h>
+
+struct stthdr {
+ __u8 version;
+ __u8 flags;
+ __u8 l4_offset;
+ __u8 reserved;
+ __be16 mss;
+ __be16 vlan_tci;
+ __be64 key;
+};
+
+/* Padding after the end of the tunnel headers to provide alignment
+ * for inner packet IP header after 14 byte Ethernet header.
+ */
+#define STT_ETH_PAD 2
+
+static inline struct stthdr *stt_hdr(const struct sk_buff *skb)
+{
+ return (struct stthdr *)(skb_transport_header(skb) +
+ sizeof(struct tcphdr));
+}
+
+struct stt_sock;
+typedef void (stt_rcv_t)(struct stt_sock *stt_sock, struct sk_buff *skb);
+
+/* @list: Per-net list of STT ports.
+ * @rcv: The callback is called on STT packet recv, STT reassembly can generate
+ * multiple packets, in this case first packet has tunnel outer header, rest
+ * of the packets are inner packet segments with no stt header.
+ * @rcv_data: user data.
+ * @sock: Fake TCP socket for the STT port.
+ */
+struct stt_sock {
+ struct list_head list;
+ stt_rcv_t *rcv;
+ void *rcv_data;
+ struct socket *sock;
+ struct rcu_head rcu;
+};
+
+struct stt_sock *stt_sock_add(struct net *net, __be16 port,
+ stt_rcv_t *rcv, void *data);
+
+void stt_sock_release(struct stt_sock *vs);
+
+int stt_xmit_skb(struct sk_buff *skb, struct rtable *rt,
+ __be32 src, __be32 dst, __u8 tos,
+ __u8 ttl, __be16 df, __be16 src_port, __be16 dst_port,
+ __be64 tun_id);
+
+#endif /*ifdef__NET_STT_H */
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index bd29016..3ab00be 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -344,6 +344,17 @@ config GENEVE
To compile this driver as a module, choose M here: the module
+config STT
+ tristate "STT Encapsulation"
+ depends on INET
+ depends on NETFILTER
+ ---help---
+ This allows one to create STT virtual interfaces that provide
+ Layer 2 Networks over Layer 3 Networks. The STT protocol
+ described in the draft:
+ http://www.ietf.org/archive/id/draft-davie-stt-06.txt
+
+ To compile this driver as a module, choose M here: the module
config INET_AH
tristate "IP: AH transformation"
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 518c04e..d504fde 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
obj-$(CONFIG_MEMCG_KMEM) += tcp_memcontrol.o
obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
obj-$(CONFIG_GENEVE) += geneve.o
+obj-$(CONFIG_STT) += stt.o
obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
xfrm4_output.o xfrm4_protocol.o
diff --git a/net/ipv4/stt.c b/net/ipv4/stt.c
new file mode 100644
index 0000000..a444df9
--- /dev/null
+++ b/net/ipv4/stt.c
@@ -0,0 +1,1386 @@
+/*
+ * Stateless TCP Tunnel (STT) vport.
+ *
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/flex_array.h>
+#include <linux/if.h>
+#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/jhash.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/percpu.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/workqueue.h>
+
+#include <net/icmp.h>
+#include <net/inet_ecn.h>
+#include <net/ip.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+#include <net/stt.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#define STT_VER 0
+
+#define STT_CSUM_VERIFIED BIT(0)
+#define STT_CSUM_PARTIAL BIT(1)
+#define STT_PROTO_IPV4 BIT(2)
+#define STT_PROTO_TCP BIT(3)
+#define STT_PROTO_TYPES (STT_PROTO_IPV4 | STT_PROTO_TCP)
+
+#define STT_BASE_HLEN (sizeof(struct stthdr) + STT_ETH_PAD)
+#define STT_HEADER_LEN (sizeof(struct tcphdr) + STT_BASE_HLEN)
+
+/* The length and offset of a fragment are encoded in the sequence number.
+ * STT_SEQ_LEN_SHIFT is the left shift needed to store the length.
+ * STT_SEQ_OFFSET_MASK is the mask to extract the offset.
+ */
+#define STT_SEQ_LEN_SHIFT 16
+#define STT_SEQ_OFFSET_MASK (BIT(STT_SEQ_LEN_SHIFT) - 1)
+
+/* The maximum amount of memory used to store packets waiting to be reassembled
+ * on a given CPU. Once this threshold is exceeded we will begin freeing the
+ * least recently used fragments.
+ */
+#define REASM_HI_THRESH (4 * 1024 * 1024)
+/* The target for the high memory evictor. Once we have exceeded
+ * REASM_HI_THRESH, we will continue freeing fragments until we hit
+ * this limit.
+ */
+#define REASM_LO_THRESH (3 * 1024 * 1024)
+/* The length of time a given packet has to be reassembled from the time the
+ * first fragment arrives. Once this limit is exceeded it becomes available
+ * for cleaning.
+ */
+#define FRAG_EXP_TIME (30 * HZ)
+/* Number of hash entries. Each entry has only a single slot to hold a packet
+ * so if there are collisions, we will drop packets. This is allocated
+ * per-cpu and each entry consists of struct pkt_frag.
+ */
+#define FRAG_HASH_SHIFT 8
+#define FRAG_HASH_ENTRIES BIT(FRAG_HASH_SHIFT)
+#define FRAG_HASH_SEGS ((sizeof(u32) * 8) / FRAG_HASH_SHIFT)
+
+#define CLEAN_PERCPU_INTERVAL (30 * HZ)
+
+struct pkt_key {
+ __be32 saddr;
+ __be32 daddr;
+ __be32 pkt_seq;
+ u32 mark;
+};
+
+struct pkt_frag {
+ struct sk_buff *skbs;
+ unsigned long timestamp;
+ struct list_head lru_node;
+ struct pkt_key key;
+};
+
+struct stt_percpu {
+ struct flex_array *frag_hash;
+ struct list_head frag_lru;
+ unsigned int frag_mem_used;
+
+ /* Protect frags table. */
+ spinlock_t lock;
+};
+
+struct first_frag {
+ struct sk_buff *last_skb;
+ unsigned int mem_used;
+ u16 tot_len;
+ u16 rcvd_len;
+ bool ecn_ce;
+};
+
+struct frag_skb_cb {
+ u16 offset;
+
+ /* Only valid for the first skb in the chain. */
+ struct first_frag first;
+};
+
+#define FRAG_CB(skb) ((struct frag_skb_cb *)(skb)->cb)
+
+static struct stt_percpu __percpu *stt_percpu_data __read_mostly;
+static u32 frag_hash_seed __read_mostly;
+
+/* Protects sock-hash and refcounts. */
+static DEFINE_MUTEX(stt_mutex);
+
+/* per-network namespace private data for this module */
+struct stt_net {
+ struct list_head sock_list;
+};
+
+static int stt_net_id;
+static int n_tunnels;
+static DEFINE_PER_CPU(u32, pkt_seq_counter);
+
+static void clean_percpu(struct work_struct *work);
+static DECLARE_DELAYED_WORK(clean_percpu_wq, clean_percpu);
+
+static struct stt_sock *stt_find_sock(struct net *net, __be16 port)
+{
+ struct stt_net *sn = net_generic(net, stt_net_id);
+ struct stt_sock *stt_sock;
+
+ list_for_each_entry_rcu(stt_sock, &sn->sock_list, list) {
+ if (inet_sk(stt_sock->sock->sk)->inet_sport == port)
+ return stt_sock;
+ }
+ return NULL;
+}
+
+static __be32 ack_seq(void)
+{
+#if NR_CPUS <= 65536
+ u32 pkt_seq, ack;
+
+ pkt_seq = this_cpu_read(pkt_seq_counter);
+ ack = pkt_seq << ilog2(NR_CPUS) | smp_processor_id();
+ this_cpu_inc(pkt_seq_counter);
+
+ return (__force __be32)ack;
+#else
+#error "Support for greater than 64k CPUs not implemented"
+#endif
+}
+
+static int clear_gso(struct sk_buff *skb)
+{
+ struct skb_shared_info *shinfo = skb_shinfo(skb);
+ int err;
+
+ if (shinfo->gso_type == 0 && shinfo->gso_size == 0 &&
+ shinfo->gso_segs == 0)
+ return 0;
+
+ err = skb_unclone(skb, GFP_ATOMIC);
+ if (unlikely(err))
+ return err;
+
+ shinfo = skb_shinfo(skb);
+ shinfo->gso_type = 0;
+ shinfo->gso_size = 0;
+ shinfo->gso_segs = 0;
+ return 0;
+}
+
+static struct sk_buff *stt_build_header(struct sk_buff *skb, __be64 tun_id,
+ __be16 s_port, __be16 d_port,
+ __be32 saddr, __be32 dst,
+ __be16 h_proto, u8 nw_proto,
+ int dst_mtu)
+{
+ int data_len = skb->len + sizeof(struct stthdr) + STT_ETH_PAD;
+ unsigned short encap_mss;
+ struct tcphdr *tcph;
+ struct stthdr *stth;
+
+ skb_push(skb, STT_HEADER_LEN);
+ skb_reset_transport_header(skb);
+ tcph = tcp_hdr(skb);
+ memset(tcph, 0, STT_HEADER_LEN);
+ stth = stt_hdr(skb);
+
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ stth->flags |= STT_CSUM_PARTIAL;
+
+ stth->l4_offset = skb->csum_start -
+ (skb_headroom(skb) +
+ skb_transport_offset(skb) +
+ STT_HEADER_LEN);
+
+ if (h_proto == htons(ETH_P_IP))
+ stth->flags |= STT_PROTO_IPV4;
+
+ if (nw_proto == IPPROTO_TCP)
+ stth->flags |= STT_PROTO_TCP;
+
+ stth->mss = htons(skb_shinfo(skb)->gso_size);
+ } else if (skb->ip_summed == CHECKSUM_UNNECESSARY) {
+ stth->flags |= STT_CSUM_VERIFIED;
+ }
+
+ stth->vlan_tci = htons(skb->vlan_tci);
+ skb->vlan_tci = 0;
+ put_unaligned(tun_id, &stth->key);
+
+ tcph->source = s_port;
+ tcph->dest = d_port;
+ tcph->doff = sizeof(struct tcphdr) / 4;
+ tcph->ack = 1;
+ tcph->psh = 1;
+ tcph->window = htons(USHRT_MAX);
+ tcph->seq = htonl(data_len << STT_SEQ_LEN_SHIFT);
+ tcph->ack_seq = ack_seq();
+ tcph->check = ~tcp_v4_check(skb->len - skb_transport_offset(skb),
+ saddr, dst, 0);
+
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb->ip_summed = CHECKSUM_PARTIAL;
+
+ encap_mss = dst_mtu - sizeof(struct iphdr) - sizeof(struct tcphdr);
+ if (data_len > encap_mss) {
+ /* It's pretty rare to hit this case, so just fall back to
+ * linearizing for now.
+ */
+ if (skb_shinfo(skb)->frag_list &&
+ unlikely(__skb_linearize(skb)))
+ goto error;
+
+ if (unlikely(skb_unclone(skb, GFP_ATOMIC)))
+ goto error;
+
+ skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
+ skb_shinfo(skb)->gso_size = encap_mss;
+ skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(data_len, encap_mss);
+ } else {
+ if (unlikely(clear_gso(skb)))
+ goto error;
+ }
+
+ return skb;
+error:
+ kfree_skb(skb);
+ return NULL;
+}
+
+static bool stt_can_offload(struct sk_buff *skb, __be16 h_proto, u8 nw_proto)
+{
+ if (skb_is_gso(skb) && skb->ip_summed != CHECKSUM_PARTIAL) {
+ int csum_offset;
+ int len;
+ __sum16 *csum;
+
+ if (nw_proto == IPPROTO_TCP)
+ csum_offset = offsetof(struct tcphdr, check);
+ else if (nw_proto == IPPROTO_UDP)
+ csum_offset = offsetof(struct udphdr, check);
+ else
+ return false;
+
+ len = skb->len - skb_transport_offset(skb);
+ csum = (__sum16 *)(skb_transport_header(skb) + csum_offset);
+
+ if (h_proto == htons(ETH_P_IP)) {
+ struct iphdr *iph = ip_hdr(skb);
+ *csum = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
+ len, nw_proto, 0);
+ } else if (h_proto == htons(ETH_P_IPV6)) {
+ struct ipv6hdr *ip6h = ipv6_hdr(skb);
+ *csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+ len, nw_proto, 0);
+ } else {
+ return false;
+ }
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = csum_offset;
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ }
+
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ /* Assume receiver can only offload TCP/UDP over IPv4/6,
+ * and require 802.1Q VLANs to be accelerated.
+ */
+ if (h_proto != htons(ETH_P_IP) &&
+ h_proto != htons(ETH_P_IPV6))
+ return false;
+ if (nw_proto != IPPROTO_TCP && nw_proto != IPPROTO_UDP)
+ return false;
+
+ /* L4 offset must fit in a 1-byte field. */
+ if (skb->csum_start - skb_headroom(skb) > 255)
+ return false;
+
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_TCP_ECN)
+ return false;
+ }
+ /* Total size of encapsulated packet must fit in 16 bits. */
+ if (skb->len + STT_HEADER_LEN + sizeof(struct iphdr) > 65535)
+ return false;
+
+ return true;
+}
+
+static bool need_linearize(const struct sk_buff *skb)
+{
+ struct skb_shared_info *shinfo = skb_shinfo(skb);
+ int i;
+
+ if (unlikely(shinfo->frag_list))
+ return true;
+
+ /* Generally speaking we should linearize if there are paged frags.
+ * However, if all of the refcounts are 1 we know nobody else can
+ * change them from underneath us and we can skip the linearization.
+ */
+ for (i = 0; i < shinfo->nr_frags; i++)
+ if (unlikely(page_count(skb_frag_page(&shinfo->frags[i])) > 1))
+ return true;
+
+ return false;
+}
+
+static struct sk_buff *handle_offloads(struct sk_buff *skb)
+{
+ int err;
+
+ if (skb_is_gso(skb)) {
+ struct sk_buff *nskb;
+ char cb[sizeof(skb->cb)];
+
+ memcpy(cb, skb->cb, sizeof(cb));
+
+ nskb = __skb_gso_segment(skb, 0, false);
+ if (IS_ERR(nskb)) {
+ err = PTR_ERR(nskb);
+ goto error;
+ }
+
+ consume_skb(skb);
+ skb = nskb;
+ while (nskb) {
+ memcpy(nskb->cb, cb, sizeof(cb));
+ nskb = nskb->next;
+ }
+ } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ /* Pages aren't locked and could change at any time.
+ * If this happens after we compute the checksum, the
+ * checksum will be wrong. We linearize now to avoid
+ * this problem.
+ */
+ if (unlikely(need_linearize(skb))) {
+ err = __skb_linearize(skb);
+ if (unlikely(err))
+ goto error;
+ }
+
+ err = skb_checksum_help(skb);
+ if (unlikely(err))
+ goto error;
+ }
+
+ skb->ip_summed = CHECKSUM_NONE;
+
+ return skb;
+error:
+ return ERR_PTR(err);
+}
+
+int stt_xmit_skb(struct sk_buff *skb, struct rtable *rt,
+ __be32 src, __be32 dst, __u8 tos,
+ __u8 ttl, __be16 df, __be16 src_port, __be16 dst_port,
+ __be64 tun_id)
+{
+ struct ethhdr *eh = eth_hdr(skb);
+ struct iphdr *iph = ip_hdr(skb);
+ __be16 inner_h_proto;
+ u8 inner_nw_proto;
+ int ret = 0, min_headroom;
+
+ inner_h_proto = eh->h_proto;
+ inner_nw_proto = iph->protocol;
+
+ min_headroom = LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len
+ + STT_HEADER_LEN + sizeof(struct iphdr);
+
+ if (skb_headroom(skb) < min_headroom || skb_header_cloned(skb)) {
+ int head_delta = SKB_DATA_ALIGN(min_headroom -
+ skb_headroom(skb) +
+ 16);
+
+ ret = pskb_expand_head(skb, max_t(int, head_delta, 0),
+ 0, GFP_ATOMIC);
+ if (unlikely(ret))
+ goto err_free_rt;
+ }
+
+ if (!stt_can_offload(skb, inner_h_proto, inner_nw_proto)) {
+ struct sk_buff *nskb;
+
+ nskb = handle_offloads(skb);
+ if (IS_ERR(nskb)) {
+ ret = PTR_ERR(nskb);
+ goto err_free_rt;
+ }
+ skb = nskb;
+ }
+
+ while (skb) {
+ struct sk_buff *next_skb = skb->next;
+
+ skb->next = NULL;
+
+ if (next_skb)
+ dst_clone(&rt->dst);
+
+ /* Push STT and TCP header. */
+ skb = stt_build_header(skb, tun_id, src_port, dst_port, src,
+ dst, inner_h_proto, inner_nw_proto,
+ dst_mtu(&rt->dst));
+ if (unlikely(!skb))
+ goto next;
+ /* Push IP header. */
+ ret += iptunnel_xmit(NULL, rt, skb, src, dst, IPPROTO_TCP,
+ tos, ttl, df, false);
+
+next:
+ skb = next_skb;
+ }
+
+ return ret;
+
+err_free_rt:
+ ip_rt_put(rt);
+ kfree_skb(skb);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(stt_xmit_skb);
+
+static struct sk_buff *normalize_frag_list(struct sk_buff *head,
+ struct sk_buff **skbp)
+{
+ struct sk_buff *skb = *skbp;
+ struct sk_buff *last;
+
+ do {
+ struct sk_buff *frags;
+
+ if (skb_shared(skb)) {
+ struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
+
+ if (unlikely(!nskb))
+ return ERR_PTR(-ENOMEM);
+
+ nskb->next = skb->next;
+ consume_skb(skb);
+ skb = nskb;
+ *skbp = skb;
+ }
+
+ if (head) {
+ head->len -= skb->len;
+ head->data_len -= skb->len;
+ head->truesize -= skb->truesize;
+ }
+
+ frags = skb_shinfo(skb)->frag_list;
+ if (frags) {
+ int err;
+
+ err = skb_unclone(skb, GFP_ATOMIC);
+ if (unlikely(err))
+ return ERR_PTR(err);
+
+ last = normalize_frag_list(skb, &frags);
+ if (IS_ERR(last))
+ return last;
+
+ skb_shinfo(skb)->frag_list = NULL;
+ last->next = skb->next;
+ skb->next = frags;
+ } else {
+ last = skb;
+ }
+
+ skbp = &skb->next;
+ } while ((skb = skb->next));
+
+ return last;
+}
+
+/* Takes a linked list of skbs, which potentially contain frag_list
+ * (whose members in turn potentially contain frag_lists, etc.) and
+ * converts them into a single linear linked list.
+ */
+static int straighten_frag_list(struct sk_buff **skbp)
+{
+ struct sk_buff *err_skb;
+
+ err_skb = normalize_frag_list(NULL, skbp);
+ if (IS_ERR(err_skb))
+ return PTR_ERR(err_skb);
+
+ return 0;
+}
+
+static void copy_skb_metadata(struct sk_buff *to, struct sk_buff *from)
+{
+ to->tstamp = from->tstamp;
+ to->priority = from->priority;
+ to->mark = from->mark;
+ to->vlan_tci = from->vlan_tci;
+ skb_copy_secmark(to, from);
+}
+
+static void update_seg_headers(struct sk_buff *skb, bool head,
+ unsigned int l4_offset, unsigned int hdr_len,
+ bool ipv4, u32 tcp_seq)
+{
+ u16 old_len, new_len;
+ __be32 delta;
+ struct tcphdr *tcph;
+ int gso_size;
+
+ if (ipv4) {
+ struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN);
+
+ old_len = ntohs(iph->tot_len);
+ new_len = skb->len - ETH_HLEN;
+ iph->tot_len = htons(new_len);
+
+ ip_send_check(iph);
+ } else {
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(skb->data + ETH_HLEN);
+
+ old_len = ntohs(ip6h->payload_len);
+ new_len = skb->len - ETH_HLEN - sizeof(struct ipv6hdr);
+ ip6h->payload_len = htons(new_len);
+ }
+
+ tcph = (struct tcphdr *)(skb->data + l4_offset);
+ if (!head) {
+ tcph->seq = htonl(tcp_seq);
+ tcph->cwr = 0;
+ }
+
+ if (skb->next) {
+ tcph->fin = 0;
+ tcph->psh = 0;
+ }
+
+ delta = htonl(~old_len + new_len);
+ tcph->check = ~csum_fold((__force __wsum)((__force u32)tcph->check +
+ (__force u32)delta));
+
+ gso_size = skb_shinfo(skb)->gso_size;
+ if (gso_size && skb->len - hdr_len <= gso_size)
+ BUG_ON(clear_gso(skb));
+}
+
+static int __linearize(struct sk_buff *head, bool *ipv4)
+{
+ struct stthdr *stth = stt_hdr(head);
+
+ /* If no offloading is in use then we don't have enough information
+ * to process the headers.
+ */
+ if (!(stth->flags & STT_CSUM_PARTIAL))
+ goto linearize;
+
+ /* Handling UDP packets requires IP fragmentation, which means that
+ * the L4 checksum can no longer be calculated by hardware (since the
+ * fragments are in different packets. If we have to compute the
+ * checksum it's faster just to linearize and large UDP packets are
+ * pretty uncommon anyways, so it's not worth dealing with for now.
+ */
+ if (!(stth->flags & STT_PROTO_TCP))
+ goto linearize;
+
+ if ((stth->flags & STT_PROTO_IPV4)) {
+ struct iphdr *iph = (struct iphdr *)(head->data + ETH_HLEN);
+
+ /* It's difficult to get the IP IDs exactly right here due to
+ * varying segment sizes and potentially multiple layers of
+ * segmentation. IP ID isn't important when DF is set and DF
+ * is generally set for TCP packets, so just linearize if it's
+ * not.
+ */
+ if (!(iph->frag_off & htons(IP_DF)))
+ goto linearize;
+
+ *ipv4 = true;
+ } else {
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(head->data + ETH_HLEN);
+
+ /* Jumbograms require more processing to update and we'll
+ * probably never see them, so just linearize.
+ */
+ if (ip6h->payload_len == 0)
+ goto linearize;
+
+ *ipv4 = false;
+ }
+ return false;
+
+linearize:
+ return true;
+}
+
+static int update_seg(struct sk_buff *head, struct sk_buff *frag,
+ bool ipv4, int l4_offset, int hdr_len, u32 seq)
+{
+ u16 csum_start = head->csum_start - skb_headroom(head);
+
+ if (skb_cloned(frag) || skb_headroom(frag) < hdr_len) {
+ int extra_head = hdr_len - skb_headroom(frag);
+
+ extra_head = extra_head > 0 ? extra_head : 0;
+
+ if (unlikely(pskb_expand_head(frag, extra_head, 0,
+ GFP_ATOMIC)))
+ return -ENOMEM;
+ }
+
+ memcpy(__skb_push(frag, hdr_len), head->data, hdr_len);
+
+ frag->csum_start = skb_headroom(frag) + csum_start;
+ frag->csum_offset = head->csum_offset;
+ frag->ip_summed = head->ip_summed;
+
+ skb_shinfo(frag)->gso_size = skb_shinfo(head)->gso_size;
+ skb_shinfo(frag)->gso_type = skb_shinfo(head)->gso_type;
+ skb_shinfo(frag)->gso_segs = 0;
+
+ copy_skb_metadata(frag, head);
+
+ update_seg_headers(frag, false, l4_offset, hdr_len, ipv4, seq);
+ return 0;
+}
+
+static int __build_segments(struct sk_buff **headp)
+{
+ struct sk_buff *head = *headp;
+ struct sk_buff *nskb = NULL;
+ struct sk_buff *rskb, *skb;
+ struct tcphdr *tcph;
+ int seg_len = 0;
+ int l4_offset;
+ int hdr_len;
+ int tcp_len;
+ bool ipv4;
+ u32 seq;
+
+ /* GRO can produce skbs with only the headers, which we've
+ * already pulled off. We can just dump them.
+ */
+ while (head->len == 0) {
+ nskb = head->next;
+ copy_skb_metadata(nskb, head);
+ consume_skb(head);
+ head = nskb;
+ }
+ *headp = head;
+
+ if (__linearize(head, &ipv4))
+ return skb_list_linearize(head, GFP_ATOMIC);
+
+ l4_offset = stt_hdr(head)->l4_offset;
+ tcph = (struct tcphdr *)(head->data + l4_offset);
+ tcp_len = tcph->doff * 4;
+ hdr_len = l4_offset + tcp_len;
+
+ if (unlikely((tcp_len < sizeof(struct tcphdr)) ||
+ (head->len < hdr_len)))
+ return -EINVAL;
+
+ if (unlikely(!pskb_may_pull(head, hdr_len)))
+ return -ENOMEM;
+
+ seq = ntohl(tcph->seq);
+ rskb = head;
+ for (skb = head->next; ; skb = nskb) {
+ bool headstolen;
+ int delta;
+
+ if (!skb)
+ goto update_seg; /* Update current segment. */
+
+ if (unlikely(skb_unclone(rskb, GFP_ATOMIC)))
+ return -ENOMEM;
+
+ nskb = skb->next;
+ if (!skb_try_coalesce(rskb, skb, &headstolen, &delta))
+ goto update_seg;
+
+ rskb->next = skb->next;
+ kfree_skb_partial(skb, headstolen);
+ continue;
+update_seg:
+ if (rskb == head) {
+ /* update head segment at the end. */
+ seg_len = head->len - hdr_len;
+ } else {
+ int err;
+
+ seq += seg_len;
+ seg_len = rskb->len;
+
+ err = update_seg(head, rskb, ipv4, l4_offset,
+ hdr_len, seq);
+ if (err)
+ return err;
+ }
+ if (!skb)
+ break;
+ rskb->truesize = SKB_TRUESIZE(skb_end_offset(rskb)) +
+ rskb->data_len;
+ rskb = skb;
+ }
+ update_seg_headers(head, true, l4_offset, hdr_len, ipv4, 0);
+ return 0;
+}
+
+static int build_segments(struct sk_buff **headp)
+{
+ int err;
+
+ err = straighten_frag_list(headp);
+ if (unlikely(err))
+ return err;
+
+ if ((*headp)->next) {
+ err = __build_segments(headp);
+ if (unlikely(err))
+ return err;
+ }
+ return 0;
+}
+
+static void free_frag(struct stt_percpu *stt_percpu,
+ struct pkt_frag *frag)
+{
+ stt_percpu->frag_mem_used -= FRAG_CB(frag->skbs)->first.mem_used;
+ kfree_skb_list(frag->skbs);
+ list_del(&frag->lru_node);
+ frag->skbs = NULL;
+}
+
+static void evict_frags(struct stt_percpu *stt_percpu)
+{
+ while (!list_empty(&stt_percpu->frag_lru) &&
+ stt_percpu->frag_mem_used > REASM_LO_THRESH) {
+ struct pkt_frag *frag = list_first_entry(&stt_percpu->frag_lru,
+ struct pkt_frag,
+ lru_node);
+ free_frag(stt_percpu, frag);
+ }
+}
+
+static bool pkt_key_match(struct net *net,
+ const struct pkt_frag *a, const struct pkt_key *b)
+{
+ return a->key.saddr == b->saddr && a->key.daddr == b->daddr &&
+ a->key.pkt_seq == b->pkt_seq && a->key.mark == b->mark &&
+ net_eq(dev_net(a->skbs->dev), net);
+}
+
+static u32 pkt_key_hash(const struct net *net, const struct pkt_key *key)
+{
+ u32 initval = frag_hash_seed ^ (u32)(unsigned long)net ^ key->mark;
+
+ return jhash_3words((__force u32)key->saddr, (__force u32)key->daddr,
+ (__force u32)key->pkt_seq, initval);
+}
+
+static struct pkt_frag *lookup_frag(struct net *net,
+ struct stt_percpu *stt_percpu,
+ const struct pkt_key *key, u32 hash)
+{
+ struct pkt_frag *frag, *victim_frag = NULL;
+ int i;
+
+ for (i = 0; i < FRAG_HASH_SEGS; i++) {
+ frag = flex_array_get(stt_percpu->frag_hash,
+ hash & (FRAG_HASH_ENTRIES - 1));
+
+ if (frag->skbs &&
+ time_before(jiffies, frag->timestamp + FRAG_EXP_TIME) &&
+ pkt_key_match(net, frag, key))
+ return frag;
+
+ if (!victim_frag ||
+ (victim_frag->skbs &&
+ (!frag->skbs ||
+ time_before(frag->timestamp, victim_frag->timestamp))))
+ victim_frag = frag;
+
+ hash >>= FRAG_HASH_SHIFT;
+ }
+
+ if (victim_frag->skbs)
+ free_frag(stt_percpu, victim_frag);
+
+ return victim_frag;
+}
+
+static struct sk_buff *reassemble(struct sk_buff *skb)
+{
+ struct iphdr *iph = ip_hdr(skb);
+ struct tcphdr *tcph = tcp_hdr(skb);
+ u32 seq = ntohl(tcph->seq);
+ int tot_len;
+ struct pkt_key key;
+ struct stt_percpu *stt_percpu;
+ u32 hash;
+ struct pkt_frag *frag;
+ struct sk_buff *last_skb;
+
+ tot_len = seq >> STT_SEQ_LEN_SHIFT;
+ FRAG_CB(skb)->offset = seq & STT_SEQ_OFFSET_MASK;
+
+ if (unlikely(skb->len == 0))
+ goto out_free;
+
+ if (unlikely(FRAG_CB(skb)->offset + skb->len > tot_len))
+ goto out_free;
+
+ if (tot_len == skb->len)
+ goto out;
+
+ key.saddr = iph->saddr;
+ key.daddr = iph->daddr;
+ key.pkt_seq = tcph->ack_seq;
+ key.mark = skb->mark;
+ hash = pkt_key_hash(dev_net(skb->dev), &key);
+
+ stt_percpu = per_cpu_ptr(stt_percpu_data, smp_processor_id());
+
+ spin_lock(&stt_percpu->lock);
+
+ if (unlikely(stt_percpu->frag_mem_used + skb->truesize > REASM_HI_THRESH))
+ evict_frags(stt_percpu);
+
+ frag = lookup_frag(dev_net(skb->dev), stt_percpu, &key, hash);
+ if (!frag->skbs) {
+ frag->skbs = skb;
+ frag->key = key;
+ frag->timestamp = jiffies;
+ FRAG_CB(skb)->first.last_skb = skb;
+ FRAG_CB(skb)->first.mem_used = skb->truesize;
+ FRAG_CB(skb)->first.tot_len = tot_len;
+ FRAG_CB(skb)->first.rcvd_len = skb->len;
+ FRAG_CB(skb)->first.ecn_ce = INET_ECN_is_ce(iph->tos);
+ list_add_tail(&frag->lru_node, &stt_percpu->frag_lru);
+ stt_percpu->frag_mem_used += skb->truesize;
+
+ skb = NULL;
+ goto unlock;
+ }
+
+ /* Optimize for the common case where fragments are received in-order
+ * and not overlapping.
+ */
+ last_skb = FRAG_CB(frag->skbs)->first.last_skb;
+ if (likely(FRAG_CB(last_skb)->offset + last_skb->len ==
+ FRAG_CB(skb)->offset)) {
+ last_skb->next = skb;
+ FRAG_CB(frag->skbs)->first.last_skb = skb;
+ } else {
+ struct sk_buff *prev = NULL, *next;
+
+ for (next = frag->skbs; next; next = next->next) {
+ if (FRAG_CB(next)->offset >= FRAG_CB(skb)->offset)
+ break;
+ prev = next;
+ }
+
+ /* Overlapping fragments aren't allowed. We shouldn't start
+ * before the end of the previous fragment.
+ */
+ if (prev &&
+ FRAG_CB(prev)->offset + prev->len > FRAG_CB(skb)->offset)
+ goto unlock_free;
+
+ /* We also shouldn't end after the beginning of the next
+ * fragment.
+ */
+ if (next &&
+ FRAG_CB(skb)->offset + skb->len > FRAG_CB(next)->offset)
+ goto unlock_free;
+
+ if (prev) {
+ prev->next = skb;
+ } else {
+ FRAG_CB(skb)->first = FRAG_CB(frag->skbs)->first;
+ frag->skbs = skb;
+ }
+
+ if (next)
+ skb->next = next;
+ else
+ FRAG_CB(frag->skbs)->first.last_skb = skb;
+ }
+
+ FRAG_CB(frag->skbs)->first.ecn_ce |= INET_ECN_is_ce(iph->tos);
+ FRAG_CB(frag->skbs)->first.rcvd_len += skb->len;
+ FRAG_CB(frag->skbs)->first.mem_used += skb->truesize;
+ stt_percpu->frag_mem_used += skb->truesize;
+
+ if (FRAG_CB(frag->skbs)->first.tot_len ==
+ FRAG_CB(frag->skbs)->first.rcvd_len) {
+ struct sk_buff *frag_head = frag->skbs;
+
+ frag_head->tstamp = skb->tstamp;
+
+ list_del(&frag->lru_node);
+ stt_percpu->frag_mem_used -= FRAG_CB(frag_head)->first.mem_used;
+ frag->skbs = NULL;
+ skb = frag_head;
+ } else {
+ list_move_tail(&frag->lru_node, &stt_percpu->frag_lru);
+ skb = NULL;
+ }
+
+ goto unlock;
+
+unlock_free:
+ kfree_skb(skb);
+ skb = NULL;
+unlock:
+ spin_unlock(&stt_percpu->lock);
+ return skb;
+out_free:
+ kfree_skb(skb);
+ skb = NULL;
+out:
+ return skb;
+}
+
+static bool validate_checksum(struct sk_buff *skb)
+{
+ struct iphdr *iph = ip_hdr(skb);
+
+ if (skb_csum_unnecessary(skb))
+ return true;
+
+ if (skb->ip_summed == CHECKSUM_COMPLETE &&
+ !tcp_v4_check(skb->len, iph->saddr, iph->daddr, skb->csum))
+ return true;
+
+ skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len,
+ IPPROTO_TCP, 0);
+
+ return __tcp_checksum_complete(skb) == 0;
+}
+
+static bool set_offloads(struct sk_buff *skb)
+{
+ struct stthdr *stth = stt_hdr(skb);
+ u8 proto_type;
+ u16 csum_offset;
+ unsigned short gso_type;
+ int l3_header_size;
+ int l4_header_size;
+
+ skb->vlan_tci = ntohs(stth->vlan_tci);
+
+ if (!(stth->flags & STT_CSUM_PARTIAL)) {
+ if (stth->flags & STT_CSUM_VERIFIED)
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ else
+ skb->ip_summed = CHECKSUM_NONE;
+
+ return clear_gso(skb) == 0;
+ }
+
+ proto_type = stth->flags & STT_PROTO_TYPES;
+
+ if (proto_type == (STT_PROTO_IPV4 | STT_PROTO_TCP)) {
+ /* TCP/IPv4 */
+ csum_offset = offsetof(struct tcphdr, check);
+ gso_type = SKB_GSO_TCPV4;
+ l3_header_size = sizeof(struct iphdr);
+ l4_header_size = sizeof(struct tcphdr);
+ } else if (proto_type == STT_PROTO_TCP) {
+ /* TCP/IPv6 */
+ csum_offset = offsetof(struct tcphdr, check);
+ gso_type = SKB_GSO_TCPV6;
+ l3_header_size = sizeof(struct ipv6hdr);
+ l4_header_size = sizeof(struct tcphdr);
+ } else if (proto_type == STT_PROTO_IPV4) {
+ /* UDP/IPv4 */
+ csum_offset = offsetof(struct udphdr, check);
+ gso_type = SKB_GSO_UDP;
+ l3_header_size = sizeof(struct iphdr);
+ l4_header_size = sizeof(struct udphdr);
+ } else {
+ /* UDP/IPv6 */
+ csum_offset = offsetof(struct udphdr, check);
+ gso_type = SKB_GSO_UDP;
+ l3_header_size = sizeof(struct ipv6hdr);
+ l4_header_size = sizeof(struct udphdr);
+ }
+
+ if (unlikely(stth->l4_offset < ETH_HLEN + l3_header_size))
+ return false;
+
+ if (unlikely(!pskb_may_pull(skb, stth->l4_offset + l4_header_size)))
+ return false;
+ stth = stt_hdr(skb);
+
+ skb->csum_start = skb_headroom(skb) + stth->l4_offset;
+ skb->csum_offset = csum_offset;
+ skb->ip_summed = CHECKSUM_PARTIAL;
+
+ if (stth->mss) {
+ if (unlikely(skb_unclone(skb, GFP_ATOMIC)))
+ return false;
+
+ skb_shinfo(skb)->gso_type = gso_type | SKB_GSO_DODGY;
+ skb_shinfo(skb)->gso_size = ntohs(stth->mss);
+ skb_shinfo(skb)->gso_segs = 0;
+ } else {
+ if (unlikely(clear_gso(skb)))
+ return false;
+ }
+
+ return true;
+}
+
+static void stt_rcv(struct stt_sock *stt_sock, struct sk_buff *skb)
+{
+ int err;
+
+ if (unlikely(!validate_checksum(skb)))
+ goto drop;
+
+ skb = reassemble(skb);
+ if (!skb)
+ return;
+
+ if (unlikely(stt_hdr(skb)->version != 0))
+ goto drop;
+
+ err = iptunnel_pull_header(skb,
+ sizeof(struct stthdr) + STT_ETH_PAD,
+ htons(ETH_P_TEB));
+ if (unlikely(err))
+ goto drop;
+
+ if (unlikely(!set_offloads(skb)))
+ goto drop;
+
+ if (unlikely(build_segments(&skb)))
+ goto drop;
+
+ stt_sock->rcv(stt_sock, skb);
+ return;
+drop:
+ /* Consume bad packet */
+ kfree_skb_list(skb);
+}
+
+static void tcp_sock_release(struct socket *sock)
+{
+ kernel_sock_shutdown(sock, SHUT_RDWR);
+ sk_release_kernel(sock->sk);
+}
+
+static int tcp_sock_create4(struct net *net, __be16 port,
+ struct socket **sockp)
+{
+ int err;
+ struct socket *sock = NULL;
+ struct sockaddr_in tcp_addr;
+
+ err = sock_create_kern(AF_INET, SOCK_STREAM, 0, &sock);
+ if (err < 0)
+ goto error;
+
+ sk_change_net(sock->sk, net);
+
+ memset(&tcp_addr, 0, sizeof(tcp_addr));
+ tcp_addr.sin_family = AF_INET;
+ tcp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ tcp_addr.sin_port = port;
+ err = kernel_bind(sock, (struct sockaddr *)&tcp_addr,
+ sizeof(tcp_addr));
+ if (err < 0)
+ goto error;
+
+ *sockp = sock;
+ return 0;
+
+error:
+ if (sock)
+ tcp_sock_release(sock);
+ *sockp = NULL;
+ return err;
+}
+
+static void schedule_clean_percpu(void)
+{
+ schedule_delayed_work(&clean_percpu_wq, CLEAN_PERCPU_INTERVAL);
+}
+
+static void clean_percpu(struct work_struct *work)
+{
+ int i;
+
+ for_each_possible_cpu(i) {
+ struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+ int j;
+
+ for (j = 0; j < FRAG_HASH_ENTRIES; j++) {
+ struct pkt_frag *frag;
+
+ frag = flex_array_get(stt_percpu->frag_hash, j);
+ if (!frag->skbs ||
+ time_before(jiffies, frag->timestamp + FRAG_EXP_TIME))
+ continue;
+
+ spin_lock_bh(&stt_percpu->lock);
+
+ if (frag->skbs &&
+ time_after(jiffies, frag->timestamp + FRAG_EXP_TIME))
+ free_frag(stt_percpu, frag);
+
+ spin_unlock_bh(&stt_percpu->lock);
+ }
+ }
+ schedule_clean_percpu();
+}
+
+static unsigned int nf_ip_hook(const struct nf_hook_ops *ops,
+ struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct stt_sock *stt_sock;
+ int ip_hdr_len;
+
+ if (ip_hdr(skb)->protocol != IPPROTO_TCP)
+ return NF_ACCEPT;
+
+ ip_hdr_len = ip_hdrlen(skb);
+ if (unlikely(!pskb_may_pull(skb, ip_hdr_len + sizeof(struct tcphdr))))
+ return NF_ACCEPT;
+
+ skb_set_transport_header(skb, ip_hdr_len);
+
+ stt_sock = stt_find_sock(dev_net(skb->dev), tcp_hdr(skb)->dest);
+ if (unlikely(!stt_sock))
+ return NF_ACCEPT;
+
+ if (unlikely(!pskb_pull(skb, ip_hdr_len + tcp_hdrlen(skb)))) {
+ kfree_skb(skb);
+ return NF_STOLEN;
+ }
+
+ stt_rcv(stt_sock, skb);
+ return NF_STOLEN;
+}
+
+static struct nf_hook_ops nf_hook_ops __read_mostly = {
+ .hook = nf_ip_hook,
+ .owner = THIS_MODULE,
+ .pf = NFPROTO_IPV4,
+ .hooknum = NF_INET_LOCAL_IN,
+ .priority = INT_MAX,
+};
+
+static int stt_start(void)
+{
+ int err;
+ int i;
+
+ if (n_tunnels) {
+ n_tunnels++;
+ return 0;
+ }
+
+ get_random_bytes(&frag_hash_seed, sizeof(u32));
+
+ stt_percpu_data = alloc_percpu(struct stt_percpu);
+ if (!stt_percpu_data) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ for_each_possible_cpu(i) {
+ struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+ struct flex_array *frag_hash;
+
+ spin_lock_init(&stt_percpu->lock);
+ INIT_LIST_HEAD(&stt_percpu->frag_lru);
+ get_random_bytes(&per_cpu(pkt_seq_counter, i), sizeof(u32));
+
+ frag_hash = flex_array_alloc(sizeof(struct pkt_frag),
+ FRAG_HASH_ENTRIES,
+ GFP_KERNEL | __GFP_ZERO);
+ if (!frag_hash) {
+ err = -ENOMEM;
+ goto free_percpu;
+ }
+ stt_percpu->frag_hash = frag_hash;
+
+ err = flex_array_prealloc(stt_percpu->frag_hash, 0,
+ FRAG_HASH_ENTRIES,
+ GFP_KERNEL | __GFP_ZERO);
+ if (err)
+ goto free_percpu;
+ }
+ err = nf_register_hook(&nf_hook_ops);
+ if (err)
+ goto free_percpu;
+
+ schedule_clean_percpu();
+ n_tunnels++;
+ return 0;
+
+free_percpu:
+ for_each_possible_cpu(i) {
+ struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+
+ if (stt_percpu->frag_hash)
+ flex_array_free(stt_percpu->frag_hash);
+ }
+
+ free_percpu(stt_percpu_data);
+
+error:
+ return err;
+}
+
+static void stt_cleanup(void)
+{
+ int i;
+
+ n_tunnels--;
+ if (n_tunnels)
+ return;
+
+ cancel_delayed_work_sync(&clean_percpu_wq);
+ nf_unregister_hook(&nf_hook_ops);
+
+ for_each_possible_cpu(i) {
+ struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+ int j;
+
+ for (j = 0; j < FRAG_HASH_ENTRIES; j++) {
+ struct pkt_frag *frag;
+
+ frag = flex_array_get(stt_percpu->frag_hash, j);
+ kfree_skb_list(frag->skbs);
+ }
+
+ flex_array_free(stt_percpu->frag_hash);
+ }
+
+ free_percpu(stt_percpu_data);
+}
+
+static struct stt_sock *stt_socket_create(struct net *net, __be16 port,
+ stt_rcv_t *rcv, void *data)
+{
+ struct stt_net *sn = net_generic(net, stt_net_id);
+ struct stt_sock *stt_sock;
+ struct socket *sock;
+ int err;
+
+ stt_sock = kzalloc(sizeof(*stt_sock), GFP_KERNEL);
+ if (!stt_sock)
+ return ERR_PTR(-ENOMEM);
+
+ err = tcp_sock_create4(net, port, &sock);
+ if (err) {
+ kfree(stt_sock);
+ return ERR_PTR(err);
+ }
+
+ stt_sock->sock = sock;
+ stt_sock->rcv = rcv;
+ stt_sock->rcv_data = data;
+
+ list_add_rcu(&stt_sock->list, &sn->sock_list);
+
+ return stt_sock;
+}
+
+static void __stt_sock_release(struct stt_sock *stt_sock)
+{
+ list_del_rcu(&stt_sock->list);
+ tcp_sock_release(stt_sock->sock);
+ kfree_rcu(stt_sock, rcu);
+}
+
+struct stt_sock *stt_sock_add(struct net *net, __be16 port,
+ stt_rcv_t *rcv, void *data)
+{
+ struct stt_sock *stt_sock;
+
+ mutex_lock(&stt_mutex);
+ rcu_read_lock();
+ stt_sock = stt_find_sock(net, port);
+ rcu_read_unlock();
+ if (stt_sock) {
+ stt_sock = ERR_PTR(-EBUSY);
+ } else {
+ stt_sock = stt_socket_create(net, port, rcv, data);
+ if (!IS_ERR(stt_sock)) {
+ int err;
+
+ err = stt_start();
+ if (err) {
+ __stt_sock_release(stt_sock);
+ stt_sock = ERR_PTR(err);
+ }
+ }
+ }
+ mutex_unlock(&stt_mutex);
+
+ return stt_sock;
+}
+EXPORT_SYMBOL_GPL(stt_sock_add);
+
+void stt_sock_release(struct stt_sock *stt_sock)
+{
+ mutex_lock(&stt_mutex);
+ __stt_sock_release(stt_sock);
+ stt_cleanup();
+ mutex_unlock(&stt_mutex);
+}
+EXPORT_SYMBOL_GPL(stt_sock_release);
+
+static __net_init int stt_init_net(struct net *net)
+{
+ struct stt_net *sn = net_generic(net, stt_net_id);
+
+ INIT_LIST_HEAD(&sn->sock_list);
+ return 0;
+}
+
+static struct pernet_operations stt_net_ops = {
+ .init = stt_init_net,
+ .id = &stt_net_id,
+ .size = sizeof(struct stt_net),
+};
+
+static int __init stt_init_module(void)
+{
+ return register_pernet_subsys(&stt_net_ops);
+}
+module_init(stt_init_module);
+
+static void __exit stt_cleanup_module(void)
+{
+ unregister_pernet_subsys(&stt_net_ops);
+}
+module_exit(stt_cleanup_module);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for STT encapsulated traffic");
+MODULE_ALIAS_RTNL_LINK("stt");
--
1.9.1
^ permalink raw reply related
* [PATCH net-next 3/3] openvswitch: Add support for STT tunneling.
From: Pravin B Shelar @ 2015-01-20 20:25 UTC (permalink / raw)
To: davem; +Cc: netdev, Pravin B Shelar, Jesse Gross
Use STT driver to create STT tunnel port.
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: Jesse Gross <jesse@nicira.com>
---
include/uapi/linux/openvswitch.h | 1 +
net/openvswitch/Kconfig | 10 ++
net/openvswitch/Makefile | 1 +
net/openvswitch/vport-stt.c | 214 +++++++++++++++++++++++++++++++++++++++
4 files changed, 226 insertions(+)
create mode 100644 net/openvswitch/vport-stt.c
diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index cd8d933..a7396a7 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -204,6 +204,7 @@ enum ovs_vport_type {
OVS_VPORT_TYPE_GRE, /* GRE tunnel. */
OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel. */
OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */
+ OVS_VPORT_TYPE_STT, /* STT tunnel. */
__OVS_VPORT_TYPE_MAX
};
diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig
index b7d818c..65b5fb7 100644
--- a/net/openvswitch/Kconfig
+++ b/net/openvswitch/Kconfig
@@ -64,3 +64,13 @@ config OPENVSWITCH_GENEVE
If you say Y here, then the Open vSwitch will be able create geneve vport.
Say N to exclude this support and reduce the binary size.
+
+config OPENVSWITCH_STT
+ tristate "Open vSwitch STT tunneling support"
+ depends on OPENVSWITCH
+ depends on STT
+ default OPENVSWITCH
+ ---help---
+ If you say Y here, then the Open vSwitch will be able create stt vport.
+
+ Say N to exclude this support and reduce the binary size.
diff --git a/net/openvswitch/Makefile b/net/openvswitch/Makefile
index 91b9478..352bd05 100644
--- a/net/openvswitch/Makefile
+++ b/net/openvswitch/Makefile
@@ -18,3 +18,4 @@ openvswitch-y := \
obj-$(CONFIG_OPENVSWITCH_GENEVE)+= vport-geneve.o
obj-$(CONFIG_OPENVSWITCH_VXLAN) += vport-vxlan.o
obj-$(CONFIG_OPENVSWITCH_GRE) += vport-gre.o
+obj-$(CONFIG_OPENVSWITCH_STT) += vport-stt.o
diff --git a/net/openvswitch/vport-stt.c b/net/openvswitch/vport-stt.c
new file mode 100644
index 0000000..f614b3d
--- /dev/null
+++ b/net/openvswitch/vport-stt.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2014 Nicira, Inc.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/if_vlan.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/rculist.h>
+#include <linux/udp.h>
+
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/stt.h>
+#include <net/udp.h>
+
+#include "datapath.h"
+#include "vport.h"
+
+static struct vport_ops ovs_stt_vport_ops;
+
+/**
+ * struct stt_port - Keeps track of open UDP ports
+ * @stt_sock: The socket created for this port number.
+ * @name: vport name.
+ */
+struct stt_port {
+ struct stt_sock *stt_sock;
+ char name[IFNAMSIZ];
+};
+
+static inline struct stt_port *stt_vport(const struct vport *vport)
+{
+ return vport_priv(vport);
+}
+
+static void stt_rcv(struct stt_sock *stt_sock, struct sk_buff *skb)
+{
+ struct vport *vport = stt_sock->rcv_data;
+ struct stthdr *stth = stt_hdr(skb);
+ struct ovs_tunnel_info tun_info;
+ struct sk_buff *next;
+
+ ovs_flow_tun_info_init(&tun_info, ip_hdr(skb),
+ tcp_hdr(skb)->source, tcp_hdr(skb)->dest,
+ get_unaligned(&stth->key),
+ TUNNEL_KEY | TUNNEL_CSUM,
+ NULL, 0);
+ do {
+ next = skb->next;
+ skb->next = NULL;
+ ovs_vport_receive(vport, skb, &tun_info);
+ } while ((skb = next));
+}
+
+static int stt_tnl_get_options(const struct vport *vport,
+ struct sk_buff *skb)
+{
+ struct stt_port *stt_port = stt_vport(vport);
+ struct inet_sock *sk = inet_sk(stt_port->stt_sock->sock->sk);
+
+ if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(sk->inet_sport)))
+ return -EMSGSIZE;
+ return 0;
+}
+
+static void stt_tnl_destroy(struct vport *vport)
+{
+ struct stt_port *stt_port = stt_vport(vport);
+
+ stt_sock_release(stt_port->stt_sock);
+ ovs_vport_deferred_free(vport);
+}
+
+static struct vport *stt_tnl_create(const struct vport_parms *parms)
+{
+ struct net *net = ovs_dp_get_net(parms->dp);
+ struct nlattr *options = parms->options;
+ struct stt_port *stt_port;
+ struct stt_sock *stt_sock;
+ struct vport *vport;
+ struct nlattr *a;
+ int err;
+ u16 dst_port;
+
+ if (!options) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT);
+ if (a && nla_len(a) == sizeof(u16)) {
+ dst_port = nla_get_u16(a);
+ } else {
+ /* Require destination port from userspace. */
+ err = -EINVAL;
+ goto error;
+ }
+
+ vport = ovs_vport_alloc(sizeof(struct stt_port),
+ &ovs_stt_vport_ops, parms);
+ if (IS_ERR(vport))
+ return vport;
+
+ stt_port = stt_vport(vport);
+ strncpy(stt_port->name, parms->name, IFNAMSIZ);
+
+ stt_sock = stt_sock_add(net, htons(dst_port), stt_rcv, vport);
+ if (IS_ERR(stt_sock)) {
+ ovs_vport_free(vport);
+ return (void *)stt_sock;
+ }
+ stt_port->stt_sock = stt_sock;
+
+ return vport;
+error:
+ return ERR_PTR(err);
+}
+
+static int stt_tnl_send(struct vport *vport, struct sk_buff *skb)
+{
+ struct net *net = ovs_dp_get_net(vport->dp);
+ struct stt_port *stt_port = stt_vport(vport);
+ __be16 dport = inet_sk(stt_port->stt_sock->sock->sk)->inet_sport;
+ const struct ovs_key_ipv4_tunnel *tun_key;
+ const struct ovs_tunnel_info *tun_info;
+ struct rtable *rt;
+ struct flowi4 fl;
+ __be16 sport;
+ __be16 df;
+ int err;
+
+ tun_info = OVS_CB(skb)->egress_tun_info;
+ if (unlikely(!tun_info)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ tun_key = &tun_info->tunnel;
+ rt = ovs_tunnel_route_lookup(net, tun_key, skb->mark, &fl, IPPROTO_TCP);
+ if (IS_ERR(rt)) {
+ err = PTR_ERR(rt);
+ goto error;
+ }
+
+ df = tun_key->tun_flags & TUNNEL_DONT_FRAGMENT ? htons(IP_DF) : 0;
+ sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+ skb->ignore_df = 1;
+
+ return stt_xmit_skb(skb, rt, fl.saddr, tun_key->ipv4_dst,
+ tun_key->ipv4_tos, tun_key->ipv4_ttl,
+ df, sport, dport, tun_key->tun_id);
+error:
+ kfree_skb(skb);
+ return err;
+}
+
+static const char *stt_tnl_get_name(const struct vport *vport)
+{
+ return stt_vport(vport)->name;
+}
+
+static int stt_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+ struct ovs_tunnel_info *egress_tun_info)
+{
+ struct stt_port *stt_port = stt_vport(vport);
+ struct net *net = ovs_dp_get_net(vport->dp);
+ __be16 dport = inet_sk(stt_port->stt_sock->sock->sk)->inet_sport;
+ __be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+
+ /* Get tp_src and tp_dst, refert to stt_build_header().
+ */
+ return ovs_tunnel_get_egress_info(egress_tun_info,
+ ovs_dp_get_net(vport->dp),
+ OVS_CB(skb)->egress_tun_info,
+ IPPROTO_UDP, skb->mark, sport, dport);
+}
+
+static struct vport_ops ovs_stt_vport_ops = {
+ .type = OVS_VPORT_TYPE_STT,
+ .create = stt_tnl_create,
+ .destroy = stt_tnl_destroy,
+ .get_name = stt_tnl_get_name,
+ .get_options = stt_tnl_get_options,
+ .send = stt_tnl_send,
+ .owner = THIS_MODULE,
+ .get_egress_tun_info = stt_get_egress_tun_info,
+};
+
+static int __init ovs_stt_tnl_init(void)
+{
+ return ovs_vport_ops_register(&ovs_stt_vport_ops);
+}
+
+static void __exit ovs_stt_tnl_exit(void)
+{
+ ovs_vport_ops_unregister(&ovs_stt_vport_ops);
+}
+
+module_init(ovs_stt_tnl_init);
+module_exit(ovs_stt_tnl_exit);
+
+MODULE_DESCRIPTION("OVS: STT swiching port");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("vport-type-6");
--
1.9.1
^ permalink raw reply related
* Re: [net-next PATCH v3 04/12] net: flow_table: create a set of common headers and actions
From: John W. Linville @ 2015-01-20 20:59 UTC (permalink / raw)
To: John Fastabend
Cc: tgraf, simon.horman, sfeldma, netdev, jhs, davem, gerlitz.or,
andy, ast
In-Reply-To: <20150120202752.1741.13810.stgit@nitbit.x32>
On Tue, Jan 20, 2015 at 12:27:53PM -0800, John Fastabend wrote:
> This adds common headers and actions that drivers can use.
>
> I have not yet moved the header graphs into the common header
> because I'm not entirely convinced its re-usable. The devices
> I have been looking at have different enough header graphs that
> they wouldn't be re-usable. However possibly many 40Gbp NICs
> for example could share a common header graph. When we get
> multiple implementations we can move this into the common file
> if it makes sense.
>
> And table structures seem to be unique enough that there is
> little value in putting each devices table layout into the
> common file so its left for device specific implementation.
>
> Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
> ---
> include/linux/if_flow_common.h | 257 ++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 257 insertions(+)
> create mode 100644 include/linux/if_flow_common.h
>
> diff --git a/include/linux/if_flow_common.h b/include/linux/if_flow_common.h
> new file mode 100644
> index 0000000..ef2d66f
> --- /dev/null
> +++ b/include/linux/if_flow_common.h
<snip>
> +struct net_flow_action net_flow_pop_vlan = {
> + .name = "pop_vlan",
> + .uid = ACTION_POP_VLAN,
> + .args = net_flow_null_args,
> +};
Random thought, should there be a "push_vlan" (for double VLAN tagging)?
--
John W. Linville Someday the world will need a hero, and you
linville@tuxdriver.com might be all we have. Be ready.
^ permalink raw reply
* RE: [PATCH net-next 1/3] skbuff: Add skb_list_linearize()
From: Harout Hedeshian @ 2015-01-20 21:16 UTC (permalink / raw)
To: 'Pravin B Shelar', davem; +Cc: netdev
In-Reply-To: <1421785543-19823-1-git-send-email-pshelar@nicira.com>
> -----Original Message-----
> From: netdev-owner@vger.kernel.org [mailto:netdev-owner@vger.kernel.org]
> On Behalf Of Pravin B Shelar
> Sent: Tuesday, January 20, 2015 1:26 PM
> To: davem@davemloft.net
> Cc: netdev@vger.kernel.org; Pravin B Shelar
> Subject: [PATCH net-next 1/3] skbuff: Add skb_list_linearize()
>
> similar to skb_linearize(), this API takes skb list as arg and linearize
> it into one big skb. STT driver patch will use this.
>
> Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
> ---
> include/linux/skbuff.h | 2 ++
> net/core/skbuff.c | 35 +++++++++++++++++++++++++++++++++++
> 2 files changed, 37 insertions(+)
>
> diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index
> 85ab7d7..c9194c1 100644
> --- a/include/linux/skbuff.h
> +++ b/include/linux/skbuff.h
> @@ -2532,6 +2532,8 @@ static inline int skb_linearize(struct sk_buff
> *skb)
> return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0; }
>
> +int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask);
> +
> /**
> * skb_has_shared_frag - can any frag be overwritten
> * @skb: buffer to test
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c index
> 56db472..9ef2881 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -2329,6 +2329,41 @@ void skb_copy_and_csum_dev(const struct sk_buff
> *skb, u8 *to) } EXPORT_SYMBOL(skb_copy_and_csum_dev);
>
> +int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask) {
> + struct sk_buff *skb;
> + int tlen = 0;
> + int err;
> +
> + err = skb_linearize(head);
> + if (err)
> + return err;
> +
> + skb = head->next;
> + while (skb) {
> + tlen += skb->len;
> + skb = skb->next;
> + }
> + err = pskb_expand_head(head, 0, tlen, gfp_mask);
> + if (err)
> + return err;
> +
> + skb = head->next;
> + while (skb) {
> + err = skb_copy_bits(skb, 0, skb_tail_pointer(head),
skb->len);
> + if (err)
> + return err;
> + head->tail += skb->len;
> + skb = skb->next;
> + }
> + kfree_skb_list(head->next);
> + head->next = NULL;
> + head->len += tlen;
> + head->truesize = SKB_TRUESIZE(skb_end_offset(head));
> + return 0;
> +}
> +EXPORT_SYMBOL(skb_list_linearize);
> +
> /**
> * skb_dequeue - remove from the head of the queue
> * @list: list to dequeue from
> --
> 1.9.1
>
This looks useful for us as well.
Acked-by: Harout Hedeshian <harouth@codeaurora.org>
Thanks,
Harout
--
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux
Foundation Collaborative Project
^ permalink raw reply
* Re: [PATCH] net: ipv4: Fix incorrect free in ICMP receive
From: Eric Dumazet @ 2015-01-20 21:35 UTC (permalink / raw)
To: Cong Wang; +Cc: subashab, David Miller, netdev
In-Reply-To: <CAHA+R7N-j5-iizM2pfjtAExn4Y3rq=UWGn1xyw8Z5OFO9u95ag@mail.gmail.com>
On Tue, 2015-01-20 at 11:42 -0800, Cong Wang wrote:
>
> Or let the socket layer drop the packet instead of unconditionally
> dropping all icmp packets after success?
Sure, but that's probably at least 50 lines patch.
net-next candidate most likely.
^ permalink raw reply
* Re: [PATCH net-next 1/3] skbuff: Add skb_list_linearize()
From: Eric Dumazet @ 2015-01-20 21:43 UTC (permalink / raw)
To: Pravin B Shelar; +Cc: davem, netdev
In-Reply-To: <1421785543-19823-1-git-send-email-pshelar@nicira.com>
On Tue, 2015-01-20 at 12:25 -0800, Pravin B Shelar wrote:
> similar to skb_linearize(), this API takes skb list as arg and
> linearize it into one big skb. STT driver patch will use this.
>
> Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
> ---
> include/linux/skbuff.h | 2 ++
> net/core/skbuff.c | 35 +++++++++++++++++++++++++++++++++++
> 2 files changed, 37 insertions(+)
>
> diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
> index 85ab7d7..c9194c1 100644
> --- a/include/linux/skbuff.h
> +++ b/include/linux/skbuff.h
> @@ -2532,6 +2532,8 @@ static inline int skb_linearize(struct sk_buff *skb)
> return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
> }
>
> +int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask);
> +
> /**
> * skb_has_shared_frag - can any frag be overwritten
> * @skb: buffer to test
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index 56db472..9ef2881 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -2329,6 +2329,41 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
> }
> EXPORT_SYMBOL(skb_copy_and_csum_dev);
>
> +int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask)
> +{
> + struct sk_buff *skb;
> + int tlen = 0;
> + int err;
> +
> + err = skb_linearize(head);
> + if (err)
> + return err;
> +
> + skb = head->next;
> + while (skb) {
> + tlen += skb->len;
> + skb = skb->next;
> + }
> + err = pskb_expand_head(head, 0, tlen, gfp_mask);
> + if (err)
> + return err;
> +
> + skb = head->next;
> + while (skb) {
> + err = skb_copy_bits(skb, 0, skb_tail_pointer(head), skb->len);
> + if (err)
> + return err;
> + head->tail += skb->len;
> + skb = skb->next;
> + }
> + kfree_skb_list(head->next);
> + head->next = NULL;
> + head->len += tlen;
> + head->truesize = SKB_TRUESIZE(skb_end_offset(head));
This is buggy : skb can have a destructor, like sock_wfree()
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox