aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries/python/IPv6Util.py
blob: f89d17169baa8bfd09e9abf3c0f886bf0bc9d8b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# Copyright (c) 2018 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""IPv6 utilities library."""

import re

from resources.libraries.python.ssh import SSH
from resources.libraries.python.VatExecutor import VatTerminal
from resources.libraries.python.topology import Topology


class IPv6Util(object):
    """IPv6 utilities"""

    @staticmethod
    def ipv6_ping(src_node, dst_addr, count=3, data_size=56, timeout=1):
        """IPv6 ping.

        :param src_node: Node where ping run.
        :param dst_addr: Destination IPv6 address.
        :param count: Number of echo requests. (Optional)
        :param data_size: Number of the data bytes. (Optional)
        :param timeout: Time to wait for a response, in seconds. (Optional)
        :type src_node: dict
        :type dst_addr: str
        :type count: int
        :type data_size: int
        :type timeout: int
        :returns: Number of lost packets.
        :rtype: int
        """
        ssh = SSH()
        ssh.connect(src_node)

        cmd = "ping6 -c {c} -s {s} -W {W} {dst}".format(c=count, s=data_size,
                                                        W=timeout,
                                                        dst=dst_addr)
        (_, stdout, _) = ssh.exec_command(cmd)

        regex = re.compile(r'(\d+) packets transmitted, (\d+) received')
        match = regex.search(stdout)
        sent, received = match.groups()
        packet_lost = int(sent) - int(received)

        return packet_lost

    @staticmethod
    def ipv6_ping_port(nodes_ip, src_node, dst_node, port, cnt=3,
                       size=56, timeout=1):
        """Send IPv6 ping to the node port.

        :param nodes_ip: Nodes IPv6 addresses.
        :param src_node: Node where ping run.
        :param dst_node: Destination node.
        :param port: Port on the destination node.
        :param cnt: Number of echo requests. (Optional)
        :param size: Number of the data bytes. (Optional)
        :param timeout: Time to wait for a response, in seconds. (Optional)
        :type nodes_ip: dict
        :type src_node: dict
        :type dst_node: dict
        :type port: str
        :type cnt: int
        :type size: int
        :type timeout: int
        :returns: Number of lost packets.
        :rtype: int
        """
        dst_ip = IPv6Util.get_node_port_ipv6_address(dst_node, port, nodes_ip)
        return IPv6Util.ipv6_ping(src_node, dst_ip, cnt, size, timeout)

    @staticmethod
    def get_node_port_ipv6_address(node, iface_key, nodes_addr):
        """Return IPv6 address of the node port.

        :param node: Node in the topology.
        :param iface_key: Interface key of the node.
        :param nodes_addr: Nodes IPv6 addresses.
        :type node: dict
        :type iface_key: str
        :type nodes_addr: dict
        :returns: IPv6 address string.
        :rtype: str
        """
        interface = Topology.get_interface_name(node, iface_key)
        for net in nodes_addr.values():
            for port in net['ports'].values():
                host = port.get('node')
                dev = port.get('if')
                if host == node['host'] and dev == interface:
                    ip_addr = port.get('addr')
                    if ip_addr is not None:
                        return ip_addr
                    else:
                        raise Exception(
                            'Node {n} port {p} IPv6 address is not set'.format(
                                n=node['host'], p=interface))

        raise Exception('Node {n} port {p} IPv6 address not found.'.format(
            n=node['host'], p=interface))

    @staticmethod
    def add_ip_neighbor(node, interface, ip_address, mac_address):
        """Add IP neighbor.

        :param node: VPP node to add ip neighbor.
        :param interface: Interface name or sw_if_index.
        :param ip_address: IP address.
        :param mac_address: MAC address.
        :type node: dict
        :type interface: str or int
        :type ip_address: str
        :type mac_address: str
        """
        if isinstance(interface, basestring):
            sw_if_index = Topology.get_interface_sw_index(node, interface)
        else:
            sw_if_index = interface

        with VatTerminal(node) as vat:
            vat.vat_terminal_exec_cmd_from_template("add_ip_neighbor.vat",
                                                    sw_if_index=sw_if_index,
                                                    ip_address=ip_address,
                                                    mac_address=mac_address)
4' href='#n864'>864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
# Copyright (c) 2019 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""IPsec utilities library."""

import os

from random import choice
from string import letters

from enum import Enum, IntEnum
from ipaddress import ip_network, ip_address

from resources.libraries.python.Constants import Constants
from resources.libraries.python.IPUtil import IPUtil
from resources.libraries.python.InterfaceUtil import InterfaceUtil
from resources.libraries.python.PapiExecutor import PapiSocketExecutor
from resources.libraries.python.topology import Topology
from resources.libraries.python.VatExecutor import VatExecutor


def gen_key(length):
    """Generate random string as a key.

    :param length: Length of generated payload.
    :type length: int
    :returns: The generated payload.
    :rtype: str
    """
    return ''.join(choice(letters) for _ in range(length))


class PolicyAction(Enum):
    """Policy actions."""
    BYPASS = ('bypass', 0)
    DISCARD = ('discard', 1)
    PROTECT = ('protect', 3)

    def __init__(self, policy_name, policy_int_repr):
        self.policy_name = policy_name
        self.policy_int_repr = policy_int_repr


class CryptoAlg(Enum):
    """Encryption algorithms."""
    AES_CBC_128 = ('aes-cbc-128', 1, 'AES-CBC', 16)
    AES_CBC_256 = ('aes-cbc-256', 3, 'AES-CBC', 32)
    AES_GCM_128 = ('aes-gcm-128', 7, 'AES-GCM', 16)
    AES_GCM_256 = ('aes-gcm-256', 9, 'AES-GCM', 32)

    def __init__(self, alg_name, alg_int_repr, scapy_name, key_len):
        self.alg_name = alg_name
        self.alg_int_repr = alg_int_repr
        self.scapy_name = scapy_name
        self.key_len = key_len


class IntegAlg(Enum):
    """Integrity algorithm."""
    SHA_256_128 = ('sha-256-128', 4, 'SHA2-256-128', 32)
    SHA_512_256 = ('sha-512-256', 6, 'SHA2-512-256', 64)

    def __init__(self, alg_name, alg_int_repr, scapy_name, key_len):
        self.alg_name = alg_name
        self.alg_int_repr = alg_int_repr
        self.scapy_name = scapy_name
        self.key_len = key_len


class IPsecProto(IntEnum):
    """IPsec protocol."""
    ESP = 1
    SEC_AH = 0


class IPsecSadFlags(IntEnum):
    """IPsec Security Association Database flags."""
    IPSEC_API_SAD_FLAG_NONE = 0
    IPSEC_API_SAD_FLAG_IS_TUNNEL = 4
    IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 8


class IPsecUtil(object):
    """IPsec utilities."""

    @staticmethod
    def policy_action_bypass():
        """Return policy action bypass.

        :returns: PolicyAction enum BYPASS object.
        :rtype: PolicyAction
        """
        return PolicyAction.BYPASS

    @staticmethod
    def policy_action_discard():
        """Return policy action discard.

        :returns: PolicyAction enum DISCARD object.
        :rtype: PolicyAction
        """
        return PolicyAction.DISCARD

    @staticmethod
    def policy_action_protect():
        """Return policy action protect.

        :returns: PolicyAction enum PROTECT object.
        :rtype: PolicyAction
        """
        return PolicyAction.PROTECT

    @staticmethod
    def crypto_alg_aes_cbc_128():
        """Return encryption algorithm aes-cbc-128.

        :returns: CryptoAlg enum AES_CBC_128 object.
        :rtype: CryptoAlg
        """
        return CryptoAlg.AES_CBC_128

    @staticmethod
    def crypto_alg_aes_cbc_256():
        """Return encryption algorithm aes-cbc-256.

        :returns: CryptoAlg enum AES_CBC_256 object.
        :rtype: CryptoAlg
        """
        return CryptoAlg.AES_CBC_256

    @staticmethod
    def crypto_alg_aes_gcm_128():
        """Return encryption algorithm aes-gcm-128.

        :returns: CryptoAlg enum AES_GCM_128 object.
        :rtype: CryptoAlg
        """
        return CryptoAlg.AES_GCM_128

    @staticmethod
    def crypto_alg_aes_gcm_256():
        """Return encryption algorithm aes-gcm-256.

        :returns: CryptoAlg enum AES_GCM_128 object.
        :rtype: CryptoAlg
        """
        return CryptoAlg.AES_GCM_256

    @staticmethod
    def get_crypto_alg_key_len(crypto_alg):
        """Return encryption algorithm key length.

        :param crypto_alg: Encryption algorithm.
        :type crypto_alg: CryptoAlg
        :returns: Key length.
        :rtype: int
        """
        return crypto_alg.key_len

    @staticmethod
    def get_crypto_alg_scapy_name(crypto_alg):
        """Return encryption algorithm scapy name.

        :param crypto_alg: Encryption algorithm.
        :type crypto_alg: CryptoAlg
        :returns: Algorithm scapy name.
        :rtype: str
        """
        return crypto_alg.scapy_name

    @staticmethod
    def integ_alg_sha_256_128():
        """Return integrity algorithm SHA-256-128.

        :returns: IntegAlg enum SHA_256_128 object.
        :rtype: IntegAlg
        """
        return IntegAlg.SHA_256_128

    @staticmethod
    def integ_alg_sha_512_256():
        """Return integrity algorithm SHA-512-256.

        :returns: IntegAlg enum SHA_512_256 object.
        :rtype: IntegAlg
        """
        return IntegAlg.SHA_512_256

    @staticmethod
    def get_integ_alg_key_len(integ_alg):
        """Return integrity algorithm key length.

        :param integ_alg: Integrity algorithm.
        :type integ_alg: IntegAlg
        :returns: Key length.
        :rtype: int
        """
        return integ_alg.key_len

    @staticmethod
    def get_integ_alg_scapy_name(integ_alg):
        """Return integrity algorithm scapy name.

        :param integ_alg: Integrity algorithm.
        :type integ_alg: IntegAlg
        :returns: Algorithm scapy name.
        :rtype: str
        """
        return integ_alg.scapy_name

    @staticmethod
    def ipsec_proto_esp():
        """Return IPSec protocol ESP.

        :returns: IPsecProto enum ESP object.
        :rtype: IPsecProto
        """
        return int(IPsecProto.ESP)

    @staticmethod
    def ipsec_proto_ah():
        """Return IPSec protocol AH.

        :returns: IPsecProto enum AH object.
        :rtype: IPsecProto
        """
        return int(IPsecProto.SEC_AH)

    @staticmethod
    def vpp_ipsec_select_backend(node, protocol, index=1):
        """Select IPsec backend.

        :param node: VPP node to select IPsec backend on.
        :param protocol: IPsec protocol.
        :param index: Backend index.
        :type node: dict
        :type protocol: IPsecProto
        :type index: int
        :raises RuntimeError: If failed to select IPsec backend or if no API
            reply received.
        """
        cmd = 'ipsec_select_backend'
        err_msg = 'Failed to select IPsec backend on host {host}'.format(
            host=node['host'])
        args = dict(
            protocol=protocol,
            index=index
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def vpp_ipsec_backend_dump(node):
        """Dump IPsec backends.

        :param node: VPP node to dump IPsec backend on.
        :type node: dict
        """
        err_msg = 'Failed to dump IPsec backends on host {host}'.format(
            host=node['host'])
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add('ipsec_backend_dump').get_details(err_msg)

    @staticmethod
    def vpp_ipsec_add_sad_entry(
            node, sad_id, spi, crypto_alg, crypto_key, integ_alg=None,
            integ_key='', tunnel_src=None, tunnel_dst=None):
        """Create Security Association Database entry on the VPP node.

        :param node: VPP node to add SAD entry on.
        :param sad_id: SAD entry ID.
        :param spi: Security Parameter Index of this SAD entry.
        :param crypto_alg: The encryption algorithm name.
        :param crypto_key: The encryption key string.
        :param integ_alg: The integrity algorithm name.
        :param integ_key: The integrity key string.
        :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
            specified ESP transport mode is used.
        :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
            not specified ESP transport mode is used.
        :type node: dict
        :type sad_id: int
        :type spi: int
        :type crypto_alg: CryptoAlg
        :type crypto_key: str
        :type integ_alg: IntegAlg
        :type integ_key: str
        :type tunnel_src: str
        :type tunnel_dst: str
        """
        ckey = dict(
            length=len(crypto_key),
            data=crypto_key
        )
        ikey = dict(
            length=len(integ_key),
            data=integ_key if integ_key else 0
        )

        flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
        if tunnel_src and tunnel_dst:
            flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
            src_addr = ip_address(unicode(tunnel_src))
            dst_addr = ip_address(unicode(tunnel_dst))
            if src_addr.version == 6:
                flags = \
                    flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6)
        else:
            src_addr = ''
            dst_addr = ''

        cmd = 'ipsec_sad_entry_add_del'
        err_msg = 'Failed to add Security Association Database entry on ' \
                  'host {host}'.format(host=node['host'])
        sad_entry = dict(
            sad_id=int(sad_id),
            spi=int(spi),
            crypto_algorithm=crypto_alg.alg_int_repr,
            crypto_key=ckey,
            integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
            integrity_key=ikey,
            flags=flags,
            tunnel_src=str(src_addr),
            tunnel_dst=str(dst_addr),
            protocol=int(IPsecProto.ESP)
        )
        args = dict(
            is_add=1,
            entry=sad_entry
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def vpp_ipsec_add_sad_entries(
            node, n_entries, sad_id, spi, crypto_alg, crypto_key,
            integ_alg=None, integ_key='', tunnel_src=None, tunnel_dst=None):
        """Create multiple Security Association Database entries on VPP node.

        :param node: VPP node to add SAD entry on.
        :param n_entries: Number of SAD entries to be created.
        :param sad_id: First SAD entry ID. All subsequent SAD entries will have
            id incremented by 1.
        :param spi: Security Parameter Index of first SAD entry. All subsequent
            SAD entries will have spi incremented by 1.
        :param crypto_alg: The encryption algorithm name.
        :param crypto_key: The encryption key string.
        :param integ_alg: The integrity algorithm name.
        :param integ_key: The integrity key string.
        :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
            specified ESP transport mode is used.
        :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
            not specified ESP transport mode is used.
        :type node: dict
        :type n_entries: int
        :type sad_id: int
        :type spi: int
        :type crypto_alg: CryptoAlg
        :type crypto_key: str
        :type integ_alg: IntegAlg
        :type integ_key: str
        :type tunnel_src: str
        :type tunnel_dst: str
        """
        if tunnel_src and tunnel_dst:
            src_addr = ip_address(unicode(tunnel_src))
            dst_addr = ip_address(unicode(tunnel_dst))
        else:
            src_addr = ''
            dst_addr = ''

        addr_incr = 1 << (128 - 96) if src_addr.version == 6 \
            else 1 << (32 - 24)

        if int(n_entries) > 10:
            tmp_filename = '/tmp/ipsec_sad_{0}_add_del_entry.script'.\
                format(sad_id)

            with open(tmp_filename, 'w') as tmp_file:
                for i in xrange(n_entries):
                    integ = (
                        'integ-alg {integ_alg} integ-key {integ_key}'.format(
                            integ_alg=integ_alg.alg_name,
                            integ_key=integ_key.encode('hex'))
                        if integ_alg else '')
                    tunnel = (
                        'tunnel-src {laddr} tunnel-dst {raddr}'.format(
                            laddr=src_addr + i * addr_incr,
                            raddr=dst_addr + i * addr_incr)
                        if tunnel_src and tunnel_dst else '')
                    conf = (
                        'exec ipsec sa add {sad_id} esp spi {spi} '
                        'crypto-alg {crypto_alg} crypto-key {crypto_key} '
                        '{integ} {tunnel}\n'.format(
                            sad_id=sad_id + i,
                            spi=spi + i,
                            crypto_alg=crypto_alg.alg_name,
                            crypto_key=crypto_key.encode('hex'),
                            integ=integ,
                            tunnel=tunnel))
                    tmp_file.write(conf)
            vat = VatExecutor()
            vat.execute_script(tmp_filename, node, timeout=300, json_out=False,
                               copy_on_execute=True)
            os.remove(tmp_filename)
            return

        ckey = dict(
            length=len(crypto_key),
            data=crypto_key
        )
        ikey = dict(
            length=len(integ_key),
            data=integ_key if integ_key else 0
        )

        flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
        if tunnel_src and tunnel_dst:
            flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
            if src_addr.version == 6:
                flags = flags | int(
                    IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6)

        cmd = 'ipsec_sad_entry_add_del'
        err_msg = 'Failed to add Security Association Database entry on ' \
                  'host {host}'.format(host=node['host'])

        sad_entry = dict(
            sad_id=int(sad_id),
            spi=int(spi),
            crypto_algorithm=crypto_alg.alg_int_repr,
            crypto_key=ckey,
            integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
            integrity_key=ikey,
            flags=flags,
            tunnel_src=str(src_addr),
            tunnel_dst=str(dst_addr),
            protocol=int(IPsecProto.ESP)
        )
        args = dict(
            is_add=1,
            entry=sad_entry
        )
        with PapiSocketExecutor(node) as papi_exec:
            for i in xrange(n_entries):
                args['entry']['sad_id'] = int(sad_id) + i
                args['entry']['spi'] = int(spi) + i
                args['entry']['tunnel_src'] = str(src_addr + i * addr_incr) \
                    if tunnel_src and tunnel_dst else src_addr
                args['entry']['tunnel_dst'] = str(dst_addr + i * addr_incr) \
                    if tunnel_src and tunnel_dst else dst_addr
                history = False if 1 < i < n_entries - 1 else True
                papi_exec.add(cmd, history=history, **args)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK == 0:
                    papi_exec.get_replies(err_msg)
            papi_exec.get_replies(err_msg)

    @staticmethod
    def vpp_ipsec_set_ip_route(
            node, n_tunnels, tunnel_src, traffic_addr, tunnel_dst, interface,
            raddr_range):
        """Set IP address and route on interface.

        :param node: VPP node to add config on.
        :param n_tunnels: Number of tunnels to create.
        :param tunnel_src: Tunnel header source IPv4 or IPv6 address.
        :param traffic_addr: Traffic destination IP address to route.
        :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address.
        :param interface: Interface key on node 1.
        :param raddr_range: Mask specifying range of Policy selector Remote IP
            addresses. Valid values are from 1 to 32 in case of IPv4 and to 128
            in case of IPv6.
        :type node: dict
        :type n_tunnels: int
        :type tunnel_src: str
        :type traffic_addr: str
        :type tunnel_dst: str
        :type interface: str
        :type raddr_range: int
        """
        laddr = ip_address(unicode(tunnel_src))
        raddr = ip_address(unicode(tunnel_dst))
        taddr = ip_address(unicode(traffic_addr))
        addr_incr = 1 << (128 - raddr_range) if laddr.version == 6 \
            else 1 << (32 - raddr_range)

        if int(n_tunnels) > 10:
            tmp_filename = '/tmp/ipsec_set_ip.script'

            with open(tmp_filename, 'w') as tmp_file:
                for i in xrange(n_tunnels):
                    conf = (
                        'exec set interface ip address {interface} '
                        '{laddr}/{laddr_l}\n'
                        'exec ip route add {taddr}/{taddr_l} via {raddr} '
                        '{interface}\n'.format(
                            interface=Topology.get_interface_name(
                                node, interface),
                            laddr=laddr + i * addr_incr,
                            laddr_l=raddr_range,
                            raddr=raddr + i * addr_incr,
                            taddr=taddr + i,
                            taddr_l=128 if taddr.version == 6 else 32))
                    tmp_file.write(conf)
            vat = VatExecutor()
            vat.execute_script(tmp_filename, node, timeout=300, json_out=False,
                               copy_on_execute=True)
            os.remove(tmp_filename)
            return

        cmd1 = 'sw_interface_add_del_address'
        args1 = dict(
            sw_if_index=InterfaceUtil.get_interface_index(node, interface),
            is_add=1,
            is_ipv6=1 if laddr.version == 6 else 0,
            del_all=0,
            address_length=raddr_range,
            address=None
        )
        cmd2 = 'ip_route_add_del'
        route = IPUtil.compose_vpp_route_structure(
            node, taddr,
            prefix_len=128 if taddr.version == 6 else 32,
            interface=interface,
            gateway=tunnel_dst
        )
        args2 = dict(
            is_add=1,
            is_multipath=0,
            route=route
        )
        err_msg = 'Failed to configure IP addresses and IP routes on ' \
                  'interface {ifc} on host {host}'.\
            format(ifc=interface, host=node['host'])

        with PapiSocketExecutor(node) as papi_exec:
            for i in xrange(n_tunnels):
                args1['address'] = getattr(laddr + i * addr_incr, 'packed')
                args2['route']['prefix']['address']['un'] = \
                    IPUtil.union_addr(taddr + i)
                args2['route']['paths'][0]['nh']['address'] = \
                    IPUtil.union_addr(raddr + i * addr_incr)
                history = False if 1 < i < n_tunnels - 1 else True
                papi_exec.add(cmd1, history=history, **args1).\
                    add(cmd2, history=history, **args2)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK / 2 == 0:
                    papi_exec.get_replies(err_msg)
            papi_exec.get_replies(err_msg)

    @staticmethod
    def vpp_ipsec_add_spd(node, spd_id):
        """Create Security Policy Database on the VPP node.

        :param node: VPP node to add SPD on.
        :param spd_id: SPD ID.
        :type node: dict
        :type spd_id: int
        """
        cmd = 'ipsec_spd_add_del'
        err_msg = 'Failed to add Security Policy Database on host {host}'.\
            format(host=node['host'])
        args = dict(
            is_add=1,
            spd_id=int(spd_id)
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def vpp_ipsec_spd_add_if(node, spd_id, interface):
        """Add interface to the Security Policy Database.

        :param node: VPP node.
        :param spd_id: SPD ID to add interface on.
        :param interface: Interface name or sw_if_index.
        :type node: dict
        :type spd_id: int
        :type interface: str or int
        """
        cmd = 'ipsec_interface_add_del_spd'
        err_msg = 'Failed to add interface {ifc} to Security Policy Database ' \
                  '{spd} on host {host}'.\
            format(ifc=interface, spd=spd_id, host=node['host'])
        args = dict(
            is_add=1,
            sw_if_index=InterfaceUtil.get_interface_index(node, interface),
            spd_id=int(spd_id)
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def vpp_ipsec_policy_add(
            node, spd_id, priority, action, inbound=True, sa_id=None,
            laddr_range=None, raddr_range=None, proto=None, lport_range=None,
            rport_range=None, is_ipv6=False):
        """Create Security Policy Database entry on the VPP node.

        :param node: VPP node to add SPD entry on.
        :param spd_id: SPD ID to add entry on.
        :param priority: SPD entry priority, higher number = higher priority.
        :param action: Policy action.
        :param inbound: If True policy is for inbound traffic, otherwise
            outbound.
        :param sa_id: SAD entry ID for protect action.
        :param laddr_range: Policy selector local IPv4 or IPv6 address range in
            format IP/prefix or IP/mask. If no mask is provided,
            it's considered to be /32.
        :param raddr_range: Policy selector remote IPv4 or IPv6 address range in
            format IP/prefix or IP/mask. If no mask is provided,
            it's considered to be /32.
        :param proto: Policy selector next layer protocol number.
        :param lport_range: Policy selector local TCP/UDP port range in format
            <port_start>-<port_end>.
        :param rport_range: Policy selector remote TCP/UDP port range in format
            <port_start>-<port_end>.
        :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
            not defined so it will default to address ::/0, otherwise False.
        :type node: dict
        :type spd_id: int
        :type priority: int
        :type action: PolicyAction
        :type inbound: bool
        :type sa_id: int
        :type laddr_range: string
        :type raddr_range: string
        :type proto: int
        :type lport_range: string
        :type rport_range: string
        :type is_ipv6: bool
        """

        if laddr_range is None:
            laddr_range = '::/0' if is_ipv6 else '0.0.0.0/0'

        if raddr_range is None:
            raddr_range = '::/0' if is_ipv6 else '0.0.0.0/0'

        cmd = 'ipsec_spd_entry_add_del'
        err_msg = 'Failed to add entry to Security Policy Database ' \
                  '{spd} on host {host}'.format(spd=spd_id, host=node['host'])

        spd_entry = dict(
            spd_id=int(spd_id),
            priority=int(priority),
            is_outbound=0 if inbound else 1,
            sa_id=int(sa_id) if sa_id else 0,
            policy=action.policy_int_repr,
            protocol=int(proto) if proto else 0,
            remote_address_start=IPUtil.create_ip_address_object(
                ip_network(unicode(raddr_range), strict=False).network_address),
            remote_address_stop=IPUtil.create_ip_address_object(
                ip_network(
                    unicode(raddr_range), strict=False).broadcast_address),
            local_address_start=IPUtil.create_ip_address_object(
                ip_network(
                    unicode(laddr_range), strict=False).network_address),
            local_address_stop=IPUtil.create_ip_address_object(
                ip_network(
                    unicode(laddr_range), strict=False).broadcast_address),
            remote_port_start=int(rport_range.split('-')[0]) if rport_range
            else 0,
            remote_port_stop=int(rport_range.split('-')[1]) if rport_range
            else 65535,
            local_port_start=int(lport_range.split('-')[0]) if lport_range
            else 0,
            local_port_stop=int(lport_range.split('-')[1]) if rport_range
            else 65535
        )
        args = dict(
            is_add=1,
            entry=spd_entry
        )
        with PapiSocketExecutor(node) as papi_exec:
            papi_exec.add(cmd, **args).get_reply(err_msg)

    @staticmethod
    def vpp_ipsec_spd_add_entries(
            node, n_entries, spd_id, priority, inbound, sa_id, raddr_ip):
        """Create multiple Security Policy Database entries on the VPP node.

        :param node: VPP node to add SPD entries on.
        :param n_entries: Number of SPD entries to be added.
        :param spd_id: SPD ID to add entries on.
        :param priority: SPD entries priority, higher number = higher priority.
        :param inbound: If True policy is for inbound traffic, otherwise
            outbound.
        :param sa_id: SAD entry ID for first entry. Each subsequent entry will
            SAD entry ID incremented by 1.
        :param raddr_ip: Policy selector remote IPv4 start address for the first
            entry. Remote IPv4 end address will be calculated depending on
            raddr_range parameter. Each subsequent entry will have start address
            next after IPv4 end address of previous entry.
        :type node: dict
        :type n_entries: int
        :type spd_id: int
        :type priority: int
        :type inbound: bool
        :type sa_id: int
        :type raddr_ip: string
        """
        if int(n_entries) > 10:
            tmp_filename = '/tmp/ipsec_spd_{0}_add_del_entry.script'.\
                format(sa_id)

            with open(tmp_filename, 'w') as tmp_file:
                for i in xrange(n_entries):
                    raddr_s = ip_address(unicode(raddr_ip)) + i
                    raddr_e = ip_address(unicode(raddr_ip)) + (i + 1) - 1
                    tunnel = (
                        'exec ipsec policy add spd {spd_id} '
                        'priority {priority} {direction} action protect '
                        'sa {sa_id} remote-ip-range {raddr_s} - {raddr_e} '
                        'local-ip-range 0.0.0.0 - 255.255.255.255\n'.
                        format(
                            spd_id=spd_id,
                            priority=priority,
                            direction='inbound' if inbound else 'outbound',
                            sa_id=sa_id+i,
                            raddr_s=raddr_s,
                            raddr_e=raddr_e))
                    tmp_file.write(tunnel)
            vat = VatExecutor()
            vat.execute_script(tmp_filename, node, timeout=300, json_out=False,
                               copy_on_execute=True)
            os.remove(tmp_filename)
            return

        raddr_ip = ip_address(unicode(raddr_ip))
        laddr_range = '::/0' if raddr_ip.version == 6 else '0.0.0.0/0'

        cmd = 'ipsec_spd_entry_add_del'
        err_msg = 'Failed to add entry to Security Policy Database ' \
                  '{spd} on host {host}'.format(spd=spd_id, host=node['host'])

        spd_entry = dict(
            spd_id=int(spd_id),
            priority=int(priority),
            is_outbound=0 if inbound else 1,
            sa_id=int(sa_id) if sa_id else 0,
            policy=IPsecUtil.policy_action_protect().policy_int_repr,
            protocol=0,
            remote_address_start=IPUtil.create_ip_address_object(raddr_ip),
            remote_address_stop=IPUtil.create_ip_address_object(raddr_ip),
            local_address_start=IPUtil.create_ip_address_object(
                ip_network(unicode(laddr_range), strict=False).network_address),
            local_address_stop=IPUtil.create_ip_address_object(
                ip_network(
                    unicode(laddr_range), strict=False).broadcast_address),
            remote_port_start=0,
            remote_port_stop=65535,
            local_port_start=0,
            local_port_stop=65535
        )
        args = dict(
            is_add=1,
            entry=spd_entry
        )

        with PapiSocketExecutor(node) as papi_exec:
            for i in xrange(n_entries):
                args['entry']['remote_address_start']['un'] = \
                    IPUtil.union_addr(raddr_ip + i)
                args['entry']['remote_address_stop']['un'] = \
                    IPUtil.union_addr(raddr_ip + i)
                history = False if 1 < i < n_entries - 1 else True
                papi_exec.add(cmd, history=history, **args)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK == 0:
                    papi_exec.get_replies(err_msg)
            papi_exec.get_replies(err_msg)

    @staticmethod
    def vpp_ipsec_create_tunnel_interfaces(
            nodes, if1_ip_addr, if2_ip_addr, if1_key, if2_key, n_tunnels,
            crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range):
        """Create multiple IPsec tunnel interfaces between two VPP nodes.

        :param nodes: VPP nodes to create tunnel interfaces.
        :param if1_ip_addr: VPP node 1 interface IPv4/IPv6 address.
        :param if2_ip_addr: VPP node 2 interface IPv4/IPv6 address.
        :param if1_key: VPP node 1 interface key from topology file.
        :param if2_key: VPP node 2 interface key from topology file.
        :param n_tunnels: Number of tunnell interfaces to create.
        :param crypto_alg: The encryption algorithm name.
        :param integ_alg: The integrity algorithm name.
        :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
            first tunnel in direction node1->node2.
        :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
            first tunnel in direction node2->node1.
        :param raddr_range: Mask specifying range of Policy selector Remote
            IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
            and to 128 in case of IPv6.
        :type nodes: dict
        :type if1_ip_addr: str
        :type if2_ip_addr: str
        :type if1_key: str
        :type if2_key: str
        :type n_tunnels: int
        :type crypto_alg: CryptoAlg
        :type integ_alg: IntegAlg
        :type raddr_ip1: string
        :type raddr_ip2: string
        :type raddr_range: int
        """
        n_tunnels = int(n_tunnels)
        spi_1 = 100000
        spi_2 = 200000
        if1_ip = ip_address(unicode(if1_ip_addr))
        if2_ip = ip_address(unicode(if2_ip_addr))
        raddr_ip1 = ip_address(unicode(raddr_ip1))
        raddr_ip2 = ip_address(unicode(raddr_ip2))
        addr_incr = 1 << (128 - raddr_range) if if1_ip.version == 6 \
            else 1 << (32 - raddr_range)

        if n_tunnels > 10:
            tmp_fn1 = '/tmp/ipsec_create_tunnel_dut1.config'
            tmp_fn2 = '/tmp/ipsec_create_tunnel_dut2.config'
            vat = VatExecutor()
            with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2:
                tmp_f1.write(
                    'exec create loopback interface\n'
                    'exec set interface state loop0 up\n'
                    'exec set interface ip address {uifc} {iaddr}/{mask}\n'
                    .format(
                        iaddr=if2_ip - 1,
                        uifc=Topology.get_interface_name(
                            nodes['DUT1'], if1_key),
                        mask=96 if if2_ip.version == 6 else 24))
                tmp_f2.write(
                    'exec set interface ip address {uifc} {iaddr}/{mask}\n'
                    .format(
                        iaddr=if2_ip,
                        uifc=Topology.get_interface_name(
                            nodes['DUT2'], if2_key),
                        mask=96 if if2_ip.version == 6 else 24))
                for i in xrange(n_tunnels):
                    ckey = gen_key(IPsecUtil.get_crypto_alg_key_len(
                        crypto_alg)).encode('hex')
                    if integ_alg:
                        ikey = gen_key(IPsecUtil.get_integ_alg_key_len(
                            integ_alg)).encode('hex')
                        integ = (
                            'integ_alg {integ_alg} '
                            'local_integ_key {local_integ_key} '
                            'remote_integ_key {remote_integ_key} '
                            .format(
                                integ_alg=integ_alg.alg_name,
                                local_integ_key=ikey,
                                remote_integ_key=ikey))
                    else:
                        integ = ''
                    tmp_f1.write(
                        'exec set interface ip address loop0 {laddr}/32\n'
                        'ipsec_tunnel_if_add_del '
                        'local_spi {local_spi} '
                        'remote_spi {remote_spi} '
                        'crypto_alg {crypto_alg} '
                        'local_crypto_key {local_crypto_key} '
                        'remote_crypto_key {remote_crypto_key} '
                        '{integ} '
                        'local_ip {laddr} '
                        'remote_ip {raddr}\n'
                        .format(
                            local_spi=spi_1 + i,
                            remote_spi=spi_2 + i,
                            crypto_alg=crypto_alg.alg_name,
                            local_crypto_key=ckey,
                            remote_crypto_key=ckey,
                            integ=integ,
                            laddr=if1_ip + i * addr_incr,
                            raddr=if2_ip))
                    tmp_f2.write(
                        'ipsec_tunnel_if_add_del '
                        'local_spi {local_spi} '
                        'remote_spi {remote_spi} '
                        'crypto_alg {crypto_alg} '
                        'local_crypto_key {local_crypto_key} '
                        'remote_crypto_key {remote_crypto_key} '
                        '{integ} '
                        'local_ip {laddr} '
                        'remote_ip {raddr}\n'
                        .format(
                            local_spi=spi_2 + i,
                            remote_spi=spi_1 + i,
                            crypto_alg=crypto_alg.alg_name,
                            local_crypto_key=ckey,
                            remote_crypto_key=ckey,
                            integ=integ,
                            laddr=if2_ip,
                            raddr=if1_ip + i * addr_incr))
            vat.execute_script(
                tmp_fn1, nodes['DUT1'], timeout=1800, json_out=False,
                copy_on_execute=True)
            vat.execute_script(
                tmp_fn2, nodes['DUT2'], timeout=1800, json_out=False,
                copy_on_execute=True)
            os.remove(tmp_fn1)
            os.remove(tmp_fn2)

            with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2:
                tmp_f2.write(
                    'exec ip route add {raddr} via {uifc} {iaddr}\n'
                    .format(
                        raddr=ip_network(unicode(if1_ip_addr+'/8'), False),
                        iaddr=if2_ip - 1,
                        uifc=Topology.get_interface_name(
                            nodes['DUT2'], if2_key)))
                for i in xrange(n_tunnels):
                    tmp_f1.write(
                        'exec set interface unnumbered ipsec{i} use {uifc}\n'
                        'exec set interface state ipsec{i} up\n'
                        'exec ip route add {taddr}/{mask} via ipsec{i}\n'
                        .format(
                            taddr=raddr_ip2 + i,
                            i=i,
                            uifc=Topology.get_interface_name(nodes['DUT1'],
                                                             if1_key),
                            mask=128 if if2_ip.version == 6 else 32))
                    tmp_f2.write(
                        'exec set interface unnumbered ipsec{i} use {uifc}\n'
                        'exec set interface state ipsec{i} up\n'
                        'exec ip route add {taddr}/{mask} via ipsec{i}\n'
                        .format(
                            taddr=raddr_ip1 + i,
                            i=i,
                            uifc=Topology.get_interface_name(nodes['DUT2'],
                                                             if2_key),
                            mask=128 if if2_ip.version == 6 else 32))
            vat.execute_script(
                tmp_fn1, nodes['DUT1'], timeout=1800, json_out=False,
                copy_on_execute=True)
            vat.execute_script(
                tmp_fn2, nodes['DUT2'], timeout=1800, json_out=False,
                copy_on_execute=True)
            os.remove(tmp_fn1)
            os.remove(tmp_fn2)
            return

        with PapiSocketExecutor(nodes['DUT1']) as papi_exec:
            # Create loopback interface on DUT1, set it to up state
            cmd1 = 'create_loopback'
            args1 = dict(mac_address=0)
            err_msg = 'Failed to create loopback interface on host {host}'.\
                format(host=nodes['DUT1']['host'])
            loop_sw_if_idx = papi_exec.add(cmd1, **args1).\
                get_sw_if_index(err_msg)
            cmd1 = 'sw_interface_set_flags'
            args1 = dict(
                sw_if_index=loop_sw_if_idx,
                admin_up_down=1)
            err_msg = 'Failed to set loopback interface state up on host ' \
                      '{host}'.format(host=nodes['DUT1']['host'])
            papi_exec.add(cmd1, **args1).get_reply(err_msg)
            # Set IP address on VPP node 1 interface
            cmd1 = 'sw_interface_add_del_address'
            args1 = dict(
                sw_if_index=InterfaceUtil.get_interface_index(
                    nodes['DUT1'], if1_key),
                is_add=1,
                is_ipv6=1 if if2_ip.version == 6 else 0,
                del_all=0,
                address_length=96 if if2_ip.version == 6 else 24,
                address=getattr(if2_ip - 1, 'packed'))
            err_msg = 'Failed to set IP address on interface {ifc} on host ' \
                      '{host}'.format(ifc=if1_key, host=nodes['DUT1']['host'])
            papi_exec.add(cmd1, **args1).get_reply(err_msg)
            # Configure IPsec tunnel interfaces
            args1 = dict(
                sw_if_index=loop_sw_if_idx,
                is_add=1,
                is_ipv6=1 if if1_ip.version == 6 else 0,
                del_all=0,
                address_length=128 if if1_ip.version == 6 else 32,
                address='')
            cmd2 = 'ipsec_tunnel_if_add_del'
            args2 = dict(
                is_add=1,
                local_ip=None,
                remote_ip=None,
                local_spi=0,
                remote_spi=0,
                crypto_alg=crypto_alg.alg_int_repr,
                local_crypto_key_len=0,
                local_crypto_key=None,
                remote_crypto_key_len=0,
                remote_crypto_key=None,
                integ_alg=integ_alg.alg_int_repr if integ_alg else 0,
                local_integ_key_len=0,
                local_integ_key=None,
                remote_integ_key_len=0,
                remote_integ_key=None,
                tx_table_id=0
            )
            err_msg = 'Failed to add IPsec tunnel interfaces on host {host}'.\
                format(host=nodes['DUT1']['host'])
            ipsec_tunnels = list()
            ckeys = list()
            ikeys = list()
            for i in xrange(n_tunnels):
                ckeys.append(
                    gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)))
                if integ_alg:
                    ikeys.append(
                        gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)))
                args1['address'] = getattr(if1_ip + i * addr_incr, 'packed')
                args2['local_spi'] = spi_1 + i
                args2['remote_spi'] = spi_2 + i
                args2['local_ip'] = IPUtil.create_ip_address_object(
                    if1_ip + i * addr_incr)
                args2['remote_ip'] = IPUtil.create_ip_address_object(if2_ip)
                args2['local_crypto_key_len'] = len(ckeys[i])
                args2['local_crypto_key'] = ckeys[i]
                args2['remote_crypto_key_len'] = len(ckeys[i])
                args2['remote_crypto_key'] = ckeys[i]
                if integ_alg:
                    args2['local_integ_key_len'] = len(ikeys[i])
                    args2['local_integ_key'] = ikeys[i]
                    args2['remote_integ_key_len'] = len(ikeys[i])
                    args2['remote_integ_key'] = ikeys[i]
                history = False if 1 < i < n_tunnels - 1 else True
                papi_exec.add(cmd1, history=history, **args1).\
                    add(cmd2, history=history, **args2)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK / 2 == 0:
                    replies = papi_exec.get_replies(err_msg)
                    for reply in replies:
                        if 'sw_if_index' in reply:
                            ipsec_tunnels.append(reply["sw_if_index"])
            replies = papi_exec.get_replies(err_msg)
            for reply in replies:
                if 'sw_if_index' in reply:
                    ipsec_tunnels.append(reply["sw_if_index"])
            # Configure IP routes
            cmd1 = 'sw_interface_set_unnumbered'
            args1 = dict(
                is_add=1,
                sw_if_index=InterfaceUtil.get_interface_index(
                    nodes['DUT1'], if1_key),
                unnumbered_sw_if_index=0
            )
            cmd2 = 'sw_interface_set_flags'
            args2 = dict(
                sw_if_index=0,
                admin_up_down=1)
            cmd3 = 'ip_route_add_del'
            route = IPUtil.compose_vpp_route_structure(
                nodes['DUT1'], raddr_ip2.compressed,
                prefix_len=128 if raddr_ip2.version == 6 else 32,
                interface=0
            )
            args3 = dict(
                is_add=1,
                is_multipath=0,
                route=route
            )
            err_msg = 'Failed to add IP routes on host {host}'.format(
                host=nodes['DUT1']['host'])
            for i in xrange(n_tunnels):
                args1['unnumbered_sw_if_index'] = ipsec_tunnels[i]
                args2['sw_if_index'] = ipsec_tunnels[i]
                args3['route']['prefix']['address']['un'] = \
                    IPUtil.union_addr(raddr_ip2 + i)
                args3['route']['paths'][0]['sw_if_index'] = \
                    ipsec_tunnels[i]
                history = False if 1 < i < n_tunnels - 1 else True
                papi_exec.add(cmd1, history=history, **args1).\
                    add(cmd2, history=history, **args2).\
                    add(cmd3, history=history, **args3)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK / 3 == 0:
                    papi_exec.get_replies(err_msg)
            papi_exec.get_replies(err_msg)

        with PapiSocketExecutor(nodes['DUT2']) as papi_exec:
            # Set IP address on VPP node 2 interface
            cmd1 = 'sw_interface_add_del_address'
            args1 = dict(
                sw_if_index=InterfaceUtil.get_interface_index(
                    nodes['DUT2'], if2_key),
                is_add=1,
                is_ipv6=1 if if2_ip.version == 6 else 0,
                del_all=0,
                address_length=96 if if2_ip.version == 6 else 24,
                address=if2_ip.packed)
            err_msg = 'Failed to set IP address on interface {ifc} on host ' \
                      '{host}'.format(ifc=if2_key, host=nodes['DUT2']['host'])
            papi_exec.add(cmd1, **args1).get_reply(err_msg)
            # Configure IPsec tunnel interfaces
            cmd2 = 'ipsec_tunnel_if_add_del'
            args2 = dict(
                is_add=1,
                local_ip=IPUtil.create_ip_address_object(if2_ip),
                remote_ip=None,
                local_spi=0,
                remote_spi=0,
                crypto_alg=crypto_alg.alg_int_repr,
                local_crypto_key_len=0,
                local_crypto_key=None,
                remote_crypto_key_len=0,
                remote_crypto_key=None,
                integ_alg=integ_alg.alg_int_repr if integ_alg else 0,
                local_integ_key_len=0,
                local_integ_key=None,
                remote_integ_key_len=0,
                remote_integ_key=None,
                tx_table_id=0
            )
            err_msg = 'Failed to add IPsec tunnel interfaces on host {host}'. \
                format(host=nodes['DUT2']['host'])
            ipsec_tunnels = list()
            for i in xrange(n_tunnels):
                args2['local_spi'] = spi_2 + i
                args2['remote_spi'] = spi_1 + i
                args2['local_ip'] = IPUtil.create_ip_address_object(if2_ip)
                args2['remote_ip'] = IPUtil.create_ip_address_object(
                    if1_ip + i * addr_incr)
                args2['local_crypto_key_len'] = len(ckeys[i])
                args2['local_crypto_key'] = ckeys[i]
                args2['remote_crypto_key_len'] = len(ckeys[i])
                args2['remote_crypto_key'] = ckeys[i]
                if integ_alg:
                    args2['local_integ_key_len'] = len(ikeys[i])
                    args2['local_integ_key'] = ikeys[i]
                    args2['remote_integ_key_len'] = len(ikeys[i])
                    args2['remote_integ_key'] = ikeys[i]
                history = False if 1 < i < n_tunnels - 1 else True
                papi_exec.add(cmd2, history=history, **args2)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK / 2 == 0:
                    replies = papi_exec.get_replies(err_msg)
                    for reply in replies:
                        if 'sw_if_index' in reply:
                            ipsec_tunnels.append(reply["sw_if_index"])
            replies = papi_exec.get_replies(err_msg)
            for reply in replies:
                if 'sw_if_index' in reply:
                    ipsec_tunnels.append(reply["sw_if_index"])
            # Configure IP routes
            cmd1 = 'ip_route_add_del'
            route = IPUtil.compose_vpp_route_structure(
                nodes['DUT2'], if1_ip.compressed,
                prefix_len=32 if if1_ip.version == 6 else 8,
                interface=if2_key,
                gateway=(if2_ip - 1).compressed
            )
            args1 = dict(
                is_add=1,
                is_multipath=0,
                route=route
            )
            papi_exec.add(cmd1, **args1)
            cmd1 = 'sw_interface_set_unnumbered'
            args1 = dict(
                is_add=1,
                sw_if_index=InterfaceUtil.get_interface_index(
                    nodes['DUT2'], if2_key),
                unnumbered_sw_if_index=0
            )
            cmd2 = 'sw_interface_set_flags'
            args2 = dict(
                sw_if_index=0,
                admin_up_down=1)
            cmd3 = 'ip_route_add_del'
            route = IPUtil.compose_vpp_route_structure(
                nodes['DUT2'], raddr_ip1.compressed,
                prefix_len=128 if raddr_ip1.version == 6 else 32,
                interface=0
            )
            args3 = dict(
                is_add=1,
                is_multipath=0,
                route=route
            )
            err_msg = 'Failed to add IP routes on host {host}'.format(
                host=nodes['DUT2']['host'])
            for i in xrange(n_tunnels):
                args1['unnumbered_sw_if_index'] = ipsec_tunnels[i]
                args2['sw_if_index'] = ipsec_tunnels[i]
                args3['route']['prefix']['address']['un'] = \
                    IPUtil.union_addr(raddr_ip1 + i)
                args3['route']['paths'][0]['sw_if_index'] = \
                    ipsec_tunnels[i]
                history = False if 1 < i < n_tunnels - 1 else True
                papi_exec.add(cmd1, history=history, **args1). \
                    add(cmd2, history=history, **args2). \
                    add(cmd3, history=history, **args3)
                if i > 0 and i % Constants.PAPI_MAX_API_BULK / 3 == 0:
                    papi_exec.get_replies(err_msg)
            papi_exec.get_replies(err_msg)

    @staticmethod
    def vpp_ipsec_add_multiple_tunnels(
            nodes, interface1, interface2, n_tunnels, crypto_alg, integ_alg,
            tunnel_ip1, tunnel_ip2, raddr_ip1, raddr_ip2, raddr_range):
        """Create multiple IPsec tunnels between two VPP nodes.

        :param nodes: VPP nodes to create tunnels.
        :param interface1: Interface name or sw_if_index on node 1.
        :param interface2: Interface name or sw_if_index on node 2.
        :param n_tunnels: Number of tunnels to create.
        :param crypto_alg: The encryption algorithm name.
        :param integ_alg: The integrity algorithm name.
        :param tunnel_ip1: Tunnel node1 IPv4 address.
        :param tunnel_ip2: Tunnel node2 IPv4 address.
        :param raddr_ip1: Policy selector remote IPv4 start address for the
            first tunnel in direction node1->node2.
        :param raddr_ip2: Policy selector remote IPv4 start address for the
            first tunnel in direction node2->node1.
        :param raddr_range: Mask specifying range of Policy selector Remote IPv4
            addresses. Valid values are from 1 to 32.
        :type nodes: dict
        :type interface1: str or int
        :type interface2: str or int
        :type n_tunnels: int
        :type crypto_alg: CryptoAlg
        :type integ_alg: IntegAlg
        :type tunnel_ip1: str
        :type tunnel_ip2: str
        :type raddr_ip1: string
        :type raddr_ip2: string
        :type raddr_range: int
        """
        spd_id = 1
        p_hi = 100
        p_lo = 10
        sa_id_1 = 100000
        sa_id_2 = 200000
        spi_1 = 300000
        spi_2 = 400000

        crypto_key = gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
        integ_key = gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) \
            if integ_alg else ''

        IPsecUtil.vpp_ipsec_set_ip_route(
            nodes['DUT1'], n_tunnels, tunnel_ip1, raddr_ip2, tunnel_ip2,
            interface1, raddr_range)
        IPsecUtil.vpp_ipsec_set_ip_route(
            nodes['DUT2'], n_tunnels, tunnel_ip2, raddr_ip1, tunnel_ip1,
            interface2, raddr_range)

        IPsecUtil.vpp_ipsec_add_spd(
            nodes['DUT1'], spd_id)
        IPsecUtil.vpp_ipsec_spd_add_if(
            nodes['DUT1'], spd_id, interface1)
        IPsecUtil.vpp_ipsec_policy_add(
            nodes['DUT1'], spd_id, p_hi, PolicyAction.BYPASS, inbound=False,
            proto=50, laddr_range='100.0.0.0/8', raddr_range='100.0.0.0/8')
        IPsecUtil.vpp_ipsec_policy_add(
            nodes['DUT1'], spd_id, p_hi, PolicyAction.BYPASS, inbound=True,
            proto=50, laddr_range='100.0.0.0/8', raddr_range='100.0.0.0/8')

        IPsecUtil.vpp_ipsec_add_spd(
            nodes['DUT2'], spd_id)
        IPsecUtil.vpp_ipsec_spd_add_if(
            nodes['DUT2'], spd_id, interface2)
        IPsecUtil.vpp_ipsec_policy_add(
            nodes['DUT2'], spd_id, p_hi, PolicyAction.BYPASS, inbound=False,
            proto=50, laddr_range='100.0.0.0/8', raddr_range='100.0.0.0/8')
        IPsecUtil.vpp_ipsec_policy_add(
            nodes['DUT2'], spd_id, p_hi, PolicyAction.BYPASS, inbound=True,
            proto=50, laddr_range='100.0.0.0/8', raddr_range='100.0.0.0/8')

        IPsecUtil.vpp_ipsec_add_sad_entries(
            nodes['DUT1'], n_tunnels, sa_id_1, spi_1, crypto_alg, crypto_key,
            integ_alg, integ_key, tunnel_ip1, tunnel_ip2)

        IPsecUtil.vpp_ipsec_spd_add_entries(
            nodes['DUT1'], n_tunnels, spd_id, p_lo, False, sa_id_1, raddr_ip2)

        IPsecUtil.vpp_ipsec_add_sad_entries(
            nodes['DUT2'], n_tunnels, sa_id_1, spi_1, crypto_alg, crypto_key,
            integ_alg, integ_key, tunnel_ip1, tunnel_ip2)

        IPsecUtil.vpp_ipsec_spd_add_entries(
            nodes['DUT2'], n_tunnels, spd_id, p_lo, True, sa_id_1, raddr_ip2)

        IPsecUtil.vpp_ipsec_add_sad_entries(
            nodes['DUT2'], n_tunnels, sa_id_2, spi_2, crypto_alg, crypto_key,
            integ_alg, integ_key, tunnel_ip2, tunnel_ip1)

        IPsecUtil.vpp_ipsec_spd_add_entries(
            nodes['DUT2'], n_tunnels, spd_id, p_lo, False, sa_id_2, raddr_ip1)

        IPsecUtil.vpp_ipsec_add_sad_entries(
            nodes['DUT1'], n_tunnels, sa_id_2, spi_2, crypto_alg, crypto_key,
            integ_alg, integ_key, tunnel_ip2, tunnel_ip1)

        IPsecUtil.vpp_ipsec_spd_add_entries(
            nodes['DUT1'], n_tunnels, spd_id, p_lo, True, sa_id_2, raddr_ip1)

    @staticmethod
    def vpp_ipsec_show(node):
        """Run "show ipsec" debug CLI command.

        :param node: Node to run command on.
        :type node: dict
        """
        PapiSocketExecutor.run_cli_cmd(node, 'show ipsec')