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/acl/support.py
ADDED
|
@@ -0,0 +1,1431 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This code is originally from parser.py. It consists of a lot of classes which
|
|
3
|
+
support the various modules for parsing. This file is not meant to by used by itself.
|
|
4
|
+
|
|
5
|
+
# Functions
|
|
6
|
+
'check_name',
|
|
7
|
+
'check_range',
|
|
8
|
+
'do_dscp_lookup',
|
|
9
|
+
'do_icmp_type_lookup',
|
|
10
|
+
'do_icmp_code_lookup',
|
|
11
|
+
'do_ip_option_lookup',
|
|
12
|
+
'do_lookup',
|
|
13
|
+
'do_port_lookup',
|
|
14
|
+
'do_protocol_lookup',
|
|
15
|
+
'make_inverse_mask',
|
|
16
|
+
'strip_comments',
|
|
17
|
+
# Classes
|
|
18
|
+
'ACL',
|
|
19
|
+
'Comment',
|
|
20
|
+
'Matches',
|
|
21
|
+
'Modifiers',
|
|
22
|
+
'MyDict',
|
|
23
|
+
'Protocol',
|
|
24
|
+
'RangeList',
|
|
25
|
+
'Term',
|
|
26
|
+
'TermList',
|
|
27
|
+
'TIP',
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Copied metadata from parser.py
|
|
31
|
+
__author__ = "Jathan McCollum, Mike Biancaniello, Michael Harding, Michael Shields"
|
|
32
|
+
__editor__ = "Joseph Malone"
|
|
33
|
+
__maintainer__ = "Jathan McCollum"
|
|
34
|
+
__email__ = "jathanism@aol.com"
|
|
35
|
+
__copyright__ = "Copyright 2006-2013, AOL Inc.; 2013 Saleforce.com"
|
|
36
|
+
|
|
37
|
+
import IPy
|
|
38
|
+
|
|
39
|
+
from trigger import exceptions
|
|
40
|
+
|
|
41
|
+
from .dicts import *
|
|
42
|
+
|
|
43
|
+
# Python 2/3 compatibility
|
|
44
|
+
try:
|
|
45
|
+
unicode
|
|
46
|
+
except NameError:
|
|
47
|
+
# Python 3
|
|
48
|
+
unicode = str
|
|
49
|
+
|
|
50
|
+
# Temporary resting place for comments, so the rest of the parser can
|
|
51
|
+
# ignore them. Yes, this makes the library not thread-safe.
|
|
52
|
+
Comments = []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_name(name, exc, max_len=255, extra_chars=" -_."):
|
|
56
|
+
"""
|
|
57
|
+
Test whether something is a valid identifier (for any vendor).
|
|
58
|
+
This means letters, numbers, and other characters specified in the
|
|
59
|
+
@extra_chars argument. If the string is invalid, throw the specified
|
|
60
|
+
exception.
|
|
61
|
+
|
|
62
|
+
:param name: The name to test.
|
|
63
|
+
:param exc: Exception type to raise if the name is invalid.
|
|
64
|
+
:param max_len: Integer of the maximum length of the name.
|
|
65
|
+
:param extra_chars: Extra non-alphanumeric characters to allow in the name.
|
|
66
|
+
"""
|
|
67
|
+
if name is None:
|
|
68
|
+
return
|
|
69
|
+
if name == "":
|
|
70
|
+
raise exc("Name cannot be null string")
|
|
71
|
+
if len(name) > max_len:
|
|
72
|
+
raise exc('Name "%s" cannot be longer than %d characters' % (name, max_len))
|
|
73
|
+
for char in name:
|
|
74
|
+
if not (
|
|
75
|
+
(extra_chars is not None and char in extra_chars)
|
|
76
|
+
or (char >= "a" and char <= "z")
|
|
77
|
+
or (char >= "A" and char <= "Z")
|
|
78
|
+
or (char >= "0" and char <= "9")
|
|
79
|
+
):
|
|
80
|
+
raise exc(f'Invalid character "{char}" in name "{name}"')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def check_range(values, min, max):
|
|
84
|
+
for value in values:
|
|
85
|
+
try:
|
|
86
|
+
for subvalue in value:
|
|
87
|
+
check_range([subvalue], min, max)
|
|
88
|
+
except TypeError:
|
|
89
|
+
if not min <= value <= max:
|
|
90
|
+
raise exceptions.BadMatchArgRange(
|
|
91
|
+
"match arg %s must be between %d and %d" % (str(value), min, max)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Having this take the dictionary itself instead of a function is very slow.
|
|
96
|
+
def do_lookup(lookup_func, arg):
|
|
97
|
+
if isinstance(arg, tuple):
|
|
98
|
+
return tuple([do_lookup(lookup_func, elt) for elt in arg])
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
return int(arg)
|
|
102
|
+
except TypeError:
|
|
103
|
+
return arg
|
|
104
|
+
except ValueError:
|
|
105
|
+
pass
|
|
106
|
+
# Ok, look it up by name.
|
|
107
|
+
try:
|
|
108
|
+
return lookup_func(arg)
|
|
109
|
+
except KeyError:
|
|
110
|
+
raise exceptions.UnknownMatchArg(f'match argument "{arg}" not known')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def do_protocol_lookup(arg):
|
|
114
|
+
if isinstance(arg, tuple):
|
|
115
|
+
return (Protocol(arg[0]), Protocol(arg[1]))
|
|
116
|
+
else:
|
|
117
|
+
return Protocol(arg)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def do_port_lookup(arg):
|
|
121
|
+
return do_lookup(lambda x: ports[x], arg)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def do_icmp_type_lookup(arg):
|
|
125
|
+
return do_lookup(lambda x: icmp_types[x], arg)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def do_icmp_code_lookup(arg):
|
|
129
|
+
return do_lookup(lambda x: icmp_codes[x], arg)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def do_ip_option_lookup(arg):
|
|
133
|
+
return do_lookup(lambda x: ip_option_names[x], arg)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def do_dscp_lookup(arg):
|
|
137
|
+
return do_lookup(lambda x: dscp_names[x], arg)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def make_inverse_mask(prefixlen):
|
|
141
|
+
"""
|
|
142
|
+
Return an IP object of the inverse mask of the CIDR prefix.
|
|
143
|
+
|
|
144
|
+
:param prefixlen:
|
|
145
|
+
CIDR prefix
|
|
146
|
+
"""
|
|
147
|
+
inverse_bits = 2 ** (32 - prefixlen) - 1
|
|
148
|
+
return TIP(inverse_bits)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def strip_comments(tags):
|
|
152
|
+
if tags is None:
|
|
153
|
+
return
|
|
154
|
+
noncomments = []
|
|
155
|
+
for tag in tags:
|
|
156
|
+
if isinstance(tag, Comment):
|
|
157
|
+
Comments.append(tag)
|
|
158
|
+
else:
|
|
159
|
+
noncomments.append(tag)
|
|
160
|
+
return noncomments
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class MyDict(dict):
|
|
164
|
+
"""
|
|
165
|
+
A dictionary subclass to collect common behavior changes used in container
|
|
166
|
+
classes for the ACL components: Modifiers, Matches.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, d=None, **kwargs):
|
|
170
|
+
if d:
|
|
171
|
+
if not hasattr(d, "keys"):
|
|
172
|
+
d = dict(d)
|
|
173
|
+
self.update(d)
|
|
174
|
+
if kwargs:
|
|
175
|
+
self.update(kwargs)
|
|
176
|
+
|
|
177
|
+
def __repr__(self):
|
|
178
|
+
return f"<{self.__class__.__name__}: {str(self)}>"
|
|
179
|
+
|
|
180
|
+
def __str__(self):
|
|
181
|
+
return ", ".join([f"{k} {v}" for k, v in self.items()])
|
|
182
|
+
|
|
183
|
+
def update(self, d):
|
|
184
|
+
"""Force this to go through __setitem__."""
|
|
185
|
+
for k, v in d.items():
|
|
186
|
+
self[k] = v
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class Modifiers(MyDict):
|
|
190
|
+
"""
|
|
191
|
+
Container class for modifiers. These are only supported by JunOS format
|
|
192
|
+
and are ignored by all others.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def __setitem__(self, key, value):
|
|
196
|
+
# Handle argument-less modifiers first.
|
|
197
|
+
if key in ("log", "sample", "syslog", "port-mirror"):
|
|
198
|
+
if value not in (None, True):
|
|
199
|
+
raise exceptions.ActionError(f'"{key}" action takes no argument')
|
|
200
|
+
super().__setitem__(key, None)
|
|
201
|
+
return
|
|
202
|
+
# Everything below requires an argument.
|
|
203
|
+
if value is None:
|
|
204
|
+
raise exceptions.ActionError(f'"{key}" action requires an argument')
|
|
205
|
+
if key == "count":
|
|
206
|
+
# JunOS 7.3 docs say this cannot contain underscores and that
|
|
207
|
+
# it must be 24 characters or less, but this appears to be false.
|
|
208
|
+
# Doc bug filed 2006-02-09, doc-sw/68420.
|
|
209
|
+
check_name(value, exceptions.BadCounterName, max_len=255)
|
|
210
|
+
elif key == "forwarding-class":
|
|
211
|
+
check_name(value, exceptions.BadForwardingClassName)
|
|
212
|
+
elif key == "ipsec-sa":
|
|
213
|
+
check_name(value, exceptions.BadIPSecSAName)
|
|
214
|
+
elif key == "loss-priority":
|
|
215
|
+
if value not in ("low", "high"):
|
|
216
|
+
raise exceptions.ActionError('"loss-priority" must be "low" or "high"')
|
|
217
|
+
elif key == "policer":
|
|
218
|
+
check_name(value, exceptions.BadPolicerName)
|
|
219
|
+
else:
|
|
220
|
+
raise exceptions.ActionError("invalid action: " + str(key))
|
|
221
|
+
super().__setitem__(key, value)
|
|
222
|
+
|
|
223
|
+
def output_junos(self):
|
|
224
|
+
"""
|
|
225
|
+
Output the modifiers to the only supported format!
|
|
226
|
+
"""
|
|
227
|
+
# Python 3: dict.keys() returns a view, convert to list and sort
|
|
228
|
+
keys = sorted(self.keys())
|
|
229
|
+
return [k + (self[k] and " " + str(self[k]) or "") + ";" for k in keys]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RangeList:
|
|
233
|
+
"""
|
|
234
|
+
A type which stores ordered sets, with efficient handling of
|
|
235
|
+
ranges. It can also store non-incrementable terms as an sorted set
|
|
236
|
+
without collapsing into ranges.
|
|
237
|
+
|
|
238
|
+
This is currently used to just store match conditions (e.g. protocols,
|
|
239
|
+
ports), but could be fleshed out into a general-purpose class. One
|
|
240
|
+
thing to think about is how/whether to handle a list of tuples as distinct
|
|
241
|
+
from a list of ranges. Should we just store them as xrange objects?
|
|
242
|
+
Should the object appear as discrete elements by default, for example
|
|
243
|
+
in len(), with the collapsed view as a method, or should we keep it
|
|
244
|
+
as it is now? All the current uses of this class are in this file
|
|
245
|
+
and have unit tests, so when we decided what the semantics of the
|
|
246
|
+
generalized module ought to be, we can make it so without worry.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
# Another way to implement this would be as a radix tree.
|
|
250
|
+
def __init__(self, data=None):
|
|
251
|
+
if data is None:
|
|
252
|
+
data = []
|
|
253
|
+
|
|
254
|
+
self.data = data
|
|
255
|
+
self._do_collapse()
|
|
256
|
+
|
|
257
|
+
def _cleanup(self, L):
|
|
258
|
+
"""
|
|
259
|
+
Prepare a potential list of lists, tuples, digits for collapse. Does
|
|
260
|
+
the following::
|
|
261
|
+
|
|
262
|
+
1. Sort & Convert all inner lists to tuples
|
|
263
|
+
2. Convert all tuples w/ only 1 item into single item
|
|
264
|
+
3. Gather all single digits
|
|
265
|
+
4. Convert to set to remove duplicates
|
|
266
|
+
5. Return as a sorted list
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
ret = []
|
|
270
|
+
|
|
271
|
+
# Get all list/tuples and return tuples
|
|
272
|
+
tuples = [tuple(sorted(i)) for i in L if isinstance(i, (list, tuple))]
|
|
273
|
+
singles = [i[0] for i in tuples if len(i) == 1] # Grab len of 1
|
|
274
|
+
tuples = [i for i in tuples if len(i) == 2] # Filter out len of 1
|
|
275
|
+
digits = [i for i in L if isinstance(i, int)] # Get digits
|
|
276
|
+
|
|
277
|
+
ret.extend(singles)
|
|
278
|
+
ret.extend(tuples)
|
|
279
|
+
ret.extend(digits)
|
|
280
|
+
|
|
281
|
+
if not ret:
|
|
282
|
+
ret = L
|
|
283
|
+
|
|
284
|
+
return sorted(set(ret))
|
|
285
|
+
|
|
286
|
+
def _collapse(self, l):
|
|
287
|
+
"""
|
|
288
|
+
Reduce a sorted list of elements to ranges represented as tuples;
|
|
289
|
+
e.g. [1, 2, 3, 4, 10] -> [(1, 4), 10]
|
|
290
|
+
"""
|
|
291
|
+
l = self._cleanup(l) # Remove duplicates
|
|
292
|
+
|
|
293
|
+
# Don't bother reducing a single item
|
|
294
|
+
if len(l) <= 1:
|
|
295
|
+
return l
|
|
296
|
+
|
|
297
|
+
# Make sure the elements are incrementable, or we can't reduce at all.
|
|
298
|
+
try:
|
|
299
|
+
l[0] + 1
|
|
300
|
+
except (TypeError, AttributeError):
|
|
301
|
+
return l
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
l[0][0] + 1
|
|
305
|
+
except (TypeError, AttributeError):
|
|
306
|
+
return l
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
# This last step uses a loop instead of pure functionalism because
|
|
310
|
+
# it will be common to step through it tens of thousands of times,
|
|
311
|
+
# for example in the case of (1024, 65535).
|
|
312
|
+
# [x, x+1, ..., x+n] -> [(x, x+n)]
|
|
313
|
+
n = 0
|
|
314
|
+
try:
|
|
315
|
+
while l[n] + 1 == l[n + 1]:
|
|
316
|
+
n += 1
|
|
317
|
+
except IndexError: # entire list collapses to one range
|
|
318
|
+
return [(l[0], l[-1])]
|
|
319
|
+
if n == 0:
|
|
320
|
+
return [l[0]] + self._collapse(l[1:])
|
|
321
|
+
else:
|
|
322
|
+
return [(l[0], l[n])] + self._collapse(l[n + 1 :])
|
|
323
|
+
|
|
324
|
+
def _do_collapse(self):
|
|
325
|
+
self.data = self._collapse(self._expand(self.data))
|
|
326
|
+
|
|
327
|
+
def _expand(self, l):
|
|
328
|
+
"""Expand a list of elements and tuples back to discrete elements.
|
|
329
|
+
Opposite of _collapse()."""
|
|
330
|
+
if not l:
|
|
331
|
+
return l
|
|
332
|
+
try:
|
|
333
|
+
# Python 3: range() returns a range object, not a list
|
|
334
|
+
return list(range(l[0][0], l[0][1] + 1)) + self._expand(l[1:])
|
|
335
|
+
except AttributeError: # not incrementable
|
|
336
|
+
return l
|
|
337
|
+
except (TypeError, IndexError):
|
|
338
|
+
return [l[0]] + self._expand(l[1:])
|
|
339
|
+
|
|
340
|
+
def expanded(self):
|
|
341
|
+
"""Return a list with all ranges converted to discrete elements."""
|
|
342
|
+
return self._expand(self.data)
|
|
343
|
+
|
|
344
|
+
def __add__(self, y):
|
|
345
|
+
for elt in y:
|
|
346
|
+
self.append(elt)
|
|
347
|
+
|
|
348
|
+
def append(self, obj):
|
|
349
|
+
# We could make this faster.
|
|
350
|
+
self.data.append(obj)
|
|
351
|
+
self._do_collapse()
|
|
352
|
+
|
|
353
|
+
def __eq__(self, other):
|
|
354
|
+
"""Compare RangeList to another RangeList or a list."""
|
|
355
|
+
if isinstance(other, RangeList):
|
|
356
|
+
return self.data == other.data
|
|
357
|
+
elif isinstance(other, list):
|
|
358
|
+
return self.data == other
|
|
359
|
+
return NotImplemented
|
|
360
|
+
|
|
361
|
+
def __ne__(self, other):
|
|
362
|
+
result = self.__eq__(other)
|
|
363
|
+
if result is NotImplemented:
|
|
364
|
+
return NotImplemented
|
|
365
|
+
return not result
|
|
366
|
+
|
|
367
|
+
def __lt__(self, other):
|
|
368
|
+
if isinstance(other, RangeList):
|
|
369
|
+
return self.data < other.data
|
|
370
|
+
elif isinstance(other, list):
|
|
371
|
+
return self.data < other
|
|
372
|
+
return NotImplemented
|
|
373
|
+
|
|
374
|
+
def __le__(self, other):
|
|
375
|
+
if isinstance(other, RangeList):
|
|
376
|
+
return self.data <= other.data
|
|
377
|
+
elif isinstance(other, list):
|
|
378
|
+
return self.data <= other
|
|
379
|
+
return NotImplemented
|
|
380
|
+
|
|
381
|
+
def __gt__(self, other):
|
|
382
|
+
if isinstance(other, RangeList):
|
|
383
|
+
return self.data > other.data
|
|
384
|
+
elif isinstance(other, list):
|
|
385
|
+
return self.data > other
|
|
386
|
+
return NotImplemented
|
|
387
|
+
|
|
388
|
+
def __ge__(self, other):
|
|
389
|
+
if isinstance(other, RangeList):
|
|
390
|
+
return self.data >= other.data
|
|
391
|
+
elif isinstance(other, list):
|
|
392
|
+
return self.data >= other
|
|
393
|
+
return NotImplemented
|
|
394
|
+
|
|
395
|
+
def __contains__(self, obj):
|
|
396
|
+
"""
|
|
397
|
+
Performs voodoo to compare the following:
|
|
398
|
+
* Compare single ports to tuples (i.e. 1700 in (1700, 1800))
|
|
399
|
+
* Compare tuples to tuples (i.e. (1700,1800) in (0,65535))
|
|
400
|
+
* Comparing tuple to integer ALWAYS returns False!!
|
|
401
|
+
"""
|
|
402
|
+
for elt in self.data:
|
|
403
|
+
if isinstance(elt, tuple):
|
|
404
|
+
if isinstance(obj, tuple):
|
|
405
|
+
## if obj is a tuple, see if it is within the range of elt
|
|
406
|
+
## using range here (add +1 to include elt[1])
|
|
407
|
+
## otherwise you end up 1 digit short of max
|
|
408
|
+
rng = range(elt[0], elt[1] + 1)
|
|
409
|
+
if obj[0] in rng and obj[1] in rng:
|
|
410
|
+
return True
|
|
411
|
+
else:
|
|
412
|
+
if elt[0] <= obj <= elt[1]:
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
elif hasattr(elt, "__contains__"):
|
|
416
|
+
if obj in elt:
|
|
417
|
+
return True
|
|
418
|
+
else:
|
|
419
|
+
if elt == obj:
|
|
420
|
+
return True
|
|
421
|
+
return False
|
|
422
|
+
|
|
423
|
+
def __repr__(self):
|
|
424
|
+
return f"<{self.__class__.__name__}: {str(self.data)}>"
|
|
425
|
+
|
|
426
|
+
def __str__(self):
|
|
427
|
+
return str(self.data)
|
|
428
|
+
|
|
429
|
+
# Straight passthrough of these:
|
|
430
|
+
def __hash__(self):
|
|
431
|
+
return self.data.__hash__(self.data)
|
|
432
|
+
|
|
433
|
+
def __len__(self):
|
|
434
|
+
return len(self.data)
|
|
435
|
+
|
|
436
|
+
def __getitem__(self, key):
|
|
437
|
+
return self.data[key]
|
|
438
|
+
|
|
439
|
+
def __setitem__(self, key, value):
|
|
440
|
+
self.data[key] = value
|
|
441
|
+
|
|
442
|
+
def __delitem__(self, key):
|
|
443
|
+
del self.data[key]
|
|
444
|
+
|
|
445
|
+
def __iter__(self):
|
|
446
|
+
return self.data.__iter__()
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class TIP(IPy.IP):
|
|
450
|
+
"""
|
|
451
|
+
Class based on IPy.IP, but with extensions for Trigger.
|
|
452
|
+
|
|
453
|
+
Currently, only the only extension is the ability to negate a network
|
|
454
|
+
block. Only used internally within the parser, as it's not complete
|
|
455
|
+
(doesn't interact well with IPy.IP objects). Does not handle IPv6 yet.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
def __init__(self, data, **kwargs):
|
|
459
|
+
# Insert logic to handle 'except' preserve negated flag if it exists
|
|
460
|
+
# already
|
|
461
|
+
negated = getattr(data, "negated", False)
|
|
462
|
+
|
|
463
|
+
# Handle 'inactive:' address objects by setting inactive flag
|
|
464
|
+
inactive = getattr(data, "inactive", False)
|
|
465
|
+
|
|
466
|
+
# Is data a string?
|
|
467
|
+
if isinstance(data, (str, unicode)):
|
|
468
|
+
d = data.split()
|
|
469
|
+
# This means we got something like "1.2.3.4 except" or "inactive:
|
|
470
|
+
# 1.2.3.4'
|
|
471
|
+
if len(d) == 2:
|
|
472
|
+
# Check if last word is 'except', set negated=True
|
|
473
|
+
if d[-1] == "except":
|
|
474
|
+
negated = True
|
|
475
|
+
data = d[0]
|
|
476
|
+
# Check if first word is 'inactive:', set inactive=True
|
|
477
|
+
elif d[0] == "inactive:":
|
|
478
|
+
inactive = True
|
|
479
|
+
data = d[1]
|
|
480
|
+
elif len(d) == 3:
|
|
481
|
+
if d[-1] == "except":
|
|
482
|
+
negated = True
|
|
483
|
+
if d[0] == "inactive:":
|
|
484
|
+
inactive = True
|
|
485
|
+
if inactive and negated:
|
|
486
|
+
data = d[1]
|
|
487
|
+
|
|
488
|
+
self.negated = negated # Set 'negated' variable
|
|
489
|
+
self.inactive = inactive # Set 'inactive' variable
|
|
490
|
+
IPy.IP.__init__(self, data, **kwargs)
|
|
491
|
+
|
|
492
|
+
# Make it print prefixes for /32, /128 if we're negated or inactive (and
|
|
493
|
+
# therefore assuming we're being used in a Juniper ACL.)
|
|
494
|
+
if self.negated or self.inactive:
|
|
495
|
+
self.NoPrefixForSingleIp = False
|
|
496
|
+
|
|
497
|
+
def _compare_to(self, other):
|
|
498
|
+
"""Helper method for comparison. Returns -1, 0, or 1."""
|
|
499
|
+
# Regular IPy sorts by prefix length before network base, but Juniper
|
|
500
|
+
# (our baseline) does not. We also need comparisons to be different for
|
|
501
|
+
# negation. Following Juniper's sorting, use IP compare, and then break
|
|
502
|
+
# ties where negated < not negated.
|
|
503
|
+
# Python 3: Implement cmp() logic inline
|
|
504
|
+
if self.ip < other.ip:
|
|
505
|
+
diff = -1
|
|
506
|
+
elif self.ip > other.ip:
|
|
507
|
+
diff = 1
|
|
508
|
+
else:
|
|
509
|
+
diff = 0
|
|
510
|
+
|
|
511
|
+
if diff == 0:
|
|
512
|
+
# If the same IP, compare by prefixlen
|
|
513
|
+
if self.prefixlen() < other.prefixlen():
|
|
514
|
+
diff = -1
|
|
515
|
+
elif self.prefixlen() > other.prefixlen():
|
|
516
|
+
diff = 1
|
|
517
|
+
else:
|
|
518
|
+
diff = 0
|
|
519
|
+
|
|
520
|
+
# If both negated, they're the same
|
|
521
|
+
if self.negated == other.negated:
|
|
522
|
+
return diff
|
|
523
|
+
# Sort to make negated < not negated
|
|
524
|
+
if self.negated:
|
|
525
|
+
diff = -1
|
|
526
|
+
else:
|
|
527
|
+
diff = 1
|
|
528
|
+
# Return the base comparison
|
|
529
|
+
return diff
|
|
530
|
+
|
|
531
|
+
def __lt__(self, other):
|
|
532
|
+
return self._compare_to(other) < 0
|
|
533
|
+
|
|
534
|
+
def __le__(self, other):
|
|
535
|
+
return self._compare_to(other) <= 0
|
|
536
|
+
|
|
537
|
+
def __gt__(self, other):
|
|
538
|
+
return self._compare_to(other) > 0
|
|
539
|
+
|
|
540
|
+
def __ge__(self, other):
|
|
541
|
+
return self._compare_to(other) >= 0
|
|
542
|
+
|
|
543
|
+
def __eq__(self, other):
|
|
544
|
+
return self._compare_to(other) == 0
|
|
545
|
+
|
|
546
|
+
def __ne__(self, other):
|
|
547
|
+
return self._compare_to(other) != 0
|
|
548
|
+
|
|
549
|
+
def __hash__(self):
|
|
550
|
+
# Make TIP hashable for use in sets and as dict keys
|
|
551
|
+
# Base hash on IP address, prefix length, and negation status
|
|
552
|
+
return hash((str(self.ip), self.prefixlen(), self.negated, self.inactive))
|
|
553
|
+
|
|
554
|
+
def __repr__(self):
|
|
555
|
+
# Just stick an 'except' at the end if except is set since we don't
|
|
556
|
+
# code to accept this in the constructor really just provided, for now,
|
|
557
|
+
# as a debugging aid.
|
|
558
|
+
rs = IPy.IP.__repr__(self)
|
|
559
|
+
if self.negated:
|
|
560
|
+
# Insert ' except' into the repr. (Yes, it's a hack!)
|
|
561
|
+
rs = rs.split("'")
|
|
562
|
+
rs[1] += " except"
|
|
563
|
+
rs = "'".join(rs) # Restore original repr
|
|
564
|
+
if self.inactive:
|
|
565
|
+
# Insert 'inactive: ' into the repr. (Yes, it's also a hack!)
|
|
566
|
+
rs = rs.split("'")
|
|
567
|
+
rs[1] = "inactive: " + rs[1]
|
|
568
|
+
rs = "'".join(rs) # Restore original repr
|
|
569
|
+
return rs
|
|
570
|
+
|
|
571
|
+
def __str__(self):
|
|
572
|
+
# IPy is not a new-style class, so the following doesn't work:
|
|
573
|
+
# return super(TIP, self).__str__()
|
|
574
|
+
rs = IPy.IP.__str__(self)
|
|
575
|
+
if self.negated:
|
|
576
|
+
rs += " except"
|
|
577
|
+
if self.inactive:
|
|
578
|
+
rs = "inactive: " + rs
|
|
579
|
+
return rs
|
|
580
|
+
|
|
581
|
+
def __contains__(self, item):
|
|
582
|
+
"""
|
|
583
|
+
Containment logic, including except.
|
|
584
|
+
"""
|
|
585
|
+
item = TIP(item)
|
|
586
|
+
# Calculate XOR
|
|
587
|
+
xor = self.negated ^ item.negated
|
|
588
|
+
# If one item is negated, it's never contained.
|
|
589
|
+
if xor:
|
|
590
|
+
return False
|
|
591
|
+
matched = IPy.IP.__contains__(self, item)
|
|
592
|
+
return matched ^ self.negated
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class Comment:
|
|
596
|
+
"""
|
|
597
|
+
Container for inline comments.
|
|
598
|
+
"""
|
|
599
|
+
|
|
600
|
+
def __init__(self, data):
|
|
601
|
+
self.data = data
|
|
602
|
+
|
|
603
|
+
def __repr__(self):
|
|
604
|
+
return f"<{self.__class__.__name__}: {repr(self.data)}>"
|
|
605
|
+
|
|
606
|
+
def __str__(self):
|
|
607
|
+
return self.data
|
|
608
|
+
|
|
609
|
+
def __len__(self):
|
|
610
|
+
"""Defining this method allows null comments to be false."""
|
|
611
|
+
return len(self.data)
|
|
612
|
+
|
|
613
|
+
def __iter__(self):
|
|
614
|
+
return self.data.__iter__()
|
|
615
|
+
|
|
616
|
+
def __contains__(self, item):
|
|
617
|
+
return item in self.data
|
|
618
|
+
|
|
619
|
+
def output_junos(self):
|
|
620
|
+
"""Output the Comment to JunOS format."""
|
|
621
|
+
return f"/*{self.data}*/"
|
|
622
|
+
|
|
623
|
+
def output_ios(self):
|
|
624
|
+
"""Output the Comment to IOS traditional format."""
|
|
625
|
+
if not self.data:
|
|
626
|
+
return "!"
|
|
627
|
+
|
|
628
|
+
data = self.data
|
|
629
|
+
if data.startswith("!"):
|
|
630
|
+
prefix = "!"
|
|
631
|
+
data = prefix + data
|
|
632
|
+
else:
|
|
633
|
+
prefix = "! "
|
|
634
|
+
lines = data.splitlines()
|
|
635
|
+
|
|
636
|
+
return "\n".join(prefix + line for line in lines)
|
|
637
|
+
|
|
638
|
+
def output_ios_named(self):
|
|
639
|
+
"""Output the Comment to IOS named format."""
|
|
640
|
+
return self.output_ios()
|
|
641
|
+
|
|
642
|
+
def output_iosxr(self):
|
|
643
|
+
"""Output the Comment to IOS XR format."""
|
|
644
|
+
return self.output_ios()
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class ACL:
|
|
648
|
+
"""
|
|
649
|
+
An abstract access-list object intended to be created by the :func:`parse`
|
|
650
|
+
function.
|
|
651
|
+
"""
|
|
652
|
+
|
|
653
|
+
def __init__(
|
|
654
|
+
self, name=None, terms=None, format=None, family=None, interface_specific=False
|
|
655
|
+
):
|
|
656
|
+
check_name(name, exceptions.ACLNameError, max_len=24)
|
|
657
|
+
self.name = name
|
|
658
|
+
self.family = family
|
|
659
|
+
self.interface_specific = interface_specific
|
|
660
|
+
self.format = format
|
|
661
|
+
self.policers = []
|
|
662
|
+
if terms:
|
|
663
|
+
self.terms = terms
|
|
664
|
+
else:
|
|
665
|
+
self.terms = TermList()
|
|
666
|
+
global Comments
|
|
667
|
+
self.comments = Comments
|
|
668
|
+
Comments = []
|
|
669
|
+
|
|
670
|
+
def __repr__(self):
|
|
671
|
+
return f"<ACL: {self.name}>"
|
|
672
|
+
|
|
673
|
+
def __str__(self):
|
|
674
|
+
return "\n".join(self.output(format=self.format, family=self.family))
|
|
675
|
+
|
|
676
|
+
def output(self, format=None, *largs, **kwargs):
|
|
677
|
+
"""
|
|
678
|
+
Output the ACL data in the specified format.
|
|
679
|
+
"""
|
|
680
|
+
if format is None:
|
|
681
|
+
format = self.format
|
|
682
|
+
return getattr(self, "output_" + format)(*largs, **kwargs)
|
|
683
|
+
|
|
684
|
+
def output_junos(self, replace=False, family=None):
|
|
685
|
+
"""
|
|
686
|
+
Output the ACL in JunOS format.
|
|
687
|
+
|
|
688
|
+
:param replace: If set the ACL is wrapped in a
|
|
689
|
+
``firewall { replace: ... }`` section.
|
|
690
|
+
:param family: If set, the value is used to wrap the ACL in a
|
|
691
|
+
``family inet { ...}`` section.
|
|
692
|
+
"""
|
|
693
|
+
if self.name is None:
|
|
694
|
+
raise exceptions.MissingACLName("JunOS format requires a name")
|
|
695
|
+
|
|
696
|
+
# Make sure we properly set 'family' so it's automatically used for
|
|
697
|
+
# printing.
|
|
698
|
+
if family is not None:
|
|
699
|
+
assert family in ("inet", "inet6")
|
|
700
|
+
else:
|
|
701
|
+
family = self.family
|
|
702
|
+
|
|
703
|
+
# Prep the filter body
|
|
704
|
+
out = [f"filter {self.name} {{"]
|
|
705
|
+
out += [" " + c.output_junos() for c in self.comments if c]
|
|
706
|
+
|
|
707
|
+
# Add the policers
|
|
708
|
+
if self.policers:
|
|
709
|
+
for policer in self.policers:
|
|
710
|
+
out += [" " + x for x in policer.output()]
|
|
711
|
+
|
|
712
|
+
# Add interface-specific
|
|
713
|
+
if self.interface_specific:
|
|
714
|
+
out += [" " + "interface-specific;"]
|
|
715
|
+
|
|
716
|
+
# Add the terms
|
|
717
|
+
for t in self.terms:
|
|
718
|
+
out += [" " + x for x in t.output_junos()]
|
|
719
|
+
out += ["}"]
|
|
720
|
+
|
|
721
|
+
# Wrap in 'firewall {}' thingy.
|
|
722
|
+
if replace:
|
|
723
|
+
"""
|
|
724
|
+
#out = ['firewall {', 'replace:'] + [' '+x for x in out] + ['}']
|
|
725
|
+
if family is None: # This happens more often
|
|
726
|
+
out = ['firewall {', 'replace:'] + [' '+x for x in out] + ['}']
|
|
727
|
+
else:
|
|
728
|
+
out = ['firewall {', family_head, 'replace:'] + [' '+x for x in out] + [family_tail, '}']
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
head = ["firewall {"]
|
|
732
|
+
body = ["replace:"] + [" " + x for x in out]
|
|
733
|
+
tail = ["}"]
|
|
734
|
+
if family is not None:
|
|
735
|
+
body = [f"family {family} {{"] + body + tail
|
|
736
|
+
body = [" " + x for x in body]
|
|
737
|
+
out = head + body + tail
|
|
738
|
+
|
|
739
|
+
return out
|
|
740
|
+
|
|
741
|
+
def output_ios(self, replace=False):
|
|
742
|
+
"""
|
|
743
|
+
Output the ACL in IOS traditional format.
|
|
744
|
+
|
|
745
|
+
:param replace: If set the ACL is preceded by a ``no access-list`` line.
|
|
746
|
+
"""
|
|
747
|
+
if self.name is None:
|
|
748
|
+
raise exceptions.MissingACLName("IOS format requires a name")
|
|
749
|
+
try:
|
|
750
|
+
x = int(self.name)
|
|
751
|
+
if not (100 <= x <= 199 or 2000 <= x <= 2699):
|
|
752
|
+
raise exceptions.BadACLName("IOS ACLs are 100-199 or 2000-2699")
|
|
753
|
+
except (TypeError, ValueError):
|
|
754
|
+
raise exceptions.BadACLName("IOS format requires a number as name")
|
|
755
|
+
out = [c.output_ios() for c in self.comments]
|
|
756
|
+
if self.policers:
|
|
757
|
+
raise exceptions.VendorSupportLacking("policers not supported in IOS")
|
|
758
|
+
if replace:
|
|
759
|
+
out.append("no access-list " + self.name)
|
|
760
|
+
prefix = f"access-list {self.name} "
|
|
761
|
+
for t in self.terms:
|
|
762
|
+
out += [x for x in t.output_ios(prefix)]
|
|
763
|
+
return out
|
|
764
|
+
|
|
765
|
+
def output_ios_brocade(self, replace=False, receive_acl=False):
|
|
766
|
+
"""
|
|
767
|
+
Output the ACL in Brocade-flavored IOS format.
|
|
768
|
+
|
|
769
|
+
The difference between this and "traditional" IOS are:
|
|
770
|
+
|
|
771
|
+
- Stripping of comments
|
|
772
|
+
- Appending of ``ip rebind-acl`` or ``ip rebind-receive-acl`` line
|
|
773
|
+
|
|
774
|
+
:param replace: If set the ACL is preceded by a ``no access-list`` line.
|
|
775
|
+
:param receive_acl: If set the ACL is suffixed with a ``ip
|
|
776
|
+
rebind-receive-acl' instead of ``ip rebind-acl``.
|
|
777
|
+
"""
|
|
778
|
+
self.strip_comments()
|
|
779
|
+
|
|
780
|
+
# Check if the is_receive_acl attr was set by the parser. This way we
|
|
781
|
+
# don't always have to pass the argument.
|
|
782
|
+
if hasattr(self, "is_receive_acl") and not receive_acl:
|
|
783
|
+
receive_acl = self.is_receive_acl
|
|
784
|
+
|
|
785
|
+
out = self.output_ios(replace=replace)
|
|
786
|
+
if receive_acl:
|
|
787
|
+
out.append(f"ip rebind-receive-acl {self.name}")
|
|
788
|
+
else:
|
|
789
|
+
out.append(f"ip rebind-acl {self.name}")
|
|
790
|
+
|
|
791
|
+
return out
|
|
792
|
+
|
|
793
|
+
def output_ios_named(self, replace=False):
|
|
794
|
+
"""
|
|
795
|
+
Output the ACL in IOS named format.
|
|
796
|
+
|
|
797
|
+
:param replace: If set the ACL is preceded by a ``no access-list`` line.
|
|
798
|
+
"""
|
|
799
|
+
if self.name is None:
|
|
800
|
+
raise exceptions.MissingACLName("IOS format requires a name")
|
|
801
|
+
out = [c.output_ios_named() for c in self.comments]
|
|
802
|
+
if self.policers:
|
|
803
|
+
raise exceptions.VendorSupportLacking("policers not supported in IOS")
|
|
804
|
+
if replace:
|
|
805
|
+
out.append("no ip access-list extended " + self.name)
|
|
806
|
+
out.append(f"ip access-list extended {self.name}")
|
|
807
|
+
for t in self.terms:
|
|
808
|
+
out += [x for x in t.output_ios_named(" ")]
|
|
809
|
+
return out
|
|
810
|
+
|
|
811
|
+
def output_iosxr(self, replace=False):
|
|
812
|
+
"""
|
|
813
|
+
Output the ACL in IOS XR format.
|
|
814
|
+
|
|
815
|
+
:param replace: If set the ACL is preceded by a ``no ipv4 access-list`` line.
|
|
816
|
+
"""
|
|
817
|
+
if self.name is None:
|
|
818
|
+
raise exceptions.MissingACLName("IOS XR format requires a name")
|
|
819
|
+
out = [c.output_iosxr() for c in self.comments]
|
|
820
|
+
if self.policers:
|
|
821
|
+
raise exceptions.VendorSupportLacking("policers not supported in IOS")
|
|
822
|
+
if replace:
|
|
823
|
+
out.append("no ipv4 access-list " + self.name)
|
|
824
|
+
out.append("ipv4 access-list " + self.name)
|
|
825
|
+
counter = 0 # 10 PRINT "CISCO SUCKS" 20 GOTO 10
|
|
826
|
+
for t in self.terms:
|
|
827
|
+
if t.name is None:
|
|
828
|
+
for line in t.output_ios():
|
|
829
|
+
counter = counter + 10
|
|
830
|
+
out += [" %d %s" % (counter, line)]
|
|
831
|
+
else:
|
|
832
|
+
try:
|
|
833
|
+
counter = int(t.name)
|
|
834
|
+
if not 1 <= counter <= 2147483646:
|
|
835
|
+
raise exceptions.BadTermName("Term %d out of range" % counter)
|
|
836
|
+
line = t.output_iosxr()
|
|
837
|
+
if len(line) > 1:
|
|
838
|
+
raise exceptions.VendorSupportLacking("one name per line")
|
|
839
|
+
out += [" " + line[0]]
|
|
840
|
+
except ValueError:
|
|
841
|
+
raise exceptions.BadTermName("IOS XR requires numbered terms")
|
|
842
|
+
return out
|
|
843
|
+
|
|
844
|
+
def name_terms(self):
|
|
845
|
+
"""Assign names to all unnamed terms."""
|
|
846
|
+
n = 1
|
|
847
|
+
for t in self.terms:
|
|
848
|
+
if t.name is None:
|
|
849
|
+
t.name = "T%d" % n
|
|
850
|
+
n += 1
|
|
851
|
+
|
|
852
|
+
def strip_comments(self):
|
|
853
|
+
"""Strips all comments from ACL header and all terms."""
|
|
854
|
+
self.comments = []
|
|
855
|
+
for term in self.terms:
|
|
856
|
+
term.comments = []
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
class Term:
|
|
860
|
+
"""An individual term from which an ACL is made"""
|
|
861
|
+
|
|
862
|
+
def __init__(
|
|
863
|
+
self,
|
|
864
|
+
name=None,
|
|
865
|
+
action="accept",
|
|
866
|
+
match=None,
|
|
867
|
+
modifiers=None,
|
|
868
|
+
inactive=False,
|
|
869
|
+
isglobal=False,
|
|
870
|
+
extra=None,
|
|
871
|
+
):
|
|
872
|
+
self.name = name
|
|
873
|
+
self.action = action
|
|
874
|
+
self.inactive = inactive
|
|
875
|
+
self.isglobal = isglobal
|
|
876
|
+
self.extra = extra
|
|
877
|
+
self.makediscard = False # set to True if 'make discard' is used
|
|
878
|
+
if match is None:
|
|
879
|
+
self.match = Matches()
|
|
880
|
+
else:
|
|
881
|
+
self.match = match
|
|
882
|
+
|
|
883
|
+
if modifiers is None:
|
|
884
|
+
self.modifiers = Modifiers()
|
|
885
|
+
else:
|
|
886
|
+
self.modifiers = modifiers
|
|
887
|
+
|
|
888
|
+
global Comments
|
|
889
|
+
self.comments = Comments
|
|
890
|
+
Comments = []
|
|
891
|
+
|
|
892
|
+
def __repr__(self):
|
|
893
|
+
return f"<Term: {self.name}>"
|
|
894
|
+
|
|
895
|
+
def getname(self):
|
|
896
|
+
return self.__name
|
|
897
|
+
|
|
898
|
+
def setname(self, name):
|
|
899
|
+
check_name(name, exceptions.BadTermName)
|
|
900
|
+
self.__name = name
|
|
901
|
+
|
|
902
|
+
def delname(self):
|
|
903
|
+
self.name = None
|
|
904
|
+
|
|
905
|
+
name = property(getname, setname, delname)
|
|
906
|
+
|
|
907
|
+
def getaction(self):
|
|
908
|
+
return self.__action
|
|
909
|
+
|
|
910
|
+
def setaction(self, action):
|
|
911
|
+
if action is None:
|
|
912
|
+
action = "accept"
|
|
913
|
+
if action == "next term":
|
|
914
|
+
action = ("next", "term")
|
|
915
|
+
if isinstance(action, str):
|
|
916
|
+
action = (action,)
|
|
917
|
+
if len(action) > 2:
|
|
918
|
+
raise exceptions.ActionError(
|
|
919
|
+
f'too many arguments to action "{str(action)}"'
|
|
920
|
+
)
|
|
921
|
+
action = tuple(action)
|
|
922
|
+
if action in (("accept",), ("discard",), ("reject",), ("next", "term")):
|
|
923
|
+
self.__action = action
|
|
924
|
+
elif action == ("permit",):
|
|
925
|
+
self.__action = ("accept",)
|
|
926
|
+
elif action == ("deny",):
|
|
927
|
+
self.__action = ("reject",)
|
|
928
|
+
elif action[0] == "reject":
|
|
929
|
+
if action[1] not in icmp_reject_codes:
|
|
930
|
+
raise exceptions.BadRejectCode("invalid rejection code " + action[1])
|
|
931
|
+
if action[1] == icmp_reject_codes[0]:
|
|
932
|
+
action = ("reject",)
|
|
933
|
+
self.__action = action
|
|
934
|
+
elif action[0] == "routing-instance":
|
|
935
|
+
check_name(action[1], exceptions.BadRoutingInstanceName)
|
|
936
|
+
self.__action = action
|
|
937
|
+
else:
|
|
938
|
+
raise exceptions.UnknownActionName(f'unknown action "{str(action)}"')
|
|
939
|
+
|
|
940
|
+
def delaction(self):
|
|
941
|
+
self.action = "accept"
|
|
942
|
+
|
|
943
|
+
action = property(getaction, setaction, delaction)
|
|
944
|
+
|
|
945
|
+
def set_action_or_modifier(self, action):
|
|
946
|
+
"""
|
|
947
|
+
Add or replace a modifier, or set the primary action. This method exists
|
|
948
|
+
for the convenience of parsers.
|
|
949
|
+
"""
|
|
950
|
+
try:
|
|
951
|
+
self.action = action
|
|
952
|
+
except exceptions.UnknownActionName:
|
|
953
|
+
if not isinstance(action, tuple):
|
|
954
|
+
self.modifiers[action] = None
|
|
955
|
+
else:
|
|
956
|
+
if len(action) == 1:
|
|
957
|
+
self.modifiers[action[0]] = None
|
|
958
|
+
else:
|
|
959
|
+
self.modifiers[action[0]] = action[1]
|
|
960
|
+
|
|
961
|
+
def output(self, format, *largs, **kwargs):
|
|
962
|
+
"""
|
|
963
|
+
Output the term to the specified format
|
|
964
|
+
|
|
965
|
+
:param format: The desired output format.
|
|
966
|
+
"""
|
|
967
|
+
return getattr(self, "output_" + format)(*largs, **kwargs)
|
|
968
|
+
|
|
969
|
+
def output_junos(self, *args, **kwargs):
|
|
970
|
+
"""Convert the term to JunOS format."""
|
|
971
|
+
if self.name is None:
|
|
972
|
+
raise exceptions.MissingTermName("JunOS requires terms to be named")
|
|
973
|
+
out = ["{}term {} {{".format(self.inactive and "inactive: " or "", self.name)]
|
|
974
|
+
out += [" " + c.output_junos() for c in self.comments if c]
|
|
975
|
+
if self.extra:
|
|
976
|
+
blah = str(self.extra)
|
|
977
|
+
out += "/*", blah, "*/"
|
|
978
|
+
if self.match:
|
|
979
|
+
out.append(" from {")
|
|
980
|
+
out += [" " * 8 + x for x in self.match.output_junos()]
|
|
981
|
+
out.append(" }")
|
|
982
|
+
out.append(" then {")
|
|
983
|
+
acttext = " {};".format(" ".join(self.action))
|
|
984
|
+
# add a comment if 'make discard' is in use
|
|
985
|
+
if self.makediscard:
|
|
986
|
+
acttext += " /* REALLY AN ACCEPT, MODIFIED BY 'make discard' ABOVE */"
|
|
987
|
+
out.append(acttext)
|
|
988
|
+
out += [" " * 8 + x for x in self.modifiers.output_junos()]
|
|
989
|
+
out.append(" }")
|
|
990
|
+
out.append("}")
|
|
991
|
+
return out
|
|
992
|
+
|
|
993
|
+
def _ioslike(self, prefix=""):
|
|
994
|
+
if self.inactive:
|
|
995
|
+
raise exceptions.VendorSupportLacking("inactive terms not supported by IOS")
|
|
996
|
+
action = ""
|
|
997
|
+
if self.action == ("accept",):
|
|
998
|
+
action = "permit "
|
|
999
|
+
# elif self.action == ('reject',):
|
|
1000
|
+
elif self.action in (("reject",), ("discard",)):
|
|
1001
|
+
action = "deny "
|
|
1002
|
+
else:
|
|
1003
|
+
raise VendorSupportLacking(
|
|
1004
|
+
'"{}" action not supported by IOS'.format(" ".join(self.action))
|
|
1005
|
+
)
|
|
1006
|
+
suffix = ""
|
|
1007
|
+
for k, v in self.modifiers.items():
|
|
1008
|
+
if k == "syslog":
|
|
1009
|
+
suffix += " log"
|
|
1010
|
+
elif k == "count":
|
|
1011
|
+
pass # counters are implicit in IOS
|
|
1012
|
+
else:
|
|
1013
|
+
raise exceptions.VendorSupportLacking(
|
|
1014
|
+
f'"{k}" modifier not supported by IOS'
|
|
1015
|
+
)
|
|
1016
|
+
return [prefix + action + x + suffix for x in self.match.output_ios()]
|
|
1017
|
+
|
|
1018
|
+
def output_ios(self, prefix=None, acl_name=None):
|
|
1019
|
+
"""
|
|
1020
|
+
Output term to IOS traditional format.
|
|
1021
|
+
|
|
1022
|
+
:param prefix: Prefix to use, default: 'access-list'
|
|
1023
|
+
:param acl_name: Name of access-list to display
|
|
1024
|
+
"""
|
|
1025
|
+
comments = [c.output_ios() for c in self.comments]
|
|
1026
|
+
# If prefix isn't set, but name is, force the template
|
|
1027
|
+
if prefix is None and acl_name is not None:
|
|
1028
|
+
prefix = f"access-list {acl_name} "
|
|
1029
|
+
|
|
1030
|
+
# Or if prefix is set, but acl_name isn't, make sure prefix ends with ' '
|
|
1031
|
+
elif prefix is not None and acl_name is None:
|
|
1032
|
+
if not prefix.endswith(" "):
|
|
1033
|
+
prefix += " "
|
|
1034
|
+
|
|
1035
|
+
# Or if both are set, use them
|
|
1036
|
+
elif prefix is not None and acl_name is not None:
|
|
1037
|
+
prefix = f"{prefix.strip()} {acl_name.strip()} "
|
|
1038
|
+
|
|
1039
|
+
# Otherwise no prefix
|
|
1040
|
+
else:
|
|
1041
|
+
prefix = ""
|
|
1042
|
+
|
|
1043
|
+
return comments + self._ioslike(prefix)
|
|
1044
|
+
|
|
1045
|
+
def output_ios_named(self, prefix="", *args, **kwargs):
|
|
1046
|
+
"""Output term to IOS named format."""
|
|
1047
|
+
comments = [c.output_ios_named() for c in self.comments]
|
|
1048
|
+
return comments + self._ioslike(prefix)
|
|
1049
|
+
|
|
1050
|
+
def output_iosxr(self, prefix="", *args, **kwargs):
|
|
1051
|
+
"""Output term to IOS XR format."""
|
|
1052
|
+
comments = [c.output_iosxr() for c in self.comments]
|
|
1053
|
+
return comments + self._ioslike(prefix)
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
class TermList(list):
|
|
1057
|
+
"""Container class for Term objects within an ACL object."""
|
|
1058
|
+
|
|
1059
|
+
pass
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
class Protocol:
|
|
1063
|
+
"""
|
|
1064
|
+
A protocol object used for access membership tests in :class:`Term` objects.
|
|
1065
|
+
Acts like an integer, but stringify into a name if possible.
|
|
1066
|
+
"""
|
|
1067
|
+
|
|
1068
|
+
num2name = {
|
|
1069
|
+
1: "icmp",
|
|
1070
|
+
2: "igmp",
|
|
1071
|
+
4: "ipip",
|
|
1072
|
+
6: "tcp",
|
|
1073
|
+
8: "egp",
|
|
1074
|
+
17: "udp",
|
|
1075
|
+
41: "ipv6",
|
|
1076
|
+
# 46: 'rsvp',
|
|
1077
|
+
47: "gre",
|
|
1078
|
+
50: "esp",
|
|
1079
|
+
51: "ah",
|
|
1080
|
+
89: "ospf",
|
|
1081
|
+
94: "nos",
|
|
1082
|
+
103: "pim",
|
|
1083
|
+
# 112: 'vrrp' # Breaks Cisco compatibility
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
name2num = {v: k for k, v in num2name.items()}
|
|
1087
|
+
name2num["ahp"] = 51 # undocumented Cisco special name
|
|
1088
|
+
|
|
1089
|
+
def __init__(self, arg):
|
|
1090
|
+
if isinstance(arg, Protocol):
|
|
1091
|
+
self.value = arg.value
|
|
1092
|
+
elif arg in Protocol.name2num:
|
|
1093
|
+
self.value = Protocol.name2num[arg]
|
|
1094
|
+
else:
|
|
1095
|
+
self.value = int(arg)
|
|
1096
|
+
|
|
1097
|
+
def __str__(self):
|
|
1098
|
+
if self.value in Protocol.num2name:
|
|
1099
|
+
return Protocol.num2name[self.value]
|
|
1100
|
+
else:
|
|
1101
|
+
return str(self.value)
|
|
1102
|
+
|
|
1103
|
+
def __repr__(self):
|
|
1104
|
+
return f"<{self.__class__.__name__}: {str(self)}>"
|
|
1105
|
+
|
|
1106
|
+
def _get_compare_value(self, other):
|
|
1107
|
+
"""Helper to get comparison value from other."""
|
|
1108
|
+
try:
|
|
1109
|
+
return Protocol(other).value
|
|
1110
|
+
except (ValueError, TypeError):
|
|
1111
|
+
return NotImplemented
|
|
1112
|
+
|
|
1113
|
+
def __eq__(self, other):
|
|
1114
|
+
"""Protocol(6) == 'tcp' == 6 == Protocol('6')."""
|
|
1115
|
+
other_value = self._get_compare_value(other)
|
|
1116
|
+
if other_value is NotImplemented:
|
|
1117
|
+
return NotImplemented
|
|
1118
|
+
return self.value == other_value
|
|
1119
|
+
|
|
1120
|
+
def __ne__(self, other):
|
|
1121
|
+
result = self.__eq__(other)
|
|
1122
|
+
if result is NotImplemented:
|
|
1123
|
+
return NotImplemented
|
|
1124
|
+
return not result
|
|
1125
|
+
|
|
1126
|
+
def __lt__(self, other):
|
|
1127
|
+
other_value = self._get_compare_value(other)
|
|
1128
|
+
if other_value is NotImplemented:
|
|
1129
|
+
return NotImplemented
|
|
1130
|
+
return self.value < other_value
|
|
1131
|
+
|
|
1132
|
+
def __le__(self, other):
|
|
1133
|
+
other_value = self._get_compare_value(other)
|
|
1134
|
+
if other_value is NotImplemented:
|
|
1135
|
+
return NotImplemented
|
|
1136
|
+
return self.value <= other_value
|
|
1137
|
+
|
|
1138
|
+
def __gt__(self, other):
|
|
1139
|
+
other_value = self._get_compare_value(other)
|
|
1140
|
+
if other_value is NotImplemented:
|
|
1141
|
+
return NotImplemented
|
|
1142
|
+
return self.value > other_value
|
|
1143
|
+
|
|
1144
|
+
def __ge__(self, other):
|
|
1145
|
+
other_value = self._get_compare_value(other)
|
|
1146
|
+
if other_value is NotImplemented:
|
|
1147
|
+
return NotImplemented
|
|
1148
|
+
return self.value >= other_value
|
|
1149
|
+
|
|
1150
|
+
def __hash__(self):
|
|
1151
|
+
return hash(self.value)
|
|
1152
|
+
|
|
1153
|
+
def __getattr__(self, name):
|
|
1154
|
+
"""Allow arithmetic operations to work."""
|
|
1155
|
+
return getattr(self.value, name)
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
class Matches(MyDict):
|
|
1159
|
+
"""
|
|
1160
|
+
Container class for Term.match object used for membership tests on
|
|
1161
|
+
access checks.
|
|
1162
|
+
"""
|
|
1163
|
+
|
|
1164
|
+
def __setitem__(self, key, arg):
|
|
1165
|
+
if key in (
|
|
1166
|
+
"ah-spi",
|
|
1167
|
+
"destination-mac-address",
|
|
1168
|
+
"ether-type",
|
|
1169
|
+
"esp-spi",
|
|
1170
|
+
"forwarding-class",
|
|
1171
|
+
"interface-group",
|
|
1172
|
+
"source-mac-address",
|
|
1173
|
+
"vlan-ether-type",
|
|
1174
|
+
"fragment-flags",
|
|
1175
|
+
"source-class",
|
|
1176
|
+
"destination-class",
|
|
1177
|
+
):
|
|
1178
|
+
raise NotImplementedError(f"match on {key} not implemented")
|
|
1179
|
+
|
|
1180
|
+
if arg is None:
|
|
1181
|
+
raise exceptions.MatchError("match must have an argument")
|
|
1182
|
+
|
|
1183
|
+
negated = False
|
|
1184
|
+
if key.endswith("-except"):
|
|
1185
|
+
negated = True
|
|
1186
|
+
key = key[:-7]
|
|
1187
|
+
|
|
1188
|
+
if key in ("port", "source-port", "destination-port"):
|
|
1189
|
+
# Python 3: map() returns an iterator, convert to list
|
|
1190
|
+
arg = list(map(do_port_lookup, arg))
|
|
1191
|
+
check_range(arg, 0, 65535)
|
|
1192
|
+
elif key == "protocol":
|
|
1193
|
+
arg = list(map(do_protocol_lookup, arg))
|
|
1194
|
+
check_range(arg, 0, 255)
|
|
1195
|
+
elif key == "fragment-offset":
|
|
1196
|
+
arg = list(map(do_port_lookup, arg))
|
|
1197
|
+
check_range(arg, 0, 8191)
|
|
1198
|
+
elif key == "icmp-type":
|
|
1199
|
+
arg = list(map(do_icmp_type_lookup, arg))
|
|
1200
|
+
check_range(arg, 0, 255)
|
|
1201
|
+
elif key == "icmp-code":
|
|
1202
|
+
arg = list(map(do_icmp_code_lookup, arg))
|
|
1203
|
+
check_range(arg, 0, 255)
|
|
1204
|
+
elif key == "icmp-type-code":
|
|
1205
|
+
# Not intended for external use; this is for parser convenience.
|
|
1206
|
+
self["icmp-type"] = [arg[0]]
|
|
1207
|
+
try:
|
|
1208
|
+
self["icmp-code"] = [arg[1]]
|
|
1209
|
+
except IndexError:
|
|
1210
|
+
try:
|
|
1211
|
+
del self["icmp-code"]
|
|
1212
|
+
except KeyError:
|
|
1213
|
+
pass
|
|
1214
|
+
return
|
|
1215
|
+
elif key == "packet-length":
|
|
1216
|
+
arg = list(map(int, arg))
|
|
1217
|
+
check_range(arg, 0, 65535)
|
|
1218
|
+
elif key in ("address", "source-address", "destination-address"):
|
|
1219
|
+
arg = list(map(TIP, arg))
|
|
1220
|
+
elif key in ("prefix-list", "source-prefix-list", "destination-prefix-list"):
|
|
1221
|
+
for pl in arg:
|
|
1222
|
+
check_name(pl, exceptions.MatchError)
|
|
1223
|
+
elif key in tcp_flag_specials:
|
|
1224
|
+
# This cannot be the final form of how to represent tcp-flags.
|
|
1225
|
+
# Instead, we need to implement a real parser for it.
|
|
1226
|
+
# See: http://www.juniper.net/techpubs/software/junos/junos73/swconfig73-policy/html/firewall-config14.html
|
|
1227
|
+
arg = [tcp_flag_specials[key]]
|
|
1228
|
+
key = "tcp-flags"
|
|
1229
|
+
elif key == "tcp-flags":
|
|
1230
|
+
pass
|
|
1231
|
+
elif key == "ip-options":
|
|
1232
|
+
arg = list(map(do_ip_option_lookup, arg))
|
|
1233
|
+
check_range(arg, 0, 255)
|
|
1234
|
+
elif key in ("first-fragment", "is-fragment"):
|
|
1235
|
+
arg = []
|
|
1236
|
+
elif key == "dscp":
|
|
1237
|
+
pass
|
|
1238
|
+
elif key == "precedence":
|
|
1239
|
+
pass
|
|
1240
|
+
else:
|
|
1241
|
+
raise exceptions.UnknownMatchType(f'unknown match type "{key}"')
|
|
1242
|
+
|
|
1243
|
+
arg = RangeList(arg)
|
|
1244
|
+
|
|
1245
|
+
replacing = [key, key + "-except"]
|
|
1246
|
+
for type in ("port", "address", "prefix-list"):
|
|
1247
|
+
if key == type:
|
|
1248
|
+
for sd in ("source", "destination"):
|
|
1249
|
+
replacing += [sd + "-" + type, sd + "-" + type + "-except"]
|
|
1250
|
+
for k in replacing:
|
|
1251
|
+
try:
|
|
1252
|
+
del self[k]
|
|
1253
|
+
except KeyError:
|
|
1254
|
+
pass
|
|
1255
|
+
if negated:
|
|
1256
|
+
super().__setitem__(key + "-except", arg)
|
|
1257
|
+
else:
|
|
1258
|
+
super().__setitem__(key, arg)
|
|
1259
|
+
|
|
1260
|
+
def junos_str(self, pair):
|
|
1261
|
+
"""
|
|
1262
|
+
Convert a 2-tuple into a hyphenated string, e.g. a range of ports. If
|
|
1263
|
+
not a tuple, tries to treat it as IPs or failing that, casts it to a
|
|
1264
|
+
string.
|
|
1265
|
+
|
|
1266
|
+
:param pair:
|
|
1267
|
+
The 2-tuple to convert.
|
|
1268
|
+
"""
|
|
1269
|
+
try:
|
|
1270
|
+
return "%s-%s" % pair # Tuples back to ranges.
|
|
1271
|
+
except TypeError:
|
|
1272
|
+
try:
|
|
1273
|
+
# Make it print prefixes for /32, /128
|
|
1274
|
+
pair.NoPrefixForSingleIp = False
|
|
1275
|
+
except AttributeError:
|
|
1276
|
+
pass
|
|
1277
|
+
return str(pair)
|
|
1278
|
+
|
|
1279
|
+
def ios_port_str(self, ports):
|
|
1280
|
+
"""
|
|
1281
|
+
Convert a list of tuples back to ranges, then to strings.
|
|
1282
|
+
|
|
1283
|
+
:param ports:
|
|
1284
|
+
A list of port tuples, e.g. [(0,65535), (1,2)].
|
|
1285
|
+
"""
|
|
1286
|
+
a = []
|
|
1287
|
+
for port in ports:
|
|
1288
|
+
try:
|
|
1289
|
+
if port[0] == 0:
|
|
1290
|
+
# Omit ports if 0-65535
|
|
1291
|
+
if port[1] == 65535:
|
|
1292
|
+
continue
|
|
1293
|
+
a.append("lt %s" % (port[1] + 1))
|
|
1294
|
+
elif port[1] == 65535:
|
|
1295
|
+
a.append("gt %s" % (port[0] - 1))
|
|
1296
|
+
else:
|
|
1297
|
+
a.append("range {} {}".format(*port))
|
|
1298
|
+
except TypeError:
|
|
1299
|
+
a.append(f"eq {str(port)}")
|
|
1300
|
+
return a
|
|
1301
|
+
|
|
1302
|
+
def ios_address_str(self, addrs):
|
|
1303
|
+
"""
|
|
1304
|
+
Convert a list of addresses to IOS-style stupid strings.
|
|
1305
|
+
|
|
1306
|
+
:param addrs:
|
|
1307
|
+
List of IP address objects.
|
|
1308
|
+
"""
|
|
1309
|
+
a = []
|
|
1310
|
+
for addr in addrs:
|
|
1311
|
+
# xxx flag negated addresses?
|
|
1312
|
+
if addr.negated:
|
|
1313
|
+
raise exceptions.VendorSupportLacking(
|
|
1314
|
+
"negated addresses are not supported in IOS"
|
|
1315
|
+
)
|
|
1316
|
+
if addr.prefixlen() == 0:
|
|
1317
|
+
a.append("any")
|
|
1318
|
+
elif addr.prefixlen() == 32:
|
|
1319
|
+
a.append(f"host {addr.net()}")
|
|
1320
|
+
else:
|
|
1321
|
+
inverse_mask = make_inverse_mask(addr.prefixlen())
|
|
1322
|
+
a.append(f"{addr.net()} {inverse_mask}")
|
|
1323
|
+
return a
|
|
1324
|
+
|
|
1325
|
+
def output_junos(self):
|
|
1326
|
+
"""Return a list that can form the ``from { ... }`` clause of the term."""
|
|
1327
|
+
a = []
|
|
1328
|
+
# Python 3: dict.keys() returns a view, convert to list for sorting
|
|
1329
|
+
keys = sorted(self.keys(), key=lambda x: junos_match_order.get(x, 999))
|
|
1330
|
+
for s in keys:
|
|
1331
|
+
# Python 3: map() returns an iterator, convert to list
|
|
1332
|
+
matches = list(map(self.junos_str, self[s]))
|
|
1333
|
+
has_negated_addrs = any(m for m in matches if m.endswith(" except"))
|
|
1334
|
+
if s in address_matches:
|
|
1335
|
+
# Check to see if any of the added is any, and if so break out,
|
|
1336
|
+
# but only if none of the addresses is "negated".
|
|
1337
|
+
if "0.0.0.0/0" in matches and not has_negated_addrs:
|
|
1338
|
+
continue
|
|
1339
|
+
a.append(s + " {")
|
|
1340
|
+
a += [" " + x + ";" for x in matches]
|
|
1341
|
+
a.append("}")
|
|
1342
|
+
continue
|
|
1343
|
+
if s == "tcp-flags" and len(self[s]) == 1:
|
|
1344
|
+
try:
|
|
1345
|
+
a.append(tcp_flag_rev[self[s][0]] + ";")
|
|
1346
|
+
continue
|
|
1347
|
+
except KeyError:
|
|
1348
|
+
pass
|
|
1349
|
+
if len(matches) == 1:
|
|
1350
|
+
s += " " + matches[0]
|
|
1351
|
+
elif len(matches) > 1:
|
|
1352
|
+
s += " [ " + " ".join(matches) + " ]"
|
|
1353
|
+
a.append(s + ";")
|
|
1354
|
+
return a
|
|
1355
|
+
|
|
1356
|
+
def output_ios(self):
|
|
1357
|
+
"""Return a string of IOS ACL bodies."""
|
|
1358
|
+
# This is a mess! Thanks, Cisco.
|
|
1359
|
+
protos = []
|
|
1360
|
+
sources = []
|
|
1361
|
+
dests = []
|
|
1362
|
+
sourceports = []
|
|
1363
|
+
destports = []
|
|
1364
|
+
trailers = []
|
|
1365
|
+
for key, arg in self.items():
|
|
1366
|
+
if key == "source-port":
|
|
1367
|
+
sourceports += self.ios_port_str(arg)
|
|
1368
|
+
elif key == "destination-port":
|
|
1369
|
+
destports += self.ios_port_str(arg)
|
|
1370
|
+
elif key == "source-address":
|
|
1371
|
+
sources += self.ios_address_str(arg)
|
|
1372
|
+
elif key == "destination-address":
|
|
1373
|
+
dests += self.ios_address_str(arg)
|
|
1374
|
+
elif key == "protocol":
|
|
1375
|
+
# Python 3: map() returns an iterator, convert to list
|
|
1376
|
+
protos += list(map(str, arg))
|
|
1377
|
+
elif key == "icmp-type":
|
|
1378
|
+
for type in arg.expanded():
|
|
1379
|
+
if "icmp-code" in self:
|
|
1380
|
+
for code in self["icmp-code"]:
|
|
1381
|
+
try:
|
|
1382
|
+
destports.append(ios_icmp_names[(type, code)])
|
|
1383
|
+
except KeyError:
|
|
1384
|
+
destports.append("%d %d" % (type, code))
|
|
1385
|
+
else:
|
|
1386
|
+
try:
|
|
1387
|
+
destports.append(ios_icmp_names[(type,)])
|
|
1388
|
+
except KeyError:
|
|
1389
|
+
destports.append(str(type))
|
|
1390
|
+
elif key == "icmp-code":
|
|
1391
|
+
if "icmp-type" not in self:
|
|
1392
|
+
raise exceptions.VendorSupportLacking("need ICMP code w/type")
|
|
1393
|
+
elif key == "tcp-flags":
|
|
1394
|
+
if arg != [tcp_flag_specials["tcp-established"]]:
|
|
1395
|
+
raise exceptions.VendorSupportLacking(
|
|
1396
|
+
'IOS supports only "tcp-flags established"'
|
|
1397
|
+
)
|
|
1398
|
+
trailers += ["established"]
|
|
1399
|
+
else:
|
|
1400
|
+
raise exceptions.VendorSupportLacking(f'"{key}" not in IOS')
|
|
1401
|
+
if not protos:
|
|
1402
|
+
protos = ["ip"]
|
|
1403
|
+
if not sources:
|
|
1404
|
+
sources = ["any"]
|
|
1405
|
+
if not dests:
|
|
1406
|
+
dests = ["any"]
|
|
1407
|
+
if not sourceports:
|
|
1408
|
+
sourceports = [""]
|
|
1409
|
+
if not destports:
|
|
1410
|
+
destports = [""]
|
|
1411
|
+
if not trailers:
|
|
1412
|
+
trailers = [""]
|
|
1413
|
+
a = []
|
|
1414
|
+
|
|
1415
|
+
# There is no mercy in this Dojo!!
|
|
1416
|
+
for proto in protos:
|
|
1417
|
+
for source in sources:
|
|
1418
|
+
for sourceport in sourceports:
|
|
1419
|
+
for dest in dests:
|
|
1420
|
+
for destport in destports:
|
|
1421
|
+
for trailer in trailers:
|
|
1422
|
+
s = proto + " " + source
|
|
1423
|
+
if sourceport:
|
|
1424
|
+
s += " " + sourceport
|
|
1425
|
+
s += " " + dest
|
|
1426
|
+
if destport:
|
|
1427
|
+
s += " " + destport
|
|
1428
|
+
if trailer:
|
|
1429
|
+
s += " " + trailer
|
|
1430
|
+
a.append(s)
|
|
1431
|
+
return a
|