Compare commits

...

123 Commits

Author SHA1 Message Date
Kostiantyn Buravchenko
fa325f283a Merge branch 'review-fixes' into 'larch_platform_github'
Review fixes for uCentral upstream contribution

See merge request openlan/ols-ucentral-client!39
2024-10-04 11:58:43 +00:00
Viacheslav Holovetskyi
ff381be8b5 Remove requirements from the "requires" and "after" manifest lists, as those ..
.. requirements are not crossplatform across different platforms
2024-10-02 15:53:21 +03:00
Viacheslav Holovetskyi
e3ae4b0324 Proper parsing of jumbo-frames option
Parse switch.jumbo-frames instead of switch.properties.jumbo-frames
(according to uCentral schema)
2024-10-02 15:37:58 +03:00
Viacheslav Holovetskyi
0a0820e169 Move declarations to top of the functions 2024-10-02 02:24:10 +03:00
Viacheslav Holovetskyi
2bc20dab60 Place curly brackets on the same line 2024-10-02 02:19:22 +03:00
Viacheslav Holovetskyi
22781547a1 Revert "Reimplement yang-to-gnmi path conversion .."
This reverts commit c0f92db209.
2024-10-02 02:08:45 +03:00
Viacheslav Holovetskyi
dc80301aee Revert "Disable JWT authentication"
This reverts commit 58d32190a8.
2024-10-02 02:08:39 +03:00
Viacheslav Holovetskyi
cfcbc2b724 Revert "Add debug symbols to the binaries, install gdb in Docker container"
This reverts commit 0d7a532f53.
2024-10-02 02:08:06 +03:00
Viacheslav Holovetskyi
ece8a6833f Adjust column limit to 120 2024-09-04 17:07:50 +03:00
Viacheslav Holovetskyi
1d3f947f89 Use spaces instead of tabs 2024-09-04 17:07:50 +03:00
Viacheslav Holovetskyi
98abb522eb Add clang-format config 2024-09-04 17:07:50 +03:00
Mykola Gerasymenko
1599ecceeb [igmp] Add IGMP-snooping support #29075 2024-09-04 17:07:50 +03:00
Viacheslav Holovetskyi
708b753f70 Implement jumbo frames configuration 2024-09-04 17:07:50 +03:00
Viacheslav Holovetskyi
c03231213d Implement jumbo frames config parsing 2024-09-04 17:07:50 +03:00
Dmitriy Nabok
7a9d2b19df Handle Syslog configuration #28496 2024-09-04 17:07:48 +03:00
Mykola Gerasymenko
7fc85d481c Fix comments review 2024-09-04 17:07:35 +03:00
Mykola Gerasymenko
5180f1e9da [stp] Add spanning-tree support #28505 2024-09-04 17:07:33 +03:00
Dmitriy Nabok
caef6ad4c3 refactoring 2024-09-04 17:06:54 +03:00
Dmitriy Nabok
57595fa5ae add dhcpv6 servers 2024-09-04 17:06:54 +03:00
Dmitriy Nabok
82cec0550a 28497: DHCP Relay init 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
8205ee7a0a Delete existing NTP servers that are not present in the config 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
43fd03ebb7 Implement NTP configuration via YANG models 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
6d46db300d Implement NTP config parsing 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
4d8ca0d9eb Fix includes 2024-09-04 17:06:54 +03:00
Andrii Mandiuk
fc1fecbb29 Add GNOI reboot 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
abee1977fc Disallow the configuration of VLAN 1
SONIC utilities doesn't allow to neither create VLAN 1, nor add any members
to it. gNMI lets you do this (for some reason), but in that case SWSS crashes,
and so do the whole system. Hence, we should also prevent it.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
bf1ca8fe89 Remove the services from "requires" list
This fields corresponds to the systemd's "Requires" clause, which means that
if one of these services gets stopped, ucentral-client services is stopped too,
which is generally not something we want
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f8c61adac1 Implement health reporting (always 100% for now) 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
1ac8d8cfa8 Implement metrics config as a JSON file instead of Protobuf
This is mostly done in order to be able to modify the config by hand
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2bc70cc145 Implement metrics config saving and loading 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f6a56a6a57 Add delay between retries, add additional debug messages 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
7b4947508d Add workaround for ucentral-client starting too early 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f6bfadad3e Add TIP issuing certificate to trusted CA certificates 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ddba3d5652 Bring the old RTTY client executable back 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
565a32fe74 Return nullopt in case if LLDP peer info is unavailable instead of throwing ..
.. an exception
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2c367ed818 Make get_state_info more fail-resistant
Retrieval failure of one particular part of state should not spoil the whole
telemetry response
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
3c9f3c56cf Add package dependencies to manifest 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
d4efe7e596 Revert "[To be reviewed] Implement serial retrieval from environment variable"
This reverts commit 209685905d2e0afcefc43252fba64e8c19808a20.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ea5edeaa60 Fix indentation 2024-09-04 17:06:54 +03:00
Kostiantyn Buravchenko
9f28fa135f Fixes to run in Sonic 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0667d9e4bd Don't include static routes and interface addresses into GW list 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ec69be8141 Implement dynamic routes 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
bd52b8b658 Implement static routes 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0bc7d0a373 Make error codes consistent 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
9a76bfadff Implement addition and deletion of interface addresses 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f8b293dbb8 Add interface addresses to platform state 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
b2b24f8e07 Implement getting interface addresses 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
1cc4e44a19 Add extern C to router-utils.h header 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2102f4e3c4 Add LAG interface mapping 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
3621e48411 Remove object ID prefix from the interface mapping keys 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
6080da5a05 Fix getting FDB entry MAC address 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
6a5f77f6fc Clear keys lists before scan to prevent processing them multiple times 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
bd31fdef3e Fix getting VLAN by object ID 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0aee7a3c74 Fix setting Redis SCAN cursor 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f1ffaedf0a Handle absence of counters, make it non-critical ..
.. (in this case all the counters are zeroes)
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
e53ccaf9b7 Handle the absence of oper-status field in interface YANG model 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
dffe061778 Add discrete exception type for gNMI errors 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
1b869b9d9d Add debug build flags for ucentral-client and larch-sonic platform 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
5aacf9a82d Implement FDB 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
7da9ce52d4 Link redis client library 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
4536be8e09 Fix port speed JSON type 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
3a8dd3666a Remove thread_local specifier from state variable ..
.. as the state is used from multiple threads and must be the same across them.
Maybe some synchronization will be required in future if concurrent
reads/writes would be possible.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c1d128a64b Implement saving and loading config id 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
7265aa1e70 Remove httplib dependency and related utilities as it's not used anymore 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
570b2ff1cc Implement plat_info_get using YANG model instead of a REST API 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
72fa3f794e Workaround for LLDP peer info 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
7120faa622 Use name instead of ifname as an interface name field 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
afda76f04d Implement LLDP peer info support 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c482e56767 Implement split_string utility 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0ad4d6e0ef Fix telemetry poll stop 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
38edc8b513 Implement telemetry and state polling 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c01e0a08ed Implement class for periodically called functions 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ce744a589b Use vector instead of heap-allocated array for ports info 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
3f14c324a0 Implement getting port info 2024-09-04 17:06:54 +03:00
Oleh
fc646e5daf Feature #28020. RTTY Support. Change name to Larch. 2024-09-04 17:06:54 +03:00
Oleh
d5291ec1cc Feature #28020 RTTY Support 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
9e2abdcd35 [To be reviewed] Implement serial retrieval from environment variable 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f4e37d67e5 [To be reviewed] Add CA certificate 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
54b15ebbc8 Fix port speed (should be integer instead of string) 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
11caedb29c Better handling of empty JSON responses ..
.. Some functions are expected to return non-default values, like
`get_vlan_membership()`, which must contain some amount of elements even if
the response is empty.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
9b6c77a6dc Fix errors if there are no ports or VLANs defined in the system ..
.. In this case JSON response is an completely empty object, that doesn't even
contains a, for example, "sonic-vlan:VLAN_LIST" object inside it.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
25b95decbb Fix processing of the last part of YANG path 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
7b20f06aef Implement key-value parsing for YANG paths 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
93360f91ac Fix port interface name field 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
6642f0b060 Remove unnecessary include, zero-initialize system info 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
5ffa368c60 Implement getting system info 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
af365a7479 Implement getting list and number of ports 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
f53624d25a Throw exceptions on JSON parse failures 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
74bcf8604a [To be reviewed] Fix multiple definitions of key variable ..
It's unclear whether this variable should be shared between compilation units
(definition in source file + `extern` in header) or should there be copy of
the variable in each compilation unit (simply make it `static`).

Currently this variable isn't used anywhere, so we can safely remove its
definition.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
39b58f3faf Add extern C to allow the use of log functions from C++ ..
Without `extern C` C++ will try to link against C++ (mangled) version of
`uc_log` function, which doesn't exist, hence the linking process fails with
an error.
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c7c038f04b Fix state value 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0137a3e87a Pass arguments by reference in gnmi_set 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
3c5cd07084 Implement config apply for ports 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2a96865f52 Add usings for frequently used std entities 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
69dab0a2fc Add logging, implement config applying, implement plat_reboot 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
22ed337904 Improve error-handling, perform refactoring 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
afd6e10a14 Fix port field instead of ifname as a port ID
For some reason it was `ifname` in Broadcom implementation, although this
field doesn't appear anywhere in SONIC YANG models
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
9f83e79568 Implement config apply for VLAN 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c88fa3515a Add missing header guards 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
fb4bdd5cf4 Download CMake manually as Debian-installed version doesn't suit the needs 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
78cc463fe9 Implement gnmi_operation class for batch updates and deletes 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
14073ecb83 Implement setting multiple entries via gNMI 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
cac8e861ab Restructure the project 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
af79b86980 Remove external libraries from the repo, download them with FetchContent instead 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
51dab835bc Implement set via gNMI 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
31cd55935f Implement get via gNMI 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ed64b97bf2 Implement dummy certificate verifier (connection doesn't succeed without it) 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
dda06db648 Move credentials shared_ptr instead of copying 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
4feb498fa0 Change REST API address to localhost 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
83f9155f4c Implement gRPC channel and gGNMI stub initialization 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
088f12dd89 Add the platform dependencies to the result file 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
99b0f6b797 Reorder targets to build by default 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
caf9199379 Add target to connect CMake to ucentral-client Makefile 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2a8e2338bf Implement CMake to build larch-sonic platform 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
2537609e91 Implement plat_info_get using sonic-restapi 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
40f3f78a72 Add empty implementation for Larch platform 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
ddd8b96c19 Install dev CA certificate in the Docker container 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
a4398e8588 Add Makefile target to build dpkg package for ARM64 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
88aed5461d Remove unnecessary mentions of ARM64 platform in build files ..
.. (It builds successfully without them)
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
c0f92db209 Reimplement yang-to-gnmi path conversion ..
.. Now it uses "origin" and "elem" fields, instead of a deprecated "element"
field
2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
58d32190a8 Disable JWT authentication 2024-09-04 17:06:54 +03:00
Viacheslav Holovetskyi
0d7a532f53 Add debug symbols to the binaries, install gdb in Docker container 2024-09-04 17:06:52 +03:00
Viacheslav Holovetskyi
9ba671834d Specify ARM64 architecture in build files 2024-09-04 15:43:35 +03:00
49 changed files with 5333 additions and 14 deletions

View File

@@ -1,4 +1,4 @@
FROM debian:buster
FROM arm64v8/debian:buster
LABEL Description="Ucentral client (Build) environment"
ARG HOME /root
@@ -23,7 +23,8 @@ RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
autoconf \
libtool \
pkg-config \
libjsoncpp-dev
libjsoncpp-dev \
libhiredis-dev
RUN git config --global http.sslverify false
RUN git clone https://github.com/DaveGamble/cJSON.git ${HOME}/ucentral-external-libs/cJSON/

View File

@@ -7,6 +7,9 @@ IMG_ID := "ucentral-client-build-env"
IMG_TAG := $(shell cat Dockerfile | sha1sum | awk '{print substr($$1,0,11);}')
CONTAINER_NAME := "ucentral_client_build_env"
DPKG_IMAGE := "dpkg-builder:latest"
DPKG_CONTAINER_NAME := "dpkg_build_env"
.PHONY: all clean build-host-env build-final-deb build-ucentral-docker-img run-host-env run-ucentral-docker-img
all: build-host-env build-ucentral-app build-ucentral-docker-img build-final-deb
@@ -59,7 +62,7 @@ build-ucentral-docker-img: build-ucentral-app
cp docker/deliverables/ucentral-client docker/
cp docker/deliverables/rtty docker/
OLDIMG=$$(docker images --format "{{.ID}}" ucentral-client:latest)
docker build --file docker/Dockerfile --tag ucentral-client:latest docker
docker build --file docker/Dockerfile --tag ucentral-client:latest docker --label com.azure.sonic.manifest="$$(cat docker/manifest.json)"
NEWIMG=$$(docker images --format "{{.ID}}" ucentral-client:latest)
if [ -n "$$OLDIMG" ] && [ ! "$$OLDIMG" = "$$NEWIMG" ]; then
docker image rm $$OLDIMG
@@ -81,11 +84,39 @@ build-final-deb: build-ucentral-docker-img
@echo
@echo "ucentral client deb pkg is available under ./output/ dir"
build-arm64-deb: build-ucentral-docker-img
docker inspect --type=image ${DPKG_IMAGE} >/dev/null 2>&1 || \
docker build --file dpkg-builder.Dockerfile --tag ${DPKG_IMAGE} .
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container run -d -t \
--name ${DPKG_CONTAINER_NAME} \
--platform linux/arm64 \
-v $(realpath ./):$(realpath ./) \
-w $(realpath ./src/) \
--user $(shell id -u):$(shell id -g) \
--tmpfs /tmp \
${DPKG_IMAGE} \
bash
docker exec -t ${DPKG_CONTAINER_NAME} dpkg-buildpackage -rfakeroot -b -us -uc -j
mv ucentral-client*deb ./output/
mv src/docker-ucentral-client.gz ./output/
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
clean:
docker container stop ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container stop ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${DPKG_CONTAINER_NAME} > /dev/null 2>&1 || true;
docker rmi ucentral-client 2>/dev/null || true;
docker rmi ${IMG_ID}:${IMG_TAG} 2>/dev/null || true;
docker rmi ${DPKG_IMAGE} 2>/dev/null || true;
rm -rf output 2>/dev/null || true;
rm -rf docker 2>/dev/null || true;
rm -rf src/docker/deliverables || true;
@@ -96,4 +127,4 @@ clean:
rm -rf src/debian/.debhelper src/debian/ucentral-client 2>/dev/null || true;
rm -rf src/debian/shasta-ucentral-client* 2>/dev/null || true;
rm -rf src/debian/debhelper-build-stamp* 2>/dev/null || true;
rm -rf src/debian/files shasta_1.0_amd64.changes shasta_1.0_amd64.buildinfo 2>/dev/null || true;
rm -rf src/debian/files shasta_1.0_arm64.changes shasta_1.0_arm64.buildinfo 2>/dev/null || true;

8
dpkg-builder.Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM arm64v8/debian:buster
RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
build-essential \
fakeroot \
dpkg-dev \
dh-exec \
debhelper

View File

@@ -8,7 +8,7 @@ export DH_VERBOSE = 1
export DH_BUILD_DDEBS=1
export DEB_BUILD_OPTIONS=autodbgsym
CONFIGURED_ARCH ?= amd64
CONFIGURED_ARCH ?= arm64
UCENTRAL_CLIENT_VERSION ?= 1.0
DPKG_EXPORT_BUILDFLAGS = 1
INSTALL ?= debian/ucentral-client/

View File

@@ -1,4 +1,4 @@
FROM debian:buster
FROM arm64v8/debian:buster
RUN echo "uCentral client support"
RUN apt-get update && apt-get install --no-install-recommends -y \
@@ -9,6 +9,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl \
libjsoncpp-dev \
busybox \
libhiredis0.14 \
&& rm -rf /var/lib/apt/lists/*
RUN ln -s /bin/busybox /usr/local/bin/nslookup
@@ -21,6 +22,11 @@ COPY /ucentral-client /usr/local/bin/ucentral-client
COPY /rtty /usr/local/bin/
COPY /lib* /usr/local/lib/
# Install root CA certificate for development purposes
COPY /ca-cert.pem /usr/local/share/ca-certificates/ca-cert.crt
COPY /tip-cert.pem /usr/local/share/ca-certificates/tip-cert.crt
RUN update-ca-certificates
RUN ldconfig
RUN ls -l /usr/local/bin/ucentral-client

35
src/docker/ca-cert.pem Normal file
View File

@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGHzCCBAegAwIBAgIUe4hxamQ8YDS/GuFPIN+uqH4BgMswDQYJKoZIhvcNAQEL
BQAwgZ4xCzAJBgNVBAYTAlVBMRQwEgYDVQQIDAtLeWl2IE9ibGFzdDENMAsGA1UE
BwwES3lpdjEbMBkGA1UECgwSTGFyY2ggTmV0d29ya3MgTHRkMREwDwYDVQQLDAhT
b2Z0d2FyZTERMA8GA1UEAwwIY2EubGFyY2gxJzAlBgkqhkiG9w0BCQEWGGFkbWlu
QGxhcmNoLW5ldHdvcmtzLmNvbTAeFw0yNDA0MDQwNzUxMDlaFw0zNDA0MDIwNzUx
MDlaMIGeMQswCQYDVQQGEwJVQTEUMBIGA1UECAwLS3lpdiBPYmxhc3QxDTALBgNV
BAcMBEt5aXYxGzAZBgNVBAoMEkxhcmNoIE5ldHdvcmtzIEx0ZDERMA8GA1UECwwI
U29mdHdhcmUxETAPBgNVBAMMCGNhLmxhcmNoMScwJQYJKoZIhvcNAQkBFhhhZG1p
bkBsYXJjaC1uZXR3b3Jrcy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDG5kue5pYz+PC/NBZpBesN2NjWzpcTAunzFnQQ/XAMY9XLcUdyrXLz6r2p
bFbmUiW1am+2TZMg6sQbBPId7vjuHZykXavgUd8ybUoQ2GHnWVsCCy8b/Fn2WF2r
Zc14ffRncZ4BsHCPJsWboCmYVsgNgr1nmDT6Le5VIRJ2MJksuTz+55bK9qCIIWWC
PFBpGjMHpEdybvwa5gzv2Do9vwb0m76LKL5ygyle4hFoI54JwRV8O7g8FishTrmI
oSDmapB7+K9egK2wXhbHQ6pIc4qvs9YdSPaTkLV/tRa3Oyw+vUGDW5Bg5UezPJNf
U9yCZIjKZXYXbqaZRgtcXJfqBGFlmuVX44zfjpEe2ovHXrb7HokZxEcJA/yooxzw
3eZXDxsdVXU02jA9jSQjX1Q44lwxUn1wVZlTdr9AUJM6JiEujITy2fwyfaeTwCyS
Y4xU7qgMr9Wml0kHPPFnlsTWo+dswpztRt5BKCKMcsyLETiLkapsI784ameiEgpA
3klYJsMh/2+Q9ABuK5BQOuHCPvalNoEKn4rUyQgdTL/ay3oqlvNf4r08hgHA72Aj
oM60Ldq2gJeuaEh6l3fV182qd4funDgrMpQpfRcIKDJcDRuN8vKzqmTXLNY8zgNY
xharAsEm3CdBwbpTF4E1yDCH8seuj3TCcMwJAWGq73QRgu0DwQIDAQABo1MwUTAd
BgNVHQ4EFgQUwqiRcQgGkVIdKjDjA3KCVTl0rTwwHwYDVR0jBBgwFoAUwqiRcQgG
kVIdKjDjA3KCVTl0rTwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AgEAwUEtLkinQmWmRqQfKZayUDjKl0B7HZy9en0LI6WzKppfj9OwZcAxWNqavDwp
WRCEmzt27NPF42C6lr/i0ZBOT5V1qDoP8/JR3QW/dsEIS2wW61803pw77U8SyfKP
tEwbQsCuw7jBCSDjDKI5k9aBlltHzTzSKjo1nBTwMYHho1dR5Th6mOXr9soyNopU
l7SlvC5UWuCYxhxjWlZnxaupbKdPNt88SPWDLWa6HLBil2XSu/5wMVdubyCZrlN/
8SajdHtev9lk3pucnLmqxCq6sChim9EzDcVVRPNzR59H6dbyMec16e0XrnNcd9ch
2Zo2HQm3jyIh4YL1iRrHyqJgVfmI1hKlvqUOfAxVCV8pSec40EQD2sd1Ges5QTiH
LCk2osPvDjV1JyFKcr5Pf50+S02MViU8tza+VfUwozRa6A9qR/JlBTBlCrDEYexm
CK7I11Qln26vvtlnJTC7OzXTaOSYQ1NlJCwLwsMOvhYVm0skxL1HX1AYHKZ0t0QW
PAQYij2QP1YK4SU68MlXiE3D5N7vZ+42pfHx0DNZXlsudM7kRmkT3FBIh54mh4xt
0a5nUDjd3IBPcVAeVJZqbxglAwKlue+MhJRKSFxehE3qEKurmH1tGUTb5NnmMllC
6G51ZGoyb64oGejymIc2IB5rXH5+Uu2HUsyoVLt0wihgU7E=
-----END CERTIFICATE-----

View File

@@ -4,7 +4,8 @@
"package": {
"version": "1.0.0",
"depends": [],
"name": "ucentral_client"
"name": "ucentral_client",
"description": "SONiC uCentral client package"
},
"service": {
"name": "ucentral_client",
@@ -25,12 +26,24 @@
},
"container": {
"privileged": false,
"volumes": [],
"volumes": ["/var/lib/ucentral:/var/lib/ucentral"],
"mounts": [
{
"source": "TCA",
"target": "/etc/ucentral",
"type": "volume",
"volume-opt": {
"device": "/dev/disk/by-label/ONIE-TIP-CA-CERT",
"type": "ext4",
"o": "ro"
}
}
],
"tmpfs": []
},
"cli": {
"config": "",
"show": "",
"clear": ""
"config": [],
"show": [],
"clear": []
}
}

49
src/docker/tip-cert.pem Normal file
View File

@@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIEnDCCA4SgAwIBAgIUVpyCUx1MUeUwxg+7I1BvGFTz7HkwDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjUxMjZaFw0yNjA0MTMyMjM4NDZaMGwx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEpMCcGA1UEAxMgVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IElzc3VpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtKBrq
qd2aKVSk25KfL5xHu8X7/8rJrz3IvyPuVKWhk/N1zabot3suBcGaYNKjnRHxg78R
yKwKzajKYWtiQFqztu24g16LQeAnoUxZnF6a0z3JkkRPsz14A2y8TUhdEe1tx+UU
4VGsk3n+FMmOQHL+79FO57zQC1LwylgfLSltrI6mF3jowVUQvnwzKhUzT87AJ6EO
ndK/q0T/Bgi+aI39zfVOjJjsTJwghvrmYW3iarP1THSKxeib2s02bZKrvvHa5HL4
UI8+LvREpVZl4mzt1z6Nl344Y6f+UeJlYa/Ci0jJqaXJmyVnUbAz+c0i5JfwAVn3
YQzfC4eLnZCmdF8zAgMBAAGjggE3MIIBMzAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSzG1S44EerPfM4gOQ85f0AYW3R6DAfBgNVHSMEGDAWgBQCRpZgebFT9qny
98WfIUDk6ZEB+jAOBgNVHQ8BAf8EBAMCAYYwgYMGCCsGAQUFBwEBBHcwdTAoBggr
BgEFBQcwAYYcaHR0cDovL29jc3Aub25lLmRpZ2ljZXJ0LmNvbTBJBggrBgEFBQcw
AoY9aHR0cDovL2NhY2VydHMub25lLmRpZ2ljZXJ0LmNvbS9UZWxlY29tSW5mcmFQ
cm9qZWN0Um9vdENBLmNydDBKBgNVHR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLm9u
ZS5kaWdpY2VydC5jb20vVGVsZWNvbUluZnJhUHJvamVjdFJvb3RDQS5jcmwwDQYJ
KoZIhvcNAQELBQADggEBAFbz+K94bHIkBMJqps0dApniUmOn0pO6Q6cGh47UP/kX
IiPIsnYgG+hqYD/qtsiqJhaWi0hixRWn38UmvZxMRk27aSTGE/TWx0JTC3qDGsSe
XkUagumbSfmS0ZyiTwMPeGAjXwyzGorqZWeA95eKfImntMiOf3E7//GK0K7HpCx8
IPCnLZsZD2q/mLyBsduImFIRQJbLAhwIxpcd1qYJk+BlGFL+HtBpEbq6JxW2Xy+v
DpNWc2WIsUTle0rTc9JNJrLX4ChUJmKqf8obKHap3Xh3//qw/jDB9pOAinA33FLJ
EmCnwBvQr9mfNmPBGMYZVU8cPruDQJ57GjmmvdisbJY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIUPVYBpqNbcLYygF6Mx+qxSWwQyFowDQYJKoZIhvcNAQEL
BQAwaTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG1RlbGVjb20gSW5mcmEgUHJvamVj
dCwgSW5jLjEMMAoGA1UECxMDVElQMSYwJAYDVQQDEx1UZWxlY29tIEluZnJhIFBy
b2plY3QgUm9vdCBDQTAeFw0yMTA0MTMyMjQyNDRaFw0zMTA0MTMyMjM4NDZaMGkx
CzAJBgNVBAYTAlVTMSQwIgYDVQQKExtUZWxlY29tIEluZnJhIFByb2plY3QsIElu
Yy4xDDAKBgNVBAsTA1RJUDEmMCQGA1UEAxMdVGVsZWNvbSBJbmZyYSBQcm9qZWN0
IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIGCibwf5u
AAwZ+1H8U0e3u2V+0d2gSctucoK86XwUmfe1V2a/qlCYZd29r80IuN1IIeB0naIm
KnK/MzXW87clF6tFd1+HzEvmlY/W4KyIXalVCTEzirFSvBEG2oZpM0yC3AefytAO
aOpA00LaM3xTfTqMKIRhJBuLy0I4ANUVG6ixVebbGuc78IodleqiLoWy2Q9QHyEO
t/7hZndJhiVogh0PveRhho45EbsACu7ymDY+JhlIleevqwlE3iQoq0YcmYADHno6
Eq8vcwLpZFxihupUafkd1T3WJYQAJf9coCjBu2qIhNgrcrGD8R9fGswwNRzMRMpX
720+GjcDW3bJAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAJG
lmB5sVP2qfL3xZ8hQOTpkQH6MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEAVjl9dm4epG9NUYnagT9sg7scVQEPfz3Lt6w1NXJXgD8mAUlK0jXmEyvM
dCPD4514n+8+lM7US8fh+nxc7jO//LwK17Wm9FblgjNFR7+anv0Q99T9fP19DLlF
PSNHL2emogy1bl1lLTAoj8nxg2wVKPDSHBGviQ5LR9fsWUIJDv9Bs5k0qWugWYSj
19S6qnHeskRDB8MqRLhKMG82oDVLerSnhD0P6HjySBHgTTU7/tYS/OZr1jI6MPbG
L+/DtiR5fDVMNdBSGU89UNTi0wHY9+RFuNlIuvZC+x/swF0V9R5mN+ywquTPtDLA
5IOM7ItsRmen6u3qu+JXros54e4juQ==
-----END CERTIFICATE-----

View File

@@ -19,7 +19,7 @@ platform/plat.a:
ucentral-client: ucentral-client.o proto.o platform/plat.a \
ucentral-json-parser.o ucentral-log.o router-utils.o base64.o
g++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv
g++ -g -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv -lhiredis
test:
@echo "========= running unit tests ========="

View File

@@ -1,12 +1,19 @@
#ifndef ROUTER_UTILS_H
#define ROUTER_UTILS_H
/* Defines router types and utils for them (lookup/etc) */
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
struct ucentral_router_fib_key {
/* TODO vrf */
struct in_addr prefix;
int prefix_len;
} key;
};
struct ucentral_router_fib_info { /* Destination info */
enum {
@@ -92,3 +99,9 @@ int ucentral_router_fib_info_cmp(const struct ucentral_router_fib_info *a,
(DIFF) > 0 ? ++(IOLD) : 0, \
(DIFF) < 0 ? ++(INEW) : 0 \
)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -3,6 +3,10 @@
#include <syslog.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef UC_LOG_COMPONENT
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_UNKNOWN
#endif
@@ -58,4 +62,8 @@ void uc_log(enum uc_log_component c, int sv, const char *fmt, ...);
FMT __VA_OPT__(, ) __VA_ARGS__); \
} while (0)
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,3 +1,6 @@
#ifndef UCENTRAL_PLATFORM_H
#define UCENTRAL_PLATFORM_H
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
@@ -25,6 +28,7 @@ extern "C" {
#define RTTY_CFG_FIELD_STR_MAX_LEN (64)
#define PLATFORM_INFO_STR_MAX_LEN (96)
#define SYSLOG_CFG_FIELD_STR_MAX_LEN (64)
#define NTP_CFG_HOSTNAME_STR_MAX_LEN (64)
#define RADIUS_CFG_HOSTNAME_STR_MAX_LEN (64)
#define RADIUS_CFG_PASSKEY_STR_MAX_LEN (64)
#define RADIUS_CFG_DEFAULT_PORT (1812)
@@ -325,6 +329,15 @@ struct plat_enabled_service_cfg {
} http;
};
struct plat_ntp_server {
char hostname[NTP_CFG_HOSTNAME_STR_MAX_LEN];
struct plat_ntp_server *next;
};
struct plat_ntp_cfg {
struct plat_ntp_server *servers;
};
struct plat_rtty_cfg {
char id[RTTY_CFG_FIELD_STR_MAX_LEN];
char passwd[RTTY_CFG_FIELD_STR_MAX_LEN];
@@ -456,6 +469,7 @@ struct plat_cfg {
struct plat_metrics_cfg metrics;
struct plat_syslog_cfg *log_cfg;
struct plat_enabled_service_cfg enabled_services_cfg;
struct plat_ntp_cfg ntp_cfg;
/* Port's interfaces (provide l2 iface w/o bridge caps) */
struct plat_port_l2 portsl2[MAX_NUM_OF_PORTS];
struct ucentral_router router;
@@ -475,6 +489,7 @@ struct plat_cfg {
struct plat_ieee8021x_dac_list *das_dac_list;
} ieee8021x;
struct plat_port_isolation_cfg port_isolation_cfg;
bool jumbo_frames;
};
struct plat_learned_mac_addr {
@@ -779,3 +794,5 @@ plat_reboot_cause_get(struct plat_reboot_cause *cause);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,52 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: false
AfterControlStatement: Always
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
ColumnLimit: 120
DerivePointerAlignment: false
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
NamespaceIndentation: Inner
PackConstructorInitializers: CurrentLine
PointerAlignment: Right
RequiresExpressionIndentation: OuterScope
UseTab: Never
...

View File

@@ -0,0 +1,118 @@
cmake_minimum_required(VERSION 3.13)
project(larch-sonic VERSION 0.1.0 LANGUAGES C CXX)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(grpc)
include(FetchContent)
FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)
set(REDIS_PLUS_PLUS_BUILD_TEST OFF)
set(REDIS_PLUS_PLUS_BUILD_SHARED OFF)
FetchContent_Declare(redis-plus-plus URL https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.12.zip)
FetchContent_MakeAvailable(redis-plus-plus)
add_subdirectory(protos)
add_library(larch-sonic
STATIC
"fdb.cpp"
"igmp.cpp"
"metrics.cpp"
"plat-larch.cpp"
"port.cpp"
"route.cpp"
"sai_redis.cpp"
"services.cpp"
"state.cpp"
"stp.cpp"
"syslog.cpp"
"utils.cpp"
"vlan.cpp"
)
target_compile_features(larch-sonic PRIVATE cxx_std_17)
target_link_libraries(larch-sonic
PRIVATE
proto-objects
nlohmann_json::nlohmann_json
redis++::redis++_static
)
target_include_directories(larch-sonic
PRIVATE
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/../../include
)
set_target_properties(larch-sonic PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
add_custom_target(plat.a
DEPENDS larch-sonic
COMMAND ${CMAKE_AR} rcsT ${PROJECT_SOURCE_DIR}/plat.a
$<TARGET_FILE:larch-sonic>
$<TARGET_FILE:redis++::redis++_static>
/usr/local/lib/libabsl_bad_optional_access.a
/usr/local/lib/libabsl_bad_variant_access.a
/usr/local/lib/libabsl_base.a
/usr/local/lib/libabsl_city.a
/usr/local/lib/libabsl_civil_time.a
/usr/local/lib/libabsl_cord.a
/usr/local/lib/libabsl_cord_internal.a
/usr/local/lib/libabsl_cordz_functions.a
/usr/local/lib/libabsl_cordz_handle.a
/usr/local/lib/libabsl_cordz_info.a
/usr/local/lib/libabsl_debugging_internal.a
/usr/local/lib/libabsl_demangle_internal.a
/usr/local/lib/libabsl_exponential_biased.a
/usr/local/lib/libabsl_graphcycles_internal.a
/usr/local/lib/libabsl_hash.a
/usr/local/lib/libabsl_hashtablez_sampler.a
/usr/local/lib/libabsl_int128.a
/usr/local/lib/libabsl_log_severity.a
/usr/local/lib/libabsl_low_level_hash.a
/usr/local/lib/libabsl_malloc_internal.a
/usr/local/lib/libabsl_random_distributions.a
/usr/local/lib/libabsl_random_internal_platform.a
/usr/local/lib/libabsl_random_internal_pool_urbg.a
/usr/local/lib/libabsl_random_internal_randen.a
/usr/local/lib/libabsl_random_internal_randen_hwaes.a
/usr/local/lib/libabsl_random_internal_randen_hwaes_impl.a
/usr/local/lib/libabsl_random_internal_randen_slow.a
/usr/local/lib/libabsl_random_internal_seed_material.a
/usr/local/lib/libabsl_random_seed_gen_exception.a
/usr/local/lib/libabsl_random_seed_sequences.a
/usr/local/lib/libabsl_raw_hash_set.a
/usr/local/lib/libabsl_raw_logging_internal.a
/usr/local/lib/libabsl_spinlock_wait.a
/usr/local/lib/libabsl_stacktrace.a
/usr/local/lib/libabsl_status.a
/usr/local/lib/libabsl_statusor.a
/usr/local/lib/libabsl_str_format_internal.a
/usr/local/lib/libabsl_strerror.a
/usr/local/lib/libabsl_strings.a
/usr/local/lib/libabsl_strings_internal.a
/usr/local/lib/libabsl_symbolize.a
/usr/local/lib/libabsl_synchronization.a
/usr/local/lib/libabsl_throw_delegate.a
/usr/local/lib/libabsl_time.a
/usr/local/lib/libabsl_time_zone.a
/usr/local/lib/libaddress_sorting.a
/usr/local/lib/libcares.a
/usr/local/lib/libcrypto.a
/usr/local/lib/libgpr.a
/usr/local/lib/libgrpc++.a
/usr/local/lib/libgrpc++_reflection.a
/usr/local/lib/libgrpc.a
/usr/local/lib/libprotobuf.a
/usr/local/lib/libre2.a
/usr/local/lib/libssl.a
/usr/local/lib/libupb.a
/usr/local/lib/libz.a
VERBATIM
)

View File

@@ -0,0 +1,22 @@
# This is a connector between ucentral-client Makefile and larch-sonic platform CMake.
# When `plat.a` target is requested, CMake generates the underlying build system files
# (i.e. Makefile) and make is run there.
CMAKE_FILE := CMakeLists.txt
CMAKE_BUILD_DIR := build
MACHINE_ARCH := $(shell arch)
CMAKE_VERSION := 3.29.3
CMAKE_DIR := cmake-$(CMAKE_VERSION)-linux-$(MACHINE_ARCH)
CMAKE_BINARY := $(CMAKE_DIR)/bin/cmake
plat.a: $(CMAKE_BUILD_DIR)/Makefile
$(MAKE) -C $(<D) $@
# <D - directory part (i.e. path without file) of the first prerequisite
# @D - directory part of the target
$(CMAKE_BUILD_DIR)/Makefile: $(CMAKE_BINARY) $(CMAKE_FILE)
$(CMAKE_BINARY) -S $(dir $(CMAKE_FILE)) -B $(@D) -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug
$(CMAKE_BINARY):
wget -qO- --no-check-certificate "https://github.com/Kitware/CMake/releases/download/v$(CMAKE_VERSION)/cmake-$(CMAKE_VERSION)-linux-$(MACHINE_ARCH).tar.gz" | tar -xz

View File

@@ -0,0 +1,14 @@
# Find Protobuf
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using Protobuf ${Protobuf_VERSION}")
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)
set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
# Find gRPC
find_package(gRPC CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")
set(_GRPC_GRPCPP gRPC::grpc++)
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)

View File

@@ -0,0 +1,143 @@
#include <fdb.hpp>
#include <sai_redis.hpp>
#include <state.hpp>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#include <ucentral-platform.h>
#include <cstdint>
#include <cstdio> // std::snprintf
#include <iterator> // std:inserter
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using nlohmann::json;
namespace larch {
std::vector<plat_learned_mac_addr> get_learned_mac_addrs()
{
const auto bridge_mapping = sai::get_bridge_port_mapping();
const auto port_name_mapping = sai::get_port_name_mapping();
std::unordered_map<sai::object_id, std::uint16_t> vlan_cache;
std::vector<plat_learned_mac_addr> learned_mac_addrs;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_FDB_ENTRY:*";
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
plat_learned_mac_addr learned_entry{};
std::unordered_map<std::string, std::string> entry;
// Get port name
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
if (entry.at("SAI_FDB_ENTRY_ATTR_TYPE")
== "SAI_FDB_ENTRY_TYPE_STATIC")
continue;
const auto port_it = bridge_mapping.find(
entry.at("SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID")
.substr(sai::oid_prefix.size()));
if (port_it == bridge_mapping.cend())
continue;
const auto interface_it =
port_name_mapping.find(port_it->second);
const std::string interface_name =
(interface_it != port_name_mapping.cend())
? interface_it->second
: port_it->second;
// Get VLAN ID
std::uint16_t vlan_id{};
const json fdb_json =
json::parse(key.substr(pattern.size() - 1));
if (fdb_json.contains("vlan"))
{
vlan_id = static_cast<std::uint16_t>(std::stoul(
fdb_json.at("vlan")
.template get<std::string>()));
}
else
{
if (!fdb_json.contains("bvid"))
continue;
std::string vlan_oid =
fdb_json.at("bvid")
.template get<std::string>()
.substr(sai::oid_prefix.size());
const auto vlan_it = vlan_cache.find(vlan_oid);
if (vlan_it != vlan_cache.cend())
{
// VLAN is found in cache, using it
vlan_id = vlan_it->second;
}
else
{
auto vlan_id_opt =
sai::get_vlan_by_oid(vlan_oid);
if (!vlan_id_opt)
continue;
vlan_id = *vlan_id_opt;
vlan_cache.try_emplace(
std::move(vlan_oid),
std::move(*vlan_id_opt));
}
}
std::snprintf(
learned_entry.port,
PORT_MAX_NAME_LEN,
"%s",
interface_name.c_str());
learned_entry.vid = vlan_id;
std::snprintf(
learned_entry.mac,
PLATFORM_MAC_STR_SIZE,
"%s",
fdb_json.at("mac")
.template get<std::string>()
.c_str());
learned_mac_addrs.push_back(learned_entry);
}
} while (cursor != 0);
return learned_mac_addrs;
}
} // namespace larch

View File

@@ -0,0 +1,14 @@
#ifndef LARCH_PLATFORM_FDB_HPP_
#define LARCH_PLATFORM_FDB_HPP_
#include <ucentral-platform.h>
#include <vector>
namespace larch {
std::vector<plat_learned_mac_addr> get_learned_mac_addrs();
} // namespace larch
#endif // !LARCH_PLATFORM_FDB_HPP_

View File

@@ -0,0 +1,136 @@
#include <igmp.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <string>
using nlohmann::json;
using namespace std;
namespace larch {
typedef enum {
GNMI_IGMP_VERSION_NA = 0,
GNMI_IGMP_VERSION_1 = 1,
GNMI_IGMP_VERSION_2 = 2,
GNMI_IGMP_VERSION_3 = 3
} gnmi_igmp_version_t;
static void disable_igmp_snooping(uint16_t vid)
{
gnmi_operation op;
const auto igmp_snooping_list
= gnmi_get ("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/"
"CFG_L2MC_TABLE_LIST");
const json igmp_snooping_list_json = json::parse(igmp_snooping_list);
/* There are no IGMP-snoooping */
if (!igmp_snooping_list_json.contains("sonic-igmp-snooping:CFG_L2MC_TABLE_LIST"))
return;
/* Delete igmp-snooping config for specific VLAN. */
op.add_delete("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/CFG_L2MC_TABLE_LIST[vlan-name=Vlan" + to_string(vid) + "]");
op.execute();
}
static void set_igmp_snooping(uint16_t vid, struct plat_igmp *igmp)
{
bool enabled = igmp->snooping_enabled || igmp->querier_enabled;
gnmi_igmp_version_t gnmi_igmp_version = GNMI_IGMP_VERSION_NA;
gnmi_operation op;
if (!enabled)
{
disable_igmp_snooping(vid);
return;
}
if (igmp->version == PLAT_IGMP_VERSION_1)
gnmi_igmp_version = GNMI_IGMP_VERSION_1;
else if (igmp->version == PLAT_IGMP_VERSION_2)
gnmi_igmp_version = GNMI_IGMP_VERSION_2;
else if (igmp->version == PLAT_IGMP_VERSION_3)
gnmi_igmp_version = GNMI_IGMP_VERSION_3;
else
{
UC_LOG_ERR("Failed IGMP version");
throw std::runtime_error{"Failed IGMP version"};
}
/* Config IGMP-snooping */
json igmp_snooping_list_json;
igmp_snooping_list_json["vlan-name"] = "Vlan" + to_string(vid);
igmp_snooping_list_json["enabled"] = igmp->snooping_enabled;
igmp_snooping_list_json["querier"] = igmp->querier_enabled;
if (igmp->querier_enabled)
{
igmp_snooping_list_json["query-interval"] = igmp->query_interval;
igmp_snooping_list_json["query-max-response-time"] = igmp->max_response_time;
igmp_snooping_list_json["last-member-query-interval"] = igmp->last_member_query_interval;
if (gnmi_igmp_version != GNMI_IGMP_VERSION_NA)
{
igmp_snooping_list_json["version"] = igmp->version;
}
}
if (igmp->snooping_enabled) {
igmp_snooping_list_json["fast-leave"] = igmp->fast_leave_enabled;
}
json add_igmp_snooping_list_json;
add_igmp_snooping_list_json["sonic-igmp-snooping:CFG_L2MC_TABLE_LIST"] = {igmp_snooping_list_json};
op.add_update("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_TABLE/CFG_L2MC_TABLE_LIST", add_igmp_snooping_list_json.dump());
op.execute();
}
static void set_igmp_static_groups(uint16_t vid, struct plat_igmp *igmp)
{
struct plat_ports_list *port_node = NULL;
size_t group_idx;
gnmi_operation op;
for (group_idx = 0; group_idx < igmp->num_groups; group_idx++)
{
const std::string ip_addr = addr_to_str(igmp->groups[group_idx].addr);
json igmp_static_group_json;
igmp_static_group_json["vlan-name"] = "Vlan" + to_string(vid);
igmp_static_group_json["group-addr"] = ip_addr;
json out_intf_list_json = json::array();
UCENTRAL_LIST_FOR_EACH_MEMBER(port_node, &igmp->groups[group_idx].egress_ports_list)
{
out_intf_list_json.push_back(port_node->name);
}
igmp_static_group_json["out-intf"] = out_intf_list_json;
json add_igmp_static_group_json;
add_igmp_static_group_json["sonic-igmp-snooping:CFG_L2MC_STATIC_GROUP_TABLE_LIST"] = {igmp_static_group_json};
op.add_update("/sonic-igmp-snooping:sonic-igmp-snooping/CFG_L2MC_STATIC_GROUP_TABLE/CFG_L2MC_STATIC_GROUP_TABLE_LIST", add_igmp_static_group_json.dump());
}
op.execute();
}
void apply_igmp_config(uint16_t vid, struct plat_igmp *igmp)
{
set_igmp_snooping(vid, igmp);
if (igmp->num_groups > 0)
{
set_igmp_static_groups(vid, igmp);
}
}
}

View File

@@ -0,0 +1,12 @@
#ifndef LARCH_PLATFORM_IGMP_HPP_
#define LARCH_PLATFORM_IGMP_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_igmp_config(uint16_t vid, struct plat_igmp *igmp);
}
#endif // !LARCH_PLATFORM_IGMP_HPP_

View File

@@ -0,0 +1,307 @@
#include <fdb.hpp>
#include <metrics.hpp>
#include <port.hpp>
#include <route.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <sys/sysinfo.h>
#include <array>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iomanip> // std::setw
#include <iterator> // std::begin, std::size
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility> // std::move
using nlohmann::json;
namespace larch {
namespace {
const std::string metrics_config_path =
"/var/lib/ucentral/metrics_cfg.json";
}
static plat_system_info get_system_info()
{
plat_system_info system_info{};
// Get load average
std::array<double, std::size(system_info.load_average)> load_average{};
if (getloadavg(load_average.data(), load_average.size()) < 0)
{
UC_LOG_ERR("Failed to get load average");
}
else
{
for (double &elem : load_average)
elem /= 100.0;
std::copy(
load_average.cbegin(),
load_average.cend(),
std::begin(system_info.load_average));
}
// Get RAM cached
std::ifstream is{"/proc/meminfo"};
if (!is)
{
UC_LOG_ERR("Failed to get memory info");
}
else
{
std::string line;
std::uint64_t cached = 0;
bool found = false;
while (std::getline(is, line))
{
if (std::sscanf(line.c_str(), "Cached:%lu", &cached)
== 1)
{
system_info.ram_cached = cached * 1024;
found = true;
}
}
if (!found)
{
UC_LOG_ERR("Can't find Cached entry in /proc/meminfo");
}
}
// Get other system information
struct sysinfo sys_info = {};
if (sysinfo(&sys_info) < 0)
{
UC_LOG_ERR(
"Failed to get system info: %s",
std::strerror(errno));
}
else
{
system_info.localtime =
static_cast<std::uint64_t>(std::time(nullptr));
system_info.uptime = sys_info.uptime;
system_info.ram_buffered =
sys_info.bufferram * sys_info.mem_unit;
system_info.ram_free =
(sys_info.freeram + sys_info.freeswap) * sys_info.mem_unit;
system_info.ram_total = sys_info.totalram * sys_info.mem_unit;
}
return system_info;
}
std::pair<plat_state_info, state_data> get_state_info()
{
plat_state_info state_info{};
state_data data{};
state_info.system_info = get_system_info();
// Get port info
try
{
data.port_info = get_port_info();
state_info.port_info = data.port_info.data();
state_info.port_info_count = data.port_info.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get port info: %s", ex.what());
}
// Get learned MAC addresses
try
{
data.learned_mac_addrs = get_learned_mac_addrs();
state_info.learned_mac_list = data.learned_mac_addrs.data();
state_info.learned_mac_list_size =
data.learned_mac_addrs.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR(
"Failed to get learned MAC addresses: %s",
ex.what());
}
// Get GW addresses
try
{
data.gw_addresses = get_gw_addresses();
state_info.gw_addr_list = data.gw_addresses.data();
state_info.gw_addr_list_size = data.gw_addresses.size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get GW addresses: %s", ex.what());
}
return {std::move(state_info), std::move(data)};
}
void save_metrics_config(const plat_metrics_cfg *cfg)
{
json metrics_cfg;
json &telemetry_cfg = metrics_cfg["telemetry"];
telemetry_cfg["enabled"] = static_cast<bool>(cfg->telemetry.enabled);
telemetry_cfg["interval"] = cfg->telemetry.interval;
json &healthcheck_cfg = metrics_cfg["healthcheck"];
healthcheck_cfg["enabled"] =
static_cast<bool>(cfg->healthcheck.enabled);
healthcheck_cfg["interval"] = cfg->healthcheck.interval;
json &state_cfg = metrics_cfg["state"];
state_cfg["enabled"] = static_cast<bool>(cfg->state.enabled);
state_cfg["lldp_enabled"] = static_cast<bool>(cfg->state.lldp_enabled);
state_cfg["clients_enabled"] =
static_cast<bool>(cfg->state.clients_enabled);
state_cfg["interval"] = cfg->state.interval;
state_cfg["max_mac_count"] = cfg->state.max_mac_count;
state_cfg["public_ip_lookup"] = cfg->state.public_ip_lookup;
std::ofstream os{metrics_config_path};
os << std::setw(4) << metrics_cfg << std::endl;
if (!os)
{
throw std::runtime_error{
"Failed to write metrics config to the file"};
}
}
void load_metrics_config(plat_metrics_cfg *cfg)
{
std::ifstream is{metrics_config_path};
// Metrics configuration doesn't exist yet, return silently without any
// error
if (!is.is_open())
return;
json metrics_cfg = json::parse(is);
if (metrics_cfg.contains("telemetry"))
{
const json &telemetry_cfg = metrics_cfg.at("telemetry");
cfg->telemetry.enabled =
telemetry_cfg.at("enabled").template get<bool>();
cfg->telemetry.interval =
telemetry_cfg.at("interval").template get<std::size_t>();
}
if (metrics_cfg.contains("healthcheck"))
{
const json &healthcheck_cfg = metrics_cfg.at("healthcheck");
cfg->healthcheck.enabled =
healthcheck_cfg.at("enabled").template get<bool>();
cfg->healthcheck.interval =
healthcheck_cfg.at("interval").template get<std::size_t>();
}
if (metrics_cfg.contains("state"))
{
const json &state_cfg = metrics_cfg.at("state");
cfg->state.enabled =
state_cfg.at("enabled").template get<bool>();
cfg->state.lldp_enabled =
state_cfg.at("lldp_enabled").template get<bool>();
cfg->state.clients_enabled =
state_cfg.at("clients_enabled").template get<bool>();
cfg->state.interval =
state_cfg.at("interval").template get<std::size_t>();
cfg->state.max_mac_count =
state_cfg.at("max_mac_count").template get<std::size_t>();
std::strncpy(
cfg->state.public_ip_lookup,
state_cfg.at("public_ip_lookup")
.template get<std::string>()
.c_str(),
std::size(cfg->state.public_ip_lookup) - 1);
}
}
periodic::~periodic()
{
if (thread_ && thread_->joinable())
thread_->join();
}
void periodic::start(
std::function<void()> callback,
std::chrono::seconds period)
{
if (thread_)
stop();
callback_ = std::move(callback);
period_ = std::move(period);
thread_ =
std::make_unique<std::thread>(std::bind(&periodic::worker, this));
}
void periodic::stop()
{
if (!thread_)
return;
{
std::scoped_lock lk{mut_};
stop_signal_ = true;
}
cv_.notify_one();
if (thread_->joinable())
thread_->join();
thread_.reset();
stop_signal_ = false;
}
void periodic::worker()
{
std::unique_lock lk{mut_};
while (!stop_signal_)
{
try
{
callback_();
}
catch (const std::exception &ex)
{
UC_LOG_ERR(
"Error occurred during periodic task execution: %s",
ex.what());
}
cv_.wait_for(lk, period_, [this] { return stop_signal_; });
}
}
} // namespace larch

View File

@@ -0,0 +1,62 @@
#ifndef LARCH_PLATFORM_METRICS_HPP_
#define LARCH_PLATFORM_METRICS_HPP_
#include <state.hpp>
#include <ucentral-platform.h>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <utility> // std::pair
#include <vector>
namespace larch {
struct state_data {
std::vector<plat_port_info> port_info;
std::vector<plat_learned_mac_addr> learned_mac_addrs;
std::vector<plat_gw_address> gw_addresses;
};
/**
* @brief Get state information.
*
* @return A pair of @c plat_state_info and @c state_data. The latter is used as
* an actual storage for all the gathered information, while the former
* references the data in it. Pay attention to the fact that @c state_data must
* outlive @c plat_state_info.
*/
std::pair<plat_state_info, state_data> get_state_info();
void save_metrics_config(const plat_metrics_cfg *cfg);
void load_metrics_config(plat_metrics_cfg *cfg);
class periodic {
public:
periodic() = default;
~periodic();
void start(std::function<void()> callback, std::chrono::seconds period);
void stop();
protected:
void worker();
std::unique_ptr<std::thread> thread_;
std::condition_variable cv_;
std::mutex mut_;
bool stop_signal_ = false;
std::chrono::seconds period_{};
std::function<void()> callback_;
};
} // namespace larch
#endif // !LARCH_PLATFORM_METRICS_HPP_

View File

@@ -0,0 +1,631 @@
#include <metrics.hpp>
#include <port.hpp>
#include <route.hpp>
#include <services.hpp>
#include <state.hpp>
#include <stp.hpp>
#include <syslog.hpp>
#include <utils.hpp>
#include <vlan.hpp>
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <grpc++/create_channel.h>
#include <grpc++/security/credentials.h>
#include <grpc/grpc.h>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <router-utils.h>
#include <cerrno>
#include <chrono>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility> // std::move
#include <sys/types.h>
#include <sys/wait.h>
#define UNUSED_PARAM(param) (void)((param))
#define RTTY_SESS_MAX (10)
using nlohmann::json;
namespace {
const std::string config_id_path = "/var/lib/ucentral/saved_config_id";
}
int plat_init(void)
{
using namespace larch;
state = std::make_unique<platform_state>();
auto verifier = grpc::experimental::ExternalCertificateVerifier::Create<certificate_verifier>();
grpc::experimental::TlsChannelCredentialsOptions options;
options.set_verify_server_certs(false);
options.set_certificate_verifier(verifier);
options.set_check_call_host(false);
auto credentials = grpc::experimental::TlsCredentials(options);
state->channel = grpc::CreateChannel("127.0.0.1:8080", credentials);
state->gnmi_stub = gnmi::gNMI::NewStub(state->channel);
// created new channel for gnoi
state->system_gnoi_stub = gnoi::system::System::NewStub(grpc::CreateChannel("127.0.0.1:8080", credentials));
state->stub_gnoi_sonic = gnoi::sonic::SonicService::NewStub(grpc::CreateChannel("127.0.0.1:8080", credentials));
state->telemetry_periodic = std::make_unique<periodic>();
state->state_periodic = std::make_unique<periodic>();
state->health_periodic = std::make_unique<periodic>();
state->redis_asic = std::make_unique<sw::redis::Redis>("tcp://127.0.0.1:6379/1");
state->redis_counters = std::make_unique<sw::redis::Redis>("tcp://127.0.0.1:6379/2");
/*
* Workaround to fix the issue when ucentral-client starts too early
* (before gNMI container or before ports are up)
*/
UC_LOG_INFO("Trying to get initial port list...");
std::vector<port> ports;
while (true)
{
try
{
ports = get_port_list();
if (ports.empty())
{
UC_LOG_DBG("Port list is empty");
}
else
{
break;
}
}
catch (const std::exception &ex)
{
UC_LOG_DBG(
"Failed to get initial port list: %s",
ex.what());
}
UC_LOG_DBG("Retrying in 10 seconds...");
std::this_thread::sleep_for(std::chrono::seconds{10});
}
UC_LOG_INFO("Successfully got the port list, continuing platform "
"initialization");
try
{
/*
* Get the state of interfaces addresses
*/
const plat_ipv4 no_address{false};
for (port &p : get_port_list())
{
const auto addresses = get_port_addresses(p);
state->interfaces_addrs.push_back(
addresses.empty() ? no_address : addresses[0]);
}
/*
* Initialize the router
*/
ucentral_router_fib_db_free(&state->router);
auto routes = get_routes(0);
if (ucentral_router_fib_db_alloc(&state->router, routes.size())
!= 0)
{
UC_LOG_CRIT("Failed to allocate FIB DB");
return -1;
}
for (auto &node : routes)
{
if (ucentral_router_fib_db_append(&state->router, &node)
!= 0)
{
UC_LOG_CRIT("Failed to append entry to FIB DB");
return -1;
}
}
}
catch (const std::exception &ex)
{
UC_LOG_CRIT("Platform initialization failed: %s", ex.what());
return -1;
}
return 0;
}
int plat_info_get(struct plat_platform_info *info)
{
using namespace larch;
try
{
const json metadata_json =
json::parse(
gnmi_get("/sonic-device_metadata:sonic-device_metadata/"
"DEVICE_METADATA/localhost"))
.at("sonic-device_metadata:localhost");
auto copy_from_json =
[](const json &obj, char *dest, std::size_t dest_size) {
std::strncpy(
dest,
obj.template get<std::string>().c_str(),
dest_size > 0 ? dest_size - 1 : 0);
};
copy_from_json(
metadata_json.at("platform"),
info->platform,
std::size(info->platform));
copy_from_json(
metadata_json.at("hwsku"),
info->hwsku,
std::size(info->hwsku));
copy_from_json(
metadata_json.at("mac"),
info->mac,
std::size(info->mac));
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get device metadata: %s", ex.what());
return -1;
}
return 0;
}
int plat_reboot(void)
{
grpc::Status status;
gnoi::system::RebootResponse gres;
gnoi::system::RebootRequest greq;
grpc::ClientContext context;
status = (larch::state->system_gnoi_stub)->Reboot(&context, greq, &gres);
if (!status.ok()) {
UC_LOG_ERR("Request failed");
UC_LOG_ERR("Code: %d", status.error_code());
return 1;
}
return 0;
}
int plat_config_apply(struct plat_cfg *cfg, uint32_t id)
{
using namespace larch;
UNUSED_PARAM(id);
try
{
apply_vlan_config(cfg);
apply_port_config(cfg);
apply_route_config(cfg);
apply_stp_config(cfg);
apply_services_config(cfg);
apply_vlan_ipv4_config(cfg);
/* apply_syslog_config() call must always be the last one */
apply_syslog_config(cfg->log_cfg, cfg->log_cfg_cnt);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to apply config: %s", ex.what());
return -1;
}
return 0;
}
int plat_config_save(uint64_t id)
{
// TO-DO: actually save the config, not only config id
std::ofstream os{config_id_path};
if (!os)
{
UC_LOG_ERR(
"Failed to save config id - can't open the file: %s",
std::strerror(errno));
return -1;
}
os << id << std::endl;
return 0;
}
int plat_config_restore(void)
{
return 0;
}
int plat_metrics_save(const struct plat_metrics_cfg *cfg)
{
try
{
larch::save_metrics_config(cfg);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to save metrics config: %s", ex.what());
return -1;
}
return 0;
}
int plat_metrics_restore(struct plat_metrics_cfg *cfg)
{
try
{
larch::load_metrics_config(cfg);
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to load metrics config: %s", ex.what());
return -1;
}
return 0;
}
int plat_saved_config_id_get(uint64_t *id)
{
std::ifstream is{config_id_path};
if (!is)
{
UC_LOG_ERR(
"Failed to get saved config id - can't open the file: %s",
std::strerror(errno));
return -1;
}
is >> *id;
if (!is.good())
{
UC_LOG_ERR("Failed to get saved config id - read failed");
return -1;
}
return 0;
}
void plat_config_destroy(struct plat_cfg *cfg)
{
UNUSED_PARAM(cfg);
}
int plat_factory_default(void)
{
return 0;
}
int plat_rtty(struct plat_rtty_cfg *rtty_cfg)
{
static pid_t child[RTTY_SESS_MAX];
int n, i, e;
/* wait the dead children */
for (i = 0; i < RTTY_SESS_MAX;) {
n = 0;
if (child[i] > 0) {
while ((n = waitpid(child[i], 0, WNOHANG)) < 0 && errno == EINTR);
}
if (n <= 0) {
++i;
} else {
if (RTTY_SESS_MAX > 1)
memmove(&child[i], &child[i+1], (RTTY_SESS_MAX-i-1)*sizeof(pid_t));
child[RTTY_SESS_MAX - 1] = -1;
}
}
/* find a place for a new session */
for (i = 0; i < RTTY_SESS_MAX && child[i] > 0; ++i);
/* if there are RTTY_SESS_MAX sessions, kill the oldest */
if (i == RTTY_SESS_MAX) {
if (child[0] <= 0) {
UC_LOG_CRIT("child[0]==%jd", (intmax_t)child[0]);
} else {
if (kill(child[0], SIGKILL)) {
UC_LOG_CRIT("kill failed: %s", strerror(errno));
} else {
while ((n = waitpid(child[0], 0, 0)) < 0 && errno == EINTR);
if (n < 0)
UC_LOG_CRIT("waitpid failed: %s", strerror(errno));
}
if (RTTY_SESS_MAX > 1)
memmove(&child[0], &child[1], (RTTY_SESS_MAX - 1) * sizeof(pid_t));
}
i = RTTY_SESS_MAX - 1;
}
child[i] = fork();
if (!child[i]) {
char argv[][128] = {
"--id=",
"--host=",
"--port=",
"--token="
};
setsid();
strcat(argv[0], rtty_cfg->id);
strcat(argv[1], rtty_cfg->server);
sprintf(argv[2], "--port=%u", rtty_cfg->port);
strcat(argv[3], rtty_cfg->token);
execl("/usr/local/bin/rtty", "rtty", argv[0], argv[1], argv[2], argv[3], "-d Larch Switch device", "-v", "-s", NULL);
e = errno;
UC_LOG_DBG("execv failed %d\n", e);
/* If we got to this line, that means execl failed, and
* currently, due to simple design (fork/exec), it's impossible
* to notify <main> process, that forked child failed to execl.
* TBD: notify about execl fail.
*/
_exit(e);
}
if (child[i] < (pid_t)0) {
return -1;
}
return 0;
}
int plat_upgrade(char *uri, char *signature)
{
UNUSED_PARAM(signature);
UNUSED_PARAM(uri);
return 0;
}
char *plat_log_pop(void)
{
return NULL;
}
void plat_log_flush(void)
{
}
char *plat_log_pop_concatenate(void)
{
return NULL;
}
int plat_event_subscribe(const struct plat_event_callbacks *cbs)
{
UNUSED_PARAM(cbs);
return 0;
}
void plat_event_unsubscribe(void)
{
}
void plat_health_poll(void (*cb)(struct plat_health_info *), int period_sec)
{
using namespace larch;
try
{
state->health_periodic->stop();
state->health_periodic->start(
[cb] {
plat_health_info health_info{100};
cb(&health_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start health poll: %s", ex.what());
}
}
void plat_health_poll_stop(void)
{
using namespace larch;
try
{
state->health_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop health poll: %s", ex.what());
}
}
void plat_telemetry_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
using namespace larch;
try
{
state->telemetry_periodic->stop();
state->telemetry_periodic->start(
[cb] {
auto [state_info, data] = get_state_info();
cb(&state_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start telemetry poll: %s", ex.what());
}
}
void plat_telemetry_poll_stop(void)
{
using namespace larch;
try
{
state->telemetry_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop telemetry poll: %s", ex.what());
}
}
void plat_state_poll(void (*cb)(struct plat_state_info *), int period_sec)
{
using namespace larch;
try
{
state->state_periodic->stop();
state->state_periodic->start(
[cb] {
auto [state_info, data] = get_state_info();
cb(&state_info);
},
std::chrono::seconds{period_sec});
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to start state poll: %s", ex.what());
}
}
void plat_state_poll_stop(void)
{
using namespace larch;
try
{
state->state_periodic->stop();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to stop state poll: %s", ex.what());
}
}
void plat_upgrade_poll(int (*cb)(struct plat_upgrade_info *), int period_sec)
{
UNUSED_PARAM(period_sec);
UNUSED_PARAM(cb);
}
void plat_upgrade_poll_stop(void)
{
}
int plat_run_script(struct plat_run_script *)
{
return 0;
}
int plat_port_list_get(uint16_t list_size, struct plat_ports_list *ports)
{
try
{
const auto port_list = larch::get_port_list();
if (port_list.size() < list_size)
{
UC_LOG_ERR(
"Too much ports requested (requested %hu, while "
"only %zu available)",
list_size,
port_list.size());
return -1;
}
auto it = port_list.cbegin();
for (plat_ports_list *node = ports; node; node = node->next)
{
std::strncpy(
node->name,
it++->name.c_str(),
sizeof(node->name) - 1);
}
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get list of ports: %s", ex.what());
return -1;
}
return 0;
}
int plat_port_num_get(uint16_t *num_of_active_ports)
{
try
{
*num_of_active_ports = larch::get_port_list().size();
}
catch (const std::exception &ex)
{
UC_LOG_ERR("Failed to get count of ports: %s", ex.what());
return -1;
}
return 0;
}
int plat_running_img_name_get(char *str, size_t str_max_len)
{
UNUSED_PARAM(str_max_len);
UNUSED_PARAM(str);
return 0;
}
int plat_revision_get(char *str, size_t str_max_len)
{
UNUSED_PARAM(str_max_len);
UNUSED_PARAM(str);
return 0;
}
int plat_reboot_cause_get(struct plat_reboot_cause *cause)
{
UNUSED_PARAM(cause);
return 0;
}
int plat_diagnostic(char *res_path)
{
UNUSED_PARAM(res_path);
return 0;
}

View File

@@ -0,0 +1,528 @@
#include <port.hpp>
#include <state.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#include <bitmap.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <algorithm> // std::find_if
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio> // std::snprintf, std::sscanf
#include <cstring>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility> // std::move
#include <vector>
using nlohmann::json;
namespace larch {
std::vector<port> get_port_list()
{
const json port_list_json =
json::parse(gnmi_get("/sonic-port:sonic-port/PORT/PORT_LIST"));
std::vector<port> port_list;
for (const auto port_json :
port_list_json.value("sonic-port:PORT_LIST", json::array()))
{
port &p = port_list.emplace_back();
p.name = port_json.at("name").template get<std::string>();
}
return port_list;
}
static bool get_port_oper_status(const std::string &port_name)
{
std::string port_status_data;
try
{
port_status_data = gnmi_get(
"/openconfig-interfaces:interfaces/interface[name="
+ port_name + "]/state/oper-status");
}
catch (const gnmi_exception &ex)
{
// For some reason there's no oper-status field in the gNMI
// response when carrier is down
return false;
}
const json port_status_json = json::parse(port_status_data);
const std::string port_status_str =
port_status_json.at("openconfig-interfaces:oper-status")
.template get<std::string>();
if (port_status_str == "UP")
return true;
else if (port_status_str == "DOWN")
return false;
else
{
UC_LOG_ERR(
"Unknown port oper status: %s",
port_status_str.c_str());
throw std::runtime_error{
"Unknown oper status: " + port_status_str};
}
}
static void set_port_admin_state(const std::string &port_name, bool state)
{
json port_state_json;
port_state_json["openconfig-interfaces:config"]["enabled"] = state;
gnmi_set(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/config",
port_state_json.dump());
}
static std::uint32_t get_port_speed(const std::string &port_name)
{
const json port_speed_json = json::parse(gnmi_get(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name
+ "]/speed"));
return port_speed_json["sonic-port:speed"]
.template get<std::uint32_t>();
}
static void set_port_speed(const std::string &port_name, std::uint32_t speed)
{
auto speed_to_num = [](std::uint32_t speed) -> std::uint32_t {
switch (speed)
{
case UCENTRAL_PORT_SPEED_10_E:
return 10;
case UCENTRAL_PORT_SPEED_100_E:
return 100;
case UCENTRAL_PORT_SPEED_1000_E:
return 1000;
case UCENTRAL_PORT_SPEED_2500_E:
return 2500;
case UCENTRAL_PORT_SPEED_5000_E:
return 5000;
case UCENTRAL_PORT_SPEED_10000_E:
return 10000;
case UCENTRAL_PORT_SPEED_25000_E:
return 25000;
case UCENTRAL_PORT_SPEED_40000_E:
return 40000;
case UCENTRAL_PORT_SPEED_100000_E:
return 100000;
default:
{
UC_LOG_ERR("Unknown port speed");
throw std::runtime_error{"Unknown port speed"};
}
}
};
json port_speed_json;
port_speed_json["name"] = port_name;
port_speed_json["speed"] = speed_to_num(speed);
json set_port_speed_json;
set_port_speed_json["sonic-port:PORT_LIST"] = {port_speed_json};
gnmi_set(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name + "]",
set_port_speed_json.dump());
}
static void set_port_mtu(const std::string &port_name, std::uint16_t mtu)
{
json port_mtu_json;
port_mtu_json["name"] = port_name;
port_mtu_json["mtu"] = mtu;
json set_port_mtu_json;
set_port_mtu_json["sonic-port:PORT_LIST"] = {port_mtu_json};
gnmi_set(
"/sonic-port:sonic-port/PORT/PORT_LIST[name=" + port_name + "]",
set_port_mtu_json.dump());
}
static std::unordered_map<std::string, std::uint64_t>
get_port_counters(const std::string &port_name)
{
const json port_counters_json = json::parse(gnmi_get(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/state/counters"));
std::unordered_map<std::string, std::uint64_t> counters;
if (!port_counters_json.contains("openconfig-interfaces:counters"))
return counters;
for (const auto &item :
port_counters_json["openconfig-interfaces:counters"].items())
{
counters[item.key()] =
std::stoull(item.value().template get<std::string>());
}
return counters;
}
static std::optional<plat_port_lldp_peer_info>
get_lldp_peer_info(const std::string &port_name)
{
/*
* Actually, more specific YANG path should be used here
* (/openconfig-lldp:lldp/interfaces/interface[name=<interface>]/neighbors/neighbor[id=<interface>])
* but for some reason gNMI response for this path is empty, so the
* workaround is to make more generic request and filter the response
* and find necessary data.
*/
const json lldp_json = json::parse(gnmi_get(
"/openconfig-lldp:lldp/interfaces/interface[name=" + port_name
+ "]"));
const auto &neighbors = lldp_json.at("openconfig-lldp:interface")
.at(0)
.at("neighbors")
.at("neighbor");
const auto neighbor_it = std::find_if(
neighbors.cbegin(),
neighbors.cend(),
[&port_name](const auto &neighbor) {
return neighbor.at("id").template get<std::string>()
== port_name;
});
if (neighbor_it == neighbors.cend())
{
throw std::runtime_error{"Failed to find LLDP neighbor"};
}
if (!neighbor_it->contains("capabilities"))
{
return std::nullopt;
}
plat_port_lldp_peer_info peer_info{};
for (const auto &cap : neighbor_it->at("capabilities").at("capability"))
{
const std::string name =
cap.at("name").template get<std::string>();
const bool enabled =
cap.at("state").at("enabled").template get<bool>();
if (name == "openconfig-lldp-types:MAC_BRIDGE")
peer_info.capabilities.is_bridge = enabled;
else if (name == "openconfig-lldp-types:ROUTER")
peer_info.capabilities.is_router = enabled;
else if (name == "openconfig-lldp-types:WLAN_ACCESS_POINT")
peer_info.capabilities.is_wlan_ap = enabled;
else if (name == "openconfig-lldp-types:STATION_ONLY")
peer_info.capabilities.is_station = enabled;
}
const json &state = neighbor_it->at("state");
std::strncpy(
peer_info.name,
state.at("system-name").template get<std::string>().c_str(),
std::size(peer_info.name) - 1);
std::strncpy(
peer_info.description,
state.at("system-description").template get<std::string>().c_str(),
std::size(peer_info.description) - 1);
std::strncpy(
peer_info.mac,
state.at("chassis-id").template get<std::string>().c_str(),
std::size(peer_info.mac) - 1);
std::strncpy(
peer_info.port,
neighbor_it->at("id").template get<std::string>().c_str(),
std::size(peer_info.port) - 1);
// Parse management addresses
const auto addresses = split_string(
state.at("management-address").template get<std::string>(),
",");
for (std::size_t i = 0; i < UCENTRAL_PORT_LLDP_PEER_INFO_MAX_MGMT_IPS;
++i)
{
if (i >= addresses.size())
break;
const char *address = addresses[i].c_str();
// Verify that retrieved address is either valid IPv4 or IPv6
// address. If so - copy it to peer_info.
bool success = false;
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, address, addr_buf.data()) == 1)
success = true;
else if (inet_pton(AF_INET6, address, addr_buf.data()) == 1)
success = true;
if (success)
std::strncpy(
peer_info.mgmt_ips[i],
address,
INET6_ADDRSTRLEN);
}
return peer_info;
}
std::vector<plat_ipv4> get_port_addresses(const port &p)
{
// TO-DO: should gnmi_exception be caught (this would mean that there're
// no addresses assigned to interface)?
const json addresses_json = json::parse(gnmi_get(
"/openconfig-interfaces:interfaces/interface[name=" + p.name
+ "]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/"
"addresses"));
if (!addresses_json.contains("openconfig-if-ip:addresses"))
return {};
std::vector<plat_ipv4> addresses;
for (const auto &address_json :
addresses_json.at("openconfig-if-ip:addresses")
.value("address", json::array()))
{
const json &config_json = address_json.at("config");
const std::string address_str =
config_json.at("ip").template get<std::string>();
plat_ipv4 address{};
if (inet_pton(AF_INET, address_str.c_str(), &address.subnet)
!= 1)
{
UC_LOG_ERR(
"Failed to parse interface IP address: %s",
address_str.c_str());
continue;
}
address.subnet_len = config_json.at("prefix-length")
.template get<std::int32_t>();
if (address.subnet_len < 0 || address.subnet_len > 32)
{
UC_LOG_ERR(
"Incorrect subnet length: %d (address %s)",
address.subnet_len,
address_str.c_str());
continue;
}
address.exist = true;
addresses.push_back(std::move(address));
}
return addresses;
}
static void
add_port_address(const std::string &port_name, const plat_ipv4 &address)
{
const std::string addr_str = addr_to_str(address.subnet);
json address_json;
address_json["ip"] = addr_str;
address_json["config"]["ip"] = addr_str;
address_json["config"]["prefix-length"] = address.subnet_len;
json port_json;
port_json["index"] = 0;
port_json["openconfig-if-ip:ipv4"]["addresses"]["address"] = {
address_json};
json add_port_json;
add_port_json["openconfig-interfaces:subinterface"] = {port_json};
gnmi_set(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/subinterfaces/subinterface",
add_port_json.dump());
}
static void
delete_port_address(const std::string &port_name, const plat_ipv4 &address)
{
const std::string addr_str = addr_to_str(address.subnet);
gnmi_operation op;
op.add_delete(
"/openconfig-interfaces:interfaces/interface[name=" + port_name
+ "]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/"
"addresses/address[ip="
+ addr_str + "]");
op.execute();
}
void apply_port_config(plat_cfg *cfg)
{
std::size_t i = 0;
BITMAP_FOR_EACH_BIT_SET(i, cfg->ports_to_cfg, MAX_NUM_OF_PORTS)
{
const plat_port &port = cfg->ports[i];
const std::string port_name = "Ethernet" + std::to_string(i);
const bool admin_state = port.state == UCENTRAL_PORT_ENABLED_E;
set_port_admin_state(port_name, admin_state);
if (admin_state)
{
set_port_speed(port_name, port.speed);
set_port_mtu(
port_name,
cfg->jumbo_frames ? 9216 : 1500);
}
/*
* Configure the interface address
*/
const plat_ipv4 &address = cfg->portsl2[i].ipv4;
plat_ipv4 &port_addr = state->interfaces_addrs.at(i);
if (address.exist)
{
if (!port_addr.exist
|| port_addr.subnet.s_addr != address.subnet.s_addr
|| port_addr.subnet_len != address.subnet_len)
{
if (port_addr.exist)
{
delete_port_address(
port_name,
port_addr);
}
add_port_address(port_name, address);
port_addr = address;
}
}
else if (port_addr.exist)
{
delete_port_address(port_name, port_addr);
port_addr = plat_ipv4{false};
}
}
}
std::vector<plat_port_info> get_port_info()
{
std::vector<port> ports = get_port_list();
std::vector<plat_port_info> ports_info(ports.size());
std::size_t i = 0;
for (auto &port_info : ports_info)
{
const std::string &port_name = ports[i++].name;
std::snprintf(
port_info.name,
PORT_MAX_NAME_LEN,
"%s",
port_name.c_str());
port_info.speed = get_port_speed(port_name);
port_info.duplex = true;
port_info.carrier_up = get_port_oper_status(port_name);
// Get port counters
std::unordered_map<std::string, std::uint64_t> counters;
try
{
counters = get_port_counters(port_name);
}
catch (const gnmi_exception &ex)
{
UC_LOG_ERR(
"Couldn't get counters for port %s: %s",
port_name.c_str(),
ex.what());
}
auto get_counter =
[&counters](const std::string &counter) -> std::uint64_t {
const auto it = counters.find(counter);
return it != counters.cend() ? it->second : 0;
};
auto &stats = port_info.stats;
stats.collisions = 0;
stats.multicast = 0;
stats.rx_bytes = get_counter("in-octets");
stats.rx_dropped = get_counter("in-discards");
stats.rx_error = get_counter("in-errors");
stats.rx_packets = get_counter("in-unicast-pkts")
+ get_counter("in-multicast-pkts")
+ get_counter("in-broadcast-pkts");
stats.tx_bytes = get_counter("out-octets");
stats.tx_dropped = get_counter("out-discards");
stats.tx_error = get_counter("out-errors");
stats.tx_packets = get_counter("out-unicast-pkts")
+ get_counter("out-multicast-pkts")
+ get_counter("out-broadcast-pkts");
try
{
auto peer_info_opt = get_lldp_peer_info(port_name);
if (peer_info_opt)
{
port_info.lldp_peer_info =
std::move(*peer_info_opt);
port_info.has_lldp_peer_info = 1;
}
}
catch (const std::exception &ex)
{
UC_LOG_DBG(
"Couldn't get LLDP peer info: %s",
ex.what());
}
}
return ports_info;
}
} // namespace larch

View File

@@ -0,0 +1,26 @@
#ifndef LARCH_PLATFORM_PORT_HPP_
#define LARCH_PLATFORM_PORT_HPP_
#include <ucentral-platform.h>
#include <cstddef>
#include <string>
#include <vector>
namespace larch {
struct port {
std::string name;
};
std::vector<port> get_port_list();
std::vector<plat_ipv4> get_port_addresses(const port &p);
void apply_port_config(plat_cfg *cfg);
std::vector<plat_port_info> get_port_info();
} // namespace larch
#endif // !LARCH_PLATFORM_PORT_HPP_

View File

@@ -0,0 +1,27 @@
file(GLOB proto_files CONFIGURE_DEPENDS "*.proto")
add_library(proto-objects OBJECT ${proto_files})
target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf gRPC::grpc++)
set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(PROTO_IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}")
target_include_directories(proto-objects PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>")
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})
protobuf_generate(
TARGET proto-objects
IMPORT_DIRS ${PROTO_IMPORT_DIRS}
PROTOC_OUT_DIR "${PROTO_BINARY_DIR}"
)
protobuf_generate(
TARGET proto-objects
LANGUAGE grpc
GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
PLUGIN "protoc-gen-grpc=\$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
IMPORT_DIRS ${PROTO_IMPORT_DIRS}
PROTOC_OUT_DIR "${PROTO_BINARY_DIR}"
)

View File

@@ -0,0 +1,49 @@
//
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package gnoi.common;
import "types.proto";
option go_package = "github.com/openconfig/gnoi/common";
// RemoteDownload defines the details for a device to initiate a file transfer
// from or to a remote location.
message RemoteDownload {
// The path information containing where to download the data from or to.
// For HTTP(S), this will be the URL (i.e. foo.com/file.tbz2).
// For SFTP and SCP, this will be the address:/path/to/file
// (i.e. host.foo.com:/bar/baz).
string path = 1;
enum Protocol {
UNKNOWN = 0;
SFTP = 1;
HTTP = 2;
HTTPS = 3;
SCP = 4;
}
Protocol protocol = 2;
types.Credentials credentials = 3;
// Optional source address used to initiate connections from the device.
// It can be either an IPv4 address or an IPv6 address, depending on the
// connection's destination address.
string source_address = 4;
}

View File

@@ -0,0 +1,457 @@
//
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
import "google/protobuf/any.proto";
import "google/protobuf/descriptor.proto";
import "gnmi_ext.proto";
// Package gNMI defines a service specification for the gRPC Network Management
// Interface. This interface is defined to be a standard interface via which
// a network management system ("client") can subscribe to state values,
// retrieve snapshots of state information, and manipulate the state of a data
// tree supported by a device ("target").
//
// This document references the gNMI Specification which can be found at
// http://github.com/openconfig/reference/blob/master/rpc/gnmi
package gnmi;
// Define a protobuf FileOption that defines the gNMI service version.
extend google.protobuf.FileOptions {
// The gNMI service semantic version.
string gnmi_service = 1001;
}
// gNMI_service is the current version of the gNMI service, returned through
// the Capabilities RPC.
option (gnmi_service) = "0.7.0";
service gNMI {
// Capabilities allows the client to retrieve the set of capabilities that
// is supported by the target. This allows the target to validate the
// service version that is implemented and retrieve the set of models that
// the target supports. The models can then be specified in subsequent RPCs
// to restrict the set of data that is utilized.
// Reference: gNMI Specification Section 3.2
rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
// Retrieve a snapshot of data from the target. A Get RPC requests that the
// target snapshots a subset of the data tree as specified by the paths
// included in the message and serializes this to be returned to the
// client using the specified encoding.
// Reference: gNMI Specification Section 3.3
rpc Get(GetRequest) returns (GetResponse);
// Set allows the client to modify the state of data on the target. The
// paths to modified along with the new values that the client wishes
// to set the value to.
// Reference: gNMI Specification Section 3.4
rpc Set(SetRequest) returns (SetResponse);
// Subscribe allows a client to request the target to send it values
// of particular paths within the data tree. These values may be streamed
// at a particular cadence (STREAM), sent one off on a long-lived channel
// (POLL), or sent as a one-off retrieval (ONCE).
// Reference: gNMI Specification Section 3.5
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
}
// Notification is a re-usable message that is used to encode data from the
// target to the client. A Notification carries two types of changes to the data
// tree:
// - Deleted values (delete) - a set of paths that have been removed from the
// data tree.
// - Updated values (update) - a set of path-value pairs indicating the path
// whose value has changed in the data tree.
// Reference: gNMI Specification Section 2.1
message Notification {
int64 timestamp = 1; // Timestamp in nanoseconds since Epoch.
Path prefix = 2; // Prefix used for paths in the message.
// An alias for the path specified in the prefix field.
// Reference: gNMI Specification Section 2.4.2
string alias = 3;
repeated Update update = 4; // Data elements that have changed values.
repeated Path delete = 5; // Data elements that have been deleted.
// This notification contains a set of paths that are always updated together
// referenced by a globally unique prefix.
bool atomic = 6;
}
// Update is a re-usable message that is used to store a particular Path,
// Value pair.
// Reference: gNMI Specification Section 2.1
message Update {
Path path = 1; // The path (key) for the update.
Value value = 2 [deprecated=true]; // The value (value) for the update.
TypedValue val = 3; // The explicitly typed update value.
uint32 duplicates = 4; // Number of coalesced duplicates.
}
// TypedValue is used to encode a value being sent between the client and
// target (originated by either entity).
message TypedValue {
// One of the fields within the val oneof is populated with the value
// of the update. The type of the value being included in the Update
// determines which field should be populated. In the case that the
// encoding is a particular form of the base protobuf type, a specific
// field is used to store the value (e.g., json_val).
oneof value {
string string_val = 1; // String value.
int64 int_val = 2; // Integer value.
uint64 uint_val = 3; // Unsigned integer value.
bool bool_val = 4; // Bool value.
bytes bytes_val = 5; // Arbitrary byte sequence value.
float float_val = 6; // Floating point value.
Decimal64 decimal_val = 7; // Decimal64 encoded value.
ScalarArray leaflist_val = 8; // Mixed type scalar array value.
google.protobuf.Any any_val = 9; // protobuf.Any encoded bytes.
bytes json_val = 10; // JSON-encoded text.
bytes json_ietf_val = 11; // JSON-encoded text per RFC7951.
string ascii_val = 12; // Arbitrary ASCII text.
// Protobuf binary encoded bytes. The message type is not included.
// See the specification at
// github.com/openconfig/reference/blob/master/rpc/gnmi/protobuf-vals.md
// for a complete specification.
bytes proto_bytes = 13;
}
}
// Path encodes a data tree path as a series of repeated strings, with
// each element of the path representing a data tree node name and the
// associated attributes.
// Reference: gNMI Specification Section 2.2.2.
message Path {
// Elements of the path are no longer encoded as a string, but rather within
// the elem field as a PathElem message.
repeated string element = 1 [deprecated=true];
string origin = 2; // Label to disambiguate path.
repeated PathElem elem = 3; // Elements of the path.
string target = 4; // The name of the target
// (Sec. 2.2.2.1)
}
// PathElem encodes an element of a gNMI path, along with any attributes (keys)
// that may be associated with it.
// Reference: gNMI Specification Section 2.2.2.
message PathElem {
string name = 1; // The name of the element in the path.
map<string, string> key = 2; // Map of key (attribute) name to value.
}
// Value encodes a data tree node's value - along with the way in which
// the value is encoded. This message is deprecated by gNMI 0.3.0.
// Reference: gNMI Specification Section 2.2.3.
message Value {
option deprecated = true;
bytes value = 1; // Value of the variable being transmitted.
Encoding type = 2; // Encoding used for the value field.
}
// Encoding defines the value encoding formats that are supported by the gNMI
// protocol. These encodings are used by both the client (when sending Set
// messages to modify the state of the target) and the target when serializing
// data to be returned to the client (in both Subscribe and Get RPCs).
// Reference: gNMI Specification Section 2.3
enum Encoding {
JSON = 0; // JSON encoded text.
BYTES = 1; // Arbitrarily encoded bytes.
PROTO = 2; // Encoded according to out-of-band agreed Protobuf.
ASCII = 3; // ASCII text of an out-of-band agreed format.
JSON_IETF = 4; // JSON encoded text as per RFC7951.
}
// Error message previously utilised to return errors to the client. Deprecated
// in favour of using the google.golang.org/genproto/googleapis/rpc/status
// message in the RPC response.
// Reference: gNMI Specification Section 2.5
message Error {
option deprecated = true;
uint32 code = 1; // Canonical gRPC error code.
string message = 2; // Human readable error.
google.protobuf.Any data = 3; // Optional additional information.
}
// Decimal64 is used to encode a fixed precision decimal number. The value
// is expressed as a set of digits with the precision specifying the
// number of digits following the decimal point in the digit set.
message Decimal64 {
int64 digits = 1; // Set of digits.
uint32 precision = 2; // Number of digits following the decimal point.
}
// ScalarArray is used to encode a mixed-type array of values.
message ScalarArray {
// The set of elements within the array. Each TypedValue message should
// specify only elements that have a field identifier of 1-7 (i.e., the
// values are scalar values).
repeated TypedValue element = 1;
}
// SubscribeRequest is the message sent by the client to the target when
// initiating a subscription to a set of paths within the data tree. The
// request field must be populated and the initial message must specify a
// SubscriptionList to initiate a subscription. The message is subsequently
// used to define aliases or trigger polled data to be sent by the target.
// Reference: gNMI Specification Section 3.5.1.1
message SubscribeRequest {
oneof request {
SubscriptionList subscribe = 1; // Specify the paths within a subscription.
Poll poll = 3; // Trigger a polled update.
AliasList aliases = 4; // Aliases to be created.
}
// Extension messages associated with the SubscribeRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// Poll is sent within a SubscribeRequest to trigger the device to
// send telemetry updates for the paths that are associated with the
// subscription.
// Reference: gNMI Specification Section Section 3.5.1.4
message Poll {
}
// SubscribeResponse is the message used by the target within a Subscribe RPC.
// The target includes a Notification message which is used to transmit values
// of the path(s) that are associated with the subscription. The same message
// is to indicate that the target has sent all data values once (is
// synchronized).
// Reference: gNMI Specification Section 3.5.1.4
message SubscribeResponse {
oneof response {
Notification update = 1; // Changed or sampled value for a path.
// Indicate target has sent all values associated with the subscription
// at least once.
bool sync_response = 3;
// Deprecated in favour of google.golang.org/genproto/googleapis/rpc/status
Error error = 4 [deprecated=true];
}
// Extension messages associated with the SubscribeResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// SubscriptionList is used within a Subscribe message to specify the list of
// paths that the client wishes to subscribe to. The message consists of a
// list of (possibly prefixed) paths, and options that relate to the
// subscription.
// Reference: gNMI Specification Section 3.5.1.2
message SubscriptionList {
Path prefix = 1; // Prefix used for paths.
repeated Subscription subscription = 2; // Set of subscriptions to create.
// Whether target defined aliases are allowed within the subscription.
bool use_aliases = 3;
QOSMarking qos = 4; // DSCP marking to be used.
// Mode of the subscription.
enum Mode {
STREAM = 0; // Values streamed by the target (Sec. 3.5.1.5.2).
ONCE = 1; // Values sent once-off by the target (Sec. 3.5.1.5.1).
POLL = 2; // Values sent in response to a poll request (Sec. 3.5.1.5.3).
}
Mode mode = 5;
// Whether elements of the schema that are marked as eligible for aggregation
// should be aggregated or not.
bool allow_aggregation = 6;
// The set of schemas that define the elements of the data tree that should
// be sent by the target.
repeated ModelData use_models = 7;
// The encoding that the target should use within the Notifications generated
// corresponding to the SubscriptionList.
Encoding encoding = 8;
// An optional field to specify that only updates to current state should be
// sent to a client. If set, the initial state is not sent to the client but
// rather only the sync message followed by any subsequent updates to the
// current state. For ONCE and POLL modes, this causes the server to send only
// the sync message (Sec. 3.5.2.3).
bool updates_only = 9;
}
// Subscription is a single request within a SubscriptionList. The path
// specified is interpreted (along with the prefix) as the elements of the data
// tree that the client is subscribing to. The mode determines how the target
// should trigger updates to be sent.
// Reference: gNMI Specification Section 3.5.1.3
message Subscription {
Path path = 1; // The data tree path.
SubscriptionMode mode = 2; // Subscription mode to be used.
uint64 sample_interval = 3; // ns between samples in SAMPLE mode.
// Indicates whether values that have not changed should be sent in a SAMPLE
// subscription.
bool suppress_redundant = 4;
// Specifies the maximum allowable silent period in nanoseconds when
// suppress_redundant is in use. The target should send a value at least once
// in the period specified.
uint64 heartbeat_interval = 5;
}
// SubscriptionMode is the mode of the subscription, specifying how the
// target must return values in a subscription.
// Reference: gNMI Specification Section 3.5.1.3
enum SubscriptionMode {
TARGET_DEFINED = 0; // The target selects the relevant mode for each element.
ON_CHANGE = 1; // The target sends an update on element value change.
SAMPLE = 2; // The target samples values according to the interval.
}
// QOSMarking specifies the DSCP value to be set on transmitted telemetry
// updates from the target.
// Reference: gNMI Specification Section 3.5.1.2
message QOSMarking {
uint32 marking = 1;
}
// Alias specifies a data tree path, and an associated string which defines an
// alias which is to be used for this path in the context of the RPC. The alias
// is specified as a string which is prefixed with "#" to disambiguate it from
// data tree element paths.
// Reference: gNMI Specification Section 2.4.2
message Alias {
Path path = 1; // The path to be aliased.
string alias = 2; // The alias value, a string prefixed by "#".
}
// AliasList specifies a list of aliases. It is used in a SubscribeRequest for
// a client to create a set of aliases that the target is to utilize.
// Reference: gNMI Specification Section 3.5.1.6
message AliasList {
repeated Alias alias = 1; // The set of aliases to be created.
}
// SetRequest is sent from a client to the target to update values in the data
// tree. Paths are either deleted by the client, or modified by means of being
// updated, or replaced. Where a replace is used, unspecified values are
// considered to be replaced, whereas when update is used the changes are
// considered to be incremental. The set of changes that are specified within
// a single SetRequest are considered to be a transaction.
// Reference: gNMI Specification Section 3.4.1
message SetRequest {
Path prefix = 1; // Prefix used for paths in the message.
repeated Path delete = 2; // Paths to be deleted from the data tree.
repeated Update replace = 3; // Updates specifying elements to be replaced.
repeated Update update = 4; // Updates specifying elements to updated.
// Extension messages associated with the SetRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// SetResponse is the response to a SetRequest, sent from the target to the
// client. It reports the result of the modifications to the data tree that were
// specified by the client. Errors for this RPC should be reported using the
// https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
// message in the RPC return. The gnmi.Error message can be used to add additional
// details where required.
// Reference: gNMI Specification Section 3.4.2
message SetResponse {
Path prefix = 1; // Prefix used for paths.
// A set of responses specifying the result of the operations specified in
// the SetRequest.
repeated UpdateResult response = 2;
Error message = 3 [deprecated=true]; // The overall status of the transaction.
int64 timestamp = 4; // Timestamp of transaction (ns since epoch).
// Extension messages associated with the SetResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 5;
}
// UpdateResult is used within the SetResponse message to communicate the
// result of an operation specified within a SetRequest message.
// Reference: gNMI Specification Section 3.4.2
message UpdateResult {
// The operation that was associated with the Path specified.
enum Operation {
INVALID = 0;
DELETE = 1; // The result relates to a delete of Path.
REPLACE = 2; // The result relates to a replace of Path.
UPDATE = 3; // The result relates to an update of Path.
}
// Deprecated timestamp for the UpdateResult, this field has been
// replaced by the timestamp within the SetResponse message, since
// all mutations effected by a set should be applied as a single
// transaction.
int64 timestamp = 1 [deprecated=true];
Path path = 2; // Path associated with the update.
Error message = 3 [deprecated=true]; // Status of the update operation.
Operation op = 4; // Update operation type.
}
// GetRequest is sent when a client initiates a Get RPC. It is used to specify
// the set of data elements for which the target should return a snapshot of
// data. The use_models field specifies the set of schema modules that are to
// be used by the target - where use_models is not specified then the target
// must use all schema models that it has.
// Reference: gNMI Specification Section 3.3.1
message GetRequest {
Path prefix = 1; // Prefix used for paths.
repeated Path path = 2; // Paths requested by the client.
// Type of elements within the data tree.
enum DataType {
ALL = 0; // All data elements.
CONFIG = 1; // Config (rw) only elements.
STATE = 2; // State (ro) only elements.
// Data elements marked in the schema as operational. This refers to data
// elements whose value relates to the state of processes or interactions
// running on the device.
OPERATIONAL = 3;
}
DataType type = 3; // The type of data being requested.
Encoding encoding = 5; // Encoding to be used.
repeated ModelData use_models = 6; // The schema models to be used.
// Extension messages associated with the GetRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 7;
}
// GetResponse is used by the target to respond to a GetRequest from a client.
// The set of Notifications corresponds to the data values that are requested
// by the client in the GetRequest.
// Reference: gNMI Specification Section 3.3.2
message GetResponse {
repeated Notification notification = 1; // Data values.
Error error = 2 [deprecated=true]; // Errors that occurred in the Get.
// Extension messages associated with the GetResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 3;
}
// CapabilityRequest is sent by the client in the Capabilities RPC to request
// that the target reports its capabilities.
// Reference: gNMI Specification Section 3.2.1
message CapabilityRequest {
// Extension messages associated with the CapabilityRequest. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 1;
}
// CapabilityResponse is used by the target to report its capabilities to the
// client within the Capabilities RPC.
// Reference: gNMI Specification Section 3.2.2
message CapabilityResponse {
repeated ModelData supported_models = 1; // Supported schema models.
repeated Encoding supported_encodings = 2; // Supported encodings.
string gNMI_version = 3; // Supported gNMI version.
// Extension messages associated with the CapabilityResponse. See the
// gNMI extension specification for further definition.
repeated gnmi_ext.Extension extension = 4;
}
// ModelData is used to describe a set of schema modules. It can be used in a
// CapabilityResponse where a target reports the set of modules that it
// supports, and within the SubscribeRequest and GetRequest messages to specify
// the set of models from which data tree elements should be reported.
// Reference: gNMI Specification Section 3.2.3
message ModelData {
string name = 1; // Name of the model.
string organization = 2; // Organization publishing the model.
string version = 3; // Semantic version of the model.
}

View File

@@ -0,0 +1,89 @@
//
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
// Package gnmi_ext defines a set of extensions messages which can be optionally
// included with the request and response messages of gNMI RPCs. A set of
// well-known extensions are defined within this file, along with a registry for
// extensions defined outside of this package.
package gnmi_ext;
// The Extension message contains a single gNMI extension.
message Extension {
oneof ext {
RegisteredExtension registered_ext = 1; // A registered extension.
// Well known extensions.
MasterArbitration master_arbitration = 2; // Master arbitration extension.
History history = 3; // History extension.
}
}
// The RegisteredExtension message defines an extension which is defined outside
// of this file.
message RegisteredExtension {
ExtensionID id = 1; // The unique ID assigned to this extension.
bytes msg = 2; // The binary-marshalled protobuf extension payload.
}
// RegisteredExtension is an enumeration acting as a registry for extensions
// defined by external sources.
enum ExtensionID {
EID_UNSET = 0;
// New extensions are to be defined within this enumeration - their definition
// MUST link to a reference describing their implementation.
// An experimental extension that may be used during prototyping of a new
// extension.
EID_EXPERIMENTAL = 999;
}
// MasterArbitration is used to select the master among multiple gNMI clients
// with the same Roles. The client with the largest election_id is honored as
// the master.
// The document about gNMI master arbitration can be found at
// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-master-arbitration.md
message MasterArbitration {
Role role = 1;
Uint128 election_id = 2;
}
// Representation of unsigned 128-bit integer.
message Uint128 {
uint64 high = 1;
uint64 low = 2;
}
// There can be one master for each role. The role is identified by its id.
message Role {
string id = 1;
// More fields can be added if needed, for example, to specify what paths the
// role can read/write.
}
// The History extension allows clients to request historical data. Its
// spec can be found at
// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-history.md
message History {
oneof request {
int64 snapshot_time = 1; // Nanoseconds since the epoch
TimeRange range = 2;
}
}
message TimeRange {
int64 start = 1; // Nanoseconds since the epoch
int64 end = 2; // Nanoseconds since the epoch
}

View File

@@ -0,0 +1,315 @@
syntax = "proto3";
package gnoi.sonic;
//option (types.gnoi_version) = "0.1.0";
service SonicService {
rpc ShowTechsupport (TechsupportRequest) returns (TechsupportResponse) {}
rpc ShowTechsupportCancel (TechsupportCancelRequest) returns (TechsupportCancelResponse) {}
rpc Sum (SumRequest) returns (SumResponse) {}
rpc ImageInstall(ImageInstallRequest) returns (ImageInstallResponse) {}
rpc ImageRemove(ImageRemoveRequest) returns (ImageRemoveResponse) {}
rpc ImageDefault(ImageDefaultRequest) returns (ImageDefaultResponse) {}
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {}
rpc Refresh(RefreshRequest) returns (RefreshResponse) {}
rpc ClearNeighbors(ClearNeighborsRequest) returns (ClearNeighborsResponse) {}
rpc VlanReplace(VlanReplaceRequest) returns (VlanReplaceResponse) {}
rpc GetAuditLog (GetAuditLogRequest) returns (GetAuditLogResponse) {}
rpc ClearAuditLog (ClearAuditLogRequest) returns (ClearAuditLogResponse) {}
rpc ShowSysLog(ShowSysLogRequest) returns (GetShowSysLogResponse) {}
rpc GetEvents (GetEventsRequest) returns (GetEventsResponse) {}
rpc GetAlarms (GetAlarmsRequest) returns (GetAlarmsResponse) {}
rpc AckAlarms (AckAlarmsRequest) returns (AckAlarmsResponse) {}
rpc UnackAlarms (UnackAlarmsRequest) returns (UnackAlarmsResponse) {}
rpc GetEventProfile (GetEventProfileRequest) returns (GetEventProfileResponse) {}
rpc SetEventProfile (SetEventProfileRequest) returns (SetEventProfileResponse) {}
}
message SonicOutput {
int32 status = 1;
string status_detail = 2;
}
message GetEventProfileRequest {
}
message GetEventProfileResponse {
message Output {
string file_name = 1;
repeated string file_list = 2;
}
Output output = 1;
}
message SetEventProfileRequest {
message Input {
string filename =1;
}
Input input =1;
}
message SetEventProfileResponse {
SonicOutput output = 1;
}
message AckAlarmsRequest {
message Input {
repeated string id = 1;
}
Input input = 1;
}
message AckAlarmsResponse {
SonicOutput output = 1;
}
message UnackAlarmsRequest {
message Input {
repeated string id = 1;
}
Input input = 1;
}
message UnackAlarmsResponse {
SonicOutput output = 1;
}
message EventTimeFilter {
string begin = 1;
string end = 2;
}
message EventId {
string begin = 1;
string end = 2;
}
message EventsFilter {
EventTimeFilter time = 1;
string interval = 2;
string severity = 3;
EventId id = 4;
}
message GetEventsRequest {
EventsFilter input = 1;
}
message Event {
string id = 1;
string resource = 2;
string text = 3;
string time_created = 4;
string type_id = 5;
string severity = 6;
string action = 7;
}
message Events {
repeated Event EVENT_LIST = 1;
}
message EventsResponse {
int32 status = 1;
string status_detail = 2;
Events EVENT =3;
}
message GetEventsResponse {
EventsResponse output = 1;
}
message Alarm {
string id = 1;
string resource = 2;
string text = 3;
string time_created = 4;
string type_id = 5;
string severity = 6;
bool acknowledged = 7;
string acknowledge_time = 8;
}
message GetAlarmsRequest {
EventsFilter input = 1;
}
message Alarms {
repeated Alarm ALARM_LIST = 1;
}
message AlarmsResponse {
int32 status = 1;
string status_detail = 2;
Alarms ALARM =3;
}
message GetAlarmsResponse {
AlarmsResponse output = 1;
}
message TechsupportRequest {
message Input {
string date = 1;
}
Input input = 1;
}
message TechsupportResponse {
message Output {
uint32 status = 1;
string status_detail = 2;
string output_filename = 3;
}
Output output = 1;
}
message TechsupportCancelRequest {
}
message TechsupportCancelResponse {
message Output {
uint32 status = 1;
string status_detail = 2;
}
Output output = 1;
}
message ClearNeighborsRequest {
message Input {
bool force = 1;
string family = 2;
string ip = 3;
string ifname = 4;
}
Input input = 1;
}
message ClearNeighborsResponse {
message Output {
string response = 1;
}
Output output = 1;
}
message VlanReplaceRequest {
message Input {
string ifname = 1;
string vlanlist = 2;
}
Input input = 1;
}
message VlanReplaceResponse {
message Output {
string response = 1;
}
Output output = 1;
}
message SumRequest {
message Input {
int32 left = 1;
int32 right = 2;
}
Input input = 1;
}
message SumResponse {
message Output {
int32 result = 1;
}
Output output = 1;
}
message ImageInstallRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageInstallResponse {
SonicOutput output = 1;
}
message ImageRemoveRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageRemoveResponse {
SonicOutput output = 1;
}
message ImageDefaultRequest {
message Input {
string imagename = 1;
}
Input input = 1;
}
message ImageDefaultResponse {
SonicOutput output = 1;
}
message GetAuditLogRequest {
message Input {
string content_type = 1;
}
Input input = 1;
}
message GetAuditLogResponse {
message AuditOutput {
repeated string audit_content = 1;
}
AuditOutput output = 1;
}
message ClearAuditLogRequest {
}
message ClearAuditLogResponse {
SonicOutput output = 1;
}
message ShowSysLogRequest {
message Input {
int32 num_lines = 1 ;
}
Input input = 1;
}
message ShowSysLogResponse {
repeated string status_detail = 1;
}
message GetShowSysLogResponse {
ShowSysLogResponse output = 1;
}
message JwtToken {
string access_token = 1;
string type = 2;
int64 expires_in = 3;
}
message AuthenticateRequest {
string username = 1;
string password = 2;
}
message AuthenticateResponse {
JwtToken Token = 1;
}
message RefreshRequest {
}
message RefreshResponse {
JwtToken Token = 1;
}

View File

@@ -0,0 +1,339 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Generic Network Operation Interface, GNOI, defines a set of RPC's used for
// the operational aspects of network targets. These services are meant to be
// used in conjunction with GNMI for all target state and operational aspects
// of a network target. The gnoi.system.Service is the only mandatory vendor
// implementation.
syntax = "proto3";
package gnoi.system;
import "common.proto";
import "types.proto";
option (types.gnoi_version) = "1.0.0";
// The gNOI service is a collection of operational RPC's that allow for the
// management of a target outside of the configuration and telemetry pipeline.
service System {
// Ping executes the ping command on the target and streams back
// the results. Some targets may not stream any results until all
// results are in. The stream should provide single ping packet responses
// and must provide summary statistics.
rpc Ping(PingRequest) returns (stream PingResponse) {}
// Traceroute executes the traceroute command on the target and streams back
// the results. Some targets may not stream any results until all
// results are in. If a hop count is not explicitly provided,
// 30 is used.
rpc Traceroute(TracerouteRequest) returns (stream TracerouteResponse) {}
// Time returns the current time on the target. Time is typically used to
// test if a target is actually responding.
rpc Time(TimeRequest) returns (TimeResponse) {}
// SetPackage places a software package (possibly including bootable images)
// on the target. The file is sent in sequential messages, each message
// up to 64KB of data. A final message must be sent that includes the hash
// of the data sent. An error is returned if the location does not exist or
// there is an error writing the data. If no checksum is received, the target
// must assume the operation is incomplete and remove the partially
// transmitted file. The target should initially write the file to a temporary
// location so a failure does not destroy the original file.
rpc SetPackage(stream SetPackageRequest) returns (SetPackageResponse) {}
// SwitchControlProcessor will switch from the current route processor to the
// provided route processor. If the current route processor is the same as the
// one provided it is a NOOP. If the target does not exist an error is
// returned.
rpc SwitchControlProcessor(SwitchControlProcessorRequest)
returns (SwitchControlProcessorResponse) {}
// Reboot causes the target to reboot, possibly at some point in the future.
// If the method of reboot is not supported then the Reboot RPC will fail.
// If the reboot is immediate the command will block until the subcomponents
// have restarted.
// If a reboot on the active control processor is pending the service must
// reject all other reboot requests.
// If a reboot request for active control processor is initiated with other
// pending reboot requests it must be rejected.
rpc Reboot(RebootRequest) returns (RebootResponse) {}
// RebootStatus returns the status of reboot for the target.
rpc RebootStatus(RebootStatusRequest) returns (RebootStatusResponse) {}
// CancelReboot cancels any pending reboot request.
rpc CancelReboot(CancelRebootRequest) returns (CancelRebootResponse) {}
// KillProcess kills an OS process and optionally restarts it.
rpc KillProcess(KillProcessRequest) returns (KillProcessResponse) {}
// TODO(hines): Add RotateCertificate workflow
// TODO(hines): Add SetSSHPrivateKey
}
message SwitchControlProcessorRequest {
types.Path control_processor = 1;
}
message SwitchControlProcessorResponse {
types.Path control_processor = 1;
string version = 2; // Current software version.
int64 uptime = 3; // Uptime in nanoseconds since epoch.
}
// A RebootRequest requests the specified target be rebooted using the specified
// method after the specified delay. Only the DEFAULT method with a delay of 0
// is guaranteed to be accepted for all target types.
message RebootRequest {
RebootMethod method = 1;
// Delay in nanoseconds before issuing reboot.
uint64 delay = 2;
// Informational reason for the reboot.
string message = 3;
// Optional sub-components to reboot.
repeated types.Path subcomponents = 4;
// Force reboot if sanity checks fail. (ex. uncommited configuration)
bool force = 5;
}
message RebootResponse {
}
// A RebootMethod determines what should be done with a target when a Reboot is
// requested. Only the COLD method is required to be supported by all
// targets. Methods the target does not support should result in failure.
//
// It is vendor defined if a WARM reboot is the same as an NSF reboot.
enum RebootMethod {
UNKNOWN = 0; // Invalid default method.
COLD = 1; // Shutdown and restart OS and all hardware.
POWERDOWN = 2; // Halt and power down, if possible.
HALT = 3; // Halt, if possible.
WARM = 4; // Reload configuration but not underlying hardware.
NSF = 5; // Non-stop-forwarding reboot, if possible.
// RESET method is deprecated in favor of the gNOI FactoryReset.Start().
reserved 6;
POWERUP = 7; // Apply power, no-op if power is already on.
}
// A CancelRebootRequest requests the cancelation of any outstanding reboot
// request.
message CancelRebootRequest {
string message = 1; // informational reason for the cancel
repeated types.Path subcomponents = 2; // optional sub-components.
}
message CancelRebootResponse {
}
message RebootStatusRequest {
repeated types.Path subcomponents = 1; // optional sub-component.
}
message RebootStatusResponse {
bool active = 1; // If reboot is active.
uint64 wait = 2; // Time left until reboot.
uint64 when = 3; // Time to reboot in nanoseconds since the epoch.
string reason = 4; // Reason for reboot.
uint32 count = 5; // Number of reboots since active.
}
// A TimeRequest requests the current time accodring to the target.
message TimeRequest {
}
message TimeResponse {
uint64 time = 1; // Current time in nanoseconds since epoch.
}
// A PingRequest describes the ping operation to perform. Only the destination
// fields is required. Any field not specified is set to a reasonable server
// specified value. Not all fields are supported by all vendors.
//
// A count of 0 defaults to a vendor specified value, typically 5. A count of
// -1 means continue until the RPC times out or is canceled.
//
// If the interval is -1 then a flood ping is issued.
//
// If the size is 0, the vendor default size will be used (typically 56 bytes).
message PingRequest {
string destination = 1; // Destination address to ping. required.
string source = 2; // Source address to ping from.
int32 count = 3; // Number of packets.
int64 interval = 4; // Nanoseconds between requests.
int64 wait = 5; // Nanoseconds to wait for a response.
int32 size = 6; // Size of request packet. (excluding ICMP header)
bool do_not_fragment = 7; // Set the do not fragment bit. (IPv4 destinations)
bool do_not_resolve = 8; // Do not try resolve the address returned.
types.L3Protocol l3protocol = 9; // Layer3 protocol requested for the ping.
}
// A PingResponse represents either the reponse to a single ping packet
// (the bytes field is non-zero) or the summary statistics (sent is non-zero).
//
// For a single ping packet, time is the round trip time, in nanoseconds. For
// summary statistics, it is the time spent by the ping operation. The time is
// not always present in summary statistics. The std_dev is not always present
// in summary statistics.
message PingResponse {
string source = 1; // Source of received bytes.
int64 time = 2;
int32 sent = 3; // Total packets sent.
int32 received = 4; // Total packets received.
int64 min_time = 5; // Minimum round trip time in nanoseconds.
int64 avg_time = 6; // Average round trip time in nanoseconds.
int64 max_time = 7; // Maximum round trip time in nanoseconds.
int64 std_dev = 8; // Standard deviation in round trip time.
int32 bytes = 11; // Bytes received.
int32 sequence = 12; // Sequence number of received packet.
int32 ttl = 13; // Remaining time to live value.
}
// A TracerouteRequest describes the traceroute operation to perform. Only the
// destination field is required. Any field not specified is set to a
// reasonable server specified value. Not all fields are supported by all
// vendors.
//
// If the hop_count is -1 the traceroute will continue forever.
//
message TracerouteRequest {
string source = 1; // Source address to ping from.
string destination = 2; // Destination address to ping.
uint32 initial_ttl = 3; // Initial TTL. (default=1)
int32 max_ttl = 4; // Maximum number of hops. (default=30)
int64 wait = 5; // Nanoseconds to wait for a response.
bool do_not_fragment = 6; // Set the do not fragment bit. (IPv4 destinations)
bool do_not_resolve = 7; // Do not try resolve the address returned.
types.L3Protocol l3protocol = 8; // Layer-3 protocol requested for the ping.
enum L4Protocol {
ICMP = 0; // Use ICMP ECHO for probes.
TCP = 1; // Use TCP SYN for probes.
UDP = 2; // Use UDP for probes.
}
L4Protocol l4protocol = 9;
bool do_not_lookup_asn = 10; // Do not try to lookup ASN
}
// A TraceRouteResponse contains the result of a single traceoute packet.
//
// There may be an optional initial response that provides information about the
// traceroute request itself and contains at least one of the fields in the the
// initial block of fields and none of the fields following that block. All
// subsequent responses should not contain any of these fields.
//
// Typically multiple responses are received for each hop, as the packets are
// received.
//
// The mpls field maps names to values. Example names include "Label", "CoS",
// "TTL", "S", and "MRU".
// [Perhaps we should list the canonical names that must be used when
// applicable].
message TracerouteResponse {
// The following fields are only filled in for the first message.
// If any of these fields are specified, all fields following this
// block are left unspecified.
string destination_name = 1;
string destination_address = 2;
int32 hops = 3;
int32 packet_size = 4;
// State is the resulting state of a single traceoroute packet.
enum State {
DEFAULT = 0; // Normal hop response.
NONE = 1; // No response.
UNKNOWN = 2; // Unknown response state.
ICMP = 3; // See icmp_code field.
HOST_UNREACHABLE = 4; // Host unreachable.
NETWORK_UNREACHABLE = 5; // Network unreachable.
PROTOCOL_UNREACHABLE = 6; // Protocol unreachable.
SOURCE_ROUTE_FAILED = 7; // Source route failed.
FRAGMENTATION_NEEDED = 8; // Fragmentation needed.
PROHIBITED = 9; // Communication administratively prohibited.
PRECEDENCE_VIOLATION = 10; // Host precedence violation.
PRECEDENCE_CUTOFF = 11; // Precedence cutoff in effect.
}
// The following fields provide the disposition of a single traceroute
// packet.
int32 hop = 5; // Hop number. required.
string address = 6; // Address of responding hop. required.
string name = 7; // Name of responding hop.
int64 rtt = 8; // Round trip time in nanoseconds.
State state = 9; // State of this hop.
int32 icmp_code = 10; // Code terminating hop.
map<string, string> mpls = 11; // MPLS key/value pairs.
repeated int32 as_path = 12; // AS path.
}
// Package defines a single package file to be placed on the target.
message Package {
// Destination path and filename of the package.
string filename = 1;
// Version of the package. (vendor internal name)
string version = 4;
// Indicates that the package should be made active after receipt on
// the device. For system image packages, the new image is expected to
// be active after a reboot.
bool activate = 5;
// Details for the device to download the package from a remote location.
common.RemoteDownload remote_download = 6;
}
// SetPackageRequest will place the package onto the target and optionally mark
// it as the next bootable image. The initial message must be a package
// message containing the filename and information about the file. Following the
// initial message the contents are then streamed in maximum 64k chunks. The
// final message must be a hash message contains the hash of the file contents.
message SetPackageRequest {
oneof request {
Package package = 1;
bytes contents = 2;
types.HashType hash = 3; // Verification hash of data.
}
}
message SetPackageResponse {
}
// KillProcessRequest describes the process kill operation. Either a pid or
// process name must be specified, and a termination signal must be specified.
message KillProcessRequest {
// Process ID of the process to be killed.
uint32 pid = 1;
// Name of the process to be killed.
string name = 2;
// Termination signal sent to the process.
enum Signal {
SIGNAL_UNSPECIFIED = 0; // Invalid default.
SIGNAL_TERM = 1; // Terminate the process gracefully.
SIGNAL_KILL = 2; // Terminate the process immediately.
SIGNAL_HUP = 3; // Reload the process configuration.
}
Signal signal = 3;
// Whether the process should be restarted after termination.
// This value is ignored when the termination signal is SIGHUP.
bool restart = 4;
}
// KillProcessResponse describes the result of the process kill operation.
message KillProcessResponse {
}

View File

@@ -0,0 +1,75 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package gnoi.types;
import "google/protobuf/descriptor.proto";
option go_package = "github.com/openconfig/gnoi/types";
// Define a protobuf FileOption that defines the gNOI service version.
extend google.protobuf.FileOptions {
// The gNOI service semantic version.
string gnoi_version = 1002;
}
// Generic Layer 3 Protocol enumeration.
enum L3Protocol {
UNSPECIFIED = 0;
IPV4 = 1;
IPV6 = 2;
}
// HashType defines the valid hash methods for data verification. UNSPECIFIED
// should be treated an error.
message HashType {
enum HashMethod {
UNSPECIFIED = 0;
SHA256 = 1;
SHA512 = 2;
MD5 = 3;
}
HashMethod method = 1;
bytes hash = 2;
}
// Path encodes a data tree path as a series of repeated strings, with
// each element of the path representing a data tree node name and the
// associated attributes.
// Reference: gNMI Specification Section 2.2.2.
message Path {
string origin = 2; // Label to disambiguate path.
repeated PathElem elem = 3; // Elements of the path.
}
// PathElem encodes an element of a gNMI path, along with any attributes (keys)
// that may be associated with it.
// Reference: gNMI Specification Section 2.2.2.
message PathElem {
string name = 1; // The name of the element in the path.
map<string, string> key = 2; // Map of key (attribute) name to value.
}
// Credentials defines credentials needed to perform authentication on a device.
message Credentials {
string username = 1;
oneof password {
string cleartext = 2;
HashType hashed = 3;
}
}

View File

@@ -0,0 +1,408 @@
#include <route.hpp>
#include <sai_redis.hpp>
#include <state.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#include <sw/redis++/redis++.h>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <router-utils.h>
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <algorithm> // std::find_if
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iterator> // std::inserter
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using nlohmann::json;
namespace larch {
static std::string fib_key_to_str(const ucentral_router_fib_key &fib_key)
{
std::array<char, INET_ADDRSTRLEN + 1> ip_buf{};
if (!inet_ntop(AF_INET, &fib_key.prefix, ip_buf.data(), ip_buf.size()))
throw std::runtime_error{
"Failed to convert FIB prefix from binary to text form"};
std::string ip{ip_buf.data()};
ip += "/" + std::to_string(fib_key.prefix_len);
return ip;
}
void create_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key,
const ucentral_router_fib_info &fib_info)
{
// VRF is not supported
if (router_id != 0)
return;
json route_json;
route_json["prefix"] = fib_key_to_str(fib_key);
route_json["vrf-name"] = "default";
switch (fib_info.type)
{
case ucentral_router_fib_info::UCENTRAL_ROUTE_BLACKHOLE:
{
route_json["blackhole"] = "true,false";
break;
}
case ucentral_router_fib_info::UCENTRAL_ROUTE_CONNECTED:
{
route_json["ifname"] =
"Vlan" + std::to_string(fib_info.connected.vid);
break;
}
case ucentral_router_fib_info::UCENTRAL_ROUTE_NH:
{
route_json["ifname"] =
"Vlan" + std::to_string(fib_info.nh.vid);
std::array<char, INET_ADDRSTRLEN + 1> ip_buf{};
if (!inet_ntop(
AF_INET,
&fib_info.nh.gw,
ip_buf.data(),
ip_buf.size()))
{
throw std::runtime_error{
"Failed to convert gateway address from "
"binary to text form"};
}
break;
}
default:
{
return;
}
}
json add_route_json;
add_route_json["sonic-static-route:sonic-static-route"]["STATIC_ROUTE"]
["STATIC_ROUTE_LIST"] = {route_json};
gnmi_set(
"/sonic-static-route:sonic-static-route/",
add_route_json.dump());
}
void remove_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key)
{
// VRF is not supported
if (router_id != 0)
return;
gnmi_operation op;
op.add_delete(
"/sonic-static-route:sonic-static-route/STATIC_ROUTE/"
"STATIC_ROUTE_LIST[prefix="
+ fib_key_to_str(fib_key) + "][vrf-name=default]");
op.execute();
}
std::vector<ucentral_router_fib_node> get_routes(std::uint16_t router_id)
{
// VRF is not supported
if (router_id != 0)
return {};
const json static_routes_json =
json::parse(gnmi_get("/sonic-static-route:sonic-static-route/"
"STATIC_ROUTE/STATIC_ROUTE_LIST"));
std::vector<ucentral_router_fib_node> routes;
for (const auto &route_json : static_routes_json.value(
"sonic-static-route:STATIC_ROUTE_LIST",
json::array()))
{
if (route_json.contains("vrf-name")
&& route_json.at("vrf-name").template get<std::string>()
!= "default")
{
continue;
}
if (!route_json.contains("prefix"))
continue;
ucentral_router_fib_info fib_info{};
// For now only blackhole is supported
if (route_json.contains("blackhole"))
fib_info.type =
ucentral_router_fib_info::UCENTRAL_ROUTE_BLACKHOLE;
else
continue;
ucentral_router_fib_key fib_key{};
const int ret = inet_net_pton(
AF_INET,
route_json.at("prefix").template get<std::string>().c_str(),
&fib_key.prefix,
sizeof(fib_key.prefix));
if (ret == -1)
continue;
fib_key.prefix_len = ret;
routes.push_back({fib_key, fib_info});
}
return routes;
}
struct router_interface {
std::string mac;
sai::object_id port_oid;
};
static std::optional<router_interface>
parse_router_interface(const sai::object_id &oid)
{
router_interface router_if{};
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
"ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE:" + oid,
std::inserter(entry, entry.begin()));
try
{
if (entry.at("SAI_ROUTER_INTERFACE_ATTR_TYPE")
!= "SAI_ROUTER_INTERFACE_TYPE_PORT")
{
// Other types are not supported
return std::nullopt;
}
router_if.mac =
entry.at("SAI_ROUTER_INTERFACE_ATTR_SRC_MAC_ADDRESS");
router_if.port_oid =
entry.at("SAI_ROUTER_INTERFACE_ATTR_PORT_ID");
}
catch (const std::out_of_range &ex)
{
return std::nullopt;
}
return router_if;
}
std::vector<plat_gw_address> get_gw_addresses()
{
// TO-DO: remove this and use state->interfaces_addrs after mergin
// interface-addresses PR
std::vector<plat_ipv4> interfaces_addrs;
const auto port_name_mapping = sai::get_port_name_mapping();
std::vector<plat_gw_address> gw_addresses;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:*";
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
const json route_json =
json::parse(key.substr(pattern.size() - 1));
plat_gw_address gw_addr{};
// Get IP
cidr gw_ip_range = parse_cidr(
route_json.at("dest").template get<std::string>());
if (inet_pton(
AF_INET,
gw_ip_range.ip_address.c_str(),
&gw_addr.ip)
!= 1)
{
UC_LOG_ERR(
"Failed to parse GW IP address %s",
gw_ip_range.ip_address.c_str());
continue;
}
const auto *static_routes_begin = state->router.arr;
const auto *static_routes_end =
static_routes_begin + state->router.len;
if (std::find_if(
static_routes_begin,
static_routes_end,
[&gw_addr](const auto &fib_node) {
// TO-DO: uncomment after merging
// interface-addresses PR return
// addr_to_str(fib_node.key.prefix)
// == addr_to_str(gw_addr.ip);
return false;
})
!= static_routes_end)
{
continue;
}
if (std::find_if(
interfaces_addrs.cbegin(),
interfaces_addrs.cend(),
[&gw_addr](const auto &interface_addr) {
// TO-DO: uncomment after merging
// interface-addresses PR return
// addr_to_str(interface_addr.subnet)
// == addr_to_str(gw_addr.ip);
return false;
})
!= interfaces_addrs.cend())
{
continue;
}
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
const auto router_it =
entry.find("SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID");
if (router_it == entry.cend())
continue;
auto router_if_opt =
parse_router_interface(router_it->second);
if (!router_if_opt)
continue;
// Get MAC
std::strncpy(
gw_addr.mac,
router_if_opt->mac.c_str(),
std::size(gw_addr.mac) - 1);
// Get port name
const auto port_name_it =
port_name_mapping.find(router_if_opt->port_oid);
if (port_name_it == port_name_mapping.cend())
continue;
std::strncpy(
gw_addr.port,
port_name_it->second.c_str(),
std::size(gw_addr.port) - 1);
gw_addresses.push_back(gw_addr);
}
} while (cursor != 0);
return gw_addresses;
}
void apply_route_config(plat_cfg *cfg)
{
ucentral_router old_router{}, new_router{};
// Save the old router
old_router = state->router;
// Load new router, this also does the necessary allocations
if (ucentral_router_fib_db_copy(&cfg->router, &new_router) != 0)
throw std::runtime_error{"Failed to copy FIB DB"};
if (!old_router.sorted)
ucentral_router_fib_db_sort(&old_router);
if (!new_router.sorted)
ucentral_router_fib_db_sort(&new_router);
std::size_t old_idx = 0, new_idx = 0;
int diff = 0;
for_router_db_diff(&new_router, &old_router, new_idx, old_idx, diff)
{
diff = router_db_diff_get(
&new_router,
&old_router,
new_idx,
old_idx);
if (diff_case_upd(diff))
{
if (!ucentral_router_fib_info_cmp(
&router_db_get(&old_router, old_idx)->info,
&router_db_get(&new_router, new_idx)->info))
continue;
const auto &node = *router_db_get(&new_router, new_idx);
remove_route(0, node.key);
create_route(0, node.key, node.info);
}
if (diff_case_del(diff))
{
remove_route(
0,
router_db_get(&old_router, old_idx)->key);
}
if (diff_case_add(diff))
{
const auto &node = *router_db_get(&new_router, new_idx);
create_route(0, node.key, node.info);
}
}
ucentral_router_fib_db_free(&old_router);
state->router = new_router;
}
} // namespace larch

View File

@@ -0,0 +1,29 @@
#ifndef LARCH_PLATFORM_ROUTE_HPP_
#define LARCH_PLATFORM_ROUTE_HPP_
#include <router-utils.h>
#include <ucentral-platform.h>
#include <cstdint>
#include <vector>
namespace larch {
void create_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key,
const ucentral_router_fib_info &fib_info);
void remove_route(
std::uint16_t router_id,
const ucentral_router_fib_key &fib_key);
std::vector<ucentral_router_fib_node> get_routes(std::uint16_t router_id);
std::vector<plat_gw_address> get_gw_addresses();
void apply_route_config(plat_cfg *cfg);
} // namespace larch
#endif // !LARCH_PLATFORM_ROUTE_HPP_

View File

@@ -0,0 +1,139 @@
#include <sai_redis.hpp>
#include <state.hpp>
#include <sw/redis++/redis++.h>
#include <cstddef>
#include <cstdint>
#include <iterator> // std::inserter
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace larch::sai {
std::unordered_map<object_id, object_id> get_bridge_port_mapping()
{
std::unordered_map<object_id, object_id> mapping;
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
do
{
constexpr std::string_view pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:*";
// Example key is
// ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:oid:0x3a000000000616
// and we need to get only the 3a00... part. -1 here is for the
// trailing '*' at the end of the pattern.
constexpr std::size_t offset =
pattern.size() - 1 + oid_prefix.size();
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
for (const auto &key : keys)
{
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
key,
std::inserter(entry, entry.begin()));
const auto it =
entry.find("SAI_BRIDGE_PORT_ATTR_PORT_ID");
if (it != entry.cend())
{
mapping[key.substr(offset)] =
it->second.substr(oid_prefix.size());
}
}
} while (cursor != 0);
return mapping;
}
std::unordered_map<object_id, std::string> get_port_name_mapping()
{
std::unordered_map<std::string, std::string> entry;
state->redis_counters->hgetall(
"COUNTERS_PORT_NAME_MAP",
std::inserter(entry, entry.begin()));
state->redis_counters->hgetall(
"COUNTERS_LAG_NAME_MAP",
std::inserter(entry, entry.begin()));
std::unordered_map<object_id, std::string> mapping;
for (auto it = entry.cbegin(); it != entry.cend();)
{
// TO-DO: validate interface name?
auto node = entry.extract(it++);
mapping.try_emplace(
node.mapped().substr(oid_prefix.size()),
std::move(node.key()));
}
return mapping;
}
std::optional<std::uint16_t> get_vlan_by_oid(const object_id &oid)
{
std::int64_t cursor = 0;
std::unordered_set<std::string> keys;
const std::string pattern =
"ASIC_STATE:SAI_OBJECT_TYPE_VLAN:" + std::string{oid_prefix} + oid;
// There is no guarantee that the necessary key will be found during the
// first scan, so we need to scan until we find it
do
{
keys.clear();
cursor = state->redis_asic->scan(
cursor,
pattern,
std::inserter(keys, keys.begin()));
if (keys.empty())
continue;
std::unordered_map<std::string, std::string> entry;
state->redis_asic->hgetall(
*keys.begin(),
std::inserter(entry, entry.begin()));
const auto it = entry.find("SAI_VLAN_ATTR_VLAN_ID");
if (it == entry.cend())
return std::nullopt;
try
{
return static_cast<std::uint16_t>(
std::stoul(it->second));
}
catch (const std::logic_error &)
{
throw std::runtime_error{"Failed to parse VLAN ID"};
}
{}
} while (cursor != 0);
throw std::runtime_error{"Failed to get VLAN by object ID"};
}
} // namespace larch::sai

View File

@@ -0,0 +1,46 @@
/**
* @file sai_redis.hpp
* @brief Commonly used functions to interact with SAI via Redis DB.
*
* Note, that object IDs (OIDs) are used without "oid:0x" prefix.
*/
#ifndef LARCH_PLATFORM_SAI_REDIS_HPP_
#define LARCH_PLATFORM_SAI_REDIS_HPP_
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
namespace larch::sai {
using object_id = std::string;
inline constexpr std::string_view oid_prefix = "oid:0x";
/**
* @brief Get mapping of bridge port OIDs to port OIDs.
*
* @return Map with bridge port OID as a key and port OID as a value.
*/
std::unordered_map<object_id, object_id> get_bridge_port_mapping();
/**
* @brief Get mapping of port OIDs to port names.
*
* @return Map with port OID and port name as a value.
*/
std::unordered_map<object_id, std::string> get_port_name_mapping();
/**
* @brief Get VLAN ID by its OID.
*
* @throw std::runtime_error If VLAN can't be found
*/
std::optional<std::uint16_t> get_vlan_by_oid(const object_id &oid);
} // namespace larch::sai
#endif // !LARCH_PLATFORM_SAI_REDIS_HPP_

View File

@@ -0,0 +1,114 @@
#include <services.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <arpa/inet.h>
#include <array>
#include <string>
#include <unordered_set>
using nlohmann::json;
namespace larch {
static std::unordered_set<std::string> get_existing_ntp_servers()
{
const json existing_servers_json = json::parse(
gnmi_get("/sonic-ntp:sonic-ntp/NTP_SERVER/NTP_SERVER_LIST"));
std::unordered_set<std::string> existing_servers;
if (existing_servers_json.contains("sonic-ntp:NTP_SERVER_LIST"))
{
for (const auto &server_json :
existing_servers_json.at("sonic-ntp:NTP_SERVER_LIST"))
{
if (!server_json.contains("server_address"))
continue;
existing_servers.insert(
server_json.at("server_address")
.template get<std::string>());
}
}
return existing_servers;
}
static void apply_ntp_config(const plat_ntp_cfg *ntp_cfg)
{
if (ntp_cfg->servers)
{
gnmi_operation op;
std::unordered_set<std::string> existing_servers =
get_existing_ntp_servers();
json ntp_json;
json &server_list_json = ntp_json["sonic-ntp:NTP_SERVER_LIST"];
server_list_json = json::array();
const plat_ntp_server *it = nullptr;
UCENTRAL_LIST_FOR_EACH_MEMBER(it, &ntp_cfg->servers)
{
const auto existing_it =
existing_servers.find(it->hostname);
if (existing_it != existing_servers.cend())
{
existing_servers.erase(existing_it);
continue;
}
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, it->hostname, addr_buf.data())
!= 1
&& inet_pton(
AF_INET6,
it->hostname,
addr_buf.data())
!= 1)
{
UC_LOG_ERR(
"Domains are not supported in NTP server "
"list, use IP addresses");
continue;
}
json server_json;
server_json["association_type"] = "server";
server_json["server_address"] = it->hostname;
server_json["resolve_as"] = it->hostname;
server_list_json.push_back(server_json);
}
op.add_update(
"/sonic-ntp:sonic-ntp/NTP_SERVER/NTP_SERVER_LIST",
ntp_json.dump());
for (const auto &server : existing_servers)
{
op.add_delete(
"/sonic-ntp:sonic-ntp/NTP_SERVER/"
"NTP_SERVER_LIST[server_address="
+ server + "]");
}
op.execute();
}
}
void apply_services_config(plat_cfg *cfg)
{
apply_ntp_config(&cfg->ntp_cfg);
}
} // namespace larch

View File

@@ -0,0 +1,12 @@
#ifndef LARCH_PLATFORM_SERVICES_HPP_
#define LARCH_PLATFORM_SERVICES_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_services_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_SERVICES_HPP_

View File

@@ -0,0 +1,10 @@
#include <metrics.hpp>
#include <state.hpp>
#include <sw/redis++/redis++.h>
namespace larch {
platform_state::~platform_state() = default;
} // namespace larch

View File

@@ -0,0 +1,53 @@
#ifndef LARCH_PLATFORM_STATE_HPP_
#define LARCH_PLATFORM_STATE_HPP_
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <sonic_gnoi.grpc.pb.h>
#include <sonic_gnoi.pb.h>
#include <system.grpc.pb.h>
#include <system.pb.h>
#include <grpc++/grpc++.h>
#include <router-utils.h>
#include <ucentral-platform.h>
#include <memory>
#include <vector>
// Forward declarations
namespace sw::redis {
class Redis;
}
namespace larch {
class periodic;
struct platform_state {
~platform_state();
std::shared_ptr<grpc::ChannelInterface> channel;
std::unique_ptr<gnmi::gNMI::Stub> gnmi_stub;
std::unique_ptr<gnoi::system::System::Stub> system_gnoi_stub;
std::unique_ptr<gnoi::sonic::SonicService::Stub> stub_gnoi_sonic;
std::unique_ptr<periodic> telemetry_periodic;
std::unique_ptr<periodic> state_periodic;
std::unique_ptr<periodic> health_periodic;
std::unique_ptr<sw::redis::Redis> redis_asic;
std::unique_ptr<sw::redis::Redis> redis_counters;
std::vector<plat_ipv4> interfaces_addrs;
ucentral_router router{};
};
inline std::unique_ptr<platform_state> state;
} // namespace larch
#endif // !LARCH_PLATFORM_STATE_HPP_

View File

@@ -0,0 +1,107 @@
#include <stp.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <string>
using nlohmann::json;
namespace larch {
void apply_stp_config(struct plat_cfg *cfg)
{
std::size_t i = 0;
gnmi_operation op;
switch (cfg->stp_mode) {
case PLAT_STP_MODE_NONE:
{
const auto stp_list = gnmi_get("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST");
const json stp_list_json = json::parse(stp_list);
/* There are no STPs */
if (!stp_list_json.contains("sonic-spanning-tree:STP_LIST"))
return;
/* This will clear all per port/vlan stp entries */
/* Delete global config since you cannot change the stp mode otherwise */
op.add_delete("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST[keyleaf=GLOBAL]");
break;
}
case PLAT_STP_MODE_PVST:
{
/* Config mode */
json stp_cfg_mode_json;
stp_cfg_mode_json["priority"] = cfg->stp_instances[0].priority;
stp_cfg_mode_json["keyleaf"] = "GLOBAL";
stp_cfg_mode_json["bpdu_filter"] = false;
stp_cfg_mode_json["mode"] = "pvst";
stp_cfg_mode_json["rootguard_timeout"] = 30;
json add_stp_cfg_mode_json;
add_stp_cfg_mode_json["sonic-spanning-tree:STP_LIST"] = {stp_cfg_mode_json};
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP/STP_LIST", add_stp_cfg_mode_json.dump());
/* Once mode enabled - create entries for all ports */
BITMAP_FOR_EACH_BIT_SET(i, cfg->ports_to_cfg, MAX_NUM_OF_PORTS)
{
json stp_port_json;
stp_port_json["ifname"] = "Ethernet" + std::to_string(i);
stp_port_json["enabled"] = true;
json add_stp_port_json;
add_stp_port_json["sonic-spanning-tree:STP_PORT_LIST"] = {stp_port_json};
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP_PORT/STP_PORT_LIST", add_stp_port_json.dump());
}
/* Config vlans */
for (i = FIRST_VLAN; i < MAX_VLANS; i++) {
if (!cfg->stp_instances[i].enabled) {
continue;
}
json stp_vlan_json;
stp_vlan_json["vlanid"] = i;
stp_vlan_json["name"] = "Vlan" + std::to_string(i);
stp_vlan_json["enabled"] = cfg->stp_instances[i].enabled;
stp_vlan_json["priority"] = cfg->stp_instances[i].priority;
stp_vlan_json["forward_delay"] = cfg->stp_instances[i].forward_delay;
stp_vlan_json["hello_time"] = cfg->stp_instances[i].hello_time;
stp_vlan_json["max_age"] = cfg->stp_instances[i].max_age;
json add_stp_vlan_json;
add_stp_vlan_json["sonic-spanning-tree:STP_VLAN_LIST"] = {stp_vlan_json};
UC_LOG_DBG(
"set vlan=%d state.enabled=%d state.priority=%d "
"state.forward_delay=%d state.hello_time=%d "
"state.max_age=%d ",
i,
cfg->stp_instances[i].enabled,
cfg->stp_instances[i].priority,
cfg->stp_instances[i].forward_delay,
cfg->stp_instances[i].hello_time,
cfg->stp_instances[i].max_age);
op.add_update("/sonic-spanning-tree:sonic-spanning-tree/STP_VLAN/STP_VLAN_LIST", add_stp_vlan_json.dump());
}
break;
}
default:
throw std::runtime_error{"Failed apply stp config"};
}
op.execute();
}
}

View File

@@ -0,0 +1,12 @@
#ifndef LARCH_PLATFORM_STP_HPP_
#define LARCH_PLATFORM_STP_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_stp_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_STP_HPP_

View File

@@ -0,0 +1,61 @@
#include <syslog.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <string>
using nlohmann::json;
namespace larch {
void gnma_syslog_cfg_clear(void)
{
std::string path = "/sonic-syslog:sonic-syslog/SYSLOG_SERVER/SYSLOG_SERVER_LIST";
gnmi_operation op;
op.add_delete(path);
op.execute();
}
void apply_syslog_config(struct plat_syslog_cfg *cfg, int count)
{
std::string path = "/sonic-syslog:sonic-syslog/SYSLOG_SERVER/SYSLOG_SERVER_LIST";
const std::array<std::string, 8> priority2str = {
"crit",
"crit",
"crit",
"error",
"warn",
"notice",
"info",
"debug"
};
int i;
gnmi_operation op;
for (i = 0; i < count; ++i)
{
json syslog_cfg_member_json;
syslog_cfg_member_json["server_address"] = cfg[i].host;
syslog_cfg_member_json["port"] = cfg[i].port;
syslog_cfg_member_json["protocol"] = cfg[i].is_tcp ? "tcp" : "udp";
syslog_cfg_member_json["severity"] = priority2str.at(cfg[i].priority);
syslog_cfg_member_json["vrf"] = "default";
json add_syslog_cfg_member_json;
add_syslog_cfg_member_json["sonic-syslog:SYSLOG_SERVER_LIST"] = {syslog_cfg_member_json};
op.add_update(path, add_syslog_cfg_member_json.dump());
}
gnma_syslog_cfg_clear();
op.execute();
}
}

View File

@@ -0,0 +1,14 @@
#ifndef LARCH_PLATFORM_SYSLOG_HPP_
#define LARCH_PLATFORM_SYSLOG_HPP_
#include <ucentral-platform.h>
namespace larch {
void gnma_syslog_cfg_clear(void);
void apply_syslog_config(struct plat_syslog_cfg *cfg, int count);
}
#endif // !LARCH_PLATFORM_SYSLOG_HPP_

View File

@@ -0,0 +1,308 @@
#include <state.hpp>
#include <utils.hpp>
#include <gnmi.grpc.pb.h>
#include <gnmi.pb.h>
#include <grpcpp/grpcpp.h>
#include <arpa/inet.h>
#include <array>
#include <cstring>
#include <map>
#include <stdexcept>
#include <string>
#include <utility> // std::move
#include <vector>
namespace larch {
std::vector<std::string>
split_string(std::string str, const std::string &delimiter)
{
std::vector<std::string> results;
std::string::size_type pos{};
while ((pos = str.find(delimiter)) != std::string::npos)
{
results.push_back(str.substr(0, pos));
str.erase(0, pos + 1);
}
// Process the last part
results.push_back(std::move(str));
return results;
}
std::map<std::string, std::string> parse_kv(const std::string &kv_str)
{
enum class parse_state { open_bracket, close_bracket, key, value };
parse_state state = parse_state::close_bracket;
std::map<std::string, std::string> kv;
std::string key_buf, value_buf;
for (const char c : kv_str)
{
switch (c)
{
case '[':
if (state != parse_state::close_bracket)
throw std::runtime_error{
"Unexpected opening bracket"};
state = parse_state::open_bracket;
break;
case ']':
if (state != parse_state::value)
throw std::runtime_error{
"Unexpected closing bracket"};
state = parse_state::close_bracket;
if (key_buf.empty())
throw std::runtime_error{"Empty key"};
if (value_buf.empty())
throw std::runtime_error{"Empty value"};
kv.emplace(
std::move(key_buf),
std::move(value_buf));
key_buf.clear();
value_buf.clear();
break;
case '=':
if (state != parse_state::key)
throw std::runtime_error{
"Unexpected equals sign"};
state = parse_state::value;
break;
default:
{
if (state == parse_state::open_bracket)
state = parse_state::key;
if (state == parse_state::key)
key_buf.push_back(c);
else if (state == parse_state::value)
value_buf.push_back(c);
else
throw std::runtime_error{
"Unexpected character '"
+ std::string{c} + "'"};
break;
}
}
}
if (state != parse_state::close_bracket)
throw std::runtime_error{"Couldn't find closing bracket"};
return kv;
}
void convert_yang_path_to_proto(std::string yang_path, gnmi::Path *proto_path)
{
struct path_element {
std::string name;
std::map<std::string, std::string> kv;
};
std::vector<path_element> elements;
auto process_elem_str = [&elements](std::string elem_str) {
if (!elem_str.empty())
{
const auto open_bracket_pos = elem_str.find('[');
if (open_bracket_pos == std::string::npos)
{
elements.push_back({std::move(elem_str), {}});
}
else
{
// Parse the key-value part of YANG path
// (e.g. [Vlan=100][SomeKey=SomeValue]...)
try
{
elements.push_back(
{elem_str.substr(
0,
open_bracket_pos),
parse_kv(elem_str.substr(
open_bracket_pos))});
}
catch (const std::runtime_error &ex)
{
using namespace std::string_literals;
throw std::runtime_error{
"Failed to parse key-value part of YANG path: "s
+ ex.what()};
}
}
}
};
std::string::size_type pos{};
while ((pos = yang_path.find('/')) != std::string::npos)
{
process_elem_str(yang_path.substr(0, pos));
yang_path.erase(0, pos + 1);
}
// Process the last part of split string
process_elem_str(std::move(yang_path));
std::string &first_element = elements[0].name;
const auto colon_pos = first_element.find(':');
if (colon_pos != std::string::npos)
{
proto_path->set_origin(first_element.substr(0, colon_pos));
first_element.erase(0, colon_pos + 1);
}
for (const auto &elem : elements)
{
gnmi::PathElem *path_elem = proto_path->add_elem();
path_elem->set_name(elem.name);
if (!elem.kv.empty())
{
auto path_kv = path_elem->mutable_key();
for (const auto &[key, value] : elem.kv)
(*path_kv)[key] = value;
}
}
}
std::string gnmi_get(const std::string &yang_path)
{
gnmi::GetRequest greq;
greq.set_encoding(gnmi::JSON_IETF);
convert_yang_path_to_proto(yang_path, greq.add_path());
grpc::ClientContext context;
gnmi::GetResponse gres;
const grpc::Status status = state->gnmi_stub->Get(&context, greq, &gres);
if (!status.ok())
{
throw gnmi_exception{
"gNMI get operation wasn't successful: "
+ status.error_message() + "; error code "
+ std::to_string(status.error_code())};
}
if (gres.notification_size() != 1)
{
throw gnmi_exception{"Unsupported notification size"};
}
gnmi::Notification notification = gres.notification(0);
if (notification.update_size() != 1)
{
throw gnmi_exception{"Unsupported update size"};
}
gnmi::Update update = notification.update(0);
if (!update.has_val())
{
throw gnmi_exception{"Empty value"};
}
gnmi::TypedValue value = update.val();
if (!value.has_json_ietf_val())
{
throw gnmi_exception{"Empty JSON value"};
}
return value.json_ietf_val();
}
void gnmi_set(const std::string &yang_path, const std::string &json_data)
{
gnmi_operation op;
op.add_update(yang_path, json_data);
op.execute();
}
void gnmi_operation::add_update(const std::string &yang_path, const std::string &json_data)
{
gnmi::Update *update = set_request_.add_update();
convert_yang_path_to_proto(yang_path, update->mutable_path());
update->mutable_val()->set_json_ietf_val(json_data);
}
void gnmi_operation::add_delete(const std::string &yang_path)
{
convert_yang_path_to_proto(yang_path, set_request_.add_delete_());
}
void gnmi_operation::execute()
{
grpc::ClientContext context;
gnmi::SetResponse response;
const grpc::Status status = state->gnmi_stub->Set(&context, set_request_, &response);
set_request_.Clear();
if (!status.ok())
{
throw gnmi_exception{
"gNMI set operation wasn't successful: "
+ status.error_message() + "; error code "
+ std::to_string(status.error_code())};
}
}
std::string addr_to_str(const in_addr &address)
{
std::array<char, INET_ADDRSTRLEN> addr_str_buf{};
if (!inet_ntop(
AF_INET,
&address,
addr_str_buf.data(),
addr_str_buf.size()))
{
throw std::runtime_error{
"Failed to convert binary IP address to string"};
}
return addr_str_buf.data();
}
cidr parse_cidr(const std::string &str)
{
cidr result{};
std::array<char, 16> addr_buf{};
if (std::sscanf(
str.c_str(),
"%15[^/]/%hhu",
addr_buf.data(),
&result.mask)
!= 2)
throw std::runtime_error{"Failed to parse CIDR notation"};
result.ip_address = addr_buf.data();
return result;
}
}

View File

@@ -0,0 +1,93 @@
#ifndef LARCH_PLATFORM_UTILS_HPP_
#define LARCH_PLATFORM_UTILS_HPP_
#include <grpcpp/grpcpp.h>
#include <gnmi.pb.h>
#include <arpa/inet.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
namespace larch {
std::vector<std::string>
split_string(std::string str, const std::string &delimiter);
void convert_yang_path_to_proto(std::string yang_path, gnmi::Path *proto_path);
class gnmi_exception : public std::runtime_error {
using std::runtime_error::runtime_error;
};
/**
* @brief Get value by specified path.
*
* @throw std::runtime_error If get request wasn't successful
*/
std::string gnmi_get(const std::string &yang_path);
/**
* @brief Convenience wrapper to set only one entry.
*
* @throw std::runtime_error If set request wasn't successful
*/
void gnmi_set(const std::string &yang_path, const std::string &json_data);
class gnmi_operation {
public:
gnmi_operation() = default;
void add_update(const std::string &yang_path, const std::string &json_data);
void add_delete(const std::string &yang_path);
/**
* @brief Execute the previously added modifications.
*
* @throw std::runtime_error If set request wasn't successful
*/
void execute();
protected:
gnmi::SetRequest set_request_;
};
/**
* @brief Convert address from binary form to text form.
*
* @throw std::runtime_error If conversion failed
*/
std::string addr_to_str(const in_addr &address);
/**
* @brief Verifier that marks all the certificates as valid.
*/
class certificate_verifier : public grpc::experimental::ExternalCertificateVerifier {
public:
bool Verify(
grpc::experimental::TlsCustomVerificationCheckRequest *request,
std::function<void(grpc::Status)> callback,
grpc::Status *sync_status) override
{
(void)request;
(void)callback;
*sync_status = grpc::Status(grpc::StatusCode::OK, "");
return true;
}
void Cancel(grpc::experimental::TlsCustomVerificationCheckRequest *) override {}
};
struct cidr {
std::string ip_address;
std::uint16_t mask;
};
cidr parse_cidr(const std::string &str);
}
#endif // !LARCH_PLATFORM_UTILS_HPP_

View File

@@ -0,0 +1,229 @@
#include <igmp.hpp>
#include <vlan.hpp>
#include <utils.hpp>
#include <nlohmann/json.hpp>
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_PLAT
#include <ucentral-log.h>
#include <ucentral-platform.h>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
#include <tuple>
#include <utility> // std::move
using nlohmann::json;
using std::bitset;
using std::vector;
namespace larch {
void del_nonconfig_vlans(BITMAP_DECLARE(vlans_to_cfg, MAX_VLANS))
{
const auto vlan_list = gnmi_get("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST");
gnmi_operation op;
const json vlan_list_json = json::parse(vlan_list);
// There are no VLANs
if (!vlan_list_json.contains("sonic-vlan:VLAN_LIST"))
return;
for (const auto vlan : vlan_list_json.at("sonic-vlan:VLAN_LIST"))
{
const int vlan_id = vlan.at("vlanid").template get<int>();
if (vlan_id < MAX_VLANS)
{
if (BITMAP_TEST_BIT(vlans_to_cfg, vlan_id))
continue;
if (vlan_id == FIRST_VLAN)
continue;
op.add_delete("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST[name=Vlan" + std::to_string(vlan_id) + "]");
}
}
op.execute();
}
std::tuple<vector<bitset<MAX_NUM_OF_PORTS>>, vector<bitset<MAX_NUM_OF_PORTS>>>
get_vlan_membership()
{
vector<bitset<MAX_NUM_OF_PORTS>> vlan_membership(MAX_VLANS);
vector<bitset<MAX_NUM_OF_PORTS>> vlan_tagged(MAX_VLANS);
const auto vlan_membership_data = gnmi_get("/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST");
const json vlan_membership_json = json::parse(vlan_membership_data);
for (const auto entry : vlan_membership_json.value(
"sonic-vlan:VLAN_MEMBER_LIST",
json::array()))
{
std::uint16_t vlan_id = 0;
std::uint16_t port_id = 0;
if (NAME_TO_VLAN(&vlan_id, entry.at("name").template get<std::string>().c_str()) < 1)
{
UC_LOG_ERR("Failed to parse VLAN ID");
throw std::runtime_error{"Failed to parse VLAN ID"};
}
if (NAME_TO_PID(&port_id, entry.at("port").template get<std::string>().c_str()) < 1)
{
UC_LOG_ERR("Failed to parse port ID");
throw std::runtime_error{"Failed to parse port ID"};
}
vlan_membership[vlan_id].set(port_id);
if (entry.at("tagging_mode").template get<std::string>() == "tagged")
vlan_tagged[vlan_id].set(port_id);
}
return {std::move(vlan_membership), std::move(vlan_tagged)};
}
void apply_vlan_config(plat_cfg *cfg)
{
if (BITMAP_TEST_BIT(cfg->vlans_to_cfg, FIRST_VLAN))
{
UC_LOG_ERR("VLAN 1 is reserved and cannot be configured");
throw std::runtime_error{"VLAN 1 is reserved and cannot be configured"};
}
// Step 1: delete VLANs that currently exist, but are not present in the supplied config
del_nonconfig_vlans(cfg->vlans_to_cfg);
const auto [vlan_membership, vlan_tagged] = get_vlan_membership();
gnmi_operation op;
std::size_t i = 0;
BITMAP_FOR_EACH_BIT_SET(i, cfg->vlans_to_cfg, MAX_VLANS)
{
const plat_port_vlan *vlan = &cfg->vlans[i];
// Step 2: create the VLAN
json vlan_json;
vlan_json["vlanid"] = vlan->id;
vlan_json["name"] = "Vlan" + std::to_string(vlan->id);
// add DHCP Relay server
if (vlan->dhcp.relay.enabled)
{
const std::string addr_str = addr_to_str(vlan->dhcp.relay.server_address);
std::array<unsigned char, sizeof(in6_addr)> addr_buf{};
if (inet_pton(AF_INET, addr_str.c_str(), addr_buf.data()) == 1)
{
vlan_json["dhcp_servers"] = json::array();
vlan_json["dhcp_servers"].push_back(addr_str);
}
else if (inet_pton(AF_INET6, addr_str.c_str(), addr_buf.data()) == 1)
{
vlan_json["dhcpv6_servers"] = json::array();
vlan_json["dhcpv6_servers"].push_back(addr_str);
}
else
{
UC_LOG_ERR("Failed to parse DHCP Relay server address");
throw std::runtime_error{"Failed to parse DHCP Relay server address"};
}
}
json add_vlan_json;
add_vlan_json["sonic-vlan:VLAN_LIST"] = {vlan_json};
op.add_update("/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST", add_vlan_json.dump());
// Step 3: delete VLAN members that are not in the config
bitset<MAX_NUM_OF_PORTS> vlan_members_config;
bitset<MAX_NUM_OF_PORTS> vlan_tagged_config;
for (plat_vlan_memberlist *pv = vlan->members_list_head; pv; pv = pv->next)
{
std::uint16_t port_id = 0;
if (NAME_TO_PID(&port_id, pv->port.name) < 1)
{
UC_LOG_ERR("Failed to parse port ID");
throw std::runtime_error{"Failed to parse port ID"};
}
vlan_members_config.set(port_id);
if (pv->tagged)
vlan_tagged_config.set(port_id);
}
// Get bits that are set in the first bitset, but not set in the second one
const auto vlan_members_to_delete = vlan_membership[vlan->id] & ~vlan_members_config;
for (std::size_t port_id = 0; port_id < vlan_members_to_delete.size(); ++port_id)
{
if (vlan_members_to_delete[port_id])
{
op.add_delete(
"/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST[name=Vlan"
+ std::to_string(i) + "][port=Ethernet" + std::to_string(port_id) + "]");
}
}
// Step 4: add VLAN members
for (std::size_t port_id = 0; port_id < vlan_members_config.size(); ++port_id)
{
if (!vlan_members_config[port_id])
continue;
const bool tagged = vlan_tagged_config.test(port_id);
// VLAN member is already configured in the same way as in the config, skipping
if (vlan_membership[vlan->id].test(port_id) && tagged == vlan_tagged[vlan->id].test(port_id))
continue;
json vlan_member_json;
vlan_member_json["name"] = "Vlan" + std::to_string(vlan->id);
vlan_member_json["port"] = "Ethernet" + std::to_string(port_id);
vlan_member_json["tagging_mode"] = tagged ? "tagged" : "untagged";
json add_vlan_member_json;
add_vlan_member_json["sonic-vlan:VLAN_MEMBER_LIST"] = {vlan_member_json};
op.add_update("/sonic-vlan:sonic-vlan/VLAN_MEMBER/VLAN_MEMBER_LIST", add_vlan_member_json.dump());
}
}
op.execute();
}
void apply_vlan_ipv4_config(struct plat_cfg *cfg)
{
size_t i;
BITMAP_FOR_EACH_BIT_SET(i, cfg->vlans_to_cfg, MAX_VLANS)
{
if (i == FIRST_VLAN)
{
UC_LOG_DBG("Skipping L3 configuration for default vlan\n");
continue;
}
UC_LOG_DBG("Configuring vlan ip <%u>\n", (uint16_t)i);
// TODO: configure vlan IP
if (cfg->vlans[i].igmp.exist)
{
apply_igmp_config(cfg->vlans[i].id, &cfg->vlans[i].igmp);
}
}
}
}

View File

@@ -0,0 +1,14 @@
#ifndef LARCH_PLATFORM_VLAN_HPP_
#define LARCH_PLATFORM_VLAN_HPP_
#include <ucentral-platform.h>
namespace larch {
void apply_vlan_config(plat_cfg *cfg);
void apply_vlan_ipv4_config(plat_cfg *cfg);
}
#endif // !LARCH_PLATFORM_VLAN_HPP_

View File

@@ -1522,6 +1522,49 @@ err:
return ret;
}
static int cfg_service_ntp_parse(const cJSON *s, struct plat_ntp_cfg *ntp_cfg)
{
int ret = -1;
cJSON *servers;
const cJSON *server_json;
const char *hostname;
struct plat_ntp_server *server;
servers = cJSON_GetObjectItemCaseSensitive(s, "servers");
if (!cJSON_IsArray(servers)) {
UC_LOG_ERR("Unexpected type of services:ntp:servers: Array expected");
goto err;
}
cJSON_ArrayForEach(server_json, servers) {
if (!cJSON_IsString(server_json)) {
UC_LOG_ERR("Unexpected type of services:ntp:servers:<element>: String expected");
continue;
}
hostname = cJSON_GetStringValue(server_json);
if (!hostname) {
UC_LOG_ERR("Cannot read services:ntp:servers:<element (string)>");
continue;
}
server = calloc(1, sizeof(struct plat_ntp_server));
if (!server) {
UC_LOG_ERR("calloc failed");
continue;
}
strncpy(server->hostname, hostname, sizeof(server->hostname) - 1);
UCENTRAL_LIST_PUSH_MEMBER(&ntp_cfg->servers, server);
}
ret = 0;
err:
return ret;
}
static int cfg_services_parse(cJSON *services, struct plat_cfg *cfg)
{
cJSON *s;
@@ -1598,6 +1641,17 @@ static int cfg_services_parse(cJSON *services, struct plat_cfg *cfg)
cfg->enabled_services_cfg.http.enabled = cJSON_IsTrue(enable);
}
s = cJSON_GetObjectItemCaseSensitive(services, "ntp");
if (s) {
if (!cJSON_IsObject(s)) {
UC_LOG_ERR("Unexpected type of services:ntp: Object expected");
return -1;
}
if (cfg_service_ntp_parse(s, &cfg->ntp_cfg))
return -1;
}
return 0;
}
@@ -1689,7 +1743,7 @@ static int cfg_switch_ieee8021x_parse(cJSON *sw, struct plat_cfg *cfg)
static int cfg_switch_parse(cJSON *root, struct plat_cfg *cfg)
{
cJSON *sw, *obj, *iter, *arr, *port_isolation;
cJSON *sw, *obj, *iter, *arr, *port_isolation, *jumbo_frames;
BITMAP_DECLARE(instances_parsed, MAX_VLANS);
int id, prio, fwd, hello, age;
bool enabled;
@@ -1763,6 +1817,14 @@ static int cfg_switch_parse(cJSON *root, struct plat_cfg *cfg)
return -1;
}
jumbo_frames = cJSON_GetObjectItemCaseSensitive(sw, "jumbo-frames");
if (jumbo_frames && !cJSON_IsBool(jumbo_frames)) {
UC_LOG_ERR("Unexpected type of switch:jumbo-frames: Boolean expected");
return -1;
}
cfg->jumbo_frames = cJSON_IsTrue(jumbo_frames);
return 0;
}
@@ -2152,6 +2214,14 @@ static struct plat_cfg * cfg_parse(cJSON *config)
err_parse:
/* TODO: free all ports->vlans as well */
/* Empty statement is needed because labels can only be followed by
* statements (and declaration is not a statement)
*/
;
struct plat_ntp_server *ntp_node = NULL;
UCENTRAL_LIST_DESTROY_SAFE(&cfg->ntp_cfg.servers, ntp_node);
free(cfg->log_cfg);
free(cfg);
return NULL;
@@ -2255,6 +2325,9 @@ configure_handle(cJSON **rpc)
err_apply:
plat_config_destroy(plat_cfg);
struct plat_ntp_server *ntp_node = NULL;
UCENTRAL_LIST_DESTROY_SAFE(&plat_cfg->ntp_cfg.servers, ntp_node);
free(plat_cfg->log_cfg);
free(plat_cfg);