aboutsummaryrefslogtreecommitdiffstats
path: root/extras/packetforge/packetforge.py
diff options
context:
space:
mode:
authorTing Xu <ting.xu@intel.com>2022-04-24 06:14:25 +0000
committerDave Wallace <dwallacelf@gmail.com>2022-09-20 20:44:42 +0000
commitce4b6451787389c5b0ebfac413c350ef3a424b8b (patch)
treeaa777368e14fca9b6613f747817331336ca2b11b /extras/packetforge/packetforge.py
parentf5e0a17c9cca09822296a0ed3196fde36c1ca5f8 (diff)
packetforge: add packetforge for generic flow to extras
Add a new tool packetforge to extras. This tool is to support generic flow. Packetforge is a library to translate naming or json profile format flow pattern to the required input of generic flow, i.e. spec and mask. Using python script flow_create.py, it can add and enable a new flow rule for an interface via flow VAPI, and can delete an existed flow rule as well. Command examples are shown below. Json profile examples can be found in ./parsegraph/samples. Naming format input: python flow_create.py --add -p "mac()/ipv4(src=1.1.1.1,dst=2.2.2.2)/udp()" -a "redirect-to-queue 3" -i 1 python flow_create.py --del -i 1 -I 0 Json profile format input: python flow_create.py -f "./flow_rule_examples/mac_ipv4.json" -i 1 With this command, flow rule can be added or deleted, and the flow entry can be listed with "show flow entry" command in VPP CLI. Packetforge is based on a parsegraph. The parsegraph can be built by users. A Spec can be found in ./parsegraph as guidance. More details about packetforge are in README file. Type: feature Signed-off-by: Ting Xu <ting.xu@intel.com> Change-Id: Ia9f539741c5dca27ff236f2bcc493c5dd48c0df1
Diffstat (limited to 'extras/packetforge/packetforge.py')
-rw-r--r--extras/packetforge/packetforge.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/extras/packetforge/packetforge.py b/extras/packetforge/packetforge.py
new file mode 100644
index 00000000000..6a419bc7c4f
--- /dev/null
+++ b/extras/packetforge/packetforge.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2022 Intel and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from vpp_papi.vpp_papi import VppEnum
+from ParseGraph import *
+from Path import *
+import json
+import re
+import os
+
+parsegraph_path = os.getcwd() + "/parsegraph"
+
+
+def Forge(pattern, actions, file_flag):
+ pg = ParseGraph.Create(parsegraph_path)
+ if pg == None:
+ print("error: create parsegraph failed")
+ return None
+
+ if not file_flag:
+ token = ParsePattern(pattern)
+ if token == None:
+ return None
+ else:
+ if not os.path.exists(pattern):
+ print("error: file not exist '%s' " % (pattern))
+ return
+ f = open(pattern, "r", encoding="utf-8")
+ token = json.load(f)
+ if "actions" in token:
+ actions = token["actions"]
+
+ path = Path.Create(token)
+ if path == None:
+ print("error: path not exit")
+ return None
+
+ result = pg.Forge(path)
+ if result == None:
+ print("error: result not available")
+ return None
+
+ spec, mask = GetBinary(result.ToJSON())
+
+ # create generic flow
+ my_flow = {
+ "type": VppEnum.vl_api_flow_type_v2_t.FLOW_TYPE_GENERIC_V2,
+ "flow": {
+ "generic": {
+ "pattern": {"spec": bytes(spec.encode()), "mask": bytes(mask.encode())}
+ }
+ },
+ }
+
+ # update actions entry
+ my_flow = GetAction(actions, my_flow)
+
+ return my_flow
+
+
+def GetAction(actions, flow):
+ if len(actions.split(" ")) > 1:
+ type = actions.split(" ")[0]
+ else:
+ type = actions
+
+ if type == "mark":
+ flow.update(
+ {
+ "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_MARK_V2,
+ "mark_flow_id": int(actions.split(" ")[1]),
+ }
+ )
+ elif type == "next-node":
+ flow.update(
+ {
+ "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_NODE_V2,
+ "redirect_node_index": int(actions.split(" ")[1]),
+ }
+ )
+ elif type == "buffer-advance":
+ flow.update(
+ {
+ "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_BUFFER_ADVANCE_V2,
+ "buffer_advance": int(actions.split(" ")[1]),
+ }
+ )
+ elif type == "redirect-to-queue":
+ flow.update(
+ {
+ "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_QUEUE_V2,
+ "redirect_queue": int(actions.split(" ")[1]),
+ }
+ )
+ elif type == "rss":
+ flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2})
+ elif type == "rss-queues":
+ queue_end = int(actions.split(" ")[-1])
+ queue_start = int(actions.split(" ")[-3])
+ flow.update(
+ {
+ "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2,
+ "queue_index": queue_start,
+ "queue_num": queue_end - queue_start + 1,
+ }
+ )
+ elif type == "drop":
+ flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_DROP_V2})
+
+ return flow
+
+
+def GetBinary(flow_info):
+ spec = "".join(flow_info["Packet"])
+ mask = "".join(flow_info["Mask"])
+ return spec, mask
+
+
+def ParseFields(item):
+ # get protocol name
+ prot = item.split("(")[0]
+ stack = {"header": prot}
+ # get fields contents
+ fields = re.findall(r"[(](.*?)[)]", item)
+ if not fields:
+ print("error: invalid pattern")
+ return None
+ if fields == [""]:
+ return stack
+ stack.update({"fields": []})
+ return ParseStack(stack, fields[0].split(","))
+
+
+def GetMask(item):
+ if "format" in item:
+ format = item["format"]
+ if format == "mac":
+ mask = "ff.ff.ff.ff.ff.ff"
+ elif format == "ipv4":
+ mask = "255.255.255.255"
+ elif format == "ipv6":
+ mask = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ return mask
+ if "size" in item:
+ mask = str((1 << int(item["size"])) - 1)
+ else:
+ print("mask error")
+ return mask
+
+
+# parse protocol headers and its fields. Available fields are defined in corresponding nodes.
+def ParseStack(stack, fields):
+ prot = stack["header"]
+ node_path = parsegraph_path + "/nodes/" + prot + ".json"
+ if not os.path.exists(node_path):
+ print("error file not exist '%s' " % (node_path))
+ return None
+ f = open(node_path, "r", encoding="utf-8")
+ nodeinfo = json.load(f)
+ for field in fields:
+ fld_name = field.split("=")[0].strip()
+ fld_value = (
+ field.split("=")[-1].strip() if (len(field.split("=")) >= 2) else None
+ )
+ for item in nodeinfo["layout"]:
+ if fld_name == item["name"]:
+ mask = GetMask(item)
+ stack["fields"].append(
+ {"name": fld_name, "value": fld_value, "mask": mask}
+ )
+ break
+ if not stack["fields"]:
+ print("warning: invalid field '%s'" % (fld_name))
+ return None
+
+ return stack
+
+
+def ParsePattern(pattern):
+ # create json template
+ json_tmp = {"type": "path", "stack": []}
+
+ items = pattern.split("/")
+ for item in items:
+ stack = ParseFields(item)
+ if stack == None:
+ return None
+ json_tmp["stack"].append(stack)
+
+ return json_tmp