packetforge: add option to show spec and mask only
[vpp.git] / extras / packetforge / packetforge.py
1 # Copyright (c) 2022 Intel and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 from vpp_papi.vpp_papi import VppEnum
15 from ParseGraph import *
16 from Path import *
17 import json
18 import re
19 import os
20
21 parsegraph_path = os.getcwd() + "/parsegraph"
22
23
24 def Forge(pattern, actions, file_flag, show_result_only):
25     pg = ParseGraph.Create(parsegraph_path)
26     if pg == None:
27         print("error: create parsegraph failed")
28         return None
29
30     if not file_flag:
31         token = ParsePattern(pattern)
32         if token == None:
33             return None
34     else:
35         if not os.path.exists(pattern):
36             print("error: file not exist '%s' " % (pattern))
37             return
38         f = open(pattern, "r", encoding="utf-8")
39         token = json.load(f)
40         if "actions" in token:
41             actions = token["actions"]
42
43     path = Path.Create(token)
44     if path == None:
45         print("error: path not exit")
46         return None
47
48     result = pg.Forge(path)
49     if result == None:
50         print("error: result not available")
51         return None
52
53     spec, mask = GetBinary(result.ToJSON())
54
55     # create generic flow
56     my_flow = {
57         "flow": {
58             "generic": {
59                 "pattern": {"spec": bytes(spec.encode()), "mask": bytes(mask.encode())}
60             }
61         },
62     }
63
64     if show_result_only:
65         return my_flow
66
67     my_flow.update(
68         {
69             "type": VppEnum.vl_api_flow_type_v2_t.FLOW_TYPE_GENERIC_V2,
70         }
71     )
72
73     # update actions entry
74     my_flow = GetAction(actions, my_flow)
75
76     return my_flow
77
78
79 def GetAction(actions, flow):
80     if len(actions.split(" ")) > 1:
81         type = actions.split(" ")[0]
82     else:
83         type = actions
84
85     if type == "mark":
86         flow.update(
87             {
88                 "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_MARK_V2,
89                 "mark_flow_id": int(actions.split(" ")[1]),
90             }
91         )
92     elif type == "next-node":
93         flow.update(
94             {
95                 "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_NODE_V2,
96                 "redirect_node_index": int(actions.split(" ")[1]),
97             }
98         )
99     elif type == "buffer-advance":
100         flow.update(
101             {
102                 "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_BUFFER_ADVANCE_V2,
103                 "buffer_advance": int(actions.split(" ")[1]),
104             }
105         )
106     elif type == "redirect-to-queue":
107         flow.update(
108             {
109                 "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_REDIRECT_TO_QUEUE_V2,
110                 "redirect_queue": int(actions.split(" ")[1]),
111             }
112         )
113     elif type == "rss":
114         flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2})
115     elif type == "rss-queues":
116         queue_end = int(actions.split(" ")[-1])
117         queue_start = int(actions.split(" ")[-3])
118         flow.update(
119             {
120                 "actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_RSS_V2,
121                 "queue_index": queue_start,
122                 "queue_num": queue_end - queue_start + 1,
123             }
124         )
125     elif type == "drop":
126         flow.update({"actions": VppEnum.vl_api_flow_action_v2_t.FLOW_ACTION_DROP_V2})
127
128     return flow
129
130
131 def GetBinary(flow_info):
132     spec = "".join(flow_info["Packet"])
133     mask = "".join(flow_info["Mask"])
134     return spec, mask
135
136
137 def ParseFields(item):
138     # get protocol name
139     prot = item.split("(")[0]
140     stack = {"header": prot}
141     # get fields contents
142     fields = re.findall(r"[(](.*?)[)]", item)
143     if not fields:
144         print("error: invalid pattern")
145         return None
146     if fields == [""]:
147         return stack
148     stack.update({"fields": []})
149     return ParseStack(stack, fields[0].split(","))
150
151
152 def GetMask(item):
153     if "format" in item:
154         format = item["format"]
155         if format == "mac":
156             mask = "ff:ff:ff:ff:ff:ff"
157         elif format == "ipv4":
158             mask = "255.255.255.255"
159         elif format == "ipv6":
160             mask = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
161         return mask
162     if "size" in item:
163         mask = str((1 << int(item["size"])) - 1)
164     else:
165         print("mask error")
166     return mask
167
168
169 # parse protocol headers and its fields. Available fields are defined in corresponding nodes.
170 def ParseStack(stack, fields):
171     prot = stack["header"]
172     node_path = parsegraph_path + "/nodes/" + prot + ".json"
173     if not os.path.exists(node_path):
174         print("error file not exist '%s' " % (node_path))
175         return None
176     f = open(node_path, "r", encoding="utf-8")
177     nodeinfo = json.load(f)
178     for field in fields:
179         fld_name = field.split("=")[0].strip()
180         fld_value = (
181             field.split("=")[-1].strip() if (len(field.split("=")) >= 2) else None
182         )
183         for item in nodeinfo["layout"]:
184             if fld_name == item["name"]:
185                 mask = GetMask(item)
186                 stack["fields"].append(
187                     {"name": fld_name, "value": fld_value, "mask": mask}
188                 )
189                 break
190         if not stack["fields"]:
191             print("warning: invalid field '%s'" % (fld_name))
192             return None
193
194     return stack
195
196
197 def ParsePattern(pattern):
198     # create json template
199     json_tmp = {"type": "path", "stack": []}
200
201     items = pattern.split("/")
202     for item in items:
203         stack = ParseFields(item)
204         if stack == None:
205             return None
206         json_tmp["stack"].append(stack)
207
208     return json_tmp