trigger 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trigger/__init__.py +7 -0
- trigger/acl/__init__.py +32 -0
- trigger/acl/autoacl.py +70 -0
- trigger/acl/db.py +324 -0
- trigger/acl/dicts.py +357 -0
- trigger/acl/grammar.py +112 -0
- trigger/acl/ios.py +222 -0
- trigger/acl/junos.py +422 -0
- trigger/acl/models.py +118 -0
- trigger/acl/parser.py +168 -0
- trigger/acl/queue.py +296 -0
- trigger/acl/support.py +1431 -0
- trigger/acl/tools.py +746 -0
- trigger/bin/__init__.py +0 -0
- trigger/bin/acl.py +233 -0
- trigger/bin/acl_script.py +574 -0
- trigger/bin/aclconv.py +82 -0
- trigger/bin/check_access.py +93 -0
- trigger/bin/check_syntax.py +66 -0
- trigger/bin/fe.py +197 -0
- trigger/bin/find_access.py +191 -0
- trigger/bin/gnng.py +434 -0
- trigger/bin/gong.py +86 -0
- trigger/bin/load_acl.py +841 -0
- trigger/bin/load_config.py +18 -0
- trigger/bin/netdev.py +317 -0
- trigger/bin/optimizer.py +638 -0
- trigger/bin/run_cmds.py +18 -0
- trigger/changemgmt/__init__.py +352 -0
- trigger/changemgmt/bounce.py +57 -0
- trigger/cmds.py +1217 -0
- trigger/conf/__init__.py +94 -0
- trigger/conf/global_settings.py +674 -0
- trigger/contrib/__init__.py +7 -0
- trigger/exceptions.py +307 -0
- trigger/gorc.py +172 -0
- trigger/netdevices/__init__.py +1288 -0
- trigger/netdevices/loader.py +174 -0
- trigger/netscreen.py +1030 -0
- trigger/packages/__init__.py +6 -0
- trigger/packages/peewee.py +8084 -0
- trigger/rancid.py +463 -0
- trigger/tacacsrc.py +584 -0
- trigger/twister.py +2203 -0
- trigger/twister2.py +745 -0
- trigger/utils/__init__.py +88 -0
- trigger/utils/cli.py +349 -0
- trigger/utils/importlib.py +77 -0
- trigger/utils/network.py +157 -0
- trigger/utils/rcs.py +178 -0
- trigger/utils/templates.py +81 -0
- trigger/utils/url.py +78 -0
- trigger/utils/xmltodict.py +298 -0
- trigger-2.0.0.dist-info/METADATA +146 -0
- trigger-2.0.0.dist-info/RECORD +61 -0
- trigger-2.0.0.dist-info/WHEEL +5 -0
- trigger-2.0.0.dist-info/entry_points.txt +15 -0
- trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
- trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
- trigger-2.0.0.dist-info/top_level.txt +2 -0
- twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/bin/optimizer.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
optimizer - ACL Optimizer
|
|
5
|
+
|
|
6
|
+
Optimizes filters (usually best on Juniper filters) using
|
|
7
|
+
various algorithms to determine which filters can be merged
|
|
8
|
+
and removed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "1.5"
|
|
12
|
+
|
|
13
|
+
import copy
|
|
14
|
+
import logging
|
|
15
|
+
import signal
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
from optparse import OptionParser
|
|
19
|
+
|
|
20
|
+
from simpleparse.error import ParserSyntaxError
|
|
21
|
+
|
|
22
|
+
from trigger.acl.parser import ACL, TIP, Comment, parse
|
|
23
|
+
|
|
24
|
+
stop_all = False
|
|
25
|
+
|
|
26
|
+
# Logger
|
|
27
|
+
logging.basicConfig(
|
|
28
|
+
level=logging.INFO, format="%(asctime)s [%(levelname)s]: %(message)s"
|
|
29
|
+
)
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sig_handler(s, d):
|
|
34
|
+
global stop_all
|
|
35
|
+
stop_all = True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def parse_args(argv):
|
|
39
|
+
parser = OptionParser(
|
|
40
|
+
usage="%prog [options] [acls]",
|
|
41
|
+
description="""ACL Optimizer
|
|
42
|
+
|
|
43
|
+
Optimizes filters (usually best on Juniper filters) using
|
|
44
|
+
various algorithms to determine which filters can be merged
|
|
45
|
+
and removed.
|
|
46
|
+
|
|
47
|
+
There are several phases of optimization which include source
|
|
48
|
+
address optimization, destination address optimization, and
|
|
49
|
+
destination port optimization.
|
|
50
|
+
|
|
51
|
+
By default the optimizer will continue to optimize until the
|
|
52
|
+
no more optimizations can be made.
|
|
53
|
+
|
|
54
|
+
This can be very time consuming if run on a very large acl.
|
|
55
|
+
It is suggested that the focus argument should be used if
|
|
56
|
+
such an acl is needed to be optimized.
|
|
57
|
+
|
|
58
|
+
This will output to a file: <original_acl_filename>.optimized
|
|
59
|
+
""",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
parser.add_option(
|
|
63
|
+
"-p",
|
|
64
|
+
"--passes",
|
|
65
|
+
type="int",
|
|
66
|
+
default=0,
|
|
67
|
+
help="""The number of passes the optimizer should make.
|
|
68
|
+
|
|
69
|
+
By defaut the optimizer will continue to make more passes until
|
|
70
|
+
no more optimizations can be made. Specify this to limit this.
|
|
71
|
+
""",
|
|
72
|
+
)
|
|
73
|
+
parser.add_option(
|
|
74
|
+
"-f",
|
|
75
|
+
"--focus",
|
|
76
|
+
type="int",
|
|
77
|
+
default=0,
|
|
78
|
+
help="""Focus on a specific set of terms based on the
|
|
79
|
+
number of destination ports found. This will count the number
|
|
80
|
+
of destination ports, and if the port count is over X, the terms
|
|
81
|
+
in which this port was found will be accounted for in its optimization
|
|
82
|
+
checks. All other terms will be left alone. By default this feature
|
|
83
|
+
is set to 0 (or off).
|
|
84
|
+
""",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
parser.add_option(
|
|
88
|
+
"",
|
|
89
|
+
"--no-source-ip",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="This will turn off the source-ip optimization",
|
|
92
|
+
)
|
|
93
|
+
parser.add_option(
|
|
94
|
+
"",
|
|
95
|
+
"--no-destination-ip",
|
|
96
|
+
action="store_true",
|
|
97
|
+
help="This will turn off the destination-ip optimization",
|
|
98
|
+
)
|
|
99
|
+
parser.add_option(
|
|
100
|
+
"",
|
|
101
|
+
"--no-destination-port",
|
|
102
|
+
action="store_true",
|
|
103
|
+
help="This will turn off the destination-port optimization",
|
|
104
|
+
)
|
|
105
|
+
parser.add_option(
|
|
106
|
+
"-v", "--verbose", action="store_true", help="Turn on verbose/debug output"
|
|
107
|
+
)
|
|
108
|
+
parser.add_option(
|
|
109
|
+
"",
|
|
110
|
+
"--no-expires",
|
|
111
|
+
action="store_true",
|
|
112
|
+
help="If a term includes an expire date, mark non-eligible for optimize",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
parser.add_option(
|
|
116
|
+
"-d",
|
|
117
|
+
"--debug",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="Warning: this is very noisy. It will display every action"
|
|
120
|
+
"from the optimization process.",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
opts, args = parser.parse_args(argv)
|
|
124
|
+
|
|
125
|
+
return opts, args
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ProgressMeter:
|
|
129
|
+
def __init__(self, **kw):
|
|
130
|
+
# What time do we start tracking our progress from?
|
|
131
|
+
self.timestamp = kw.get("timestamp", time.time())
|
|
132
|
+
# What kind of unit are we tracking?
|
|
133
|
+
self.unit = str(kw.get("unit", ""))
|
|
134
|
+
# Number of units to process
|
|
135
|
+
self.total = int(kw.get("total", 100))
|
|
136
|
+
# Number of units already processed
|
|
137
|
+
self.count = int(kw.get("count", 0))
|
|
138
|
+
# Refresh rate in seconds
|
|
139
|
+
self.rate_refresh = float(kw.get("rate_refresh", 0.5))
|
|
140
|
+
# Number of ticks in meter
|
|
141
|
+
self.meter_ticks = int(kw.get("ticks", 60))
|
|
142
|
+
self.meter_division = float(self.total) / self.meter_ticks
|
|
143
|
+
self.meter_value = int(self.count / self.meter_division)
|
|
144
|
+
self.last_update = None
|
|
145
|
+
self.rate_history_idx = 0
|
|
146
|
+
self.rate_history_len = 10
|
|
147
|
+
self.rate_history = [None] * self.rate_history_len
|
|
148
|
+
self.rate_current = 0.0
|
|
149
|
+
self.last_refresh = 0
|
|
150
|
+
self.prev_meter_len = 0
|
|
151
|
+
|
|
152
|
+
def update(self, count, **kw):
|
|
153
|
+
now = time.time()
|
|
154
|
+
# Caclulate rate of progress
|
|
155
|
+
rate = 0.0
|
|
156
|
+
# Add count to Total
|
|
157
|
+
self.count += count
|
|
158
|
+
self.count = min(self.count, self.total)
|
|
159
|
+
if self.last_update:
|
|
160
|
+
delta = now - float(self.last_update)
|
|
161
|
+
if delta:
|
|
162
|
+
rate = count / delta
|
|
163
|
+
else:
|
|
164
|
+
rate = count
|
|
165
|
+
self.rate_history[self.rate_history_idx] = rate
|
|
166
|
+
self.rate_history_idx += 1
|
|
167
|
+
self.rate_history_idx %= self.rate_history_len
|
|
168
|
+
cnt = 0
|
|
169
|
+
total = 0.0
|
|
170
|
+
# Average rate history
|
|
171
|
+
for rate in self.rate_history:
|
|
172
|
+
if rate is None:
|
|
173
|
+
continue
|
|
174
|
+
cnt += 1
|
|
175
|
+
total += rate
|
|
176
|
+
rate = total / cnt
|
|
177
|
+
self.rate_current = rate
|
|
178
|
+
self.last_update = now
|
|
179
|
+
# Device Total by meter division
|
|
180
|
+
value = int(self.count / self.meter_division)
|
|
181
|
+
if value > self.meter_value:
|
|
182
|
+
self.meter_value = value
|
|
183
|
+
if self.last_refresh:
|
|
184
|
+
if (now - self.last_refresh) > self.rate_refresh or (
|
|
185
|
+
self.count >= self.total
|
|
186
|
+
):
|
|
187
|
+
self.refresh()
|
|
188
|
+
else:
|
|
189
|
+
self.refresh()
|
|
190
|
+
|
|
191
|
+
def get_meter(self, **kw):
|
|
192
|
+
bar = "-" * self.meter_value
|
|
193
|
+
pad = " " * (self.meter_ticks - self.meter_value)
|
|
194
|
+
perc = (float(self.count) / self.total) * 100
|
|
195
|
+
return "[%s>%s] %d%% %.1f/sec" % (bar, pad, perc, self.rate_current)
|
|
196
|
+
|
|
197
|
+
def refresh(self, **kw):
|
|
198
|
+
# Clear line and return cursor to start-of-line
|
|
199
|
+
sys.stderr.write(" " * self.prev_meter_len + "\x08" * self.prev_meter_len)
|
|
200
|
+
# Get meter text
|
|
201
|
+
meter_text = self.get_meter(**kw)
|
|
202
|
+
# Write meter and return cursor to start-of-line
|
|
203
|
+
sys.stderr.write(meter_text + "\x08" * len(meter_text))
|
|
204
|
+
self.prev_meter_len = len(meter_text)
|
|
205
|
+
|
|
206
|
+
# Are we finished?
|
|
207
|
+
if self.count >= self.total:
|
|
208
|
+
sys.stderr.write("\n")
|
|
209
|
+
sys.stderr.flush()
|
|
210
|
+
# Timestamp
|
|
211
|
+
self.last_refresh = time.time()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def focus_terms(pcount, terms):
|
|
215
|
+
"""
|
|
216
|
+
Generates a list of term names that have a port count
|
|
217
|
+
greater than pcount for the optimizer to 'focus' in on.
|
|
218
|
+
"""
|
|
219
|
+
focused = dict()
|
|
220
|
+
matched_ports = dict()
|
|
221
|
+
ports = dict()
|
|
222
|
+
|
|
223
|
+
for term in terms:
|
|
224
|
+
if "source-port" in term.match:
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
if "destination-port" not in term.match:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
for port in term.match["destination-port"]:
|
|
231
|
+
if port == 0:
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
if port in ports:
|
|
235
|
+
ports[port] += 1
|
|
236
|
+
else:
|
|
237
|
+
ports[port] = 1
|
|
238
|
+
|
|
239
|
+
for port, cnt in ports.items():
|
|
240
|
+
if cnt >= pcount:
|
|
241
|
+
log.info("port %s had a count of %d" % (str(port), cnt))
|
|
242
|
+
matched_ports[port] = 1
|
|
243
|
+
|
|
244
|
+
for term in terms:
|
|
245
|
+
if "destination-port" not in term.match:
|
|
246
|
+
continue
|
|
247
|
+
if "source-port" in term.match:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
for tport in term.match["destination-port"]:
|
|
251
|
+
if tport in matched_ports:
|
|
252
|
+
focused[term.name] = 1
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
log.info("%d focused terms" % len(focused))
|
|
256
|
+
return focused
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
chk_keys = ["protocol", "source-address", "destination-address", "destination-port"]
|
|
260
|
+
|
|
261
|
+
rej_keys = ["reject", "deny", "discard"]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def optimize_terms(terms, focused, which, opts):
|
|
265
|
+
global stop_all
|
|
266
|
+
to_delete = dict()
|
|
267
|
+
other = ""
|
|
268
|
+
total = 0
|
|
269
|
+
|
|
270
|
+
total = len(terms)
|
|
271
|
+
|
|
272
|
+
if which == "source-address":
|
|
273
|
+
other = ["destination-address"]
|
|
274
|
+
elif which == "destination-address":
|
|
275
|
+
other = ["source-address"]
|
|
276
|
+
else:
|
|
277
|
+
# this is used primarily for port optimization
|
|
278
|
+
other = ["source-address", "destination-address"]
|
|
279
|
+
|
|
280
|
+
meter = ProgressMeter(total=total)
|
|
281
|
+
|
|
282
|
+
for term1 in terms:
|
|
283
|
+
if stop_all:
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
meter.update(1)
|
|
287
|
+
|
|
288
|
+
if focused and term1.name not in focused:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
dont_merge = False
|
|
292
|
+
if opts.no_expires:
|
|
293
|
+
for c in term1.comments:
|
|
294
|
+
if "UNTIL" in c and "Never" not in c:
|
|
295
|
+
dont_merge = True
|
|
296
|
+
|
|
297
|
+
if dont_merge:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# make sure that there are not any source-ports
|
|
301
|
+
# defined in term1
|
|
302
|
+
if (
|
|
303
|
+
"destination-port" not in term1.match
|
|
304
|
+
or "source-port" in term1.match
|
|
305
|
+
or term1.action[0] in rej_keys
|
|
306
|
+
or term1 in to_delete
|
|
307
|
+
):
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
for term2 in terms:
|
|
311
|
+
breaker = False
|
|
312
|
+
|
|
313
|
+
if stop_all:
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
log.debug(
|
|
317
|
+
"Comparing term %s to term %s [%d terms deleted]"
|
|
318
|
+
% (term1.name, term2.name, len(to_delete))
|
|
319
|
+
)
|
|
320
|
+
if focused and term2.name not in focused:
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
if opts.no_expires:
|
|
324
|
+
for c in term2.comments:
|
|
325
|
+
if "UNTIL" in c and "Never" not in c:
|
|
326
|
+
dont_merge = True
|
|
327
|
+
|
|
328
|
+
if dont_merge:
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
# check to make sure that neither term
|
|
332
|
+
# has been marked for deletion. Also
|
|
333
|
+
# check to make sure we're not comparing
|
|
334
|
+
# the same terms, and this is not a
|
|
335
|
+
# rejected action.
|
|
336
|
+
if (
|
|
337
|
+
term1.name in to_delete
|
|
338
|
+
or term2.name in to_delete
|
|
339
|
+
or term1.name == term2.name
|
|
340
|
+
or term2.action[0] in rej_keys
|
|
341
|
+
or "source-port" in term2.match
|
|
342
|
+
):
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
# check to make sure both terms include somethin
|
|
346
|
+
# that can be matched.
|
|
347
|
+
for key in chk_keys:
|
|
348
|
+
if key not in term1.match or key not in term2.match:
|
|
349
|
+
breaker = True
|
|
350
|
+
break
|
|
351
|
+
|
|
352
|
+
if breaker:
|
|
353
|
+
continue
|
|
354
|
+
|
|
355
|
+
# make sure that both protocols match up.
|
|
356
|
+
if len(term1.match["protocol"]) != len(term2.match["protocol"]):
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
if "icmp" in term1.match["protocol"] or "icmp" in term2.match["protocol"]:
|
|
360
|
+
break
|
|
361
|
+
|
|
362
|
+
for proto in term1.match["protocol"]:
|
|
363
|
+
if proto not in term2.match["protocol"]:
|
|
364
|
+
breaker = True
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
if breaker:
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
# we don't do this check if we are optimizing destination-ports
|
|
371
|
+
if which != "destination-port":
|
|
372
|
+
# make sure that both destination-ports match up.
|
|
373
|
+
if len(term1.match["destination-port"]) != len(
|
|
374
|
+
term2.match["destination-port"]
|
|
375
|
+
):
|
|
376
|
+
breaker = True
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
for port in term1.match["destination-port"]:
|
|
380
|
+
for port2 in term2.match["destination-port"]:
|
|
381
|
+
if port != port2:
|
|
382
|
+
breaker = True
|
|
383
|
+
if breaker:
|
|
384
|
+
break
|
|
385
|
+
if breaker:
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
if breaker:
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
for ent in other:
|
|
392
|
+
# check to make sure that the other side
|
|
393
|
+
# has the IP's from term1 to term2
|
|
394
|
+
len1 = len(term1.match[ent])
|
|
395
|
+
len2 = len(term2.match[ent])
|
|
396
|
+
|
|
397
|
+
if len1 != len2:
|
|
398
|
+
breaker = True
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
matches = [x for x in term1.match[ent] if x in term2.match[ent]]
|
|
402
|
+
|
|
403
|
+
if len(matches) != len1:
|
|
404
|
+
breaker = True
|
|
405
|
+
break
|
|
406
|
+
matches = [x for x in term2.match[ent] if x in term1.match[ent]]
|
|
407
|
+
|
|
408
|
+
if len(matches) != len2:
|
|
409
|
+
breaker = True
|
|
410
|
+
break
|
|
411
|
+
|
|
412
|
+
if breaker:
|
|
413
|
+
continue
|
|
414
|
+
|
|
415
|
+
# append old comments
|
|
416
|
+
for comment in term1.comments:
|
|
417
|
+
term2.comments.append(comment)
|
|
418
|
+
|
|
419
|
+
ips = []
|
|
420
|
+
for to_add in term1.match[which]:
|
|
421
|
+
term2.match[which].append(to_add)
|
|
422
|
+
ips.append(str(to_add))
|
|
423
|
+
|
|
424
|
+
cmt = Comment(
|
|
425
|
+
"merged [({}) {}] from {}".format(which, ",".join(ips), term1.name)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
term2.comments.append(cmt)
|
|
429
|
+
|
|
430
|
+
to_delete[term1.name] = 1
|
|
431
|
+
|
|
432
|
+
new_terms = []
|
|
433
|
+
for term in terms:
|
|
434
|
+
if term.name not in to_delete:
|
|
435
|
+
new_terms.append(term)
|
|
436
|
+
|
|
437
|
+
return new_terms
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def terms_unchunk(chunks):
|
|
441
|
+
terms = []
|
|
442
|
+
|
|
443
|
+
for chunk in chunks:
|
|
444
|
+
for term in chunk:
|
|
445
|
+
terms.append(term)
|
|
446
|
+
|
|
447
|
+
return terms
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def terms_chunker(terms):
|
|
451
|
+
"""
|
|
452
|
+
In order to achieve true optimization we must break our filter up
|
|
453
|
+
into chunks that are the aggregate of the same modifier. What this
|
|
454
|
+
means is if we have the following term structure:
|
|
455
|
+
|
|
456
|
+
term1 { accept }
|
|
457
|
+
term2 { accept }
|
|
458
|
+
term3 { accept }
|
|
459
|
+
term4 { deny }
|
|
460
|
+
term5 { deny }
|
|
461
|
+
term6 { accept }
|
|
462
|
+
term7 { accept }
|
|
463
|
+
term8 { deny }
|
|
464
|
+
|
|
465
|
+
We would break it up as so:
|
|
466
|
+
|
|
467
|
+
chunks = [ [term1, term2, term3], [term4, term5], [term6, term7, term8] ]
|
|
468
|
+
|
|
469
|
+
We then only optimize on chunks of terms so that we don't accidently
|
|
470
|
+
optimize something accepted above a deny to an accept below the deny.
|
|
471
|
+
"""
|
|
472
|
+
ret = []
|
|
473
|
+
current_chunk = []
|
|
474
|
+
|
|
475
|
+
total = len(terms)
|
|
476
|
+
meter = ProgressMeter(total=total)
|
|
477
|
+
current_modifier = None
|
|
478
|
+
|
|
479
|
+
for term in terms:
|
|
480
|
+
meter.update(1)
|
|
481
|
+
if current_modifier is None:
|
|
482
|
+
current_modifier = term.action[0]
|
|
483
|
+
if current_modifier != term.action[0]:
|
|
484
|
+
ret.append(copy.copy(current_chunk))
|
|
485
|
+
current_chunk = []
|
|
486
|
+
current_modifier = term.action[0]
|
|
487
|
+
|
|
488
|
+
current_chunk.append(term)
|
|
489
|
+
|
|
490
|
+
ret.append(copy.copy(current_chunk))
|
|
491
|
+
|
|
492
|
+
return ret
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def optimize(opts, terms, focused):
|
|
496
|
+
global stop_all
|
|
497
|
+
|
|
498
|
+
terms_old = terms
|
|
499
|
+
mtypes = []
|
|
500
|
+
|
|
501
|
+
if not opts.no_source_ip:
|
|
502
|
+
mtypes.append("source-address")
|
|
503
|
+
if not opts.no_destination_ip:
|
|
504
|
+
mtypes.append("destination-address")
|
|
505
|
+
if not opts.no_destination_port:
|
|
506
|
+
mtypes.append("destination-port")
|
|
507
|
+
|
|
508
|
+
for term in terms:
|
|
509
|
+
for mtype in mtypes:
|
|
510
|
+
if mtype == "destination-port":
|
|
511
|
+
continue
|
|
512
|
+
if mtype not in term.match:
|
|
513
|
+
term.match[mtype] = [TIP("0.0.0.0/0")]
|
|
514
|
+
|
|
515
|
+
chunks = terms_chunker(terms)
|
|
516
|
+
|
|
517
|
+
for type in mtypes:
|
|
518
|
+
if stop_all:
|
|
519
|
+
return terms
|
|
520
|
+
|
|
521
|
+
chunk_count = 0
|
|
522
|
+
|
|
523
|
+
for chunk in chunks:
|
|
524
|
+
log.info("Optimizing %s [Chunk %d]" % (type, chunk_count))
|
|
525
|
+
chunks[chunk_count] = optimize_terms(chunk, focused, type, opts)
|
|
526
|
+
log.info("TCount: %d/%d" % (len(terms_old), len(terms)))
|
|
527
|
+
chunk_count += 1
|
|
528
|
+
|
|
529
|
+
terms = terms_unchunk(chunks)
|
|
530
|
+
return terms
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def do_work(opts, files):
|
|
534
|
+
global stop_all
|
|
535
|
+
for acl_file in files:
|
|
536
|
+
focused = None
|
|
537
|
+
out_file = acl_file + ".optimized"
|
|
538
|
+
|
|
539
|
+
if stop_all:
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
log.info(f"Parsing {acl_file}")
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
acl = parse(open(acl_file))
|
|
546
|
+
except ParserSyntaxError as e:
|
|
547
|
+
etxt = str(e).split()
|
|
548
|
+
log.error(etxt)
|
|
549
|
+
return
|
|
550
|
+
|
|
551
|
+
log.info("Done parsing")
|
|
552
|
+
|
|
553
|
+
len(acl.terms)
|
|
554
|
+
|
|
555
|
+
if opts.focus:
|
|
556
|
+
log.info("Finding focused terms")
|
|
557
|
+
focused = focus_terms(opts.focus, acl.terms)
|
|
558
|
+
if not focused:
|
|
559
|
+
log.warn(f"No focused terms could be found in acl {acl_file}")
|
|
560
|
+
continue
|
|
561
|
+
|
|
562
|
+
log.info("Done focused term")
|
|
563
|
+
|
|
564
|
+
passes = 1
|
|
565
|
+
|
|
566
|
+
terms_old = acl.terms
|
|
567
|
+
|
|
568
|
+
# destination ports should always be optimized LAST
|
|
569
|
+
real_port_optimize_opt = opts.no_destination_port
|
|
570
|
+
# first set to none
|
|
571
|
+
opts.no_destination_port = True
|
|
572
|
+
|
|
573
|
+
while True:
|
|
574
|
+
if stop_all:
|
|
575
|
+
break
|
|
576
|
+
|
|
577
|
+
log.info("Optimization pass %d" % passes)
|
|
578
|
+
terms = optimize(opts, terms_old, focused)
|
|
579
|
+
|
|
580
|
+
if opts.passes and passes >= opts.passes:
|
|
581
|
+
break
|
|
582
|
+
|
|
583
|
+
if len(terms_old) == len(terms):
|
|
584
|
+
break
|
|
585
|
+
|
|
586
|
+
terms_old = terms
|
|
587
|
+
passes += 1
|
|
588
|
+
|
|
589
|
+
terms_old = terms
|
|
590
|
+
if not real_port_optimize_opt:
|
|
591
|
+
passes = 1
|
|
592
|
+
opts.no_destination_port = False
|
|
593
|
+
opts.no_source_ip = True
|
|
594
|
+
opts.no_destination_ip = True
|
|
595
|
+
while True:
|
|
596
|
+
if stop_all:
|
|
597
|
+
break
|
|
598
|
+
|
|
599
|
+
log.info("PORT Optimization pass %d" % passes)
|
|
600
|
+
terms = optimize(opts, terms_old, focused)
|
|
601
|
+
|
|
602
|
+
if opts.passes and passes >= opts.passes:
|
|
603
|
+
break
|
|
604
|
+
|
|
605
|
+
if len(terms_old) == len(terms):
|
|
606
|
+
break
|
|
607
|
+
|
|
608
|
+
terms_old = terms
|
|
609
|
+
passes += 1
|
|
610
|
+
|
|
611
|
+
new_acl = ACL()
|
|
612
|
+
new_acl.policers = acl.policers
|
|
613
|
+
new_acl.format = acl.format
|
|
614
|
+
new_acl.comments = acl.comments
|
|
615
|
+
new_acl.name = acl.name
|
|
616
|
+
new_acl.terms = terms
|
|
617
|
+
|
|
618
|
+
out = open(out_file, "w")
|
|
619
|
+
|
|
620
|
+
for x in new_acl.output(replace=True):
|
|
621
|
+
print(x, file=out)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def main():
|
|
625
|
+
"""Main entry point for the CLI tool."""
|
|
626
|
+
opts, args = parse_args(sys.argv)
|
|
627
|
+
if opts.debug:
|
|
628
|
+
log.setLevel(logging.DEBUG)
|
|
629
|
+
|
|
630
|
+
acl_files = args[1:]
|
|
631
|
+
|
|
632
|
+
signal.signal(signal.SIGINT, sig_handler)
|
|
633
|
+
|
|
634
|
+
do_work(opts, acl_files)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if __name__ == "__main__":
|
|
638
|
+
main()
|
trigger/bin/run_cmds.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Uses Trigger libraries to run commands on network devices.
|
|
5
|
+
|
|
6
|
+
Please see `~trigger.contrib.docommand.CommandRunner` for details.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from trigger.contrib import docommand
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
"""Main entry point for the CLI tool."""
|
|
14
|
+
docommand.main(action_class=docommand.CommandRunner)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|