Compare commits

..

7 Commits

Author SHA1 Message Date
Mike Hansen
84789e07ce OLS 410
Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-05-28 10:08:52 -04:00
Mike Hansen
10cc5bec80 Merge pull request #19 from Telecominfraproject/OLS-578-Tag-ols-ucentral-client-and-ols-ucentral-schema-4.0.0-pre-release
[OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-r…
2025-02-07 08:06:04 -05:00
Mike Hansen
ca74a49604 [OLS-578] Tag ols-ucentral-client and ols-ucentral-schema 4.0.0 pre-release
Take the schema version from tag v4.0.0-rc1
Update version.json to 4.0.0

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-02-05 08:36:43 -05:00
Mike Hansen
fb10d141d0 Merge pull request #18 from Telecominfraproject/OLS-563-version-client-tag-and-update-schema-ref
[OLS-563] Add version to ols-ucentral-client
2025-02-03 09:27:55 -05:00
Mike Hansen
176d2b9f36 [OLS-563] Add version to ols-ucentral-client
Dockerfile updated to pull tagged version of schema to get the schema.json file.
This will make subsequent versioning much easier.

Once this is merged we can tag the client as 3.2.7 and then move forward to 4.x as we did with the schema.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-31 16:48:09 -05:00
Mike Hansen
41d50f4650 Merge pull request #17 from Telecominfraproject/OLS-563-Add-version-to-ols-ucentral-client
[OLS-563] Add version to ols-ucentral-client
2025-01-27 09:36:05 -05:00
Mike Hansen
00ae4001e7 [OLS-563] Add version to ols-ucentral-client
Add version to ols-ucentral-client
Augment the build to pull the schema version file from the ols-ucentral-schema repo (if present) based on commit id of
schema used as baseline for this client version.
Use both it and the version to provide the version information in the connect message.

Signed-off-by: Mike Hansen <mike.hansen@netexperience.com>
2025-01-22 19:49:46 -05:00
55 changed files with 182 additions and 5354 deletions

View File

@@ -1,9 +1,12 @@
FROM arm64v8/debian:buster
FROM debian:buster
LABEL Description="Ucentral client (Build) environment"
ARG HOME /root
ARG EXTERNAL_LIBS ${HOME}/ucentral-external-libs
ARG SCHEMA="4.1.0-rc1"
ARG SCHEMA_VERSION="v${SCHEMA}"
ARG SCHEMA_ZIP_FILE="${SCHEMA_VERSION}.zip"
ARG SCHEMA_UNZIPPED="ols-ucentral-schema-${SCHEMA}"
ARG OLS_SCHEMA_SRC="https://github.com/Telecominfraproject/ols-ucentral-schema/archive/refs/tags/${SCHEMA_ZIP_FILE}"
SHELL ["/bin/bash", "-c"]
RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
@@ -24,13 +27,15 @@ RUN apt-get update -q -y && apt-get -q -y --no-install-recommends install \
libtool \
pkg-config \
libjsoncpp-dev \
libhiredis-dev
unzip
RUN git config --global http.sslverify false
RUN git clone https://github.com/DaveGamble/cJSON.git ${HOME}/ucentral-external-libs/cJSON/
RUN git clone https://libwebsockets.org/repo/libwebsockets ${HOME}/ucentral-external-libs/libwebsockets/
RUN git clone --recurse-submodules -b v1.50.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc ${HOME}/ucentral-external-libs/grpc/
RUN git clone --recursive --branch v7.1.4 https://github.com/zhaojh329/rtty.git ${HOME}/ucentral-external-libs/rtty/
ADD ${OLS_SCHEMA_SRC} /tmp/
# The following libs should be prebuilt in docker-build-env img to speed-up
# recompilation of only the ucentral-client itself
@@ -42,6 +47,8 @@ RUN cd ${HOME}/ucentral-external-libs/cJSON/ && \
make install
RUN cd ${HOME}/ucentral-external-libs/libwebsockets/ && \
git branch --all && \
git checkout a9b8fe7ebf61b8c0e7891e06e70d558412933a33 && \
mkdir build && \
cd build && \
cmake .. && \
@@ -63,3 +70,8 @@ RUN cd ${HOME}/ucentral-external-libs/rtty/ && \
cd build && \
cmake .. && \
make -j4
RUN unzip /tmp/${SCHEMA_ZIP_FILE} -d ${HOME}/ucentral-external-libs/
RUN cd ${HOME}/ucentral-external-libs/ && \
mv ${SCHEMA_UNZIPPED} ols-ucentral-schema

View File

@@ -7,9 +7,6 @@ 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
@@ -53,8 +50,13 @@ build-ucentral-app: run-host-env
@echo Running ucentralclient docker-build-env container to build ucentral-client...;
docker exec -t ${CONTAINER_NAME} /root/ols-nos/docker-build-client.sh
docker cp ${CONTAINER_NAME}:/root/deliverables/ src/docker/
# copy the schema version, if it is there
docker cp ${CONTAINER_NAME}:/root/ucentral-external-libs/ols-ucentral-schema/schema.json src/docker/ || true
docker container stop ${CONTAINER_NAME} > /dev/null 2>&1 || true;
docker container rm ${CONTAINER_NAME} > /dev/null 2>&1 || true;
if [ -f version.json ]; then
cp version.json src/docker/
fi
build-ucentral-docker-img: build-ucentral-app
pushd src
@@ -62,7 +64,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 --label com.azure.sonic.manifest="$$(cat docker/manifest.json)"
docker build --file docker/Dockerfile --tag ucentral-client:latest docker
NEWIMG=$$(docker images --format "{{.ID}}" ucentral-client:latest)
if [ -n "$$OLDIMG" ] && [ ! "$$OLDIMG" = "$$NEWIMG" ]; then
docker image rm $$OLDIMG
@@ -84,47 +86,21 @@ 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;
rm -rf src/docker/lib* || true;
rm -rf src/docker/ucentral-client || true;
rm -rf src/docker/version.json || true;
rm -rf src/docker/schema.json || true;
rm -rf src/debian/ucentral-client.substvars 2>/dev/null || true;
rm -rf src/debian/shasta-ucentral-client.debhelper.log 2>/dev/null || true;
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_arm64.changes shasta_1.0_arm64.buildinfo 2>/dev/null || true;
rm -rf src/debian/files shasta_1.0_amd64.changes shasta_1.0_amd64.buildinfo 2>/dev/null || true;

View File

@@ -1,8 +0,0 @@
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 ?= arm64
CONFIGURED_ARCH ?= amd64
UCENTRAL_CLIENT_VERSION ?= 1.0
DPKG_EXPORT_BUILDFLAGS = 1
INSTALL ?= debian/ucentral-client/

View File

@@ -1,4 +1,4 @@
FROM arm64v8/debian:buster
FROM debian:buster
RUN echo "uCentral client support"
RUN apt-get update && apt-get install --no-install-recommends -y \
@@ -9,7 +9,6 @@ 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
@@ -22,11 +21,8 @@ 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
COPY /version.jso[n] /etc/
COPY /schema.jso[n] /etc/
RUN ldconfig
RUN ls -l /usr/local/bin/ucentral-client

View File

@@ -1,35 +0,0 @@
-----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,8 +4,7 @@
"package": {
"version": "1.0.0",
"depends": [],
"name": "ucentral_client",
"description": "SONiC uCentral client package"
"name": "ucentral_client"
},
"service": {
"name": "ucentral_client",
@@ -26,24 +25,12 @@
},
"container": {
"privileged": false,
"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"
}
}
],
"volumes": [],
"tmpfs": []
},
"cli": {
"config": [],
"show": [],
"clear": []
"config": "",
"show": "",
"clear": ""
}
}

View File

@@ -1,49 +0,0 @@
-----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++ -g -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv -lhiredis
g++ -o $@ $^ -lcurl -lwebsockets -lcjson -lssl -lcrypto -lpthread -ljsoncpp -lresolv
test:
@echo "========= running unit tests ========="

View File

@@ -1,19 +1,12 @@
#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 {
@@ -99,9 +92,3 @@ 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,10 +3,6 @@
#include <syslog.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef UC_LOG_COMPONENT
#define UC_LOG_COMPONENT UC_LOG_COMPONENT_UNKNOWN
#endif
@@ -62,8 +58,4 @@ 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,6 +1,3 @@
#ifndef UCENTRAL_PLATFORM_H
#define UCENTRAL_PLATFORM_H
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
@@ -28,7 +25,6 @@ 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)
@@ -329,15 +325,6 @@ 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];
@@ -469,7 +456,6 @@ 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;
@@ -489,7 +475,6 @@ 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 {
@@ -794,5 +779,3 @@ plat_reboot_cause_get(struct plat_reboot_cause *cause);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -2,4 +2,8 @@ plat.a: plat-example.o
ar crs $@ $^
%.o: %.c
ifdef PLATFORM_REVISION
gcc -c -o $@ ${CFLAGS} -I ./ -I ../../include -D PLATFORM_REVISION='"$(PLATFORM_REVISION)"' $^
else
gcc -c -o $@ ${CFLAGS} -I ./ -I ../../include $^
endif

View File

@@ -2,6 +2,7 @@
#include <ucentral-platform.h>
#include <ucentral-log.h>
#include <plat-revision.h>
#define UNUSED_PARAM(param) (void)((param))
@@ -12,7 +13,11 @@ int plat_init(void)
int plat_info_get(struct plat_platform_info *info)
{
UNUSED_PARAM(info);
*info = (struct plat_platform_info){0};
snprintf(info->platform, sizeof info->platform, "%s", "Example Platform" );
snprintf(info->hwsku, sizeof info->hwsku, "%s", "example-platform-sku");
snprintf(info->mac, sizeof info->mac, "%s", "24:fe:9a:0f:48:f0");
return 0;
}
@@ -156,10 +161,45 @@ int plat_port_num_get(uint16_t *num_of_active_ports)
UNUSED_PARAM(num_of_active_ports);
return 0;
}
int plat_revision_get(char *str, size_t str_max_len)
{
snprintf(str, str_max_len, PLATFORM_REVISION);
return 0;
}
int plat_reboot_cause_get(struct plat_reboot_cause *cause)
{
UNUSED_PARAM(cause);
return 0;
}
int plat_event_subscribe(const struct plat_event_callbacks *cbs)
{
UNUSED_PARAM(cbs);
return 0;
}
void plat_event_unsubscribe(void)
{
return;
}
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_metrics_save(const struct plat_metrics_cfg *cfg)
{
UNUSED_PARAM(cfg);
return 0;
}
int plat_metrics_restore(struct plat_metrics_cfg *cfg)
{
UNUSED_PARAM(cfg);
return 0;
}
int plat_run_script(struct plat_run_script *p)
{
UNUSED_PARAM(p);
return 0;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAT_REVISION
#define _PLAT_REVISION
#define XSTR(x) STR(x)
#define STR(x) #x
#define PLATFORM_REL_NUM 3.2.0
#define PLATFORM_BUILD_NUM 5
#ifndef PLATFORM_REVISION
#define PLATFORM_REVISION "Rel " XSTR(PLATFORM_REL_NUM) " build " XSTR(PLATFORM_BUILD_NUM)
#endif
#endif

View File

@@ -1,52 +0,0 @@
---
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

@@ -1,118 +0,0 @@
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

@@ -1,22 +0,0 @@
# 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

@@ -1,14 +0,0 @@
# 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

@@ -1,143 +0,0 @@
#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

@@ -1,14 +0,0 @@
#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

@@ -1,136 +0,0 @@
#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

@@ -1,12 +0,0 @@
#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

@@ -1,307 +0,0 @@
#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

@@ -1,62 +0,0 @@
#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

@@ -1,631 +0,0 @@
#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

@@ -1,528 +0,0 @@
#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

@@ -1,26 +0,0 @@
#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

@@ -1,27 +0,0 @@
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

@@ -1,49 +0,0 @@
//
// 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

@@ -1,457 +0,0 @@
//
// 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

@@ -1,89 +0,0 @@
//
// 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

@@ -1,315 +0,0 @@
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

@@ -1,339 +0,0 @@
//
// 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

@@ -1,75 +0,0 @@
//
// 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

@@ -1,408 +0,0 @@
#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

@@ -1,29 +0,0 @@
#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

@@ -1,139 +0,0 @@
#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

@@ -1,46 +0,0 @@
/**
* @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

@@ -1,114 +0,0 @@
#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

@@ -1,12 +0,0 @@
#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

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

View File

@@ -1,53 +0,0 @@
#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

@@ -1,107 +0,0 @@
#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

@@ -1,12 +0,0 @@
#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

@@ -1,61 +0,0 @@
#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

@@ -1,14 +0,0 @@
#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

@@ -1,308 +0,0 @@
#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

@@ -1,93 +0,0 @@
#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

@@ -1,229 +0,0 @@
#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

@@ -1,14 +0,0 @@
#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

@@ -406,17 +406,46 @@ err:
proto_destroy_blob(&blob);
}
void
connect_send(void)
{
static cJSON *readJsonFile(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
fprintf(stderr, "Error opening file: %s\n", filename);
return NULL;
}
cJSON *ret;
// Get the file size
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
// Read the entire file into a buffer
char *buffer = (char *)malloc(fileSize + 1);
fread(buffer, 1, fileSize, file);
buffer[fileSize] = '\0'; // Null-terminate the string
// Close the file
fclose(file);
ret = cJSON_Parse(buffer);
return ret;
}
void connect_send(void) {
/* WIP: TMP hardcode; to be removed*/
unsigned mac[6];
struct plat_platform_info pinfo = {0};
struct plat_metrics_cfg restore_metrics = { 0 };
struct plat_metrics_cfg restore_metrics = {0};
struct blob blob = {0};
uint64_t uuid_buf; /* fixed storage size */
cJSON *params;
cJSON *cap;
cJSON *ver;
int ret;
blob.obj = proto_new_blob("connect");
@@ -446,7 +475,6 @@ connect_send(void)
if (password) {
if (!cJSON_AddStringToObject(params, "password", password))
goto err;
memset(password, 0, strlen(password));
free(password);
password = NULL;
@@ -456,30 +484,60 @@ connect_send(void)
if (!cap)
goto err;
ver = cJSON_AddObjectToObject(cap, "version");
if (!ver)
goto err;
if (plat_info_get(&pinfo)) {
UC_LOG_CRIT("failed to get platform info");
} else {
if (!cJSON_AddStringToObject(cap, "compatible", pinfo.hwsku))
goto err;
if (!cJSON_AddStringToObject(cap, "model", pinfo.platform))
goto err;
}
if (!cJSON_AddStringToObject(cap, "serial", client.serial))
goto err;
if (!cJSON_AddStringToObject(cap, "firmware", client.firmware))
goto err;
cJSON *client_version_json = readJsonFile(client.ols_client_version_file);
if (!client_version_json)
goto err;
if (!cJSON_AddItemToObject(ver, "switch", client_version_json))
goto err;
else
UC_LOG_DBG("client version added to connect.capabilities.version");
cJSON *schema_version_json = readJsonFile(client.ols_schema_version_file);
if (!schema_version_json) {
UC_LOG_DBG("No schema version present.");
}
else {
if (!cJSON_AddItemToObject(ver, "schema", schema_version_json))
goto err;
else
UC_LOG_DBG("schema version added to connect.capabilities.version");
}
if (!cJSON_AddStringToObject(cap, "compatible", pinfo.hwsku))
goto err;
if (!cJSON_AddStringToObject(cap, "model", pinfo.platform))
goto err;
if (!cJSON_AddStringToObject(cap, "platform", "switch"))
goto err;
if (client.serial &&
sscanf(client.serial, "%2x%2x%2x%2x%2x%2x",
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
sscanf(client.serial, "%2x%2x%2x%2x%2x%2x",
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
char label_mac[32];
snprintf(label_mac, sizeof label_mac,
"%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
"%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
if (!cJSON_AddStringToObject(cap, "label_macaddr", label_mac)) {
goto err;
}
} else {
}
else {
UC_LOG_DBG("failed to parse serial as label_macaddr");
}
@@ -490,11 +548,11 @@ connect_send(void)
if (ucentral_metrics.state.enabled)
plat_state_poll(state_send,
ucentral_metrics.state.interval);
ucentral_metrics.state.interval);
if (ucentral_metrics.healthcheck.enabled)
plat_health_poll(health_send,
ucentral_metrics.healthcheck.interval);
ucentral_metrics.healthcheck.interval);
}
UC_LOG_DBG("xmit connect\n");
@@ -1522,49 +1580,6 @@ 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;
@@ -1641,17 +1656,6 @@ 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;
}
@@ -1743,7 +1747,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, *jumbo_frames;
cJSON *sw, *obj, *iter, *arr, *port_isolation;
BITMAP_DECLARE(instances_parsed, MAX_VLANS);
int id, prio, fwd, hello, age;
bool enabled;
@@ -1817,14 +1821,6 @@ 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;
}
@@ -2214,14 +2210,6 @@ 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;
@@ -2325,9 +2313,6 @@ 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);

View File

@@ -67,6 +67,8 @@ lws_protocols protocols[] = {
struct client_config client = {
.redirector_file = "/tmp/ucentral-redirector.json",
.redirector_file_dbg = "/tmp/firstcontact.hdr",
.ols_schema_version_file = "/etc/schema.json",
.ols_client_version_file = "/etc/version.json",
.server = NULL,
.port = 15002,
.path = "/",

View File

@@ -41,6 +41,8 @@ extern "C" {
struct client_config {
const char *redirector_file;
const char *redirector_file_dbg;
const char *ols_client_version_file;
const char *ols_schema_version_file;
const char *server;
int16_t port;
const char *path;

5
version.json Normal file
View File

@@ -0,0 +1,5 @@
{
"major": 4,
"minor": 1,
"patch": 0
}