aboutsummaryrefslogtreecommitdiffstats
path: root/test/patches
AgeCommit message (Collapse)AuthorFilesLines
2020-09-07ipsec: fix padding/alignment for native IPsec encryptionChristian Hopps2-30/+76
Not all ESP crypto algorithms require padding/alignment to be the same as AES block/IV size. CCM, CTR and GCM all have no padding/alignment requirements, and the RFCs indicate that no padding (beyond ESPs 4 octet alignment requirement) should be used unless TFC (traffic flow confidentiality) has been requested. CTR: https://tools.ietf.org/html/rfc3686#section-3.2 GCM: https://tools.ietf.org/html/rfc4106#section-3.2 CCM: https://tools.ietf.org/html/rfc4309#section-3.2 - VPP is incorrectly using the IV/AES block size to pad CTR and GCM. These modes do not require padding (beyond ESPs 4 octet requirement), as a result packets will have unnecessary padding, which will waste bandwidth at least and possibly fail certain network configurations that have finely tuned MTU configurations at worst. Fix this as well as changing the field names from ".*block_size" to ".*block_align" to better represent their actual (and only) use. Rename "block_sz" in esp_encrypt to "esp_align" and set it correctly as well. test: ipsec: Add unit-test to test for RFC correct padding/alignment test: patch scapy to not incorrectly pad ccm, ctr, gcm modes as well - Scapy is also incorrectly using the AES block size of 16 to pad CCM, CTR, and GCM cipher modes. A bug report has been opened with the and acknowledged with the upstream scapy project as well: https://github.com/secdev/scapy/issues/2322 Ticket: VPP-1928 Type: fix Signed-off-by: Christian Hopps <chopps@labn.net> Change-Id: Iaa4d6a325a2e99fdcb2c375a3395bcfe7947770e
2020-05-05ipsec: User can choose the UDP source portNeale Ranns1-1/+10
Type: feature thus allowing NAT traversal, Signed-off-by: Neale Ranns <nranns@cisco.com> Change-Id: Ie8650ceeb5074f98c68d2d90f6adc2f18afeba08 Signed-off-by: Paul Vinciguerra <pvinci@vinciconsulting.com>
2020-02-13vrrp: add plugin providing vrrp supportMatthew Smith1-0/+35
Type: feature Add a new plugin to support HA using VRRPv3 (RFC 5798). Change-Id: Iaa2c37e6172f8f41e9165f178f44d481f6e247b9 Signed-off-by: Matthew Smith <mgsmith@netgate.com>
2020-02-11sr: update NH value for Ethernet payloadspcamaril1-1/+1
Upon encapsulation of L2 frames, IETF has replaced the NextHeader value from 59 (IPv6 No Next Header) to 143 (Ethernet). https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml Type: fix Signed-off-by: pcamaril <pcamaril@cisco.com> Change-Id: I88aa5590c81d16700ff7a0bbe6337e113179496e Signed-off-by: pcamaril <pcamaril@cisco.com>
2019-12-18tests: fix cdp patch for scapy 2.4.3Paul Vinciguerra1-20/+2
Type: test Fixes: 5d4b8912d2fe186b4fb920a72b3a2f7b556f4e7d Change-Id: Ib64ae00eba41b2b6afc728142cbccc02d07f4997 Signed-off-by: Paul Vinciguerra <pvinci@vinciconsulting.com>
2019-12-14tests: changes for scapy 2.4.3 migrationsnaramre2-0/+193
Type: fix Change-Id: I7e041b666dabd90df23a920a1f1d99db4c10ddfe Signed-off-by: snaramre <snaramre@cisco.com>
2019-11-05misc: Fix python scripts shebang lineRenato Botelho do Couto2-2/+2
Type: fix Since CentOS 8, RPM build script doesn't accept '#!/usr/bin/env python' as a valid shebang line. It requires scripts to explicitly chose between python2 or python3. Change all to use python3 as suggested by Paul Vinciguerra. Depends-On: https://gerrit.fd.io/r/23170 Signed-off-by: Renato Botelho do Couto <renato@netgate.com> Change-Id: Ie72af9f60fd0609e07f05b70f8d96e738b2754d1
2019-10-23cdp: re-enable skipped tests for python3Ole Troan1-3/+10
CDP uses the running sytems host name, which caused different failures on different systems. The root cause was an python3 specific error in checksum calculation. Type: fix Signed-off-by: Ole Troan <ot@cisco.com> Change-Id: I205436682d46e7e8cbb8c057c03a76dbbcab4d72
2019-07-24ipsec: GCM, Anti-replay and ESN fixessNeale Ranns1-23/+29
Type: fix Several Fixes: 1 - Anti-replay did not work with GCM becuase it overwrote the sequence number in the ESP header. To fix i added the seq num to the per-packet data so it is preserved 2 - The high sequence number was not byte swapped during ESP encrypt. 3 - openssl engine was the only one to return FAIL_DECRYPT for bad GCM the others return BAD_HMAC. removed the former 4 - improved tracing to show the low and high seq numbers 5 - documented the anti-replay window checks 6 - fixed scapy patch for ESN support for GCM 7 - tests for anti-reply (w/ and w/o ESN) for each crypto algo Change-Id: Id65d96b6d1d4dd821b2ab557e87468fff6d70e5b Signed-off-by: Neale Ranns <nranns@cisco.com>
2019-04-16IPSEC: support GCM in ESPNeale Ranns1-15/+37
Change-Id: Id2ddb77b4ec3dd543d6e638bc882923f2bac011d Signed-off-by: Neale Ranns <nranns@cisco.com>
2019-04-11IPSEC: ESP with ESN tests and fixesNeale Ranns1-14/+21
Change-Id: Ie42b26e6d5cdb7b23f370ea2933c65079e8d1089 Signed-off-by: Neale Ranns <nranns@cisco.com>
2019-03-25IPSEC tests fnd fix or Extended Sequence NumbersNeale Ranns1-0/+156
Change-Id: Iad6c4b867961ec8036110a4e15a829ddb93193ed Signed-off-by: Neale Ranns <nranns@cisco.com>
2018-11-02cdp scapy protocol & cdp unit testsFilip Varga1-0/+24
Change-Id: Ieb362523f81f7ae3e1a9dceb341c499ff1f402c8 Signed-off-by: Filip Varga <fivarga@cisco.com>
2018-09-27IPIP and IPv6 fragmentationOle Troan1-0/+12
- Error where ICMPv6 error code doesn't reset VLIB_TX = -1 Leading to crash for ICMP generated on tunnelled packets - Missed setting VNET_BUFFER_F_LOCALLY_ORIGINATED, so IP in IPv6 packets never got fragmented. - Add support for fragmentation of buffer chains. - Remove support for inner fragmentation in frag code itself. Change-Id: If9a97301b7e35ca97ffa5c0fada2b9e7e7dbfb27 Signed-off-by: Ole Troan <ot@cisco.com>
2018-09-10vxlan-gbp: Add support for vxlan gbpMohsin Kazmi2-0/+13
This patch implements vxlan with extension of group based policy support. Change-Id: I70405bf7332c02867286da8958d9652837edd3c2 Signed-off-by: Mohsin Kazmi <sykazmi@cisco.com>
2018-07-11srv6: Fixing SRH parsing bug in Scapy 2.4Francois Clad1-0/+28
Change-Id: Ib2cb345d07665735697bf54ad48d353ba4112eda Signed-off-by: Francois Clad <fclad@cisco.com>
2018-03-19Scapy upgrade to 2.4.0.rc5Neale Ranns5-0/+197
- many of the patches fd.io applies in test/patches/2.3.3 are now upstreamed in 2.4 - 2.4 adds support for IGMPv3 which is my main motivation for the upgrade Change-Id: If2c0a524e3cba320b4a5d8cd07817c6ea2bf0c5a Signed-off-by: Neale Ranns <nranns@cisco.com>
2018-03-01Fix ERSPAN encap to set EN bits in the header and add test caseJohn Lo1-1/+29
For ERSPAN encap, both bits in the EN field of the header should be set to indicate any VLAN tag in the original Ethernet frame is preserved. Added SPAN L2 test case where the mirrored packet output is a GRE ERSPAN tunnel. Change-Id: Ie7a40992a9278469c24aa6fa9e122b4505797d10 Signed-off-by: John Lo <loj@cisco.com>
2018-02-26update BIER scapy patch to match the scapy repo PRNeale Ranns1-4/+2
Change-Id: I4953b8444b49d1ad445c98a199ae8fd1635e24a5 Signed-off-by: Neale Ranns <nranns@cisco.com>
2018-02-06BIER: fix support for longer bit-string lengthsNeale Ranns1-30/+35
Change-Id: I2421197b76be58099e5f8ed5554410adff202109 Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
2018-02-01IPv4/6 reassemblyKlement Sekera1-12/+13
Change-Id: Ic5dcadd13c88b8a5e7896dab82404509c081614a Signed-off-by: Klement Sekera <ksekera@cisco.com>
2017-12-13GRE: fix single loop decap and add testNeale Ranns1-0/+9
Change-Id: I64e8a76a17057ae69de72a5a80c0a194cd0c21cb Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-12-09BIER in non-MPLS netowrksNeale Ranns1-3/+17
as decsribed in section 2.2 ihttps://tools.ietf.org/html/draft-ietf-bier-mpls-encapsulation-10 with BIFT encoding from: https://tools.ietf.org/html/draft-wijnandsxu-bier-non-mpls-bift-encoding-00 changes: 1 - introduce the new BIFT lookup table. BIER tables that have an associated MPLS label are added to the MPLS-FIB. Those that don't are added to the BIER table 2 - BIER routes that have no associated output MPLS label will add a BIFT label. 3 - The BIER FMask has a path-list as a member to resolve via any possible path. Change-Id: I1fd4d9dbd074f0e855c16e9329b81460ebe1efce Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-11-15vxlan extended tests - fix scapy-related issuesGabriel Ganne1-0/+11
- Add vxlan-gpe binding on udp port 4790 (taken from scapy upstream) - VXLAN.VNI -> VXLAN.vni Change-Id: If7ad38fa04fbfec01e01c81a06e88ffe70183672 Signed-off-by: Gabriel Ganne <gabriel.ganne@enea.com>
2017-11-09BIERNeale Ranns2-8/+72
- see draft-ietf-bier-mpls-encapsulation-10 - midpoint, head and tail functions - supported payload protocols; IPv4 and IPv6 only. Change-Id: I59d7363bb6fdfdce8e4016a68a9c8f5a5e5791cb Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-10-06Initial GENEVE TUNNEL implementation and tests.Marco Varlese2-0/+69
Notes on this first implementation: * First version of the implementation does NOT support GENEVE OPTIONS HEADER: it isn't well understood what the purpose of the OPTIONS will be and/or what content would be placed in the variable option data; Once the IETF work will evolve and further information will be available it could be possible to modify the frame rewrite to contemplate the actual GENEVE OPTIONS. Change-Id: Iddfe6f408cc45bb0800f00ce6a3e302e48a4ed52 Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2017-08-22SRv6 testsKris Michielsen1-0/+185
Change-Id: Ib1d2fc5a83d9d007a0468591a73881675f1bec9b Signed-off-by: Kris Michielsen <kmichiel@cisco.com>
2017-05-25MPLS hash function improvementsNeale Ranns1-0/+5
Change-Id: I28e98f445c01493562b6196a4f5b532a51f178af Signed-off-by: Neale Ranns <nranns@cisco.com>
2017-01-26DHCPv[46] proxy testsNeale Ranns1-0/+58
Change-Id: I6aaf9c602cd515ed9d4416d286f9191d048c1a87 Signed-off-by: Neale Ranns <nranns@cisco.com>
2016-12-02MPLS infrastructure improvmentsNeale Ranns1-0/+13
- deprecate MPLSoEth and MPLSoGRE; replace with generic MPLS tunnel. - deprecates CLI 'mpls encap ..'; replace with addition of MPLS out label to a route/tunnel. - support for MPLS 'routes', e.g. MPLS x-connects. - deprecates CLI 'mpls decap ..'; replace with 'mpls route .. ' Change-Id: Ibda46544912f880d0200f22bf9ff9b52828fcc2f Signed-off-by: Neale Ranns <nranns@cisco.com>
2016-11-22GRE tests and fixesNeale Ranns1-0/+25
Change-Id: I234240e9bdd4b69ad64a17b1449ae1e81c0edaca Signed-off-by: Neale Ranns <nranns@cisco.com>
ighlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
#!/usr/bin/env python
#
# Copyright (c) 2016 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.
#

from __future__ import print_function
from __future__ import absolute_import
import ctypes
import sys
import multiprocessing as mp
import os
import logging
import functools
import json
import threading
import fnmatch
import weakref
import atexit
from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType
from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias

if sys.version[0] == '2':
    import Queue as queue
else:
    import queue as queue

__all__ = ('FuncWrapper', 'VPP', 'VppApiDynamicMethodHolder',
           'VppEnum', 'VppEnumType',
           'VPPIOError', 'VPPRuntimeError', 'VPPValueError',
           'VPPApiClient', )


def metaclass(metaclass):
    @functools.wraps(metaclass)
    def wrapper(cls):
        return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())

    return wrapper


class VppEnumType(type):
    def __getattr__(cls, name):
        t = vpp_get_type(name)
        return t.enum


@metaclass(VppEnumType)
class VppEnum(object):
    pass


def vpp_atexit(vpp_weakref):
    """Clean up VPP connection on shutdown."""
    vpp_instance = vpp_weakref()
    if vpp_instance and vpp_instance.transport.connected:
        vpp_instance.logger.debug('Cleaning up VPP on exit')
        vpp_instance.disconnect()


if sys.version[0] == '2':
    def vpp_iterator(d):
        return d.iteritems()
else:
    def vpp_iterator(d):
        return d.items()


class VppApiDynamicMethodHolder(object):
    pass


class FuncWrapper(object):
    def __init__(self, func):
        self._func = func
        self.__name__ = func.__name__
        self.__doc__ = func.__doc__

    def __call__(self, **kwargs):
        return self._func(**kwargs)

    def __repr__(self):
        return '<FuncWrapper(func=<%s(%s)>)>' % (self.__name__, self.__doc__)


class VPPApiError(Exception):
    pass


class VPPNotImplementedError(NotImplementedError):
    pass


class VPPIOError(IOError):
    pass


class VPPRuntimeError(RuntimeError):
    pass


class VPPValueError(ValueError):
    pass

class VPPApiJSONFiles(object):
    @classmethod
    def find_api_dir(cls, dirs):
        """Attempt to find the best directory in which API definition
        files may reside. If the value VPP_API_DIR exists in the environment
        then it is first on the search list. If we're inside a recognized
        location in a VPP source tree (src/scripts and src/vpp-api/python)
        then entries from there to the likely locations in build-root are
        added. Finally the location used by system packages is added.

        :returns: A single directory name, or None if no such directory
            could be found.
        """

        # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
        # in which case, plot a course to likely places in the src tree
        import __main__ as main
        if hasattr(main, '__file__'):
            # get the path of the calling script
            localdir = os.path.dirname(os.path.realpath(main.__file__))
        else:
            # use cwd if there is no calling script
            localdir = os.getcwd()
        localdir_s = localdir.split(os.path.sep)

        def dmatch(dir):
            """Match dir against right-hand components of the script dir"""
            d = dir.split('/')  # param 'dir' assumes a / separator
            length = len(d)
            return len(localdir_s) > length and localdir_s[-length:] == d

        def sdir(srcdir, variant):
            """Build a path from srcdir to the staged API files of
            'variant'  (typically '' or '_debug')"""
            # Since 'core' and 'plugin' files are staged
            # in separate directories, we target the parent dir.
            return os.path.sep.join((
                srcdir,
                'build-root',
                'install-vpp%s-native' % variant,
                'vpp',
                'share',
                'vpp',
                'api',
            ))

        srcdir = None
        if dmatch('src/scripts'):
            srcdir = os.path.sep.join(localdir_s[:-2])
        elif dmatch('src/vpp-api/python'):
            srcdir = os.path.sep.join(localdir_s[:-3])
        elif dmatch('test'):
            # we're apparently running tests
            srcdir = os.path.sep.join(localdir_s[:-1])

        if srcdir:
            # we're in the source tree, try both the debug and release
            # variants.
            dirs.append(sdir(srcdir, '_debug'))
            dirs.append(sdir(srcdir, ''))

        # Test for staged copies of the scripts
        # For these, since we explicitly know if we're running a debug versus
        # release variant, target only the relevant directory
        if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
            srcdir = os.path.sep.join(localdir_s[:-4])
            dirs.append(sdir(srcdir, '_debug'))
        if dmatch('build-root/install-vpp-native/vpp/bin'):
            srcdir = os.path.sep.join(localdir_s[:-4])
            dirs.append(sdir(srcdir, ''))

        # finally, try the location system packages typically install into
        dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))

        # check the directories for existence; first one wins
        for dir in dirs:
            if os.path.isdir(dir):
                return dir

        return None

    @classmethod
    def find_api_files(cls, api_dir=None, patterns='*'):
        """Find API definition files from the given directory tree with the
        given pattern. If no directory is given then find_api_dir() is used
        to locate one. If no pattern is given then all definition files found
        in the directory tree are used.

        :param api_dir: A directory tree in which to locate API definition
            files; subdirectories are descended into.
            If this is None then find_api_dir() is called to discover it.
        :param patterns: A list of patterns to use in each visited directory
            when looking for files.
            This can be a list/tuple object or a comma-separated string of
            patterns. Each value in the list will have leading/trialing
            whitespace stripped.
            The pattern specifies the first part of the filename, '.api.json'
            is appended.
            The results are de-duplicated, thus overlapping patterns are fine.
            If this is None it defaults to '*' meaning "all API files".
        :returns: A list of file paths for the API files found.
        """
        if api_dir is None:
            api_dir = cls.find_api_dir([])
            if api_dir is None:
                raise VPPApiError("api_dir cannot be located")

        if isinstance(patterns, list) or isinstance(patterns, tuple):
            patterns = [p.strip() + '.api.json' for p in patterns]
        else:
            patterns = [p.strip() + '.api.json' for p in patterns.split(",")]

        api_files = []
        for root, dirnames, files in os.walk(api_dir):
            # iterate all given patterns and de-dup the result
            files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
            for filename in files:
                api_files.append(os.path.join(root, filename))

        return api_files

    @classmethod
    def process_json_file(self, apidef_file):
        api = json.load(apidef_file)
        types = {}
        services = {}
        messages = {}
        for t in api['enums']:
            t[0] = 'vl_api_' + t[0] + '_t'
            types[t[0]] = {'type': 'enum', 'data': t}
        for t in api['unions']:
            t[0] = 'vl_api_' + t[0] + '_t'
            types[t[0]] = {'type': 'union', 'data': t}
        for t in api['types']:
            t[0] = 'vl_api_' + t[0] + '_t'
            types[t[0]] = {'type': 'type', 'data': t}
        for t, v in api['aliases'].items():
            types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
        services.update(api['services'])

        i = 0
        while True:
            unresolved = {}
            for k, v in types.items():
                t = v['data']
                if not vpp_get_type(k):
                    if v['type'] == 'enum':
                        try:
                            VPPEnumType(t[0], t[1:])
                        except ValueError:
                            unresolved[k] = v
                    elif v['type'] == 'union':
                        try:
                            VPPUnionType(t[0], t[1:])
                        except ValueError:
                            unresolved[k] = v
                    elif v['type'] == 'type':
                        try:
                            VPPType(t[0], t[1:])
                        except ValueError:
                            unresolved[k] = v
                    elif v['type'] == 'alias':
                        try:
                            VPPTypeAlias(k, t)
                        except ValueError:
                            unresolved[k] = v
            if len(unresolved) == 0:
                break
            if i > 3:
                raise VPPValueError('Unresolved type definitions {}'
                                    .format(unresolved))
            types = unresolved
            i += 1

        for m in api['messages']:
            try:
                messages[m[0]] = VPPMessage(m[0], m[1:])
            except VPPNotImplementedError:
                ### OLE FIXME
                self.logger.error('Not implemented error for {}'.format(m[0]))
        return messages, services

class VPPApiClient(object):
    """VPP interface.

    This class provides the APIs to VPP.  The APIs are loaded
    from provided .api.json files and makes functions accordingly.
    These functions are documented in the VPP .api files, as they
    are dynamically created.

    Additionally, VPP can send callback messages; this class
    provides a means to register a callback function to receive
    these messages in a background thread.
    """
    apidir = None
    VPPApiError = VPPApiError
    VPPRuntimeError = VPPRuntimeError
    VPPValueError = VPPValueError
    VPPNotImplementedError = VPPNotImplementedError
    VPPIOError = VPPIOError


    def __init__(self, apifiles=None, testmode=False, async_thread=True,
                 logger=None, loglevel=None,
                 read_timeout=5, use_socket=False,
                 server_address='/run/vpp/api.sock'):
        """Create a VPP API object.

        apifiles is a list of files containing API
        descriptions that will be loaded - methods will be
        dynamically created reflecting these APIs.  If not
        provided this will load the API files from VPP's
        default install location.

        logger, if supplied, is the logging logger object to log to.
        loglevel, if supplied, is the log level this logger is set
        to report at (from the loglevels in the logging module).
        """
        if logger is None:
            logger = logging.getLogger(
                "{}.{}".format(__name__, self.__class__.__name__))
            if loglevel is not None:
                logger.setLevel(loglevel)
        self.logger = logger

        self.messages = {}
        self.services = {}
        self.id_names = []
        self.id_msgdef = []
        self.header = VPPType('header', [['u16', 'msgid'],
                                         ['u32', 'client_index']])
        self.apifiles = []
        self.event_callback = None
        self.message_queue = queue.Queue()
        self.read_timeout = read_timeout
        self.async_thread = async_thread
        self.event_thread = None
        self.testmode = testmode
        self.use_socket = use_socket
        self.server_address = server_address
        self._apifiles = apifiles

        if use_socket:
            from . vpp_transport_socket import VppTransport
        else:
            from . vpp_transport_shmem import VppTransport

        if not apifiles:
            # Pick up API definitions from default directory
            try:
                apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
            except RuntimeError:
                # In test mode we don't care that we can't find the API files
                if testmode:
                    apifiles = []
                else:
                    raise VPPRuntimeError

        for file in apifiles:
            with open(file) as apidef_file:
                m, s = VPPApiJSONFiles.process_json_file(apidef_file)
                self.messages.update(m)
                self.services.update(s)

        self.apifiles = apifiles

        # Basic sanity check
        if len(self.messages) == 0 and not testmode:
            raise VPPValueError(1, 'Missing JSON message definitions')

        self.transport = VppTransport(self, read_timeout=read_timeout,
                                      server_address=server_address)
        # Make sure we allow VPP to clean up the message rings.
        atexit.register(vpp_atexit, weakref.ref(self))

    def get_function(self, name):
        return getattr(self._api, name)


    class ContextId(object):
        """Multiprocessing-safe provider of unique context IDs."""
        def __init__(self):
            self.context = mp.Value(ctypes.c_uint, 0)
            self.lock = mp.Lock()

        def __call__(self):
            """Get a new unique (or, at least, not recently used) context."""
            with self.lock:
                self.context.value += 1
                return self.context.value
    get_context = ContextId()

    def get_type(self, name):
        return vpp_get_type(name)

    @property
    def api(self):
        if not hasattr(self, "_api"):
            raise VPPApiError("Not connected, api definitions not available")
        return self._api

    def make_function(self, msg, i, multipart, do_async):
        if (do_async):
            def f(**kwargs):
                return self._call_vpp_async(i, msg, **kwargs)
        else:
            def f(**kwargs):
                return self._call_vpp(i, msg, multipart, **kwargs)

        f.__name__ = str(msg.name)
        f.__doc__ = ", ".join(["%s %s" %
                               (msg.fieldtypes[j], k)
                               for j, k in enumerate(msg.fields)])
        f.msg = msg

        return f

    def _register_functions(self, do_async=False):
        self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
        self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
        self._api = VppApiDynamicMethodHolder()
        for name, msg in vpp_iterator(self.messages):
            n = name + '_' + msg.crc[2:]
            i = self.transport.get_msg_index(n)
            if i > 0:
                self.id_msgdef[i] = msg
                self.id_names[i] = name

                # Create function for client side messages.
                if name in self.services:
                    if 'stream' in self.services[name] and \
                       self.services[name]['stream']:
                        multipart = True
                    else:
                        multipart = False
                    f = self.make_function(msg, i, multipart, do_async)
                    setattr(self._api, name, FuncWrapper(f))
            else:
                self.logger.debug(
                    'No such message type or failed CRC checksum: %s', n)

    def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
                         do_async):
        pfx = chroot_prefix.encode('utf-8') if chroot_prefix else None

        rv = self.transport.connect(name, pfx,
                                    msg_handler, rx_qlen)
        if rv != 0:
            raise VPPIOError(2, 'Connect failed')
        self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
        self._register_functions(do_async=do_async)

        # Initialise control ping
        crc = self.messages['control_ping'].crc
        self.control_ping_index = self.transport.get_msg_index(
            ('control_ping' + '_' + crc[2:]))
        self.control_ping_msgdef = self.messages['control_ping']
        if self.async_thread:
            self.event_thread = threading.Thread(
                target=self.thread_msg_handler)
            self.event_thread.daemon = True
            self.event_thread.start()
        else:
            self.event_thread = None
        return rv

    def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
        """Attach to VPP.

        name - the name of the client.
        chroot_prefix - if VPP is chroot'ed, the prefix of the jail
        do_async - if true, messages are sent without waiting for a reply
        rx_qlen - the length of the VPP message receive queue between
        client and server.
        """
        msg_handler = self.transport.get_callback(do_async)
        return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
                                     do_async)

    def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
        """Attach to VPP in synchronous mode. Application must poll for events.

        name - the name of the client.
        chroot_prefix - if VPP is chroot'ed, the prefix of the jail
        rx_qlen - the length of the VPP message receive queue between
        client and server.
        """

        return self.connect_internal(name, None, chroot_prefix, rx_qlen,
                                     do_async=False)

    def disconnect(self):
        """Detach from VPP."""
        rv = self.transport.disconnect()
        if self.event_thread is not None:
            self.message_queue.put("terminate event thread")
        return rv

    def msg_handler_sync(self, msg):
        """Process an incoming message from VPP in sync mode.

        The message may be a reply or it may be an async notification.
        """
        r = self.decode_incoming_msg(msg)
        if r is None:
            return

        # If we have a context, then use the context to find any
        # request waiting for a reply
        context = 0
        if hasattr(r, 'context') and r.context > 0:
            context = r.context

        if context == 0:
            # No context -> async notification that we feed to the callback
            self.message_queue.put_nowait(r)
        else:
            raise VPPIOError(2, 'RPC reply message received in event handler')

    def has_context(self, msg):
        if len(msg) < 10:
            return False

        header = VPPType('header_with_context', [['u16', 'msgid'],
                                                 ['u32', 'client_index'],
                                                 ['u32', 'context']])

        (i, ci, context), size = header.unpack(msg, 0)
        if self.id_names[i] == 'rx_thread_exit':
            return

        #
        # Decode message and returns a tuple.
        #
        msgobj = self.id_msgdef[i]
        if 'context' in msgobj.field_by_name and context >= 0:
            return True
        return False

    def decode_incoming_msg(self, msg, no_type_conversion=False):
        if not msg:
            self.logger.warning('vpp_api.read failed')
            return

        (i, ci), size = self.header.unpack(msg, 0)
        if self.id_names[i] == 'rx_thread_exit':
            return

        #
        # Decode message and returns a tuple.
        #
        msgobj = self.id_msgdef[i]
        if not msgobj:
            raise VPPIOError(2, 'Reply message undefined')

        r, size = msgobj.unpack(msg, ntc=no_type_conversion)
        return r

    def msg_handler_async(self, msg):
        """Process a message from VPP in async mode.

        In async mode, all messages are returned to the callback.
        """
        r = self.decode_incoming_msg(msg)
        if r is None:
            return

        msgname = type(r).__name__

        if self.event_callback:
            self.event_callback(msgname, r)

    def _control_ping(self, context):
        """Send a ping command."""
        self._call_vpp_async(self.control_ping_index,
                             self.control_ping_msgdef,
                             context=context)

    def validate_args(self, msg, kwargs):
        d = set(kwargs.keys()) - set(msg.field_by_name.keys())
        if d:
            raise VPPValueError('Invalid argument {} to {}'
                                .format(list(d), msg.name))

    def _call_vpp(self, i, msgdef, multipart, **kwargs):
        """Given a message, send the message and await a reply.

        msgdef - the message packing definition
        i - the message type index
        multipart - True if the message returns multiple
        messages in return.
        context - context number - chosen at random if not
        supplied.
        The remainder of the kwargs are the arguments to the API call.

        The return value is the message or message array containing
        the response.  It will raise an IOError exception if there was
        no response within the timeout window.
        """

        if 'context' not in kwargs:
            context = self.get_context()
            kwargs['context'] = context
        else:
            context = kwargs['context']
        kwargs['_vl_msg_id'] = i

        no_type_conversion = kwargs.pop('_no_type_conversion', False)

        try:
            if self.transport.socket_index:
                kwargs['client_index'] = self.transport.socket_index
        except AttributeError:
            pass
        self.validate_args(msgdef, kwargs)

        s = 'Calling {}({})'.format(msgdef.name,
            ','.join(['{!r}:{!r}'.format(k, v) for k, v in kwargs.items()]))
        self.logger.debug(s)

        b = msgdef.pack(kwargs)
        self.transport.suspend()

        self.transport.write(b)

        if multipart:
            # Send a ping after the request - we use its response
            # to detect that we have seen all results.
            self._control_ping(context)

        # Block until we get a reply.
        rl = []
        while (True):
            msg = self.transport.read()
            if not msg:
                raise VPPIOError(2, 'VPP API client: read failed')
            r = self.decode_incoming_msg(msg, no_type_conversion)
            msgname = type(r).__name__
            if context not in r or r.context == 0 or context != r.context:
                # Message being queued
                self.message_queue.put_nowait(r)
                continue

            if not multipart:
                rl = r
                break
            if msgname == 'control_ping_reply':
                break

            rl.append(r)

        self.transport.resume()

        self.logger.debug('Return from {!r}'.format(r))
        return rl

    def _call_vpp_async(self, i, msg, **kwargs):
        """Given a message, send the message and await a reply.

        msgdef - the message packing definition
        i - the message type index
        context - context number - chosen at random if not
        supplied.
        The remainder of the kwargs are the arguments to the API call.
        """
        if 'context' not in kwargs:
            context = self.get_context()
            kwargs['context'] = context
        else:
            context = kwargs['context']
        try:
            if self.transport.socket_index:
                kwargs['client_index'] = self.transport.socket_index
        except AttributeError:
            kwargs['client_index'] = 0
        kwargs['_vl_msg_id'] = i
        b = msg.pack(kwargs)

        self.transport.write(b)

    def register_event_callback(self, callback):
        """Register a callback for async messages.

        This will be called for async notifications in sync mode,
        and all messages in async mode.  In sync mode, replies to
        requests will not come here.

        callback is a fn(msg_type_name, msg_type) that will be
        called when a message comes in.  While this function is
        executing, note that (a) you are in a background thread and
        may wish to use threading.Lock to protect your datastructures,
        and (b) message processing from VPP will stop (so if you take
        a long while about it you may provoke reply timeouts or cause
        VPP to fill the RX buffer).  Passing None will disable the
        callback.
        """
        self.event_callback = callback

    def thread_msg_handler(self):
        """Python thread calling the user registered message handler.

        This is to emulate the old style event callback scheme. Modern
        clients should provide their own thread to poll the event
        queue.
        """
        while True:
            r = self.message_queue.get()
            if r == "terminate event thread":
                break
            msgname = type(r).__name__
            if self.event_callback:
                self.event_callback(msgname, r)

    def __repr__(self):
        return "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, " \
               "logger=%s, read_timeout=%s, use_socket=%s, " \
               "server_address='%s'>" % (
                   self._apifiles, self.testmode, self.async_thread,
                   self.logger, self.read_timeout, self.use_socket,
                   self.server_address)


# Provide the old name for backward compatibility.
VPP = VPPApiClient

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4