diff options
author | 2018-11-20 03:35:38 +0800 | |
---|---|---|
committer | 2018-11-20 03:41:08 +0800 | |
commit | fd6c6f7c42d148a980030c7535846a4643d8cc9b (patch) | |
tree | 42df5fa7198777b4f3f711c0142f6cb0a7e7fea5 | |
parent | a45498c3a487d9fb561c24b2f3df7910fa54c0f8 (diff) |
Initial code commit for Sweetcomb project
The initial code is contruted by Cisco and HuachenTel.
It supports three key features:
1). Keeps compatible with IETF Yang Model and Honeycomb Yang Model.
2). Supports mulitiple clients to configure VPP simultaneously.
3). Automatically connect to VPP.
Change-Id: I3dda4fddb14062e5f113d505c6a7ee64d60c44c0
Signed-off-by: Rastislav Szabo <raszabo@cisco.com>
Signed-off-by: Chuanguo Wang <wangchuanguo@huachentel.com>
Signed-off-by: Hongjun Ni <hongjun.ni@intel.com>
-rw-r--r-- | Makefile | 112 | ||||
-rwxr-xr-x | build-root/scripts/checkstyle.sh | 150 | ||||
-rw-r--r-- | src/CMakeLists.txt | 35 | ||||
-rw-r--r-- | src/README.md | 9 | ||||
-rw-r--r-- | src/plugins/CMakeLists.txt | 39 | ||||
-rw-r--r-- | src/plugins/vpp-interfaces.c | 593 | ||||
-rw-r--r-- | src/plugins/yang/ietf-interfaces.data.xml | 21 | ||||
-rw-r--r-- | src/plugins/yang/ietf-interfaces@2014-05-08.yang | 707 | ||||
-rw-r--r-- | src/plugins/yang/ietf-ip@2014-06-16.yang | 742 | ||||
-rw-r--r-- | src/plugins/yang/ietf-yang-types@2013-07-15.yang | 474 | ||||
-rw-r--r-- | src/srvpp/CMakeLists.txt | 40 | ||||
-rw-r--r-- | src/srvpp/libsrvpp.pc.in | 10 | ||||
-rw-r--r-- | src/srvpp/src/CMakeLists.txt | 44 | ||||
-rw-r--r-- | src/srvpp/src/srvpp.c | 823 | ||||
-rw-r--r-- | src/srvpp/src/srvpp.h | 185 | ||||
-rw-r--r-- | src/srvpp/src/srvpp_logger.c | 83 | ||||
-rw-r--r-- | src/srvpp/src/srvpp_logger.h | 89 | ||||
-rw-r--r-- | src/srvpp/tests/CMakeLists.txt | 40 | ||||
-rw-r--r-- | src/srvpp/tests/srvpp_test.c | 160 |
19 files changed, 4356 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4383135 --- /dev/null +++ b/Makefile @@ -0,0 +1,112 @@ +# Copyright (c) 2018 Intel 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. + +export WS_ROOT=$(CURDIR) +export BR=$(WS_ROOT)/build-root +CCACHE_DIR?=$(BR)/.ccache +GDB?=gdb +PLATFORM?=sweetcomb +MACHINE=$(shell uname -m) +SUDO?=sudo + +# +# OS Detection +# +ifneq ($(shell uname),Darwin) +OS_ID = $(shell grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +endif + +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) +PKG=deb +else ifeq ($(filter rhel centos fedora opensuse opensuse-leap opensuse-tumbleweed,$(OS_ID)),$(OS_ID)) +PKG=rpm +endif + +# +libganglia1-dev if building the gmond plugin + +DEB_DEPENDS = curl build-essential autoconf automake ccache +DEB_DEPENDS += bison flex libpcre3-dev libev-dev libavl-dev libprotobuf-c-dev protobuf-c-compiler +DEB_DEPENDS += cmake ninja-build +ifeq ($(OS_VERSION_ID),14.04) + DEB_DEPENDS += libssl-dev +else + DEB_DEPENDS += libssl-dev +endif + +ifeq ($(findstring y,$(UNATTENDED)),y) +CONFIRM=-y +FORCE=--force-yes +endif + +TARGETS = sweetcomb + +.PHONY: help wipe wipe-release build build-release rebuild rebuild-release + +define banner + @echo "========================================================================" + @echo " $(1)" + @echo "========================================================================" + @echo " " +endef + +help: + @echo "Make Targets:" + @echo " install-dep - install software dependencies" + @echo " checkstyle - check coding style" + @echo " fixstyle - fix coding style" + +$(BR)/.deps.ok: +ifeq ($(findstring y,$(UNATTENDED)),y) + make install-dep +endif +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) + @MISSING=$$(apt-get install -y -qq -s $(DEB_DEPENDS) | grep "^Inst ") ; \ + if [ -n "$$MISSING" ] ; then \ + echo "\nPlease install missing packages: \n$$MISSING\n" ; \ + echo "by executing \"make install-dep\"\n" ; \ + exit 1 ; \ + fi ; \ + exit 0 +endif + @touch $@ + +install-dep: +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) +ifeq ($(OS_VERSION_ID),14.04) + @sudo -E apt-get $(CONFIRM) $(FORCE) install software-properties-common + @sudo -E add-apt-repository ppa:openjdk-r/ppa $(CONFIRM) +endif + @sudo -E apt-get update + @sudo -E apt-get $(APT_ARGS) $(CONFIRM) $(FORCE) install $(DEB_DEPENDS) +else + $(error "This option currently works only on Ubuntu, Debian, Centos or openSUSE systems") +endif + +define make + @make -C $(BR) PLATFORM=$(PLATFORM) TAG=$(1) $(2) +endef + +$(BR)/scripts/.version: +ifneq ("$(wildcard /etc/redhat-release)","") + $(shell $(BR)/scripts/version rpm-string > $(BR)/scripts/.version) +else + $(shell $(BR)/scripts/version > $(BR)/scripts/.version) +endif + +checkstyle: + @build-root/scripts/checkstyle.sh + +fixstyle: + @build-root/scripts/checkstyle.sh --fix + diff --git a/build-root/scripts/checkstyle.sh b/build-root/scripts/checkstyle.sh new file mode 100755 index 0000000..3a898cb --- /dev/null +++ b/build-root/scripts/checkstyle.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +# Copyright (c) 2015 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. + +SWEETCOMB_DIR=`dirname $0`/../../ +EXIT_CODE=0 +FIX="0" +FULL="0" +CHECKSTYLED_FILES="" +UNCHECKSTYLED_FILES="" + +# If the user provides --fix, then actually fix things +# Note: this is meant for use outside of the CI Jobs, by users cleaning things up + +while true; do + case ${1} in + --fix) + FIX="1" + ;; + --full) + FULL="1" + ;; + esac + shift || break +done + +if [ "${FULL}" == "1" ]; then + FILELIST=$(git ls-tree -r HEAD --name-only) +else + FILELIST=$((git diff HEAD~1.. --name-only; git ls-files -m ) | sort -u) +fi + +# Check to make sure we have indent. Exit if we don't with an error message, but +# don't *fail*. +command -v indent > /dev/null +if [ $? != 0 ]; then + echo "Cound not find required command \"indent\". Checkstyle aborted" + exit ${EXIT_CODE} +fi +indent --version + +# Check to make sure we have clang-format. Exit if we don't with an error message, but +# don't *fail*. +HAVE_CLANG_FORMAT=0 +command -v clang-format > /dev/null +if [ $? != 0 ]; then + echo "Could not find command \"clang-format\". Checking C++ files will cause abort" +else + clang-format --version + x=$(echo "" | clang-format 2>&1) + if [[ "$x" == "" ]]; then + HAVE_CLANG_FORMAT=1 + else + echo "Output produced while formatting empty file (expected empty string):" + echo "$x" + echo "Could not find working \"clang-format\". Checking C++ files will cause abort" + fi +fi + +cd ${SWEETCOMB_DIR} +git status +for i in ${FILELIST}; do + if [ -f ${i} ] && [ ${i} != "build-root/scripts/checkstyle.sh" ] && [ ${i} != "extras/emacs/fix-coding-style.el" ]; then + grep -q "fd.io coding-style-patch-verification: ON" ${i} + if [ $? == 0 ]; then + EXTENSION=`basename ${i} | sed 's/^\w\+.//'` + case ${EXTENSION} in + hpp|cpp|cc|hh) + CMD="clang-format" + if [ ${HAVE_CLANG_FORMAT} == 0 ]; then + echo "C++ file detected. Abort. (missing clang-format)" + exit ${EXIT_CODE} + fi + ;; + *) + CMD="indent" + ;; + esac + CHECKSTYLED_FILES="${CHECKSTYLED_FILES} ${i}" + if [ ${FIX} == 0 ]; then + if [ "${CMD}" == "clang-format" ] + then + clang-format ${i} > ${i}.out2 + else + indent ${i} -o ${i}.out1 > /dev/null 2>&1 + indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i}.out2 + diff -q ${i} ${i}.out2 + else + if [ "${CMD}" == "clang-format" ]; then + clang-format -i ${i} > /dev/null 2>&1 + else + indent ${i} + indent ${i} + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i} + fi + if [ $? != 0 ]; then + EXIT_CODE=1 + echo + echo "Checkstyle failed for ${i}." + if [ "${CMD}" == "clang-format" ]; then + echo "Run clang-format as shown to fix the problem:" + echo "clang-format -i ${SWEETCOMB_DIR}${i}" + else + echo "Run indent (twice!) as shown to fix the problem:" + echo "indent ${SWEETCOMB_DIR}${i}" + echo "indent ${SWEETCOMB_DIR}${i}" + fi + fi + if [ -f ${i}.out1 ]; then + rm ${i}.out1 + fi + if [ -f ${i}.out2 ]; then + rm ${i}.out2 + fi + else + UNCHECKSTYLED_FILES="${UNCHECKSTYLED_FILES} ${i}" + fi + else + UNCHECKSTYLED_FILES="${UNCHECKSTYLED_FILES} ${i}" + fi +done + +if [ ${EXIT_CODE} == 0 ]; then + echo "*******************************************************************" + echo "* SWEETCOMB CHECKSTYLE SUCCESSFULLY COMPLETED" + echo "*******************************************************************" +else + echo "*******************************************************************" + echo "* VPP CHECKSTYLE FAILED" + echo "* CONSULT FAILURE LOG ABOVE" + echo "* NOTE: Running 'build-root/scripts/checkstyle.sh --fix' *MAY* fix the issue" + echo "*******************************************************************" +fi +exit ${EXIT_CODE} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..8d31550 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# 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. +# + +cmake_minimum_required(VERSION 2.8) +project(sysrepo-vpp) + +include(GNUInstallDirs) + +# set default build type if not specified by user +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE debug) +endif() +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + +# set compiler options +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=gnu99") +set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O2") +set(CMAKE_C_FLAGS_DEBUG "-g -O0") + +# add subdirectories +add_subdirectory(srvpp) +add_subdirectory(plugins) diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..3375d25 --- /dev/null +++ b/src/README.md @@ -0,0 +1,9 @@ +# Sysrepo - VPP Integration + +This repository contains a work-in-progress code of the integration of [Sysrepo datastore](https://github.com/sysrepo/sysrepo/) with [VPP](https://fd.io/). + +It consists of two components: +- [srvpp](srvpp/) library that provides convenient API for managing VPP from Sysrepo plugins, +- [plugins](plugins/) for Sysrepo that provide the VPP management functionality. + +As of now, basic VPP interface management (interface enable/disble, IPv4/IPv6 address config) has been implemented in [vpp-interfaces plugin](plugins/vpp-interfaces.c). diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt new file mode 100644 index 0000000..f21d06f --- /dev/null +++ b/src/plugins/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# 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. +# + +cmake_minimum_required(VERSION 2.8) +project(sysrepo-vpp-plugins) + +find_package(PkgConfig) +pkg_check_modules(SYSREPO REQUIRED libsysrepo) +pkg_check_modules(SRVPP REQUIRED libsrvpp) + +# get sysrepo plugins directory from pkgconfig +if (NOT SR_PLUGINS_DIR) + if (PKG_CONFIG_FOUND) + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} "--variable=SR_PLUGINS_DIR" "libsysrepo" OUTPUT_VARIABLE SR_PLUGINS_DIR) + string(STRIP ${SR_PLUGINS_DIR} SR_PLUGINS_DIR) + endif() +endif() +if (NOT SR_PLUGINS_DIR) + message(FATAL_ERROR "Cannot get sysrepo plugins directory due to missing pkg-config, set SR_PLUGINS_DIR manually.") +endif() + +# build the source code into shared library +add_library(vpp-interfaces SHARED vpp-interfaces.c) +target_link_libraries(vpp-interfaces ${SYSREPO_LIBRARIES} ${SRVPP_LIBRARIES}) + +# install the plugin into plugins dir +install(TARGETS vpp-interfaces DESTINATION ${SR_PLUGINS_DIR}) diff --git a/src/plugins/vpp-interfaces.c b/src/plugins/vpp-interfaces.c new file mode 100644 index 0000000..9525c18 --- /dev/null +++ b/src/plugins/vpp-interfaces.c @@ -0,0 +1,593 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sysrepo.h> +#include <sysrepo/plugins.h> +#include <sysrepo/values.h> +#include <sysrepo/xpath.h> +#include <srvpp.h> + +/** forward declaration */ +void sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *private_ctx); + +/** + * @brief Plugin context structure. + */ +typedef struct plugin_ctx_s { + srvpp_ctx_t *srvpp_ctx; /**< srvpp context. */ + sr_subscription_ctx_t *sr_subscription; /**< Sysrepo subscription context. */ +} plugin_ctx_t; + +/** + * @brief Helper function for converting netmask into prefix length. + */ +static uint8_t +netmask_to_prefix(const char *netmask) +{ + in_addr_t n = 0; + uint8_t i = 0; + + inet_pton(AF_INET, netmask, &n); + + while (n > 0) { + n = n >> 1; + i++; + } + + return i; +} + +/** + * @brief Helper function for converting IPv4/IPv6 address string into binary representation. + */ +static void +ip_addr_str_to_binary(const char *ip_address_str, uint8_t *ip_address_bin, bool is_ipv6) +{ + struct in6_addr addr6 = { 0, }; + struct in_addr addr4 = { 0, }; + + if (is_ipv6) { + inet_pton(AF_INET6, ip_address_str, &(addr6)); + memcpy(ip_address_bin, &addr6, sizeof(addr6)); + } else { + inet_pton(AF_INET, ip_address_str, &(addr4)); + memcpy(ip_address_bin, &addr4, sizeof(addr4)); + } +} + +/** + * @brief Helper function for converting VPP interface speed information into actual number in bits per second. + */ +static uint64_t +get_if_link_speed(uint8_t speed) +{ +#define ONE_MEGABIT (uint64_t)1000000 + switch (speed) { + case 1: + /* 10M */ + return 10 * ONE_MEGABIT; + break; + case 2: + /* 100M */ + return 100 * ONE_MEGABIT; + break; + case 4: + /* 1G */ + return 1000 * ONE_MEGABIT; + break; + case 8: + /* 10G */ + return 10000 * ONE_MEGABIT; + break; + case 16: + /* 40 G */ + return 40000 * ONE_MEGABIT; + break; + case 32: + /* 100G */ + return 100000 * ONE_MEGABIT; + break; + default: + return 0; + } +} + +/** + * @brief Enable or disable given interface. + */ +static int +interface_enable_disable(plugin_ctx_t *plugin_ctx, const char *if_name, bool enable) +{ + vl_api_sw_interface_set_flags_t *if_set_flags_req = NULL; + uint32_t if_index = ~0; + int rc = 0; + + SRP_LOG_DBG("%s interface '%s'", enable ? "Enabling" : "Disabling", if_name); + + /* get interface index */ + rc = srvpp_get_if_index(plugin_ctx->srvpp_ctx, if_name, &if_index); + if (0 != rc) { + SRP_LOG_ERR("Invalid interface name: %s", if_name); + return SR_ERR_INVAL_ARG; + } + + /* process VPP API request */ + if_set_flags_req = srvpp_alloc_msg(VL_API_SW_INTERFACE_SET_FLAGS, sizeof(*if_set_flags_req)); + + if_set_flags_req->sw_if_index = ntohl(if_index); + if_set_flags_req->admin_up_down = (uint8_t)enable; + + rc = srvpp_send_request(plugin_ctx->srvpp_ctx, if_set_flags_req, NULL); + + if (0 != rc) { + SRP_LOG_ERR("Error by processing of the sw_interface_set_flags request, rc=%d", rc); + return SR_ERR_OPERATION_FAILED; + } else { + return SR_ERR_OK; + } +} + +/** + * @brief Callback to be called by any config change of "/ietf-interfaces:interfaces/interface/enabled" leaf. + */ +static int +interface_enable_disable_cb(sr_session_ctx_t *session, const char *xpath, sr_notif_event_t event, void *private_ctx) +{ + char *if_name = NULL; + sr_change_iter_t *iter = NULL; + sr_change_oper_t op = SR_OP_CREATED; + sr_val_t *old_val = NULL; + sr_val_t *new_val = NULL; + sr_xpath_ctx_t xpath_ctx = { 0, }; + int rc = SR_ERR_OK, op_rc = SR_ERR_OK; + + /* no-op for apply, we only care about SR_EV_ENABLED, SR_EV_VERIFY, SR_EV_ABORT */ + if (SR_EV_APPLY == event) { + return SR_ERR_OK; + } + SRP_LOG_DBG("'%s' modified, event=%d", xpath, event); + + /* get changes iterator */ + rc = sr_get_changes_iter(session, xpath, &iter); + if (SR_ERR_OK != rc) { + SRP_LOG_ERR("Unable to retrieve change iterator: %s", sr_strerror(rc)); + return rc; + } + + /* iterate over all changes */ + while ((SR_ERR_OK == op_rc || event == SR_EV_ABORT) && + (SR_ERR_OK == (rc = sr_get_change_next(session, iter, &op, &old_val, &new_val)))) { + + SRP_LOG_DBG("A change detected in '%s', op=%d", new_val ? new_val->xpath : old_val->xpath, op); + if_name = sr_xpath_key_value(new_val ? new_val->xpath : old_val->xpath, "interface", "name", &xpath_ctx); + switch (op) { + case SR_OP_CREATED: + case SR_OP_MODIFIED: + op_rc = interface_enable_disable(private_ctx, if_name, new_val->data.bool_val); + break; + case SR_OP_DELETED: + op_rc = interface_enable_disable(private_ctx, if_name, false /* !enable */); + break; + default: + break; + } + sr_xpath_recover(&xpath_ctx); + if (SR_ERR_INVAL_ARG == op_rc) { + sr_set_error(session, "Invalid interface name.", new_val ? new_val->xpath : old_val->xpath); + } + sr_free_val(old_val); + sr_free_val(new_val); + } + sr_free_change_iter(iter); + + return op_rc; +} + +/** + * @brief Add or remove IPv4/IPv6 address to/from an interface. + */ +static int +interface_ipv46_config_add_remove(plugin_ctx_t *plugin_ctx, const char *if_name, uint8_t *addr, uint8_t prefix, + bool is_ipv6, bool add) +{ + vl_api_sw_interface_add_del_address_t *add_del_req = NULL; + uint32_t if_index = ~0; + int rc = 0; + + SRP_LOG_DBG("%s IP config on interface '%s'.", add ? "Adding" : "Removing", if_name); + + /* get interface index */ + rc = srvpp_get_if_index(plugin_ctx->srvpp_ctx, if_name, &if_index); + if (0 != rc) { + SRP_LOG_ERR("Invalid interface name: %s", if_name); + return SR_ERR_INVAL_ARG; + } + + /* process VPP API request */ + add_del_req = srvpp_alloc_msg(VL_API_SW_INTERFACE_ADD_DEL_ADDRESS, sizeof(*add_del_req)); + + memcpy(&add_del_req->address, addr, is_ipv6 ? 16 : 4); + add_del_req->is_ipv6 = (uint32_t)is_ipv6; + add_del_req->address_length = prefix; + + add_del_req->sw_if_index = ntohl(if_index); + add_del_req->is_add = (uint32_t)add; + + rc = srvpp_send_request(plugin_ctx->srvpp_ctx, add_del_req, NULL); + + if (0 != rc) { + SRP_LOG_ERR("Error by processing of the sw_interface_set_flags request, rc=%d", rc); + return SR_ERR_OPERATION_FAILED; + } else { + return SR_ERR_OK; + } +} + +/** + * @brief Modify existing IPv4/IPv6 config on an interface. + */ +static int +interface_ipv46_config_modify(plugin_ctx_t *plugin_ctx, sr_session_ctx_t *session, const char *if_name, + sr_val_t *old_val, sr_val_t *new_val, bool is_ipv6) +{ + sr_xpath_ctx_t xpath_ctx = { 0, }; + char *addr_str = NULL; + uint8_t addr[16] = { 0, }; + uint8_t prefix = 0; + int rc = SR_ERR_OK; + + SRP_LOG_DBG("Updating IP config on interface '%s'.", if_name); + + /* get old config to be deleted */ + if (SR_UINT8_T == old_val->type) { + prefix = old_val->data.uint8_val; + } else if (SR_STRING_T == old_val->type) { + prefix = netmask_to_prefix(old_val->data.string_val); + } else { + return SR_ERR_INVAL_ARG; + } + addr_str = sr_xpath_key_value((char*)old_val->xpath, "address", "ip", &xpath_ctx); + ip_addr_str_to_binary(addr_str, addr, is_ipv6); + sr_xpath_recover(&xpath_ctx); + + /* delete old IP config */ + rc = interface_ipv46_config_add_remove(plugin_ctx, if_name, addr, prefix, is_ipv6, false /* remove */); + if (SR_ERR_OK != rc) { + SRP_LOG_ERR("Unable to remove old IP address config, rc=%d", rc); + return rc; + } + + /* update the config with the new value */ + if (sr_xpath_node_name_eq(new_val->xpath, "prefix-length")) { + prefix = new_val->data.uint8_val; + } else if (sr_xpath_node_name_eq(new_val->xpath, "netmask")) { + prefix = netmask_to_prefix(new_val->data.string_val); + } + + /* set new IP config */ + rc = interface_ipv46_config_add_remove(plugin_ctx, if_name, addr, prefix, is_ipv6, true /* add */); + if (SR_ERR_OK != rc) { + SRP_LOG_ERR("Unable to remove old IP address config, rc=%d", rc); + return rc; + } + + return rc; +} + +/** + * @brief Callback to be called by any config change in subtrees "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" + * or "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address". + */ +static int +interface_ipv46_address_change_cb(sr_session_ctx_t *session, const char *xpath, sr_notif_event_t event, void *private_ctx) +{ + sr_change_iter_t *iter = NULL; + sr_change_oper_t op = SR_OP_CREATED; + sr_val_t *old_val = NULL; + sr_val_t *new_val = NULL; + sr_xpath_ctx_t xpath_ctx = { 0, }; + bool is_ipv6 = false, has_addr = false, has_prefix = false; + uint8_t addr[16] = { 0, }; + uint8_t prefix = 0; + char *node_name = NULL, *if_name = NULL; + int rc = SR_ERR_OK, op_rc = SR_ERR_OK; + + /* no-op for apply, we only care about SR_EV_ENABLED, SR_EV_VERIFY, SR_EV_ABORT */ + if (SR_EV_APPLY == event) { + return SR_ERR_OK; + } + SRP_LOG_DBG("'%s' modified, event=%d", xpath, event); + + /* check whether we are handling ipv4 or ipv6 config */ + node_name = sr_xpath_node_idx((char*)xpath, 2, &xpath_ctx); + if (NULL != node_name && 0 == strcmp(node_name, "ipv6")) { + is_ipv6 = true; + } + sr_xpath_recover(&xpath_ctx); + + /* get changes iterator */ + rc = sr_get_changes_iter(session, xpath, &iter); + if (SR_ERR_OK != rc) { + SRP_LOG_ERR("Unable to retrieve change iterator: %s", sr_strerror(rc)); + return rc; + } + + /* iterate over all changes */ + while ((SR_ERR_OK == op_rc || event == SR_EV_ABORT) && + (SR_ERR_OK == (rc = sr_get_change_next(session, iter, &op, &old_val, &new_val)))) { + + SRP_LOG_DBG("A change detected in '%s', op=%d", new_val ? new_val->xpath : old_val->xpath, op); + if_name = strdup(sr_xpath_key_value(new_val ? new_val->xpath : old_val->xpath, "interface", "name", &xpath_ctx)); + sr_xpath_recover(&xpath_ctx); + + switch (op) { + case SR_OP_CREATED: + if (SR_LIST_T == new_val->type) { + /* create on list item - reset state vars */ + has_addr = has_prefix = false; + } else { + if (sr_xpath_node_name_eq(new_val->xpath, "ip")) { + ip_addr_str_to_binary(new_val->data.string_val, addr, is_ipv6); + has_addr = true; + } else if (sr_xpath_node_name_eq(new_val->xpath, "prefix-length")) { + prefix = new_val->data.uint8_val; + has_prefix = true; + } else if (sr_xpath_node_name_eq(new_val->xpath, "netmask")) { + prefix = netmask_to_prefix(new_val->data.string_val); + has_prefix = true; + } + if (has_addr && has_prefix) { + op_rc = interface_ipv46_config_add_remove(private_ctx, if_name, addr, prefix, is_ipv6, true /* add */); + } + } + break; + case SR_OP_MODIFIED: + op_rc = interface_ipv46_config_modify(private_ctx, session, if_name, old_val, new_val, is_ipv6); + break; + case SR_OP_DELETED: + if (SR_LIST_T == old_val->type) { + /* delete on list item - reset state vars */ + has_addr = has_prefix = false; + } else { + if (sr_xpath_node_name_eq(old_val->xpath, "ip")) { + ip_addr_str_to_binary(old_val->data.string_val, addr, is_ipv6); + has_addr = true; + } else if (sr_xpath_node_name_eq(old_val->xpath, "prefix-length")) { + prefix = old_val->data.uint8_val; + has_prefix = true; + } else if (sr_xpath_node_name_eq(old_val->xpath, "netmask")) { + prefix = netmask_to_prefix(old_val->data.string_val); + has_prefix = true; + } + if (has_addr && has_prefix) { + op_rc = interface_ipv46_config_add_remove(private_ctx, if_name, addr, prefix, is_ipv6, false /* !add */); + } + } + break; + default: + break; + } + if (SR_ERR_INVAL_ARG == op_rc) { + sr_set_error(session, "Invalid interface name.", new_val ? new_val->xpath : old_val->xpath); + } + free(if_name); + sr_free_val(old_val); + sr_free_val(new_val); + } + sr_free_change_iter(iter); + + return op_rc; +} + +/** + * @brief Callback to be called by any config change under "/ietf-interfaces:interfaces-state/interface" path. + * Does not provide any functionality, needed just to cover not supported config leaves. + */ +static int +interface_change_cb(sr_session_ctx_t *session, const char *xpath, sr_notif_event_t event, void *private_ctx) +{ + SRP_LOG_DBG("'%s' modified, event=%d", xpath, event); + + return SR_ERR_OK; +} + +/** + * @brief Callback to be called by any request for state data under "/ietf-interfaces:interfaces-state/interface" path. + */ +static int +interface_state_cb(const char *xpath, sr_val_t **values, size_t *values_cnt, void *private_ctx) +{ + plugin_ctx_t *plugin_ctx = private_ctx; + vl_api_sw_interface_dump_t *if_dump_req = NULL; + vl_api_sw_interface_details_t *if_details = NULL; + void **details = NULL; + size_t details_cnt = 0; + sr_val_t *values_arr = NULL; + size_t values_arr_size = 0, values_arr_cnt = 0; + int rc = 0; + + SRP_LOG_DBG("Requesting state data for '%s'", xpath); + + if (! sr_xpath_node_name_eq(xpath, "interface")) { + /* statistics, ipv4 and ipv6 state data not supported */ + *values_cnt = 0; + return SR_ERR_OK; + } + + /* dump interfaces */ + if_dump_req = srvpp_alloc_msg(VL_API_SW_INTERFACE_DUMP, sizeof(*if_dump_req)); + rc = srvpp_send_dumprequest(plugin_ctx->srvpp_ctx, if_dump_req, &details, &details_cnt); + if (0 != rc) { + SRP_LOG_ERR_MSG("Error by processing of a interface dump request."); + return SR_ERR_INTERNAL; + } + + /* allocate array of values to be returned */ + values_arr_size = details_cnt * 5; + rc = sr_new_values(values_arr_size, &values_arr); + if (0 != rc) { + return rc; + } + + for (size_t i = 0; i < details_cnt; i++) { + if_details = (vl_api_sw_interface_details_t *) details[i]; + + /* currently the only supported interface types are propVirtual / ethernetCsmacd */ + sr_val_build_xpath(&values_arr[values_arr_cnt], "%s[name='%s']/type", xpath, if_details->interface_name); + sr_val_set_str_data(&values_arr[values_arr_cnt], SR_IDENTITYREF_T, + strstr((char*)if_details->interface_name, "local") ? "iana-if-type:propVirtual" : "iana-if-type:ethernetCsmacd"); + values_arr_cnt++; + + sr_val_build_xpath(&values_arr[values_arr_cnt], "%s[name='%s']/admin-status", xpath, if_details->interface_name); + sr_val_set_str_data(&values_arr[values_arr_cnt], SR_ENUM_T, if_details->admin_up_down ? "up" : "down"); + values_arr_cnt++; + + sr_val_build_xpath(&values_arr[values_arr_cnt], "%s[name='%s']/oper-status", xpath, if_details->interface_name); + sr_val_set_str_data(&values_arr[values_arr_cnt], SR_ENUM_T, if_details->link_up_down ? "up" : "down"); + values_arr_cnt++; + + if (if_details->l2_address_length > 0) { + sr_val_build_xpath(&values_arr[values_arr_cnt], "%s[name='%s']/phys-address", xpath, if_details->interface_name); + sr_val_build_str_data(&values_arr[values_arr_cnt], SR_STRING_T, "%02x:%02x:%02x:%02x:%02x:%02x", + if_details->l2_address[0], if_details->l2_address[1], if_details->l2_address[2], + if_details->l2_address[3], if_details->l2_address[4], if_details->l2_address[5]); + values_arr_cnt++; + } + + sr_val_build_xpath(&values_arr[values_arr_cnt], "%s[name='%s']/speed", xpath, if_details->interface_name); + values_arr[values_arr_cnt].type = SR_UINT64_T; + values_arr[values_arr_cnt].data.uint64_val = get_if_link_speed(if_details->link_speed); + values_arr_cnt++; + } + + SRP_LOG_DBG("Returning %zu state data elements for '%s'", values_arr, xpath); + + *values = values_arr; + *values_cnt = values_arr_cnt; + return SR_ERR_OK; +} + +/** + * @brief Callback to be called by plugin daemon upon plugin load. + */ +int +sr_plugin_init_cb(sr_session_ctx_t *session, void **private_ctx) +{ + plugin_ctx_t *ctx = NULL; + int rc = SR_ERR_OK; + + SRP_LOG_DBG_MSG("Initializing vpp-interfaces plugin."); + + /* allocate the plugin context */ + ctx = calloc(1, sizeof(*ctx)); + if (NULL == ctx) { + return SR_ERR_NOMEM; + } + + /* get srvpp context */ + ctx->srvpp_ctx = srvpp_get_ctx(); + if (NULL == ctx->srvpp_ctx) { + return SR_ERR_INIT_FAILED; + } + + /* setup handlers for required VPP API messages */ + srvpp_setup_handler(SW_INTERFACE_SET_FLAGS_REPLY, sw_interface_set_flags_reply); + srvpp_setup_handler(SW_INTERFACE_ADD_DEL_ADDRESS_REPLY, sw_interface_add_del_address_reply); + + rc = sr_subtree_change_subscribe(session, "/ietf-interfaces:interfaces/interface", + interface_change_cb, ctx, 0, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_EV_ENABLED, &ctx->sr_subscription); + if (SR_ERR_OK != rc) { + goto error; + } + + rc = sr_subtree_change_subscribe(session, "/ietf-interfaces:interfaces/interface/enabled", + interface_enable_disable_cb, ctx, 100, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_EV_ENABLED, &ctx->sr_subscription); + if (SR_ERR_OK != rc) { + goto error; + } + + rc = sr_subtree_change_subscribe(session, "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address", + interface_ipv46_address_change_cb, ctx, 99, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_EV_ENABLED, &ctx->sr_subscription); + if (SR_ERR_OK != rc) { + goto error; + } + + rc = sr_subtree_change_subscribe(session, "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address", + interface_ipv46_address_change_cb, ctx, 98, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_EV_ENABLED, &ctx->sr_subscription); + if (SR_ERR_OK != rc) { + goto error; + } + + rc = sr_dp_get_items_subscribe(session, "/ietf-interfaces:interfaces-state", + interface_state_cb, ctx, SR_SUBSCR_CTX_REUSE, &ctx->sr_subscription); + if (SR_ERR_OK != rc) { + goto error; + } + + *private_ctx = ctx; + + SRP_LOG_INF_MSG("vpp-interfaces plugin initialized successfully."); + + return SR_ERR_OK; + +error: + SRP_LOG_ERR_MSG("Error by initialization of the vpp-interfaces plugin."); + sr_plugin_cleanup_cb(session, ctx); + return rc; +} + +/** + * @brief Callback to be called by plugin daemon upon plugin unload. + */ +void +sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *private_ctx) +{ + plugin_ctx_t *ctx = (plugin_ctx_t *) private_ctx; + + SRP_LOG_DBG_MSG("Cleanup of vpp-interfaces plugin requested."); + + sr_unsubscribe(session, ctx->sr_subscription); + + srvpp_release_ctx(ctx->srvpp_ctx); + free(ctx); +} + +/** + * @brief Callback to be called by plugin daemon periodically, to check whether the plugin and managed app is healthy. + */ +/* +int +sr_plugin_health_check_cb(sr_session_ctx_t *session, void *private_ctx) +{ + plugin_ctx_t *ctx = (plugin_ctx_t *) private_ctx; + vl_api_control_ping_t *ping = NULL; + int rc = 0; + + ping = srvpp_alloc_msg(VL_API_CONTROL_PING, sizeof(*ping)); + + rc = srvpp_send_request(ctx->srvpp_ctx, ping, NULL); + + if (0 != rc) { + return SR_ERR_DISCONNECT; + } else { + return SR_ERR_OK; + } +} +*/ diff --git a/src/plugins/yang/ietf-interfaces.data.xml b/src/plugins/yang/ietf-interfaces.data.xml new file mode 100644 index 0000000..3aae96e --- /dev/null +++ b/src/plugins/yang/ietf-interfaces.data.xml @@ -0,0 +1,21 @@ +<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> + <interface> + <name>GigabitEthernet0/8/0</name> + <description>Ethernet 8</description> + <type>ethernetCsmacd</type> + <enabled>true</enabled> + <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> + <address> + <ip>192.168.2.100</ip> + <prefix-length>24</prefix-length> + </address> + </ipv4> + <ipv6 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> + <address> + <ip>2001:db8::10</ip> + <prefix-length>32</prefix-length> + </address> + </ipv6> + </interface> +</interfaces> + diff --git a/src/plugins/yang/ietf-interfaces@2014-05-08.yang b/src/plugins/yang/ietf-interfaces@2014-05-08.yang new file mode 100644 index 0000000..a0eb30e --- /dev/null +++ b/src/plugins/yang/ietf-interfaces@2014-05-08.yang @@ -0,0 +1,707 @@ +module ietf-interfaces { + + yang-version 1; + + namespace + "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + + prefix if; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "This module contains a collection of YANG definitions for + managing network interfaces. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7223; see + the RFC itself for full legal notices."; + + revision "2014-05-08" { + description "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + + } + + + typedef interface-ref { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + configured interfaces."; + } + + typedef interface-state-ref { + type leafref { + path "/if:interfaces-state/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + the operationally present interfaces."; + } + + identity interface-type { + description + "Base identity from which specific interface types are + derived."; + } + + feature arbitrary-names { + description + "This feature indicates that the device allows user-controlled + interfaces to be named arbitrarily."; + } + + feature pre-provisioning { + description + "This feature indicates that the device supports + pre-provisioning of interface configuration, i.e., it is + possible to configure an interface whose physical interface + hardware is not present on the device."; + } + + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + + } + + container interfaces { + description + "Interface configuration parameters."; + list interface { + key "name"; + description + "The list of configured interfaces on the device. + + The operational state of an interface is available in the + /interfaces-state/interface list. If the configuration of a + system-controlled interface cannot be used by the system + (e.g., the interface hardware present does not match the + interface type), then the configuration is not applied to + the system-controlled interface shown in the + /interfaces-state/interface list. If the configuration + of a user-controlled interface cannot be used by the system, + the configured interface is not instantiated in the + /interfaces-state/interface list."; + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. The 'config false' + list /interfaces-state/interface contains the currently + existing interfaces on the device. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + /interfaces-state/interface list, the server MAY reject + the request if the implementation does not support + pre-provisioning of interfaces or if the name refers to + an interface that can never exist in the system. A + NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case. + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + /interface-state/interface list."; + } + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + datastore. + + Specifically, if the device supports ':startup', when + ifAlias is read the device MUST return the value of + 'description' in the 'startup' datastore, and when it is + written, it MUST be written to the 'running' and 'startup' + datastores. Note that it is up to the implementation to + + decide whether to modify this single leaf in 'startup' or + perform an implicit copy-config from 'running' to + 'startup'. + + If the device does not support ':startup', ifAlias MUST + be mapped to the 'description' leaf in the 'running' + datastore."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the 'running' datastore to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + + + Changes in this leaf in the 'running' datastore are + reflected in ifAdminStatus, but if ifAdminStatus is + changed over SNMP, this leaf is not affected."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + + } + + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum "enabled" { + value 1; + } + enum "disabled" { + value 2; + } + } + description + "Controls whether linkUp/linkDown SNMP notifications + should be generated for this interface. + + If this node is not configured, the value 'enabled' is + operationally used by the server for interfaces that do + not operate on top of any other interface (i.e., there are + no 'lower-layer-if' entries), and 'disabled' otherwise."; + reference + "RFC 2863: The Interfaces Group MIB - + ifLinkUpDownTrapEnable"; + + } + } // list interface + } // container interfaces + + container interfaces-state { + config false; + description + "Data nodes for the operational state of interfaces."; + list interface { + key "name"; + description + "The list of interfaces on the device. + + System-controlled interfaces created by the system are + always present in this list, whether they are configured or + not."; + leaf name { + type string; + description + "The name of the interface. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum "up" { + value 1; + description + "Ready to pass packets."; + } + enum "down" { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + enum "testing" { + value 3; + description + "In some test mode."; + } + } + mandatory true; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + + } + + leaf oper-status { + type enumeration { + enum "up" { + value 1; + description + "Ready to pass packets."; + } + enum "down" { + value 2; + description + "The interface does not pass any packets."; + } + enum "testing" { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum "unknown" { + value 4; + description + "Status cannot be determined for some reason."; + } + enum "dormant" { + value 5; + description + "Waiting for some external event."; + } + enum "not-present" { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum "lower-layer-down" { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + + } + + leaf last-change { + type yang:date-and-time; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + mandatory true; + description + "The ifIndex value for the ifEntry represented by this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + + } + + leaf phys-address { + type yang:phys-address; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + + + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + + } + + leaf-list higher-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + + } + + leaf-list lower-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + + } + + container statistics { + description + "A collection of interface-related statistics objects."; + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + + } + + leaf in-unicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + + } + + leaf in-broadcast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + + } + + leaf in-multicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + + } + + leaf in-discards { + type yang:counter32; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + + } + + leaf in-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + + } + + leaf in-unknown-protos { + type yang:counter32; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + + } + + leaf out-octets { + type yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + + } + + leaf out-unicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + + } + + leaf out-broadcast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + + } + + leaf out-multicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + + } + + leaf out-discards { + type yang:counter32; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + + } + + leaf out-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + + } + } // container statistics + } // list interface + } // container interfaces-state + } // module ietf-interfaces diff --git a/src/plugins/yang/ietf-ip@2014-06-16.yang b/src/plugins/yang/ietf-ip@2014-06-16.yang new file mode 100644 index 0000000..79606cc --- /dev/null +++ b/src/plugins/yang/ietf-ip@2014-06-16.yang @@ -0,0 +1,742 @@ +module ietf-ip { + + yang-version 1; + + namespace + "urn:ietf:params:xml:ns:yang:ietf-ip"; + + prefix ip; + + import ietf-interfaces { + prefix if; + } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "This module contains a collection of YANG definitions for + configuring IP implementations. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7277; see + the RFC itself for full legal notices."; + + revision "2014-06-16" { + description "Initial revision."; + reference + "RFC 7277: A YANG Data Model for IP Management"; + + } + + + feature ipv4-non-contiguous-netmasks { + description + "Indicates support for configuring non-contiguous + subnet masks."; + } + + feature ipv6-privacy-autoconf { + description + "Indicates support for Privacy Extensions for Stateless Address + Autoconfiguration in IPv6."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + + } + + typedef ip-address-origin { + type enumeration { + enum "other" { + value 0; + description + "None of the following."; + } + enum "static" { + value 1; + description + "Indicates that the address has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum "dhcp" { + value 2; + description + "Indicates an address that has been assigned to this + system by a DHCP server."; + } + enum "link-layer" { + value 3; + description + "Indicates an address created by IPv6 stateless + autoconfiguration that embeds a link-layer address in its + interface identifier."; + } + enum "random" { + value 4; + description + "Indicates an address chosen by the system at + + random, e.g., an IPv4 address within 169.254/16, an + RFC 4941 temporary address, or an RFC 7217 semantically + opaque address."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + RFC 7217: A Method for Generating Semantically Opaque + Interface Identifiers with IPv6 Stateless + Address Autoconfiguration (SLAAC)"; + + } + } + description + "The origin of an address."; + } + + typedef neighbor-origin { + type enumeration { + enum "other" { + value 0; + description + "None of the following."; + } + enum "static" { + value 1; + description + "Indicates that the mapping has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum "dynamic" { + value 2; + description + "Indicates that the mapping has been dynamically resolved + using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol."; + } + } + description + "The origin of a neighbor entry."; + } + + augment /if:interfaces/if:interface { + description + "Parameters for configuring IP on interfaces. + + If an interface is not capable of running IP, the server + must not allow the client to configure these parameters."; + container ipv4 { + presence + "Enables IPv4 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv4 address family."; + leaf enabled { + type boolean; + default 'true'; + description + "Controls whether IPv4 is enabled or disabled on this + interface. When IPv4 is enabled, this interface is + connected to an IPv4 stack, and the interface can send + and receive IPv4 packets."; + } + + leaf forwarding { + type boolean; + default 'false'; + description + "Controls IPv4 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv4 routers + forward datagrams. IPv4 hosts do not (except those + source-routed via the host)."; + } + + leaf mtu { + type uint16 { + range "68..max"; + } + units "octets"; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 791: Internet Protocol"; + + } + + list address { + key "ip"; + description + "The list of configured IPv4 addresses on the interface."; + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + + choice subnet { + mandatory true; + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } // choice subnet + } // list address + + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } // list neighbor + } // container ipv4 + + container ipv6 { + presence + "Enables IPv6 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv6 address family."; + leaf enabled { + type boolean; + default 'true'; + description + "Controls whether IPv6 is enabled or disabled on this + interface. When IPv6 is enabled, this interface is + connected to an IPv6 stack, and the interface can send + and receive IPv6 packets."; + } + + leaf forwarding { + type boolean; + default 'false'; + description + "Controls IPv6 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv6 routers + forward datagrams. IPv6 hosts do not (except those + source-routed via the host)."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + + } + + leaf mtu { + type uint32 { + range "1280..max"; + } + units "octets"; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + + } + + list address { + key "ip"; + description + "The list of configured IPv6 addresses on the interface."; + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + } // list address + + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } // list neighbor + + leaf dup-addr-detect-transmits { + type uint32; + default '1'; + description + "The number of consecutive Neighbor Solicitation messages + sent while performing Duplicate Address Detection on a + tentative address. A value of zero indicates that + Duplicate Address Detection is not performed on + tentative addresses. A value of one indicates a single + transmission with no follow-up retransmissions."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + } + + container autoconf { + description + "Parameters to control the autoconfiguration of IPv6 + addresses, as described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + leaf create-global-addresses { + type boolean; + default 'true'; + description + "If enabled, the host creates global addresses as + described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration + Section 5.5"; + + } + + leaf create-temporary-addresses { + if-feature ipv6-privacy-autoconf; + type boolean; + default 'false'; + description + "If enabled, the host creates temporary addresses as + described in RFC 4941."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + + } + + leaf temporary-valid-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default '604800'; + description + "The time period during which the temporary address + is valid."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_VALID_LIFETIME"; + + } + + leaf temporary-preferred-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default '86400'; + description + "The time period during which the temporary address is + preferred."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_PREFERRED_LIFETIME"; + + } + } // container autoconf + } // container ipv6 + } + + augment /if:interfaces-state/if:interface { + description + "Data nodes for the operational state of IP on interfaces."; + container ipv4 { + presence + "Present if IPv4 is enabled on this interface"; + config false; + description + "Interface-specific parameters for the IPv4 address family."; + leaf forwarding { + type boolean; + description + "Indicates whether IPv4 packet forwarding is enabled or + disabled on this interface."; + } + + leaf mtu { + type uint16 { + range "68..max"; + } + units "octets"; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive."; + reference + "RFC 791: Internet Protocol"; + + } + + list address { + key "ip"; + description + "The list of IPv4 addresses on the interface."; + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + + choice subnet { + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } // choice subnet + + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + } // list address + + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + This list represents the ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + } // list neighbor + } // container ipv4 + + container ipv6 { + presence + "Present if IPv6 is enabled on this interface"; + config false; + description + "Parameters for the IPv6 address family."; + leaf forwarding { + type boolean; + default 'false'; + description + "Indicates whether IPv6 packet forwarding is enabled or + disabled on this interface."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + + } + + leaf mtu { + type uint32 { + range "1280..max"; + } + units "octets"; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + + } + + list address { + key "ip"; + description + "The list of IPv6 addresses on the interface."; + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + + leaf status { + type enumeration { + enum "preferred" { + value 0; + description + "This is a valid address that can appear as the + destination or source address of a packet."; + } + enum "deprecated" { + value 1; + description + "This is a valid but deprecated address that should + no longer be used as a source address in new + communications, but packets addressed to such an + address are processed as expected."; + } + enum "invalid" { + value 2; + description + "This isn't a valid address, and it shouldn't appear + as the destination or source address of a packet."; + } + enum "inaccessible" { + value 3; + description + "The address is not accessible because the interface + to which this address is assigned is not + operational."; + } + enum "unknown" { + value 4; + description + "The status cannot be determined for some reason."; + } + enum "tentative" { + value 5; + description + "The uniqueness of the address on the link is being + verified. Addresses in this state should not be + used for general communication and should only be + used to determine the uniqueness of the address."; + } + enum "duplicate" { + value 6; + description + "The address has been determined to be non-unique on + the link and so must not be used."; + } + enum "optimistic" { + value 7; + description + "The address is available for use, subject to + restrictions, while its uniqueness on a link is + being verified."; + } + } + description + "The status of an address. Most of the states correspond + to states from the IPv6 Stateless Address + Autoconfiguration protocol."; + reference + "RFC 4293: Management Information Base for the + Internet Protocol (IP) + - IpAddressStatusTC + RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + } + } // list address + + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + This list represents the Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + + leaf is-router { + type empty; + description + "Indicates that the neighbor node acts as a router."; + } + + leaf state { + type enumeration { + enum "incomplete" { + value 0; + description + "Address resolution is in progress, and the link-layer + address of the neighbor has not yet been + determined."; + } + enum "reachable" { + value 1; + description + "Roughly speaking, the neighbor is known to have been + reachable recently (within tens of seconds ago)."; + } + enum "stale" { + value 2; + description + "The neighbor is no longer known to be reachable, but + until traffic is sent to the neighbor no attempt + should be made to verify its reachability."; + } + enum "delay" { + value 3; + description + "The neighbor is no longer known to be reachable, and + traffic has recently been sent to the neighbor. + Rather than probe the neighbor immediately, however, + delay sending probes for a short while in order to + give upper-layer protocols a chance to provide + reachability confirmation."; + } + enum "probe" { + value 4; + description + "The neighbor is no longer known to be reachable, and + unicast Neighbor Solicitation probes are being sent + to verify reachability."; + } + } + description + "The Neighbor Unreachability Detection state of this + entry."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 7.3.2"; + + } + } // list neighbor + } // container ipv6 + } + } // module ietf-ip diff --git a/src/plugins/yang/ietf-yang-types@2013-07-15.yang b/src/plugins/yang/ietf-yang-types@2013-07-15.yang new file mode 100644 index 0000000..ee58fa3 --- /dev/null +++ b/src/plugins/yang/ietf-yang-types@2013-07-15.yang @@ -0,0 +1,474 @@ +module ietf-yang-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types"; + prefix "yang"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: David Kessens + <mailto:david.kessens@nsn.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de>"; + + description + "This module contains a collection of generally useful derived + YANG data types. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - yang-identifier + - hex-string + - uuid + - dotted-quad"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of counter and gauge types ***/ + + typedef counter32 { + type uint32; + description + "The counter32 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter32 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter32 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter32. + + In the value set and its semantics, this type is equivalent + to the Counter32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter32 { + type yang:counter32; + default "0"; + description + "The zero-based-counter32 type represents a counter32 + that has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter32 textual convention of the SMIv2."; + reference + "RFC 4502: Remote Network Monitoring Management Information + Base Version 2"; + } + + typedef counter64 { + type uint64; + description + "The counter64 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter64 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter64 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter64. + + In the value set and its semantics, this type is equivalent + to the Counter64 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter64 { + type yang:counter64; + default "0"; + description + "The zero-based-counter64 type represents a counter64 that + has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter64 textual convention of the SMIv2."; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + typedef gauge32 { + type uint32; + description + "The gauge32 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^32-1 (4294967295 decimal), and + the minimum value cannot be smaller than 0. The value of + a gauge32 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge32 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the Gauge32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef gauge64 { + type uint64; + description + "The gauge64 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^64-1 (18446744073709551615), and + the minimum value cannot be smaller than 0. The value of + a gauge64 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge64 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the CounterBasedGauge64 SMIv2 textual convention defined + in RFC 2856"; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + /*** collection of identifier-related types ***/ + + typedef object-identifier { + type string { + pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))' + + '(\.(0|([1-9]\d*)))*'; + } + description + "The object-identifier type represents administratively + assigned names in a registration-hierarchical-name tree. + + Values of this type are denoted as a sequence of numerical + non-negative sub-identifier values. Each sub-identifier + value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers + are separated by single dots and without any intermediate + whitespace. + + The ASN.1 standard restricts the value space of the first + sub-identifier to 0, 1, or 2. Furthermore, the value space + of the second sub-identifier is restricted to the range + 0 to 39 if the first sub-identifier is 0 or 1. Finally, + the ASN.1 standard requires that an object identifier + has always at least two sub-identifiers. The pattern + captures these restrictions. + + Although the number of sub-identifiers is not limited, + module designers should realize that there may be + implementations that stick with the SMIv2 limit of 128 + sub-identifiers. + + This type is a superset of the SMIv2 OBJECT IDENTIFIER type + since it is not restricted to 128 sub-identifiers. Hence, + this type SHOULD NOT be used to represent the SMIv2 OBJECT + IDENTIFIER type; the object-identifier-128 type SHOULD be + used instead."; + reference + "ISO9834-1: Information technology -- Open Systems + Interconnection -- Procedures for the operation of OSI + Registration Authorities: General procedures and top + arcs of the ASN.1 Object Identifier tree"; + } + + typedef object-identifier-128 { + type object-identifier { + pattern '\d*(\.\d*){1,127}'; + } + description + "This type represents object-identifiers restricted to 128 + sub-identifiers. + + In the value set and its semantics, this type is equivalent + to the OBJECT IDENTIFIER type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef yang-identifier { + type string { + length "1..max"; + pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*'; + pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*'; + } + description + "A YANG identifier string as defined by the 'identifier' + rule in Section 12 of RFC 6020. An identifier must + start with an alphabetic character or an underscore + followed by an arbitrary sequence of alphabetic or + numeric characters, underscores, hyphens, or dots. + + A YANG identifier MUST NOT start with any possible + combination of the lowercase or uppercase character + sequence 'xml'."; + reference + "RFC 6020: YANG - A Data Modeling Language for the Network + Configuration Protocol (NETCONF)"; + } + + /*** collection of types related to date and time***/ + + typedef date-and-time { + type string { + pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?' + + '(Z|[\+\-]\d{2}:\d{2})'; + } + description + "The date-and-time type is a profile of the ISO 8601 + standard for representation of dates and times using the + Gregorian calendar. The profile is defined by the + date-time production in Section 5.6 of RFC 3339. + + The date-and-time type is compatible with the dateTime XML + schema type with the following notable exceptions: + + (a) The date-and-time type does not allow negative years. + + (b) The date-and-time time-offset -00:00 indicates an unknown + time zone (see RFC 3339) while -00:00 and +00:00 and Z + all represent the same time zone in dateTime. + + (c) The canonical format (see below) of data-and-time values + differs from the canonical format used by the dateTime XML + schema type, which requires all times to be in UTC using + the time-offset 'Z'. + + This type is not equivalent to the DateAndTime textual + convention of the SMIv2 since RFC 3339 uses a different + separator between full-date and full-time and provides + higher resolution of time-secfrac. + + The canonical format for date-and-time values with a known time + zone uses a numeric time zone offset that is calculated using + the device's configured known offset to UTC time. A change of + the device's offset to UTC time will cause date-and-time values + to change accordingly. Such changes might happen periodically + in case a server follows automatically daylight saving time + (DST) time zone offset changes. The canonical format for + date-and-time values with an unknown time zone (usually + referring to the notion of local time) uses the time-offset + -00:00."; + reference + "RFC 3339: Date and Time on the Internet: Timestamps + RFC 2579: Textual Conventions for SMIv2 + XSD-TYPES: XML Schema Part 2: Datatypes Second Edition"; + } + + typedef timeticks { + type uint32; + description + "The timeticks type represents a non-negative integer that + represents the time, modulo 2^32 (4294967296 decimal), in + hundredths of a second between two epochs. When a schema + node is defined that uses this type, the description of + the schema node identifies both of the reference epochs. + + In the value set and its semantics, this type is equivalent + to the TimeTicks type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef timestamp { + type yang:timeticks; + description + "The timestamp type represents the value of an associated + timeticks schema node at which a specific occurrence + happened. The specific occurrence must be defined in the + description of any schema node defined using this type. When + the specific occurrence occurred prior to the last time the + associated timeticks attribute was zero, then the timestamp + value is zero. Note that this requires all timestamp values + to be reset to zero when the value of the associated timeticks + attribute reaches 497+ days and wraps around to zero. + + The associated timeticks schema node must be specified + in the description of any schema node using this type. + + In the value set and its semantics, this type is equivalent + to the TimeStamp textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of generic address types ***/ + + typedef phys-address { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + + description + "Represents media- or physical-level addresses represented + as a sequence octets, each octet represented by two hexadecimal + numbers. Octets are separated by colons. The canonical + representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the PhysAddress textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + typedef mac-address { + type string { + pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'; + } + description + "The mac-address type represents an IEEE 802 MAC address. + The canonical representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the MacAddress textual convention of the SMIv2."; + reference + "IEEE 802: IEEE Standard for Local and Metropolitan Area + Networks: Overview and Architecture + RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of XML-specific types ***/ + + typedef xpath1.0 { + type string; + description + "This type represents an XPATH 1.0 expression. + + When a schema node is defined that uses this type, the + description of the schema node MUST specify the XPath + context in which the XPath expression is evaluated."; + reference + "XPATH: XML Path Language (XPath) Version 1.0"; + } + + /*** collection of string types ***/ + + typedef hex-string { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + description + "A hexadecimal string with octets represented as hex digits + separated by colons. The canonical representation uses + lowercase characters."; + } + + typedef uuid { + type string { + pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-' + + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; + } + description + "A Universally Unique IDentifier in the string representation + defined in RFC 4122. The canonical representation uses + lowercase characters. + + The following is an example of a UUID in string representation: + f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + "; + reference + "RFC 4122: A Universally Unique IDentifier (UUID) URN + Namespace"; + } + + typedef dotted-quad { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + } + description + "An unsigned 32-bit number expressed in the dotted-quad + notation, i.e., four octets written as decimal numbers + and separated with the '.' (full stop) character."; + } +} diff --git a/src/srvpp/CMakeLists.txt b/src/srvpp/CMakeLists.txt new file mode 100644 index 0000000..20fe6f0 --- /dev/null +++ b/src/srvpp/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# 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. +# + +cmake_minimum_required(VERSION 2.8) +project(srvpp) + +# add subdirectories +add_subdirectory(src) + +# enable testing if requested and possible +SET(ENABLE_TESTS 1 CACHE BOOL "Enable unit tests.") +if(ENABLE_TESTS) + find_package(CMOCKA) + if(CMOCKA_FOUND) + MESSAGE(STATUS "CMocka found, tests are enabled.") + enable_testing() + add_subdirectory(tests) + else(CMOCKA_FOUND) + MESSAGE(WARNING "CMocka not found, tests are disabled.") + endif(CMOCKA_FOUND) +endif(ENABLE_TESTS) + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + # generate and install pkg-config file + configure_file("libsrvpp.pc.in" "libsrvpp.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libsrvpp.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endif() diff --git a/src/srvpp/libsrvpp.pc.in b/src/srvpp/libsrvpp.pc.in new file mode 100644 index 0000000..c57c267 --- /dev/null +++ b/src/srvpp/libsrvpp.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ + +Name: srvpp +Description: VPP-Sysrepo integration library +Version: 0.0.1 +Libs: -L${libdir} -lsrvpp +Cflags: -I${includedir} + diff --git a/src/srvpp/src/CMakeLists.txt b/src/srvpp/src/CMakeLists.txt new file mode 100644 index 0000000..7fec80d --- /dev/null +++ b/src/srvpp/src/CMakeLists.txt @@ -0,0 +1,44 @@ +# +# 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. +# + +include(GNUInstallDirs) + +# srvpp sources +set(SRVPP_SOURCES + srvpp_logger.c + srvpp.c +) + +# srvpp public headers +set(SRVPP_HEADERS + srvpp.h +) + +set(CMAKE_C_FLAGS " -g -O0 -fpic -fPIC -std=gnu99 -Wl,-rpath-link=/usr/lib") + +# libraries to link with +set(LINK_LIBRARIES sysrepo vlibmemoryclient vapiclient vppapiclient svm vppinfra pthread rt dl) + +# build instructions +add_library(srvpp SHARED ${SRVPP_SOURCES}) +add_library(srvpp_a ${SRVPP_SOURCES}) + +# linker instructions +target_link_libraries(srvpp ${LINK_LIBRARIES}) +target_link_libraries(srvpp_a ${LINK_LIBRARIES}) + +# install rules +install(TARGETS srvpp DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES ${SRVPP_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/srvpp/src/srvpp.c b/src/srvpp/src/srvpp.c new file mode 100644 index 0000000..9b3467b --- /dev/null +++ b/src/srvpp/src/srvpp.c @@ -0,0 +1,823 @@ +/* + * 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. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <pthread.h> + +#include "srvpp.h" +#include "srvpp_logger.h" + +#undef vl_api_version +#define vl_api_version(n,v) static u32 vpe_api_version = v; +#include <vpp/api/vpe.api.h> +#undef vl_api_version + + +#define SRVPP_RESPONSE_TIMEOUT 2 /**< Maximum time (in seconds) that a client waits for the response(s) to a request or dumprequest. */ + +#define CHECK_NULL(ARG) \ + if (NULL == ARG) { \ + SRVPP_LOG_ERR("NULL value detected for %s argument of %s", #ARG, __func__); \ + return -1; \ + } \ + +#define CHECK_NULL_RET(ARG, RET) \ + if (NULL == ARG) { \ + SRVPP_LOG_ERR("NULL value detected for %s argument of %s", #ARG, __func__); \ + return RET; \ + } \ + +/** + * @brief Type of the response expected from VPP. + */ +typedef enum srvpp_response_type_e { + SRVPP_REPLY, /**< A reply message (single message). */ + SRVPP_DETAILS, /**< Multiple details messages. */ +} srvpp_response_type_t; + +/** + * @brief srvpp request context structure. + */ +typedef struct srvpp_request_ctx_s { + struct srvpp_request_ctx_s *_next; /**< Pointer to the next request context in the linked-list. */ + u32 ctx_id; /**< Context ID used to map responses with requests. */ + srvpp_response_type_t resp_type; /**< Type of the response expected from VPP. */ + + i32 resp_retval; /**< Return value of the last response (0 = success). */ + bool resp_data_copy; /**< Controls whether data of the responses shall be copied (returned) or ignored. */ + void **resp_msg_arr; /**< Array of the pointers to response messages. */ + size_t *resp_msg_sizes; /**< Array of sizes of messages in the ::resp_msg_arr. */ + size_t resp_msg_arr_size; /**< Size of the ::resp_msg_arr array. */ + size_t resp_msg_cnt; /**< Count of the messages currently stored in ::resp_msg_arr array. */ + + bool resp_ready; /**< Signals that the expected response has arrived and is ready to be returned. */ + pthread_cond_t resp_cv; /**< Condition variable for ::resp_ready. */ + pthread_mutex_t lock; /**< Mutex to protect shared access to the context. */ +} srvpp_request_ctx_t; + +/** + * @brief srvpp interface info context structure. + */ +typedef struct srvpp_if_info_s { + struct srvpp_if_info_s *_next; + const char *if_name; + u32 if_index; +} srvpp_if_info_t; + +/** + * @brief srvpp context structure. + */ +typedef struct srvpp_ctx_s { + size_t ref_cnt; /**< Context reference counter. */ + unix_shared_memory_queue_t *vlib_input_queue; /**< VPP Library input queue. */ + u32 vlib_client_index; /**< VPP Library client index. */ + srvpp_request_ctx_t *request_ctx_list; /**< Linked-list of request contexts. */ + srvpp_if_info_t *if_info_list; /**< Linked-list of VPP interfaces information. */ + pthread_key_t request_key; /**< Key to the thread-specific request context. */ +} srvpp_ctx_t; + +/** + * @brief Generic VPP request structure. + */ +typedef struct __attribute__ ((packed)) vl_generic_request_s { + unsigned short _vl_msg_id; + unsigned int client_index; + unsigned int context; +} vl_generic_request_t; + +/** + * @brief Generic VPP response structure. + */ +typedef struct __attribute__ ((packed)) vl_generic_response_s { + u16 _vl_msg_id; + u32 context; +} vl_generic_response_t; + +/** + * @brief Generic VPP reply structure (response with a single message). + */ +typedef struct __attribute__ ((packed)) vl_generic_reply_s { + u16 _vl_msg_id; + u32 context; + i32 retval; +} vl_generic_reply_t; + +/** + * @brief Global srvpp context. + */ +static srvpp_ctx_t *srvpp_ctx = NULL; + +/** + * @brief Mutex for the global context. + */ +static pthread_mutex_t srvpp_ctx_lock = PTHREAD_MUTEX_INITIALIZER; + +/** + * @brief Not used, just to satisfy external references when -lvlib is not available. + */ +void +vlib_cli_output(struct vlib_main_t *vm, char *fmt, ...) +{ + SRVPP_LOG_WRN_MSG("vlib_cli_output callled!"); +} + +/** + * @brief Sets correct VPP API version. + */ +void +vl_client_add_api_signatures(vl_api_memclnt_create_t *mp) +{ + /* + * Send the main API signature in slot 0. This bit of code must + * match the checks in ../vpe/api/api.c: vl_msg_api_version_check(). + */ + mp->api_versions[0] = clib_host_to_net_u32(vpe_api_version); +} + +/** + * @brief Returns the request context assigned to the active thread. + * If no request context exists for the thread, it will be automatically created. + */ +static srvpp_request_ctx_t * +srvpp_get_thread_request_ctx(srvpp_ctx_t *ctx) +{ + srvpp_request_ctx_t *req_ctx = NULL; + + CHECK_NULL_RET(ctx, NULL); + + if (NULL == (req_ctx = pthread_getspecific(ctx->request_key))) { + /* allocate a new request context */ + req_ctx = calloc(1, sizeof(*req_ctx)); + if (NULL != req_ctx) { + /* initialize the new context */ + req_ctx->ctx_id = (u32)(((uintptr_t)req_ctx) & 0xFFFFFFFF) << 16; + SRVPP_LOG_DBG("Creating new request ctx with id=%u", req_ctx->ctx_id); + + pthread_mutex_init(&req_ctx->lock, NULL); + pthread_cond_init (&req_ctx->resp_cv, NULL); + + /* save the request ctx in the srvpp context */ + pthread_mutex_lock(&srvpp_ctx_lock); + req_ctx->_next = ctx->request_ctx_list; + ctx->request_ctx_list = req_ctx; + pthread_mutex_unlock(&srvpp_ctx_lock); + + /* save the request ctx in the thread-local memory */ + pthread_setspecific(ctx->request_key, req_ctx); + } else { + SRVPP_LOG_ERR_MSG("Unable to allocate new request context."); + } + } + + return req_ctx; +} + +/** + * @brief Returns the request context matching with the provided context id. + */ +static srvpp_request_ctx_t * +srvpp_get_request_ctx(srvpp_ctx_t *ctx, u32 req_ctx_id) +{ + srvpp_request_ctx_t *req_ctx = NULL, *match = NULL; + + CHECK_NULL_RET(ctx, NULL); + + pthread_mutex_lock(&srvpp_ctx_lock); + + req_ctx = ctx->request_ctx_list; + + while (NULL != req_ctx) { + if (req_ctx->ctx_id == req_ctx_id) { + match = req_ctx; + break; + } + req_ctx = req_ctx->_next; + } + + pthread_mutex_unlock(&srvpp_ctx_lock); + + return match; +} + +/** + * @brief Copy data of the message into the provided request context. + */ +static int +srvpp_msg_data_copy(srvpp_request_ctx_t *req_ctx, void *msg, size_t msg_size) +{ + void **msg_arr = NULL; + size_t *sizes_arr = NULL; + void *msg_space = NULL; + + CHECK_NULL(req_ctx); + + if (req_ctx->resp_msg_arr_size < (req_ctx->resp_msg_cnt + 1)) { + /* reallocate arrays to fit one new message */ + msg_arr = realloc(req_ctx->resp_msg_arr, (req_ctx->resp_msg_cnt + 1) * sizeof(*req_ctx->resp_msg_arr)); + if (NULL == msg_arr) { + SRVPP_LOG_ERR_MSG("Unable to reallocate message array."); + return -1; + } + sizes_arr = realloc(req_ctx->resp_msg_sizes, (req_ctx->resp_msg_cnt + 1) * sizeof(*req_ctx->resp_msg_sizes)); + if (NULL == sizes_arr) { + SRVPP_LOG_ERR_MSG("Unable to reallocate message sizes array."); + return -1; + } + req_ctx->resp_msg_arr = msg_arr; + req_ctx->resp_msg_sizes = sizes_arr; + req_ctx->resp_msg_arr_size = req_ctx->resp_msg_cnt + 1; + + req_ctx->resp_msg_arr[req_ctx->resp_msg_cnt] = NULL; + req_ctx->resp_msg_sizes[req_ctx->resp_msg_cnt] = 0; + } + + if (req_ctx->resp_msg_sizes[req_ctx->resp_msg_cnt] < msg_size) { + /* reallocate space for the message */ + msg_space = realloc(req_ctx->resp_msg_arr[req_ctx->resp_msg_cnt], msg_size); + if (NULL == msg_space) { + SRVPP_LOG_ERR_MSG("Unable to reallocate message space."); + return -1; + } + req_ctx->resp_msg_arr[req_ctx->resp_msg_cnt] = msg_space; + req_ctx->resp_msg_sizes[req_ctx->resp_msg_cnt] = msg_size; + } + + /* copy the message content */ + memcpy(req_ctx->resp_msg_arr[req_ctx->resp_msg_cnt], msg, msg_size); + + req_ctx->resp_msg_cnt++; + + return 0; +} + +/** + * @brief Processes a reply to a single VPP request. + */ +static int +srvpp_process_reply_msg(srvpp_request_ctx_t *req_ctx, u16 msg_id, void *msg, size_t msg_size) +{ + vl_generic_reply_t *reply = NULL; + int rc = 0; + + CHECK_NULL(req_ctx); + + reply = (vl_generic_reply_t *) msg; + + if (req_ctx->ctx_id != reply->context) { + SRVPP_LOG_ERR_MSG("Invalid request context for provided message, ignoring the message."); + return -1; + } + + if (req_ctx->resp_data_copy) { + /* copy msg data into req_ctx */ + rc = srvpp_msg_data_copy(req_ctx, msg, msg_size); + } + + if (0 == rc) { + req_ctx->resp_retval = reply->retval; + } else { + req_ctx->resp_retval = rc; + } + + /* signal the requesting thread */ + req_ctx->resp_ready = true; + pthread_cond_signal(&req_ctx->resp_cv); + + return rc; +} + +/** + * @brief Processes a reply to a dump request to VPP (response consisting of multiple messages). + */ +static int +srvpp_process_details_msg(srvpp_request_ctx_t *req_ctx, u16 msg_id, void *msg, size_t msg_size) +{ + vl_generic_response_t *response = NULL; + int rc = 0; + + CHECK_NULL(req_ctx); + + response = (vl_generic_response_t *) msg; + + if (req_ctx->ctx_id != response->context) { + SRVPP_LOG_ERR_MSG("Invalid request context for provided message, ignoring the message."); + return -1; + } + + if (VL_API_CONTROL_PING_REPLY != msg_id) { + /* details message - copy message data into req contex*/ + rc = srvpp_msg_data_copy(req_ctx, msg, msg_size); + if (0 != rc && 0 == req_ctx->resp_retval) { + /* in case of error, propagate it to the req context */ + req_ctx->resp_retval = rc; + } + } else { + /* control ping reply, signal the requesting thread */ + req_ctx->resp_ready = true; + pthread_cond_signal(&req_ctx->resp_cv); + } + + return req_ctx->resp_retval; +} + +/** + * @brief Internal callback automatically called by VPP library when a message + * from VPP is received. + */ +void +_srvpp_receive_msg_handler(void *msg) +{ + srvpp_request_ctx_t *req_ctx = NULL; + vl_generic_response_t *response = NULL; + msgbuf_t *msg_header = NULL; + size_t msg_size = 0; + u16 msg_id = 0; + + if (NULL == msg) { + SRVPP_LOG_WRN_MSG("NULL message received, ignoring."); + return; + } + + response = (vl_generic_response_t *) msg; + + /* get message details */ + msg_header = (msgbuf_t *) (((u8 *) msg) - offsetof(msgbuf_t, data)); + msg_size = ntohl(msg_header->data_len); + msg_id = ntohs(*((u16 *)msg)); + + SRVPP_LOG_DBG("New message received from VPP (id=%d, size=%zu).", msg_id, msg_size); + + /* get request context matching with context id */ + req_ctx = srvpp_get_request_ctx(srvpp_ctx, response->context); + if (NULL == req_ctx) { + SRVPP_LOG_WRN("Unexpected context id=%d within the received message, ignoring.", response->context); + return; + } + + pthread_mutex_lock(&req_ctx->lock); + + if (SRVPP_REPLY == req_ctx->resp_type) { + srvpp_process_reply_msg(req_ctx, msg_id, msg, msg_size); + } else { + srvpp_process_details_msg(req_ctx, msg_id, msg, msg_size); + } + + pthread_mutex_unlock(&req_ctx->lock); +} + +/** + * @brief Blocks the thread until a response from VPP comes or until a timeout expires. + */ +static int +srvpp_wait_response(srvpp_request_ctx_t *req_ctx) +{ + struct timespec ts = { 0, }; + int retval = 0, rc = 0; + + CHECK_NULL(req_ctx); + + pthread_mutex_lock(&req_ctx->lock); + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += SRVPP_RESPONSE_TIMEOUT; + + while (!req_ctx->resp_ready && (0 == rc)) { + rc = pthread_cond_timedwait(&req_ctx->resp_cv, &req_ctx->lock, &ts); + } + if (0 == rc) { + SRVPP_LOG_DBG("Received the response from VPP, retval=%d", req_ctx->resp_retval); + retval = req_ctx->resp_retval; + } else { + SRVPP_LOG_ERR("Response not received from VPP within the timeout period (%d sec).", SRVPP_RESPONSE_TIMEOUT); + retval = -1; + } + + /* invalidate previous context id */ + ++req_ctx->ctx_id; + + pthread_mutex_unlock(&req_ctx->lock); + + return retval; +} + +/** + * @brief Connects to VPP. + */ +static int +srvpp_vlib_connect(srvpp_ctx_t *ctx) +{ + api_main_t *am = &api_main; + int rc = 0; + + CHECK_NULL(ctx); + + SRVPP_LOG_DBG_MSG("Connecting to VPP..."); + + rc = vl_client_connect_to_vlib("/vpe-api", "srvpp", 32); + + if (rc < 0) { + SRVPP_LOG_ERR("Unable to connect to VPP, rc=%d.", rc); + } else { + SRVPP_LOG_DBG("Connection to VPP established, client index=%d.", am->my_client_index); + ctx->vlib_client_index = am->my_client_index; + ctx->vlib_input_queue = am->shmem_hdr->vl_input_queue; + } + + return rc; +} + +/** + * @brief Disconnects from VPP. + */ +static void +srvpp_vlib_disconnect() +{ + vl_client_disconnect_from_vlib(); +} + +/** + * @brief Adds a new interface into interfaces list. + */ +static int +srvpp_if_info_add(srvpp_ctx_t *ctx, const char *if_name, u32 if_index) +{ + srvpp_if_info_t *tmp = NULL, *if_info = NULL; + + SRVPP_LOG_DBG("Adding interface '%s', id=%d", if_name, if_index); + + if_info = calloc(1, sizeof(*if_info)); + if (NULL == if_info) { + return 1; + } + + if_info->if_name = strdup(if_name); + if (NULL == if_info->if_name) { + return 1; + } + + if_info->if_index = if_index; + + if (NULL == ctx->if_info_list) { + ctx->if_info_list = if_info; + } else { + tmp = ctx->if_info_list; + while (NULL != tmp->_next) { + tmp = tmp->_next; + } + tmp->_next = if_info; + } + + return 0; +} + +/** + * @brief Loads VPP interfaces information. + */ +static int +srvpp_if_info_load(srvpp_ctx_t *ctx) +{ + vl_api_sw_interface_dump_t *if_dump_req = NULL; + vl_api_sw_interface_details_t *if_details = NULL; + void **details = NULL; + size_t details_cnt = 0; + int ret = 0; + + SRVPP_LOG_DBG_MSG("Loading VPP interfaces information"); + + /* dump interfaces */ + if_dump_req = srvpp_alloc_msg(VL_API_SW_INTERFACE_DUMP, sizeof(*if_dump_req)); + + ret = srvpp_send_dumprequest(ctx, if_dump_req, &details, &details_cnt); + if (0 != ret) { + return ret; + } + + pthread_mutex_lock(&srvpp_ctx_lock); + + for (size_t i = 0; i < details_cnt; i++) { + if_details = (vl_api_sw_interface_details_t *) details[i]; + ret = srvpp_if_info_add(ctx, (char*)if_details->interface_name, ntohl(if_details->sw_if_index)); + } + + pthread_mutex_unlock(&srvpp_ctx_lock); + + return 0; +} + +/** + * @brief Cleans up VPP interfaces information. + */ +static void +srvpp_if_info_cleanup(srvpp_ctx_t *ctx) +{ + srvpp_if_info_t *tmp = NULL, *if_info = NULL; + + if (NULL != ctx) { + if_info = ctx->if_info_list; + + while (NULL != if_info) { + tmp = if_info; + if_info = if_info->_next; + free((void*)tmp->if_name); + free(tmp); + } + } +} + +/** + * @brief Initializes the srvpp context. + */ +srvpp_ctx_t * +srvpp_ctx_init() +{ + srvpp_ctx_t *ctx = NULL; + int rc = 0; + + srvpp_logger_init(); + + ctx = calloc(1, sizeof(*ctx)); + if (NULL == ctx) { + SRVPP_LOG_ERR_MSG("Unable to allocate srvpp context."); + return NULL; + } + + rc = srvpp_vlib_connect(ctx); + if (0 != rc) { + SRVPP_LOG_ERR_MSG("Unable to initialize srvpp context."); + free(ctx); + return NULL; + } + + while (pthread_key_create(&ctx->request_key, NULL) == EAGAIN); + pthread_setspecific(ctx->request_key, NULL); + + ctx->ref_cnt = 1; + + SRVPP_LOG_INF_MSG("srvpp context initialized successfully."); + + return ctx; +} + +/** + * @brief Cleans up the srvpp context. + */ +static void +srvpp_ctx_cleanup(srvpp_ctx_t *ctx) +{ + srvpp_request_ctx_t *tmp = NULL, *req_ctx = NULL; + + if (NULL != ctx) { + srvpp_vlib_disconnect(); + + srvpp_if_info_cleanup(ctx); + + req_ctx = ctx->request_ctx_list; + while (NULL != req_ctx) { + tmp = req_ctx; + req_ctx = req_ctx->_next; + + pthread_mutex_destroy(&tmp->lock); + pthread_cond_destroy(&tmp->resp_cv); + + for (size_t i = 0; i < tmp->resp_msg_arr_size; i++) { + free(tmp->resp_msg_arr[i]); + } + free(tmp->resp_msg_arr); + free(tmp->resp_msg_sizes); + + free(tmp); + } + free(ctx); + + srvpp_logger_cleanup(); + + SRVPP_LOG_INF_MSG("srvpp context cleaned up successfully."); + } +} + +srvpp_ctx_t * +srvpp_get_ctx() +{ + bool setup_handlers = false; + + pthread_mutex_lock(&srvpp_ctx_lock); + + if (NULL == srvpp_ctx) { + /* initialize a new context */ + SRVPP_LOG_DBG_MSG("Initializing a new srvpp context."); + srvpp_ctx = srvpp_ctx_init(); + setup_handlers = true; + } else { + /* increment ref. count */ + srvpp_ctx->ref_cnt++; + SRVPP_LOG_DBG("Reusing existing srvpp context, new refcount=%zu.", srvpp_ctx->ref_cnt); + } + + pthread_mutex_unlock(&srvpp_ctx_lock); + + if (setup_handlers) { + /* setup required handlers */ + srvpp_setup_handler(CONTROL_PING_REPLY, control_ping_reply); + srvpp_setup_handler(SW_INTERFACE_DETAILS, sw_interface_details); + + /* load VPP interfaces information */ + srvpp_if_info_load(srvpp_ctx); + } + + return srvpp_ctx; +} + +void +srvpp_release_ctx(srvpp_ctx_t *ctx) +{ + pthread_mutex_lock(&srvpp_ctx_lock); + + if (ctx != srvpp_ctx) { + SRVPP_LOG_ERR_MSG("Invalid srvpp context passed in, unable to release."); + pthread_mutex_unlock(&srvpp_ctx_lock); + return; + } + + if (srvpp_ctx->ref_cnt > 1) { + /* there are still some other references */ + srvpp_ctx->ref_cnt--; + SRVPP_LOG_DBG("Releasing a reference to srvpp context, new refcount=%zu.", srvpp_ctx->ref_cnt); + } else { + /* last reference - cleanup */ + SRVPP_LOG_DBG_MSG("Releasing srvpp context (last ctx reference)."); + srvpp_ctx_cleanup(srvpp_ctx); + srvpp_ctx = NULL; + } + + pthread_mutex_unlock(&srvpp_ctx_lock); +} + +void * +srvpp_alloc_msg(uint16_t msg_id, size_t msg_size) +{ + vl_generic_request_t *req = NULL; + + req = vl_msg_api_alloc(msg_size); + if (NULL != req) { + memset(req, 0, msg_size); + req->_vl_msg_id = ntohs(msg_id); + } + + return req; +} + +int +srvpp_send_request(srvpp_ctx_t *ctx, void *request, void **response) +{ + vl_generic_request_t *req = NULL; + srvpp_request_ctx_t *req_ctx = NULL; + int retval = 0; + + CHECK_NULL(ctx); + CHECK_NULL(request); + + req_ctx = srvpp_get_thread_request_ctx(ctx); + if (NULL == req_ctx) { + SRVPP_LOG_ERR_MSG("Unable to obtain a request context."); + return -1; + } + + req = (vl_generic_request_t *) request; + req->client_index = ctx->vlib_client_index; + + pthread_mutex_lock(&req_ctx->lock); + + if (NULL != response) { + /* response data is requested */ + req_ctx->resp_data_copy = true; + req_ctx->resp_msg_cnt = 0; + } else { + /* not interested in response data */ + req_ctx->resp_data_copy = false; + } + + req_ctx->resp_type = SRVPP_REPLY; + req_ctx->resp_ready = false; + req_ctx->resp_retval = 0; + req->context = ++req_ctx->ctx_id; + + pthread_mutex_unlock(&req_ctx->lock); + + SRVPP_LOG_DBG_MSG("Sending a request to VPP."); + + vl_msg_api_send_shmem(ctx->vlib_input_queue, (u8*)&req); + + /* wait for expected response */ + retval = srvpp_wait_response(req_ctx); + + if (0 == retval) { + if (NULL != response && req_ctx->resp_msg_cnt > 0) { + *response = req_ctx->resp_msg_arr[0]; + } + SRVPP_LOG_DBG_MSG("VPP request successfully processed."); + } else { + SRVPP_LOG_ERR_MSG("Error by handling of a VPP request."); + } + + return retval; +} + +int +srvpp_send_dumprequest(srvpp_ctx_t *ctx, void *request, void ***response_arr, size_t *response_cnt) +{ + vl_generic_request_t *req = NULL; + srvpp_request_ctx_t *req_ctx = NULL; + vl_api_control_ping_t *ping = NULL; + int retval = 0; + + CHECK_NULL(ctx); + CHECK_NULL(request); + CHECK_NULL(response_arr); + CHECK_NULL(response_cnt); + + req_ctx = srvpp_get_thread_request_ctx(ctx); + if (NULL == req_ctx) { + SRVPP_LOG_ERR_MSG("Unable to obtain a request context."); + return -1; + } + + req = (vl_generic_request_t *) request; + req->client_index = ctx->vlib_client_index; + + /* allocate control ping request */ + ping = srvpp_alloc_msg(VL_API_CONTROL_PING, sizeof(*ping)); + if (NULL == ping) { + SRVPP_LOG_ERR_MSG("Unable to allocate control ping message."); + return -1; + } + ping->client_index = ctx->vlib_client_index; + + pthread_mutex_lock(&req_ctx->lock); + + req_ctx->resp_data_copy = true; + req_ctx->resp_msg_cnt = 0; + + req_ctx->resp_type = SRVPP_DETAILS; + req_ctx->resp_ready = false; + req_ctx->resp_retval = 0; + + req_ctx->ctx_id++; + req->context = req_ctx->ctx_id; + ping->context = req_ctx->ctx_id; + + pthread_mutex_unlock(&req_ctx->lock); + + SRVPP_LOG_DBG_MSG("Sending a dumprequest to VPP."); + + vl_msg_api_send_shmem(ctx->vlib_input_queue, (u8*)&req); + + vl_msg_api_send_shmem(ctx->vlib_input_queue, (u8*)&ping); + + /* wait for expected response */ + retval = srvpp_wait_response(req_ctx); + + if (0 == retval) { + *response_arr = req_ctx->resp_msg_arr; + *response_cnt = req_ctx->resp_msg_cnt; + SRVPP_LOG_DBG_MSG("VPP dumprequest successfully processed."); + } else { + SRVPP_LOG_ERR_MSG("Error by handling of a VPP dumprequest."); + } + + return retval; +} + +int +srvpp_get_if_index(srvpp_ctx_t *ctx, const char *if_name, uint32_t *if_index) +{ + CHECK_NULL(if_name); + CHECK_NULL(if_index); + + srvpp_if_info_t *if_info = NULL; + + if_info = ctx->if_info_list; + + while ((NULL != if_info) && (NULL != if_info->if_name)) { + if (0 == strcmp(if_info->if_name, if_name)) { + *if_index = if_info->if_index; + return 0; + } + if_info = if_info->_next; + } + + return 1; +} diff --git a/src/srvpp/src/srvpp.h b/src/srvpp/src/srvpp.h new file mode 100644 index 0000000..4830d7b --- /dev/null +++ b/src/srvpp/src/srvpp.h @@ -0,0 +1,185 @@ +/* + * 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. + */ + +#ifndef INC_SRVPP_H_ +#define INC_SRVPP_H_ + +/** + * @defgroup srvpp Sysrepo-VPP Integration Library + * @{ + * + * @brief Provides synchronous interface to VPP binary API aimed primarily for + * the integration of VPP with Sysrepo datastore. + * + * The library is thread-safe - can be used to communicate with VPP from multiple + * threads simultaneously. + */ + +#include <stddef.h> + +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <vapi/vpe.api.vapi.h> + +#include <vpp/api/vpe_msg_enum.h> + +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_printfun + +/** + * @brief Sysrepo - VPP interface context. + */ +typedef struct srvpp_ctx_s srvpp_ctx_t; + +/** + * @brief srvpp logger severity levels. + */ +typedef enum srvpp_log_level_e { + SRVPP_NONE, /**< Do not print any messages. */ + SRVPP_ERR, /**< Print only error messages. */ + SRVPP_WRN, /**< Print error and warning messages. */ + SRVPP_INF, /**< Besides errors and warnings, print some other informational messages. */ + SRVPP_DBG, /**< Print all messages including some development debug messages. */ +} srvpp_log_level_t; + +/** + * @brief Sets callback that will be called when a log entry would be populated. + * + * @param[in] level Severity level of the log entry. + * @param[in] message Message of the log entry. + */ +typedef void (*srvpp_log_cb)(srvpp_log_level_t level, const char *message); + +/** + * @brief Returns a reference to the global srvpp context. If the global context + * does not exists yet, it will be automatically created (a connection to VPP + * will be established). + * + * @note The caller is supposed to call ::srvpp_release_ctx after it finishes + * the work with VPP. + * + * @return srvpp context to be used for subsequent API calls. + */ +srvpp_ctx_t* srvpp_get_ctx(); + +/** + * @brief Releases a reference to the global srvpp context. If this is the last + * reference, the context will be released (the connection to the VPP will be closed). + * + * @param[in] ctx srvpp context acquired using ::srvpp_get_ctx call. + */ +void srvpp_release_ctx(srvpp_ctx_t *ctx); + +/** + * @brief Allocates a VPP API message of specified type and size. + * + * @param[in] msg_id Message ID. + * @param[in] msg_size Size of the message. + * + * @return Space allocated for the message. + */ +void* srvpp_alloc_msg(uint16_t msg_id, size_t msg_size); + +/** + * @brief Sends a simple request to VPP and receive the response. + * + * @note Subsequent ::srvpp_send_request or ::srvpp_send_dumprequest calls from the same + * thread may overwrite the content of ::response, therefore it is not safe to call + * them until the response is fully consumed / processed by the caller. + * + * @param[in] ctx srvpp context acquired using ::srvpp_get_ctx call. + * @param[in] request Message with the request to be sent to VPP. + * @param[out] response (optional) Response to the request received from VPP. + * Caller must not free it - to save memory allocations, response is always + * placed into the same thread-local buffer owned by the library. + * + * @return 0 on success, non-zero in case of error. + */ +int srvpp_send_request(srvpp_ctx_t *ctx, void *request, void **response); + +/** + * @brief Sends a dump request to VPP and receives all responses with details. + * + * @note Subsequent ::srvpp_send_request or ::srvpp_send_dumprequest calls from the same + * thread may overwrite the content of ::response_arr, therefore it is not safe to call + * them until the response array is fully consumed / processed by the caller. + * + * @param[in] ctx srvpp context acquired using ::srvpp_get_ctx call. + * @param[in] request Message with the request to be sent to VPP. + * @param[out] response_arr Array of responses to the request received from VPP. + * Caller must not free it - to save memory allocations, response_arr is always + * placed into the same thread-local buffer owned by the library. + * @param[out] response_cnt Count of the response messages in the response_arr array. + * + * @return 0 on success, non-zero in case of error. + */ +int srvpp_send_dumprequest(srvpp_ctx_t *ctx, void *request, void ***response_arr, size_t *response_cnt); + +/** + * @brief Get interface index for provided interface name. + * + * @param[in] ctx srvpp context acquired using ::srvpp_get_ctx call. + * @param[in] if_name Name of an existing VPP interface. + * @param[out] if_index Index of the interface. + * + * @return 0 on success, non-zero in case of error. + */ +int srvpp_get_if_index(srvpp_ctx_t *ctx, const char *if_name, uint32_t *if_index); + +/** + * @brief Sets callback that will be called when a log entry would be populated. + * Callback will be called for each message with any log level. + * + * @param[in] log_callback Callback to be called when a log entry would populated. + */ +void srvpp_set_log_cb(srvpp_log_cb log_callback); + +/** + * @brief Setups message handler to provided VPP API call. Needs to be called + * for each VPP API function that can arrive as response from VPP. + * + * @param[in] ID VPP API function ID. + * @param[in] NAME VPP API function name. + */ +#define srvpp_setup_handler(ID, NAME) \ + do { \ + vl_msg_api_set_handlers(VL_API_##ID, #NAME, \ + (void*)_srvpp_receive_msg_handler, \ + (void*)vl_noop_handler, \ + (void*)vl_api_##NAME##_t_endian, \ + (void*)vl_api_##NAME##_t_print, \ + sizeof(vl_api_##NAME##_t), 1); \ + } while(0) + +/** + * @brief Internal callback automatically called by VPP library when a message + * from VPP is received. + */ +void _srvpp_receive_msg_handler(void *message); + +/**@} srvpp */ + +#endif /* INC_SRVPP_H_ */ diff --git a/src/srvpp/src/srvpp_logger.c b/src/srvpp/src/srvpp_logger.c new file mode 100644 index 0000000..ee03a4b --- /dev/null +++ b/src/srvpp/src/srvpp_logger.c @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#include <stdlib.h> +#include <pthread.h> + +#include "srvpp_logger.h" + +#define MAX_LOG_MSG_SIZE 2048 /**< Maximum size of one log entry. */ + +volatile srvpp_log_cb srvpp_log_callback = NULL; /**< Global variable used to store logging callback, if set. */ + +static pthread_once_t srvpp_log_buff_create_key_once = PTHREAD_ONCE_INIT; /** Used to control that ::srvpp_log_buff_create_key is called only once per thread. */ +static pthread_key_t srvpp_log_buff_key; /**< Key for thread-specific buffer data. */ + +/** + * @brief Create key for thread-specific buffer data. Should be called only once per thread. + */ +static void +srvpp_log_buff_create_key(void) +{ + while (pthread_key_create(&srvpp_log_buff_key, free) == EAGAIN); + pthread_setspecific(srvpp_log_buff_key, NULL); +} + +void +srvpp_logger_init() +{ + /* no init needed */ +} + +void +srvpp_logger_cleanup() +{ + /* since the thread-specific data for the main thread seems to be not auto-freed, + * (at least on Linux), we explicitly release it here for the thread from which + * sr_logger_cleanup has been called (which should be always the main thread). */ + pthread_once(&srvpp_log_buff_create_key_once, srvpp_log_buff_create_key); + char *msg_buff = pthread_getspecific(srvpp_log_buff_key); + if (NULL != msg_buff) { + free(msg_buff); + pthread_setspecific(srvpp_log_buff_key, NULL); + } +} + +void +srvpp_log_to_cb(srvpp_log_level_t level, const char *format, ...) +{ + char *msg_buff = NULL; + va_list arg_list; + + if (NULL != srvpp_log_callback) { + /* get thread-local message buffer */ + pthread_once(&srvpp_log_buff_create_key_once, srvpp_log_buff_create_key); + msg_buff = pthread_getspecific(srvpp_log_buff_key); + if (NULL == msg_buff) { + msg_buff = calloc(MAX_LOG_MSG_SIZE, sizeof(*msg_buff)); + pthread_setspecific(srvpp_log_buff_key, msg_buff); + } + /* print the message into buffer and call callback */ + if (NULL != msg_buff) { + va_start(arg_list, format); + vsnprintf(msg_buff, MAX_LOG_MSG_SIZE - 1, format, arg_list); + va_end(arg_list); + msg_buff[MAX_LOG_MSG_SIZE - 1] = '\0'; + /* call the callback */ + srvpp_log_callback(level, msg_buff); + } + } +} + diff --git a/src/srvpp/src/srvpp_logger.h b/src/srvpp/src/srvpp_logger.h new file mode 100644 index 0000000..3a1c947 --- /dev/null +++ b/src/srvpp/src/srvpp_logger.h @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#ifndef SRC_SRVPP_LOGGER_H_ +#define SRC_SRVPP_LOGGER_H_ + +#include "srvpp.h" + +/** + * @brief Pointer to logging callback, if set. + */ +extern volatile srvpp_log_cb srvpp_log_callback; + +/** + * @brief Returns the string representing the specified log level. + */ +#define _SRVPP_LL_STR(LL) \ + ((SRVPP_ERR == LL) ? "ERR" : \ + (SRVPP_WRN == LL) ? "WRN" : \ + (SRVPP_INF == LL) ? "INF" : \ + (SRVPP_DBG == LL) ? "DBG" : \ + "") + +/** + * @brief Internal logging macro for formatted messages. + */ +#ifdef NDEBUG +#define _SRVPP_LOG_FMT(LL, MSG, ...) \ + do { \ + if (NULL != srvpp_log_callback) { \ + srvpp_log_to_cb(LL, MSG, __VA_ARGS__); \ + } else { \ + fprintf(stderr, "[%s] " MSG "\n", _SRVPP_LL_STR(LL), __VA_ARGS__); \ + } \ +} while(0) +#else +#define _SRVPP_LOG_FMT(LL, MSG, ...) \ + do { \ + if (NULL != srvpp_log_callback) { \ + srvpp_log_to_cb(LL, "(%s:%d) " MSG, __func__, __LINE__, __VA_ARGS__); \ + } else { \ + fprintf(stderr, "[%s] (%s:%d) " MSG "\n", _SRVPP_LL_STR(LL), __func__, __LINE__, __VA_ARGS__); \ + } \ +} while(0) +#endif + +/* Logging macros for formatted messages. */ +#define SRVPP_LOG_ERR(MSG, ...) _SRVPP_LOG_FMT(SRVPP_ERR, MSG, __VA_ARGS__); +#define SRVPP_LOG_WRN(MSG, ...) _SRVPP_LOG_FMT(SRVPP_WRN, MSG, __VA_ARGS__); +#define SRVPP_LOG_INF(MSG, ...) _SRVPP_LOG_FMT(SRVPP_INF, MSG, __VA_ARGS__); +#define SRVPP_LOG_DBG(MSG, ...) _SRVPP_LOG_FMT(SRVPP_DBG, MSG, __VA_ARGS__); + +/* Logging macros for unformatted messages. */ +#define SRVPP_LOG_ERR_MSG(MSG) _SRVPP_LOG_FMT(SRVPP_ERR, MSG "%s", ""); +#define SRVPP_LOG_WRN_MSG(MSG) _SRVPP_LOG_FMT(SRVPP_WRN, MSG "%s", ""); +#define SRVPP_LOG_INF_MSG(MSG) _SRVPP_LOG_FMT(SRVPP_INF, MSG "%s", ""); +#define SRVPP_LOG_DBG_MSG(MSG) _SRVPP_LOG_FMT(SRVPP_DBG, MSG "%s", ""); + +/** + * @brief Initializes logger. + */ +void srvpp_logger_init(); + +/** + * @brief Cleans up resources used by he logger. + */ +void srvpp_logger_cleanup(); + +/** + * @brief Logs into the callback pre-specified by ::srvpp_set_log_cb. + * + * @param[in] level Log level. + * @param[in] format Format message. + */ +void srvpp_log_to_cb(srvpp_log_level_t level, const char *format, ...); + +#endif /* SRC_SRVPP_LOGGER_H_ */ diff --git a/src/srvpp/tests/CMakeLists.txt b/src/srvpp/tests/CMakeLists.txt new file mode 100644 index 0000000..37b8c32 --- /dev/null +++ b/src/srvpp/tests/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# 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. +# + +include_directories ("${PROJECT_SOURCE_DIR}/src") + +# check whether valgrind is installed +find_program(valgrind_FOUND valgrind) + +# macro for adding of an unit test +macro(ADD_UNIT_TEST TEST_NAME) + set(TEST_SRC + ${TEST_NAME}.c + ) + add_executable(${TEST_NAME} ${TEST_SRC}) + target_link_libraries(${TEST_NAME} ${CMOCKA_LIBRARIES} srvpp_a) + add_test(${TEST_NAME} ${TEST_NAME}) + + if(valgrind_FOUND) + add_test(${TEST_NAME}_valgrind valgrind + --error-exitcode=1 --read-var-info=yes + --leak-check=full --show-leak-kinds=all + ./${TEST_NAME} + ) + endif(valgrind_FOUND) +endmacro(ADD_UNIT_TEST) + +# add individual unit-tests +ADD_UNIT_TEST(srvpp_test)
\ No newline at end of file diff --git a/src/srvpp/tests/srvpp_test.c b/src/srvpp/tests/srvpp_test.c new file mode 100644 index 0000000..89e56db --- /dev/null +++ b/src/srvpp/tests/srvpp_test.c @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "srvpp.h" + +#include <vpp/api/vpe_msg_enum.h> + +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_printfun + +static int +srvpp_test_setup(void **state) +{ + srvpp_ctx_t *ctx = NULL; + + ctx = srvpp_get_ctx(); + assert_non_null(ctx); + + srvpp_setup_handler(CONTROL_PING_REPLY, control_ping_reply); + srvpp_setup_handler(CREATE_LOOPBACK_REPLY, create_loopback_reply); + srvpp_setup_handler(DELETE_LOOPBACK_REPLY, delete_loopback_reply); + srvpp_setup_handler(SW_INTERFACE_DETAILS, sw_interface_details); + + *state = ctx; + return 0; +} + +static int +srvpp_test_teardown(void **state) +{ + srvpp_ctx_t *ctx = *state; + + srvpp_release_ctx(ctx); + + return 0; +} + +static void +srvpp_ctx_test(void **state) +{ + srvpp_ctx_t *ctx = *state; + assert_non_null(ctx); + + srvpp_ctx_t *ctx1 = NULL, *ctx2 = NULL; + vl_api_control_ping_t *msg = NULL; + int ret = 0; + + /* try to get additional contexts */ + ctx1 = srvpp_get_ctx(); + assert_non_null(ctx1); + + ctx2 = srvpp_get_ctx(); + assert_non_null(ctx2); + + assert_true(ctx == ctx1); + assert_true(ctx1 == ctx2); + + /* send a control ping */ + msg = srvpp_alloc_msg(VL_API_CONTROL_PING, sizeof(*msg)); + assert_non_null(msg); + + ret = srvpp_send_request(ctx1, msg, NULL); + assert_int_equal(ret, 0); + + /* release not needed contexts */ + srvpp_release_ctx(ctx1); + srvpp_release_ctx(ctx2); +} + +static void +srvpp_msg_test(void **state) +{ + srvpp_ctx_t *ctx = *state; + assert_non_null(ctx); + + vl_api_create_loopback_t *create_loop_req = NULL; + vl_api_create_loopback_reply_t *create_loop_reply = NULL; + vl_api_delete_loopback_t *del_loop_req = NULL; + vl_api_sw_interface_dump_t *if_dump_req = NULL; + vl_api_sw_interface_details_t *if_details = NULL; + void **details = NULL; + size_t details_cnt = 0; + u8 mac[6] = { 0, 10, 11, 12, 13, 14}; + int if_index = -1; + int ret = 0; + + /* create a loopback interface */ + create_loop_req = srvpp_alloc_msg(VL_API_CREATE_LOOPBACK, sizeof(*create_loop_req)); + assert_non_null(create_loop_req); + + memcpy(create_loop_req->mac_address, mac, sizeof(mac)); + + ret = srvpp_send_request(ctx, create_loop_req, (void**)&create_loop_reply); + assert_int_equal(ret, 0); + assert_non_null(create_loop_reply); + if_index = ntohl(create_loop_reply->sw_if_index); + + printf("connected loopback interface, ifindex=%d\n", if_index); + + /* dump interfaces */ + if_dump_req = srvpp_alloc_msg(VL_API_SW_INTERFACE_DUMP, sizeof(*if_dump_req)); + assert_non_null(if_dump_req); + + ret = srvpp_send_dumprequest(ctx, if_dump_req, &details, &details_cnt); + assert_int_equal(ret, 0); + + for (size_t i = 0; i < details_cnt; i++) { + if_details = (vl_api_sw_interface_details_t *) details[i]; + printf("interface %s id=%d %s\n", if_details->interface_name, ntohl(if_details->sw_if_index), if_details->admin_up_down ? "up" : "down"); + } + + /* delete a loopback interface */ + del_loop_req = srvpp_alloc_msg(VL_API_DELETE_LOOPBACK, sizeof(*del_loop_req)); + assert_non_null(del_loop_req); + + del_loop_req->sw_if_index = htonl(if_index); + + ret = srvpp_send_request(ctx, del_loop_req, NULL); + assert_int_equal(ret, 0); +} + +int +main() +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(srvpp_ctx_test, srvpp_test_setup, srvpp_test_teardown), + cmocka_unit_test_setup_teardown(srvpp_msg_test, srvpp_test_setup, srvpp_test_teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} |