def __str__(self): # Verify platform specific terms. Skip whole term if platform does not # match. if self.term.platform: if self.platform not in self.term.platform: return '' if self.term.platform_exclude: if self.platform in self.term.platform_exclude: return '' source_address_set = set() destination_address_set = set() ret_str = ['\n'] if self.verbose: ret_str.append(' remark %s' % self.term.name) comments = aclgenerator.WrapWords(self.term.comment, _COMMENT_MAX_WIDTH) if comments and comments[0]: for comment in comments: ret_str.append(' remark %s' % str(comment)) # Term verbatim output - this will skip over normal term creation # code by returning early. Warnings provided in policy.py. if self.term.verbatim: for next_verbatim in self.term.verbatim: if next_verbatim[0] == self.platform: ret_str.append(str(next_verbatim[1])) return '\n'.join(ret_str) # protocol if not self.term.protocol: protocol = ['ip'] else: protocol = [proto if proto in self.ALLOWED_PROTO_STRINGS else self.PROTO_MAP.get(proto) for proto in self.term.protocol] # addresses source_address = self.term.source_address if not self.term.source_address: source_address = [nacaddr.IPv4('0.0.0.0/0', token='any')] source_address_set.add(source_address[0].parent_token) destination_address = self.term.destination_address if not self.term.destination_address: destination_address = [nacaddr.IPv4('0.0.0.0/0', token='any')] destination_address_set.add(destination_address[0].parent_token) # ports source_port = [()] destination_port = [()] if self.term.source_port: source_port = self.term.source_port if self.term.destination_port: destination_port = self.term.destination_port for saddr in source_address_set: for daddr in destination_address_set: for sport in source_port: for dport in destination_port: for proto in protocol: ret_str.append( self._TermletToStr(_ACTION_TABLE.get(str( self.term.action[0])), proto, saddr, sport, daddr, dport)) return '\n'.join(ret_str)
def testParseNetFile(self): filedefs = naming.Naming(None) data = ['FOO = 127.0.0.1 # some network\n'] filedefs._ParseFile(data, 'networks') self.assertEqual(filedefs.GetNetAddr('FOO'), [nacaddr.IPv4('127.0.0.1')])
def testNacaddrNetToDSMNet(self): nacaddr_net = nacaddr.IPv4('192.168.0.64/27') dsm_net = summarizer.DSMNet(3232235584, 4294967264, '') self.assertEqual(summarizer._NacaddrNetToDSMNet(nacaddr_net), dsm_net)
def testAddressListExclusion(self): a1 = nacaddr.IPv4('1.1.1.0/24') a2 = nacaddr.IPv4('10.0.0.0/24') b1 = nacaddr.IPv4('1.1.1.1/32') b2 = nacaddr.IPv4('10.0.0.25/32') b3 = nacaddr.IPv4('192.168.0.0/16') expected = [ nacaddr.IPv4('1.1.1.0/32'), nacaddr.IPv4('1.1.1.2/31'), nacaddr.IPv4('1.1.1.4/30'), nacaddr.IPv4('1.1.1.8/29'), nacaddr.IPv4('1.1.1.16/28'), nacaddr.IPv4('1.1.1.32/27'), nacaddr.IPv4('1.1.1.64/26'), nacaddr.IPv4('1.1.1.128/25'), nacaddr.IPv4('10.0.0.0/28'), nacaddr.IPv4('10.0.0.16/29'), nacaddr.IPv4('10.0.0.24/32'), nacaddr.IPv4('10.0.0.26/31'), nacaddr.IPv4('10.0.0.28/30'), nacaddr.IPv4('10.0.0.32/27'), nacaddr.IPv4('10.0.0.64/26'), nacaddr.IPv4('10.0.0.128/25') ] self.assertListEqual( nacaddr.AddressListExclude([a1, a2], [b1, b2, b3]), expected) # [1,2,3] + [4,5,6] = [1,2,3,4,5,6]. this is basically the same test as # above but i think it's a little more readable expected_two = list(a1.address_exclude(b1)) expected_two.extend(a2.address_exclude(b2)) self.assertListEqual( nacaddr.AddressListExclude([a1, a2], [b1, b2, b3]), sorted(expected_two))
def testNetworkAddress(self): self.assertListEqual(self.defs.GetNetAddr('NET1'), [nacaddr.IPv4('10.0.0.0/8')])
def testCollapseAddrListPreserveTokens(self): addr_list = [ nacaddr.IPv4('10.0.1.7/32', token='BIZ'), nacaddr.IPv4('192.168.1.10/32', token='ALSOUNDERSUPER'), nacaddr.IPv4('10.0.0.6/32', token='FOO'), nacaddr.IPv4('10.0.0.9/32', token='BAR'), nacaddr.IPv4('10.0.0.8/32', token='FOO'), nacaddr.IPv4('10.0.0.7/32', token='BAR'), nacaddr.IPv4('192.168.1.1/24', token='SUPER', strict=False), nacaddr.IPv4('10.0.1.6/32', token='BIZ'), nacaddr.IPv4('192.168.1.7/31', token='UNDERSUPER', strict=False) ] expected = [ nacaddr.IPv4('10.0.0.7/32', token='BAR'), nacaddr.IPv4('10.0.0.9/32', token='BAR'), nacaddr.IPv4('10.0.1.6/31', token='BIZ'), nacaddr.IPv4('10.0.0.6/32', token='FOO'), nacaddr.IPv4('10.0.0.8/32', token='FOO'), nacaddr.IPv4('192.168.1.1/24', token='SUPER', strict=False), ] collapsed = nacaddr.CollapseAddrListPreserveTokens(addr_list) self.assertListEqual(collapsed, expected)
def setUp(self): self.addr1 = nacaddr.IPv4(u'10.0.0.0/8', 'The 10 block') self.addr2 = nacaddr.IPv6('DEAD:BEEF:BABE:FACE:DEAF:FEED:C0DE:F001/64', 'An IPv6 Address', strict=False)
class JuniperMSMPCTest(parameterized.TestCase): def setUp(self): super(JuniperMSMPCTest, self).setUp() self.naming = mock.create_autospec(naming.Naming) def testTermAndFilterName(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('10.0.0.0/8')] self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('term good-term-1 {', output, output) self.assertIn('rule test-filter {', output, output) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testBadFilterType(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('10.0.0.0/8')] self.naming.GetServiceByProto.return_value = ['25'] pol = policy.ParsePolicy(BAD_HEADER_2 + GOOD_TERM_1, self.naming) self.assertRaises(junipermsmpc.UnsupportedHeaderError, junipermsmpc.JuniperMSMPC, pol, EXP_INFO) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testMultipleFilterType(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('10.0.0.0/8')] self.naming.GetServiceByProto.return_value = ['25'] pol = policy.ParsePolicy(BAD_HEADER_3 + GOOD_TERM_1, self.naming) self.assertRaises(junipermsmpc.ConflictingTargetOptionsError, junipermsmpc.JuniperMSMPC, pol, EXP_INFO) def testMixedv4(self): self.naming.GetNetAddr.return_value = ([ nacaddr.IPv4('192.168.0.0/24') ]) self.naming.GetServiceByProto.return_value = ['25'] expected = (' term good-term-2 {\n' + ' from {\n' + ' destination-address {\n' + ' 192.168.0.0/24;\n' + ' }') msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_MIXED + GOOD_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertIn(expected, output, output) def testMixedv6(self): self.naming.GetNetAddr.return_value = ([nacaddr.IPv6('2001::/33')]) self.naming.GetServiceByProto.return_value = ['25'] expected = (' term good-term-2 {\n' + ' from {\n' + ' destination-address {\n' + ' 2001::/33;\n' + ' }') msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_MIXED + GOOD_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertIn(expected, output, output) def testMixedBoth(self): self.naming.GetNetAddr.return_value = ([ nacaddr.IPv4('192.168.0.0/24'), nacaddr.IPv6('2001::/33') ]) self.naming.GetServiceByProto.return_value = ['25'] expectedv4 = (' term good-term-2-inet {\n' + ' from {\n' + ' destination-address {\n' + ' 192.168.0.0/24;\n' + ' }') expectedv6 = (' term good-term-2-inet6 {\n' + ' from {\n' + ' destination-address {\n' + ' 2001::/33;\n' + ' }') msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_MIXED + GOOD_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertIn(expectedv4, output, output) self.assertIn(expectedv6, output, output) def testCommentShrinking(self): long_comment = ' this is a very descriptive comment ' * 10 expected = (' ' * 32 + '/* this is a very descriptive comment this\n' + ' ' * 33 + '** is a very descriptive comment this is a\n' + ' ' * 33 + '** very descriptive comment this is a very\n' + ' ' * 33 + '** descript */') self.naming.GetNetAddr.return_value = ([ nacaddr.IPv4('10.0.0.0/8', comment=long_comment) ]) self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertIn(expected, output, output) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testDefaultDeny(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + DEFAULT_TERM_1, self.naming), EXP_INFO) output = str(msmpc) self.assertNotIn('from {', output, output) self.assertIn('discard;', output, output) def testIcmpType(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_3, self.naming), EXP_INFO) output = str(msmpc) # verify proper translation from policy icmp-type text to juniper-esque self.assertIn('icmp-type 0;', output, output) self.assertIn('icmp-type 15;', output, output) self.assertIn('icmp-type 10;', output, output) self.assertIn('icmp-type 13;', output, output) self.assertIn('icmp-type 16;', output, output) def testIcmpCode(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_35, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('icmp-code [ 3 4 ];', output, output) def testInactiveTerm(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('10.0.0.0/8')] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_36, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('inactive: term good-term-36 {', output) def testInet6(self): self.naming.GetNetAddr.return_value = [nacaddr.IP('2001::/33')] self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_V6 + GOOD_TERM_1_V6, self.naming), EXP_INFO) output = str(msmpc) self.assertTrue( 'protocol icmp6;' in output and 'protocol tcp;' in output, output) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testProtocolCase(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_5, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('protocol icmp;', output, output) self.assertIn('protocol tcp;', output, output) def testPrefixList(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_8, self.naming), EXP_INFO) spfx_re = re.compile(r'source-prefix-list foo_prefix_list;') dpfx_re = re.compile( r'destination-prefix-list bar_prefix_list;\W+destination-prefix-list baz_prefix_list;' ) output = str(msmpc) self.assertTrue(spfx_re.search(output), output) self.assertTrue(dpfx_re.search(output), output) def testPrefixListExcept(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_30, self.naming), EXP_INFO) spfx_re = re.compile(r'source-prefix-list foo_prefix_list except;') dpfx_re = re.compile( r'destination-prefix-list bar_prefix_list except;') output = str(msmpc) self.assertTrue(spfx_re.search(output), output) self.assertTrue(dpfx_re.search(output), output) def testPrefixListMixed(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_31, self.naming), EXP_INFO) spfx_re = re.compile(r'source-prefix-list foo_prefix;\W+' r'source-prefix-list foo_except except;') dpfx_re = re.compile(r'destination-prefix-list bar_prefix;\W+' r'destination-prefix-list bar_except except;') output = str(msmpc) self.assertTrue(spfx_re.search(output), output) self.assertTrue(dpfx_re.search(output), output) def testVerbatimTerm(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_11, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('mary had a little lamb', output, output) # check if other platforms verbatim shows up in output self.assertNotIn('mary had a second lamb', output, output) self.assertNotIn('mary had a third lamb', output, output) self.assertNotIn('mary had a fourth lamb', output, output) def testAccept(self): self.naming.GetServiceByProto.return_value = ['53'] policy_text = GOOD_HEADER + GOOD_TERM_25 msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(policy_text, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('then {', output, output) self.assertIn('accept;', output, output) self.naming.GetServiceByProto.assert_called_once_with('DNS', 'tcp') def testDiscardIPv4(self): self.naming.GetServiceByProto.return_value = ['53'] policy_text = GOOD_HEADER + GOOD_TERM_26 msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(policy_text, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('then {', output, output) self.assertIn('discard;', output, output) self.naming.GetServiceByProto.assert_called_once_with('DNS', 'tcp') def testDiscardIPv6(self): self.naming.GetServiceByProto.return_value = ['53'] policy_text = GOOD_HEADER_V6 + GOOD_TERM_26_V6 msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(policy_text, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('then {', output, output) self.assertIn('discard;', output, output) self.naming.GetServiceByProto.assert_called_once_with('DNS', 'tcp') def testRejectIPv6(self): self.naming.GetServiceByProto.return_value = ['53'] policy_text = GOOD_HEADER_V6 + GOOD_TERM_26_V6_REJECT msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(policy_text, self.naming), EXP_INFO) output = str(msmpc) self.assertIn('then {', output, output) self.assertIn('reject;', output, output) self.naming.GetServiceByProto.assert_called_once_with('DNS', 'tcp') def testTcpEstablished(self): self.naming.GetServiceByProto.return_value = ['53'] policy_text = GOOD_HEADER + ESTABLISHED_TERM_1 msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(policy_text, self.naming), EXP_INFO) output = str(msmpc) self.assertNotIn('term established-term-1', output, output) self.assertNotIn('tcp-established', output, output) self.naming.GetServiceByProto.assert_called_once_with('DNS', 'tcp') def testNoVerboseV4(self): addr_list = list() for octet in range(0, 256): net = nacaddr.IP('192.168.' + str(octet) + '.64/27') addr_list.append(net) self.naming.GetNetAddr.return_value = addr_list self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy( GOOD_NOVERBOSE_V4_HEADER + GOOD_TERM_1 + GOOD_TERM_COMMENT, self.naming), EXP_INFO) self.assertIn('192.168.0.64/27;', str(msmpc)) self.assertNotIn('COMMENT', str(msmpc)) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testNoVerboseV6(self): addr_list = list() for octet in range(0, 256): net = nacaddr.IPv6('2001:db8:1010:' + str(octet) + '::64/64', strict=False) addr_list.append(net) self.naming.GetNetAddr.return_value = addr_list self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy( GOOD_NOVERBOSE_V6_HEADER + GOOD_TERM_1 + GOOD_TERM_COMMENT, self.naming), EXP_INFO) self.assertIn('2001:db8:1010:90::/61;', str(msmpc)) self.assertNotIn('COMMENT', str(msmpc)) self.naming.GetNetAddr.assert_called_once_with('SOME_HOST') self.naming.GetServiceByProto.assert_called_once_with('SMTP', 'tcp') def testTermTypeIndexKeys(self): # ensure an _INET entry for each _TERM_TYPE entry self.assertCountEqual(junipermsmpc.Term._TERM_TYPE.keys(), junipermsmpc.Term.AF_MAP.keys()) @mock.patch.object(junipermsmpc.logging, 'debug') def testIcmpv6InetMismatch(self, mock_debug): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + BAD_ICMPTYPE_TERM_1, self.naming), EXP_INFO) # output happens in __str_ str(msmpc) mock_debug.assert_called_once_with( 'Term icmptype-mismatch will not be rendered,' ' as it has icmpv6 match specified but ' 'the ACL is of inet address family.') @mock.patch.object(junipermsmpc.logging, 'debug') def testIcmpInet6Mismatch(self, mock_debug): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_V6 + BAD_ICMPTYPE_TERM_2, self.naming), EXP_INFO) # output happens in __str_ str(msmpc) mock_debug.assert_called_once_with( 'Term icmptype-mismatch will not be rendered,' ' as it has icmp match specified but ' 'the ACL is of inet6 address family.') @mock.patch.object(junipermsmpc.logging, 'warning') def testExpiredTerm(self, mock_warn): _ = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + EXPIRED_TERM, self.naming), EXP_INFO) mock_warn.assert_called_once_with( 'WARNING: Term %s in policy %s is expired and will ' 'not be rendered.', 'is_expired', 'test-filter') @mock.patch.object(junipermsmpc.logging, 'info') def testExpiringTerm(self, mock_info): exp_date = datetime.date.today() + datetime.timedelta(weeks=EXP_INFO) _ = junipermsmpc.JuniperMSMPC( policy.ParsePolicy( GOOD_HEADER + EXPIRING_TERM % exp_date.strftime('%Y-%m-%d'), self.naming), EXP_INFO) mock_info.assert_called_once_with( 'INFO: Term %s in policy %s expires in ' 'less than two weeks.', 'is_expiring', 'test-filter') def testOwnerTerm(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_17, self.naming), EXP_INFO) output = str(msmpc) self.assertIn( ' /*\n' ' ** Owner: [email protected]\n' ' */', output, output) def testOwnerNoVerboseTerm(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_NOVERBOSE_V4_HEADER + GOOD_TERM_17, self.naming), EXP_INFO) output = str(msmpc) self.assertNotIn('** Owner: ', output, output) def testAddressExclude(self): big = nacaddr.IPv4('0.0.0.0/1', comment='half of everything') ip1 = nacaddr.IPv4('10.0.0.0/8', comment='RFC1918 10-net') ip2 = nacaddr.IPv4('172.16.0.0/12', comment='RFC1918 172-net') terms = (GOOD_TERM_18_SRC, GOOD_TERM_18_DST) self.naming.GetNetAddr.side_effect = [[big, ip1, ip2], [ip1] ] * len(terms) mock_calls = [] for term in terms: msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + term, self.naming), EXP_INFO) output = str(msmpc) expected_output = ( ' ' + ('source' if term == GOOD_TERM_18_SRC else 'destination') + '-address {\n' + ' /* half of everything, RFC1918 ' '10-net */\n' + ' 0.0.0.0/1;\n' + ' /* RFC1918 172-net */\n' + ' 172.16.0.0/12;\n' + ' /* RFC1918 10-net */\n' + ' 10.0.0.0/8 except;\n' + ' }') self.assertIn(expected_output, output, output) self.assertNotIn('10.0.0.0/8;', output, output) self.assertNotIn('172.16.0.0/12 except;', output, output) mock_calls.append(mock.call('INTERNAL')) mock_calls.append(mock.call('SOME_HOST')) self.naming.GetNetAddr.assert_has_calls(mock_calls) def testMinimizePrefixes(self): includes = ['1.0.0.0/8', '2.0.0.0/8'] excludes = ['1.1.1.1/32', '2.0.0.0/8', '3.3.3.3/32'] expected = ['1.0.0.0/8;', '1.1.1.1/32 except;'] unexpected = ['2.0.0.0/8;', '2.0.0.0/8 except;', '3.3.3.3/32'] self.naming.GetNetAddr.side_effect = [[ nacaddr.IPv4(ip) for ip in includes ], [nacaddr.IPv4(ip) for ip in excludes]] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_19, self.naming), EXP_INFO) output = str(msmpc) for result in expected: self.assertIn(result, output, 'expected "%s" in %s' % (result, output)) for result in unexpected: self.assertNotIn(result, output, 'unexpected "%s" in %s' % (result, output)) self.naming.GetNetAddr.assert_has_calls( [mock.call('INCLUDES'), mock.call('EXCLUDES')]) def testNoMatchReversal(self): includes = ['10.0.0.0/8', '10.0.0.0/10'] excludes = ['10.0.0.0/9'] expected = ['10.0.0.0/8;', '10.0.0.0/10;', '10.0.0.0/9 except;'] self.naming.GetNetAddr.side_effect = [[ nacaddr.IPv4(ip) for ip in includes ], [nacaddr.IPv4(ip) for ip in excludes]] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_19, self.naming), EXP_INFO) output = str(msmpc) for result in expected: self.assertIn(result, output) def testBuildTokens(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + GOOD_TERM_35, self.naming), EXP_INFO) st, sst = msmpc._BuildTokens() self.assertSetEqual(st, SUPPORTED_TOKENS) self.assertDictEqual(sst, SUPPORTED_SUB_TOKENS) def testRangedPorts(self): self.naming.GetServiceByProto.side_effect = [['67'], ['68']] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + RANGE_PORTS_TERM, self.naming), EXP_INFO) self.assertIn('destination-port 67-68;', str(msmpc)) def testNotRangedPorts(self): self.naming.GetServiceByProto.side_effect = [['67'], ['69']] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + RANGE_PORTS_TERM, self.naming), EXP_INFO) self.assertNotIn('destination-port 67-68;', str(msmpc)) self.assertIn('destination-port 67;', str(msmpc)) self.assertIn('destination-port 69;', str(msmpc)) def testApplicationSets(self): self.naming.GetServiceByProto.side_effect = [['67'], ['69']] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + RANGE_PORTS_TERM, self.naming), EXP_INFO) expected = ( ' applications {\n' ' application test-filterranged-ports-1-app1 {\n' ' protocol udp;\n' ' destination-port 67;\n' ' }\n' ' application test-filterranged-ports-1-app2 {\n' ' protocol udp;\n' ' destination-port 69;\n' ' }\n' ' application-set test-filterranged-ports-1-app {\n' ' application test-filterranged-ports-1-app1;\n' ' application test-filterranged-ports-1-app2;\n' ' }\n' ' }\n') self.assertIn(expected, str(msmpc)) def testGroup(self): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + DEFAULT_TERM_1, self.naming), EXP_INFO) self.assertEqual('b;', msmpc._Group(['B'])) self.assertEqual('B;', msmpc._Group(['B'], lc=False)) self.assertEqual('b;', msmpc._Group(['B'], lc=True)) self.assertEqual('100;', msmpc._Group([100])) self.assertEqual('100-200;', msmpc._Group([(100, 200)])) self.assertEqual('[ b a ];', msmpc._Group(['b', 'A'])) self.assertEqual('[ 99 101-199 ];', msmpc._Group([99, (101, 199)])) self.assertEqual('[ 99 101-199 ];', msmpc._Group([99, (101, 199)])) @parameterized.named_parameters( ('MIXED_TO_V4', [[nacaddr.IPv4('0.0.0.0/1'), nacaddr.IPv6('2001::/33')], [nacaddr.IPv4('192.168.0.0/24')]], [ ' term good-term-inet {\n' + ' from {\n' + ' source-address {\n' + ' 0.0.0.0/1;\n' + ' }\n' + ' destination-address {\n' + ' 192.168.0.0/24;\n' + ' }' ], ['2001::/33']), ('V4_TO_MIXED', [ [nacaddr.IPv4('192.168.0.0/24')], [nacaddr.IPv4('0.0.0.0/1'), nacaddr.IPv6('2001::/33')], ], [ ' term good-term-inet {\n' + ' from {\n' + ' source-address {\n' + ' 192.168.0.0/24;\n' + ' }\n' + ' destination-address {\n' + ' 0.0.0.0/1;\n' + ' }' ], ['2001::/33']), ('MIXED_TO_V6', [[nacaddr.IPv4('0.0.0.0/1'), nacaddr.IPv6('2001::/33')], [nacaddr.IPv6('2201::/48')]], [ ' term good-term-inet6 {\n' + ' from {\n' + ' source-address {\n' + ' 2001::/33;\n' + ' }\n' + ' destination-address {\n' + ' 2201::/48;\n' + ' }' ], ['0.0.0.0/1']), ('V6_TO_MIXED', [[ nacaddr.IPv6('2201::/48') ], [nacaddr.IPv4('0.0.0.0/1'), nacaddr.IPv6('2001::/33')]], [ ' term good-term-inet6 {\n' + ' from {\n' + ' source-address {\n' + ' 2201::/48;\n' + ' }\n' + ' destination-address {\n' + ' 2001::/33;\n' + ' }' ], ['0.0.0.0/1']), ('MIXED_TO_MIXED', [[ nacaddr.IPv4('0.0.0.0/1'), nacaddr.IPv6('2001::/33') ], [nacaddr.IPv4('192.168.0.0/24'), nacaddr.IPv6('2201::/48')]], [ ' term good-term-inet {\n' + ' from {\n' + ' source-address {\n' + ' 0.0.0.0/1;\n' + ' }\n' + ' destination-address {\n' + ' 192.168.0.0/24;\n' + ' }', ' term good-term-inet6 {\n' + ' from {\n' + ' source-address {\n' + ' 2001::/33;\n' + ' }\n' + ' destination-address {\n' + ' 2201::/48;\n' + ' }' ], []), ('V4_TO_V4', [[nacaddr.IPv4('0.0.0.0/1')], [nacaddr.IPv4('192.168.0.0/24')]], [ ' term good-term {\n' + ' from {\n' + ' source-address {\n' + ' 0.0.0.0/1;\n' + ' }\n' + ' destination-address {\n' + ' 192.168.0.0/24;\n' + ' }' ], []), ('V6_TO_V6', [[nacaddr.IPv6('2001::/33')], [nacaddr.IPv6('2201::/48')]], [ ' term good-term {\n' + ' from {\n' + ' source-address {\n' + ' 2001::/33;\n' + ' }\n' + ' destination-address {\n' + ' 2201::/48;\n' + ' }' ], []), ( 'V4_TO_V6', [[nacaddr.IPv4('0.0.0.0/1')], [nacaddr.IPv6('2201::/48')]], [], ['0.0.0.0/1', '192.168.0.0/24', '2001::/33', '2201::/48'], ), ( 'V6_TO_V4', [[nacaddr.IPv6('2001::/33')], [nacaddr.IPv4('192.168.0.0/24')]], [], ['0.0.0.0/1', '192.168.0.0/24', '2001::/33', '2201::/48'], ), ) def testMixed(self, addresses, expected, notexpected): self.naming.GetNetAddr.side_effect = addresses self.naming.GetServiceByProto.return_value = ['25'] msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER_MIXED + MIXED_TESTING_TERM, self.naming), EXP_INFO) output = str(msmpc) for expect in expected: self.assertIn(expect, output, output) for notexpect in notexpected: self.assertNotIn(notexpect, output, output) @parameterized.named_parameters( ('true', 'true', True), ('True', 'True', True), ('syslog', 'syslog', True), ('local', 'local', True), ('disable', 'disable', False), ('log-both', 'log-both', True), ) def testLogging(self, option, want_logging): self.naming.GetNetAddr.return_value = [nacaddr.IPv4('192.168.0.0/24')] self.naming.GetServiceByProto.return_value = ['25'] expected_output = ( ' test-filter {\n' + ' services {\n' + ' stateful-firewall {\n' + ' rule test-filter {\n' + ' match-direction input-output;\n' + ' term good-term-1 {\n' + ' from {\n' + ' application-sets ' 'test-filtergood-term-1-app;\n' + ' }\n' + ' then {\n' + ' accept;\n' + ' syslog;\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + ' applications {\n' + ' application test-filtergood-term-1-app1 {\n' + ' protocol icmp;\n' + ' }\n' + ' application-set test-filtergood-term-1-app {\n' + ' application test-filtergood-term-1-app1;\n' + ' }\n' + ' }\n' + ' }\n' + '}\n' + 'apply-groups test-filter;') msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy( GOOD_HEADER_MIXED_IMPLICIT + (LOGGING_TERM % option), self.naming), EXP_INFO) output = str(msmpc) if want_logging: self.assertIn(expected_output, output, output) else: self.assertNotIn(expected_output, output, output) @parameterized.named_parameters(('default', GOOD_HEADER, 'input-output'), ('ingress', GOOD_HEADER_INGRESS, 'input'), ('egress', GOOD_HEADER_EGRESS, 'output')) def testDirection(self, header, direction): msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(header + GOOD_TERM_3, self.naming), EXP_INFO) output = str(msmpc) expected_output = (' rule test-filter {\n' + ' match-direction %s;') self.assertIn(expected_output % direction, output, output) def testBadDirectionCombo(self): pol = policy.ParsePolicy(BAD_HEADER_DIRECTION + GOOD_TERM_3, self.naming) self.assertRaises(junipermsmpc.ConflictingTargetOptionsError, junipermsmpc.JuniperMSMPC, pol, EXP_INFO) def testTermNameCollision(self): short_append = '1' * (junipermsmpc.MAX_IDENTIFIER_LEN // 2 - len('?ood-term-1')) long_append = short_append + '1' not_too_long_name = (TERM_NAME_COLLISION % (short_append, short_append)) too_long_name = (TERM_NAME_COLLISION % (long_append, long_append)) pol = policy.ParsePolicy(GOOD_HEADER + too_long_name, self.naming) self.assertRaises(junipermsmpc.ConflictingApplicationSetsError, junipermsmpc.JuniperMSMPC, pol, EXP_INFO) msmpc = junipermsmpc.JuniperMSMPC( policy.ParsePolicy(GOOD_HEADER + not_too_long_name, self.naming), EXP_INFO)
def testComplexAddressListExcludesion(self): # this is a big fugly test. there was a bug in AddressListExclude # which manifested itself when more than one member of the excludes # list was a part of the same superset token. # # for example, it used to be like so: # excludes = ['1.1.1.1/32', '1.1.1.2/32'] # superset = ['1.1.1.0/30'] # # '1.1.1.0/30'.AddressExclude('1.1.1.1/32') -> # ['1.1.1.0/32', '1.1.1.2/32', '1.1.1.3/32'] # '1.1.1.0/30'.AddressExclude('1.1.1.2/32') -> # ['1.1.1.0/32', '1.1.1.1/32', '1.1.1.3/32'] # # yet combining those two results gives you # ['1.1.1.0/32', '1.1.1.1/32', '1.1.1.2/32' '1.1.1.3/32'], or # '1.1.1.0/30', which clearly isn't right. excludes = [nacaddr.IPv4('10.0.0.0/23'), nacaddr.IPv4('10.1.0.0/16')] superset = [nacaddr.IPv4('0.0.0.0/0')] expected = [ nacaddr.IPv4('0.0.0.0/5'), nacaddr.IPv4('8.0.0.0/7'), nacaddr.IPv4('10.0.2.0/23'), nacaddr.IPv4('10.0.4.0/22'), nacaddr.IPv4('10.0.8.0/21'), nacaddr.IPv4('10.0.16.0/20'), nacaddr.IPv4('10.0.32.0/19'), nacaddr.IPv4('10.0.64.0/18'), nacaddr.IPv4('10.0.128.0/17'), nacaddr.IPv4('10.2.0.0/15'), nacaddr.IPv4('10.4.0.0/14'), nacaddr.IPv4('10.8.0.0/13'), nacaddr.IPv4('10.16.0.0/12'), nacaddr.IPv4('10.32.0.0/11'), nacaddr.IPv4('10.64.0.0/10'), nacaddr.IPv4('10.128.0.0/9'), nacaddr.IPv4('11.0.0.0/8'), nacaddr.IPv4('12.0.0.0/6'), nacaddr.IPv4('16.0.0.0/4'), nacaddr.IPv4('32.0.0.0/3'), nacaddr.IPv4('64.0.0.0/2'), nacaddr.IPv4('128.0.0.0/1') ] self.assertListEqual(nacaddr.AddressListExclude(superset, excludes), expected)
def testGoodAddrExcludesFlatten(self): pol = HEADER + GOOD_TERM_27 self.naming.GetNetAddr.side_effect = [[nacaddr.IPv4('10.0.0.0/8')], [ nacaddr.IPv4('10.62.0.0/15'), nacaddr.IPv4('10.129.0.0/15') ]] ret = policy.ParsePolicy(pol, self.naming) _, terms = ret.filters[0] terms[0].FlattenAll() self.assertEquals(terms[0].address, [ nacaddr.IPv4('10.0.0.0/11'), nacaddr.IPv4('10.32.0.0/12'), nacaddr.IPv4('10.48.0.0/13'), nacaddr.IPv4('10.56.0.0/14'), nacaddr.IPv4('10.60.0.0/15'), nacaddr.IPv4('10.64.0.0/10'), nacaddr.IPv4('10.130.0.0/15'), nacaddr.IPv4('10.132.0.0/14'), nacaddr.IPv4('10.136.0.0/13'), nacaddr.IPv4('10.144.0.0/12'), nacaddr.IPv4('10.160.0.0/11'), nacaddr.IPv4('10.192.0.0/10') ]) self.naming.GetNetAddr.assert_has_calls( [mock.call('PROD_NETWRK'), mock.call('PROD_EH')], any_order=True)
def testTermEquality(self): self.naming.GetNetAddr.side_effect = [ [ nacaddr.IPv4('64.233.160.0/19'), nacaddr.IPv4('66.102.0.0/20'), nacaddr.IPv4('66.249.80.0/20'), nacaddr.IPv4('72.14.192.0/18'), nacaddr.IPv4('72.14.224.0/20'), nacaddr.IPv4('216.239.32.0/19') ], [nacaddr.IPv4('10.0.0.0/8')], [nacaddr.IPv4('10.0.0.0/8')], [ nacaddr.IPv4('64.233.160.0/19'), nacaddr.IPv4('66.102.0.0/20'), nacaddr.IPv4('66.249.80.0/20'), nacaddr.IPv4('72.14.192.0/18'), nacaddr.IPv4('72.14.224.0/20'), nacaddr.IPv4('216.239.32.0/19') ], [nacaddr.IPv4('10.0.0.0/8')], [ nacaddr.IPv4('64.233.160.0/19'), nacaddr.IPv4('66.102.0.0/20'), nacaddr.IPv4('66.249.80.0/20'), nacaddr.IPv4('72.14.192.0/18'), nacaddr.IPv4('72.14.224.0/20'), nacaddr.IPv4('216.239.32.0/19') ] ] self.naming.GetServiceByProto.side_effect = [['80'], ['3306'], ['3306'], ['80'], ['3306'], ['443']] pol_text = HEADER + GOOD_TERM_19 + GOOD_TERM_20 + GOOD_TERM_21 ret = policy.ParsePolicy(pol_text, self.naming, shade_check=False) self.assertEqual(len(ret.filters), 1) _, terms = ret.filters[0] self.assertEqual(len(terms), 3) self.assertEqual(terms[0], terms[1]) self.assertNotEqual(terms[0], terms[2]) self.naming.GetNetAddr.assert_has_calls([ mock.call('PROD_EXTERNAL_SUPER'), mock.call('PROD_NETWRK'), mock.call('PROD_NETWRK'), mock.call('PROD_EXTERNAL_SUPER'), mock.call('PROD_NETWRK'), mock.call('PROD_EXTERNAL_SUPER') ], any_order=True) self.naming.GetServiceByProto.assert_has_calls([ mock.call('HTTP', 'tcp'), mock.call('MYSQL', 'tcp'), mock.call('MYSQL', 'tcp'), mock.call('HTTP', 'tcp'), mock.call('MYSQL', 'tcp'), mock.call('HTTPS', 'tcp') ], any_order=True)
def testDestAddrNotInDestAddr(self, mock_naming): mock_naming.GetNetAddr.side_effect = [[nacaddr.IPv4('192.168.1.1/32')], [nacaddr.IPv4('10.1.1.0/24')]] term_one = policy.Term([policy.VarType(4, 'FOO')]) term_two = policy.Term([policy.VarType(4, 'FOO')]) self.assertNotIn(term_one, term_two)
def __str__(self): # Verify platform specific terms. Skip whole term if platform does not # match. if self.term.platform: if self._PLATFORM not in self.term.platform: return '' if self.term.platform_exclude: if self._PLATFORM in self.term.platform_exclude: return '' if self.enable_dsmo: raise NotImplementedError('enable_dsmo not implemented for msmpc') ret_str = juniper.Config(indent=self._DEFAULT_INDENT) # COMMENTS # this deals just fine with multi line comments, but we could probably # output them a little cleaner; do things like make sure the # len(output) < 80, etc. Note, if 'noverbose' is set for the filter, skip # all comment processing. if not self.noverbose: if self.term.owner: self.term.comment.append('Owner: %s' % self.term.owner) if self.term.comment: ret_str.Append('/*') for comment in self.term.comment: for line in comment.split('\n'): ret_str.Append('** ' + line) ret_str.Append('*/') # Term verbatim output - this will skip over normal term creation # code. Warning generated from policy.py if appropriate. if self.term.verbatim: for next_term in self.term.verbatim: if next_term[0] == self._PLATFORM: ret_str.Append(str(next_term[1]), verbatim=True) return str(ret_str) # Determine whether there are any match conditions for the term. has_match_criteria = ( self.term.address or self.term.dscp_except or self.term.dscp_match or self.term.destination_address or self.term.destination_port or self.term.destination_prefix or self.term.destination_prefix_except or self.term.encapsulate or self.term.ether_type or self.term.flexible_match_range or self.term.forwarding_class or self.term.forwarding_class_except or self.term.fragment_offset or self.term.hop_limit or self.term.next_ip or self.term.port or self.term.precedence or self.term.protocol or self.term.protocol_except or self.term.source_address or self.term.source_port or self.term.source_prefix or self.term.source_prefix_except or self.term.traffic_type or self.term.ttl) suffixes = [] duplicate_term = False has_icmp = 'icmp' in self.term.protocol has_icmpv6 = 'icmpv6' in self.term.protocol has_v4_ip = self.term.GetAddressOfVersion( 'source_address', self.AF_MAP.get('inet')) or self.term.GetAddressOfVersion( 'source_address_exclude', self.AF_MAP.get('inet')) or self.term.GetAddressOfVersion( 'destination_address', self.AF_MAP.get('inet')) or self.term.GetAddressOfVersion( 'destination_address_exclude', self.AF_MAP.get('inet')) has_v6_ip = self.term.GetAddressOfVersion( 'source_address', self.AF_MAP.get('inet6')) or self.term.GetAddressOfVersion( 'source_address_exclude', self.AF_MAP.get('inet6')) or self.term.GetAddressOfVersion( 'destination_address', self.AF_MAP.get('inet6')) or self.term.GetAddressOfVersion( 'destination_address_exclude', self.AF_MAP.get('inet6')) if self.term_type == 'mixed': if not (has_v4_ip or has_v6_ip): suffixes = ['inet'] elif not has_v6_ip: suffixes = ['inet'] elif not has_v4_ip: suffixes = ['inet6'] else: suffixes = ['inet', 'inet6'] duplicate_term = True if not suffixes and self.term_type in ['inet', 'inet6']: suffixes = [self.term_type] for suffix in suffixes: if self.term_type == 'mixed' and (not (has_icmp and has_icmpv6)) and ( has_v4_ip and has_v6_ip): if (has_icmp and suffix != 'inet') or (has_icmpv6 and suffix != 'inet6'): continue source_address = self.term.GetAddressOfVersion('source_address', self.AF_MAP.get(suffix)) source_address_exclude = self.term.GetAddressOfVersion( 'source_address_exclude', self.AF_MAP.get(suffix)) source_address, source_address_exclude = self._MinimizePrefixes( source_address, source_address_exclude) destination_address = self.term.GetAddressOfVersion( 'destination_address', self.AF_MAP.get(suffix)) destination_address_exclude = self.term.GetAddressOfVersion( 'destination_address_exclude', self.AF_MAP.get(suffix)) destination_address, destination_address_exclude = self._MinimizePrefixes( destination_address, destination_address_exclude) if ((not source_address) and self.term.GetAddressOfVersion( 'source_address', self.AF_MAP.get('mixed')) and not source_address_exclude) or ( (not destination_address) and self.term.GetAddressOfVersion( 'destination_address', self.AF_MAP.get('mixed')) and not destination_address_exclude): continue if ((has_icmpv6 and not has_icmp and suffix == 'inet') or (has_icmp and not has_icmpv6 and suffix == 'inet6')) and self.term_type != 'mixed': logging.debug( self.NO_AF_LOG_PROTO.substitute( term=self.term.name, proto=', '.join(self.term.protocol), af=suffix)) return '' # NAME # if the term is inactive we have to set the prefix if self.term.inactive: term_prefix = 'inactive:' else: term_prefix = '' ret_str.Append( '%s term %s%s {' % (term_prefix, self.term.name, '-' + suffix if duplicate_term else '')) # We only need a "from {" clause if there are any conditions to match. if has_match_criteria: ret_str.Append('from {') # SOURCE ADDRESS if source_address or source_address_exclude: ret_str.Append('source-address {') if source_address: for saddr in source_address: for comment in self._Comment(saddr): ret_str.Append('%s' % comment) if saddr.version == 6 and 0 < saddr.prefixlen < 16: for saddr2 in saddr.subnets(new_prefix=16): ret_str.Append('%s;' % saddr2) else: if saddr == nacaddr.IPv6('0::0/0'): saddr = 'any-ipv6' elif saddr == nacaddr.IPv4('0.0.0.0/0'): saddr = 'any-ipv4' ret_str.Append('%s;' % saddr) # SOURCE ADDRESS EXCLUDE if source_address_exclude: for ex in source_address_exclude: for comment in self._Comment(ex): ret_str.Append('%s' % comment) if ex.version == 6 and 0 < ex.prefixlen < 16: for ex2 in ex.subnets(new_prefix=16): ret_str.Append('%s except;' % ex2) else: if ex == nacaddr.IPv6('0::0/0'): ex = 'any-ipv6' elif ex == nacaddr.IPv4('0.0.0.0/0'): ex = 'any-ipv4' ret_str.Append('%s except;' % ex) ret_str.Append('}') # source-address {...} # DESTINATION ADDRESS if destination_address or destination_address_exclude: ret_str.Append('destination-address {') if destination_address: for daddr in destination_address: for comment in self._Comment(daddr): ret_str.Append('%s' % comment) if daddr.version == 6 and 0 < daddr.prefixlen < 16: for daddr2 in daddr.subnets(new_prefix=16): ret_str.Append('%s;' % daddr2) else: if daddr == nacaddr.IPv6('0::0/0'): daddr = 'any-ipv6' elif daddr == nacaddr.IPv4('0.0.0.0/0'): daddr = 'any-ipv4' ret_str.Append('%s;' % daddr) # DESTINATION ADDRESS EXCLUDE if destination_address_exclude: for ex in destination_address_exclude: for comment in self._Comment(ex): ret_str.Append('%s' % comment) if ex.version == 6 and 0 < ex.prefixlen < 16: for ex2 in ex.subnets(new_prefix=16): ret_str.Append('%s except;' % ex2) else: if ex == nacaddr.IPv6('0::0/0'): ex = 'any-ipv6' elif ex == nacaddr.IPv4('0.0.0.0/0'): ex = 'any-ipv4' ret_str.Append('%s except;' % ex) ret_str.Append('}') # destination-address {...} # source prefix <except> list if self.term.source_prefix or self.term.source_prefix_except: for pfx in self.term.source_prefix: ret_str.Append('source-prefix-list ' + pfx + ';') for epfx in self.term.source_prefix_except: ret_str.Append('source-prefix-list ' + epfx + ' except;') # destination prefix <except> list if self.term.destination_prefix or self.term.destination_prefix_except: for pfx in self.term.destination_prefix: ret_str.Append('destination-prefix-list ' + pfx + ';') for epfx in self.term.destination_prefix_except: ret_str.Append('destination-prefix-list ' + epfx + ' except;') # APPLICATION if (self.term.source_port or self.term.destination_port or self.term.icmp_type or self.term.protocol): if hasattr(self.term, 'replacement_application_name'): ret_str.Append('application-sets ' + self.term.replacement_application_name + '-app;') else: ret_str.Append('application-sets ' + self.filter_name[:((MAX_IDENTIFIER_LEN) // 2)] + self.term.name[-((MAX_IDENTIFIER_LEN) // 2):] + '-app;') ret_str.Append('}') # from {...} ret_str.Append('then {') # ACTION for action in self.term.action: ret_str.Append(self._ACTIONS.get(str(action)) + ';') if self.term.logging and 'disable' not in [ x.value for x in self.term.logging ]: ret_str.Append('syslog;') ret_str.Append('}') # then {...} ret_str.Append('}') # term {...} return str(ret_str)
def testNextIPNotIn(self, mock_naming): mock_naming.GetNetAddr.side_effect = [ [nacaddr.IPv4('192.168.1.1/32')]] term_one = policy.Term([policy.VarType(46, "FOO")]) term_two = policy.Term([]) self.assertNotIn(term_two, term_one)