From a33c44a0f85f7bd9028576635b0250934885440d Mon Sep 17 00:00:00 2001 From: Nathan Ward Date: Sat, 10 Sep 2022 16:25:56 +1200 Subject: [PATCH 1/2] Add ttl-except to juniper --- capirca/lib/juniper.py | 10 ++++++++-- capirca/lib/policy.py | 23 ++++++++++++++++++++++- doc/generators/juniper.md | 1 + tests/lib/juniper_test.py | 19 +++++++++++++++++++ tests/lib/policy_test.py | 22 ++++++++++++++++++++++ tests/lib/srxlo_test.py | 1 + 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/capirca/lib/juniper.py b/capirca/lib/juniper.py index 21a6d39a..575984f7 100644 --- a/capirca/lib/juniper.py +++ b/capirca/lib/juniper.py @@ -323,7 +323,8 @@ def __str__(self): self.term.source_prefix or self.term.source_prefix_except or self.term.traffic_type or - self.term.ttl) + self.term.ttl or + self.term.ttl_except) if has_match_criteria: config.Append('from {') @@ -452,6 +453,10 @@ def __str__(self): if self.term.ttl and self.term_type == 'inet': config.Append('ttl %s;' % self.term.ttl) + # ttl-except, same logic as ttl above. + if self.term.ttl_except and self.term_type == 'inet': + config.Append('ttl-except %s;' % self.term.ttl_except) + # protocol if self.term.protocol: # both are supported on JunOS, but only icmp6 is supported @@ -923,7 +928,8 @@ def _BuildTokens(self): 'source_prefix_except', 'traffic_type', 'traffic_class_count', - 'ttl'} + 'ttl', + 'ttl_except'} supported_sub_tokens.update({ 'option': { 'established', diff --git a/capirca/lib/policy.py b/capirca/lib/policy.py index 35981a31..47cddd36 100644 --- a/capirca/lib/policy.py +++ b/capirca/lib/policy.py @@ -438,6 +438,7 @@ def __init__(self, obj): self.source_port = [] self.source_prefix = [] self.ttl = None + self.ttl_except = None self.verbatim = [] # juniper specific. self.packet_length = None @@ -793,6 +794,8 @@ def __str__(self): ret_str.append(' platform_exclude: %s' % self.platform_exclude) if self.ttl: ret_str.append(' ttl: %s' % self.ttl) + if self.ttl_except: + ret_str.append(' ttl_except: %s' % self.ttl_except) if self.timeout: ret_str.append(' timeout: %s' % self.timeout) if self.vpn: @@ -884,6 +887,9 @@ def __eq__(self, other): if self.ttl != other.ttl: return False + if self.ttl_except != other.ttl_except: + return False + if sorted(self.logging) != sorted(other.logging): return False if self.log_limit != other.log_limit: @@ -1246,6 +1252,8 @@ def AddObject(self, obj): self.vpn = (obj.value[0], obj.value[1]) elif obj.var_type is VarType.TTL: self.ttl = int(obj.value) + elif obj.var_type is VarType.TTL_EXCEPT: + self.ttl_except = int(obj.value) elif obj.var_type is VarType.TARGET_RESOURCES: self.target_resources.append(obj.value) elif obj.var_type is VarType.TARGET_SERVICE_ACCOUNTS: @@ -1351,6 +1359,12 @@ def SanityCheck(self): raise InvalidTermTTLValue('Term %s contains invalid TTL: %s' % (self.name, self.ttl)) + if self.ttl_except: + if not _MIN_TTL <= self.ttl_except <= _MAX_TTL: + + raise InvalidTermTTLValue('Term %s contains invalid TTL: %s' + % (self.name, self.ttl_except)) + def AddressCleanup(self, optimize=True, addressbook=False): """Do Address and Port collapsing. @@ -1567,7 +1581,7 @@ class VarType: PORT_MIRROR = 64 SZONE = 65 DZONE = 66 - + TTL_EXCEPT = 67 def __init__(self, var_type, value): self.var_type = var_type @@ -1800,6 +1814,7 @@ def __ne__(self, other): 'TRAFFIC_CLASS_COUNT', 'TRAFFIC_TYPE', 'TTL', + 'TTL_EXCEPT', 'VERBATIM', 'VPN', ) @@ -1880,6 +1895,7 @@ def __ne__(self, other): 'traffic-class-count': 'TRAFFIC_CLASS_COUNT', 'traffic-type': 'TRAFFIC_TYPE', 'ttl': 'TTL', + 'ttl-except': 'TTL_EXCEPT', 'verbatim': 'VERBATIM', 'vpn': 'VPN', } @@ -2051,6 +2067,7 @@ def p_term_spec(p): | term_spec target_service_accounts_spec | term_spec timeout_spec | term_spec ttl_spec + | term_spec ttl_except_spec | term_spec traffic_type_spec | term_spec verbatim_spec | term_spec vpn_spec @@ -2475,6 +2492,10 @@ def p_ttl_spec(p): """ ttl_spec : TTL ':' ':' INTEGER """ p[0] = VarType(VarType.TTL, p[4]) +def p_ttl_except_spec(p): + """ ttl_except_spec : TTL_EXCEPT ':' ':' INTEGER """ + p[0] = VarType(VarType.TTL_EXCEPT, p[4]) + def p_filter_term_spec(p): """ filter_term_spec : FILTER_TERM ':' ':' STRING """ p[0] = VarType(VarType.FILTER_TERM, p[4]) diff --git a/doc/generators/juniper.md b/doc/generators/juniper.md index 764d15f3..abc4b405 100644 --- a/doc/generators/juniper.md +++ b/doc/generators/juniper.md @@ -81,6 +81,7 @@ The default format is _inet4_, and is implied if not other argument is given. * _traffic-class-count::_ * _traffic-type::_ specify traffic-type * _ttl::_ Matches on TTL. +* _ttl-except::_ Allow all TTL "except" specified. * _verbatim::_ this specifies that the text enclosed within quotes should be rendered into the output without interpretation or modification. This is sometimes used as a temporary workaround while new required features are being added. ## Sub Tokens ### Actions diff --git a/tests/lib/juniper_test.py b/tests/lib/juniper_test.py index def1bddf..022ef0d9 100644 --- a/tests/lib/juniper_test.py +++ b/tests/lib/juniper_test.py @@ -392,6 +392,12 @@ action:: accept } """ +GOOD_TERM_38 = """ +term good-term-38 { + ttl-except:: 10 + action:: accept +} +""" GOOD_TERM_COMMENT = """ term good-term-comment { comment:: "This is a COMMENT" @@ -645,6 +651,7 @@ 'traffic_type', 'translated', 'ttl', + 'ttl_except', 'verbatim']) SUPPORTED_SUB_TOKENS = { @@ -1504,12 +1511,24 @@ def testTTL(self): output = str(jcl) self.assertIn('ttl 10;', output) + def testTTLExcept(self): + jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_38, + self.naming), EXP_INFO) + output = str(jcl) + self.assertIn('ttl-except 10;', output) + def testTTLInet6(self): jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER_V6 + GOOD_TERM_21, self.naming), EXP_INFO) output = str(jcl) self.assertNotIn('ttl 10;', output) + def testTTLExceptInet6(self): + jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER_V6 + GOOD_TERM_38, + self.naming), EXP_INFO) + output = str(jcl) + self.assertNotIn('ttl-except 10;', output) + def testNextIpFormat(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('10.1.1.1/32')] diff --git a/tests/lib/policy_test.py b/tests/lib/policy_test.py index 51f2a557..5fd4d0eb 100644 --- a/tests/lib/policy_test.py +++ b/tests/lib/policy_test.py @@ -439,6 +439,12 @@ action:: accept } """ +GOOD_TERM_49 = """ +term good-term-59 { + ttl-except:: 10 + action:: accept +} +""" GOOD_TERM_V6_1 = """ term good-term-v6-1 { hop-limit:: 5 @@ -584,6 +590,12 @@ action:: accept } """ +BAD_TERM_17 = """ +term bad-term-17 { + ttl-except:: 257 + action:: accept +} +""" # pylint: disable=maybe-no-member @@ -1269,6 +1281,16 @@ def testInvalidTTL(self): self.assertRaises(policy.InvalidTermTTLValue, policy.ParsePolicy, pol, self.naming) + def testTTLExcept(self): + pol = HEADER + GOOD_TERM_49 + result = policy.ParsePolicy(pol, self.naming) + self.assertIn('ttl_except: 10', str(result)) + + def testInvalidTTLExcept(self): + pol = HEADER + BAD_TERM_17 + self.assertRaises(policy.InvalidTermTTLValue, policy.ParsePolicy, + pol, self.naming) + def testNeedAddressBook(self): pol1 = policy.ParsePolicy(HEADER + GOOD_TERM_1, self.naming) pol2 = policy.ParsePolicy(HEADER_SRX + GOOD_TERM_1, self.naming) diff --git a/tests/lib/srxlo_test.py b/tests/lib/srxlo_test.py index 341b8e13..e88d7ee3 100644 --- a/tests/lib/srxlo_test.py +++ b/tests/lib/srxlo_test.py @@ -122,6 +122,7 @@ 'traffic_type', 'translated', 'ttl', + 'ttl_except', 'verbatim', } From fbb1c0ca20a3cf446b6b9f799a63075d4135eda6 Mon Sep 17 00:00:00 2001 From: Nathan Ward Date: Sat, 10 Sep 2022 20:05:58 +1200 Subject: [PATCH 2/2] Add hop-limit-except to Juniper --- capirca/lib/juniper.py | 7 +++++++ capirca/lib/policy.py | 12 ++++++++++++ doc/generators/juniper.md | 1 + tests/lib/juniper_test.py | 22 +++++++++++++++++++++- tests/lib/policy_test.py | 13 +++++++++++++ tests/lib/srxlo_test.py | 1 + 6 files changed, 55 insertions(+), 1 deletion(-) diff --git a/capirca/lib/juniper.py b/capirca/lib/juniper.py index 575984f7..954a178e 100644 --- a/capirca/lib/juniper.py +++ b/capirca/lib/juniper.py @@ -313,6 +313,7 @@ def __str__(self): self.term.forwarding_class_except or self.term.fragment_offset or self.term.hop_limit or + self.term.hop_limit_except or self.term.next_ip or self.term.port or self.term.precedence or @@ -544,6 +545,11 @@ def __str__(self): if self.term_type == 'inet6': config.Append('hop-limit %s;' % (self.term.hop_limit)) + if self.term.hop_limit_except: + # Only generate a hop-limit-except if inet6, inet4 has not hop-limit-except. + if self.term_type == 'inet6': + config.Append('hop-limit-except %s;' % (self.term.hop_limit_except)) + # flexible-match if self.term.flexible_match_range: config.Append('flexible-match-range {') @@ -911,6 +917,7 @@ def _BuildTokens(self): 'forwarding_class_except', 'fragment_offset', 'hop_limit', + 'hop_limit_except', 'icmp_code', 'logging', 'loss_priority', diff --git a/capirca/lib/policy.py b/capirca/lib/policy.py index 47cddd36..601a6afc 100644 --- a/capirca/lib/policy.py +++ b/capirca/lib/policy.py @@ -444,6 +444,7 @@ def __init__(self, obj): self.packet_length = None self.fragment_offset = None self.hop_limit = None + self.hop_limit_except = None self.icmp_type = [] self.icmp_code = [] self.ether_type = [] @@ -904,6 +905,8 @@ def __eq__(self, other): return False if self.hop_limit != other.hop_limit: return False + if self.hop_limit_except != other.hop_limit_except: + return False if sorted(self.icmp_type) != sorted(other.icmp_type): return False if sorted(self.icmp_code) != sorted(other.icmp_code): @@ -1240,6 +1243,8 @@ def AddObject(self, obj): self.fragment_offset = obj.value elif obj.var_type is VarType.HOP_LIMIT: self.hop_limit = obj.value + elif obj.var_type is VarType.HOP_LIMIT_EXCEPT: + self.hop_limit_except = obj.value elif obj.var_type is VarType.SINTERFACE: self.source_interface = obj.value elif obj.var_type is VarType.DINTERFACE: @@ -1582,6 +1587,7 @@ class VarType: SZONE = 65 DZONE = 66 TTL_EXCEPT = 67 + HOP_LIMIT_EXCEPT = 68 def __init__(self, var_type, value): self.var_type = var_type @@ -1766,6 +1772,7 @@ def __ne__(self, other): 'FORWARDING_CLASS_EXCEPT', 'FRAGMENT_OFFSET', 'HOP_LIMIT', + 'HOP_LIMIT_EXCEPT', 'APPLY_GROUPS', 'APPLY_GROUPS_EXCEPT', 'HEADER', @@ -1854,6 +1861,7 @@ def __ne__(self, other): 'fragment-offset': 'FRAGMENT_OFFSET', 'hex': 'HEX', 'hop-limit': 'HOP_LIMIT', + 'hop-limit-except': 'HOP_LIMIT_EXCEPT', 'apply-groups': 'APPLY_GROUPS', 'apply-groups-except': 'APPLY_GROUPS_EXCEPT', 'header': 'HEADER', @@ -2039,6 +2047,7 @@ def p_term_spec(p): | term_spec forwarding_class_except_spec | term_spec fragment_offset_spec | term_spec hop_limit_spec + | term_spec hop_limit_except_spec | term_spec icmp_type_spec | term_spec icmp_code_spec | term_spec interface_spec @@ -2207,6 +2216,9 @@ def p_hop_limit_spec(p): else: p[0] = VarType(VarType.HOP_LIMIT, str(p[4]) + '-' + str(p[6])) +def p_hop_limit_except_spec(p): + """ hop_limit_except_spec : HOP_LIMIT_EXCEPT ':' ':' INTEGER""" + p[0] = VarType(VarType.HOP_LIMIT_EXCEPT, p[4]) def p_one_or_more_dscps(p): """ one_or_more_dscps : one_or_more_dscps DSCP_RANGE diff --git a/doc/generators/juniper.md b/doc/generators/juniper.md index abc4b405..37f4d7de 100644 --- a/doc/generators/juniper.md +++ b/doc/generators/juniper.md @@ -54,6 +54,7 @@ The default format is _inet4_, and is implied if not other argument is given. * _forwarding-class_except::_ Do not match the specified forwarding classes. * _fragement-offset::_ specify a fragment offset of a fragmented packet * _hop-limit::_ Match the hop limit to the specified hop limit or set of hop limits. +* _hop-limit-except::_ Allow all hop limits "except" the one specified. * _icmp-code::_ Specifies the ICMP code to filter on. * _icmp-type::_ Specify icmp-type code to match, see section [ICMP TYPES](PolicyFormat#ICMP_TYPES.md) for list of valid arguments * _logging::_ Specify that this packet should be logged via syslog. diff --git a/tests/lib/juniper_test.py b/tests/lib/juniper_test.py index 022ef0d9..172fff0f 100644 --- a/tests/lib/juniper_test.py +++ b/tests/lib/juniper_test.py @@ -251,6 +251,12 @@ action:: accept } """ +GOOD_TERM_V6_HOP_LIMIT_EXCEPT = """ +term good-term-v6-hl { + hop-limit-except:: 25 + action:: accept +} +""" GOOD_TERM_20_V6 = """ term good-term-20-v6 { protocol-except:: icmpv6 @@ -588,7 +594,6 @@ action:: deny } """ - MIXED_TESTING_TERM = """ term good-term { protocol:: tcp @@ -621,6 +626,7 @@ 'forwarding_class_except', 'fragment_offset', 'hop_limit', + 'hop_limit_except', 'icmp_code', 'icmp_type', 'stateless_reply', @@ -926,6 +932,20 @@ def testHopLimitInet(self): output = str(jcl) self.assertNotIn('hop-limit 25;', output, output) + def testHopLimitExcept(self): + jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER_V6 + + GOOD_TERM_V6_HOP_LIMIT_EXCEPT, + self.naming), EXP_INFO) + output = str(jcl) + self.assertIn('hop-limit-except 25;', output, output) + + def testHopLimitExceptInet(self): + jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER + + GOOD_TERM_V6_HOP_LIMIT_EXCEPT, + self.naming), EXP_INFO) + output = str(jcl) + self.assertNotIn('hop-limit-except 25;', output, output) + def testProtocolExcept(self): jcl = juniper.Juniper(policy.ParsePolicy(GOOD_HEADER_V6 + GOOD_TERM_7, self.naming), EXP_INFO) diff --git a/tests/lib/policy_test.py b/tests/lib/policy_test.py index 5fd4d0eb..6ce2a49c 100644 --- a/tests/lib/policy_test.py +++ b/tests/lib/policy_test.py @@ -457,6 +457,12 @@ action:: accept } """ +GOOD_TERM_V6_HOP_LIMIT_EXCEPT = """ +term good-term-v6-1 { + hop-limit-except:: 5 + action:: accept +} +""" TERM_SUPER_2 = """ term term-super { @@ -695,6 +701,13 @@ def testHopLimitRange(self): _, terms = ret.filters[0] self.assertEqual(str(terms[0].hop_limit[2]), '7') + def testHopLimitExcept(self): + pol = HEADER_V6 + GOOD_TERM_V6_HOP_LIMIT_EXCEPT + ret = policy.ParsePolicy(pol, self.naming) + self.assertEqual(len(ret.filters), 1) + _, terms = ret.filters[0] + self.assertEqual(str(terms[0].hop_limit_except[0]), '5') + def testBadPortProtocols(self): pol = HEADER + BAD_TERM_3 self.naming.GetServiceByProto('SNMP', 'tcp').AndReturn([]) diff --git a/tests/lib/srxlo_test.py b/tests/lib/srxlo_test.py index e88d7ee3..65c762c7 100644 --- a/tests/lib/srxlo_test.py +++ b/tests/lib/srxlo_test.py @@ -93,6 +93,7 @@ 'forwarding_class_except', 'fragment_offset', 'hop_limit', + 'hop_limit_except', 'icmp_code', 'icmp_type', 'stateless_reply',