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
This commit is contained in:
200
extras/packetforge/packetforge.py
Normal file
200
extras/packetforge/packetforge.py
Normal file
@ -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
|
Reference in New Issue
Block a user