*.pb.h
*.pb-c.h
*.pb-c.c
+*.pb.cc
*_clippy.c
### dist
GRTAGS
GPATH
compile_commands.json
+.ccls-cache
.dirstamp
refix
nodist_noinst_DATA =
lib_LTLIBRARIES =
module_LTLIBRARIES =
-libyang_plugins_LTLIBRARIES =
pkginclude_HEADERS =
nodist_pkginclude_HEADERS =
dist_examples_DATA =
$(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES
-$(AUTOMAKE_DUMMY)install-libyang_pluginsLTLIBRARIES: install-libLTLIBRARIES
include doc/subdir.am
include doc/user/subdir.am
include watchfrr/subdir.am
include qpb/subdir.am
include fpm/subdir.am
+include grpc/subdir.am
include tools/subdir.am
include solaris/subdir.am
doc/user/Makefile \
eigrpd/Makefile \
fpm/Makefile \
+ grpc/Makefile \
isisd/Makefile \
ldpd/Makefile \
lib/Makefile \
/* Do not put duplicated community entry. */
if (community_list_dup_check(list, entry))
community_entry_free(entry);
- else
+ else {
community_list_entry_add(list, entry);
+ route_map_notify_dependencies(name, RMAP_EVENT_LLIST_ADDED);
+ }
return 0;
}
/* Delete all of entry belongs to this community-list. */
if (!str) {
community_list_delete(cm, list);
+ route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED);
return 0;
}
return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
community_list_entry_delete(cm, list, entry);
+ route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED);
return 0;
}
struct bgp_path_info *pi;
int rd_header;
int header = 1;
+ char rd_str[BUFSIZ];
+ char buf[BUFSIZ];
unsigned long output_count = 0;
unsigned long total_count = 0;
json_object *json = NULL;
json_object *json_nroute = NULL;
json_object *json_array = NULL;
- json_object *json_scode = NULL;
- json_object *json_ocode = NULL;
+ json_object *json_prefix_info = NULL;
+
+ memset(rd_str, 0, BUFSIZ);
bgp = bgp_get_evpn();
if (bgp == NULL) {
return CMD_WARNING;
}
- if (use_json) {
- json_scode = json_object_new_object();
- json_ocode = json_object_new_object();
+ if (use_json)
json = json_object_new_object();
- json_nroute = json_object_new_object();
-
- json_object_string_add(json_scode, "suppressed", "s");
- json_object_string_add(json_scode, "damped", "d");
- json_object_string_add(json_scode, "history", "h");
- json_object_string_add(json_scode, "valid", "*");
- json_object_string_add(json_scode, "best", ">");
- json_object_string_add(json_scode, "internal", "i");
-
- json_object_string_add(json_ocode, "igp", "i");
- json_object_string_add(json_ocode, "egp", "e");
- json_object_string_add(json_ocode, "incomplete", "?");
- }
for (rn = bgp_table_top(bgp->rib[afi][SAFI_EVPN]); rn;
rn = bgp_route_next(rn)) {
uint64_t tbl_ver;
- if (use_json)
- continue; /* XXX json TODO */
-
if (prd && memcmp(rn->p.u.val, prd->val, 8) != 0)
continue;
pi->peer->su_remote, su))
continue;
}
- if (header == 0) {
+ if (header) {
if (use_json) {
- if (option
- == SHOW_DISPLAY_TAGS) {
- json_object_int_add(
- json,
- "bgpTableVersion",
- tbl_ver);
- json_object_string_add(
- json,
- "bgpLocalRouterId",
- inet_ntoa(
- bgp->router_id));
- json_object_object_add(
- json,
- "bgpStatusCodes",
- json_scode);
- json_object_object_add(
- json,
- "bgpOriginCodes",
- json_ocode);
- }
+ json_object_int_add(
+ json, "bgpTableVersion",
+ tbl_ver);
+ json_object_string_add(
+ json,
+ "bgpLocalRouterId",
+ inet_ntoa(
+ bgp->router_id));
+ json_object_int_add(
+ json,
+ "defaultLocPrf",
+ bgp->default_local_pref);
+ json_object_int_add(
+ json, "localAS",
+ bgp->as);
} else {
if (option == SHOW_DISPLAY_TAGS)
vty_out(vty,
else if (type == RD_TYPE_IP)
decode_rd_ip(pnt + 2, &rd_ip);
if (use_json) {
- char buffer[BUFSIZ];
+ json_nroute =
+ json_object_new_object();
+ json_prefix_info =
+ json_object_new_object();
+ json_array =
+ json_object_new_array();
if (type == RD_TYPE_AS
|| type == RD_TYPE_AS4)
- sprintf(buffer, "%u:%d",
+ sprintf(rd_str, "%u:%d",
rd_as.as,
rd_as.val);
else if (type == RD_TYPE_IP)
- sprintf(buffer, "%s:%d",
+ sprintf(rd_str, "%s:%d",
inet_ntoa(
rd_ip.ip),
rd_ip.val);
json_object_string_add(
json_nroute,
- "routeDistinguisher",
- buffer);
+ "rd",
+ rd_str);
+
+ json_object_string_add(
+ json_prefix_info,
+ "prefix",
+ bgp_evpn_route2str(
+ (struct prefix_evpn *)
+ &rm->p, buf, BUFSIZ));
+
+ json_object_int_add(
+ json_prefix_info,
+ "prefixLen",
+ rm->p.prefixlen);
+
} else {
vty_out(vty,
"Route Distinguisher: ");
}
rd_header = 0;
}
- if (use_json)
- json_array = json_object_new_array();
- else
- json_array = NULL;
if (option == SHOW_DISPLAY_TAGS)
route_vty_out_tag(vty, &rm->p, pi, 0,
SAFI_EVPN,
SAFI_EVPN, json_array);
output_count++;
}
- /* XXX json */
+
+ if (use_json) {
+ json_object_object_add(json_prefix_info, "paths",
+ json_array);
+ json_object_object_add(json_nroute, buf,
+ json_prefix_info);
+ json_object_object_add(json, rd_str, json_nroute);
+ }
+ }
+
+ if (use_json) {
+ json_object_int_add(json, "numPrefix", output_count);
+ json_object_int_add(json, "totalPrefix", total_count);
+ vty_out(vty, "%s\n", json_object_to_json_string_ext(
+ json, JSON_C_TO_STRING_PRETTY));
+ json_object_free(json);
+ } else {
+ if (output_count == 0)
+ vty_out(vty, "No prefixes displayed, %ld exist\n",
+ total_count);
+ else
+ vty_out(vty,
+ "\nDisplayed %ld out of %ld total prefixes\n",
+ output_count, total_count);
}
- if (output_count == 0)
- vty_out(vty, "No prefixes displayed, %ld exist\n", total_count);
- else
- vty_out(vty, "\nDisplayed %ld out of %ld total prefixes\n",
- output_count, total_count);
return CMD_SUCCESS;
}
vty_out(vty,
"ssh sockets are not supported. "
"Please recompile rtrlib and frr with ssh support. "
- "If you want to use it");
+ "If you want to use it\n");
#endif
} else { // use tcp connection
return_value = add_tcp_cache(cache, tcpport, preference);
cache->tr_config.tcp_config->host,
cache->tr_config.tcp_config->port);
+#if defined(FOUND_SSH)
} else if (cache->type == SSH) {
vty_out(vty,
"host: %s port: %d username: %s "
->server_hostkey_path,
cache->tr_config.ssh_config
->client_privkey_path);
+#endif
}
}
if (entry == list->head) {
if (all_digit(list->name))
vty_out(vty, "Large community %s list %s\n",
- entry->style == EXTCOMMUNITY_LIST_STANDARD
+ entry->style ==
+ LARGE_COMMUNITY_LIST_STANDARD
? "standard"
: "(expanded) access",
list->name);
else
vty_out(vty,
"Named large community %s list %s\n",
- entry->style == EXTCOMMUNITY_LIST_STANDARD
+ entry->style ==
+ LARGE_COMMUNITY_LIST_STANDARD
? "standard"
: "expanded",
list->name);
/* Set configuration on peer. */
filter = &peer->filter[afi][safi];
- if (filter->map[direct].name)
+ if (filter->map[direct].name) {
+ /* If the neighbor is configured with the same route-map
+ * again then, ignore the duplicate configuration.
+ */
+ if (strcmp(filter->map[direct].name, name) == 0)
+ return 0;
+
XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name);
+ }
route_map_counter_decrement(filter->map[direct].map);
filter->map[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name);
filter->map[direct].map = route_map;
##
AC_PREREQ([2.60])
-AC_INIT([frr], [7.1-dev], [https://github.com/frrouting/frr/issues])
+AC_INIT([frr], [7.2-dev], [https://github.com/frrouting/frr/issues])
PACKAGE_URL="https://frrouting.org/"
AC_SUBST([PACKAGE_URL])
PACKAGE_FULLNAME="FRRouting"
dnl --------------------
dnl note orig_cflags is also used further down
orig_cflags="$CFLAGS"
+orig_cxxflags="$CXXFLAGS"
AC_LANG([C])
AC_PROG_CC
AC_PROG_CPP
+AC_PROG_CXX
AM_PROG_CC_C_O
dnl remove autoconf default "-g -O2"
CFLAGS="$orig_cflags"
+CXXFLAGS="$orig_cxxflags"
AC_PROG_CC_C99
dnl NB: see C11 below
AS_HELP_STRING([--enable-confd=ARG], [enable confd integration]))
AC_ARG_ENABLE([sysrepo],
AS_HELP_STRING([--enable-sysrepo], [enable sysrepo integration]))
+AC_ARG_ENABLE([grpc],
+ AS_HELP_STRING([--enable-grpc], [enable the gRPC northbound plugin]))
AC_ARG_ENABLE([zeromq],
AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)]))
AC_ARG_WITH([libpam],
], [[#include <libyang/libyang.h>]])
CFLAGS="$ac_cflags_save"
-ac_libs_save="$LIBS"
-LIBS="$LIBS $LIBYANG_LIBS"
-AC_CHECK_FUNC([ly_register_types], [
- libyang_ext_builtin=true
- AC_DEFINE([LIBYANG_EXT_BUILTIN], [1], [have ly_register_types()])
-], [
- libyang_ext_builtin=false
- AC_MSG_WARN([===== old libyang (before 0.16.74) detected =====])
- AC_MSG_WARN([The available version of libyang does not seem to support])
- AC_MSG_WARN([built-in YANG extension modules. This will cause "make check"])
- AC_MSG_WARN([to fail and may create installation and version mismatch issues.])
- AC_MSG_WARN([Support for the old mechanism will be removed at some point.])
- AC_MSG_WARN([Please update libyang to version 0.16.74 or newer.])
- AC_MSG_WARN([===== old libyang (before 0.16.74) detected =====])
-])
-AM_CONDITIONAL([LIBYANG_EXT_BUILTIN], [$libyang_ext_builtin])
-LIBS="$ac_libs_save"
-
dnl ---------------
dnl configuration rollbacks
dnl ---------------
fi
AM_CONDITIONAL([SYSREPO], [test "x$enable_sysrepo" = "xyes"])
+dnl ---------------
+dnl gRPC
+dnl ---------------
+if test "$enable_grpc" = "yes"; then
+ PKG_CHECK_MODULES([GRPC], [grpc grpc++ protobuf], [
+ AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false])
+ if test "$PROTOC" = "/bin/false"; then
+ AC_MSG_FAILURE([grpc requested but protoc not found.])
+ fi
+
+ AC_DEFINE([HAVE_GRPC], [1], [Enable the gRPC northbound plugin])
+ GRPC=true
+ ], [
+ GRPC=false
+ AC_MSG_ERROR([grpc/grpc++ were not found on your system.])
+ ])
+fi
+AM_CONDITIONAL([GRPC], [test "x$enable_grpc" = "xyes"])
+
dnl ---------------
dnl math
dnl ---------------
.. code-block:: c
+ #include <typesafe.h>
+
PREDECL_XXX(Z)
struct item {
int otherdata;
The following iteration macros work across all data structures:
-.. c:function:: for_each(Z, head, item)
+.. c:function:: for_each(Z, &head, item)
Equivalent to:
.. code-block:: c
- for (item = Z_first(head); item; item = Z_next(head, item))
+ for (item = Z_first(&head); item; item = Z_next(&head, item))
Note that this will fail if the list is modified while being iterated
over.
-.. c:function:: for_each_safe(Z, head, item)
+.. c:function:: for_each_safe(Z, &head, item)
Same as the previous, but the next element is pre-loaded into a "hidden"
variable (named ``Z_safe``.) Equivalent to:
.. code-block:: c
- for (item = Z_first(head); item; item = next) {
- next = Z_next_safe(head, item);
+ for (item = Z_first(&head); item; item = next) {
+ next = Z_next_safe(&head, item);
...
}
tables is resized while iterating. This will cause items to be
skipped or iterated over twice.
-.. c:function:: for_each_from(Z, head, item, from)
+.. c:function:: for_each_from(Z, &head, item, from)
Iterates over the list, starting at item ``from``. This variant is "safe"
as in the previous macro. Equivalent to:
.. code-block:: c
for (item = from; item; item = from) {
- from = Z_next_safe(head, item);
+ from = Z_next_safe(&head, item);
...
}
Write current configuration to configuration file.
-.. index:: configure terminal
-.. clicmd:: configure terminal
+.. index:: configure [terminal]
+.. clicmd:: configure [terminal]
Change to configuration mode. This command is the first step to
configuration.
.. index:: [no] neighbor PEER maximum-prefix NUMBER
.. clicmd:: [no] neighbor PEER maximum-prefix NUMBER
-.. index:: [no] neighbor PEER local-as AS-NUMBER no-prepend
-.. clicmd:: [no] neighbor PEER local-as AS-NUMBER no-prepend
-
-.. index:: [no] neighbor PEER local-as AS-NUMBER no-prepend replace-as
-.. clicmd:: [no] neighbor PEER local-as AS-NUMBER no-prepend replace-as
-
-.. index:: [no] neighbor PEER local-as AS-NUMBER
-.. clicmd:: [no] neighbor PEER local-as AS-NUMBER
+ Sets a maximum number of prefixes we can receive from a given peer. If this
+ number is exceeded, the BGP session will be destroyed.
+
+ In practice, it is generally preferable to use a prefix-list to limit what
+ prefixes are received from the peer instead of using this knob. Tearing down
+ the BGP session when a limit is exceeded is far more destructive than merely
+ rejecting undesired prefixes. The prefix-list method is also much more
+ granular and offers much smarter matching criterion than number of received
+ prefixes, making it more suited to implementing policy.
+
+.. index:: [no] neighbor PEER local-as AS-NUMBER [no-prepend] [replace-as]
+.. clicmd:: [no] neighbor PEER local-as AS-NUMBER [no-prepend] [replace-as]
Specify an alternate AS for this BGP process when interacting with the
specified peer. With no modifiers, the specified local-as is prepended to
.. code-block:: frr
# case with VRF
- configure terminal
+ configure
vrf r1-cust1
ip route 10.0.0.0/24 10.0.0.2
exit-vrf
Display various statistics related to the installation and deletion
of routes, neighbor updates, and LSP's into the kernel.
+.. index:: show zebra client [summary]
+.. clicmd:: show zebra client [summary]
+
+ Display statistics about clients that are connected to zebra. This is
+ useful for debugging and seeing how much data is being passed between
+ zebra and it's clients. If the summary form of the command is choosen
+ a table is displayed with shortened information.
+
+.. index:: show zebra router table summary
+.. clicmd:: show zebra router table summary
+
+ Display summarized data about tables created, their afi/safi/tableid
+ and how many routes each table contains. Please note this is the
+ total number of route nodes in the table. Which will be higher than
+ the actual number of routes that are held.
+
.. index:: show zebra fpm stats
.. clicmd:: show zebra fpm stats
--- /dev/null
+all: ALWAYS
+ @$(MAKE) -s -C .. grpc/libfrrgrpc_pb.la
+%: ALWAYS
+ @$(MAKE) -s -C .. grpc/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
--- /dev/null
+//
+// Copyright (C) 2019 NetDEF, Inc.
+// Renato Westphal
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; see the file COPYING; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+syntax = "proto3";
+
+package frr;
+
+// Service specification for the FRR northbound interface.
+service Northbound {
+ // Retrieve the capabilities supported by the target.
+ rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse) {}
+
+ // Retrieve configuration data, state data or both from the target.
+ rpc Get(GetRequest) returns (stream GetResponse) {}
+
+ // Create a new candidate configuration and return a reference to it. The
+ // created candidate is a copy of the running configuration.
+ rpc CreateCandidate(CreateCandidateRequest) returns (CreateCandidateResponse) {}
+
+ // Delete a candidate configuration.
+ rpc DeleteCandidate(DeleteCandidateRequest) returns (DeleteCandidateResponse) {}
+
+ // Update a candidate configuration by rebasing the changes on top of the
+ // latest running configuration. Resolve conflicts automatically by giving
+ // preference to the changes done in the candidate configuration.
+ rpc UpdateCandidate(UpdateCandidateRequest) returns (UpdateCandidateResponse) {}
+
+ // Edit a candidate configuration. All changes are discarded if any error
+ // happens.
+ rpc EditCandidate(EditCandidateRequest) returns (EditCandidateResponse) {}
+
+ // Load configuration data into a candidate configuration. Both merge and
+ // replace semantics are supported.
+ rpc LoadToCandidate(LoadToCandidateRequest) returns (LoadToCandidateResponse) {}
+
+ // Create a new configuration transaction using a two-phase commit protocol.
+ rpc Commit(CommitRequest) returns (CommitResponse) {}
+
+ // List the metadata of all configuration transactions recorded in the
+ // transactions database.
+ rpc ListTransactions(ListTransactionsRequest) returns (stream ListTransactionsResponse) {}
+
+ // Fetch a configuration (identified by its transaction ID) from the
+ // transactions database.
+ rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {}
+
+ // Lock the running configuration, preventing other users from changing it.
+ rpc LockConfig(LockConfigRequest) returns (LockConfigResponse) {}
+
+ // Unlock the running configuration.
+ rpc UnlockConfig(UnlockConfigRequest) returns (UnlockConfigResponse) {}
+
+ // Execute a YANG RPC.
+ rpc Execute(ExecuteRequest) returns (ExecuteResponse) {}
+}
+
+// ----------------------- Parameters and return types -------------------------
+
+//
+// RPC: GetCapabilities()
+//
+message GetCapabilitiesRequest {
+ // Empty.
+}
+
+message GetCapabilitiesResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // FRR version.
+ string frr_version = 1;
+
+ // Indicates whether FRR was compiled with support for configuration
+ // rollbacks or not (--enable-config-rollbacks).
+ bool rollback_support = 2;
+
+ // Supported schema modules.
+ repeated ModuleData supported_modules = 3;
+
+ // Supported encodings.
+ repeated Encoding supported_encodings = 4;
+}
+
+//
+// RPC: Get()
+//
+message GetRequest {
+ // Type of elements within the data tree.
+ enum DataType {
+ // All data elements.
+ ALL = 0;
+
+ // Config elements.
+ CONFIG = 1;
+
+ // State elements.
+ STATE = 2;
+ }
+
+ // The type of data being requested.
+ DataType type = 1;
+
+ // Encoding to be used.
+ Encoding encoding = 2;
+
+ // Include implicit default nodes.
+ bool with_defaults = 3;
+
+ // Paths requested by the client.
+ repeated string path = 4;
+}
+
+message GetResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::INVALID_ARGUMENT: Invalid YANG data path.
+
+ // Timestamp in nanoseconds since Epoch.
+ int64 timestamp = 1;
+
+ // The requested data.
+ DataTree data = 2;
+}
+
+//
+// RPC: CreateCandidate()
+//
+message CreateCandidateRequest {
+ // Empty.
+}
+
+message CreateCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::RESOURCE_EXHAUSTED: can't create candidate
+ // configuration.
+
+ // Handle to the new created candidate configuration.
+ uint32 candidate_id = 1;
+}
+
+//
+// RPC: DeleteCandidate()
+//
+message DeleteCandidateRequest {
+ // Candidate configuration to delete.
+ uint32 candidate_id = 1;
+}
+
+message DeleteCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+}
+
+//
+// RPC: UpdateCandidate()
+//
+message UpdateCandidateRequest {
+ // Candidate configuration to update.
+ uint32 candidate_id = 1;
+}
+
+message UpdateCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+}
+
+//
+// RPC: EditCandidate()
+//
+message EditCandidateRequest {
+ // Candidate configuration that is going to be edited.
+ uint32 candidate_id = 1;
+
+ // Data elements to be created or updated.
+ repeated PathValue update = 2;
+
+ // Paths to be deleted from the data tree.
+ repeated PathValue delete = 3;
+}
+
+message EditCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+ // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while editing the
+ // candidate configuration.
+}
+
+//
+// RPC: LoadToCandidate()
+//
+message LoadToCandidateRequest {
+ enum LoadType {
+ // Merge the data tree into the candidate configuration.
+ MERGE = 0;
+
+ // Replace the candidate configuration by the provided data tree.
+ REPLACE = 1;
+ }
+
+ // Candidate configuration that is going to be edited.
+ uint32 candidate_id = 1;
+
+ // Load operation to apply.
+ LoadType type = 2;
+
+ // Configuration data.
+ DataTree config = 3;
+}
+
+message LoadToCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while performing
+ // the load operation.
+}
+
+//
+// RPC: Commit()
+//
+message CommitRequest {
+ enum Phase {
+ // Validate if the configuration changes are valid (phase 0).
+ VALIDATE = 0;
+
+ // Prepare resources to apply the configuration changes (phase 1).
+ PREPARE = 1;
+
+ // Release previously allocated resources (phase 2).
+ ABORT = 2;
+
+ // Apply the configuration changes (phase 2).
+ APPLY = 3;
+
+ // All of the above (VALIDATE + PREPARE + ABORT/APPLY).
+ //
+ // This option can't be used to implement network-wide transactions,
+ // since they require the manager entity to take into account the results
+ // of the preparation phase of multiple managed devices.
+ ALL = 4;
+ }
+
+ // Candidate configuration that is going to be committed.
+ uint32 candidate_id = 1;
+
+ // Transaction phase.
+ Phase phase = 2;
+
+ // Assign a comment to this commit.
+ string comment = 3;
+}
+
+message CommitResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: misuse of the two-phase commit
+ // protocol.
+ // - grpc::StatusCode::INVALID_ARGUMENT: Validation error.
+ // - grpc::StatusCode::RESOURCE_EXHAUSTED: Failure to allocate resource.
+
+ // ID of the created configuration transaction (when the phase is APPLY
+ // or ALL).
+ uint32 transaction_id = 1;
+}
+
+//
+// RPC: ListTransactions()
+//
+message ListTransactionsRequest {
+ // Empty.
+}
+
+message ListTransactionsResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // Transaction ID.
+ uint32 id = 1;
+
+ // Client that committed the transaction.
+ string client = 2;
+
+ // Date and time the transaction was committed.
+ string date = 3;
+
+ // Comment assigned to the transaction.
+ string comment = 4;
+}
+
+//
+// RPC: GetTransaction()
+//
+message GetTransactionRequest {
+ // Transaction to retrieve.
+ uint32 transaction_id = 1;
+
+ // Encoding to be used.
+ Encoding encoding = 2;
+
+ // Include implicit default nodes.
+ bool with_defaults = 3;
+}
+
+message GetTransactionResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Transaction wasn't found in the transactions
+ // database.
+
+ DataTree config = 1;
+}
+
+//
+// RPC: LockConfig()
+//
+message LockConfigRequest {
+ // Empty.
+}
+
+message LockConfigResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration is
+ // locked already.
+}
+
+//
+// RPC: UnlockConfig()
+//
+message UnlockConfigRequest {
+ // Empty.
+}
+
+message UnlockConfigResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration isn't
+ // locked.
+}
+
+//
+// RPC: Execute()
+//
+message ExecuteRequest {
+ // Path of the YANG RPC or YANG Action.
+ string path = 1;
+
+ // Input parameters.
+ repeated PathValue input = 2;
+}
+
+message ExecuteResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // Output parameters.
+ repeated PathValue output = 1;
+}
+
+// -------------------------------- Definitions --------------------------------
+
+// YANG module.
+message ModuleData {
+ // Name of the YANG module;
+ string name = 1;
+
+ // Organization publishing the module.
+ string organization = 2;
+
+ // Latest revision of the module;
+ string revision = 3;
+}
+
+// Supported encodings for YANG instance data.
+enum Encoding {
+ JSON = 0;
+ XML = 1;
+}
+
+// Path-value pair representing a data element.
+message PathValue {
+ // YANG data path.
+ string path = 1;
+
+ // Data value.
+ string value = 2;
+}
+
+// YANG instance data.
+message DataTree {
+ Encoding encoding = 1;
+ string data = 2;
+}
--- /dev/null
+if GRPC
+lib_LTLIBRARIES += grpc/libfrrgrpc_pb.la
+endif
+
+grpc_libfrrgrpc_pb_la_LDFLAGS = -version-info 0:0:0
+grpc_libfrrgrpc_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(GRPC_CXXFLAGS)
+
+nodist_grpc_libfrrgrpc_pb_la_SOURCES = \
+ grpc/frr-northbound.pb.cc \
+ grpc/frr-northbound.grpc.pb.cc \
+ # end
+
+CLEANFILES += \
+ grpc/frr-northbound.pb.cc \
+ grpc/frr-northbound.pb.h \
+ grpc/frr-northbound.grpc.pb.cc \
+ grpc/frr-northbound.grpc.pb.h \
+ # end
+
+EXTRA_DIST += grpc/frr-northbound.proto
+
+AM_V_PROTOC = $(am__v_PROTOC_$(V))
+am__v_PROTOC_ = $(am__v_PROTOC_$(AM_DEFAULT_VERBOSITY))
+am__v_PROTOC_0 = @echo " PROTOC" $@;
+am__v_PROTOC_1 =
+
+.proto.pb.cc:
+ $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^
+.proto.grpc.pb.cc:
+ $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --grpc_out=$(top_srcdir) --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` $(top_srcdir)/$^
}
/* check if the interface is a loopback and if so set it as passive */
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (ifp && if_is_loopback(ifp))
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
- NB_OP_MODIFY, "true");
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (ifp && if_is_loopback(ifp))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
+ NB_OP_MODIFY, "true");
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return nb_cli_apply_changes(vty, NULL);
}
}
/* check if the interface is a loopback and if so set it as passive */
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (ifp && if_is_loopback(ifp))
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
- NB_OP_MODIFY, "true");
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (ifp && if_is_loopback(ifp))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
+ NB_OP_MODIFY, "true");
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return nb_cli_apply_changes(vty, NULL);
}
"Act as both a station router and an area router\n"
"Act as an area router only\n")
{
- const char *value = NULL;
- struct isis_area *area;
+ const char *value;
- area = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ struct isis_area *area;
+
+ area = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+
+ /*
+ * Put the is-type back to defaults:
+ * - level-1-2 on first area
+ * - level-1 for the rest
+ */
+ if (area && listgetdata(listhead(isis->area_list)) == area)
+ value = "level-1-2";
+ else
+ value = NULL;
+ }
+ pthread_rwlock_unlock(&running_config->lock);
- /*
- * Put the is-type back to defaults:
- * - level-1-2 on first area
- * - level-1 for the rest
- */
- if (area && listgetdata(listhead(isis->area_list)) == area)
- value = "level-1-2";
- else
- value = NULL;
nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, value);
return nb_cli_apply_changes(vty, NULL);
"Level-1-2 adjacencies are formed\n"
"Level-2 only adjacencies are formed\n")
{
- struct interface *ifp;
- struct isis_circuit *circuit;
- int is_type;
- const char *circ_type;
+ const char *circ_type = NULL;
/*
* Default value depends on whether the circuit is part of an area,
* and the is-type of the area if there is one. So we need to do this
* here.
*/
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (!ifp)
- goto def_val;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ struct interface *ifp;
+ struct isis_circuit *circuit;
- circuit = circuit_scan_by_ifp(ifp);
- if (!circuit)
- goto def_val;
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (!ifp)
+ goto unlock;
- if (circuit->state == C_STATE_UP)
- is_type = circuit->area->is_type;
- else
- goto def_val;
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit || circuit->state != C_STATE_UP)
+ goto unlock;
- switch (is_type) {
- case IS_LEVEL_1:
- circ_type = "level-1";
- break;
- case IS_LEVEL_2:
- circ_type = "level-2";
- break;
- case IS_LEVEL_1_AND_2:
- circ_type = "level-1-2";
- break;
- default:
- return CMD_ERR_NO_MATCH;
+ switch (circuit->area->is_type) {
+ case IS_LEVEL_1:
+ circ_type = "level-1";
+ break;
+ case IS_LEVEL_2:
+ circ_type = "level-2";
+ break;
+ case IS_LEVEL_1_AND_2:
+ circ_type = "level-1-2";
+ break;
+ }
}
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type",
- NB_OP_MODIFY, circ_type);
-
- return nb_cli_apply_changes(vty, NULL);
+unlock:
+ pthread_rwlock_unlock(&running_config->lock);
-def_val:
nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type",
- NB_OP_MODIFY, NULL);
+ NB_OP_MODIFY, circ_type);
return nb_cli_apply_changes(vty, NULL);
}
/* Configuration from terminal */
DEFUN (config_terminal,
config_terminal_cmd,
- "configure terminal",
+ "configure [terminal]",
"Configuration from vty interface\n"
"Configuration terminal\n")
{
vty_out(vty, "frr defaults %s\n", DFLT_NAME);
vty_out(vty, "!\n");
- for (i = 0; i < vector_active(cmdvec); i++)
- if ((node = vector_slot(cmdvec, i)) && node->func
- && (node->vtysh || vty->type != VTY_SHELL)) {
- if ((*node->func)(vty))
- vty_out(vty, "!\n");
- }
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ for (i = 0; i < vector_active(cmdvec); i++)
+ if ((node = vector_slot(cmdvec, i)) && node->func
+ && (node->vtysh || vty->type != VTY_SHELL)) {
+ if ((*node->func)(vty))
+ vty_out(vty, "!\n");
+ }
+ }
+ pthread_rwlock_unlock(&running_config->lock);
if (vty->type == VTY_TERM) {
vty_out(vty, "end\n");
vector_free(v);
}
+char *frrstr_replace(const char *str, const char *find, const char *replace)
+{
+ char *ch;
+ char *nustr = XSTRDUP(MTYPE_TMP, str);
+
+ size_t findlen = strlen(find);
+ size_t repllen = strlen(replace);
+
+ while ((ch = strstr(nustr, find))) {
+ if (repllen > findlen) {
+ size_t nusz = strlen(nustr) + repllen - findlen + 1;
+ nustr = XREALLOC(MTYPE_TMP, nustr, nusz);
+ ch = strstr(nustr, find);
+ }
+
+ size_t nustrlen = strlen(nustr);
+ size_t taillen = (nustr + nustrlen) - (ch + findlen);
+
+ memmove(ch + findlen + (repllen - findlen), ch + findlen,
+ taillen + 1);
+ memcpy(ch, replace, repllen);
+ }
+
+ return nustr;
+}
+
bool begins_with(const char *str, const char *prefix)
{
if (!str || !prefix)
*/
void frrstr_strvec_free(vector v);
+/*
+ * Given a string, replaces all occurrences of a substring with a different
+ * string. The result is a new string. The original string is not modified.
+ *
+ * If 'replace' is longer than 'find', this function performs N+1 allocations,
+ * where N is the number of times 'find' occurs in 'str'. If 'replace' is equal
+ * in length or shorter than 'find', only 1 allocation is performed.
+ *
+ * str
+ * String to perform replacement on.
+ *
+ * find
+ * Substring to replace.
+ *
+ * replace
+ * String to replace 'find' with.
+ *
+ * Returns:
+ * A new string, allocated with MTYPE_TMP, that is the result of performing
+ * the replacement on 'str'. This must be freed by the caller.
+ */
+char *frrstr_replace(const char *str, const char *find, const char *replace);
/*
* Prefix match for string.
*
if (yang_module_find("frr-interface")) {
struct lyd_node *if_dnode;
- if_dnode = yang_dnode_get(
- running_config->dnode,
- "/frr-interface:lib/interface[name='%s'][vrf='%s']/vrf",
- ifp->name, old_vrf->name);
- if (if_dnode) {
- yang_dnode_change_leaf(if_dnode, vrf->name);
- running_config->version++;
+ pthread_rwlock_wrlock(&running_config->lock);
+ {
+ if_dnode = yang_dnode_get(
+ running_config->dnode,
+ "/frr-interface:lib/interface[name='%s'][vrf='%s']/vrf",
+ ifp->name, old_vrf->name);
+ if (if_dnode) {
+ yang_dnode_change_leaf(if_dnode, vrf->name);
+ running_config->version++;
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
}
}
-
/* Delete interface structure. */
void if_delete_retain(struct interface *ifp)
{
.description = "The northbound subsystem has detected that the libsysrepo library returned an error",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
+ {
+ .code = EC_LIB_GRPC_INIT,
+ .title = "gRPC initialization error",
+ .description = "Upon startup FRR failed to properly initialize and startup the gRPC northbound plugin",
+ .suggestion = "Check if the gRPC libraries are installed correctly in the system.",
+ },
{
.code = EC_LIB_NB_CB_CONFIG_ABORT,
.title = "A northbound configuration callback has failed in the ABORT phase",
EC_LIB_SYSREPO_INIT,
EC_LIB_SYSREPO_DATA_CONVERT,
EC_LIB_LIBSYSREPO,
+ EC_LIB_GRPC_INIT,
EC_LIB_ID_CONSISTENCY,
EC_LIB_ID_EXHAUST,
};
/*
* Update the shared candidate after reading the startup configuration.
*/
- nb_config_replace(vty_shared_candidate_config, running_config, true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty_shared_candidate_config, running_config,
+ true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return 0;
}
/* Hash table of user pointers associated with configuration entries. */
static struct hash *running_config_entries;
+/* Management lock for the running configuration. */
+static struct {
+ /* Mutex protecting this structure. */
+ pthread_mutex_t mtx;
+
+ /* Actual lock. */
+ bool locked;
+
+ /* Northbound client who owns this lock. */
+ enum nb_client owner_client;
+
+ /* Northbound user who owns this lock. */
+ const void *owner_user;
+} running_config_mgmt_lock;
+
/*
* Global lock used to prevent multiple configuration transactions from
* happening concurrently.
static struct nb_transaction *nb_transaction_new(struct nb_config *config,
struct nb_config_cbs *changes,
enum nb_client client,
+ const void *user,
const char *comment);
static void nb_transaction_free(struct nb_transaction *transaction);
static int nb_transaction_process(enum nb_event event,
else
config->dnode = yang_dnode_new(ly_native_ctx, true);
config->version = 0;
+ pthread_rwlock_init(&config->lock, NULL);
return config;
}
{
if (config->dnode)
yang_dnode_free(config->dnode);
+ pthread_rwlock_destroy(&config->lock);
XFREE(MTYPE_NB_CONFIG, config);
}
dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
dup->dnode = yang_dnode_dup(config->dnode);
dup->version = config->version;
+ pthread_rwlock_init(&dup->lock, NULL);
return dup;
}
bool nb_candidate_needs_update(const struct nb_config *candidate)
{
- if (candidate->version < running_config->version)
- return true;
+ bool ret = false;
- return false;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (candidate->version < running_config->version)
+ ret = true;
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
+ return ret;
}
int nb_candidate_update(struct nb_config *candidate)
{
struct nb_config *updated_config;
- updated_config = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ updated_config = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
if (nb_config_merge(updated_config, candidate, true) != NB_OK)
return NB_ERR;
return NB_ERR_VALIDATION;
RB_INIT(nb_config_cbs, &changes);
- nb_config_diff(running_config, candidate, &changes);
- ret = nb_candidate_validate_changes(candidate, &changes);
- nb_config_diff_del_changes(&changes);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ ret = nb_candidate_validate_changes(candidate, &changes);
+ nb_config_diff_del_changes(&changes);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return ret;
}
int nb_candidate_commit_prepare(struct nb_config *candidate,
- enum nb_client client, const char *comment,
+ enum nb_client client, const void *user,
+ const char *comment,
struct nb_transaction **transaction)
{
struct nb_config_cbs changes;
}
RB_INIT(nb_config_cbs, &changes);
- nb_config_diff(running_config, candidate, &changes);
- if (RB_EMPTY(nb_config_cbs, &changes))
- return NB_ERR_NO_CHANGES;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ if (RB_EMPTY(nb_config_cbs, &changes)) {
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_NO_CHANGES;
+ }
- if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
- flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
- "%s: failed to validate candidate configuration",
- __func__);
- nb_config_diff_del_changes(&changes);
- return NB_ERR_VALIDATION;
- }
+ if (nb_candidate_validate_changes(candidate, &changes)
+ != NB_OK) {
+ flog_warn(
+ EC_LIB_NB_CANDIDATE_INVALID,
+ "%s: failed to validate candidate configuration",
+ __func__);
+ nb_config_diff_del_changes(&changes);
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_VALIDATION;
+ }
- *transaction = nb_transaction_new(candidate, &changes, client, comment);
- if (*transaction == NULL) {
- flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
- "%s: failed to create transaction", __func__);
- nb_config_diff_del_changes(&changes);
- return NB_ERR_LOCKED;
+ *transaction = nb_transaction_new(candidate, &changes, client,
+ user, comment);
+ if (*transaction == NULL) {
+ flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: failed to create transaction", __func__);
+ nb_config_diff_del_changes(&changes);
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_LOCKED;
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
return nb_transaction_process(NB_EV_PREPARE, *transaction);
}
/* Replace running by candidate. */
transaction->config->version++;
- nb_config_replace(running_config, transaction->config, true);
+ pthread_rwlock_wrlock(&running_config->lock);
+ {
+ nb_config_replace(running_config, transaction->config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
/* Record transaction. */
if (save_transaction
}
int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
- bool save_transaction, const char *comment,
- uint32_t *transaction_id)
+ const void *user, bool save_transaction,
+ const char *comment, uint32_t *transaction_id)
{
struct nb_transaction *transaction = NULL;
int ret;
- ret = nb_candidate_commit_prepare(candidate, client, comment,
+ ret = nb_candidate_commit_prepare(candidate, client, user, comment,
&transaction);
/*
* Apply the changes if the preparation phase succeeded. Otherwise abort
return ret;
}
+int nb_running_lock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked) {
+ running_config_mgmt_lock.locked = true;
+ running_config_mgmt_lock.owner_client = client;
+ running_config_mgmt_lock.owner_user = user;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (running_config_mgmt_lock.locked
+ && running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user) {
+ running_config_mgmt_lock.locked = false;
+ running_config_mgmt_lock.owner_client = NB_CLIENT_NONE;
+ running_config_mgmt_lock.owner_user = NULL;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked
+ || (running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user))
+ ret = 0;
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
static void nb_log_callback(const enum nb_event event,
enum nb_operation operation, const char *xpath,
const char *value)
return nb_node->cbs.rpc(xpath, input, output);
}
-static struct nb_transaction *nb_transaction_new(struct nb_config *config,
- struct nb_config_cbs *changes,
- enum nb_client client,
- const char *comment)
+static struct nb_transaction *
+nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes,
+ enum nb_client client, const void *user, const char *comment)
{
struct nb_transaction *transaction;
+ if (nb_running_lock_check(client, user)) {
+ flog_warn(
+ EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: running configuration is locked by another client",
+ __func__);
+ return NULL;
+ }
+
if (transaction_in_progress) {
flog_warn(
EC_LIB_NB_TRANSACTION_CREATION_FAILED,
{
struct nb_config_cb *cb;
- RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
- struct nb_config_change *change = (struct nb_config_change *)cb;
- int ret;
-
- /*
- * Only try to release resources that were allocated
- * successfully.
- */
- if (event == NB_EV_ABORT && change->prepare_ok == false)
- break;
+ /*
+ * Need to lock the running configuration since transaction->changes
+ * can contain pointers to data nodes from the running configuration.
+ */
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+ struct nb_config_change *change =
+ (struct nb_config_change *)cb;
+ int ret;
- /* Call the appropriate callback. */
- ret = nb_callback_configuration(event, change);
- switch (event) {
- case NB_EV_PREPARE:
- if (ret != NB_OK)
- return ret;
- change->prepare_ok = true;
- break;
- case NB_EV_ABORT:
- case NB_EV_APPLY:
/*
- * At this point it's not possible to reject the
- * transaction anymore, so any failure here can lead to
- * inconsistencies and should be treated as a bug.
- * Operations prone to errors, like validations and
- * resource allocations, should be performed during the
- * 'prepare' phase.
+ * Only try to release resources that were allocated
+ * successfully.
*/
- break;
- default:
- break;
+ if (event == NB_EV_ABORT && change->prepare_ok == false)
+ break;
+
+ /* Call the appropriate callback. */
+ ret = nb_callback_configuration(event, change);
+ switch (event) {
+ case NB_EV_PREPARE:
+ if (ret != NB_OK) {
+ pthread_rwlock_unlock(
+ &running_config->lock);
+ return ret;
+ }
+ change->prepare_ok = true;
+ break;
+ case NB_EV_ABORT:
+ case NB_EV_APPLY:
+ /*
+ * At this point it's not possible to reject the
+ * transaction anymore, so any failure here can
+ * lead to inconsistencies and should be treated
+ * as a bug. Operations prone to errors, like
+ * validations and resource allocations, should
+ * be performed during the 'prepare' phase.
+ */
+ break;
+ default:
+ break;
+ }
}
}
+ pthread_rwlock_unlock(&running_config->lock);
return NB_OK;
}
return "ConfD";
case NB_CLIENT_SYSREPO:
return "Sysrepo";
+ case NB_CLIENT_GRPC:
+ return "gRPC";
default:
return "unknown";
}
running_config_entries = hash_create(running_config_entry_key_make,
running_config_entry_cmp,
"Running Configuration Entries");
+ pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL);
/* Initialize the northbound CLI. */
nb_cli_init(tm);
hash_clean(running_config_entries, running_config_entry_free);
hash_free(running_config_entries);
nb_config_free(running_config);
+ pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
}
/* Northbound clients. */
enum nb_client {
- NB_CLIENT_CLI = 0,
+ NB_CLIENT_NONE = 0,
+ NB_CLIENT_CLI,
NB_CLIENT_CONFD,
NB_CLIENT_SYSREPO,
+ NB_CLIENT_GRPC,
};
/* Northbound configuration. */
struct nb_config {
+ /* Configuration data. */
struct lyd_node *dnode;
+
+ /* Configuration version. */
uint32_t version;
+
+ /*
+ * Lock protecting this structure. The use of this lock is always
+ * necessary when reading or modifying the global running configuration.
+ * For candidate configurations, use of this lock is optional depending
+ * on the threading scheme of the northbound plugin.
+ */
+ pthread_rwlock_t lock;
};
/* Northbound configuration callback. */
* client
* Northbound client performing the commit.
*
+ * user
+ * Northbound user performing the commit (can be NULL).
+ *
* comment
* Optional comment describing the commit.
*
* - NB_ERR for other errors.
*/
extern int nb_candidate_commit_prepare(struct nb_config *candidate,
- enum nb_client client,
+ enum nb_client client, const void *user,
const char *comment,
struct nb_transaction **transaction);
* client
* Northbound client performing the commit.
*
+ * user
+ * Northbound user performing the commit (can be NULL).
+ *
* save_transaction
* Specify whether the transaction should be recorded in the transactions log
* or not.
* - NB_ERR for other errors.
*/
extern int nb_candidate_commit(struct nb_config *candidate,
- enum nb_client client, bool save_transaction,
- const char *comment, uint32_t *transaction_id);
+ enum nb_client client, const void *user,
+ bool save_transaction, const char *comment,
+ uint32_t *transaction_id);
+
+/*
+ * Lock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already locked.
+ */
+extern int nb_running_lock(enum nb_client client, const void *user);
+
+/*
+ * Unlock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already unlocked or
+ * locked by another client/user.
+ */
+extern int nb_running_unlock(enum nb_client client, const void *user);
+
+/*
+ * Check if the running configuration is locked or not for the given
+ * client/user.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 if the running configuration is unlocked or if the client/user owns the
+ * lock, -1 otherwise.
+ */
+extern int nb_running_lock_check(enum nb_client client, const void *user);
/*
- * Iterate over operetional data.
+ * Iterate over operational data.
*
* xpath
* Data path of the YANG data we want to iterate over.
/* Do an implicit "commit" when using the classic CLI mode. */
if (frr_get_cli_mode() == FRR_CLI_CLASSIC) {
ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
- false, NULL, NULL);
+ vty, false, NULL, NULL);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
vty_out(vty, "%% Configuration failed: %s.\n\n",
nb_err_name(ret));
"Please check the logs for more details.\n");
/* Regenerate candidate for consistency. */
- nb_config_replace(vty->candidate_config, running_config,
- true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config,
+ running_config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
return CMD_WARNING_CONFIG_FAILED;
}
}
/* Perform the rollback. */
ret = nb_candidate_commit(
- vty->confirmed_commit_rollback, NB_CLIENT_CLI, true,
+ vty->confirmed_commit_rollback, NB_CLIENT_CLI, vty, true,
"Rollback to previous configuration - confirmed commit has timed out",
&transaction_id);
if (ret == NB_OK)
return CMD_SUCCESS;
}
- if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) {
- vty_out(vty, "%% Configuration is locked by another VTY.\n\n");
- return CMD_WARNING;
- }
-
/* "force" parameter. */
if (!force && nb_candidate_needs_update(vty->candidate_config)) {
vty_out(vty,
/* "confirm" parameter. */
if (confirmed_timeout) {
- vty->confirmed_commit_rollback = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ vty->confirmed_commit_rollback =
+ nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
vty->t_confirmed_commit_timeout = NULL;
thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
&vty->t_confirmed_commit_timeout);
}
- ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true,
- comment, &transaction_id);
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, vty,
+ true, comment, &transaction_id);
/* Map northbound return code to CLI return code. */
switch (ret) {
case NB_OK:
- nb_config_replace(vty->candidate_config_base, running_config,
- true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config_base,
+ running_config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
vty_out(vty,
"%% Configuration committed successfully (Transaction ID #%u).\n\n",
transaction_id);
return CMD_WARNING;
}
- nb_config_replace(vty->candidate_config_base, running_config, true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config_base, running_config,
+ true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
}
}
- nb_cli_show_config(vty, running_config, format, translator,
- !!with_defaults);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_cli_show_config(vty, running_config, format, translator,
+ !!with_defaults);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return CMD_SUCCESS;
}
struct nb_config *config2, *config_transaction2 = NULL;
int ret = CMD_WARNING;
- if (c1_candidate)
- config1 = vty->candidate_config;
- else if (c1_running)
- config1 = running_config;
- else {
- config_transaction1 = nb_db_transaction_load(c1_tid);
- if (!config_transaction1) {
- vty_out(vty, "%% Transaction %u does not exist\n\n",
- (unsigned int)c1_tid);
- goto exit;
+ /*
+ * For simplicity, lock the running configuration regardless if it's
+ * going to be used or not.
+ */
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (c1_candidate)
+ config1 = vty->candidate_config;
+ else if (c1_running)
+ config1 = running_config;
+ else {
+ config_transaction1 = nb_db_transaction_load(c1_tid);
+ if (!config_transaction1) {
+ vty_out(vty,
+ "%% Transaction %u does not exist\n\n",
+ (unsigned int)c1_tid);
+ goto exit;
+ }
+ config1 = config_transaction1;
}
- config1 = config_transaction1;
- }
- if (c2_candidate)
- config2 = vty->candidate_config;
- else if (c2_running)
- config2 = running_config;
- else {
- config_transaction2 = nb_db_transaction_load(c2_tid);
- if (!config_transaction2) {
- vty_out(vty, "%% Transaction %u does not exist\n\n",
- (unsigned int)c2_tid);
- goto exit;
+ if (c2_candidate)
+ config2 = vty->candidate_config;
+ else if (c2_running)
+ config2 = running_config;
+ else {
+ config_transaction2 = nb_db_transaction_load(c2_tid);
+ if (!config_transaction2) {
+ vty_out(vty,
+ "%% Transaction %u does not exist\n\n",
+ (unsigned int)c2_tid);
+ goto exit;
+ }
+ config2 = config_transaction2;
}
- config2 = config_transaction2;
- }
- if (json)
- format = NB_CFG_FMT_JSON;
- else if (xml)
- format = NB_CFG_FMT_XML;
- else
- format = NB_CFG_FMT_CMDS;
+ if (json)
+ format = NB_CFG_FMT_JSON;
+ else if (xml)
+ format = NB_CFG_FMT_XML;
+ else
+ format = NB_CFG_FMT_CMDS;
- if (translator_family) {
- translator = yang_translator_find(translator_family);
- if (!translator) {
- vty_out(vty, "%% Module translator \"%s\" not found\n",
- translator_family);
- goto exit;
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty,
+ "%% Module translator \"%s\" not found\n",
+ translator_family);
+ goto exit;
+ }
}
- }
- ret = nb_cli_show_config_compare(vty, config1, config2, format,
- translator);
-exit:
- if (config_transaction1)
- nb_config_free(config_transaction1);
- if (config_transaction2)
- nb_config_free(config_transaction2);
+ ret = nb_cli_show_config_compare(vty, config1, config2, format,
+ translator);
+ exit:
+ if (config_transaction1)
+ nb_config_free(config_transaction1);
+ if (config_transaction2)
+ nb_config_free(config_transaction2);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return ret;
}
snprintf(comment, sizeof(comment), "Rollback to transaction %u",
transaction_id);
- ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
+ ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, vty, true, comment,
NULL);
nb_config_free(candidate);
switch (ret) {
struct cdb_iter_args iter_args;
int ret;
- candidate = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
/* Iterate over all configuration changes. */
iter_args.candidate = candidate;
*/
transaction = NULL;
ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_CONFD, NULL,
- &transaction);
+ NULL, &transaction);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
enum confd_errcode errcode;
const char *errmsg;
--- /dev/null
+//
+// Copyright (C) 2019 NetDEF, Inc.
+// Renato Westphal
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; see the file COPYING; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#include <zebra.h>
+
+#include "log.h"
+#include "libfrr.h"
+#include "version.h"
+#include "command.h"
+#include "lib_errors.h"
+#include "northbound.h"
+#include "northbound_db.h"
+
+#include <iostream>
+#include <sstream>
+#include <memory>
+#include <string>
+
+#include <grpcpp/grpcpp.h>
+#include "grpc/frr-northbound.grpc.pb.h"
+
+#define GRPC_DEFAULT_PORT 50051
+
+/*
+ * NOTE: we can't use the FRR debugging infrastructure here since it uses
+ * atomics and C++ has a different atomics API. Enable gRPC debugging
+ * unconditionally until we figure out a way to solve this problem.
+ */
+static bool nb_dbg_client_grpc = 1;
+
+static pthread_t grpc_pthread;
+
+class NorthboundImpl final : public frr::Northbound::Service
+{
+ public:
+ NorthboundImpl(void)
+ {
+ _nextCandidateId = 0;
+ }
+
+ ~NorthboundImpl(void)
+ {
+ // Delete candidates.
+ for (auto it = _candidates.begin(); it != _candidates.end();
+ it++)
+ delete_candidate(&it->second);
+ }
+
+ grpc::Status
+ GetCapabilities(grpc::ServerContext *context,
+ frr::GetCapabilitiesRequest const *request,
+ frr::GetCapabilitiesResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC GetCapabilities()");
+
+ // Response: string frr_version = 1;
+ response->set_frr_version(FRR_VERSION);
+
+ // Response: bool rollback_support = 2;
+#ifdef HAVE_CONFIG_ROLLBACKS
+ response->set_rollback_support(true);
+#else
+ response->set_rollback_support(false);
+#endif
+
+ // Response: repeated ModuleData supported_modules = 3;
+ struct yang_module *module;
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ auto m = response->add_supported_modules();
+
+ m->set_name(module->name);
+ if (module->info->rev_size)
+ m->set_revision(module->info->rev[0].date);
+ m->set_organization(module->info->org);
+ }
+
+ // Response: repeated Encoding supported_encodings = 4;
+ response->add_supported_encodings(frr::JSON);
+ response->add_supported_encodings(frr::XML);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Get(grpc::ServerContext *context,
+ frr::GetRequest const *request,
+ grpc::ServerWriter<frr::GetResponse> *writer) override
+ {
+ // Request: DataType type = 1;
+ int type = request->type();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = request->encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = request->with_defaults();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC Get(type: %u, encoding: %u, with_defaults: %u)",
+ type, encoding, with_defaults);
+
+ // Request: repeated string path = 4;
+ auto paths = request->path();
+ for (const std::string &path : paths) {
+ frr::GetResponse response;
+ grpc::Status status;
+
+ // Response: int64 timestamp = 1;
+ response.set_timestamp(time(NULL));
+
+ // Response: DataTree data = 2;
+ auto *data = response.mutable_data();
+ data->set_encoding(request->encoding());
+ status = get_path(data, path, type,
+ encoding2lyd_format(encoding),
+ with_defaults);
+
+ // Something went wrong...
+ if (!status.ok())
+ return status;
+
+ writer->Write(response);
+ }
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Get() end");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ CreateCandidate(grpc::ServerContext *context,
+ frr::CreateCandidateRequest const *request,
+ frr::CreateCandidateResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC CreateCandidate()");
+
+ struct candidate *candidate = create_candidate();
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::RESOURCE_EXHAUSTED,
+ "Can't create candidate configuration");
+
+ // Response: uint32 candidate_id = 1;
+ response->set_candidate_id(candidate->id);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ DeleteCandidate(grpc::ServerContext *context,
+ frr::DeleteCandidateRequest const *request,
+ frr::DeleteCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC DeleteCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ delete_candidate(candidate);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ UpdateCandidate(grpc::ServerContext *context,
+ frr::UpdateCandidateRequest const *request,
+ frr::UpdateCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC UpdateCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "candidate is in the middle of a transaction");
+
+ if (nb_candidate_update(candidate->config) != NB_OK)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "failed to update candidate configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ EditCandidate(grpc::ServerContext *context,
+ frr::EditCandidateRequest const *request,
+ frr::EditCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC EditCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ // Create a copy of the candidate. For consistency, we need to
+ // ensure that either all changes are accepted or none are (in
+ // the event of an error).
+ struct nb_config *candidate_tmp =
+ nb_config_dup(candidate->config);
+
+ auto pvs = request->update();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_edit(candidate_tmp->dnode, pv.path(),
+ pv.value())
+ != 0) {
+ nb_config_free(candidate_tmp);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to update \"" + pv.path()
+ + "\"");
+ }
+ }
+
+ pvs = request->delete_();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_delete(candidate_tmp->dnode, pv.path())
+ != 0) {
+ nb_config_free(candidate_tmp);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to remove \"" + pv.path()
+ + "\"");
+ }
+ }
+
+ // No errors, accept all changes.
+ nb_config_replace(candidate->config, candidate_tmp, false);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ LoadToCandidate(grpc::ServerContext *context,
+ frr::LoadToCandidateRequest const *request,
+ frr::LoadToCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+ // Request: LoadType type = 2;
+ int load_type = request->type();
+ // Request: DataTree config = 3;
+ auto config = request->config();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC LoadToCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ struct lyd_node *dnode = dnode_from_data_tree(&config, true);
+ if (!dnode)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to parse the configuration");
+
+ struct nb_config *loaded_config = nb_config_new(dnode);
+
+ if (load_type == frr::LoadToCandidateRequest::REPLACE)
+ nb_config_replace(candidate->config, loaded_config,
+ false);
+ else if (nb_config_merge(candidate->config, loaded_config,
+ false)
+ != NB_OK)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to merge the loaded configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Commit(grpc::ServerContext *context,
+ frr::CommitRequest const *request,
+ frr::CommitResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+ // Request: Phase phase = 2;
+ int phase = request->phase();
+ // Request: string comment = 3;
+ const std::string comment = request->comment();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Commit(candidate_id: %u)",
+ candidate_id);
+
+ // Find candidate configuration.
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ int ret = NB_OK;
+ uint32_t transaction_id = 0;
+
+ // Check for misuse of the two-phase commit protocol.
+ switch (phase) {
+ case frr::CommitRequest::PREPARE:
+ case frr::CommitRequest::ALL:
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "pending transaction in progress");
+ break;
+ case frr::CommitRequest::ABORT:
+ case frr::CommitRequest::APPLY:
+ if (!candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "no transaction in progress");
+ break;
+ default:
+ break;
+ }
+
+ // Execute the user request.
+ switch (phase) {
+ case frr::CommitRequest::VALIDATE:
+ ret = nb_candidate_validate(candidate->config);
+ break;
+ case frr::CommitRequest::PREPARE:
+ ret = nb_candidate_commit_prepare(
+ candidate->config, NB_CLIENT_GRPC, NULL,
+ comment.c_str(), &candidate->transaction);
+ break;
+ case frr::CommitRequest::ABORT:
+ nb_candidate_commit_abort(candidate->transaction);
+ break;
+ case frr::CommitRequest::APPLY:
+ nb_candidate_commit_apply(candidate->transaction, true,
+ &transaction_id);
+ break;
+ case frr::CommitRequest::ALL:
+ ret = nb_candidate_commit(
+ candidate->config, NB_CLIENT_GRPC, NULL, true,
+ comment.c_str(), &transaction_id);
+ break;
+ }
+
+ // Map northbound error codes to gRPC error codes.
+ switch (ret) {
+ case NB_ERR_NO_CHANGES:
+ return grpc::Status(
+ grpc::StatusCode::ABORTED,
+ "No configuration changes detected");
+ case NB_ERR_LOCKED:
+ return grpc::Status(
+ grpc::StatusCode::UNAVAILABLE,
+ "There's already a transaction in progress");
+ case NB_ERR_VALIDATION:
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Validation error");
+ case NB_ERR_RESOURCE:
+ return grpc::Status(
+ grpc::StatusCode::RESOURCE_EXHAUSTED,
+ "Failed do allocate resources");
+ case NB_ERR:
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Internal error");
+ default:
+ break;
+ }
+
+ // Response: uint32 transaction_id = 1;
+ if (transaction_id)
+ response->set_transaction_id(transaction_id);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ ListTransactions(grpc::ServerContext *context,
+ frr::ListTransactionsRequest const *request,
+ grpc::ServerWriter<frr::ListTransactionsResponse>
+ *writer) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC ListTransactions()");
+
+ nb_db_transactions_iterate(list_transactions_cb, writer);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ GetTransaction(grpc::ServerContext *context,
+ frr::GetTransactionRequest const *request,
+ frr::GetTransactionResponse *response) override
+ {
+ struct nb_config *nb_config;
+
+ // Request: uint32 transaction_id = 1;
+ uint32_t transaction_id = request->transaction_id();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = request->encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = request->with_defaults();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC GetTransaction(transaction_id: %u, encoding: %u)",
+ transaction_id, encoding);
+
+ // Load configuration from the transactions database.
+ nb_config = nb_db_transaction_load(transaction_id);
+ if (!nb_config)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Transaction not found");
+
+ // Response: DataTree config = 1;
+ auto config = response->mutable_config();
+ config->set_encoding(encoding);
+
+ // Dump data using the requested format.
+ if (data_tree_from_dnode(config, nb_config->dnode,
+ encoding2lyd_format(encoding),
+ with_defaults)
+ != 0) {
+ nb_config_free(nb_config);
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+ }
+
+ nb_config_free(nb_config);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status LockConfig(grpc::ServerContext *context,
+ frr::LockConfigRequest const *request,
+ frr::LockConfigResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC LockConfig()");
+
+ if (nb_running_lock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "running configuration is locked already");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status UnlockConfig(grpc::ServerContext *context,
+ frr::UnlockConfigRequest const *request,
+ frr::UnlockConfigResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC UnlockConfig()");
+
+ if (nb_running_unlock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "failed to unlock the running configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Execute(grpc::ServerContext *context,
+ frr::ExecuteRequest const *request,
+ frr::ExecuteResponse *response) override
+ {
+ struct nb_node *nb_node;
+ struct list *input_list;
+ struct list *output_list;
+ struct listnode *node;
+ struct yang_data *data;
+ const char *xpath;
+
+ // Request: string path = 1;
+ xpath = request->path().c_str();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Execute(path: \"%s\")", xpath);
+
+ if (request->path().empty())
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path is empty");
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown data path");
+
+ input_list = yang_data_list_new();
+ output_list = yang_data_list_new();
+
+ // Read input parameters.
+ auto input = request->input();
+ for (const frr::PathValue &pv : input) {
+ // Request: repeated PathValue input = 2;
+ data = yang_data_new(pv.path().c_str(),
+ pv.value().c_str());
+ listnode_add(input_list, data);
+ }
+
+ // Execute callback registered for this XPath.
+ if (nb_node->cbs.rpc(xpath, input_list, output_list) != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_RPC,
+ "%s: rpc callback failed: %s", __func__,
+ xpath);
+ list_delete(&input_list);
+ list_delete(&output_list);
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "RPC failed");
+ }
+
+ // Process output parameters.
+ for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) {
+ // Response: repeated PathValue output = 1;
+ frr::PathValue *pv = response->add_output();
+ pv->set_path(data->xpath);
+ pv->set_value(data->value);
+ }
+
+ // Release memory.
+ list_delete(&input_list);
+ list_delete(&output_list);
+
+ return grpc::Status::OK;
+ }
+
+ private:
+ struct candidate {
+ uint32_t id;
+ struct nb_config *config;
+ struct nb_transaction *transaction;
+ };
+ std::map<uint32_t, struct candidate> _candidates;
+ uint32_t _nextCandidateId;
+
+ static int yang_dnode_edit(struct lyd_node *dnode,
+ const std::string &path,
+ const std::string &value)
+ {
+ ly_errno = LY_SUCCESS;
+ dnode = lyd_new_path(dnode, ly_native_ctx, path.c_str(),
+ (void *)value.c_str(),
+ (LYD_ANYDATA_VALUETYPE)0,
+ LYD_PATH_OPT_UPDATE);
+ if (!dnode && ly_errno != LY_SUCCESS) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ static int yang_dnode_delete(struct lyd_node *dnode,
+ const std::string &path)
+ {
+ dnode = yang_dnode_get(dnode, path.c_str());
+ if (!dnode)
+ return -1;
+
+ lyd_free(dnode);
+
+ return 0;
+ }
+
+ static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding)
+ {
+ switch (encoding) {
+ case frr::JSON:
+ return LYD_JSON;
+ case frr::XML:
+ return LYD_XML;
+ }
+ }
+
+ static int get_oper_data_cb(const struct lys_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+ {
+ struct lyd_node *dnode = static_cast<struct lyd_node *>(arg);
+ int ret = yang_dnode_edit(dnode, data->xpath, data->value);
+ yang_data_free(data);
+
+ return (ret == 0) ? NB_OK : NB_ERR;
+ }
+
+ static void list_transactions_cb(void *arg, int transaction_id,
+ const char *client_name,
+ const char *date, const char *comment)
+ {
+ grpc::ServerWriter<frr::ListTransactionsResponse> *writer =
+ static_cast<grpc::ServerWriter<
+ frr::ListTransactionsResponse> *>(arg);
+ frr::ListTransactionsResponse response;
+
+ // Response: uint32 id = 1;
+ response.set_id(transaction_id);
+
+ // Response: string client = 2;
+ response.set_client(client_name);
+
+ // Response: string date = 3;
+ response.set_date(date);
+
+ // Response: string comment = 4;
+ response.set_comment(comment);
+
+ writer->Write(response);
+ }
+
+ static int data_tree_from_dnode(frr::DataTree *dt,
+ const struct lyd_node *dnode,
+ LYD_FORMAT lyd_format,
+ bool with_defaults)
+ {
+ char *strp;
+ int options = 0;
+
+ SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS);
+ if (with_defaults)
+ SET_FLAG(options, LYP_WD_ALL);
+ else
+ SET_FLAG(options, LYP_WD_TRIM);
+
+ if (lyd_print_mem(&strp, dnode, lyd_format, options) == 0) {
+ if (strp) {
+ dt->set_data(strp);
+ free(strp);
+ }
+ return 0;
+ }
+
+ return -1;
+ }
+
+ static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt,
+ bool config_only)
+ {
+ struct lyd_node *dnode;
+ int options;
+
+ if (config_only)
+ options = LYD_OPT_CONFIG;
+ else
+ options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
+
+ dnode = lyd_parse_mem(ly_native_ctx, dt->data().c_str(),
+ encoding2lyd_format(dt->encoding()),
+ options);
+
+ return dnode;
+ }
+
+ static struct lyd_node *get_dnode_config(const std::string &path)
+ {
+ struct lyd_node *dnode;
+
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ dnode = yang_dnode_get(running_config->dnode,
+ path.empty() ? NULL
+ : path.c_str());
+ if (dnode)
+ dnode = yang_dnode_dup(dnode);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
+ return dnode;
+ }
+
+ static struct lyd_node *get_dnode_state(const std::string &path)
+ {
+ struct lyd_node *dnode;
+
+ dnode = yang_dnode_new(ly_native_ctx, false);
+ if (nb_oper_data_iterate(path.c_str(), NULL, 0,
+ get_oper_data_cb, dnode)
+ != NB_OK) {
+ yang_dnode_free(dnode);
+ return NULL;
+ }
+
+ return dnode;
+ }
+
+ static grpc::Status get_path(frr::DataTree *dt, const std::string &path,
+ int type, LYD_FORMAT lyd_format,
+ bool with_defaults)
+ {
+ struct lyd_node *dnode_config = NULL;
+ struct lyd_node *dnode_state = NULL;
+ struct lyd_node *dnode_final;
+
+ // Configuration data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_CONFIG) {
+ dnode_config = get_dnode_config(path);
+ if (!dnode_config)
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path not found");
+ }
+
+ // Operational data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_STATE) {
+ dnode_state = get_dnode_state(path);
+ if (!dnode_state) {
+ if (dnode_config)
+ yang_dnode_free(dnode_config);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to fetch operational data");
+ }
+ }
+
+ switch (type) {
+ case frr::GetRequest_DataType_ALL:
+ //
+ // Combine configuration and state data into a single
+ // dnode.
+ //
+ if (lyd_merge(dnode_state, dnode_config,
+ LYD_OPT_EXPLICIT)
+ != 0) {
+ yang_dnode_free(dnode_state);
+ yang_dnode_free(dnode_config);
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to merge configuration and state data");
+ }
+
+ dnode_final = dnode_state;
+ break;
+ case frr::GetRequest_DataType_CONFIG:
+ dnode_final = dnode_config;
+ break;
+ case frr::GetRequest_DataType_STATE:
+ dnode_final = dnode_state;
+ break;
+ }
+
+ // Validate data to create implicit default nodes if necessary.
+ int validate_opts = 0;
+ if (type == frr::GetRequest_DataType_CONFIG)
+ validate_opts = LYD_OPT_CONFIG;
+ else
+ validate_opts = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
+ lyd_validate(&dnode_final, validate_opts, ly_native_ctx);
+
+ // Dump data using the requested format.
+ int ret = data_tree_from_dnode(dt, dnode_final, lyd_format,
+ with_defaults);
+ yang_dnode_free(dnode_final);
+ if (ret != 0)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+
+ return grpc::Status::OK;
+ }
+
+ struct candidate *create_candidate(void)
+ {
+ uint32_t candidate_id = ++_nextCandidateId;
+
+ // Check for overflow.
+ // TODO: implement an algorithm for unique reusable IDs.
+ if (candidate_id == 0)
+ return NULL;
+
+ struct candidate *candidate = &_candidates[candidate_id];
+ candidate->id = candidate_id;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate->config = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+ candidate->transaction = NULL;
+
+ return candidate;
+ }
+
+ void delete_candidate(struct candidate *candidate)
+ {
+ _candidates.erase(candidate->id);
+ nb_config_free(candidate->config);
+ if (candidate->transaction)
+ nb_candidate_commit_abort(candidate->transaction);
+ }
+
+ struct candidate *get_candidate(uint32_t candidate_id)
+ {
+ struct candidate *candidate;
+
+ if (_candidates.count(candidate_id) == 0)
+ return NULL;
+
+ return &_candidates[candidate_id];
+ }
+};
+
+static void *grpc_pthread_start(void *arg)
+{
+ unsigned long *port = static_cast<unsigned long *>(arg);
+ NorthboundImpl service;
+ std::stringstream server_address;
+
+ server_address << "0.0.0.0:" << *port;
+
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(server_address.str(),
+ grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+
+ std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+
+ zlog_notice("gRPC server listening on %s",
+ server_address.str().c_str());
+
+ server->Wait();
+
+ return NULL;
+}
+
+static int frr_grpc_init(unsigned long *port)
+{
+ /* Create a pthread for gRPC since it runs its own event loop. */
+ if (pthread_create(&grpc_pthread, NULL, grpc_pthread_start, port)) {
+ flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
+ __func__, safe_strerror(errno));
+ return -1;
+ }
+ pthread_detach(grpc_pthread);
+
+ return 0;
+}
+
+static int frr_grpc_finish(void)
+{
+ // TODO: cancel the gRPC pthreads gracefully.
+
+ return 0;
+}
+
+static int frr_grpc_module_late_init(struct thread_master *tm)
+{
+ static unsigned long port = GRPC_DEFAULT_PORT;
+ const char *args = THIS_MODULE->load_args;
+
+ // Parse port number.
+ if (args) {
+ try {
+ port = std::stoul(args);
+ if (port < 1024)
+ throw std::invalid_argument(
+ "can't use privileged port");
+ if (port > UINT16_MAX)
+ throw std::invalid_argument(
+ "port number is too big");
+ } catch (std::exception &e) {
+ flog_err(EC_LIB_GRPC_INIT,
+ "%s: failed to parse port number: %s",
+ __func__, e.what());
+ goto error;
+ }
+ }
+
+ if (frr_grpc_init(&port) < 0)
+ goto error;
+
+ hook_register(frr_fini, frr_grpc_finish);
+
+ return 0;
+
+error:
+ flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module");
+ return -1;
+}
+
+static int frr_grpc_module_init(void)
+{
+ hook_register(frr_late_init, frr_grpc_module_late_init);
+
+ return 0;
+}
+
+FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION,
+ .description = "FRR gRPC northbound module",
+ .init = frr_grpc_module_init, )
return ret;
}
- candidate = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val,
&sr_new_val))
* single event (SR_EV_ENABLED). This means we need to perform
* the full two-phase commit protocol in one go here.
*/
- ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, true,
- NULL, NULL);
+ ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, NULL,
+ true, NULL, NULL);
} else {
/*
* Validate the configuration changes and allocate all resources
* required to apply them.
*/
ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_SYSREPO,
- NULL, &transaction);
+ NULL, NULL, &transaction);
}
/* Map northbound return code to sysrepo return code. */
return ptr;
}
-unsigned prefix_hash_key(void *pp)
+unsigned prefix_hash_key(const void *pp)
{
struct prefix copy;
extern int prefix_str2mac(const char *str, struct ethaddr *mac);
extern char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size);
-extern unsigned prefix_hash_key(void *pp);
+extern unsigned prefix_hash_key(const void *pp);
extern int str_to_esi(const char *str, esi_t *esi);
extern char *esi_to_str(const esi_t *esi, char *buf, int size);
for (rule = index->match_list.head; rule; rule = next) {
next = rule->next;
if (rule->cmd == cmd) {
+ /* If the configured route-map match rule is exactly
+ * the same as the existing configuration then,
+ * ignore the duplicate configuration.
+ */
+ if (strcmp(match_arg, rule->rule_str) == 0) {
+ if (cmd->func_free)
+ (*cmd->func_free)(compile);
+ return RMAP_COMPILE_SUCCESS;
+ }
+
route_map_rule_delete(&index->match_list, rule);
replaced = 1;
}
break;
case RMAP_EVENT_CALL_ADDED:
case RMAP_EVENT_CALL_DELETED:
+ case RMAP_EVENT_MATCH_ADDED:
+ case RMAP_EVENT_MATCH_DELETED:
upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_RMAP];
break;
case RMAP_EVENT_FILTER_ADDED:
assert(index);
+ /* If "call" is invoked with the same route-map name as
+ * the one previously configured then, ignore the duplicate
+ * configuration.
+ */
+ if (index->nextrm && (strcmp(index->nextrm, rmap) == 0))
+ return CMD_SUCCESS;
+
if (index->nextrm) {
route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED,
index->nextrm, index->map->name);
lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS)
lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c
+#
+# gRPC northbound plugin
+#
+if GRPC
+module_LTLIBRARIES += lib/grpc.la
+endif
+
+lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS)
+lib_grpc_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
+lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS)
+lib_grpc_la_SOURCES = lib/northbound_grpc.cpp
+
#
# CLI utilities
#
CLIPPY_DEPS = $(HOSTTOOLS)lib/clippy $(top_srcdir)/python/clidef.py
-SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h
+SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h .pb.cc .grpc.pb.cc
.c_clippy.c:
@{ test -x $(top_builddir)/$(HOSTTOOLS)lib/clippy || \
$(MAKE) -C $(top_builddir)/$(HOSTTOOLS) lib/clippy; }
static void route_table_free(struct route_table *);
-static bool route_table_hash_cmp(const void *a, const void *b)
+static int route_table_hash_cmp(const void *a, const void *b)
{
const struct prefix *pa = a, *pb = b;
- return prefix_cmp(pa, pb) == 0;
+ return prefix_cmp(pa, pb);
}
+DECLARE_HASH(rn_hash_node, struct route_node, nodehash, route_table_hash_cmp,
+ prefix_hash_key)
/*
* route_table_init_with_delegate
*/
rt = XCALLOC(MTYPE_ROUTE_TABLE, sizeof(struct route_table));
rt->delegate = delegate;
- rt->hash = hash_create(prefix_hash_key, route_table_hash_cmp,
- "route table hash");
+ rn_hash_node_init(&rt->hash);
return rt;
}
static struct route_node *route_node_set(struct route_table *table,
const struct prefix *prefix)
{
- struct route_node *node, *inserted;
+ struct route_node *node;
node = route_node_new(table);
prefix_copy(&node->p, prefix);
node->table = table;
- inserted = hash_get(node->table->hash, node, hash_alloc_intern);
- assert(inserted == node);
+ rn_hash_node_add(&node->table->hash, node);
return node;
}
if (rt == NULL)
return;
- hash_clean(rt->hash, NULL);
- hash_free(rt->hash);
-
node = rt->top;
/* Bulk deletion of nodes remaining in this table. This function is not
tmp_node->table->count--;
tmp_node->lock = 0; /* to cause assert if unlocked after this */
+ rn_hash_node_del(&rt->hash, tmp_node);
route_node_free(rt, tmp_node);
if (node != NULL) {
assert(rt->count == 0);
+ rn_hash_node_fini(&rt->hash);
XFREE(MTYPE_ROUTE_TABLE, rt);
return;
}
prefix_copy(&p, pu.p);
apply_mask(&p);
- node = hash_get(table->hash, (void *)&p, NULL);
+ node = rn_hash_node_find(&table->hash, (void *)&p);
return (node && node->info) ? route_lock_node(node) : NULL;
}
prefix_copy(&p, pu.p);
apply_mask(&p);
- node = hash_get(table->hash, (void *)&p, NULL);
+ node = rn_hash_node_find(&table->hash, (void *)&p);
return node ? route_lock_node(node) : NULL;
}
struct route_node *new;
struct route_node *node;
struct route_node *match;
- struct route_node *inserted;
uint16_t prefixlen = p->prefixlen;
const uint8_t *prefix = &p->u.prefix;
apply_mask((struct prefix *)p);
- node = hash_get(table->hash, (void *)p, NULL);
+ node = rn_hash_node_find(&table->hash, (void *)p);
if (node && node->info)
return route_lock_node(node);
new->p.family = p->family;
new->table = table;
set_link(new, node);
- inserted = hash_get(node->table->hash, new, hash_alloc_intern);
- assert(inserted == new);
+ rn_hash_node_add(&table->hash, new);
if (match)
set_link(match, new);
node->table->count--;
- hash_release(node->table->hash, node);
+ rn_hash_node_del(&node->table->hash, node);
/* WARNING: FRAGILE CODE!
* route_node_free may have the side effect of free'ing the entire
#include "memory.h"
#include "hash.h"
#include "prefix.h"
+#include "typesafe.h"
#ifdef __cplusplus
extern "C" {
route_table_destroy_node_func_t destroy_node;
};
+PREDECL_HASH(rn_hash_node)
+
/* Routing table top structure. */
struct route_table {
struct route_node *top;
- struct hash *hash;
+ struct rn_hash_node_head hash;
/*
* Delegate that performs certain functions for this table.
/* Lock of this radix */ \
unsigned int table_rdonly(lock); \
\
+ struct rn_hash_node_item nodehash; \
/* Each node of route. */ \
void *info; \
#define DECLARE_SORTLIST_UNIQ(prefix, type, field, cmpfn) \
_DECLARE_SORTLIST(prefix, type, field, cmpfn, cmpfn) \
\
-macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \
+macro_inline type *prefix ## _find(const struct prefix##_head *h, const type *item) \
{ \
struct ssort_item *sitem = h->sh.first; \
int cmpval = 0; \
*np = &item->field.hi; \
return NULL; \
} \
-macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \
+macro_inline type *prefix ## _find(const struct prefix##_head *h, const type *item) \
{ \
if (!h->hh.tabshift) \
return NULL; \
return cmpfn(container_of(a, type, field.si), \
container_of(b, type, field.si)); \
} \
-macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \
+macro_inline type *prefix ## _find(const struct prefix##_head *h, const type *item) \
{ \
struct sskip_item *sitem = typesafe_skiplist_find(&h->sh, \
&item->field.si, &prefix ## __cmp); \
/* Current directory. */
char *vty_cwd = NULL;
-/* Exclusive configuration lock. */
-struct vty *vty_exclusive_lock;
-
/* Login password check. */
static int no_password_check = 0;
if (config == NULL && vty->candidate_config
&& frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
- true, "Read configuration file",
+ vty, true, "Read configuration file",
NULL);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
zlog_err("%s: failed to read configuration file.",
int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
{
- if (exclusive && !vty_config_exclusive_lock(vty)) {
- vty_out(vty, "VTY configuration is locked by other VTY\n");
+ if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) {
+ vty_out(vty, "%% Configuration is locked by other client\n");
return CMD_WARNING;
}
vty->private_config = private_config;
vty->xpath_index = 0;
- if (private_config) {
- vty->candidate_config = nb_config_dup(running_config);
- vty->candidate_config_base = nb_config_dup(running_config);
- vty_out(vty,
- "Warning: uncommitted changes will be discarded on exit.\n\n");
- } else {
- vty->candidate_config = vty_shared_candidate_config;
- if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (private_config) {
+ vty->candidate_config = nb_config_dup(running_config);
vty->candidate_config_base =
nb_config_dup(running_config);
+ vty_out(vty,
+ "Warning: uncommitted changes will be discarded on exit.\n\n");
+ } else {
+ vty->candidate_config = vty_shared_candidate_config;
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ vty->candidate_config_base =
+ nb_config_dup(running_config);
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
return CMD_SUCCESS;
}
nb_cli_confirmed_commit_clean(vty);
}
- vty_config_exclusive_unlock(vty);
+ (void)nb_running_unlock(NB_CLIENT_CLI, vty);
if (vty->candidate_config) {
if (vty->private_config)
vty->config = false;
}
-int vty_config_exclusive_lock(struct vty *vty)
-{
- if (vty_exclusive_lock == NULL) {
- vty_exclusive_lock = vty;
- return 1;
- }
- return 0;
-}
-
-void vty_config_exclusive_unlock(struct vty *vty)
-{
- if (vty_exclusive_lock == vty)
- vty_exclusive_lock = NULL;
-}
-
/* Master of the threads. */
static struct thread_master *vty_master;
#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP)
#endif
-/* Exported variables */
-extern struct vty *vty_exclusive_lock;
-
/* Prototypes. */
extern void vty_init(struct thread_master *);
extern void vty_init_vtysh(void);
extern int vty_config_enter(struct vty *vty, bool private_config,
bool exclusive);
extern void vty_config_exit(struct vty *);
-extern int vty_config_exclusive_lock(struct vty *vty);
-extern void vty_config_exclusive_unlock(struct vty *vty);
extern int vty_shell(struct vty *);
extern int vty_shell_serv(struct vty *);
extern void vty_hello(struct vty *);
zlog(priority, "libyang: %s", msg);
}
-#if CONFDATE > 20190401
-CPP_NOTICE("lib/yang: time to remove non-LIBYANG_EXT_BUILTIN support")
-#endif
-
-#ifdef LIBYANG_EXT_BUILTIN
-extern struct lytype_plugin_list frr_user_types[];
-#endif
-
struct ly_ctx *yang_ctx_new_setup(void)
{
struct ly_ctx *ctx;
void yang_init(void)
{
-#ifndef LIBYANG_EXT_BUILTIN
-CPP_NOTICE("lib/yang: deprecated libyang <0.16.74 extension loading in use!")
- static char ly_plugin_dir[PATH_MAX];
- const char *const *ly_loaded_plugins;
- const char *ly_plugin;
- bool found_ly_frr_types = false;
-
- /* Tell libyang where to find its plugins. */
- snprintf(ly_plugin_dir, sizeof(ly_plugin_dir), "%s=%s",
- "LIBYANG_USER_TYPES_PLUGINS_DIR", LIBYANG_PLUGINS_PATH);
- putenv(ly_plugin_dir);
-#endif
-
/* Initialize libyang global parameters that affect all containers. */
ly_set_log_clb(ly_log_cb, 1);
ly_log_options(LY_LOLOG | LY_LOSTORE);
-#ifdef LIBYANG_EXT_BUILTIN
- if (ly_register_types(frr_user_types, "frr_user_types")) {
- flog_err(EC_LIB_LIBYANG_PLUGIN_LOAD,
- "ly_register_types() failed");
- exit(1);
- }
-#endif
-
/* Initialize libyang container for native models. */
ly_native_ctx = yang_ctx_new_setup();
if (!ly_native_ctx) {
exit(1);
}
-#ifndef LIBYANG_EXT_BUILTIN
- /* Detect if the required libyang plugin(s) were loaded successfully. */
- ly_loaded_plugins = ly_get_loaded_plugins();
- for (size_t i = 0; (ly_plugin = ly_loaded_plugins[i]); i++) {
- if (strmatch(ly_plugin, "frr_user_types")) {
- found_ly_frr_types = true;
- break;
- }
- }
- if (!found_ly_frr_types) {
- flog_err(EC_LIB_LIBYANG_PLUGIN_LOAD,
- "%s: failed to load frr_user_types.so", __func__);
- exit(1);
- }
-#endif
-
yang_translator_init();
}
#include "hash.h"
#include "yang.h"
#include "yang_translator.h"
+#include "frrstr.h"
DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator")
DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module")
static unsigned int
yang_translator_validate(struct yang_translator *translator);
static unsigned int yang_module_nodes_count(const struct lys_module *module);
-static void str_replace(char *o_string, const char *s_string,
- const char *r_string);
struct yang_mapping_node {
char xpath_from_canonical[XPATH_MAXLEN];
sizeof(mapping->xpath_from_fmt));
strlcpy(mapping->xpath_to_fmt, xpath_to_fmt,
sizeof(mapping->xpath_to_fmt));
- str_replace(mapping->xpath_from_fmt, "KEY1", "%[^']");
- str_replace(mapping->xpath_from_fmt, "KEY2", "%[^']");
- str_replace(mapping->xpath_from_fmt, "KEY3", "%[^']");
- str_replace(mapping->xpath_from_fmt, "KEY4", "%[^']");
- str_replace(mapping->xpath_to_fmt, "KEY1", "%s");
- str_replace(mapping->xpath_to_fmt, "KEY2", "%s");
- str_replace(mapping->xpath_to_fmt, "KEY3", "%s");
- str_replace(mapping->xpath_to_fmt, "KEY4", "%s");
+
+ const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"};
+ char *xpfmt;
+
+ for (unsigned int i = 0; i < array_size(keys); i++) {
+ xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i],
+ "%[^']");
+ strlcpy(mapping->xpath_from_fmt, xpfmt,
+ sizeof(mapping->xpath_from_fmt));
+ XFREE(MTYPE_TMP, xpfmt);
+ }
+
+ for (unsigned int i = 0; i < array_size(keys); i++) {
+ xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s");
+ strlcpy(mapping->xpath_to_fmt, xpfmt,
+ sizeof(mapping->xpath_to_fmt));
+ XFREE(MTYPE_TMP, xpfmt);
+ }
}
struct yang_translator *yang_translator_load(const char *path)
return total;
}
-/* TODO: rewrite this function. */
-static void str_replace(char *o_string, const char *s_string,
- const char *r_string)
-{
- char buffer[BUFSIZ];
- char *ch;
-
- ch = strstr(o_string, s_string);
- if (!ch)
- return;
-
- memcpy(buffer, o_string, ch - o_string);
- buffer[ch - o_string] = 0;
-
- sprintf(buffer + (ch - o_string), "%s%s", r_string,
- ch + strlen(s_string));
-
- o_string[0] = 0;
- strcpy(o_string, buffer);
- return str_replace(o_string, s_string, r_string);
-}
-
void yang_translator_init(void)
{
ly_translator_ctx = yang_ctx_new_setup();
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
- memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+ (void)inet_pton(AF_INET, dleaf->value_str, addr);
}
void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...)
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
- memcpy(prefix4, dleaf->value.ptr, sizeof(*prefix4));
+ (void)str2prefix_ipv4(dleaf->value_str, prefix4);
}
void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...)
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
- memcpy(addr, dleaf->value.ptr, sizeof(*addr));
+ (void)inet_pton(AF_INET6, dleaf->value_str, addr);
}
void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...)
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
- memcpy(prefix6, dleaf->value.ptr, sizeof(*prefix6));
+ (void)str2prefix_ipv6(dleaf->value_str, prefix6);
}
void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...)
return;
}
-static void set_linkparams_link_id(struct ospf_interface *oi,
- struct mpls_te_link *lp)
+static void set_linkparams_link_id(struct mpls_te_link *lp,
+ struct in_addr link_id)
{
- struct ospf_neighbor *nbr;
- int done = 0;
lp->link_id.header.type = htons(TE_LINK_SUBTLV_LINK_ID);
lp->link_id.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE);
-
- /*
- * The Link ID is identical to the contents of the Link ID field
- * in the Router LSA for these link types.
- */
- switch (oi->type) {
- case OSPF_IFTYPE_POINTOPOINT:
- /* Take the router ID of the neighbor. */
- if ((nbr = ospf_nbr_lookup_ptop(oi))
- && nbr->state == NSM_Full) {
- lp->link_id.value = nbr->router_id;
- done = 1;
- }
- break;
- case OSPF_IFTYPE_BROADCAST:
- case OSPF_IFTYPE_NBMA:
- /* Take the interface address of the designated router. */
- if ((nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi))) == NULL)
- break;
-
- if (nbr->state == NSM_Full
- || (IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi))
- && ospf_nbr_count(oi, NSM_Full) > 0)) {
- lp->link_id.value = DR(oi);
- done = 1;
- }
- break;
- default:
- /* Not supported yet. */ /* XXX */
- lp->link_id.header.type = htons(0);
- break;
- }
-
- if (!done) {
- struct in_addr mask;
- masklen2ip(oi->address->prefixlen, &mask);
- lp->link_id.value.s_addr =
- oi->address->u.prefix4.s_addr & mask.s_addr;
- }
+ lp->link_id.value = link_id;
return;
}
return;
}
+/*
+ * Just add interface and set available information. Other information
+ * and flooding of LSA will be done later when adjacency will be up
+ * See ospf_mpls_te_nsm_change() after
+ */
static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_state)
{
- struct te_link_subtlv_link_type old_type;
- struct te_link_subtlv_link_id old_id;
+
struct mpls_te_link *lp;
- if ((lp = lookup_linkparams_by_ifp(oi->ifp)) == NULL) {
+ lp = lookup_linkparams_by_ifp(oi->ifp);
+ if (lp == NULL) {
flog_warn(
EC_OSPF_TE_UNEXPECTED,
- "ospf_mpls_te_ism_change: Cannot get linkparams from OI(%s)?",
- IF_NAME(oi));
+ "MPLS-TE (%s): Cannot get linkparams from OI(%s)?",
+ __func__, IF_NAME(oi));
return;
}
if (oi->area == NULL || oi->area->ospf == NULL) {
flog_warn(
EC_OSPF_TE_UNEXPECTED,
- "ospf_mpls_te_ism_change: Cannot refer to OSPF from OI(%s)?",
- IF_NAME(oi));
+ "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?",
+ __func__, IF_NAME(oi));
return;
}
-#ifdef notyet
- if ((lp->area != NULL
- && !IPV4_ADDR_SAME(&lp->area->area_id, &oi->area->area_id))
- || (lp->area != NULL && oi->area == NULL)) {
- /* How should we consider this case? */
- flog_warn(
- EC_OSPF_TE_UNEXPECTED,
- "MPLS-TE: Area for OI(%s) has changed to [%s], flush previous LSAs",
- IF_NAME(oi),
- oi->area ? inet_ntoa(oi->area->area_id) : "N/A");
- ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA);
- }
-#endif
+
/* Keep Area information in combination with linkparams. */
lp->area = oi->area;
case ISM_DROther:
case ISM_Backup:
case ISM_DR:
- old_type = lp->link_type;
- old_id = lp->link_id;
-
- /* Set Link type, Link ID, Local and Remote IP addr */
+ /* Set Link type and Local IP addr */
set_linkparams_link_type(oi, lp);
- set_linkparams_link_id(oi, lp);
set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4);
- if (oi->type == LINK_TYPE_SUBTLV_VALUE_PTP) {
- struct prefix *pref = CONNECTED_PREFIX(oi->connected);
- if (pref != NULL)
- set_linkparams_rmtif_ipaddr(lp,
- pref->u.prefix4);
- }
-
- /* Update TE parameters */
- update_linkparams(lp);
-
- /* Try to Schedule LSA */
- if ((ntohs(old_type.header.type)
- != ntohs(lp->link_type.header.type)
- || old_type.link_type.value
- != lp->link_type.link_type.value)
- || (ntohs(old_id.header.type)
- != ntohs(lp->link_id.header.type)
- || ntohl(old_id.value.s_addr)
- != ntohl(lp->link_id.value.s_addr))) {
- if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED))
- ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA);
- else
- ospf_mpls_te_lsa_schedule(lp,
- REORIGINATE_THIS_LSA);
- }
break;
default:
- lp->link_type.header.type = htons(0);
- lp->link_id.header.type = htons(0);
-
+ /* State is undefined: Flush LSA if engaged */
if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED))
ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA);
break;
}
+ if (IS_DEBUG_OSPF_TE)
+ zlog_debug(
+ "MPLS-TE(%s): Update Link parameters for interface %s",
+ __func__, IF_NAME(oi));
+
return;
}
+/*
+ * Complete TE info and schedule LSA flooding
+ * Link-ID and Remote IP address must be set with neighbor info
+ * which are only valid once NSM state is FULL
+ */
static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_state)
{
- /* Nothing to do here */
+ struct ospf_interface *oi = nbr->oi;
+ struct mpls_te_link *lp;
+
+ /* Process Neighbor only when its state is NSM Full */
+ if (nbr->state != NSM_Full)
+ return;
+
+ /* Get interface information for Traffic Engineering */
+ lp = lookup_linkparams_by_ifp(oi->ifp);
+ if (lp == NULL) {
+ flog_warn(
+ EC_OSPF_TE_UNEXPECTED,
+ "MPLS-TE (%s): Cannot get linkparams from OI(%s)?",
+ __func__, IF_NAME(oi));
+ return;
+ }
+
+ if (oi->area == NULL || oi->area->ospf == NULL) {
+ flog_warn(
+ EC_OSPF_TE_UNEXPECTED,
+ "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?",
+ __func__, IF_NAME(oi));
+ return;
+ }
+
+ /* Keep Area information in combination with SR info. */
+ lp->area = oi->area;
+
+ /* Keep interface MPLS-TE status */
+ lp->flags = HAS_LINK_PARAMS(oi->ifp);
+
+ /*
+ * The Link ID is identical to the contents of the Link ID field
+ * in the Router LSA for these link types.
+ */
+ switch (oi->state) {
+ case ISM_PointToPoint:
+ /* Set Link ID with neighbor Router ID */
+ set_linkparams_link_id(lp, nbr->router_id);
+ /* Set Remote IP address */
+ set_linkparams_rmtif_ipaddr(lp, nbr->address.u.prefix4);
+ break;
+
+ case ISM_DR:
+ case ISM_DROther:
+ case ISM_Backup:
+ /* Set Link ID with the Designated Router ID */
+ set_linkparams_link_id(lp, DR(oi));
+ break;
+
+ default:
+ /* State is undefined: Flush LSA if engaged */
+ if (OspfMplsTE.enabled &&
+ CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED))
+ ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA);
+ return;
+ }
+
+ if (IS_DEBUG_OSPF_TE)
+ zlog_debug(
+ "MPLS-TE (%s): Add Link-ID %s for interface %s ",
+ __func__, inet_ntoa(lp->link_id.value), oi->ifp->name);
+
+ /* Try to Schedule LSA */
+ if (OspfMplsTE.enabled) {
+ if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED))
+ ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA);
+ else
+ ospf_mpls_te_lsa_schedule(lp, REORIGINATE_THIS_LSA);
+ }
return;
}
print_header = 1;
for (ALL_LIST_ELEMENTS_RO(pim->upstream_list, upnode,
up)) {
+ if (!up->rpf.source_nexthop.interface)
+ continue;
if (strcmp(ifp->name,
up->rpf.source_nexthop
if (ch->upstream->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP)
mask = PIM_OIF_FLAG_PROTO_IGMP;
- /* SGRpt entry could have empty oil */
- pim_channel_del_oif(ch->upstream->channel_oil, ch->interface,
- mask);
+ /*
+ * A S,G RPT channel can have an empty oil, we also
+ * need to take into account the fact that a ifchannel
+ * might have been suppressing a *,G ifchannel from
+ * being inherited. So let's figure out what
+ * needs to be done here
+ */
+ if (pim_upstream_evaluate_join_desired_interface(
+ ch->upstream, ch, ch->parent))
+ pim_channel_add_oif(ch->upstream->channel_oil,
+ ch->interface, mask);
+ else
+ pim_channel_del_oif(ch->upstream->channel_oil,
+ ch->interface, mask);
/*
* Do we have any S,G's that are inheriting?
* Nuke from on high too.
"kat expired on %s[%s]; remove stream reference",
up->sg_str, pim->vrf->name);
PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(up->flags);
- up = pim_upstream_del(pim, up, __PRETTY_FUNCTION__);
- } else if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) {
+
+ /* Return if upstream entry got deleted.*/
+ if (!pim_upstream_del(pim, up, __PRETTY_FUNCTION__))
+ return NULL;
+ }
+ /* upstream reference would have been added to track the local
+ * membership if it is LHR. We have to clear it when KAT expires.
+ * Otherwise would result in stale entry with uncleared ref count.
+ */
+ if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) {
struct pim_upstream *parent = up->parent;
PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(up->flags);
install_it, up->channel_oil->installed);
}
- pim_channel_del_oif(up->channel_oil, ch->interface,
- PIM_OIF_FLAG_PROTO_PIM);
+ /*
+ * If a channel is being removed, check to see if we still need
+ * to inherit the interface. If so make sure it is added in
+ */
+ if (pim_upstream_evaluate_join_desired_interface(up, ch, ch->parent))
+ pim_channel_add_oif(up->channel_oil, ch->interface,
+ PIM_OIF_FLAG_PROTO_PIM);
+ else
+ pim_channel_del_oif(up->channel_oil, ch->interface,
+ PIM_OIF_FLAG_PROTO_PIM);
if (install_it && !up->channel_oil->installed)
pim_mroute_add(up->channel_oil, __PRETTY_FUNCTION__);
r1-eth0 is up
- ifindex 2, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
+ ifindex X, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
Internet Address 192.168.0.1/24, Broadcast 192.168.0.255, Area 0.0.0.0
MTU mismatch detection: enabled
Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10
Hello due in XX.XXXs
Neighbor Count is 0, Adjacent neighbor count is 0
r1-eth3 is up
- ifindex 5, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
+ ifindex X, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
Internet Address 192.168.3.1/26, Broadcast 192.168.3.63, Area 0.0.0.0
MTU mismatch detection: enabled
Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10
actual = net['r%s' % i].cmd('vtysh -c "show ip ospf interface" 2> /dev/null').rstrip()
# Mask out Bandwidth portion. They may change..
actual = re.sub(r"BW [0-9]+ Mbit", "BW XX Mbit", actual)
+ actual = re.sub(r"ifindex [0-9]", "ifindex X", actual)
+
# Drop time in next due
actual = re.sub(r"Hello due in [0-9\.]+s", "Hello due in XX.XXXs", actual)
# Fix 'MTU mismatch detection: enabled' vs 'MTU mismatch detection:enabled' - accept both
"Please, only use 32 bit atomics.\n" . $herecurr);
}
+# check for use of strcpy()
+ if ($line =~ /\bstrcpy\s*\(.*\)/) {
+ ERROR("STRCPY",
+ "strcpy() is error-prone; please use strlcpy()" . $herecurr);
+ }
+
+# check for use of strncpy()
+ if ($line =~ /\bstrncpy\s*\(.*\)/) {
+ WARN("STRNCPY",
+ "strncpy() is error-prone; please use strlcpy() if possible, or memcpy()" . $herecurr);
+ }
+
+# check for use of strcat()
+ if ($line =~ /\bstrcat\s*\(.*\)/) {
+ ERROR("STRCAT",
+ "strcat() is error-prone; please use strlcat() if possible" . $herecurr);
+ }
+
+# check for use of strncat()
+ if ($line =~ /\bstrncat\s*\(.*\)/) {
+ WARN("STRNCAT",
+ "strncat() is error-prone; please use strlcat() if possible" . $herecurr);
+ }
+
+# check for use of bzero()
+ if ($line =~ /\bbzero\s*\(.*\)/) {
+ ERROR("BZERO",
+ "bzero() is deprecated; use memset()" . $herecurr);
+ }
}
# If we have no input at all, then there is nothing to report on
vtysh_execute("exit");
} else if (tried) {
vtysh_execute("end");
- vtysh_execute("configure terminal");
+ vtysh_execute("configure");
}
}
/*
if (pager && strncmp(line, "exit", 4))
vty_open_pager(vty);
- if (!strcmp(cmd->string, "configure terminal")) {
+ if (!strcmp(cmd->string, "configure")) {
for (i = 0; i < array_size(vtysh_client); i++) {
cmd_stat = vtysh_client_execute(
&vtysh_client[i], line);
vty->node = CONFIG_NODE;
vtysh_execute_no_pager("enable");
- vtysh_execute_no_pager("configure terminal");
+ vtysh_execute_no_pager("configure");
vty_buf_copy = XCALLOC(MTYPE_VTYSH_CMD, VTY_BUFSIZ);
while (fgets(vty->buf, VTY_BUFSIZ, confp)) {
}
DEFUNSH(VTYSH_REALLYALL, vtysh_config_terminal, vtysh_config_terminal_cmd,
- "configure terminal",
+ "configure [terminal]",
"Configuration from vty interface\n"
"Configuration terminal\n")
{
case BFD_NODE:
case RPKI_NODE:
vtysh_execute("end");
- vtysh_execute("configure terminal");
+ vtysh_execute("configure");
vty->node = CONFIG_NODE;
break;
case BGP_VPNV4_NODE:
# libyang user types
#
-if LIBYANG_EXT_BUILTIN
-lib_libfrr_la_SOURCES += yang/libyang_plugins/frr_user_types.c
-else
-libyang_plugins_LTLIBRARIES += yang/libyang_plugins/frr_user_types.la
-endif
-
-yang_libyang_plugins_frr_user_types_la_CFLAGS = $(WERROR) $(LIBYANG_CFLAGS)
-yang_libyang_plugins_frr_user_types_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
-yang_libyang_plugins_frr_user_types_la_LIBADD =
-yang_libyang_plugins_frr_user_types_la_SOURCES = yang/libyang_plugins/frr_user_types.c
+# XXX: disable support for libyang custom user types temporarily to facilitate
+# the transition from libyang 0.x to libyang 1.x.
+#lib_libfrr_la_SOURCES += yang/libyang_plugins/frr_user_types.c
#include "zebra.h"
#include "hook.h"
+#include "typesafe.h"
#include "linklist.h"
#include "prefix.h"
#include "table.h"
extern "C" {
#endif
+typedef enum { RNH_NEXTHOP_TYPE, RNH_IMPORT_CHECK_TYPE } rnh_type_t;
+
+PREDECL_LIST(rnh_list)
+
+/* Nexthop structure. */
+struct rnh {
+ uint8_t flags;
+
+#define ZEBRA_NHT_CONNECTED 0x1
+#define ZEBRA_NHT_DELETED 0x2
+#define ZEBRA_NHT_EXACT_MATCH 0x4
+
+ /* VRF identifier. */
+ vrf_id_t vrf_id;
+
+ afi_t afi;
+
+ rnh_type_t type;
+
+ uint32_t seqno;
+
+ struct route_entry *state;
+ struct prefix resolved_route;
+ struct list *client_list;
+
+ /* pseudowires dependent on this nh */
+ struct list *zebra_pseudowire_list;
+
+ struct route_node *node;
+
+ /*
+ * if this has been filtered for the client
+ */
+ int filtered[ZEBRA_ROUTE_MAX];
+
+ struct rnh_list_item rnh_list_item;
+};
+
#define DISTANCE_INFINITY 255
#define ZEBRA_KERNEL_TABLE_MAX 252 /* support for no more than this rt tables */
+PREDECL_LIST(re_list)
+
struct route_entry {
/* Link list. */
- struct route_entry *next;
- struct route_entry *prev;
+ struct re_list_item next;
/* Nexthop structure */
struct nexthop_group ng;
/*
* Doubly-linked list of routes for this prefix.
*/
- struct route_entry *routes;
+ struct re_list_head routes;
struct route_entry *selected_fib;
* the data plane we will run evaluate_rnh
* on these prefixes.
*/
- struct list *nht;
+ struct rnh_list_head nht;
/*
* Linkage to put dest on the FPM processing queue.
} rib_dest_t;
+DECLARE_LIST(rnh_list, struct rnh, rnh_list_item);
+DECLARE_LIST(re_list, struct route_entry, next);
+
#define RIB_ROUTE_QUEUED(x) (1 << (x))
// If MQ_SIZE is modified this value needs to be updated.
#define RIB_ROUTE_ANY_QUEUED 0x1F
* Macro to iterate over each route for a destination (prefix).
*/
#define RE_DEST_FOREACH_ROUTE(dest, re) \
- for ((re) = (dest) ? (dest)->routes : NULL; (re); (re) = (re)->next)
+ for ((re) = (dest) ? re_list_first(&((dest)->routes)) : NULL; (re); \
+ (re) = re_list_next(&((dest)->routes), (re)))
/*
* Same as above, but allows the current node to be unlinked.
*/
#define RE_DEST_FOREACH_ROUTE_SAFE(dest, re, next) \
- for ((re) = (dest) ? (dest)->routes : NULL; \
- (re) && ((next) = (re)->next, 1); (re) = (next))
+ for ((re) = (dest) ? re_list_first(&((dest)->routes)) : NULL; \
+ (re) && ((next) = re_list_next(&((dest)->routes), (re)), 1); \
+ (re) = (next))
#define RNODE_FOREACH_RE(rn, re) \
RE_DEST_FOREACH_ROUTE (rib_dest_from_rnode(rn), re)
if (!dest)
return NULL;
- return dest->routes;
+ return re_list_first(&dest->routes);
}
/*
*/
static int rib_can_delete_dest(rib_dest_t *dest)
{
- if (dest->routes) {
+ if (re_list_first(&dest->routes)) {
return 0;
}
void zebra_rib_evaluate_rn_nexthops(struct route_node *rn, uint32_t seq)
{
rib_dest_t *dest = rib_dest_from_rnode(rn);
- struct listnode *node, *nnode;
struct rnh *rnh;
/*
* nht resolution and as such we need to call the
* nexthop tracking evaluation code
*/
- for (ALL_LIST_ELEMENTS(dest->nht, node, nnode, rnh)) {
+ for_each (rnh_list, &dest->nht, rnh) {
struct zebra_vrf *zvrf =
zebra_vrf_lookup_by_id(rnh->vrf_id);
struct prefix *p = &rnh->node->p;
zebra_rib_evaluate_rn_nexthops(rn, zebra_router_get_next_sequence());
dest->rnode = NULL;
- list_delete(&dest->nht);
+ rnh_list_fini(&dest->nht);
XFREE(MTYPE_RIB_DEST, dest);
rn->info = NULL;
rib_dest_t *dest;
dest = XCALLOC(MTYPE_RIB_DEST, sizeof(rib_dest_t));
- dest->nht = list_new();
+ rnh_list_init(&dest->nht);
route_lock_node(rn); /* rn route table reference */
rn->info = dest;
dest->rnode = rn;
/* Add RE to head of the route node. */
static void rib_link(struct route_node *rn, struct route_entry *re, int process)
{
- struct route_entry *head;
rib_dest_t *dest;
afi_t afi;
const char *rmap_name;
dest = zebra_rib_create_dest(rn);
}
- head = dest->routes;
- if (head) {
- head->prev = re;
- }
- re->next = head;
- dest->routes = re;
+ re_list_add_head(&dest->routes, re);
afi = (rn->p.family == AF_INET)
? AFI_IP
dest = rib_dest_from_rnode(rn);
- if (re->next)
- re->next->prev = re->prev;
-
- if (re->prev)
- re->prev->next = re->next;
- else {
- dest->routes = re->next;
- }
+ re_list_del(&dest->routes, re);
if (dest->selected_fib == re)
dest->selected_fib = NULL;
}
dest = rib_dest_from_rnode(rn);
- listnode_delete(dest->nht, rnh);
+ rnh_list_del(&dest->nht, rnh);
route_unlock_node(rn);
}
}
dest = rib_dest_from_rnode(rn);
- listnode_add(dest->nht, rnh);
+ rnh_list_add_tail(&dest->nht, rnh);
route_unlock_node(rn);
}
route_unlock_node(rern);
dest = rib_dest_from_rnode(rern);
- listnode_delete(dest->nht, rnh);
+ rnh_list_del(&dest->nht, rnh);
}
}
free_state(rnh->vrf_id, rnh->state, rnh->node);
extern "C" {
#endif
-typedef enum { RNH_NEXTHOP_TYPE, RNH_IMPORT_CHECK_TYPE } rnh_type_t;
-
-/* Nexthop structure. */
-struct rnh {
- uint8_t flags;
-
-#define ZEBRA_NHT_CONNECTED 0x1
-#define ZEBRA_NHT_DELETED 0x2
-#define ZEBRA_NHT_EXACT_MATCH 0x4
-
- /* VRF identifier. */
- vrf_id_t vrf_id;
-
- afi_t afi;
-
- rnh_type_t type;
-
- uint32_t seqno;
-
- struct route_entry *state;
- struct prefix resolved_route;
- struct list *client_list;
-
- /* pseudowires dependent on this nh */
- struct list *zebra_pseudowire_list;
-
- struct route_node *node;
-
- /*
- * if this has been filtered for the client
- */
- int filtered[ZEBRA_ROUTE_MAX];
-};
-
extern int zebra_rnh_ip_default_route;
extern int zebra_rnh_ipv6_default_route;
if (node->info) {
rib_dest_t *dest = node->info;
- list_delete(&dest->nht);
+ rnh_list_fini(&dest->nht);
XFREE(MTYPE_RIB_DEST, node->info);
}
}