packetforge: add packetforge for generic flow to extras
Add a new tool packetforge to extras. This tool is to support generic flow. Packetforge is a library to translate naming or json profile format flow pattern to the required input of generic flow, i.e. spec and mask. Using python script flow_create.py, it can add and enable a new flow rule for an interface via flow VAPI, and can delete an existed flow rule as well. Command examples are shown below. Json profile examples can be found in ./parsegraph/samples. Naming format input: python flow_create.py --add -p "mac()/ipv4(src=1.1.1.1,dst=2.2.2.2)/udp()" -a "redirect-to-queue 3" -i 1 python flow_create.py --del -i 1 -I 0 Json profile format input: python flow_create.py -f "./flow_rule_examples/mac_ipv4.json" -i 1 With this command, flow rule can be added or deleted, and the flow entry can be listed with "show flow entry" command in VPP CLI. Packetforge is based on a parsegraph. The parsegraph can be built by users. A Spec can be found in ./parsegraph as guidance. More details about packetforge are in README file. Type: feature Signed-off-by: Ting Xu <ting.xu@intel.com> Change-Id: Ia9f539741c5dca27ff236f2bcc493c5dd48c0df1
This commit is contained in:
@ -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>
|
||||
|
92
extras/packetforge/Edge.py
Normal file
92
extras/packetforge/Edge.py
Normal file
@ -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
|
55
extras/packetforge/EdgeAction.py
Normal file
55
extras/packetforge/EdgeAction.py
Normal file
@ -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
|
162
extras/packetforge/ExpressionConverter.py
Normal file
162
extras/packetforge/ExpressionConverter.py
Normal file
@ -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
|
45
extras/packetforge/ForgeResult.py
Normal file
45
extras/packetforge/ForgeResult.py
Normal file
@ -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
|
12
extras/packetforge/InputFormat.py
Normal file
12
extras/packetforge/InputFormat.py
Normal file
@ -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
|
202
extras/packetforge/LICENSE.txt
Normal file
202
extras/packetforge/LICENSE.txt
Normal file
@ -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.
|
||||
|
62
extras/packetforge/Node.py
Normal file
62
extras/packetforge/Node.py
Normal file
@ -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
|
65
extras/packetforge/NodeAttribute.py
Normal file
65
extras/packetforge/NodeAttribute.py
Normal file
@ -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
|
86
extras/packetforge/NodeField.py
Normal file
86
extras/packetforge/NodeField.py
Normal file
@ -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
|
179
extras/packetforge/ParseGraph.py
Normal file
179
extras/packetforge/ParseGraph.py
Normal file
@ -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
|
35
extras/packetforge/Path.py
Normal file
35
extras/packetforge/Path.py
Normal file
@ -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
|
39
extras/packetforge/PathNode.py
Normal file
39
extras/packetforge/PathNode.py
Normal file
@ -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
|
37
extras/packetforge/PathNodeField.py
Normal file
37
extras/packetforge/PathNodeField.py
Normal file
@ -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
|
387
extras/packetforge/ProtocolHeader.py
Normal file
387
extras/packetforge/ProtocolHeader.py
Normal file
File diff suppressed because it is too large
Load Diff
29
extras/packetforge/ProtocolHeaderAttribute.py
Normal file
29
extras/packetforge/ProtocolHeaderAttribute.py
Normal file
@ -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
|
48
extras/packetforge/ProtocolHeaderField.py
Normal file
48
extras/packetforge/ProtocolHeaderField.py
Normal file
@ -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
|
71
extras/packetforge/README.rst
Normal file
71
extras/packetforge/README.rst
Normal file
@ -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.
|
11
extras/packetforge/StringFormat.py
Normal file
11
extras/packetforge/StringFormat.py
Normal file
@ -0,0 +1,11 @@
|
||||
import enum
|
||||
|
||||
|
||||
class StringFormat(enum.Enum):
|
||||
mac = 0
|
||||
ipv4 = 1
|
||||
ipv6 = 2
|
||||
u8 = 3
|
||||
u16 = 4
|
||||
u32 = 5
|
||||
u64 = 6
|
157
extras/packetforge/flow_create.py
Normal file
157
extras/packetforge/flow_create.py
Normal file
@ -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
|
200
extras/packetforge/packetforge.py
Normal file
200
extras/packetforge/packetforge.py
Normal file
@ -0,0 +1,200 @@
|
||||
# Copyright (c) 2022 Intel and/or its affiliates.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from vpp_papi.vpp_papi import VppEnum
|
||||
from ParseGraph import *
|
||||
from Path import *
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
|
||||
parsegraph_path = os.getcwd() + "/parsegraph"
|
||||
|
||||
|
||||
def Forge(pattern, actions, file_flag):
|
||||
pg = ParseGraph.Create(parsegraph_path)
|
||||
if pg == None:
|
||||
print("error: create parsegraph failed")
|
||||
return None
|
||||
|
||||
if not file_flag:
|
||||
token = ParsePattern(pattern)
|
||||
if token == None:
|
||||
return None
|
||||
else:
|
||||
if not os.path.exists(pattern):
|
||||
print("error: file not exist '%s' " % (pattern))
|
||||
return
|
||||
f = open(pattern, "r", encoding="utf-8")
|
||||
token = json.load(f)
|
||||
if "actions" in token:
|
||||
actions = token["actions"]
|
||||
|
||||
path = Path.Create(token)
|
||||
if path == None:
|
||||
print("error: path not exit")
|
||||
return None
|
||||
|
||||
result = pg.Forge(path)
|
||||
if result == None:
|
||||
print("error: result not available")
|
||||
return None
|
||||
|
||||
spec, mask = GetBinary(result.ToJSON())
|
||||
|
||||
# create generic flow
|
||||
my_flow = {
|
||||
"type": VppEnum.vl_api_flow_type_v2_t.FLOW_TYPE_GENERIC_V2,
|
||||
"flow": {
|
||||
"generic": {
|
||||
"pattern": {"spec": bytes(spec.encode()), "mask": bytes(mask.encode())}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# update actions entry
|
||||
my_flow = GetAction(actions, my_flow)
|
||||
|
||||
return my_flow
|
||||
|
||||
|
||||
def GetAction(actions, flow):
|
||||
if len(actions.split(" ")) > 1:
|
||||
type = actions.split(" ")[0]
|
||||
else:
|
||||
type = actions
|
||||
|
||||
if type == "mark":
|
||||
flow.update(
|
||||
{
|
||||
"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_MARK_V2,
|
||||
"mark_flow_id": int(actions.split(" ")[1]),
|
||||
}
|
||||
)
|
||||
elif type == "next-node":
|
||||
flow.update(
|
||||
{
|
||||
"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_NODE_V2,
|
||||
"redirect_node_index": int(actions.split(" ")[1]),
|
||||
}
|
||||
)
|
||||
elif type == "buffer-advance":
|
||||
flow.update(
|
||||
{
|
||||
"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_BUFFER_ADVANCE_V2,
|
||||
"buffer_advance": int(actions.split(" ")[1]),
|
||||
}
|
||||
)
|
||||
elif type == "redirect-to-queue":
|
||||
flow.update(
|
||||
{
|
||||
"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_QUEUE_V2,
|
||||
"redirect_queue": int(actions.split(" ")[1]),
|
||||
}
|
||||
)
|
||||
elif type == "rss":
|
||||
flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2})
|
||||
elif type == "rss-queues":
|
||||
queue_end = int(actions.split(" ")[-1])
|
||||
queue_start = int(actions.split(" ")[-3])
|
||||
flow.update(
|
||||
{
|
||||
"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2,
|
||||
"queue_index": queue_start,
|
||||
"queue_num": queue_end - queue_start + 1,
|
||||
}
|
||||
)
|
||||
elif type == "drop":
|
||||
flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_DROP_V2})
|
||||
|
||||
return flow
|
||||
|
||||
|
||||
def GetBinary(flow_info):
|
||||
spec = "".join(flow_info["Packet"])
|
||||
mask = "".join(flow_info["Mask"])
|
||||
return spec, mask
|
||||
|
||||
|
||||
def ParseFields(item):
|
||||
# get protocol name
|
||||
prot = item.split("(")[0]
|
||||
stack = {"header": prot}
|
||||
# get fields contents
|
||||
fields = re.findall(r"[(](.*?)[)]", item)
|
||||
if not fields:
|
||||
print("error: invalid pattern")
|
||||
return None
|
||||
if fields == [""]:
|
||||
return stack
|
||||
stack.update({"fields": []})
|
||||
return ParseStack(stack, fields[0].split(","))
|
||||
|
||||
|
||||
def GetMask(item):
|
||||
if "format" in item:
|
||||
format = item["format"]
|
||||
if format == "mac":
|
||||
mask = "ff.ff.ff.ff.ff.ff"
|
||||
elif format == "ipv4":
|
||||
mask = "255.255.255.255"
|
||||
elif format == "ipv6":
|
||||
mask = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
|
||||
return mask
|
||||
if "size" in item:
|
||||
mask = str((1 << int(item["size"])) - 1)
|
||||
else:
|
||||
print("mask error")
|
||||
return mask
|
||||
|
||||
|
||||
# parse protocol headers and its fields. Available fields are defined in corresponding nodes.
|
||||
def ParseStack(stack, fields):
|
||||
prot = stack["header"]
|
||||
node_path = parsegraph_path + "/nodes/" + prot + ".json"
|
||||
if not os.path.exists(node_path):
|
||||
print("error file not exist '%s' " % (node_path))
|
||||
return None
|
||||
f = open(node_path, "r", encoding="utf-8")
|
||||
nodeinfo = json.load(f)
|
||||
for field in fields:
|
||||
fld_name = field.split("=")[0].strip()
|
||||
fld_value = (
|
||||
field.split("=")[-1].strip() if (len(field.split("=")) >= 2) else None
|
||||
)
|
||||
for item in nodeinfo["layout"]:
|
||||
if fld_name == item["name"]:
|
||||
mask = GetMask(item)
|
||||
stack["fields"].append(
|
||||
{"name": fld_name, "value": fld_value, "mask": mask}
|
||||
)
|
||||
break
|
||||
if not stack["fields"]:
|
||||
print("warning: invalid field '%s'" % (fld_name))
|
||||
return None
|
||||
|
||||
return stack
|
||||
|
||||
|
||||
def ParsePattern(pattern):
|
||||
# create json template
|
||||
json_tmp = {"type": "path", "stack": []}
|
||||
|
||||
items = pattern.split("/")
|
||||
for item in items:
|
||||
stack = ParseFields(item)
|
||||
if stack == None:
|
||||
return None
|
||||
json_tmp["stack"].append(stack)
|
||||
|
||||
return json_tmp
|
2
extras/packetforge/parsegraph/.gitattributes
vendored
Normal file
2
extras/packetforge/parsegraph/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
11
extras/packetforge/parsegraph/edges/ah_after_ipv4.json
Normal file
11
extras/packetforge/parsegraph/edges/ah_after_ipv4.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv4",
|
||||
"end" : "ah",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.protocol",
|
||||
"src" : "51"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/ah_after_ipv6.json
Normal file
11
extras/packetforge/parsegraph/edges/ah_after_ipv6.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
|
||||
"end" : "ah",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.nextheader",
|
||||
"src" : "51"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json
Normal file
11
extras/packetforge/parsegraph/edges/arpv4_after_macvlan.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "mac,vlan",
|
||||
"end" : "arpv4",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.ethertype",
|
||||
"src" : "0x0806"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/esp_after_ipv4.json
Normal file
11
extras/packetforge/parsegraph/edges/esp_after_ipv4.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv4",
|
||||
"end" : "esp",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.protocol",
|
||||
"src" : "50"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/esp_after_ipv6.json
Normal file
11
extras/packetforge/parsegraph/edges/esp_after_ipv6.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
|
||||
"end" : "esp",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.nextheader",
|
||||
"src" : "50"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/gre_after_ipv4.json
Normal file
11
extras/packetforge/parsegraph/edges/gre_after_ipv4.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv4",
|
||||
"end" : "gre,nvgre",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.protocol",
|
||||
"src" : "47"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/gre_after_ipv6.json
Normal file
11
extras/packetforge/parsegraph/edges/gre_after_ipv6.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "ipv6,ipv6srh,ipv6crh16,ipv6crh32",
|
||||
"end" : "gre,nvgre",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.nextheader",
|
||||
"src" : "47"
|
||||
}
|
||||
]
|
||||
}
|
11
extras/packetforge/parsegraph/edges/gtpc_after_udp.json
Normal file
11
extras/packetforge/parsegraph/edges/gtpc_after_udp.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"type" : "edge",
|
||||
"start" : "udp",
|
||||
"end" : "gtpc",
|
||||
"actions" : [
|
||||
{
|
||||
"dst" : "start.dst",
|
||||
"src" : "2123"
|
||||
}
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user