diff --git a/capirca/lib/juniper.py b/capirca/lib/juniper.py index 21a6d39a..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 @@ -323,7 +324,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 +454,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 @@ -539,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 {') @@ -906,6 +917,7 @@ def _BuildTokens(self): 'forwarding_class_except', 'fragment_offset', 'hop_limit', + 'hop_limit_except', 'icmp_code', 'logging', 'loss_priority', @@ -923,7 +935,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..601a6afc 100644 --- a/capirca/lib/policy.py +++ b/capirca/lib/policy.py @@ -438,11 +438,13 @@ 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 self.fragment_offset = None self.hop_limit = None + self.hop_limit_except = None self.icmp_type = [] self.icmp_code = [] self.ether_type = [] @@ -793,6 +795,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 +888,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: @@ -898,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): @@ -1234,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: @@ -1246,6 +1257,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 +1364,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 +1586,8 @@ class VarType: PORT_MIRROR = 64 SZONE = 65 DZONE = 66 - + TTL_EXCEPT = 67 + HOP_LIMIT_EXCEPT = 68 def __init__(self, var_type, value): self.var_type = var_type @@ -1752,6 +1772,7 @@ def __ne__(self, other): 'FORWARDING_CLASS_EXCEPT', 'FRAGMENT_OFFSET', 'HOP_LIMIT', + 'HOP_LIMIT_EXCEPT', 'APPLY_GROUPS', 'APPLY_GROUPS_EXCEPT', 'HEADER', @@ -1800,6 +1821,7 @@ def __ne__(self, other): 'TRAFFIC_CLASS_COUNT', 'TRAFFIC_TYPE', 'TTL', + 'TTL_EXCEPT', 'VERBATIM', 'VPN', ) @@ -1839,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', @@ -1880,6 +1903,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', } @@ -2023,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 @@ -2051,6 +2076,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 @@ -2190,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 @@ -2475,6 +2504,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..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. @@ -81,6 +82,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..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 @@ -392,6 +398,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" @@ -582,7 +594,6 @@ action:: deny } """ - MIXED_TESTING_TERM = """ term good-term { protocol:: tcp @@ -615,6 +626,7 @@ 'forwarding_class_except', 'fragment_offset', 'hop_limit', + 'hop_limit_except', 'icmp_code', 'icmp_type', 'stateless_reply', @@ -645,6 +657,7 @@ 'traffic_type', 'translated', 'ttl', + 'ttl_except', 'verbatim']) SUPPORTED_SUB_TOKENS = { @@ -919,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) @@ -1504,12 +1531,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..6ce2a49c 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 @@ -451,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 { @@ -584,6 +596,12 @@ action:: accept } """ +BAD_TERM_17 = """ +term bad-term-17 { + ttl-except:: 257 + action:: accept +} +""" # pylint: disable=maybe-no-member @@ -683,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([]) @@ -1269,6 +1294,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..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', @@ -122,6 +123,7 @@ 'traffic_type', 'translated', 'ttl', + 'ttl_except', 'verbatim', }