aboutsummaryrefslogtreecommitdiffstats
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
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
-rw-r--r--MAINTAINERS5
-rw-r--r--extras/packetforge/Edge.py92
-rw-r--r--extras/packetforge/EdgeAction.py55
-rw-r--r--extras/packetforge/ExpressionConverter.py162
-rw-r--r--extras/packetforge/ForgeResult.py45
-rw-r--r--extras/packetforge/InputFormat.py12
-rw-r--r--extras/packetforge/LICENSE.txt202
-rw-r--r--extras/packetforge/Node.py62
-rw-r--r--extras/packetforge/NodeAttribute.py65
-rw-r--r--extras/packetforge/NodeField.py86
-rw-r--r--extras/packetforge/ParseGraph.py179
-rw-r--r--extras/packetforge/Path.py35
-rw-r--r--extras/packetforge/PathNode.py39
-rw-r--r--extras/packetforge/PathNodeField.py37
-rw-r--r--extras/packetforge/ProtocolHeader.py387
-rw-r--r--extras/packetforge/ProtocolHeaderAttribute.py29
-rw-r--r--extras/packetforge/ProtocolHeaderField.py48
-rw-r--r--extras/packetforge/README.rst71
-rw-r--r--extras/packetforge/StringFormat.py11
-rw-r--r--extras/packetforge/flow_create.py157
-rw-r--r--extras/packetforge/packetforge.py200
-rw-r--r--extras/packetforge/parsegraph/.gitattributes2
-rw-r--r--extras/packetforge/parsegraph/edges/ah_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ah_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json11
-rw-r--r--extras/packetforge/parsegraph/edges/esp_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/esp_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/gre_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/gre_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/gtpc_after_udp.json11
-rw-r--r--extras/packetforge/parsegraph/edges/gtppsc_after_gtpu.json19
-rw-r--r--extras/packetforge/parsegraph/edges/gtpu_after_udp.json11
-rw-r--r--extras/packetforge/parsegraph/edges/icmp_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/icmpv6_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ip_after_gtppsc.json5
-rw-r--r--extras/packetforge/parsegraph/edges/ip_after_gtpu.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_geneve.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_gre.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_macvlan.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv4_after_vxlangpe.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_geneve.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_gre.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_macvlan.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6_after_vxlangpe.json11
-rw-r--r--extras/packetforge/parsegraph/edges/ipv6srh_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/l2tpv2_after_udp.json11
-rw-r--r--extras/packetforge/parsegraph/edges/mac_after_geneve.json11
-rw-r--r--extras/packetforge/parsegraph/edges/mac_after_gre.json5
-rw-r--r--extras/packetforge/parsegraph/edges/mac_after_nvgre.json5
-rw-r--r--extras/packetforge/parsegraph/edges/mac_after_vxlan.json5
-rw-r--r--extras/packetforge/parsegraph/edges/mac_after_vxlangpe.json11
-rw-r--r--extras/packetforge/parsegraph/edges/pfcp_after_udp.json11
-rw-r--r--extras/packetforge/parsegraph/edges/sctp_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/sctp_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/tcp_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/tcp_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/tunnel_after_udp.json11
-rw-r--r--extras/packetforge/parsegraph/edges/udp_after_ipv4.json11
-rw-r--r--extras/packetforge/parsegraph/edges/udp_after_ipv6.json11
-rw-r--r--extras/packetforge/parsegraph/edges/vlan_after_macvlan.json15
-rw-r--r--extras/packetforge/parsegraph/nodes/ah.json26
-rw-r--r--extras/packetforge/parsegraph/nodes/arpv4.json54
-rw-r--r--extras/packetforge/parsegraph/nodes/esp.json14
-rw-r--r--extras/packetforge/parsegraph/nodes/geneve.json51
-rw-r--r--extras/packetforge/parsegraph/nodes/gre.json56
-rw-r--r--extras/packetforge/parsegraph/nodes/gtpc.json48
-rw-r--r--extras/packetforge/parsegraph/nodes/gtppsc.json28
-rw-r--r--extras/packetforge/parsegraph/nodes/gtpu.json63
-rw-r--r--extras/packetforge/parsegraph/nodes/icmp.json18
-rw-r--r--extras/packetforge/parsegraph/nodes/icmpv6.json18
-rw-r--r--extras/packetforge/parsegraph/nodes/ipv4.json76
-rw-r--r--extras/packetforge/parsegraph/nodes/ipv6.json47
-rw-r--r--extras/packetforge/parsegraph/nodes/ipv6crh16.json36
-rw-r--r--extras/packetforge/parsegraph/nodes/ipv6crh32.json32
-rw-r--r--extras/packetforge/parsegraph/nodes/ipv6srh.json40
-rw-r--r--extras/packetforge/parsegraph/nodes/l2tpv2ctl.json74
-rw-r--r--extras/packetforge/parsegraph/nodes/l2tpv2data.json79
-rw-r--r--extras/packetforge/parsegraph/nodes/mac.json22
-rw-r--r--extras/packetforge/parsegraph/nodes/nvgre.json52
-rw-r--r--extras/packetforge/parsegraph/nodes/payload.json18
-rw-r--r--extras/packetforge/parsegraph/nodes/pfcp.json58
-rw-r--r--extras/packetforge/parsegraph/nodes/sctp.json22
-rw-r--r--extras/packetforge/parsegraph/nodes/tcp.json79
-rw-r--r--extras/packetforge/parsegraph/nodes/udp.json24
-rw-r--r--extras/packetforge/parsegraph/nodes/vlan.json30
-rw-r--r--extras/packetforge/parsegraph/nodes/vxlan.json33
-rw-r--r--extras/packetforge/parsegraph/nodes/vxlangpe.json53
-rw-r--r--extras/packetforge/parsegraph/samples/mac_ipv4.json24
-rw-r--r--extras/packetforge/parsegraph/samples/mac_ipv4_udp.json27
-rw-r--r--extras/packetforge/parsegraph/samples/mac_ipv4_udp_gtpu_gtppsc_ipv4.json43
-rw-r--r--extras/packetforge/parsegraph/samples/mac_ipv4_udp_vxlan_mac_ipv4.json43
-rw-r--r--extras/packetforge/parsegraph/samples/mac_ipv6.json44
-rw-r--r--extras/packetforge/parsegraph/samples/mac_vlan_ipv4.json34
-rw-r--r--extras/packetforge/parsegraph/spec.md533
98 files changed, 4330 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index c8cf02f4d79..4bc34b032e8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -739,6 +739,11 @@ M: Artem Glazychev <artem.glazychev@xored.com>
M: Fan Zhang <roy.fan.zhang@intel.com>
F: src/plugins/wireguard
+Packetforge
+I: packetforge
+M: Ting Xu <ting.xu@intel.com>
+F: extras/packetforge
+
VPP Config Tooling
I: vpp_config
M: John DeNisco <jdenisco@cisco.com>
diff --git a/extras/packetforge/Edge.py b/extras/packetforge/Edge.py
new file mode 100644
index 00000000000..08e6c9d9ad3
--- /dev/null
+++ b/extras/packetforge/Edge.py
@@ -0,0 +1,92 @@
+# 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 EdgeAction import *
+import json
+
+
+class Edge:
+ def __init__(self):
+ self.JSON = None
+ self.Start = None
+ self.End = None
+ self.actionList = []
+
+ def Create(jsonfile):
+ f = open(jsonfile, "r", encoding="utf-8")
+ token = json.load(f)
+
+ if token == None:
+ return None
+
+ if token["type"] != "edge":
+ return None
+
+ edgeList = []
+
+ startNodes = token["start"]
+ endNodes = token["end"]
+
+ if startNodes == None or endNodes == None:
+ return None
+
+ startTokens = startNodes.split(",")
+ endTokens = endNodes.split(",")
+
+ for start in startTokens:
+ for end in endTokens:
+
+ edge = Edge()
+
+ edge.Start = start
+ edge.End = end
+
+ if "actions" in token:
+ for at in token["actions"]:
+ action = EdgeAction.Create(at)
+ if not action:
+ return None
+
+ edge.actionList.append(action)
+
+ edge.JSON = jsonfile
+ edgeList.append(edge)
+
+ return edgeList
+
+ def Apply(self, first, second):
+ exp = []
+
+ for i in range(len(self.actionList)):
+ act = self.actionList[i]
+
+ if act.FromStartObject == True:
+ exp.append(first.GetValue(act.FromExpression))
+ elif act.FromStartObject == False:
+ exp.append(second.GetValue(act.FromExpression))
+ else:
+ exp.append(act.FromExpression)
+
+ for i in range(len(exp)):
+ act = self.actionList[i]
+
+ if act.ToStartObject:
+ first.SetFieldAuto(act.ToExpression, exp[i])
+ else:
+ second.SetFieldAuto(act.ToExpression, exp[i])
+
+ def Actions(self):
+ return self.actionList
+
+ def Name(self):
+ return self.Start + "_" + self.End
diff --git a/extras/packetforge/EdgeAction.py b/extras/packetforge/EdgeAction.py
new file mode 100644
index 00000000000..cf170088a5b
--- /dev/null
+++ b/extras/packetforge/EdgeAction.py
@@ -0,0 +1,55 @@
+# 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.
+
+
+class EdgeAction:
+ def __init__(self):
+ self.ToStartObject = None
+ self.ToExpression = None
+ self.FromStartObject = None
+ self.FromExpression = None
+
+ def Create(token):
+ if token == None:
+ return None
+
+ dststr = token["dst"]
+ srcstr = token["src"]
+
+ if srcstr == None or dststr == None:
+ return None
+
+ action = EdgeAction()
+
+ dststr = dststr.strip()
+ srcstr = srcstr.strip()
+
+ if dststr.startswith("start."):
+ action.ToStartObject = True
+ action.ToExpression = dststr[6:]
+ elif dststr.startswith("end."):
+ action.ToStartObject = False
+ action.ToExpression = dststr[4:]
+ else:
+ return None
+
+ if srcstr.startswith("start."):
+ action.FromStartObject = True
+ action.FromExpression = srcstr[6:]
+ elif srcstr.startswith("end."):
+ action.FromStartObject = False
+ action.FromExpression = srcstr[4:]
+ else:
+ action.FromExpression = srcstr
+
+ return action
diff --git a/extras/packetforge/ExpressionConverter.py b/extras/packetforge/ExpressionConverter.py
new file mode 100644
index 00000000000..1f9c73fedfa
--- /dev/null
+++ b/extras/packetforge/ExpressionConverter.py
@@ -0,0 +1,162 @@
+# 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 InputFormat import InputFormat
+
+
+def ByteArrayToString(data):
+ if len(data) == 0:
+ return ""
+
+ sb = []
+
+ for i in range(len(data) - 1):
+ sb.append("%02x" % data[i])
+
+ sb.append("%02x" % data[len(data) - 1])
+
+ return sb
+
+
+def ToNum(exp):
+ if exp == None:
+ return True, None
+
+ exp = exp.strip()
+
+ if exp.startswith("0x"):
+ out = int(exp, 16)
+ else:
+ try:
+ out = int(exp)
+ except:
+ return False, None
+
+ return True, out
+
+
+def ToIPv4Address(exp):
+ ipv4 = [0] * 4
+
+ exp = exp.strip()
+ tokens = exp.split(".")
+
+ if len(tokens) != 4:
+ return False, bytes(4)
+
+ for i in range(4):
+ u8 = int(tokens[i])
+ if u8 == None:
+ return False, bytes(4)
+
+ ipv4[i] = u8
+
+ return True, bytes(ipv4)
+
+
+def ToIPv6Address(exp):
+ ipv6 = [0] * 16
+
+ exp = exp.strip()
+ tokens = exp.split(":")
+
+ if len(tokens) != 8:
+ return False, bytes(16)
+
+ for i in range(8):
+ u16 = int(tokens[i], 16)
+ if u16 == None:
+ return False, bytes(16)
+
+ ipv6[i * 2] = u16 >> 8
+ ipv6[i * 2 + 1] = u16 & 0xFF
+
+ return True, bytes(ipv6)
+
+
+def ToMacAddress(exp):
+ mac = [0] * 6
+
+ exp = exp.strip()
+ tokens = exp.split(":")
+
+ if len(tokens) != 6:
+ return False, bytes(6)
+
+ for i in range(6):
+ u8 = int(tokens[i], 16)
+ if u8 == None:
+ return False, bytes(6)
+
+ mac[i] = u8
+
+ return True, bytes(mac)
+
+
+def ToByteArray(exp):
+ exp = exp.strip()
+ tokens = exp.split(",")
+
+ tmp = [] * len(tokens)
+
+ for i in range(len(tokens)):
+ _, num = ToNum(tokens[i])
+ if num == 0:
+ return False, bytes(len(tokens))
+
+ tmp[i] = ToNum(tokens[i])
+
+ return True, bytes(tmp)
+
+
+def Verify(format, expression):
+ if (
+ format == InputFormat.u8
+ or format == InputFormat.u16
+ or format == InputFormat.u32
+ or format == InputFormat.u64
+ ):
+ return ToNum(expression)
+ elif format == InputFormat.ipv4:
+ return ToIPv4Address(expression)
+ elif format == InputFormat.ipv6:
+ return ToIPv6Address(expression)
+ elif format == InputFormat.mac:
+ return ToMacAddress(expression)
+ elif format == InputFormat.bytearray:
+ return ToByteArray(expression)
+ else:
+ return False, 0
+
+
+def IncreaseValue(expression, size):
+ if expression == None:
+ return str(size)
+
+ _, num = ToNum(expression)
+ return str(num + size)
+
+
+def Equal(exp, val):
+ if exp == None:
+ num_1 = 0
+ else:
+ _, num_1 = ToNum(exp)
+ if not num_1:
+ return False
+
+ _, num_2 = ToNum(val)
+ if not num_2:
+ return False
+
+ return num_1 == num_2
diff --git a/extras/packetforge/ForgeResult.py b/extras/packetforge/ForgeResult.py
new file mode 100644
index 00000000000..c8611a70ae3
--- /dev/null
+++ b/extras/packetforge/ForgeResult.py
@@ -0,0 +1,45 @@
+# 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.
+
+import ExpressionConverter
+
+
+class ForgeResult:
+ def __init__(self, Header, PacketBuffer, MaskBuffer):
+ self.Headers = Header
+ self.PacketBuffer = PacketBuffer
+ self.MaskBuffer = MaskBuffer
+
+ def ToJSON(self):
+ result = {}
+ result["Length"] = str(len(self.PacketBuffer))
+ result["Packet"] = ExpressionConverter.ByteArrayToString(self.PacketBuffer)
+ result["Mask"] = ExpressionConverter.ByteArrayToString(self.MaskBuffer)
+ result["Protocol Stack"] = []
+
+ for header in self.Headers:
+ head_info = {}
+ head_info["name"] = header.Name()
+ head_info["Fields"] = []
+ for field in header.fields:
+ if field.Size == 0:
+ continue
+ field_info = {}
+ field_info["name"] = field.Field.Name
+ field_info["size"] = str(field.Size)
+ field_info["value"] = field.Value
+ field_info["mask"] = field.Mask
+ head_info["Fields"].append(field_info)
+ result["Protocol Stack"].append(head_info)
+
+ return result
diff --git a/extras/packetforge/InputFormat.py b/extras/packetforge/InputFormat.py
new file mode 100644
index 00000000000..dfba1676934
--- /dev/null
+++ b/extras/packetforge/InputFormat.py
@@ -0,0 +1,12 @@
+import enum
+
+
+class InputFormat(enum.Enum):
+ mac = 0
+ ipv4 = 1
+ ipv6 = 2
+ u8 = 3
+ u16 = 4
+ u32 = 5
+ u64 = 6
+ bytearray = 7
diff --git a/extras/packetforge/LICENSE.txt b/extras/packetforge/LICENSE.txt
new file mode 100644
index 00000000000..8f71f43fee3
--- /dev/null
+++ b/extras/packetforge/LICENSE.txt
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
+
diff --git a/extras/packetforge/Node.py b/extras/packetforge/Node.py
new file mode 100644
index 00000000000..798542a0335
--- /dev/null
+++ b/extras/packetforge/Node.py
@@ -0,0 +1,62 @@
+# 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 NodeField import *
+from NodeAttribute import *
+import json
+
+
+class Node:
+ def __init__(self):
+ self.fields = []
+ self.attributes = []
+ self.attrsDict = {}
+ self.fieldDict = {}
+
+ def Create(jsonfile):
+ f = open(jsonfile, "r", encoding="utf-8")
+ token = json.load(f)
+
+ if token == None:
+ return None
+
+ if token["type"] != "node":
+ return None
+
+ node = Node()
+
+ name = token["name"]
+ if name == None:
+ return None
+
+ node.Name = name
+
+ if token["layout"] == None:
+ return None
+
+ for ft in token["layout"]:
+ field = NodeField.Create(ft)
+ if field == None:
+ return None
+ node.fields.append(field)
+ if not field.IsReserved:
+ node.fieldDict[field.Name] = field
+
+ if "attributes" in token and token["attributes"] != None:
+ for ft in token["attributes"]:
+ attr = NodeAttribute.Create(ft)
+ node.attrsDict[attr.Name] = attr
+ node.attributes.append(attr)
+
+ node.JSON = jsonfile
+ return node
diff --git a/extras/packetforge/NodeAttribute.py b/extras/packetforge/NodeAttribute.py
new file mode 100644
index 00000000000..007b9e050ad
--- /dev/null
+++ b/extras/packetforge/NodeAttribute.py
@@ -0,0 +1,65 @@
+# 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 InputFormat import *
+import ExpressionConverter
+
+
+class NodeAttribute:
+ def __init__(self):
+ self.DefaultValue = None
+
+ def Create(token):
+ if token == None:
+ return None
+
+ attr = NodeAttribute()
+
+ if token["name"] == None:
+ return None
+ if token["size"] == None:
+ return None
+
+ # name
+ attr.Name = token["name"]
+
+ inputFormat = InputFormat.bytearray
+ res, u16 = ExpressionConverter.ToNum(token["size"])
+
+ # size
+ if res:
+ attr.Size = u16
+ if u16 <= 8:
+ inputFormat = InputFormat.u8
+ elif u16 <= 16:
+ inputFormat = InputFormat.u16
+ elif u16 <= 32:
+ inputFormat = InputFormat.u32
+ elif u16 <= 64:
+ inputFormat = InputFormat.u64
+ else:
+ inputFormat = InputFormat.bytearray
+ else:
+ return None
+
+ if "format" in token and token["format"] != None:
+ inputFormat = InputFormat[token["format"]]
+
+ attr.Format = inputFormat
+ if "default" in token and token["default"] != None:
+ attr.DefaultValue = token["default"]
+ ret, _ = ExpressionConverter.Verify(attr.Format, attr.DefaultValue)
+ if not ret:
+ return None
+
+ return attr
diff --git a/extras/packetforge/NodeField.py b/extras/packetforge/NodeField.py
new file mode 100644
index 00000000000..5788984630f
--- /dev/null
+++ b/extras/packetforge/NodeField.py
@@ -0,0 +1,86 @@
+# 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.
+
+import ExpressionConverter
+from InputFormat import *
+
+
+class NodeField:
+ def __init__(self):
+ self.DefaultValue = None
+ self.IsReserved = None
+ self.IsReadonly = None
+ self.IsAutoIncrease = None
+ self.IsIncreaseLength = None
+ self.Optional = None
+ self.VariableSize = None
+
+ def Create(token):
+ if token == None:
+ return None
+
+ field = NodeField()
+
+ if token["name"] == None:
+ return None
+ if token["size"] == None:
+ return None
+
+ # name
+ field.Name = token["name"]
+
+ if field.Name == "reserved":
+ field.IsReserved = True
+
+ inputFormat = InputFormat.bytearray
+ res, u16 = ExpressionConverter.ToNum(token["size"])
+
+ # size
+ if res:
+ field.Size = u16
+ if u16 <= 8:
+ inputFormat = InputFormat.u8
+ elif u16 <= 16:
+ inputFormat = InputFormat.u16
+ elif u16 <= 32:
+ inputFormat = InputFormat.u32
+ elif u16 <= 64:
+ inputFormat = InputFormat.u64
+ else:
+ inputFormat = InputFormat.bytearray
+ else:
+ field.Size = 0
+ field.VariableSize = token["size"]
+
+ if "format" in token and token["format"] != None:
+ inputFormat = InputFormat[token["format"]]
+
+ field.Format = inputFormat
+
+ if "default" in token and token["default"] != None:
+ field.DefaultValue = token["default"]
+ ret, _ = ExpressionConverter.Verify(field.Format, field.DefaultValue)
+ if not ret:
+ return None
+
+ if "readonly" in token and token["readonly"] == "true" or field.IsReserved:
+ field.IsReadonly = True
+ if "autoincrease" in token and token["autoincrease"] == "true":
+ field.IsAutoIncrease = True
+ field.IsReadonly = True
+ if "increaselength" in token and token["increaselength"] == "true":
+ field.IsIncreaseLength = True
+ if "optional" in token:
+ field.Optional = token["optional"]
+
+ return field
diff --git a/extras/packetforge/ParseGraph.py b/extras/packetforge/ParseGraph.py
new file mode 100644
index 00000000000..188b0732ff6
--- /dev/null
+++ b/extras/packetforge/ParseGraph.py
@@ -0,0 +1,179 @@
+# 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 ProtocolHeader import *
+from ForgeResult import *
+from Node import *
+from Edge import *
+import os
+
+
+class ParseGraph:
+ def __init__(self):
+ self.nodeDict = {}
+ self.edgeDict = {}
+
+ def Create(folder):
+ try:
+ pg = ParseGraph()
+ if not os.path.exists(folder):
+ print("folder not exisit")
+ return None
+
+ if os.path.exists(folder + "/nodes"):
+ pg.LoadNodesFromDirectory(folder + "/nodes")
+ if os.path.exists(folder + "/edges"):
+ pg.LoadEdgesFromDirectory(folder + "/edges")
+ except:
+ print("Failed to create Parse Graph")
+ return None
+ else:
+ return pg
+
+ def Nodes(self):
+ nodes = []
+ nodes.extend(self.nodeDict.values)
+ return nodes
+
+ def Edges(self):
+ edges = []
+ edges.extend(self.edgeDict.values)
+ return edges
+
+ def LoadNodesFromDirectory(self, folder):
+ for root, dirs, files in os.walk(folder):
+ for f in files:
+ self.LoadNodeFromFile(os.path.join(root, f))
+
+ def LoadEdgesFromDirectory(self, folder):
+ for root, dirs, files in os.walk(folder):
+ for f in files:
+ self.LoadEdgeFromFile(os.path.join(root, f))
+
+ def LoadNodeFromFile(self, file):
+ try:
+ node = Node.Create(file)
+
+ if node == None:
+ print("No node created")
+ return None
+
+ self.AddNode(node)
+ except:
+ print("Failed to create node from " + file)
+
+ def LoadEdgeFromFile(self, file):
+ try:
+ edges = Edge.Create(file)
+
+ if edges == None:
+ print("No edge created")
+ return None
+
+ for edge in edges:
+ self.AddEdge(edge)
+ except:
+ print("Failed to create edge from " + file)
+
+ def createProtocolHeader(self, name):
+ if name in self.nodeDict:
+ return ProtocolHeader(self.nodeDict[name])
+ return None
+
+ def GetNode(self, name):
+ if self.nodeDict.has_key(name):
+ return self.nodeDict[name]
+ return None
+
+ def GetEdge(self, start, end):
+ key = start + "-" + end
+ if key in self.edgeDict:
+ return self.edgeDict[key]
+ return None
+
+ def AddNode(self, node):
+ if node.Name in self.nodeDict:
+ print("Warning: node {0} already exist", node.Name)
+
+ self.nodeDict[node.Name] = node
+
+ def AddEdge(self, edge):
+ key = edge.Start + "-" + edge.End
+ if key in self.edgeDict:
+ print("Warning: edge {0} already exist", key)
+ self.edgeDict[key] = edge
+
+ def Forge(self, path):
+ headerList = []
+
+ # set field value/mask
+ for headerConfig in path.stack:
+ header = self.createProtocolHeader(headerConfig.Header)
+
+ if header == None:
+ return None
+
+ for hcf in headerConfig.fields:
+ attr = False
+ if not header.SetField(hcf.Name, hcf.Value):
+ if not header.SetAttribute(hcf.Name, hcf.Value):
+ print("failed to set value of " + hcf.Name)
+ return None
+ else:
+ attr = True
+
+ if not attr and not header.SetMask(hcf.Name, hcf.Mask):
+ print("failed to set mask of " + hcf.Name)
+ return None
+
+ header.Adjust()
+
+ headerList.append(header)
+
+ # apply edge actions and length autoincrease
+ for i in range(1, len(headerList)):
+ start = headerList[i - 1]
+ end = headerList[i]
+
+ edge = self.GetEdge(start.Name(), end.Name())
+
+ if edge == None:
+ print("no edge exist for {0}, {1}", start.Name, end.Name)
+ return None
+
+ edge.Apply(start, end)
+
+ increase = end.GetSize()
+ for j in range(i):
+ headerList[j].AppendAuto(increase)
+
+ # resolve buffer
+ pktLen = 0
+ for header in headerList:
+ header.Resolve()
+ pktLen += len(header.Buffer)
+
+ # join buffer
+ pktbuf = []
+ mskbuf = []
+
+ offset = 0
+ for header in headerList:
+ pktbuf.extend(header.Buffer)
+ mskbuf.extend(header.Mask)
+
+ offset += len(header.Buffer)
+
+ result = ForgeResult(headerList, pktbuf, mskbuf)
+
+ return result
diff --git a/extras/packetforge/Path.py b/extras/packetforge/Path.py
new file mode 100644
index 00000000000..ee80255e0d9
--- /dev/null
+++ b/extras/packetforge/Path.py
@@ -0,0 +1,35 @@
+# 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 PathNode import *
+
+
+class Path:
+ def __init__(self):
+ self.stack = []
+
+ def Create(token):
+ try:
+ path = Path()
+ ss = token["stack"]
+
+ if ss == None:
+ return None
+
+ for hct in ss:
+ path.stack.append(PathNode.Create(hct))
+
+ return path
+ except:
+ print("Failed to create Path from jsonfile")
+ return None
diff --git a/extras/packetforge/PathNode.py b/extras/packetforge/PathNode.py
new file mode 100644
index 00000000000..4f31a0e1f96
--- /dev/null
+++ b/extras/packetforge/PathNode.py
@@ -0,0 +1,39 @@
+# 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 PathNodeField import *
+
+
+class PathNode:
+ def __init__(self):
+ self.Header = None
+ self.fields = []
+
+ def Create(token):
+ if token == None:
+ return None
+
+ config = PathNode()
+
+ if "header" in token:
+ config.Header = token["header"]
+ if config.Header == None:
+ return None
+
+ if "fields" in token:
+ fts = token["fields"]
+ if fts != None:
+ for ft in fts:
+ config.fields.append(PathNodeField.Create(ft))
+
+ return config
diff --git a/extras/packetforge/PathNodeField.py b/extras/packetforge/PathNodeField.py
new file mode 100644
index 00000000000..8f5d3bb2adc
--- /dev/null
+++ b/extras/packetforge/PathNodeField.py
@@ -0,0 +1,37 @@
+# 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.
+
+
+class PathNodeField:
+ def __init__(self):
+ self.Name = None
+ self.Value = None
+ self.Mask = None
+
+ def Create(token):
+ if token == None:
+ return None
+
+ field = PathNodeField()
+
+ if "name" in token:
+ field.Name = token["name"]
+ if "value" in token:
+ field.Value = token["value"]
+ if "mask" in token:
+ field.Mask = token["mask"]
+
+ if field.Name == None:
+ return None
+
+ return field
diff --git a/extras/packetforge/ProtocolHeader.py b/extras/packetforge/ProtocolHeader.py
new file mode 100644
index 00000000000..272b6557460
--- /dev/null
+++ b/extras/packetforge/ProtocolHeader.py
@@ -0,0 +1,387 @@
+# 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 ProtocolHeaderAttribute import *
+from ProtocolHeaderField import *
+from InputFormat import *
+import ExpressionConverter
+import copy
+
+
+class ProtocolHeader:
+ def __init__(self, node):
+ self.fields = []
+ self.attributes = []
+ self.fieldDict = {}
+ self.attributeDict = {}
+ self.Buffer = []
+ self.Mask = []
+
+ self.node = node
+ for field in self.node.fields:
+ phf = ProtocolHeaderField(field.Size, field.DefaultValue, None, field)
+ self.fields.append(phf)
+ if field.Name != "reserved":
+ self.fieldDict[field.Name] = phf
+
+ for attr in self.node.attributes:
+ pha = ProtocolHeaderAttribute(attr.Size, attr.DefaultValue, attr)
+ self.attributes.append(pha)
+ self.attributeDict[attr.Name] = pha
+
+ def Name(self):
+ return self.node.Name
+
+ def Fields(self):
+ return self.fields
+
+ def Attributes(self):
+ return self.attributes
+
+ def setField(self, name, expression, auto):
+ if name == "reserved":
+ return False
+
+ if name not in self.fieldDict:
+ return False
+
+ field = self.fieldDict[name]
+
+ if field.UpdateValue(expression, auto):
+ field.UpdateSize()
+ return True
+
+ return False
+
+ def SetField(self, name, expression):
+ return self.setField(name, expression, False)
+
+ def SetFieldAuto(self, name, expression):
+ return self.setField(name, expression, True)
+
+ def SetAttribute(self, name, expression):
+ if name not in self.attributeDict:
+ return False
+ attr = self.attributeDict[name]
+
+ return attr.UpdateValue(expression)
+
+ def SetMask(self, name, expression):
+ if name not in self.fieldDict:
+ return False
+ field = self.fieldDict[name]
+
+ return field.UpdateMask(expression)
+
+ def resolveOptional(self, condition):
+ if condition == None:
+ return True
+
+ tokens = condition.split("|")
+
+ if len(tokens) > 1:
+ result = False
+
+ for token in tokens:
+ result |= self.resolveOptional(token)
+
+ return result
+
+ tokens = condition.split("&")
+
+ if len(tokens) > 1:
+ result = True
+
+ for token in tokens:
+ result &= self.resolveOptional(token)
+
+ return result
+
+ key = None
+ value = None
+
+ if "!=" in tokens[0]:
+ index = tokens[0].find("!=")
+ key = tokens[0][:index].strip()
+ value = tokens[0][index + 1 :].strip()
+ elif "=" in tokens[0]:
+ index = tokens[0].find("=")
+ key = tokens[0][:index].strip()
+ value = tokens[0][index + 1 :].strip()
+ else:
+ return False
+
+ if key not in self.fieldDict:
+ return False
+
+ f = self.fieldDict[key]
+ return ExpressionConverter.Equal(f.Value, value)
+
+ def resolveSize(self, exp):
+ shift = 0
+ key = exp
+
+ if "<<" in exp:
+ offset = exp.find("<<")
+ key = exp[0:offset].strip()
+ shift = int(exp[offset + 2 :].strip())
+
+ if self.fieldDict.has_key(key):
+ field = self.fieldDict[key]
+ _, u16 = ExpressionConverter.ToNum(field.Value)
+ if u16:
+ return u16 << shift
+ else:
+ return 0
+
+ if self.attributeDict.has_key(key):
+ attr = self.attributeDict[key]
+ _, u16 = ExpressionConverter.ToNum(attr.Value)
+ if u16:
+ return u16 << shift
+ else:
+ return 0
+
+ return 0
+
+ def Adjust(self):
+ autoIncreases = []
+ increaseHeaders = []
+
+ self.resolveAllSize()
+
+ for phf in self.fields:
+ if phf.Field.IsAutoIncrease:
+ autoIncreases.append(phf)
+ if phf.Field.IsIncreaseLength and self.resolveOptional(phf.Field.Optional):
+ increaseHeaders.append(phf)
+
+ for f1 in autoIncreases:
+ for f2 in increaseHeaders:
+ f1.UpdateValue(
+ ExpressionConverter.IncreaseValue(f1.Value, f2.Size >> 3), True
+ )
+
+ def resolveAllSize(self):
+ for phf in self.fields:
+ if phf.Field.Optional != None and not self.resolveOptional(
+ phf.Field.Optional
+ ):
+ size = 0
+ else:
+ if phf.Field.VariableSize != None:
+ size = self.resolveSize(phf.Field.VariableSize)
+ else:
+ size = phf.Field.Size
+ phf.Size = size
+
+ def GetSize(self):
+ size = 0
+
+ for field in self.fields:
+ size += field.Size
+
+ return size >> 3
+
+ def AppendAuto(self, size):
+ for phf in self.fields:
+ if not phf.Field.IsAutoIncrease:
+ continue
+
+ phf.UpdateValue(ExpressionConverter.IncreaseValue(phf.Value, size), True)
+
+ def getField(self, name):
+ if not self.fieldDict.has_key(name):
+ return None
+ field = self.fieldDict[name]
+
+ return field.Value
+
+ def getAttribute(self, name):
+ if not self.attributeDict.has_key(name):
+ return None
+
+ return self.attributeDict[name].Value
+
+ def GetValue(self, name):
+ result = self.getField(name)
+
+ if result == None:
+ return self.getAttribute(name)
+
+ return result
+
+ def appendNum(self, big, exp, size):
+ num = 0
+ if exp != None:
+ _, num = ExpressionConverter.ToNum(exp)
+ if num == None:
+ print("Invalid byte expression")
+ return None
+
+ # cut msb
+ num = num & ((1 << size) - 1)
+ big = big << size
+ big = big | num
+ return big
+
+ def appendUInt64(self, big, exp, size):
+ u64 = 0
+ if exp != None:
+ _, u64 = ExpressionConverter.ToNum(exp)
+ if not u64:
+ print("Invalid UInt32 expression")
+ return False
+
+ # cut msb
+ if size < 64:
+ u64 = u64 & ((1 << size) - 1)
+ big = big << size
+ big = big | u64
+ return big
+
+ def appendIPv4(self, big, exp):
+ ipv4 = bytes(4)
+ if exp != None:
+ _, ipv4 = ExpressionConverter.ToIPv4Address(exp)
+ if not ipv4:
+ print("Inavalid IPv4 Address")
+ return False
+
+ for i in range(len(ipv4)):
+ big = big << 8
+ big = big | ipv4[i]
+
+ return big
+
+ def appendIPv6(self, big, exp):
+ ipv6 = bytes(16)
+ if exp != None:
+ _, ipv6 = ExpressionConverter.ToIPv6Address(exp)
+ if not ipv6:
+ print("Inavalid IPv6 Address")
+ return False
+
+ for i in range(16):
+ big = big << 8
+ big = big | ipv6[i]
+
+ return big
+
+ def appendMAC(self, big, exp):
+ mac = bytes(6)
+ if exp != None:
+ _, mac = ExpressionConverter.ToMacAddress(exp)
+ if not mac:
+ print("Inavalid MAC Address")
+ return False
+
+ for i in range(6):
+ big = big << 8
+ big = big | mac[i]
+
+ return big
+
+ def appendByteArray(self, big, exp, size):
+ array = bytes(size >> 3)
+ if exp != None:
+ _, array = ExpressionConverter.ToByteArray(exp)
+ if not array:
+ print("Invalid byte array")
+ return False
+
+ for i in range(size >> 3):
+ big = big << 8
+ if i < len(array):
+ big = big | array[i]
+
+ return big
+
+ def append(self, big, phf):
+ bigVal = big["bigVal"]
+ bigMsk = big["bigMsk"]
+
+ if phf.Field.IsReserved:
+ bigVal <<= phf.Size
+ bigMsk <<= phf.Size
+ big.update(bigVal=bigVal, bigMsk=bigMsk)
+ return big, phf.Size
+
+ size = phf.Size
+
+ if (
+ phf.Field.Format == InputFormat.u8
+ or phf.Field.Format == InputFormat.u16
+ or phf.Field.Format == InputFormat.u32
+ ):
+ bigVal = self.appendNum(bigVal, phf.Value, size)
+ bigMsk = self.appendNum(bigMsk, phf.Mask, size)
+
+ elif phf.Field.Format == InputFormat.u64:
+ bigVal = self.appendUInt64(bigVal, phf.Value, size)
+ bigMsk = self.appendUInt64(bigMsk, phf.Mask, size)
+
+ elif phf.Field.Format == InputFormat.ipv4:
+ bigVal = self.appendIPv4(bigVal, phf.Value)
+ bigMsk = self.appendIPv4(bigMsk, phf.Mask)
+
+ elif phf.Field.Format == InputFormat.ipv6:
+ bigVal = self.appendIPv6(bigVal, phf.Value)
+ bigMsk = self.appendIPv6(bigMsk, phf.Mask)
+
+ elif phf.Field.Format == InputFormat.mac:
+ bigVal = self.appendMAC(bigVal, phf.Value)
+ bigMsk = self.appendMAC(bigMsk, phf.Mask)
+
+ elif phf.Field.Format == InputFormat.bytearray:
+ bigVal = self.appendByteArray(bigVal, phf.Value, size)
+ bigMsk = self.appendByteArray(bigMsk, phf.Mask, size)
+
+ else:
+ print("Invalid input format")
+
+ big.update(bigVal=bigVal, bigMsk=bigMsk)
+ return big, size
+
+ def Resolve(self):
+ big = {"bigVal": 0, "bigMsk": 0}
+ offset = 0
+
+ for phf in self.fields:
+ if phf.Size == 0:
+ continue
+
+ big, bits = self.append(big, phf)
+
+ offset += bits
+
+ byteList1 = []
+ byteList2 = []
+
+ bigVal = big["bigVal"]
+ bigMsk = big["bigMsk"]
+
+ while offset > 0:
+ byteList1.append(bigVal & 0xFF)
+ byteList2.append(bigMsk & 0xFF)
+ bigVal = bigVal >> 8
+ bigMsk = bigMsk >> 8
+ offset -= 8
+
+ byteList1.reverse()
+ byteList2.reverse()
+ buffer = copy.deepcopy(byteList1)
+ mask = copy.deepcopy(byteList2)
+
+ self.Buffer = buffer
+ self.Mask = mask
diff --git a/extras/packetforge/ProtocolHeaderAttribute.py b/extras/packetforge/ProtocolHeaderAttribute.py
new file mode 100644
index 00000000000..fd9ea04b96e
--- /dev/null
+++ b/extras/packetforge/ProtocolHeaderAttribute.py
@@ -0,0 +1,29 @@
+# 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.
+
+import ExpressionConverter
+
+
+class ProtocolHeaderAttribute:
+ def __init__(self, Size, Value, Attribute):
+ self.Size = Size
+ self.Value = Value
+ self.Attribute = Attribute
+
+ def UpdateValue(self, expression):
+ ret, expression = ExpressionConverter.Verify(self.Attribute.Format, expression)
+ if not ret:
+ return False
+
+ self.Value = expression
+ return True
diff --git a/extras/packetforge/ProtocolHeaderField.py b/extras/packetforge/ProtocolHeaderField.py
new file mode 100644
index 00000000000..bcb88214931
--- /dev/null
+++ b/extras/packetforge/ProtocolHeaderField.py
@@ -0,0 +1,48 @@
+# 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.
+
+import ExpressionConverter
+
+
+class ProtocolHeaderField:
+ def __init__(self, Size, Value, Mask, Field):
+ self.Size = Size
+ self.Value = Value
+ self.Mask = Mask
+ self.Field = Field
+
+ def UpdateValue(self, expression, auto):
+ if self.Field.IsReadonly and not auto:
+ return False
+
+ if expression != None:
+ ret, _ = ExpressionConverter.Verify(self.Field.Format, expression)
+ if not ret:
+ return False
+
+ self.Value = expression
+ return True
+
+ def UpdateMask(self, expression):
+ if expression != None:
+ ret, _ = ExpressionConverter.Verify(self.Field.Format, expression)
+ if not ret:
+ return False
+
+ self.Mask = expression
+ return True
+
+ def UpdateSize(self):
+ if self.Size:
+ return
+ self.Size = self.Field.Size
diff --git a/extras/packetforge/README.rst b/extras/packetforge/README.rst
new file mode 100644
index 00000000000..f477c0bab38
--- /dev/null
+++ b/extras/packetforge/README.rst
@@ -0,0 +1,71 @@
+.. _packetforge_doc:
+
+Packetforge for generic flow
+============================
+
+Packetforge is a tool to support generic flow. Since the input format of
+generic flow is hard to read and create, packetforge can help to create
+generic flow rules using a format of naming protocols (like Scapy) or json
+profile. Packetforge is built based on a parsegraph, users can modify the
+graph nodes and edges if needed.
+
+Command examples
+----------------
+
+::
+
+ $ 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 --add
+ --pattern "mac()/ipv4(src=1.1.1.1,dst=2.2.2.2)/udp()"
+ --actions "redirect-to-queue 3" --interface 1
+
+ $ python flow_create.py --del -i 1 -I 0
+
+ $ python flow_create.py --del --interface 1 --flow-index 0
+
+Naming format input. There are two operations, add and delete flow rules.
+For add, it needs three parameters. Pattern is similar to Scapy protocols.
+Actions is the same as vnet/flow command. Interface is the device to which
+we want to add the flow rule. For delete, flow index is the index of the
+flow rule we want to delete. We can get the index number when we add the
+flow or use command to show the existed flow entry in CLI.
+
+::
+
+ $ python flow_create.py --add -f "./flow_rule_examples/mac_ipv4.json" -i 1
+
+ $ python flow_create.py --add --file "./flow_rule_examples/mac_ipv4.json"
+ --interface 1
+
+ $ python flow_create.py --add -f "./flow_rule_examples/mac_ipv4.json"
+ -a "redirect-to-queue 3" -i 1
+
+Json profile format input. This command takes a json profile as parameter.
+In the json profile, there will be protocols and their fields and values.
+Users can define spec and mask for each field. Actions can be added in the
+profile directly, otherwise "-a" option should be added in the command to
+specify actions. The example can be found in parsegraph/samples folder.
+Users can create their own json files according to examples and Spec.
+
+::
+
+ $ show flow entry
+
+It is a vnet/flow command, used in VPP CLI. It can show the added flow rules
+after using the above commands. Users can get the flow index with this command
+and use it to delete the flow rule.
+
+ParseGraph
+----------
+
+Packetforge is built based on a ParseGraph. The ParseGraph is constructed
+with nodes and edges. Nodes are protocols, including information about
+protocol's name, fields and default values. Edges are the relationship
+between two protocols, including some actions needed when connecting two
+protocols. For example, change the mac header ethertype to 0x0800 when
+connecting mac and ipv4. More details are in the Spec in parsegraph folder.
+Users can build the ParseGraph following the spec by themselves, like
+adding a new protocol. If NIC supports the new protocol, the rule can be
+created. Otherwise, it will return error.
diff --git a/extras/packetforge/StringFormat.py b/extras/packetforge/StringFormat.py
new file mode 100644
index 00000000000..a9fe0905aeb
--- /dev/null
+++ b/extras/packetforge/StringFormat.py
@@ -0,0 +1,11 @@
+import enum
+
+
+class StringFormat(enum.Enum):
+ mac = 0
+ ipv4 = 1
+ ipv6 = 2
+ u8 = 3
+ u16 = 4
+ u32 = 5
+ u64 = 6
diff --git a/extras/packetforge/flow_create.py b/extras/packetforge/flow_create.py
new file mode 100644
index 00000000000..fbbdec56694
--- /dev/null
+++ b/extras/packetforge/flow_create.py
@@ -0,0 +1,157 @@
+# 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 VPPApiClient
+import sys, getopt
+import packetforge
+import fnmatch
+import os
+
+# Get VPP json API file directory
+CLIENT_ID = "Vppclient"
+VPP_JSON_DIR = (
+ os.path.abspath("../..") + "/build-root/install-vpp-native/vpp/share/vpp/api/core"
+)
+VPP_JSON_DIR_PLUGIN = (
+ os.path.abspath("../..")
+ + "/build-root/install-vpp-native/vpp/share/vpp/api/plugins"
+)
+API_FILE_SUFFIX = "*.api.json"
+
+
+def Main(argv):
+ file_flag = False
+ operation = None
+ try:
+ opts, args = getopt.getopt(
+ argv,
+ "hf:p:a:i:I:",
+ [
+ "help=",
+ "add",
+ "del",
+ "file=",
+ "pattern=",
+ "actions=",
+ "interface=",
+ "flow-index=",
+ ],
+ )
+ except getopt.GetoptError:
+ print(
+ "flow_create.py --add|del -f <file> -p <pattern> -a <actions> -i <interface> -I <flow-index>"
+ )
+ sys.exit()
+ for opt, arg in opts:
+ if opt == "-h":
+ print(
+ "flow_create.py --add|del -f <file> -p <pattern> -a <actions> -i <interface> -I <flow-index>"
+ )
+ sys.exit()
+ elif opt == "--add":
+ operation = "add"
+ elif opt == "--del":
+ operation = "del"
+ elif opt in ("-f", "--file"):
+ actions = ""
+ json_file = arg
+ file_flag = True
+ elif opt in ("-p", "--pattern") and not file_flag:
+ pattern = arg
+ elif opt in ("-a", "--actions"):
+ actions = arg
+ elif opt in ("-i", "--interface"):
+ iface = arg
+ elif opt in ("-I", "--flow-index"):
+ flow_index = arg
+
+ if operation == None:
+ print("Error: Please choose the operation: add or del")
+ sys.exit()
+
+ if operation == "add":
+ if not file_flag:
+ result = packetforge.Forge(pattern, actions, False)
+ else:
+ result = packetforge.Forge(json_file, actions, True)
+ return result, int(iface), operation, None
+ elif operation == "del":
+ return None, int(iface), operation, int(flow_index)
+
+
+def load_json_api_files(suffix=API_FILE_SUFFIX):
+ jsonfiles = []
+ json_dir = VPP_JSON_DIR
+ for root, dirnames, filenames in os.walk(json_dir):
+ for filename in fnmatch.filter(filenames, suffix):
+ jsonfiles.append(os.path.join(json_dir, filename))
+ json_dir = VPP_JSON_DIR_PLUGIN
+ for root, dirnames, filenames in os.walk(json_dir):
+ for filename in fnmatch.filter(filenames, suffix):
+ jsonfiles.append(os.path.join(json_dir, filename))
+ if not jsonfiles:
+ raise RuntimeError("Error: no json api files found")
+ else:
+ print("load json api file done")
+ return jsonfiles
+
+
+def connect_vpp(jsonfiles):
+ vpp = VPPApiClient(apifiles=jsonfiles)
+ r = vpp.connect("CLIENT_ID")
+ print("VPP api opened with code: %s" % r)
+ return vpp
+
+
+if __name__ == "__main__":
+ # Python API need json definitions to interpret messages
+ vpp = connect_vpp(load_json_api_files())
+ print(vpp.api.show_version())
+
+ # Parse the arguments
+ my_flow, iface, operation, del_flow_index = Main(sys.argv[1:])
+
+ # set inteface states
+ vpp.api.sw_interface_set_flags(sw_if_index=iface, flags=1)
+
+ if operation == "add":
+ # add flow
+ rv = vpp.api.flow_add_v2(flow=my_flow)
+ flow_index = rv[3]
+ print(rv)
+
+ # enable added flow
+ rv = vpp.api.flow_enable(flow_index=flow_index, hw_if_index=iface)
+ ena_res = rv[2]
+ # if enable flow fail, delete added flow
+ if ena_res:
+ print("Error: enable flow failed, delete flow")
+ rv = vpp.api.flow_del(flow_index=flow_index)
+ print(rv)
+
+ elif operation == "del":
+ # disable flow
+ rv = vpp.api.flow_disable(flow_index=del_flow_index, hw_if_index=iface)
+ dis_res = rv[2]
+ if dis_res:
+ print("Error: disable flow failed")
+ sys.exit()
+ print(rv)
+
+ # delete flow
+ rv = vpp.api.flow_del(flow_index=del_flow_index)
+ print(rv)
+
+# command example:
+# 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
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
diff --git a/extras/packetforge/parsegraph/.gitattributes b/extras/packetforge/parsegraph/.gitattributes
new file mode 100644
index 00000000000..dfe0770424b
--- /dev/null
+++ b/extras/packetforge/parsegraph/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/extras/packetforge/parsegraph/edges/ah_after_ipv4.json b/extras/packetforge/parsegraph/edges/ah_after_ipv4.json
new file mode 100644
index 00000000000..7f123916114
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ah_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "ah",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "51"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ah_after_ipv6.json b/extras/packetforge/parsegraph/edges/ah_after_ipv6.json
new file mode 100644
index 00000000000..64620fa276b
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ah_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "ah",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "51"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json b/extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json
new file mode 100644
index 00000000000..28c042b9c93
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "mac,vlan",
+ "end" : "arpv4",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "0x0806"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/esp_after_ipv4.json b/extras/packetforge/parsegraph/edges/esp_after_ipv4.json
new file mode 100644
index 00000000000..c573ee3aa28
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/esp_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "esp",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "50"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/esp_after_ipv6.json b/extras/packetforge/parsegraph/edges/esp_after_ipv6.json
new file mode 100644
index 00000000000..e3d8bf660d3
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/esp_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "esp",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "50"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/gre_after_ipv4.json b/extras/packetforge/parsegraph/edges/gre_after_ipv4.json
new file mode 100644
index 00000000000..5ae8eb27681
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/gre_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "gre,nvgre",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "47"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/gre_after_ipv6.json b/extras/packetforge/parsegraph/edges/gre_after_ipv6.json
new file mode 100644
index 00000000000..a5a53139de3
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/gre_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "gre,nvgre",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "47"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/gtpc_after_udp.json b/extras/packetforge/parsegraph/edges/gtpc_after_udp.json
new file mode 100644
index 00000000000..237704c87cc
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/gtpc_after_udp.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "udp",
+ "end" : "gtpc",
+ "actions" : [
+ {
+ "dst" : "start.dst",
+ "src" : "2123"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/gtppsc_after_gtpu.json b/extras/packetforge/parsegraph/edges/gtppsc_after_gtpu.json
new file mode 100644
index 00000000000..ec985bb888f
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/gtppsc_after_gtpu.json
@@ -0,0 +1,19 @@
+{
+ "type" : "edge",
+ "start" : "gtpu",
+ "end" : "gtppsc",
+ "actions" : [
+ {
+ "dst" : "start.e",
+ "src" : "1"
+ },
+ {
+ "dst" : "start.nextextentionheadertype",
+ "src" : "0x85"
+ },
+ {
+ "dst" : "start.messagetype",
+ "src" : "0xff"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/gtpu_after_udp.json b/extras/packetforge/parsegraph/edges/gtpu_after_udp.json
new file mode 100644
index 00000000000..e2591fad5ac
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/gtpu_after_udp.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "udp",
+ "end" : "gtpu",
+ "actions" : [
+ {
+ "dst" : "start.dst",
+ "src" : "2152"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/icmp_after_ipv4.json b/extras/packetforge/parsegraph/edges/icmp_after_ipv4.json
new file mode 100644
index 00000000000..e701cc8c28c
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/icmp_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "icmp",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "1"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/icmpv6_after_ipv6.json b/extras/packetforge/parsegraph/edges/icmpv6_after_ipv6.json
new file mode 100644
index 00000000000..a83181450ad
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/icmpv6_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "icmpv6",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "58"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ip_after_gtppsc.json b/extras/packetforge/parsegraph/edges/ip_after_gtppsc.json
new file mode 100644
index 00000000000..13e11a3c67a
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ip_after_gtppsc.json
@@ -0,0 +1,5 @@
+{
+ "type" : "edge",
+ "start" : "gtppsc",
+ "end" : "ipv4"
+}
diff --git a/extras/packetforge/parsegraph/edges/ip_after_gtpu.json b/extras/packetforge/parsegraph/edges/ip_after_gtpu.json
new file mode 100644
index 00000000000..03d89ffc4b9
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ip_after_gtpu.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "gtpu",
+ "end" : "ipv4,ipv6",
+ "actions" : [
+ {
+ "dst" : "start.messagetype",
+ "src" : "0xff"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_geneve.json b/extras/packetforge/parsegraph/edges/ipv4_after_geneve.json
new file mode 100644
index 00000000000..40c733b9688
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_geneve.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "geneve",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.protocoltype",
+ "src" : "0x0800"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_gre.json b/extras/packetforge/parsegraph/edges/ipv4_after_gre.json
new file mode 100644
index 00000000000..87cd14beecd
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_gre.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "gre",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.protocoltype",
+ "src" : "0x0800"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_ipv4.json b/extras/packetforge/parsegraph/edges/ipv4_after_ipv4.json
new file mode 100644
index 00000000000..0590f6738b3
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "4"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_ipv6.json b/extras/packetforge/parsegraph/edges/ipv4_after_ipv6.json
new file mode 100644
index 00000000000..e804139cfe5
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "4"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_macvlan.json b/extras/packetforge/parsegraph/edges/ipv4_after_macvlan.json
new file mode 100644
index 00000000000..3722126f6f4
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_macvlan.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "mac,vlan",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "0x0800"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv4_after_vxlangpe.json b/extras/packetforge/parsegraph/edges/ipv4_after_vxlangpe.json
new file mode 100644
index 00000000000..ab7c16707fb
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv4_after_vxlangpe.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "vxlangpe",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.nextprotocol",
+ "src" : "1"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_geneve.json b/extras/packetforge/parsegraph/edges/ipv6_after_geneve.json
new file mode 100644
index 00000000000..6ad81a4550a
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_geneve.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "geneve",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.protocoltype",
+ "src" : "0x86dd"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_gre.json b/extras/packetforge/parsegraph/edges/ipv6_after_gre.json
new file mode 100644
index 00000000000..90c40ec3974
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_gre.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "gre",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.protocoltype",
+ "src" : "0x86dd"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_ipv4.json b/extras/packetforge/parsegraph/edges/ipv6_after_ipv4.json
new file mode 100644
index 00000000000..cc57b44cf25
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "41"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_ipv6.json b/extras/packetforge/parsegraph/edges/ipv6_after_ipv6.json
new file mode 100644
index 00000000000..79184bb55dd
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "41"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_macvlan.json b/extras/packetforge/parsegraph/edges/ipv6_after_macvlan.json
new file mode 100644
index 00000000000..6da21082024
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_macvlan.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "mac,vlan",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "0x86dd"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6_after_vxlangpe.json b/extras/packetforge/parsegraph/edges/ipv6_after_vxlangpe.json
new file mode 100644
index 00000000000..ebd294cf378
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6_after_vxlangpe.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "vxlangpe",
+ "end" : "ipv6",
+ "actions" : [
+ {
+ "dst" : "start.nextprotocol",
+ "src" : "2"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/ipv6srh_after_ipv6.json b/extras/packetforge/parsegraph/edges/ipv6srh_after_ipv6.json
new file mode 100644
index 00000000000..9ddea00e926
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/ipv6srh_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "ipv6srh",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "43"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/l2tpv2_after_udp.json b/extras/packetforge/parsegraph/edges/l2tpv2_after_udp.json
new file mode 100644
index 00000000000..c34927ac47f
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/l2tpv2_after_udp.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "udp",
+ "end" : "l2tpv2ctl,l2tpv2data",
+ "actions" : [
+ {
+ "dst" : "start.dst",
+ "src" : "1701"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/mac_after_geneve.json b/extras/packetforge/parsegraph/edges/mac_after_geneve.json
new file mode 100644
index 00000000000..3eba08ee802
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/mac_after_geneve.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "geneve",
+ "end" : "mac",
+ "actions" : [
+ {
+ "dst" : "start.protocoltype",
+ "src" : "0x6558"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/mac_after_gre.json b/extras/packetforge/parsegraph/edges/mac_after_gre.json
new file mode 100644
index 00000000000..cb5fd4078e5
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/mac_after_gre.json
@@ -0,0 +1,5 @@
+{
+ "type" : "edge",
+ "start" : "gre",
+ "end" : "mac"
+}
diff --git a/extras/packetforge/parsegraph/edges/mac_after_nvgre.json b/extras/packetforge/parsegraph/edges/mac_after_nvgre.json
new file mode 100644
index 00000000000..1c9f98373bf
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/mac_after_nvgre.json
@@ -0,0 +1,5 @@
+{
+ "type" : "edge",
+ "start" : "nvgre",
+ "end" : "mac"
+}
diff --git a/extras/packetforge/parsegraph/edges/mac_after_vxlan.json b/extras/packetforge/parsegraph/edges/mac_after_vxlan.json
new file mode 100644
index 00000000000..c1c1fa25540
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/mac_after_vxlan.json
@@ -0,0 +1,5 @@
+{
+ "type" : "edge",
+ "start" : "vxlan",
+ "end" : "mac"
+}
diff --git a/extras/packetforge/parsegraph/edges/mac_after_vxlangpe.json b/extras/packetforge/parsegraph/edges/mac_after_vxlangpe.json
new file mode 100644
index 00000000000..b5012bf71fb
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/mac_after_vxlangpe.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "vxlangpe",
+ "end" : "mac",
+ "actions" : [
+ {
+ "dst" : "start.nextprotocol",
+ "src" : "3"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/pfcp_after_udp.json b/extras/packetforge/parsegraph/edges/pfcp_after_udp.json
new file mode 100644
index 00000000000..fec4aaa739e
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/pfcp_after_udp.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "udp",
+ "end" : "pfcp",
+ "actions" : [
+ {
+ "dst" : "start.dst",
+ "src" : "8805"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/sctp_after_ipv4.json b/extras/packetforge/parsegraph/edges/sctp_after_ipv4.json
new file mode 100644
index 00000000000..e58c36efc7a
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/sctp_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "sctp",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "132"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/sctp_after_ipv6.json b/extras/packetforge/parsegraph/edges/sctp_after_ipv6.json
new file mode 100644
index 00000000000..7fa306811eb
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/sctp_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6",
+ "end" : "sctp",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "132"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/tcp_after_ipv4.json b/extras/packetforge/parsegraph/edges/tcp_after_ipv4.json
new file mode 100644
index 00000000000..13c74f85edc
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/tcp_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "tcp",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/tcp_after_ipv6.json b/extras/packetforge/parsegraph/edges/tcp_after_ipv6.json
new file mode 100644
index 00000000000..f0d0b415e22
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/tcp_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "tcp",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/tunnel_after_udp.json b/extras/packetforge/parsegraph/edges/tunnel_after_udp.json
new file mode 100644
index 00000000000..fa498d4378e
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/tunnel_after_udp.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "udp",
+ "end" : "geneve,vxlan,vxlangpe",
+ "actions" : [
+ {
+ "dst" : "start.dst",
+ "src" : "end.udpport"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/udp_after_ipv4.json b/extras/packetforge/parsegraph/edges/udp_after_ipv4.json
new file mode 100644
index 00000000000..cb2f533bd39
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/udp_after_ipv4.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "udp",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "17"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/udp_after_ipv6.json b/extras/packetforge/parsegraph/edges/udp_after_ipv6.json
new file mode 100644
index 00000000000..fbbe972e4e5
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/udp_after_ipv6.json
@@ -0,0 +1,11 @@
+{
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "udp",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "17"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/edges/vlan_after_macvlan.json b/extras/packetforge/parsegraph/edges/vlan_after_macvlan.json
new file mode 100644
index 00000000000..5c0b81ebacf
--- /dev/null
+++ b/extras/packetforge/parsegraph/edges/vlan_after_macvlan.json
@@ -0,0 +1,15 @@
+{
+ "type" : "edge",
+ "start" : "mac,vlan",
+ "end" : "vlan",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "end.tpid"
+ },
+ {
+ "dst" : "end.ethertype",
+ "src" : "start.ethertype"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ah.json b/extras/packetforge/parsegraph/nodes/ah.json
new file mode 100644
index 00000000000..b3a32272f94
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ah.json
@@ -0,0 +1,26 @@
+{
+ "type" : "node",
+ "name" : "ah",
+ "layout" : [
+ {
+ "name" : "nextheader",
+ "size" : "8"
+ },
+ {
+ "name" : "payloadlength",
+ "size" : "8"
+ },
+ {
+ "name" : "reserved",
+ "size" : "16"
+ },
+ {
+ "name" : "spi",
+ "size" : "32"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "32"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/arpv4.json b/extras/packetforge/parsegraph/nodes/arpv4.json
new file mode 100644
index 00000000000..95c72c2b657
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/arpv4.json
@@ -0,0 +1,54 @@
+{
+ "type" : "node",
+ "name" : "arpv4",
+ "layout" : [
+ {
+ "name" : "htype",
+ "size" : "16",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "ptype",
+ "size" : "16",
+ "default" : "0x0800",
+ "readonly" : "true"
+ },
+ {
+ "name" : "hlen",
+ "size" : "8",
+ "default" : "6",
+ "readonly" : "true"
+ },
+ {
+ "name" : "plen",
+ "size" : "8",
+ "default" : "4",
+ "readonly" : "true"
+ },
+ {
+ "name" : "operation",
+ "size" : "16"
+ },
+ {
+ "name" : "sha",
+ "size" : "48",
+ "format" : "mac"
+ },
+ {
+ "name" : "spa",
+ "size" : "32",
+ "format" : "ipv4"
+ },
+ {
+ "name" : "tha",
+ "size" : "48",
+ "format" : "mac"
+ },
+ {
+ "name" : "tpa",
+ "size" : "32",
+ "format" : "ipv4"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/esp.json b/extras/packetforge/parsegraph/nodes/esp.json
new file mode 100644
index 00000000000..702408ab379
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/esp.json
@@ -0,0 +1,14 @@
+{
+ "type" : "node",
+ "name" : "esp",
+ "layout" : [
+ {
+ "name" : "spi",
+ "size" : "32"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "32"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/geneve.json b/extras/packetforge/parsegraph/nodes/geneve.json
new file mode 100644
index 00000000000..c31586b8f7a
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/geneve.json
@@ -0,0 +1,51 @@
+{
+ "type" : "node",
+ "name" : "geneve",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "2",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "optlen",
+ "size" : "6"
+ },
+ {
+ "name" : "o",
+ "size" : "1"
+ },
+ {
+ "name" : "c",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "6"
+ },
+ {
+ "name" : "protocoltype",
+ "size" : "16"
+ },
+ {
+ "name" : "vni",
+ "size" : "24"
+ },
+ {
+ "name" : "reserved",
+ "size" : "8"
+ },
+ {
+ "name" : "options",
+ "size" : "optlen<<5"
+ }
+ ],
+ "attributes" : [
+ {
+ "name" : "udpport",
+ "size" : "16",
+ "default" : "6081"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/gre.json b/extras/packetforge/parsegraph/nodes/gre.json
new file mode 100644
index 00000000000..db9579cd27f
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/gre.json
@@ -0,0 +1,56 @@
+{
+ "type" : "node",
+ "name" : "gre",
+ "layout" : [
+ {
+ "name" : "c",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "k",
+ "size" : "1"
+ },
+ {
+ "name" : "s",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "9"
+ },
+ {
+ "name" : "version",
+ "size" : "3",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "protocoltype",
+ "size" : "16"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16",
+ "optional" : "c=1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "16",
+ "optional" : "c=1"
+ },
+ {
+ "name" : "key",
+ "size" : "32",
+ "optional" : "k=1"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "32",
+ "optional" : "s=1"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/gtpc.json b/extras/packetforge/parsegraph/nodes/gtpc.json
new file mode 100644
index 00000000000..99ed70c06e8
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/gtpc.json
@@ -0,0 +1,48 @@
+{
+ "type" : "node",
+ "name" : "gtpc",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "3",
+ "default" : "2",
+ "readonly" : "true"
+ },
+ {
+ "name" : "p",
+ "size" : "1"
+ },
+ {
+ "name" : "t",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "3"
+ },
+ {
+ "name" : "messagetype",
+ "size" : "8"
+ },
+ {
+ "name" : "messagelength",
+ "size" : "16",
+ "default" : "4",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "teid",
+ "size" : "32",
+ "optional" : "t=1",
+ "increaselength" : "true"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "24"
+ },
+ {
+ "name" : "reserved",
+ "size" : "8"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/gtppsc.json b/extras/packetforge/parsegraph/nodes/gtppsc.json
new file mode 100644
index 00000000000..d6814be503c
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/gtppsc.json
@@ -0,0 +1,28 @@
+{
+ "type" : "node",
+ "name" : "gtppsc",
+ "layout" : [
+ {
+ "name" : "length",
+ "size" : "1",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "pdutype",
+ "size" : "4"
+ },
+ {
+ "name" : "reserved",
+ "size" : "4"
+ },
+ {
+ "name" : "reserved",
+ "size" : "2"
+ },
+ {
+ "name" : "qfi",
+ "size" : "6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/gtpu.json b/extras/packetforge/parsegraph/nodes/gtpu.json
new file mode 100644
index 00000000000..244e7d9ced0
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/gtpu.json
@@ -0,0 +1,63 @@
+{
+ "type" : "node",
+ "name" : "gtpu",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "3",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "pt",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "e",
+ "size" : "1"
+ },
+ {
+ "name" : "s",
+ "size" : "1"
+ },
+ {
+ "name" : "pn",
+ "size" : "1"
+ },
+ {
+ "name" : "messagetype",
+ "size" : "8"
+ },
+ {
+ "name" : "messagelength",
+ "size" : "16",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "teid",
+ "size" : "32"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "16",
+ "optional" : "e=1|s=1|pn=1",
+ "increaselength" : "true"
+ },
+ {
+ "name" : "npdunumber",
+ "size" : "8",
+ "optional" : "e=1|s=1|pn=1",
+ "increaselength" : "true"
+ },
+ {
+ "name" : "nextextentionheadertype",
+ "size" : "8",
+ "optional" : "e=1|s=1|pn=1",
+ "increaselength" : "true"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/icmp.json b/extras/packetforge/parsegraph/nodes/icmp.json
new file mode 100644
index 00000000000..4d96a34bd7e
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/icmp.json
@@ -0,0 +1,18 @@
+{
+ "type" : "node",
+ "name" : "icmp",
+ "layout" : [
+ {
+ "name" : "type",
+ "size" : "8"
+ },
+ {
+ "name" : "code",
+ "size" : "8"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/icmpv6.json b/extras/packetforge/parsegraph/nodes/icmpv6.json
new file mode 100644
index 00000000000..c4e89cbae81
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/icmpv6.json
@@ -0,0 +1,18 @@
+{
+ "type" : "node",
+ "name" : "icmpv6",
+ "layout" : [
+ {
+ "name" : "type",
+ "size" : "8"
+ },
+ {
+ "name" : "code",
+ "size" : "8"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ipv4.json b/extras/packetforge/parsegraph/nodes/ipv4.json
new file mode 100644
index 00000000000..297f60000cc
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ipv4.json
@@ -0,0 +1,76 @@
+{
+ "type" : "node",
+ "name" : "ipv4",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "4",
+ "default" : "4",
+ "readonly" : "true"
+ },
+ {
+ "name" : "ilh",
+ "size" : "4",
+ "default" : "5",
+ "readonly" : "true"
+ },
+ {
+ "name" : "dscp",
+ "size" : "6"
+ },
+ {
+ "name" : "ecn",
+ "size" : "2"
+ },
+ {
+ "name" : "totallength",
+ "size" : "16",
+ "default" : "20",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "identification",
+ "size" : "16"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "df",
+ "size" : "1"
+ },
+ {
+ "name" : "mf",
+ "size" : "1"
+ },
+ {
+ "name" : "fragmentoffset",
+ "size" : "13"
+ },
+ {
+ "name" : "ttl",
+ "size" : "8"
+ },
+ {
+ "name" : "protocol",
+ "size" : "8"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ },
+ {
+ "name" : "src",
+ "size" : "32",
+ "format" : "ipv4",
+ "default" : "1.1.1.1"
+ },
+ {
+ "name" : "dst",
+ "size" : "32",
+ "format" : "ipv4",
+ "default" : "2.2.2.2"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ipv6.json b/extras/packetforge/parsegraph/nodes/ipv6.json
new file mode 100644
index 00000000000..3de24a2d64b
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ipv6.json
@@ -0,0 +1,47 @@
+{
+ "type" : "node",
+ "name" : "ipv6",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "4",
+ "default" : "6",
+ "readonly" : "true"
+ },
+ {
+ "name" : "dscp",
+ "size" : "6"
+ },
+ {
+ "name" : "ecn",
+ "size" : "2"
+ },
+ {
+ "name" : "flowlabel",
+ "size" : "20"
+ },
+ {
+ "name" : "payloadlength",
+ "size" : "16",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "nextheader",
+ "size" : "8"
+ },
+ {
+ "name" : "hoplimit",
+ "size" : "8"
+ },
+ {
+ "name" : "src",
+ "size" : "128",
+ "format" : "ipv6"
+ },
+ {
+ "name" : "dst",
+ "size" : "128",
+ "format" : "ipv6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ipv6crh16.json b/extras/packetforge/parsegraph/nodes/ipv6crh16.json
new file mode 100644
index 00000000000..559ed870af2
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ipv6crh16.json
@@ -0,0 +1,36 @@
+{
+ "type" : "node",
+ "name" : "ipv6crh16",
+ "layout" : [
+ {
+ "name" : "nextheader",
+ "size" : "8"
+ },
+ {
+ "name" : "headerextlength",
+ "size" : "8"
+ },
+ {
+ "name" : "routingtype",
+ "size" : "8",
+ "default" : "5",
+ "readonly" : "true"
+ },
+ {
+ "name" : "segmentleft",
+ "size" : "8"
+ },
+ {
+ "name" : "sid0",
+ "size" : "16"
+ },
+ {
+ "name" : "sid1",
+ "size" : "16"
+ },
+ {
+ "name" : "sid16remains",
+ "size" : "headerextlength<<6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ipv6crh32.json b/extras/packetforge/parsegraph/nodes/ipv6crh32.json
new file mode 100644
index 00000000000..457050b9f13
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ipv6crh32.json
@@ -0,0 +1,32 @@
+{
+ "type" : "node",
+ "name" : "ipv6crh32",
+ "layout" : [
+ {
+ "name" : "nextheader",
+ "size" : "8"
+ },
+ {
+ "name" : "headerextlength",
+ "size" : "8"
+ },
+ {
+ "name" : "routingtype",
+ "size" : "8",
+ "default" : "6",
+ "readonly" : "true"
+ },
+ {
+ "name" : "segmentleft",
+ "size" : "8"
+ },
+ {
+ "name" : "sid0",
+ "size" : "32"
+ },
+ {
+ "name" : "sid32remains",
+ "size" : "headerextlength<<6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/ipv6srh.json b/extras/packetforge/parsegraph/nodes/ipv6srh.json
new file mode 100644
index 00000000000..48feaeb45fa
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/ipv6srh.json
@@ -0,0 +1,40 @@
+{
+ "type" : "node",
+ "name" : "ipv6srh",
+ "layout" : [
+ {
+ "name" : "nextheader",
+ "size" : "8"
+ },
+ {
+ "name" : "headerextlength",
+ "size" : "8"
+ },
+ {
+ "name" : "routingtype",
+ "size" : "8",
+ "default" : "4",
+ "readonly" : "true"
+ },
+ {
+ "name" : "segmentleft",
+ "size" : "8"
+ },
+ {
+ "name" : "lastentry",
+ "size" : "8"
+ },
+ {
+ "name" : "flags",
+ "size" : "8"
+ },
+ {
+ "name" : "tag",
+ "size" : "16"
+ },
+ {
+ "name" : "addresses",
+ "size" : "headerextlength<<6"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/l2tpv2ctl.json b/extras/packetforge/parsegraph/nodes/l2tpv2ctl.json
new file mode 100644
index 00000000000..7af22dd92fa
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/l2tpv2ctl.json
@@ -0,0 +1,74 @@
+{
+ "type" : "node",
+ "name" : "l2tpv2ctl",
+ "layout" : [
+ {
+ "name" : "t",
+ "size" : "1",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "l",
+ "size" : "1",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "2"
+ },
+ {
+ "name" : "s",
+ "size" : "1",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "o",
+ "size" : "1",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "p",
+ "size" : "1",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "4"
+ },
+ {
+ "name" : "version",
+ "size" : "4",
+ "default" : "2",
+ "readonly" : "true"
+ },
+ {
+ "name" : "length",
+ "size" : "16"
+ },
+ {
+ "name" : "tunnelid",
+ "size" : "16"
+ },
+ {
+ "name" : "sessionid",
+ "size" : "16"
+ },
+ {
+ "name" : "ns",
+ "size" : "16"
+ },
+ {
+ "name" : "nr",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/l2tpv2data.json b/extras/packetforge/parsegraph/nodes/l2tpv2data.json
new file mode 100644
index 00000000000..d9e8b006820
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/l2tpv2data.json
@@ -0,0 +1,79 @@
+{
+ "type" : "node",
+ "name" : "l2tpv2data",
+ "layout" : [
+ {
+ "name" : "t",
+ "size" : "1",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "l",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "2"
+ },
+ {
+ "name" : "s",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "o",
+ "size" : "1"
+ },
+ {
+ "name" : "p",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "4"
+ },
+ {
+ "name" : "version",
+ "size" : "4",
+ "default" : "2",
+ "readonly" : "true"
+ },
+ {
+ "name" : "length",
+ "size" : "16",
+ "optional" : "l=1"
+ },
+ {
+ "name" : "tunnelid",
+ "size" : "16"
+ },
+ {
+ "name" : "sessionid",
+ "size" : "16"
+ },
+ {
+ "name" : "ns",
+ "size" : "16",
+ "optional" : "s=1"
+ },
+ {
+ "name" : "nr",
+ "size" : "16",
+ "optional" : "s=1"
+ },
+ {
+ "name" : "offsetsize",
+ "size" : "16",
+ "optional" : "o=1"
+ },
+ {
+ "name" : "offsetpad",
+ "size" : "offsetsize<<3",
+ "optional" : "o=1"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/mac.json b/extras/packetforge/parsegraph/nodes/mac.json
new file mode 100644
index 00000000000..6fc49096889
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/mac.json
@@ -0,0 +1,22 @@
+{
+ "type" : "node",
+ "name" : "mac",
+ "layout" : [
+ {
+ "name" : "src",
+ "size" : "48",
+ "format" : "mac",
+ "default" : "00:00:00:00:00:01"
+ },
+ {
+ "name" : "dst",
+ "size" : "48",
+ "format" : "mac",
+ "default" : "00:00:00:00:00:02"
+ },
+ {
+ "name" : "ethertype",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/nvgre.json b/extras/packetforge/parsegraph/nodes/nvgre.json
new file mode 100644
index 00000000000..ec6812c8500
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/nvgre.json
@@ -0,0 +1,52 @@
+{
+ "type" : "node",
+ "name" : "nvgre",
+ "layout" : [
+ {
+ "name" : "c",
+ "size" : "1",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "1"
+ },
+ {
+ "name" : "k",
+ "size" : "1",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "s",
+ "size" : "1",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "9"
+ },
+ {
+ "name" : "version",
+ "size" : "3",
+ "default" : "0",
+ "readonly" : "true"
+ },
+ {
+ "name" : "protocoltype",
+ "size" : "16",
+ "default" : "0x6558",
+ "readonly" : "true"
+ },
+ {
+ "name" : "vsid",
+ "size" : "16"
+ },
+ {
+ "name" : "flowid",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/payload.json b/extras/packetforge/parsegraph/nodes/payload.json
new file mode 100644
index 00000000000..202116ff1ee
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/payload.json
@@ -0,0 +1,18 @@
+{
+ "type" : "node",
+ "name" : "payload",
+ "layout" : [
+ {
+ "name" : "data",
+ "size" : "bytes<<3",
+ "format" : "bytearray"
+ }
+ ],
+ "attributes" : [
+ {
+ "name" : "bytes",
+ "size" : "16",
+ "default" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/pfcp.json b/extras/packetforge/parsegraph/nodes/pfcp.json
new file mode 100644
index 00000000000..f1a6c1901bd
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/pfcp.json
@@ -0,0 +1,58 @@
+{
+ "type" : "node",
+ "name" : "pfcp",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "3",
+ "default" : "1",
+ "readonly" : "true"
+ },
+ {
+ "name" : "reserved",
+ "size" : "3"
+ },
+ {
+ "name" : "mp",
+ "size" : "1"
+ },
+ {
+ "name" : "s",
+ "size" : "1"
+ },
+ {
+ "name" : "messagetype",
+ "size" : "8"
+ },
+ {
+ "name" : "messagelength",
+ "size" : "16",
+ "default" : "8",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "seid",
+ "size" : "64",
+ "optional" : "s=1",
+ "increaselength" : "true"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "24"
+ },
+ {
+ "name" : "messagepriority",
+ "size" : "4",
+ "optional" : "mp=1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "4",
+ "optional" : "mp=0"
+ },
+ {
+ "name" : "reserved",
+ "size" : "4"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/sctp.json b/extras/packetforge/parsegraph/nodes/sctp.json
new file mode 100644
index 00000000000..58ef88cec55
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/sctp.json
@@ -0,0 +1,22 @@
+{
+ "type" : "node",
+ "name" : "sctp",
+ "layout" : [
+ {
+ "name" : "src",
+ "size" : "16"
+ },
+ {
+ "name" : "dst",
+ "size" : "16"
+ },
+ {
+ "name" : "veificationtag",
+ "size" : "16"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/tcp.json b/extras/packetforge/parsegraph/nodes/tcp.json
new file mode 100644
index 00000000000..7d0bac20ab1
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/tcp.json
@@ -0,0 +1,79 @@
+{
+ "type" : "node",
+ "name" : "tcp",
+ "layout" : [
+ {
+ "name" : "src",
+ "size" : "16"
+ },
+ {
+ "name" : "dst",
+ "size" : "16"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "32"
+ },
+ {
+ "name" : "acknowledgementnumber",
+ "size" : "32"
+ },
+ {
+ "name" : "dataoffset",
+ "size" : "4",
+ "default" : "5"
+ },
+ {
+ "name" : "reserved",
+ "size" : "3"
+ },
+ {
+ "name" : "ns",
+ "size" : "1"
+ },
+ {
+ "name" : "cwr",
+ "size" : "1"
+ },
+ {
+ "name" : "ece",
+ "size" : "1"
+ },
+ {
+ "name" : "urg",
+ "size" : "1"
+ },
+ {
+ "name" : "ack",
+ "size" : "1"
+ },
+ {
+ "name" : "psh",
+ "size" : "1"
+ },
+ {
+ "name" : "pst",
+ "size" : "1"
+ },
+ {
+ "name" : "syn",
+ "size" : "1"
+ },
+ {
+ "name" : "fin",
+ "size" : "1"
+ },
+ {
+ "name" : "windowsize",
+ "size" : "16"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ },
+ {
+ "name" : "urgentpointer",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/udp.json b/extras/packetforge/parsegraph/nodes/udp.json
new file mode 100644
index 00000000000..75a95aec4a9
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/udp.json
@@ -0,0 +1,24 @@
+{
+ "type" : "node",
+ "name" : "udp",
+ "layout" : [
+ {
+ "name" : "src",
+ "size" : "16"
+ },
+ {
+ "name" : "dst",
+ "size" : "16"
+ },
+ {
+ "name" : "length",
+ "size" : "16",
+ "default" : "8",
+ "autoincrease" : "true"
+ },
+ {
+ "name" : "checksum",
+ "size" : "16"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/vlan.json b/extras/packetforge/parsegraph/nodes/vlan.json
new file mode 100644
index 00000000000..4a87a14d12d
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/vlan.json
@@ -0,0 +1,30 @@
+{
+ "type" : "node",
+ "name" : "vlan",
+ "layout" : [
+ {
+ "name" : "pcp",
+ "size" : "3"
+ },
+ {
+ "name" : "dei",
+ "size" : "1"
+ },
+ {
+ "name" : "vid",
+ "size" : "12"
+ },
+ {
+ "name" : "ethertype",
+ "size" : "16",
+ "readonly" : "true"
+ }
+ ],
+ "attributes" : [
+ {
+ "name" : "tpid",
+ "size" : "16",
+ "default" : "0x8100"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/vxlan.json b/extras/packetforge/parsegraph/nodes/vxlan.json
new file mode 100644
index 00000000000..f797075aa84
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/vxlan.json
@@ -0,0 +1,33 @@
+{
+ "type" : "node",
+ "name" : "vxlan",
+ "layout" : [
+ {
+ "name" : "reserved",
+ "size" : "4"
+ },
+ {
+ "name" : "i",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "11"
+ },
+ {
+ "name" : "vni",
+ "size" : "24"
+ },
+ {
+ "name" : "reserved",
+ "size" : "8"
+ }
+ ],
+ "attributes" : [
+ {
+ "name" : "udpport",
+ "size" : "16",
+ "default" : "4789"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/nodes/vxlangpe.json b/extras/packetforge/parsegraph/nodes/vxlangpe.json
new file mode 100644
index 00000000000..711e8bbcd04
--- /dev/null
+++ b/extras/packetforge/parsegraph/nodes/vxlangpe.json
@@ -0,0 +1,53 @@
+{
+ "type" : "node",
+ "name" : "vxlangpe",
+ "layout" : [
+ {
+ "name" : "reserved",
+ "size" : "2"
+ },
+ {
+ "name" : "version",
+ "size" : "2"
+ },
+ {
+ "name" : "i",
+ "size" : "1"
+ },
+ {
+ "name" : "p",
+ "size" : "1"
+ },
+ {
+ "name" : "b",
+ "size" : "1"
+ },
+ {
+ "name" : "o",
+ "size" : "1"
+ },
+ {
+ "name" : "reserved",
+ "size" : "16"
+ },
+ {
+ "name" : "nextprotocol",
+ "size" : "8"
+ },
+ {
+ "name" : "vni",
+ "size" : "24"
+ },
+ {
+ "name" : "reserved",
+ "size" : "8"
+ }
+ ],
+ "attributes" : [
+ {
+ "name" : "udpport",
+ "size" : "16",
+ "default" : "4790"
+ }
+ ]
+}
diff --git a/extras/packetforge/parsegraph/samples/mac_ipv4.json b/extras/packetforge/parsegraph/samples/mac_ipv4.json
new file mode 100644
index 00000000000..c0a8d18177d
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_ipv4.json
@@ -0,0 +1,24 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "1.1.1.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "2.2.2.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ }
+ ],
+ "actions" : "redirect-to-queue 3"
+}
diff --git a/extras/packetforge/parsegraph/samples/mac_ipv4_udp.json b/extras/packetforge/parsegraph/samples/mac_ipv4_udp.json
new file mode 100644
index 00000000000..7df27cfdf12
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_ipv4_udp.json
@@ -0,0 +1,27 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "1.1.1.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "2.2.2.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ },
+ {
+ "header" : "udp"
+ }
+ ],
+ "actions" : "rss"
+} \ No newline at end of file
diff --git a/extras/packetforge/parsegraph/samples/mac_ipv4_udp_gtpu_gtppsc_ipv4.json b/extras/packetforge/parsegraph/samples/mac_ipv4_udp_gtpu_gtppsc_ipv4.json
new file mode 100644
index 00000000000..39a8d6159b4
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_ipv4_udp_gtpu_gtppsc_ipv4.json
@@ -0,0 +1,43 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4"
+ },
+ {
+ "header" : "udp"
+ },
+ {
+ "header" : "gtpu",
+ "fields" : [
+ {
+ "name" : "teid",
+ "value" : "32",
+ "mask" : "0xffffffff"
+ }
+ ]
+ },
+ {
+ "header" : "gtppsc"
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "1.1.1.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "2.2.2.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ }
+ ],
+ "actions" : "rss"
+}
diff --git a/extras/packetforge/parsegraph/samples/mac_ipv4_udp_vxlan_mac_ipv4.json b/extras/packetforge/parsegraph/samples/mac_ipv4_udp_vxlan_mac_ipv4.json
new file mode 100644
index 00000000000..346dcd27fcb
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_ipv4_udp_vxlan_mac_ipv4.json
@@ -0,0 +1,43 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4"
+ },
+ {
+ "header" : "udp"
+ },
+ {
+ "header" : "vxlan",
+ "fields" : [
+ {
+ "name" : "vni",
+ "value" : "100",
+ "mask" : "0xffffff"
+ }
+ ]
+ },
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "1.1.1.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "2.2.2.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ }
+ ],
+ "actions" : "rss"
+}
diff --git a/extras/packetforge/parsegraph/samples/mac_ipv6.json b/extras/packetforge/parsegraph/samples/mac_ipv6.json
new file mode 100644
index 00000000000..17b6490c099
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_ipv6.json
@@ -0,0 +1,44 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "00:00:00:00:00:01",
+ "mask" : "ff:ff:ff:ff:ff:ff"
+ },
+ {
+ "name" : "dst",
+ "value" : "00:00:00:00:00:02",
+ "mask" : "ff:ff:ff:ff:ff:ff"
+ },
+ {
+ "name" : "ethertype",
+ "value" : "0x2345"
+ }
+ ]
+ },
+ {
+ "header" : "ipv6",
+ "fields" : [
+ {
+ "name" : "flowlabel",
+ "value" : "1"
+ },
+ {
+ "name" : "src",
+ "value" : "0001:0002:0003:0004:0005:0006:0007:0008",
+ "mask" : "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ },
+ {
+ "name" : "dst",
+ "value" : "0001:0002:0003:0004:0005:0006:0007:0008",
+ "mask" : "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ }
+ ]
+ }
+ ],
+ "actions" : "rss"
+}
diff --git a/extras/packetforge/parsegraph/samples/mac_vlan_ipv4.json b/extras/packetforge/parsegraph/samples/mac_vlan_ipv4.json
new file mode 100644
index 00000000000..7905133a8e0
--- /dev/null
+++ b/extras/packetforge/parsegraph/samples/mac_vlan_ipv4.json
@@ -0,0 +1,34 @@
+{
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "vlan",
+ "fields" : [
+ {
+ "name" : "vid",
+ "value" : "100",
+ "mask" : "0xfff"
+ }
+ ]
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "1.1.1.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "2.2.2.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ }
+ ],
+ "actions" : "rss"
+}
diff --git a/extras/packetforge/parsegraph/spec.md b/extras/packetforge/parsegraph/spec.md
new file mode 100644
index 00000000000..93ff7791f20
--- /dev/null
+++ b/extras/packetforge/parsegraph/spec.md
@@ -0,0 +1,533 @@
+#### Packet Forger JSON Specification Rev 0.1
+
+### 0. Change Logs
+
+2021-10, initialized by Zhang, Qi
+
+### 1. Parse Graph
+
+A Parse Graph is a unidirectional graph. It is consist of a set of nodes and edges. A node represent a network protocol header, and an edge represent the linkage of two protocol headers which is adjacent in the packet. An example of a parse graph have 5 nodes and 6 edges.
+
+[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBKChNQUMpKSAtLT4gQigoSVB2NCkpXG4gICAgQSgoTUFDKSkgLS0-IEMoKElQdjYpKVxuICAgIEIgLS0-IEQoKFRDUCkpXG4gICAgQyAtLT4gRCgoVENQKSlcbiAgICBCIC0tPiBFKChVRFApKVxuICAgIEMgLS0-IEUoKFVEUCkpXG4gICAgIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRhcmsifSwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/edit#eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBKChNQUMpKSAtLT4gQigoSVB2NCkpXG4gICAgQSgoTUFDKSkgLS0-IEMoKElQdjYpKVxuICAgIEIgLS0-IEQoKFRDUCkpXG4gICAgQyAtLT4gRCgoVENQKSlcbiAgICBCIC0tPiBFKChVRFApKVxuICAgIEMgLS0-IEUoKFVEUCkpXG4gICAgIiwibWVybWFpZCI6IntcbiAgXCJ0aGVtZVwiOiBcImRhcmtcIlxufSIsInVwZGF0ZUVkaXRvciI6ZmFsc2UsImF1dG9TeW5jIjp0cnVlLCJ1cGRhdGVEaWFncmFtIjpmYWxzZX0)
+
+A Node or an Edge is described by a json object. There is no json representation for a parse graph, software should load all json objects of nodes and edges then build the parse graph logic in memory.
+
+### 2. Node
+
+A json object of Node will include below properties:
+
+* **type**
+
+ This should always be "node".
+
+* **name**
+
+ This is the name of the protocol.
+
+* **layout**
+
+ This is an array of fields in the protocol header which also imply the bit order. For example, json object of mac header as below:
+ ```
+ {
+ "type" : "node",
+ "name" : "mac",
+ "layout" : [
+ {
+ "name" : "src",
+ "size" : "48",
+ "format" : "mac",
+ },
+ {
+ "name" : "dst",
+ "size" : "48",
+ "format" : "mac",
+ },
+ {
+ "name" : "ethertype",
+ "size" : "16",
+ }
+ ]
+ }
+ ```
+
+ For each field, there are properties can be defined:
+
+ * **name**
+
+ The name of the field, typically it should be unique to all fields in the same node, except when it is "reserved".
+
+ * **size**
+
+ Size of the field, note, the unit is "bit" but not "byte".
+ Sometime a field's size can be decided by another field's value, for example, a geneve header's "options" field's size is decided by "optlen" field's value, so we have below:
+
+ ```
+ "name" : "geneve",
+ "layout" : [
+
+ ......
+
+ {
+ "name" : "reserved",
+ "size" : "8"
+ },
+ {
+ "name" : "options",
+ "size" : "optlen<<5"
+ }
+ ],
+ ```
+ Since when "optlen" increases 1 which means 4 bytes (32 bits) increase of "options"'s size so the bit value should shift left 5.
+
+ * **format**
+
+ Defined the input string format of the value, all formats are described in the section **Input Format** which also described the default format if it is not explicitly defined.
+
+ * **default**
+
+ Defined the default value of the field when a protocol header instance is created by the node. If not defined, the default value is always 0. The default value can be overwritten when forging a packet with specific value of the field. For example, we defined the default ipv4 address as below:
+
+ ```
+ "name" : "ipv4",
+ "layout" : [
+
+ ......
+
+ {
+ "name" : "src",
+ "size" : "32",
+ "format" : "ipv4",
+ "default" : "1.1.1.1"
+ },
+ {
+ "name" : "dst",
+ "size" : "32",
+ "format" : "ipv4",
+ "default" : "2.2.2.2"
+ }
+ ]
+ ```
+
+ * **readonly**
+
+ Define if a field is read only or not, typically it will be used together with "default". For example, the version of IPv4 header should be 4 and can't be overwritten.
+
+ ```
+ "name" : "ipv4",
+ "layout" : [
+ {
+ "name" : "version",
+ "size" : "4",
+ "default" : "4",
+ "readonly" : "true"
+ },
+ ......
+ ],
+ ```
+ A reserved field implies it is "readonly" and should always be 0.
+
+ * **optional**
+
+ A field could be optional depends on some flag as another field. For example, the GRE header has couple optional fields.
+
+ ```
+ "name" : "gre",
+ "layout" : [
+ {
+ "name" : "c",
+ "size" : "1",
+ },
+ {
+ "name" : "reserved",
+ "size" : "1",
+ },
+ {
+ "name" : "k",
+ "size" : "1",
+ },
+ {
+ "name" : "s",
+ "size" : "1",
+ },
+
+ ......
+
+ {
+ "name" : "checksum",
+ "size" : "16",
+ "optional" : "c=1",
+ },
+ {
+ "name" : "reserved",
+ "size" : "16",
+ "optional" : "c=1",
+ },
+ {
+ "name" : "key",
+ "size" : "32",
+ "optional" : "k=1"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "32",
+ "optional" : "s=1"
+ }
+ ]
+ ```
+
+ The expresion of an optional field can use "**&**" or "**|**" combine multiple conditions, for example for gtpu header, we have below optional fields.
+
+ ```
+ "name" : "gtpu",
+ "layout" : [
+
+ ......
+
+ {
+ "name" : "e",
+ "size" : "1"
+ },
+ {
+ "name" : "s",
+ "size" : "1"
+ },
+ {
+ "name" : "pn",
+ "size" : "1"
+ },
+
+ ......
+
+ {
+ "name" : "teid",
+ "size" : "16"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "16",
+ "optional" : "e=1|s=1|pn=1",
+ },
+
+ ......
+ ]
+
+ ```
+
+ * **autoincrease**
+
+ Some field's value cover the length of the payload or size of an optional field in the same header, so it should be auto increased during packet forging. For example the "totallength" of ipv4 header is a autoincrease feild.
+
+ ```
+ "name" : "ipv4",
+ "layout" : [
+
+ ......
+
+ {
+ "name" : "totallength",
+ "size" : "16",
+ "default" : "20",
+ "autoincrease" : "true",
+ },
+
+ ......
+
+ ]
+ ```
+
+ A field which is autoincrease also imply its readonly.
+
+ * **increaselength**
+
+ Typically this should only be enabled for an optional field to trigger another field's autoincrease. For example, the gtpc's "messagelength" field cover all the data start from field "teid", so its default size is 4 bytes which cover sequencenumber + 8 reserved bit, and should be increased if "teid" exist or any payload be appended.
+
+ ```
+ "name" : "gtpc",
+ "layout" : [
+
+ ......
+
+ {
+ "name" : "messagelength",
+ "size" : "16",
+ "default" : "4",
+ "autoincrease" : "true",
+ },
+ {
+ "name" : "teid",
+ "size" : "32",
+ "optional" : "t=1",
+ "increaselength" : "true"
+ },
+ {
+ "name" : "sequencenumber",
+ "size" : "24",
+ },
+ {
+ "name" : "reserved",
+ "size" : "8",
+ }
+ ]
+ ```
+
+* **attributes**
+
+ This defines an array of attributes, the attribute does not define the data belongs to current protocol header, but it impact the behaviour during applying actions of an edge when the protocol header is involved. For example, a geneve node has attribute "udpport" which define the udp tunnel port, so when it is appended after a udp header, the udp header's dst port is expected to be changed to this value.
+
+ ```
+ "name" : "geneve",
+
+ "fields" : [
+
+ ......
+
+ ],
+ "attributes" : [
+ {
+ "name" : "udpport",
+ "size" : "16",
+ "default" : "6081"
+ }
+ ]
+ ```
+
+ An attribute can only have below properties which take same effect when they are in field.
+
+ * name
+ * size (must be fixed value)
+ * default
+ * format
+
+### 3. Edge
+
+ A json object of Edge will include below properties:
+
+ * **type**
+
+ This should always be "edge".
+
+ * **start**
+
+ This is the start node of the edge.
+
+ * **end**
+
+ This is the end node of the edge.
+
+ * **actions**
+
+ This is an array of actions the should be applied during packet forging.
+ For example, when append a ipv4 headers after a mac header, the "ethertype" field of mac should be set to "0x0800":
+
+ ```
+ {
+ "type" : "edge",
+ "start" : "mac",
+ "end" : "ipv4",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "0x0800"
+ }
+ ]
+ }
+ ```
+ Each action should have two properties:
+
+ * **dst**
+
+ This describe the target field to set, it is formatted as <node>.<field>
+ node must be "start" or "end".
+
+ * **src**
+
+ This describe the value to set, it could be a const value or same format as dst's.
+ For example when append a vlan header after mac, we will have below actions:
+
+ ```
+ {
+ "type" : "edge",
+ "start" : "mac",
+ "end" : "vlan",
+ "actions" : [
+ {
+ "dst" : "start.ethertype",
+ "src" : "end.tpid"
+ },
+ {
+ "dst" : "end.ethertype",
+ "src" : "start.ethertype"
+ }
+ ]
+ }
+ ```
+
+
+ To avoid duplication, multiple edges can be aggregate into the one json object if there actions are same. So, multiple node name can be added to **start** or **end** with seperateor "**,**".
+
+ For example, all ipv6 and ipv6 extention header share the same actions when append a udp header
+
+ ```
+ {
+ "type" : "edge",
+ "start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
+ "end" : "udp",
+ "actions" : [
+ {
+ "dst" : "start.nextheader",
+ "src" : "17"
+ }
+ ]
+ }
+ ```
+
+ Another examples is gre and nvgre share the same actions when be appanded after a ipv4 header:
+ ```
+ {
+ "type" : "edge",
+ "start" : "ipv4",
+ "end" : "gre,nvgre",
+ "actions" : [
+ {
+ "dst" : "start.protocol",
+ "src" : "47"
+ }
+ ]
+ }
+ ```
+
+### 4. Path
+
+A path defines a sequence of nodes which is the input parameter for a packet forging, a packet forging should fail if the path can't be recognised as a subgraph of the parser graph.
+
+A json object of a path should include below properties:
+
+* **type**
+
+ This should always be "path".
+
+* **stack**
+
+ This is an array of node configurations which also imply the protocol header sequence of a packet. Below is an example to forge an ipv4 / udp packet with default value.
+
+ ```
+ {
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac"
+ },
+ {
+ "header" : "ipv4"
+ },
+ {
+ "header" : "udp"
+ },
+ ]
+ }
+ ```
+
+ A node configuration can have below properties:
+
+ * **header**
+
+ This is a protocol name (a node name).
+
+ * **fields**
+
+ This is an array of 3 member tuples:
+
+ * **name**
+
+ The name of the field or attribute that belongs to the node, note a readonly field should not be selected.
+
+ * **value**
+
+ The value to set the field or attribute.
+
+ * **mask**
+
+ This is optional, if it is not defined, corresponding bit of the mask should be set to 0, and it should be ignored for an attribute.
+
+* **actions**
+
+ This is optional. When this json file is the input of flow adding commands, it can be used directly as the flow rule's action.
+
+ An example to forge a ipv4 packet with src ip address 192.168.0.1 and dst ip address 192.168.0.2, also take ip address as mask.
+
+ ```
+ {
+ "type" : "path",
+ "stack" : [
+ {
+ "header" : "mac",
+ },
+ {
+ "header" : "ipv4",
+ "fields" : [
+ {
+ "name" : "src",
+ "value" : "192.168.0.1",
+ "mask" : "255.255.255.255"
+ },
+ {
+ "name" : "dst",
+ "value" : "192.168.0.2",
+ "mask" : "255.255.255.255"
+ }
+ ]
+ }
+ ],
+ "actions" : "redirect-to-queue 3"
+ }
+ ```
+
+
+### 5. Input Format
+
+Every field or attribute is associated with an **Input Format**, so the software can figure out how to parse default value in the node or a config value in the path.
+
+Currently we have 8 predefined format and don't support customised format.
+
+* **u8**
+
+ accept number from 0 to 255 or hex from 0x0 to 0xff.
+
+* **u16**
+
+ accept number from 0 to 65535 or hex from 0x0 to 0xffff.
+
+* **u32**
+
+ accept number from 0 to 4294967295 or hex from 0x0 to 0xffffffff
+
+* **u64**
+
+ accept number from 0 to 2^64 -1 or hex from 0x0 to 0xffffffffffffffff
+
+* **mac**
+
+ accept xx:xx:xx:xx:xx:xx , x in hex from 0 to f
+
+* **ipv4**
+
+ accept n.n.n.n , n from 0 to 255
+
+* **ipv6**
+
+ accept xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx, x in hex from 0 to f
+
+* **bytearray**
+
+ accept u8,u8,u8.....
+
+If format is not defined for a field or attribute, the default format will be selected base on size as below, and the MSB should be ignored by software if the value exceeds the limitation.
+
+| Size | Default Format |
+| ------------- | -------------- |
+| 1 - 8 | u8 |
+| 9 - 16 | u16 |
+| 17 - 32 | u32 |
+| 33 - 64 | u64 |
+| > 64 | bytearray |
+| variable size | bytearray |