├── doc ├── UserSpace CNI.pdf └── images │ ├── userspace-plugin.png │ └── userspace-with-multus.png ├── ci ├── ovs_test_setup │ ├── testpmd_image │ │ ├── requirements.txt │ │ ├── Dockerfile │ │ └── testpmd.sh │ ├── Dockerfile │ ├── ovs_network_attachment_definition.yaml │ ├── ovs_host.sh │ └── testpmd_pod.sh ├── vpp_test_setup │ ├── vpp_pod_setup_memif.sh │ ├── vpp_host.sh │ ├── vpp_pod.sh │ ├── network_attachment_definition.yaml │ └── startup.conf └── ci.sh ├── .travis.yml ├── .github ├── CODEOWNERS ├── workflows │ ├── unittest.yml │ ├── fuzz.yml │ ├── dependency-review.yml │ ├── e2e.yml │ ├── trivy.yml │ ├── trivy-testpmd.yml │ ├── codeql.yml │ ├── weekly_e2e.yml │ ├── scorecard.yml │ └── static-scan.yml └── dependabot.yml ├── .gitignore ├── release-nodes.md ├── .scorecard.yml ├── .pre-commit-config.yaml ├── tests ├── get-prefix.sh ├── vhostuser-sample.conf └── multus-sample.conf ├── docker ├── userspacecni │ ├── Dockerfile.unittest │ └── Dockerfile └── testpmd │ ├── Dockerfile │ └── testpmd.sh ├── cniovs ├── ovsctrl_fake.go ├── localdb.go ├── localdb_test.go └── ovsctrl.go ├── examples ├── vpp-memif-ping │ ├── vpp-pod-setup-memif.sh │ ├── userspace-vpp-netAttach.yaml │ ├── vpp-app-pod-1.yaml │ ├── vpp-app-pod-2.yaml │ └── startup.conf ├── ovs-vhost │ ├── userspace-ovs-netAttach-1.yaml │ ├── testpmd-pod-1.yaml │ └── testpmd-pod-2.yaml └── sample-vpp-host-config │ └── startup.conf ├── kubernetes └── userspace-daemonset.yml ├── SECURITY.md ├── userspace ├── main.go └── testdata │ └── testdata.go ├── scripts ├── usrsp-docker-run.sh └── dpdk-docker-run.sh ├── usrspcni └── usrspcni.go ├── CONTRIBUTING.md ├── Makefile ├── cnivpp ├── api │ ├── infra │ │ └── infra.go │ ├── interface │ │ └── interface.go │ ├── vhostuser │ │ └── vhostuser.go │ └── bridge │ │ └── bridge.go ├── test │ ├── cni_test.go │ ├── vhostUserAddDel │ │ └── vhostUserAddDel.go │ ├── ipAddDel │ │ └── ipAddDel.go │ └── memifAddDel │ │ └── memifAddDel.go ├── README.md ├── localdb.go └── cnivpp_test.go ├── go.mod ├── logging └── logging.go ├── pkg ├── k8sclient │ ├── k8sclient.go │ └── k8sclient_test.go ├── types │ └── types.go └── configdata │ └── configdata.go └── LICENSE /doc/UserSpace CNI.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/userspace-cni-network-plugin/HEAD/doc/UserSpace CNI.pdf -------------------------------------------------------------------------------- /doc/images/userspace-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/userspace-cni-network-plugin/HEAD/doc/images/userspace-plugin.png -------------------------------------------------------------------------------- /ci/ovs_test_setup/testpmd_image/requirements.txt: -------------------------------------------------------------------------------- 1 | meson==1.3.0 --hash=sha256:e9f54046ce5b9a1f3024f7a7d52f19f085fd57c9d26a5db0cfcf0750572a8fd8 -------------------------------------------------------------------------------- /doc/images/userspace-with-multus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/userspace-cni-network-plugin/HEAD/doc/images/userspace-with-multus.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 1.14.x 4 | 5 | before_install: 6 | - sudo apt-get update -qq 7 | 8 | script: 9 | - make install-dep 10 | - make install 11 | - make 12 | - make clean 13 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is the copy of the template: 2 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file 3 | 4 | * john.oloughlin@intel.com michael.oreilly@intel.com eoghan.russell@intel.com 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | userspace/userspace 2 | cnivpp/test/memifAddDel/memifAddDel 3 | cnivpp/test/vhostUserAddDel/vhostUserAddDel 4 | cnivpp/test/ipAddDel/ipAddDel 5 | cnivpp/bin_api/ 6 | docker/usrsp-app/usrsp-app 7 | docker/vpp-centos-userspace-cni/usrsp-app 8 | docker/ovs-centos-userspace-cni/usrsp-app 9 | docker/unit-tests/Dockerfile.* 10 | !docker/unit-tests/Dockerfile.*.in 11 | -------------------------------------------------------------------------------- /release-nodes.md: -------------------------------------------------------------------------------- 1 | # Userspace-cni Release Notes 2 | 3 | ## [v24.x](https://github.com/intel/userspace-cni-network-plugin/tree/main) 4 | 5 | * Build userspace cni in container 6 | * Bump software version for both functionallity and security 7 | * Add kubernetes deployment file 8 | * Add full e2e CI using github actions that run on every commit 9 | * Add Static code scanning for each commit 10 | * update vpp api calls to work with vpp 23.02 onwords 11 | -------------------------------------------------------------------------------- /.scorecard.yml: -------------------------------------------------------------------------------- 1 | exemptions: 2 | - checks: 3 | - pinned-dependencies 4 | annotations: 5 | # The CI files should not be pinned 6 | # because the do not ship with the product. 7 | - annotation: not-applicable 8 | paths: 9 | - ci/* 10 | - ci/ovs_test_setup/testpmd_image/Dockerfile 11 | - docker/testpmd/Dockerfile 12 | - ci/ci.sh 13 | # other dependencies can be penalized 14 | 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.3 4 | hooks: 5 | - id: gitleaks 6 | - repo: https://github.com/golangci/golangci-lint 7 | rev: v1.52.2 8 | hooks: 9 | - id: golangci-lint 10 | - repo: https://github.com/jumanjihouse/pre-commit-hooks 11 | rev: 3.0.0 12 | hooks: 13 | - id: shellcheck 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v4.4.0 16 | hooks: 17 | - id: end-of-file-fixer 18 | - id: trailing-whitespace 19 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04@sha256:6042500cf4b44023ea1894effe7890666b0c5c7871ed83a97c36c76ae560bb9b 2 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 3 | RUN apt-get update -y \ 4 | && apt-get install -y linux-headers-generic openvswitch-switch-dpdk \ 5 | && apt-get clean \ 6 | && rm -rf /var/lib/apt/lists/* 7 | RUN update-alternatives --set ovs-vswitchd /usr/lib/openvswitch-switch-dpdk/ovs-vswitchd-dpdk 8 | # RUN sed -i "/rmmod bridge/d" /usr/share/openvswitch/scripts/ovs-kmod-ctl 9 | RUN apt-get install -y --no-install-recommends linux-headers-"$(uname -r | cut -d'-' -f1)" 10 | CMD ["sh", "-c","/usr/share/openvswitch/scripts/ovs-ctl start && sleep inf"] 11 | -------------------------------------------------------------------------------- /tests/get-prefix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2017 Intel Corp 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 14 | # implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | sed -ne '/hostname/p' /proc/1/task/1/mountinfo | awk -F '/' '{print $6}' 19 | -------------------------------------------------------------------------------- /docker/userspacecni/Dockerfile.unittest: -------------------------------------------------------------------------------- 1 | FROM ligato/vpp-base:24.02@sha256:daa54ffefce805a2da087f5577d5a6644d41ab3748bde3d9a2c39b0f507019f5 as builder 2 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 3 | COPY . /root/userspace-cni-network-plugin 4 | WORKDIR /root/userspace-cni-network-plugin 5 | RUN apt-get update -y \ 6 | && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y binutils bash wget make git \ 7 | && wget -qO- https://golang.org/dl/go1.22.3.linux-amd64.tar.gz | tar -C /usr/local -xz \ 8 | && rm -rf /var/lib/apt/lists/* 9 | ENV PATH="${PATH}:/usr/local/go/bin" 10 | RUN go mod download \ 11 | && go get go.fd.io/govpp/binapigen/vppapi@v0.7.0 \ 12 | && make generate \ 13 | && go mod tidy \ 14 | && make generate-bin -------------------------------------------------------------------------------- /tests/vhostuser-sample.conf: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.1", 3 | "type": "userspace", 4 | "name": "memif-network", 5 | "host": { 6 | "engine": "vpp", 7 | "iftype": "memif", 8 | "netType": "bridge", 9 | "memif": { 10 | "role": "master", 11 | "mode": "ethernet" 12 | }, 13 | "bridge": { 14 | "bridgeId": 4 15 | }, 16 | }, 17 | "container": { 18 | "engine": "vpp", 19 | "iftype": "memif", 20 | "netType": "interface", 21 | "memif": { 22 | "role": "slave", 23 | "mode": "ethernet" 24 | } 25 | }, 26 | "ipam": { 27 | "type": "host-local", 28 | "subnet": "10.56.217.0/24", 29 | "rangeStart": "10.56.217.131", 30 | "rangeEnd": "10.56.217.190", 31 | "routes": [ 32 | { 33 | "dst": "0.0.0.0/0" 34 | } 35 | ], 36 | "gateway": "10.56.217.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/unittest.yml: -------------------------------------------------------------------------------- 1 | name: unit-tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | unit-tests: 13 | name: unit-tests 14 | runs-on: hugepage-runner 15 | steps: 16 | - name: Harden Runner 17 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 18 | with: 19 | egress-policy: audit 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 23 | with: 24 | go-version: 1.22.3 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: make unit-test 27 | run: make unit-test 28 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: fuzz-tests 2 | on: 3 | schedule: 4 | - cron: "37 4 * * 0" 5 | pull_request: 6 | paths: 7 | - '**fuzz.yml' 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | fuzz-tests: 14 | name: fuzz-tests 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Harden Runner 18 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 19 | with: 20 | egress-policy: audit 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 24 | with: 25 | go-version: 1.22.3 26 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | - name: make fuzz 28 | run: make fuzz 29 | -------------------------------------------------------------------------------- /docker/testpmd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04@sha256:e3f92abc0967a6c19d0dfa2d55838833e947b9d74edbcb0113e48535ad4be12a 2 | RUN apt-get -q update \ 3 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 4 | git \ 5 | vim \ 6 | meson \ 7 | python3-pyelftools \ 8 | libnuma-dev \ 9 | python3-pip \ 10 | ninja-build \ 11 | build-essential \ 12 | && rm -rf /var/lib/apt/lists/* 13 | RUN apt-get update -y \ 14 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates \ 15 | && rm -rf /var/lib/apt/lists/* 16 | RUN git clone https://github.com/DPDK/dpdk.git 17 | WORKDIR /dpdk/ 18 | RUN meson build 19 | WORKDIR /dpdk/build/ 20 | RUN ninja 21 | WORKDIR /dpdk/build/app 22 | COPY ./testpmd.sh testpmd.sh 23 | CMD ["sh", "-c","./testpmd.sh"] 24 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/testpmd_image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04@sha256:e3f92abc0967a6c19d0dfa2d55838833e947b9d74edbcb0113e48535ad4be12a 2 | RUN apt-get -q update \ 3 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 4 | git \ 5 | vim \ 6 | meson \ 7 | python3-pyelftools \ 8 | libnuma-dev \ 9 | python3-pip \ 10 | ninja-build \ 11 | build-essential \ 12 | && rm -rf /var/lib/apt/lists/* 13 | RUN apt-get update -y \ 14 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates \ 15 | && rm -rf /var/lib/apt/lists/* 16 | RUN git clone https://github.com/DPDK/dpdk.git 17 | WORKDIR /dpdk/ 18 | RUN meson build 19 | WORKDIR /dpdk/build/ 20 | RUN ninja 21 | WORKDIR /dpdk/build/app 22 | COPY ./testpmd.sh testpmd.sh 23 | CMD ["sh", "-c","./testpmd.sh"] 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: docker 9 | directory: / 10 | schedule: 11 | interval: daily 12 | 13 | - package-ecosystem: docker 14 | directory: /docker/userspacecni/ 15 | schedule: 16 | interval: daily 17 | 18 | - package-ecosystem: gomod 19 | directory: / 20 | schedule: 21 | interval: daily 22 | 23 | - package-ecosystem: docker 24 | directory: /ci/ovs_test_setup 25 | schedule: 26 | interval: daily 27 | 28 | - package-ecosystem: docker 29 | directory: /ci/ovs_test_setup/testpmd_image 30 | schedule: 31 | interval: daily 32 | 33 | - package-ecosystem: docker 34 | directory: /docker/testpmd 35 | schedule: 36 | interval: daily 37 | 38 | - package-ecosystem: docker 39 | directory: /docker/userspacecni 40 | schedule: 41 | interval: daily 42 | -------------------------------------------------------------------------------- /docker/testpmd/testpmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /dpdk || exit 1 4 | 5 | #set container id as env 6 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/x86_64-linux-gnu/ 7 | while ! [ -s /etc/podinfo/annotations ]; do 8 | echo "annotations not found yet" 9 | sleep 1 # throttle the check 10 | done 11 | cat /etc/podinfo/annotations 12 | sleep 10 13 | containerid=$(grep containerId /etc/podinfo/annotations |cut -d '"' -f 5 |sed 's/\\//') 14 | echo "${containerid:0:12}" 15 | 16 | if grep -q app1 /etc/podinfo/labels; then 17 | fordwardmode="txonly" 18 | cpu="2,3,4,5" 19 | else 20 | fordwardmode="rxonly" 21 | cpu="6,7,8,9" 22 | fi 23 | 24 | #--stats-period 1 is needed to avoid testpmd exiting 25 | commands="./build/app/dpdk-testpmd -l $cpu --vdev net_virtio_user0,path=/var/lib/cni/usrspcni/${containerid:0:12}-net1,server=1,queue_size=2048 --in-memory --single-file-segments -- --tx-ip 192.168.1.1,192.168.1.2 --tx-udp=4000,4000 --forward-mode=$fordwardmode --stats-period 1" #-i 26 | echo "$commands" 27 | $commands 28 | -------------------------------------------------------------------------------- /cniovs/ovsctrl_fake.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cniovs 16 | 17 | // 18 | // Fake implementation of execCommand suitable for unit testing 19 | // 20 | 21 | type FakeExecCommand struct { 22 | Out []byte 23 | Err error 24 | Cmd string 25 | Args []string 26 | } 27 | 28 | func (e *FakeExecCommand) execCommand(cmd string, args []string) ([]byte, error) { 29 | e.Cmd = cmd 30 | e.Args = args 31 | return e.Out, e.Err 32 | } 33 | -------------------------------------------------------------------------------- /ci/vpp_test_setup/vpp_pod_setup_memif.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | socket=$(ls /var/lib/cni/usrspcni/) 3 | 4 | containerid=$(grep containerID /etc/podinfo/annotations |cut -d = -f 2 | sed 's/"//g') 5 | echo "${containerid:0:12}" 6 | 7 | 8 | 9 | echo "sh int addr" 10 | vppctl "sh int addr" 11 | echo "create memif socket id 1 filename /var/lib/cni/usrspcni/$socket" 12 | vppctl "create memif socket id 1 filename /var/lib/cni/usrspcni/$socket" 13 | echo "create interface memif id 0 socket-id 1 slave no-zero-copy" 14 | vppctl "create interface memif id 0 socket-id 1 slave no-zero-copy" 15 | echo "set int state memif1/0 up" 16 | vppctl "set int state memif1/0 up" 17 | if hostname | grep -q app1; then 18 | echo app1 19 | echo "set int ip address memif1/0 192.168.1.3/24" 20 | vppctl "set int ip address memif1/0 192.168.1.3/24" 21 | else 22 | echo app2 23 | echo "set int ip address memif1/0 192.168.1.4/24" 24 | vppctl "set int ip address memif1/0 192.168.1.4/24" 25 | 26 | fi 27 | echo "sh int addr" 28 | vppctl "sh int addr" 29 | echo "sh memif" 30 | vppctl "sh memif" 31 | exit 0 32 | -------------------------------------------------------------------------------- /examples/vpp-memif-ping/vpp-pod-setup-memif.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | socket=$(ls /var/lib/cni/usrspcni/) 3 | 4 | containerid=$(grep containerID /etc/podinfo/annotations |cut -d = -f 2 | sed 's/"//g') 5 | echo "${containerid:0:12}" 6 | 7 | 8 | 9 | echo "sh int addr" 10 | vppctl "sh int addr" 11 | echo "create memif socket id 1 filename /var/lib/cni/usrspcni/$socket" 12 | vppctl "create memif socket id 1 filename /var/lib/cni/usrspcni/$socket" 13 | echo "create interface memif id 0 socket-id 1 slave no-zero-copy" 14 | vppctl "create interface memif id 0 socket-id 1 slave no-zero-copy" 15 | echo "set int state memif1/0 up" 16 | vppctl "set int state memif1/0 up" 17 | if hostname | grep -q app1; then 18 | echo app1 19 | echo "set int ip address memif1/0 192.168.1.3/24" 20 | vppctl "set int ip address memif1/0 192.168.1.3/24" 21 | else 22 | echo app2 23 | echo "set int ip address memif1/0 192.168.1.4/24" 24 | vppctl "set int ip address memif1/0 192.168.1.4/24" 25 | 26 | fi 27 | echo "sh int addr" 28 | vppctl "sh int addr" 29 | echo "sh memif" 30 | vppctl "sh memif" 31 | exit 0 32 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 21 | with: 22 | egress-policy: audit 23 | 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.5.2 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 28 | -------------------------------------------------------------------------------- /kubernetes/userspace-daemonset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: userspace-cni 6 | namespace: kube-system 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: userspace-cni 11 | template: 12 | metadata: 13 | labels: 14 | app: userspace-cni 15 | spec: 16 | containers: 17 | - name: userspace-cni-plugin 18 | image: localhost:5000/userspacecni #registory:imagename 19 | imagePullPolicy: IfNotPresent 20 | securityContext: 21 | allowPrivilegeEscalation: false 22 | privileged: false 23 | resources: 24 | requests: 25 | cpu: 1m 26 | memory: 1Mi 27 | limits: 28 | cpu: 100m 29 | memory: 30Mi 30 | volumeMounts: 31 | - name: cnibin 32 | mountPath: /opt/cni/bin 33 | command: ["/bin/sh","-c"] 34 | args: ["cp -rf /root/userspace-cni-network-plugin/userspace/userspace /opt/cni/bin; sleep inf"] 35 | volumes: 36 | - name: cnibin 37 | hostPath: 38 | path: /opt/cni/bin 39 | -------------------------------------------------------------------------------- /docker/userspacecni/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ligato/vpp-base:24.02@sha256:daa54ffefce805a2da087f5577d5a6644d41ab3748bde3d9a2c39b0f507019f5 as builder 2 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 3 | COPY . /root/userspace-cni-network-plugin 4 | WORKDIR /root/userspace-cni-network-plugin 5 | RUN apt-get update -y \ 6 | && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y binutils bash wget make git \ 7 | && wget -qO- https://golang.org/dl/go1.22.3.linux-amd64.tar.gz | tar -C /usr/local -xz \ 8 | && rm -rf /var/lib/apt/lists/* 9 | ENV PATH="${PATH}:/usr/local/go/bin" 10 | RUN go mod download \ 11 | && go get go.fd.io/govpp/binapigen/vppapi@v0.7.0 \ 12 | && make generate \ 13 | && go mod tidy \ 14 | && make generate-bin 15 | #End of builder container 16 | # Copy build userspace cni bin to a small deployer container 17 | FROM alpine:3.20@sha256:b89d9c93e9ed3597455c90a0b88a8bbb5cb7188438f70953fede212a0c4394e0 18 | RUN mkdir -p /root/userspace-cni-network-plugin/userspace 19 | COPY --from=builder /root/userspace-cni-network-plugin/userspace/userspace /root/userspace-cni-network-plugin/userspace/userspace 20 | CMD ["cp", "-rf", "/root/userspace-cni-network-plugin/userspace/userspace", "/opt/cni/bin"] 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Security Policy 21 | 22 | ## Report a Vulnerability 23 | 24 | Please report security issues or vulnerabilities to the [Intel® Security Center]. 25 | 26 | For more information on how Intel® works to resolve security issues, see 27 | [Vulnerability Handling Guidelines]. 28 | 29 | [Intel® Security Center]:https://www.intel.com/security 30 | 31 | [Vulnerability Handling Guidelines]:https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | 13 | e2e: 14 | name: E2E 15 | runs-on: hugepage-runner 16 | steps: 17 | - name: Harden Runner 18 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 19 | with: 20 | egress-policy: audit 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 24 | with: 25 | go-version: 1.22.3 26 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | - name: install_go_kubectl_kind 28 | run: source ./ci/ci.sh && install_go_kubectl_kind 29 | - name: create_kind_cluster 30 | run: source ./ci/ci.sh && create_kind_cluster 31 | - name: deploy_multus 32 | run: source ./ci/ci.sh && deploy_multus 33 | - name: deploy_userspacecni 34 | run: source ./ci/ci.sh && deploy_userspace 35 | - name: vpp_e2e_test 36 | run: source ./ci/ci.sh && vpp_e2e_test 37 | - name: build_ovs_container 38 | run: source ./ci/ci.sh && build_ovs_container 39 | - name: build_test-pmd_container 40 | run: source ./ci/ci.sh && build_testpmd_container 41 | - name: ovs_e2e_test 42 | run: source ./ci/ci.sh && ovs_e2e_test 43 | 44 | 45 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/testpmd_image/testpmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /dpdk || exit 1 4 | #set container id as env 5 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/x86_64-linux-gnu/ 6 | while ! [ -s /etc/podinfo/annotations ]; do 7 | echo "annotations not found yet" 8 | sleep 1 # throttle the check 9 | done 10 | cat /etc/podinfo/annotations 11 | sleep 10 12 | #export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/ 13 | ##containerid=$(grep containerID /etc/podinfo/annotations |cut -d = -f 2 | sed 's/"//g') 14 | containerid=$(grep containerId /etc/podinfo/annotations |cut -d '"' -f 5 |sed 's/\\//') 15 | echo "${containerid:0:12}" 16 | #./build/app/dpdk-testpmd -l 10,14,12 --vdev net_vhost0,iface=/var/lib/cni/usrspcni/${containerid:0:12}-net1 -- -i 17 | cpu=$(cat /sys/fs/cgroup/cpuset.cpus) 18 | #--vdev "net_virtio_user0,path=/run/openvswitch/host/host-net1,server=1,queue_size=2048" --in-memory --single-file-segments --no-pci 19 | 20 | if grep -q app1 /etc/podinfo/labels; then 21 | fordwardmode="txonly" 22 | cpu="2,3,4,5" 23 | else 24 | fordwardmode="rxonly" 25 | cpu="6,7,8,9" 26 | fi 27 | 28 | #--stats-period 1 is needed to avoid testpmd exiting 29 | commands="./build/app/dpdk-testpmd -l $cpu --vdev net_virtio_user0,path=/var/lib/cni/usrspcni/${containerid:0:12}-net1,server=1,queue_size=2048 --in-memory --single-file-segments -- --tx-ip 192.168.1.1,192.168.1.2 --tx-udp=4000,4000 --forward-mode=$fordwardmode --stats-period 1" #-i 30 | echo "$commands" 31 | $commands 32 | -------------------------------------------------------------------------------- /tests/multus-sample.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multus-demo-network", 3 | "type": "multus", 4 | "delegates": [ 5 | { 6 | "type": "sriov", 7 | "if0": "ens786f1", 8 | "if0name": "net0", 9 | "dpdk": { 10 | "kernel_driver": "ixgbevf", 11 | "dpdk_driver": "igb_uio", 12 | "dpdk_tool": "/path/to/dpdk/tools/dpdk-devbind.py" 13 | } 14 | }, 15 | { 16 | "cniVersion": "0.3.1", 17 | "type": "userspace", 18 | "name": "memif-network", 19 | "host": { 20 | "engine": "vpp", 21 | "iftype": "memif", 22 | "netType": "bridge", 23 | "memif": { 24 | "role": "master", 25 | "mode": "ethernet" 26 | }, 27 | "bridge": { 28 | "bridgeId": 4 29 | } 30 | }, 31 | "container": { 32 | "engine": "vpp", 33 | "iftype": "memif", 34 | "netType": "interface", 35 | "memif": { 36 | "role": "slave", 37 | "mode": "ethernet" 38 | } 39 | }, 40 | "ipam": { 41 | "type": "host-local", 42 | "subnet": "10.56.217.0/24", 43 | "rangeStart": "10.56.217.131", 44 | "rangeEnd": "10.56.217.190", 45 | "routes": [ 46 | { 47 | "dst": "0.0.0.0/0" 48 | } 49 | ], 50 | "gateway": "10.56.217.1" 51 | } 52 | }, 53 | { 54 | "type": "flannel", 55 | "name": "control-network", 56 | "masterplugin": true, 57 | "delegate": { 58 | "isDefaultGateway": true 59 | } 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /userspace/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package main 17 | 18 | import ( 19 | "github.com/containernetworking/cni/pkg/skel" 20 | cniversion "github.com/containernetworking/cni/pkg/version" 21 | "github.com/intel/userspace-cni-network-plugin/userspace/cni" 22 | ) 23 | 24 | // args *skel.CmdArgs, exec invoke.Exec, kubeClient kubernetes.Interface 25 | func main() { 26 | skel.PluginMainFuncs( 27 | skel.CNIFuncs{ 28 | Add: func(args *skel.CmdArgs) error { 29 | err := cni.CmdAdd(args, nil, nil) 30 | if err != nil { 31 | return err 32 | } 33 | return nil 34 | }, 35 | 36 | Del: func(args *skel.CmdArgs) error { return cni.CmdDel(args, nil, nil) }, 37 | 38 | Check: func(args *skel.CmdArgs) error { 39 | return cni.CmdGet(args, nil, nil) 40 | }, 41 | GC: nil, 42 | Status: nil}, 43 | cniversion.All, "USERSPACE CNI Plugin") 44 | } 45 | -------------------------------------------------------------------------------- /scripts/usrsp-docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run a docker container with network namespace set up by the 4 | # CNI plugins. This script is a copy of the following CNI script 5 | # with Userspace CNI specific modifications: 6 | # go/src/github.com/containernetworking/cni/scripts/docker-run.sh 7 | # 8 | # VPP Example usage: 9 | # cd $GOPATH/src/github.com/intel/userspace-cni-network-plugin 10 | # sudo CNI_PATH=$CNI_PATH GOPATH=$GOPATH ./scripts/usrsp-docker-run.sh -it --privileged vpp-centos-userspace-cni 11 | # 12 | # OvS Example usage: 13 | # cd $GOPATH/src/github.com/intel/userspace-cni-network-plugin 14 | # sudo CNI_PATH=$CNI_PATH GOPATH=$GOPATH ./scripts/usrsp-docker-run.sh -it --privileged ovs-centos-userspace-cni 15 | # 16 | # Add DEBUG=1 for additional output. 17 | # 18 | 19 | scriptpath=$GOPATH/src/github.com/containernetworking/cni/scripts 20 | echo "$scriptpath" 21 | 22 | contid=$(docker run -d --net=none "$@" /bin/sleep 10000000) 23 | pid=$(docker inspect -f '{{ .State.Pid }}' "$contid") 24 | netnspath=/proc/$pid/ns/net 25 | 26 | "$scriptpath"/exec-plugins.sh add "$contid" "$netnspath" 27 | 28 | function cleanup() { 29 | "$scriptpath"/exec-plugins.sh del "$contid" "$netnspath" 30 | docker rm -f "$contid" >/dev/null 31 | } 32 | trap cleanup EXIT 33 | 34 | docker run \ 35 | -v /var/lib/cni/usrspcni/shared:/var/lib/cni/usrspcni/shared:rw \ 36 | -v /var/lib/cni/usrspcni/"$contid":/var/lib/cni/usrspcni/data:rw \ 37 | --device=/dev/hugepages:/dev/hugepages \ 38 | --net=container:"$contid" "$@" 39 | 40 | -------------------------------------------------------------------------------- /examples/ovs-vhost/userspace-ovs-netAttach-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "k8s.cni.cncf.io/v1" 2 | kind: NetworkAttachmentDefinition 3 | metadata: 4 | name: userspace-ovs-net-1 5 | namespace: ovs 6 | spec: 7 | config: '{ 8 | "cniVersion": "0.3.1", 9 | "type": "userspace", 10 | "name": "userspace-ovs-net-1", 11 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 12 | "logFile": "/var/log/userspace-ovs-net-1-cni.log", 13 | "logLevel": "debug", 14 | "host": { 15 | "engine": "ovs-dpdk", 16 | "iftype": "vhostuser", 17 | "netType": "bridge", 18 | "vhost": { 19 | "mode": "client" 20 | }, 21 | "bridge": { 22 | "bridgeName": "br-4" 23 | } 24 | }, 25 | "container": { 26 | "engine": "ovs-dpdk", 27 | "iftype": "vhostuser", 28 | "netType": "interface", 29 | "vhost": { 30 | "mode": "server" 31 | } 32 | }, 33 | "ipam": { 34 | "type": "host-local", 35 | "subnet": "10.56.217.0/24", 36 | "rangeStart": "10.56.217.131", 37 | "rangeEnd": "10.56.217.190", 38 | "routes": [ 39 | { 40 | "dst": "0.0.0.0/0" 41 | } 42 | ], 43 | "gateway": "10.56.217.1" 44 | } 45 | }' 46 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/ovs_network_attachment_definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "k8s.cni.cncf.io/v1" 2 | kind: NetworkAttachmentDefinition 3 | metadata: 4 | name: userspace-ovs-net-1 5 | namespace: ovs 6 | spec: 7 | config: '{ 8 | "cniVersion": "0.3.1", 9 | "type": "userspace", 10 | "name": "userspace-ovs-net-1", 11 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 12 | "logFile": "/var/log/userspace-ovs-net-1-cni.log", 13 | "logLevel": "debug", 14 | "host": { 15 | "engine": "ovs-dpdk", 16 | "iftype": "vhostuser", 17 | "netType": "bridge", 18 | "vhost": { 19 | "mode": "client" 20 | }, 21 | "bridge": { 22 | "bridgeName": "br-4" 23 | } 24 | }, 25 | "container": { 26 | "engine": "ovs-dpdk", 27 | "iftype": "vhostuser", 28 | "netType": "interface", 29 | "vhost": { 30 | "mode": "server" 31 | } 32 | }, 33 | "ipam": { 34 | "type": "host-local", 35 | "subnet": "10.56.217.0/24", 36 | "rangeStart": "10.56.217.131", 37 | "rangeEnd": "10.56.217.190", 38 | "routes": [ 39 | { 40 | "dst": "0.0.0.0/0" 41 | } 42 | ], 43 | "gateway": "10.56.217.1" 44 | } 45 | }' 46 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/ovs_host.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kubectl delete ns ovs 3 | kubectl create ns ovs 4 | 5 | worker="kind-control-plane" 6 | cat << EOF | kubectl apply -f - 7 | --- 8 | apiVersion: v1 9 | kind: Pod 10 | metadata: 11 | name: ovs-$worker 12 | labels: 13 | name: ovs 14 | namespace: ovs 15 | spec: 16 | hostNetwork: false 17 | nodeSelector: 18 | kubernetes.io/hostname: $worker 19 | hostname: ovs-$worker 20 | subdomain: ovs 21 | containers: 22 | - image: ovs 23 | imagePullPolicy: IfNotPresent 24 | name: ovs-$worker 25 | volumeMounts: 26 | - name: vpp-api 27 | mountPath: /run/openvswitch 28 | - name: modules 29 | mountPath: /lib/modules 30 | - name: vpp-run 31 | mountPath: /var/run/openvswitch 32 | - name: hugepage 33 | mountPath: /hugepages 34 | resources: 35 | requests: 36 | hugepages-2Mi: 1Gi 37 | memory: "500Mi" 38 | cpu: "5" 39 | limits: 40 | hugepages-2Mi: 1Gi 41 | memory: "500Mi" 42 | cpu: "5" 43 | securityContext: 44 | capabilities: 45 | add: ["NET_ADMIN", "SYS_TIME"] 46 | restartPolicy: Always 47 | volumes: 48 | - name: vpp-run 49 | hostPath: 50 | path: /var/run/openvswitch/ 51 | - name: modules 52 | hostPath: 53 | path: /lib/modules 54 | - name: vpp-api 55 | hostPath: 56 | path: /run/openvswitch/ 57 | - name: userspace-api 58 | hostPath: 59 | path: /var/lib/cni/usrspcni/ 60 | - name: hugepage 61 | emptyDir: 62 | medium: HugePages 63 | EOF 64 | 65 | -------------------------------------------------------------------------------- /usrspcni/usrspcni.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package usrspcni 16 | 17 | import ( 18 | v1 "k8s.io/api/core/v1" 19 | "k8s.io/client-go/kubernetes" 20 | 21 | "github.com/containernetworking/cni/pkg/skel" 22 | current "github.com/containernetworking/cni/pkg/types/100" 23 | 24 | "github.com/intel/userspace-cni-network-plugin/pkg/types" 25 | ) 26 | 27 | // Exported Types 28 | type UsrSpCni interface { 29 | AddOnHost(conf *types.NetConf, 30 | args *skel.CmdArgs, 31 | kubeClient kubernetes.Interface, 32 | sharedDir string, 33 | ipResult *current.Result) error 34 | AddOnContainer(conf *types.NetConf, 35 | args *skel.CmdArgs, 36 | kubeClient kubernetes.Interface, 37 | sharedDir string, 38 | pod *v1.Pod, 39 | ipResult *current.Result) (*v1.Pod, error) 40 | DelFromHost(conf *types.NetConf, 41 | args *skel.CmdArgs, 42 | sharedDir string) error 43 | DelFromContainer(conf *types.NetConf, 44 | args *skel.CmdArgs, 45 | sharedDir string, 46 | pod *v1.Pod) error 47 | } 48 | -------------------------------------------------------------------------------- /scripts/dpdk-docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run a docker container with network namespace set up by the 4 | # CNI plugins. This script is a copy of the following CNI script 5 | # with Userspace CNI specific modifications: 6 | # go/src/github.com/containernetworking/cni/scripts/docker-run.sh 7 | # 8 | # DPDK Application Example usage: 9 | # cd $GOPATH/src/github.com/intel/userspace-cni-network-plugin 10 | # sudo env "PATH=$PATH" CNI_PATH=$CNI_PATH GOPATH=$GOPATH ./scripts/dpdk-docker-run.sh -it --privileged dpdk-app-testpmd 11 | # 12 | # Add DEBUG=1 for additional output. 13 | # 14 | 15 | scriptpath=$GOPATH/src/github.com/containernetworking/cni/scripts 16 | echo "$scriptpath" 17 | 18 | contid=$(docker run -d --net=none "$@" /bin/sleep 10000000) 19 | pid=$(docker inspect -f '{{ .State.Pid }}' "$contid") 20 | netnspath=/proc/$pid/ns/net 21 | 22 | "$scriptpath"/exec-plugins.sh add "$contid" "$netnspath" 23 | 24 | function cleanup() { 25 | "$scriptpath"/exec-plugins.sh del "$contid" "$netnspath" 26 | docker rm -f "$contid" >/dev/null 27 | } 28 | trap cleanup EXIT 29 | 30 | # 31 | # Temporary - Hardcode the vhost socket file: -eth0 32 | # ToDo: Need to read the DB files to pull out the and 33 | # 34 | trucContid=${contid:0:12} 35 | docker run -i -t -v /var/lib/cni/usrspcni/shared:/var/lib/cni/usrspcni/shared:rw \ 36 | -v /dev/hugepages:/dev/hugepages \ 37 | dpdk-app-testpmd testpmd -l 0-1 -n 4 -m 1024 --no-pci \ 38 | --vdev=virtio_user0,path=/var/lib/cni/usrspcni/shared/"$trucContid"-eth0 \ 39 | --file-prefix=container \ 40 | -- -i --txqflags=0xf00 --disable-hw-vlan --port-topology=chained 41 | 42 | -------------------------------------------------------------------------------- /examples/vpp-memif-ping/userspace-vpp-netAttach.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: "k8s.cni.cncf.io/v1" 3 | kind: NetworkAttachmentDefinition 4 | metadata: 5 | name: userspace-vpp-net-1 6 | namespace: vpp 7 | spec: 8 | config: '{ 9 | "cniVersion": "0.3.1", 10 | "type": "userspace", 11 | "name": "userspace-vpp-net-1", 12 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 13 | "logFile": "/var/log/userspace-vpp-net-1-cni.log", 14 | "logLevel": "debug", 15 | "host": { 16 | "engine": "vpp", 17 | "iftype": "memif", 18 | "netType": "bridge", 19 | "memif": { 20 | "role": "master", 21 | "mode": "ethernet" 22 | }, 23 | "bridge": { 24 | "bridgeName": "4" 25 | } 26 | }, 27 | "container": { 28 | "engine": "vpp", 29 | "iftype": "memif", 30 | "netType": "interface", 31 | "memif": { 32 | "role": "slave", 33 | "mode": "ethernet" 34 | } 35 | }, 36 | "ipam": { 37 | "type": "host-local", 38 | "subnet": "10.56.217.0/24", 39 | "rangeStart": "10.56.217.131", 40 | "rangeEnd": "10.56.217.190", 41 | "routes": [ 42 | { 43 | "dst": "0.0.0.0/0" 44 | } 45 | ], 46 | "gateway": "10.56.217.1" 47 | } 48 | }' 49 | -------------------------------------------------------------------------------- /examples/ovs-vhost/testpmd-pod-1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: ovs-app1 6 | labels: 7 | name: ovs-app1 8 | namespace: ovs 9 | annotations: 10 | k8s.v1.cni.cncf.io/networks: userspace-ovs-net-1 11 | userspace/mappedDir: /var/lib/cni/usrspcni/ 12 | spec: 13 | nodeSelector: 14 | kubernetes.io/hostname: kind-control-plane # replace this with your hostname 15 | hostname: ovs-app1 16 | subdomain: ovs 17 | containers: 18 | - image: localhost:5000/testpmd #replace this with your docker reg and image name 19 | imagePullPolicy: IfNotPresent 20 | name: ovs-app1 21 | volumeMounts: 22 | - name: podinfo 23 | mountPath: /etc/podinfo 24 | - name: hugepage 25 | mountPath: /hugepages 26 | - name: shared-dir 27 | mountPath: /var/lib/cni/usrspcni/ 28 | - name: vfio 29 | mountPath: /dev/vfio/ 30 | resources: 31 | requests: 32 | hugepages-2Mi: 1Gi 33 | memory: "500Mi" 34 | cpu: "5" 35 | limits: 36 | hugepages-2Mi: 1Gi 37 | memory: "500Mi" 38 | cpu: "5" 39 | # command: ["/bin/sh"] 40 | # args: ["-c", "sleep inf"] 41 | restartPolicy: Always 42 | volumes: 43 | - name: podinfo 44 | downwardAPI: 45 | items: 46 | - path: "labels" 47 | fieldRef: 48 | fieldPath: metadata.labels 49 | - path: "annotations" 50 | fieldRef: 51 | fieldPath: metadata.annotations 52 | - name: hugepage 53 | emptyDir: 54 | medium: HugePages 55 | - name: shared-dir 56 | hostPath: 57 | path: /run/openvswitch/app1 58 | - name: vfio 59 | hostPath: 60 | path: /dev/vfio/ 61 | -------------------------------------------------------------------------------- /examples/ovs-vhost/testpmd-pod-2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: ovs-app2 6 | labels: 7 | name: ovs-app2 8 | namespace: ovs 9 | annotations: 10 | k8s.v1.cni.cncf.io/networks: userspace-ovs-net-1 11 | userspace/mappedDir: /var/lib/cni/usrspcni/ 12 | spec: 13 | nodeSelector: 14 | kubernetes.io/hostname: kind-control-plane # replace this with your hostname 15 | hostname: ovs-app2 16 | subdomain: ovs 17 | containers: 18 | - image: localhost:5000/testpmd #replace this with your docker reg and image name 19 | imagePullPolicy: IfNotPresent 20 | name: ovs-app1 21 | volumeMounts: 22 | - name: podinfo 23 | mountPath: /etc/podinfo 24 | - name: hugepage 25 | mountPath: /hugepages 26 | - name: shared-dir 27 | mountPath: /var/lib/cni/usrspcni/ 28 | - name: vfio 29 | mountPath: /dev/vfio/ 30 | resources: 31 | requests: 32 | hugepages-2Mi: 1Gi 33 | memory: "500Mi" 34 | cpu: "5" 35 | limits: 36 | hugepages-2Mi: 1Gi 37 | memory: "500Mi" 38 | cpu: "5" 39 | # command: ["/bin/sh"] 40 | # args: ["-c", "sleep inf"] 41 | restartPolicy: Always 42 | volumes: 43 | - name: podinfo 44 | downwardAPI: 45 | items: 46 | - path: "labels" 47 | fieldRef: 48 | fieldPath: metadata.labels 49 | - path: "annotations" 50 | fieldRef: 51 | fieldPath: metadata.annotations 52 | - name: hugepage 53 | emptyDir: 54 | medium: HugePages 55 | - name: shared-dir 56 | hostPath: 57 | path: /run/openvswitch/app2 58 | - name: vfio 59 | hostPath: 60 | path: /dev/vfio/ 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Vhostuser CNI is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub 4 | pull requests. This document outlines some of the conventions on development 5 | workflow, commit message formatting, contact points and other resources to make 6 | it easier to get your contribution accepted. 7 | 8 | ## Coding Style 9 | 10 | Please follows the standard formatting recommendations and language idioms set out 11 | in [Effective Go](https://golang.org/doc/effective_go.html) and in the 12 | [Go Code Review Comments wiki](https://go.dev/wiki/CodeReviewComments). 13 | 14 | ## Certificate of Origin 15 | 16 | In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process) 17 | used by the Linux kernel project. 18 | 19 | ## Format of the patch 20 | 21 | Beside the signed-off-by footer, we expect each patch to comply with the following format: 22 | 23 | ``` 24 | Change summary 25 | 26 | More detailed explanation of your changes: Why and how. 27 | Wrap it to 72 characters. 28 | See [here] (http://chris.beams.io/posts/git-commit/) 29 | for some more good advices. 30 | 31 | Fixes #NUMBER (or URL to the issue) 32 | 33 | Signed-off-by: 34 | ``` 35 | 36 | For example: 37 | 38 | ``` 39 | Fix poorly named identifiers 40 | 41 | One identifier, fnname, in func.go was poorly named. It has been renamed 42 | to fnName. Another identifier retval was not needed and has been removed 43 | entirely. 44 | 45 | Fixes #1 46 | 47 | Signed-off-by: Abc Xyz 48 | ``` 49 | 50 | ## Pull requests 51 | 52 | We accept github pull requests. 53 | 54 | ## Contact Us 55 | - #General channel on [NPWG](https://npwg-team.slack.com/) Slack 56 | -------------------------------------------------------------------------------- /examples/vpp-memif-ping/vpp-app-pod-1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: vpp-app1 6 | labels: 7 | name: vpp-app1 8 | namespace: vpp 9 | annotations: 10 | k8s.v1.cni.cncf.io/networks: userspace-vpp-net-1 11 | userspace/mappedDir: /var/lib/cni/usrspcni/ 12 | spec: 13 | nodeSelector: 14 | kubernetes.io/hostname: kind-control-plane #replace this with your hostname 15 | hostname: vpp-app1 16 | subdomain: vpp 17 | containers: 18 | - image: ligato/vpp-base:23.02 #imagename 19 | imagePullPolicy: IfNotPresent 20 | name: vpp-app1 21 | volumeMounts: 22 | - name: podinfo 23 | mountPath: /etc/podinfo 24 | - name: vpp-startup-config 25 | mountPath: /etc/vpp/ 26 | - name: hugepage 27 | mountPath: /hugepages 28 | - name: shared-dir 29 | mountPath: /var/lib/cni/usrspcni/ 30 | - name: scripts 31 | mountPath: /vpp/ 32 | resources: 33 | requests: 34 | hugepages-2Mi: 1Gi 35 | memory: "1Gi" 36 | cpu: "3" 37 | limits: 38 | hugepages-2Mi: 1Gi 39 | memory: "1Gi" 40 | cpu: "3" 41 | restartPolicy: Always 42 | volumes: 43 | - name: podinfo 44 | downwardAPI: 45 | items: 46 | - path: "labels" 47 | fieldRef: 48 | fieldPath: metadata.labels 49 | - path: "annotations" 50 | fieldRef: 51 | fieldPath: metadata.annotations 52 | - name: vpp-startup-config 53 | configMap: 54 | name: vpp-startup-config 55 | - name: hugepage 56 | emptyDir: 57 | medium: HugePages 58 | - name: shared-dir 59 | hostPath: 60 | path: /run/vpp/app1 61 | - name: scripts 62 | configMap: 63 | name: vpp-pod-setup-memif 64 | defaultMode: 0777 65 | -------------------------------------------------------------------------------- /examples/vpp-memif-ping/vpp-app-pod-2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: vpp-app2 6 | labels: 7 | name: vpp-app2 8 | namespace: vpp 9 | annotations: 10 | k8s.v1.cni.cncf.io/networks: userspace-vpp-net-1 11 | userspace/mappedDir: /var/lib/cni/usrspcni/ 12 | spec: 13 | nodeSelector: 14 | kubernetes.io/hostname: kind-control-plane # replace this with your hostname 15 | hostname: vpp-app2 16 | subdomain: vpp 17 | containers: 18 | - image: ligato/vpp-base:23.02 #imagename 19 | imagePullPolicy: IfNotPresent 20 | name: vpp-app1 21 | volumeMounts: 22 | - name: podinfo 23 | mountPath: /etc/podinfo 24 | - name: vpp-startup-config 25 | mountPath: /etc/vpp/ 26 | - name: hugepage 27 | mountPath: /hugepages 28 | - name: shared-dir 29 | mountPath: /var/lib/cni/usrspcni/ 30 | - name: scripts 31 | mountPath: /vpp/ 32 | resources: 33 | requests: 34 | hugepages-2Mi: 1Gi 35 | memory: "1Gi" 36 | cpu: "3" 37 | limits: 38 | hugepages-2Mi: 1Gi 39 | memory: "1Gi" 40 | cpu: "3" 41 | restartPolicy: Always 42 | volumes: 43 | - name: podinfo 44 | downwardAPI: 45 | items: 46 | - path: "labels" 47 | fieldRef: 48 | fieldPath: metadata.labels 49 | - path: "annotations" 50 | fieldRef: 51 | fieldPath: metadata.annotations 52 | - name: vpp-startup-config 53 | configMap: 54 | name: vpp-startup-config 55 | - name: hugepage 56 | emptyDir: 57 | medium: HugePages 58 | - name: shared-dir 59 | hostPath: 60 | path: /run/vpp/app2 61 | - name: scripts 62 | configMap: 63 | name: vpp-pod-setup-memif 64 | defaultMode: 0777 65 | -------------------------------------------------------------------------------- /ci/vpp_test_setup/vpp_host.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set USERSPACEDIR if not defined in parent script 4 | USERSPACEDIR="${USERSPACEDIR:=/runner/_work/userspace-cni-network-plugin/userspace-cni-network-plugin/}" 5 | 6 | kubectl delete ns vpp 7 | kubectl create ns vpp 8 | kubectl create -n vpp configmap vpp-startup-config --from-file="${USERSPACEDIR}/examples/sample-vpp-host-config/startup.conf" 9 | 10 | worker="kind-control-plane" 11 | 12 | docker exec -i kind-control-plane bash -c "mkdir -p /var/run/vpp/app" 13 | 14 | cat << EOF | kubectl apply -f - 15 | --- 16 | apiVersion: v1 17 | kind: Pod 18 | metadata: 19 | name: vpp-$worker 20 | labels: 21 | name: vpp 22 | namespace: vpp 23 | spec: 24 | nodeSelector: 25 | kubernetes.io/hostname: $worker 26 | hostname: vpp-$worker 27 | subdomain: vpp 28 | containers: 29 | - image: ligato/vpp-base:23.02 #imagename 30 | imagePullPolicy: IfNotPresent 31 | name: vpp-$worker 32 | volumeMounts: 33 | - name: vpp-api 34 | mountPath: /run/vpp/ 35 | - name: vpp-run 36 | mountPath: /var/run/vpp/ 37 | - name: vpp-startup-config 38 | mountPath: /etc/vpp/ 39 | - name: hugepage 40 | mountPath: /hugepages 41 | - name: userspace-api 42 | mountPath: /var/lib/cni/usrspcni/ 43 | resources: 44 | requests: 45 | hugepages-2Mi: 1Gi 46 | memory: "1Gi" 47 | cpu: "3" 48 | limits: 49 | hugepages-2Mi: 1Gi 50 | memory: "1Gi" 51 | cpu: "3" 52 | restartPolicy: Always 53 | volumes: 54 | - name: vpp-run 55 | hostPath: 56 | path: /var/run/vpp/ 57 | - name: vpp-api 58 | hostPath: 59 | path: /run/vpp/ 60 | - name: userspace-api 61 | hostPath: 62 | path: /var/lib/cni/usrspcni/ 63 | - name: vpp-startup-config 64 | configMap: 65 | name: vpp-startup-config 66 | - name: hugepage 67 | emptyDir: 68 | medium: HugePages 69 | EOF 70 | 71 | -------------------------------------------------------------------------------- /.github/workflows/trivy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: trivy 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ "main" ] 14 | schedule: 15 | - cron: '40 20 * * 4' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | build: 22 | permissions: 23 | contents: read # for actions/checkout to fetch code 24 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 25 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 26 | name: Build 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Harden Runner 30 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 31 | with: 32 | egress-policy: audit 33 | 34 | - name: Checkout code 35 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 36 | 37 | 38 | - name: Build the Docker image 39 | run: docker build . -f ./docker/userspacecni/Dockerfile -t userspacecni:latest 40 | 41 | 42 | - name: Run Trivy vulnerability scanner 43 | uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 44 | with: 45 | image-ref: 'userspacecni:latest' 46 | format: 'template' 47 | template: '@/contrib/sarif.tpl' 48 | output: 'trivy-results.sarif' 49 | severity: 'CRITICAL,HIGH' 50 | 51 | - name: Upload Trivy scan results to GitHub Security tab 52 | uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 53 | with: 54 | sarif_file: 'trivy-results.sarif' 55 | -------------------------------------------------------------------------------- /.github/workflows/trivy-testpmd.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: trivy-testpmd 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ "main" ] 14 | schedule: 15 | - cron: '40 20 * * 4' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | build: 22 | permissions: 23 | contents: read # for actions/checkout to fetch code 24 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 25 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 26 | name: Build 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Harden Runner 30 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 31 | with: 32 | egress-policy: audit 33 | 34 | - name: Checkout code 35 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 36 | 37 | - name: Build testpmd image 38 | run: docker build -t testpmd:latest -f ./docker/testpmd/Dockerfile ./docker/testpmd/ 39 | 40 | - name: Testpmd Run Trivy vulnerability scanner 41 | uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 42 | with: 43 | image-ref: 'testpmd:latest' 44 | format: 'template' 45 | template: '@/contrib/sarif.tpl' 46 | output: 'testpmd-trivy-results.sarif' 47 | severity: 'CRITICAL,HIGH' 48 | 49 | - name: Upload Trivy scan results to GitHub Security tab 50 | uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 51 | with: 52 | sarif_file: 'testpmd-trivy-results.sarif' 53 | -------------------------------------------------------------------------------- /userspace/testdata/testdata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testdata 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/containernetworking/cni/pkg/skel" 21 | v1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/util/uuid" 24 | ) 25 | 26 | // Unit test related functions 27 | func GetTestPod(sharedDir string) *v1.Pod { 28 | id := uuid.NewUUID() 29 | pod := &v1.Pod{ 30 | TypeMeta: metav1.TypeMeta{ 31 | Kind: "Pod", 32 | APIVersion: "v1", 33 | }, 34 | ObjectMeta: metav1.ObjectMeta{ 35 | UID: id, 36 | Name: fmt.Sprintf("pod-%v", id[:8]), 37 | Namespace: fmt.Sprintf("namespace-%v", id[:8]), 38 | }, 39 | } 40 | if sharedDir != "" { 41 | pod.Spec.Volumes = append(pod.Spec.Volumes, 42 | v1.Volume{ 43 | Name: "shared-dir", 44 | VolumeSource: v1.VolumeSource{ 45 | HostPath: &v1.HostPathVolumeSource{ 46 | Path: sharedDir, 47 | }, 48 | }, 49 | }) 50 | pod.Spec.Containers = append(pod.Spec.Containers, 51 | v1.Container{ 52 | Name: "container", 53 | VolumeMounts: []v1.VolumeMount{{Name: "shared-dir", MountPath: sharedDir}}, 54 | }) 55 | } 56 | return pod 57 | } 58 | 59 | func GetTestArgs() *skel.CmdArgs { 60 | id := uuid.NewUUID() 61 | return &skel.CmdArgs{ 62 | ContainerID: string(id), 63 | IfName: fmt.Sprintf("eth%v", int(id[7])), 64 | StdinData: []byte("{}"), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | schedule: 10 | - cron: "37 4 * * 0" 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ go ] 28 | 29 | steps: 30 | - name: Harden Runner 31 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 32 | with: 33 | egress-policy: audit 34 | 35 | - name: Set up Go 36 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 37 | with: 38 | go-version: 1.22.3 39 | - name: Checkout 40 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.5.2 41 | 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v2.3.3 44 | with: 45 | languages: ${{ matrix.language }} 46 | queries: +security-and-quality 47 | 48 | - name: Autobuild 49 | uses: github/codeql-action/autobuild@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v2.3.3 50 | 51 | - name: Perform CodeQL Analysis 52 | uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v2.3.3 53 | with: 54 | category: "/language:${{ matrix.language }}" 55 | 56 | - name: CodeQL and Dependabot Report Action 57 | if: ${{ github.event_name == 'workflow_dispatch' }} 58 | uses: rsdmike/github-security-report-action@a149b24539044c92786ec39af8ba38c93496495d # v3.0.4 59 | with: 60 | template: report 61 | token: ${{ secrets.SECURITY_TOKEN }} 62 | 63 | - name: GitHub Upload Release Artifacts 64 | if: ${{ github.event_name == 'workflow_dispatch' }} 65 | uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 66 | with: 67 | name: report 68 | path: | 69 | ./report.pdf 70 | -------------------------------------------------------------------------------- /ci/ovs_test_setup/testpmd_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CI_DIR="/runner/_work/userspace-cni-network-plugin/userspace-cni-network-plugin/ci/" 3 | kubectl apply -f $CI_DIR/ovs_test_setup/ovs_network_attachment_definition.yaml 4 | worker="kind-control-plane" 5 | numbers=("1" "2") 6 | 7 | for number in "${numbers[@]}"; do 8 | docker exec -i kind-control-plane bash -c ' rm -rf "/var/run/openvswitch/app$number"' 9 | docker exec -i kind-control-plane bash -c ' ls -lah /var/run/openvswitch/' 10 | docker exec -i kind-control-plane bash -c ' mkdir -p "/var/run/openvswitch/app$number"' 11 | docker exec -i kind-control-plane bash -c ' mkdir -p "/run/openvswitch/app$number"' 12 | 13 | cat << EOF | kubectl apply -f - 14 | --- 15 | apiVersion: v1 16 | kind: Pod 17 | metadata: 18 | name: ovs-app$number-$worker 19 | labels: 20 | name: ovs-app$number 21 | namespace: ovs 22 | annotations: 23 | k8s.v1.cni.cncf.io/networks: userspace-ovs-net-1 24 | userspace/mappedDir: /var/lib/cni/usrspcni/ 25 | spec: 26 | nodeSelector: 27 | kubernetes.io/hostname: $worker 28 | hostname: ovs-app$number-$worker 29 | subdomain: ovs 30 | containers: 31 | - image: testpmd 32 | imagePullPolicy: IfNotPresent 33 | name: ovs-app$number-$worker 34 | volumeMounts: 35 | - name: podinfo 36 | mountPath: /etc/podinfo 37 | - name: hugepage 38 | mountPath: /hugepages 39 | - name: shared-dir 40 | mountPath: /var/lib/cni/usrspcni/ 41 | - name: scripts 42 | mountPath: /scripts/ 43 | - name: vfio 44 | mountPath: /dev/vfio/ 45 | resources: 46 | requests: 47 | hugepages-2Mi: 1Gi 48 | memory: "500Mi" 49 | cpu: "5" 50 | limits: 51 | hugepages-2Mi: 1Gi 52 | memory: "500Mi" 53 | cpu: "5" 54 | # command: ["/bin/sh"] 55 | # args: ["-c", "sleep inf"] 56 | restartPolicy: Always 57 | volumes: 58 | - name: podinfo 59 | downwardAPI: 60 | items: 61 | - path: "labels" 62 | fieldRef: 63 | fieldPath: metadata.labels 64 | - path: "annotations" 65 | fieldRef: 66 | fieldPath: metadata.annotations 67 | - name: hugepage 68 | emptyDir: 69 | medium: HugePages 70 | - name: shared-dir 71 | # - name: socket 72 | hostPath: 73 | path: /run/openvswitch/app$number 74 | - name: scripts 75 | hostPath: 76 | path: $CI_DIR/ovs_test_setup/ 77 | - name: vfio 78 | hostPath: 79 | path: /dev/vfio/ 80 | EOF 81 | done 82 | -------------------------------------------------------------------------------- /ci/vpp_test_setup/vpp_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set CI_DIR if not defined in parent script 4 | CI_DIR="${CI_DIR:=/runner/_work/userspace-cni-network-plugin/userspace-cni-network-plugin/ci/}" 5 | kubectl apply -f "$CI_DIR/vpp_test_setup/network_attachment_definition.yaml" 6 | kubectl create -n vpp configmap vpp-app-startup-config --from-file="$CI_DIR/vpp_test_setup/startup.conf" 7 | worker="kind-control-plane" 8 | numbers=("1" "2") 9 | 10 | for number in "${numbers[@]}"; do 11 | docker exec -i kind-control-plane bash -c "rm -rf /var/run/vpp/app$number" 12 | docker exec -i kind-control-plane bash -c "ls -lah /var/run/vpp/" 13 | docker exec -i kind-control-plane bash -c "mkdir -p /var/run/vpp/app$number" 14 | 15 | cat << EOF | kubectl apply -f - 16 | --- 17 | apiVersion: v1 18 | kind: Pod 19 | metadata: 20 | name: vpp-app$number-$worker 21 | labels: 22 | name: vpp-app$number 23 | namespace: vpp 24 | annotations: 25 | k8s.v1.cni.cncf.io/networks: userspace-vpp-net-1 26 | userspace/mappedDir: /var/lib/cni/usrspcni/ 27 | spec: 28 | nodeSelector: 29 | kubernetes.io/hostname: $worker 30 | hostname: vpp-app$number-$worker 31 | subdomain: vpp 32 | containers: 33 | - image: ligato/vpp-base:23.02 #imagename 34 | imagePullPolicy: IfNotPresent 35 | name: vpp-app$number-$worker 36 | volumeMounts: 37 | - name: podinfo 38 | mountPath: /etc/podinfo 39 | - name: vpp-startup-config 40 | mountPath: /etc/vpp/ 41 | - name: hugepage 42 | mountPath: /hugepages 43 | - name: shared-dir 44 | mountPath: /var/lib/cni/usrspcni/ 45 | - name: scripts 46 | mountPath: /vpp/ 47 | resources: 48 | requests: 49 | hugepages-2Mi: 1Gi 50 | memory: "1Gi" 51 | cpu: "3" 52 | limits: 53 | hugepages-2Mi: 1Gi 54 | memory: "1Gi" 55 | cpu: "3" 56 | restartPolicy: Always 57 | volumes: 58 | - name: podinfo 59 | downwardAPI: 60 | items: 61 | - path: "labels" 62 | fieldRef: 63 | fieldPath: metadata.labels 64 | - path: "annotations" 65 | fieldRef: 66 | fieldPath: metadata.annotations 67 | - name: vpp-startup-config 68 | configMap: 69 | name: vpp-startup-config 70 | - name: hugepage 71 | emptyDir: 72 | medium: HugePages 73 | - name: shared-dir 74 | hostPath: 75 | path: /run/vpp/app$number 76 | - name: scripts 77 | hostPath: 78 | path: $CI_DIR/vpp_test_setup/ 79 | EOF 80 | done 81 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE_REGISTRY?=localhost:5000/ 2 | IMAGE_VERSION?=latest 3 | IMAGE_BUILDER?=docker 4 | 5 | IMAGE_NAME?=$(IMAGE_REGISTRY)userspacecni:$(IMAGE_VERSION) 6 | 7 | default: build 8 | local: build copy 9 | all: build push deploy 10 | 11 | help: 12 | @echo "Make Targets:" 13 | @echo "make build - Build UserSpace CNI container." 14 | @echo "make copy - Copy binary from container to host:/opt/cni/bin." 15 | @echo "make local - build and copy" 16 | @echo "make deploy - kubectl apply daemonset" 17 | @echo "make undeploy - kubectl delete daemonset" 18 | @echo "make all - build push and deploy to kubernetes" 19 | 20 | build: 21 | @$(IMAGE_BUILDER) build . -f ./docker/userspacecni/Dockerfile -t $(IMAGE_NAME) 22 | 23 | push: 24 | @$(IMAGE_BUILDER) push $(IMAGE_NAME) 25 | 26 | copy: 27 | # Copying the ovs binary to host /opt/cni/bin/ 28 | @mkdir -p /opt/cni/bin/ 29 | @$(IMAGE_BUILDER) run -it --rm -v /opt/cni/bin/:/opt/cni/bin/ $(IMAGE_NAME) 30 | 31 | generate-bin: generate 32 | # Used in dockerfile 33 | @cd userspace && go build -v 34 | 35 | generate: 36 | # Used in dockerfile 37 | @for package in cnivpp/api/* ; do cd $$package ; pwd ; go generate ; cd - ; done 38 | 39 | deploy: 40 | # Use sed to replace image name and then apply deployment file 41 | @sed "s|\(image:\).*\(#registory\)|\1 $(IMAGE_NAME) \2|g" ./kubernetes/userspace-daemonset.yml |kubectl apply -f - 42 | 43 | undeploy: 44 | kubectl delete -f ./kubernetes/userspace-daemonset.yml 45 | 46 | testpmd: 47 | @$(IMAGE_BUILDER) build -t $(IMAGE_REGISTRY)testpmd:latest -f ./docker/testpmd/Dockerfile ./docker/testpmd/ 48 | @$(IMAGE_BUILDER) push $(IMAGE_REGISTRY)testpmd:latest 49 | 50 | build-test-container: 51 | @$(IMAGE_BUILDER) rm -f userspacecni-unittest 52 | @$(IMAGE_BUILDER) build . -f ./docker/userspacecni/Dockerfile.unittest -t userspacecni-unittest:latest 53 | @$(IMAGE_BUILDER) run -m 100g --privileged -v ./examples/sample-vpp-host-config/startup.conf:/etc/vpp/startup.conf --name userspacecni-unittest -itd userspacecni-unittest:latest 54 | @$(IMAGE_BUILDER) cp userspacecni-unittest:/root/userspace-cni-network-plugin/cnivpp ./ 55 | 56 | unit-test: build-test-container 57 | @$(IMAGE_BUILDER) exec userspacecni-unittest bash -c "go test ./cnivpp/ -v -cover" 58 | @$(IMAGE_BUILDER) rm -f userspacecni-unittest 59 | 60 | fuzz: build-test-container 61 | @$(IMAGE_BUILDER) exec userspacecni-unittest bash -c "cd ./cnivpp/test; go test -fuzz=FuzzLoadNetConf -v -fuzztime 120s" 62 | @$(IMAGE_BUILDER) rm -f userspacecni-unittest 63 | -------------------------------------------------------------------------------- /cnivpp/api/infra/infra.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package vppinfra 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/sirupsen/logrus" 26 | 27 | "go.fd.io/govpp" 28 | "go.fd.io/govpp/api" 29 | "go.fd.io/govpp/core" 30 | ) 31 | 32 | // Constants 33 | const debugInfra = false 34 | 35 | // Types 36 | type ConnectionData struct { 37 | conn *core.Connection 38 | disconnectFlag bool 39 | Ch api.Channel 40 | closeFlag bool 41 | } 42 | 43 | // 44 | // API Functions 45 | // 46 | 47 | // Open a Connection and Channel to VPP to allow communication to VPP. 48 | func VppOpenCh() (ConnectionData, error) { 49 | 50 | var vppCh ConnectionData 51 | var err error 52 | 53 | // Set log level 54 | // Logrus has six logging levels: DebugLevel, InfoLevel, WarningLevel, ErrorLevel, FatalLevel and PanicLevel. 55 | core.SetLogger(&logrus.Logger{Level: logrus.ErrorLevel}) 56 | 57 | // Connect to VPP 58 | vppCh.conn, err = govpp.Connect("") 59 | if err != nil { 60 | if debugInfra { 61 | fmt.Println("Error:", err) 62 | } 63 | return vppCh, err 64 | } 65 | vppCh.disconnectFlag = true 66 | 67 | // Create an API channel to VPP 68 | vppCh.Ch, err = vppCh.conn.NewAPIChannel() 69 | if err != nil { 70 | VppCloseCh(vppCh) 71 | if debugInfra { 72 | fmt.Println("Error:", err) 73 | } 74 | return vppCh, err 75 | } 76 | vppCh.closeFlag = true 77 | 78 | return vppCh, err 79 | } 80 | 81 | // Close the Connection and Channel to VPP. 82 | func VppCloseCh(vppCh ConnectionData) { 83 | 84 | if vppCh.closeFlag { 85 | vppCh.Ch.Close() 86 | vppCh.closeFlag = false 87 | } 88 | 89 | if vppCh.disconnectFlag { 90 | vppCh.conn.Disconnect() 91 | vppCh.disconnectFlag = false 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.github/workflows/weekly_e2e.yml: -------------------------------------------------------------------------------- 1 | name: Weekly_E2E 2 | on: 3 | schedule: 4 | - cron: "37 4 * * 0" 5 | pull_request: 6 | paths: 7 | - '**weekly_e2e.yml' 8 | push: 9 | branches: 10 | - main 11 | paths: 12 | - '**weekly_e2e.yml' 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | 18 | vpp_latest: 19 | name: E2E_vpp_latest 20 | runs-on: hugepage-runner 21 | steps: 22 | - name: Harden Runner 23 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 24 | with: 25 | egress-policy: audit 26 | 27 | - name: Set up Go 28 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 29 | with: 30 | go-version: 1.22.3 31 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 32 | - name: vpp_ligato_latest_container 33 | run: source ./ci/ci.sh && vpp_ligato_latest_container 34 | - name: install_go_kubectl_kind 35 | run: source ./ci/ci.sh && install_go_kubectl_kind 36 | - name: create_kind_cluster 37 | run: source ./ci/ci.sh && create_kind_cluster 38 | - name: deploy_multus 39 | run: source ./ci/ci.sh && deploy_multus 40 | - name: deploy_userspacecni 41 | run: source ./ci/ci.sh && deploy_userspace 42 | - name: vpp_e2e_test 43 | run: source ./ci/ci.sh && vpp_e2e_test 44 | 45 | kind_multiversion: 46 | name: E2E_kind_multiversion-${{ matrix.kubernetes_version }} 47 | runs-on: hugepage-runner 48 | strategy: 49 | matrix: 50 | kubernetes_version: [v1.28.0,v1.27.0,v1.26.0] 51 | steps: 52 | - name: Harden Runner 53 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 54 | with: 55 | egress-policy: audit 56 | 57 | - name: Set up Go 58 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 59 | with: 60 | go-version: 1.22.3 61 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 62 | - name: install_go_kubectl_kind 63 | run: source ./ci/ci.sh && install_go_kubectl_kind 64 | - name: create_kind_cluster 65 | run: source ./ci/ci.sh && create_kind_cluster -v ${{ matrix.kubernetes_version }} 66 | - name: deploy_multus 67 | run: source ./ci/ci.sh && deploy_multus 68 | - name: deploy_userspacecni 69 | run: source ./ci/ci.sh && deploy_userspace 70 | - name: vpp_e2e_test 71 | run: source ./ci/ci.sh && vpp_e2e_test 72 | - name: build_ovs_container 73 | run: source ./ci/ci.sh && build_ovs_container 74 | - name: build_test-pmd_container 75 | run: source ./ci/ci.sh && build_testpmd_container 76 | - name: ovs_e2e_test 77 | run: source ./ci/ci.sh && ovs_e2e_test 78 | -------------------------------------------------------------------------------- /cnivpp/test/cni_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package fuzz 17 | 18 | import ( 19 | "strings" 20 | "testing" 21 | 22 | "github.com/intel/userspace-cni-network-plugin/userspace/cni" 23 | ) 24 | 25 | func FuzzLoadNetConf(f *testing.F) { 26 | 27 | seed := ` 28 | { 29 | "cniVersion": "0.3.1", 30 | "type": "userspace", 31 | "name": "userspace-ovs-net-1", 32 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 33 | "logFile": "/var/log/userspace-ovs-net-1-cni.log", 34 | "logLevel": "debug", 35 | "host": { 36 | "engine": "ovs-dpdk", 37 | "iftype": "vhostuser", 38 | "netType": "bridge", 39 | "vhost": { 40 | "mode": "client" 41 | }, 42 | "bridge": { 43 | "bridgeName": "br-4" 44 | } 45 | }, 46 | "container": { 47 | "engine": "ovs-dpdk", 48 | "iftype": "vhostuser", 49 | "netType": "interface", 50 | "vhost": { 51 | "mode": "server" 52 | } 53 | }, 54 | "ipam": { 55 | "type": "host-local", 56 | "subnet": "10.56.217.0/24", 57 | "rangeStart": "10.56.217.131", 58 | "rangeEnd": "10.56.217.190", 59 | "routes": [ 60 | { 61 | "dst": "0.0.0.0/0" 62 | } 63 | ], 64 | "gateway": "10.56.217.1" 65 | } 66 | } 67 | ` 68 | 69 | f.Add([]byte(seed)) 70 | 71 | f.Fuzz(func(t *testing.T, fcfg []byte) { 72 | _, err := cni.LoadNetConf(fcfg) 73 | if err != nil { 74 | if strings.Contains(err.Error(), "failed to load netconf:") { 75 | return 76 | } else { 77 | t.Errorf("Error: %s, for input %s", err.Error(), string(fcfg)) 78 | } 79 | 80 | } else { 81 | return 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/userspace-cni-network-plugin 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.3 6 | 7 | require ( 8 | github.com/containernetworking/cni v1.2.3 9 | github.com/containernetworking/plugins v1.5.1 10 | github.com/go-logfmt/logfmt v0.6.0 11 | github.com/pkg/errors v0.9.1 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/stretchr/testify v1.10.0 14 | github.com/vishvananda/netlink v1.3.0 15 | go.fd.io/govpp v0.11.0 16 | golang.org/x/sys v0.30.0 17 | k8s.io/api v0.30.2 18 | k8s.io/apimachinery v0.31.1 19 | k8s.io/client-go v0.30.2 20 | ) 21 | 22 | require ( 23 | github.com/coreos/go-iptables v0.7.0 // indirect 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 25 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 26 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 27 | github.com/fsnotify/fsnotify v1.7.0 // indirect 28 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 29 | github.com/go-logr/logr v1.4.2 // indirect 30 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 31 | github.com/go-openapi/jsonreference v0.20.2 // indirect 32 | github.com/go-openapi/swag v0.22.4 // indirect 33 | github.com/gogo/protobuf v1.3.2 // indirect 34 | github.com/golang/protobuf v1.5.4 // indirect 35 | github.com/google/gnostic-models v0.6.8 // indirect 36 | github.com/google/gofuzz v1.2.0 // indirect 37 | github.com/google/uuid v1.6.0 // indirect 38 | github.com/imdario/mergo v0.3.6 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect 42 | github.com/mailru/easyjson v0.7.7 // indirect 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 44 | github.com/modern-go/reflect2 v1.0.2 // indirect 45 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 46 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 47 | github.com/safchain/ethtool v0.4.0 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | github.com/vishvananda/netns v0.0.4 // indirect 50 | github.com/x448/float16 v0.8.4 // indirect 51 | golang.org/x/net v0.33.0 // indirect 52 | golang.org/x/oauth2 v0.10.0 // indirect 53 | golang.org/x/term v0.27.0 // indirect 54 | golang.org/x/text v0.21.0 // indirect 55 | golang.org/x/time v0.3.0 // indirect 56 | google.golang.org/appengine v1.6.7 // indirect 57 | google.golang.org/protobuf v1.34.2 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/klog/v2 v2.130.1 // indirect 62 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 63 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 64 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 65 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 66 | sigs.k8s.io/yaml v1.4.0 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /ci/vpp_test_setup/network_attachment_definition.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: "k8s.cni.cncf.io/v1" 3 | kind: NetworkAttachmentDefinition 4 | metadata: 5 | name: userspace-vpp-net-2 6 | namespace: vpp 7 | spec: 8 | config: '{ 9 | "cniVersion": "0.3.1", 10 | "type": "userspace", 11 | "name": "userspace-vpp-net-2", 12 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 13 | "logFile": "/var/log/userspace-vpp-net-2-cni.log", 14 | "logLevel": "debug", 15 | "host": { 16 | "engine": "vpp", 17 | "iftype": "memif", 18 | "netType": "bridge", 19 | "memif": { 20 | "role": "master", 21 | "mode": "ethernet" 22 | }, 23 | "bridge": { 24 | "bridgeName": "12" 25 | } 26 | }, 27 | "container": { 28 | "engine": "vpp", 29 | "iftype": "memif", 30 | "netType": "interface", 31 | "memif": { 32 | "role": "slave", 33 | "mode": "ethernet" 34 | } 35 | }, 36 | "ipam": { 37 | "type": "host-local", 38 | "subnet": "10.77.217.0/24", 39 | "rangeStart": "10.77.217.131", 40 | "rangeEnd": "10.77.217.190", 41 | "routes": [ 42 | { 43 | "dst": "0.0.0.0/0" 44 | } 45 | ], 46 | "gateway": "10.77.217.1" 47 | } 48 | }' 49 | --- 50 | apiVersion: "k8s.cni.cncf.io/v1" 51 | kind: NetworkAttachmentDefinition 52 | metadata: 53 | name: userspace-vpp-net-1 54 | namespace: vpp 55 | spec: 56 | config: '{ 57 | "cniVersion": "0.3.1", 58 | "type": "userspace", 59 | "name": "userspace-vpp-net-1", 60 | "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 61 | "logFile": "/var/log/userspace-vpp-net-1-cni.log", 62 | "logLevel": "debug", 63 | "host": { 64 | "engine": "vpp", 65 | "iftype": "memif", 66 | "netType": "bridge", 67 | "memif": { 68 | "role": "master", 69 | "mode": "ethernet" 70 | }, 71 | "bridge": { 72 | "bridgeName": "4" 73 | } 74 | }, 75 | "container": { 76 | "engine": "vpp", 77 | "iftype": "memif", 78 | "netType": "interface", 79 | "memif": { 80 | "role": "slave", 81 | "mode": "ethernet" 82 | } 83 | }, 84 | "ipam": { 85 | "type": "host-local", 86 | "subnet": "10.56.217.0/24", 87 | "rangeStart": "10.56.217.131", 88 | "rangeEnd": "10.56.217.190", 89 | "routes": [ 90 | { 91 | "dst": "0.0.0.0/0" 92 | } 93 | ], 94 | "gateway": "10.56.217.1" 95 | } 96 | }' 97 | -------------------------------------------------------------------------------- /cnivpp/README.md: -------------------------------------------------------------------------------- 1 | # VPP CNI Library Intro 2 | VPP CNI Library is written in GO and used by UserSpace CNI to interface with the 3 | VPP GO-API. The UserSpace CNI is a CNI implementation designed to implement 4 | User Space networking (as apposed to kernel space networking), like DPDK based 5 | applications. For example VPP and OVS-DPDK. 6 | 7 | The UserSpace CNI, based on the input config data, uses this library to add 8 | interfaces (memif and vhost-user interface) to a local VPP instance running on 9 | the host and add that interface to a local network, like a L2 Bridge. The 10 | UserSpace CNI then processes config data intended for the container and uses 11 | this library to add that data to a Database the container can consume. 12 | 13 | UserSpace CNI README.md describes how to test with VPP CNI. Below are a 14 | few notes regarding packages in this sub-folder. 15 | 16 | ## usrsp-app 17 | The **usrsp-app** is intended to run in a container. It leverages the VPP CNI code 18 | to consume interfaces in the container. 19 | 20 | ## cnivpp/localdb.go 21 | **localdb** is use to store data in a DB. For the local VPP instance, the localdb 22 | is used to store the swIndex generated when the interface is created. It is used 23 | later to delete the interface. The usrspdb is used to pass configuration 24 | data to the container so the container can consume the interface. 25 | 26 | The initial implementation of the DB is just json data written to a file. 27 | This can be expanded at a later date to write to something like an etcd DB. 28 | 29 | 30 | The following filenames and directory structure is used to store the data: 31 | * **Host**: 32 | * **/var/lib/cni/usrspcni/data/**: 33 | * **local--.json**: Used to store local data 34 | needed to delete and cleanup. 35 | 36 | * **/var/lib/cni/usrspcni/shared/**: Not a database directory, but this directory 37 | is used for interface socket files, for example: **memif--.sock** 38 | This directory is mapped into that container as the same directory in the container. 39 | 40 | * **/var/lib/cni/usrspcni//**: This directory is mapped into that container 41 | as **/var/lib/cni/usrspcni/data/**, so appears to the container as its local data 42 | directory. This is where the container writes its 43 | **local--.json** file described above. 44 | * **remote--.json**: This file contains the configuration 45 | to apply the interface in the container. The data is the same json data passed into 46 | the UserSpace CNI (define in **user-space-net-plugin/pkg/types/types.go**), but 47 | the Container data has been copied into the Host data label. The usrsp-app processes the 48 | data as local data. Once this data is read in the container, the usrsp-app deletes the 49 | file. 50 | * **addData--.json**: This file is used to pass 51 | additional data into the the container, which is not defined by **pkg/types/types.go**. 52 | This includes the ContainerId itself, and the results from the IPAM plugin that 53 | were processed locally. Once this data is read in the container, the usrsp-app deletes 54 | the file. 55 | 56 | * **Container**: 57 | * **/var/lib/cni/usrspcni/data/**: Mapped from **/var/lib/cni/usrspcni/container/** 58 | on the host. 59 | * **/var/lib/cni/usrspcni/shared/**: Mapped from **/var/lib/cni/usrspcni/shared/** on 60 | the host. 61 | 62 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | # schedule: 13 | # - cron: '32 15 * * 6' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: Harden Runner 35 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 36 | with: 37 | egress-policy: audit 38 | 39 | - name: "Checkout code" 40 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.1.0 41 | with: 42 | persist-credentials: false 43 | 44 | - name: "Run analysis" 45 | uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 46 | with: 47 | results_file: results.sarif 48 | results_format: sarif 49 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 50 | # - you want to enable the Branch-Protection check on a *public* repository, or 51 | # - you are installing Scorecard on a *private* repository 52 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 53 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 54 | 55 | # Public repositories: 56 | # - Publish results to OpenSSF REST API for easy access by consumers 57 | # - Allows the repository to include the Scorecard badge. 58 | # - See https://github.com/ossf/scorecard-action#publishing-results. 59 | # For private repositories: 60 | # - `publish_results` will always be set to `false`, regardless 61 | # of the value entered here. 62 | publish_results: true 63 | 64 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 65 | # format to the repository Actions tab. 66 | - name: "Upload artifact" 67 | uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 68 | with: 69 | name: SARIF file 70 | path: results.sarif 71 | retention-days: 5 72 | 73 | # Upload the results to GitHub's code scanning dashboard. 74 | - name: "Upload to code-scanning" 75 | uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 76 | with: 77 | sarif_file: results.sarif 78 | -------------------------------------------------------------------------------- /cnivpp/api/interface/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package vppinterface 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | 25 | current "github.com/containernetworking/cni/pkg/types/100" 26 | interfaces "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface" 27 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 28 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/ip_types" 29 | "go.fd.io/govpp/api" 30 | ) 31 | 32 | // Constants 33 | const debugInterface = false 34 | 35 | // 36 | // API Functions 37 | // 38 | 39 | // Attempt to set an interface state. isUp (1 = up, 0 = down) 40 | func SetState(ch api.Channel, swIfIndex interface_types.InterfaceIndex, isUp interface_types.IfStatusFlags) error { 41 | // Populate the Add Structure 42 | req := &interfaces.SwInterfaceSetFlags{ 43 | SwIfIndex: swIfIndex, 44 | // 1 = up, 0 = down 45 | Flags: isUp, 46 | } 47 | 48 | reply := &interfaces.SwInterfaceSetFlagsReply{} 49 | 50 | err := ch.SendRequest(req).ReceiveReply(reply) 51 | 52 | if err != nil { 53 | if debugInterface { 54 | fmt.Println("Error:", err) 55 | } 56 | return err 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func AddDelIpAddress(ch api.Channel, swIfIndex interface_types.InterfaceIndex, isAdd bool, ipResult *current.Result) error { 63 | 64 | // Populate the Add Structure 65 | req := &interfaces.SwInterfaceAddDelAddress{ 66 | SwIfIndex: swIfIndex, 67 | IsAdd: isAdd, // 1 = add, 0 = delete 68 | DelAll: false, 69 | } 70 | for _, ip := range ipResult.IPs { 71 | var addressWithPrefix ip_types.AddressWithPrefix 72 | 73 | if prefix, _ := ip.Address.Mask.Size(); prefix == 4 { 74 | addressWithPrefix = ip_types.AddressWithPrefix{Address: ip_types.AddressFromIP(ip.Address.IP.To4()), Len: 4} 75 | 76 | } else if prefix, _ := ip.Address.Mask.Size(); prefix == 16 { 77 | addressWithPrefix = ip_types.AddressWithPrefix{Address: ip_types.AddressFromIP(ip.Address.IP.To16()), Len: 16} 78 | } else { 79 | break 80 | } 81 | fmt.Println(addressWithPrefix) 82 | req.Prefix = addressWithPrefix 83 | 84 | // Only one address is currently supported. 85 | if req.Prefix.Len != 0 { 86 | break 87 | } 88 | } 89 | 90 | reply := &interfaces.SwInterfaceAddDelAddressReply{} 91 | 92 | err := ch.SendRequest(req).ReceiveReply(reply) 93 | 94 | if err != nil { 95 | if debugInterface { 96 | fmt.Println("Error:", err) 97 | } 98 | return err 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /cniovs/localdb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2020 Red Hat, Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // This module provides the database library functions. Initial implementation 17 | // copies the json data to a known file location. 18 | // 19 | 20 | package cniovs 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/containernetworking/cni/pkg/skel" 29 | 30 | "github.com/intel/userspace-cni-network-plugin/pkg/annotations" 31 | "github.com/intel/userspace-cni-network-plugin/pkg/configdata" 32 | "github.com/intel/userspace-cni-network-plugin/pkg/types" 33 | ) 34 | 35 | // 36 | // Constants 37 | // 38 | 39 | // 40 | // Types 41 | // 42 | 43 | // This structure is a union of all the OVS data (for all types of 44 | // interfaces) that need to be preserved for later use. 45 | type OvsSavedData struct { 46 | Vhostname string `json:"vhostname"` // Vhost Port name 47 | VhostMac string `json:"vhostmac"` // Vhost port MAC address 48 | IfMac string `json:"ifmac"` // Interface Mac address 49 | } 50 | 51 | // 52 | // API Functions 53 | // 54 | 55 | // SaveConfig() - Some data needs to be saved for cmdDel(). 56 | // 57 | // This function squirrels the data away to be retrieved later. 58 | func SaveConfig(conf *types.NetConf, args *skel.CmdArgs, data *OvsSavedData) error { 59 | 60 | // Current implementation is to write data to a file with the name: 61 | // /var/run/ovs/cni/data/local--.json 62 | 63 | fileName := fmt.Sprintf("local-%s-%s.json", args.ContainerID[:12], args.IfName) 64 | if dataBytes, err := json.Marshal(data); err == nil { 65 | localDir := annotations.DefaultLocalCNIDir 66 | 67 | if _, err := os.Stat(localDir); err != nil { 68 | if os.IsNotExist(err) { 69 | if err := os.MkdirAll(localDir, 0700); err != nil { 70 | return err 71 | } 72 | } else { 73 | return err 74 | } 75 | } 76 | 77 | path := filepath.Join(localDir, fileName) 78 | 79 | return os.WriteFile(path, dataBytes, 0644) 80 | } else { 81 | return fmt.Errorf("ERROR: serializing delegate OVS saved data: %v", err) 82 | } 83 | } 84 | 85 | func LoadConfig(conf *types.NetConf, args *skel.CmdArgs, data *OvsSavedData) error { 86 | 87 | fileName := fmt.Sprintf("local-%s-%s.json", args.ContainerID[:12], args.IfName) 88 | localDir := annotations.DefaultLocalCNIDir 89 | path := filepath.Join(localDir, fileName) 90 | 91 | if _, err := os.Stat(path); err == nil { 92 | if dataBytes, err := os.ReadFile(path); err == nil { 93 | if err = json.Unmarshal(dataBytes, data); err != nil { 94 | return fmt.Errorf("ERROR: Failed to parse OVS saved data: %v", err) 95 | } 96 | } else { 97 | return fmt.Errorf("ERROR: Failed to read OVS saved data: %v", err) 98 | } 99 | 100 | } else { 101 | path = "" 102 | } 103 | 104 | // Delete file (and directory if empty) 105 | _ = configdata.FileCleanup(localDir, path) 106 | 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /.github/workflows/static-scan.yml: -------------------------------------------------------------------------------- 1 | name: Go-static-analysis 2 | on: [push, pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | 9 | golangci: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Harden Runner 14 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 15 | with: 16 | egress-policy: audit 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 20 | with: 21 | go-version: 1.22.3 22 | 23 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.5.3 24 | - name: remove deployer container from dockerfile 25 | run: sed -ie '/End of builder/,+5d' ./docker/userspacecni/Dockerfile 26 | - name: Build the Docker image 27 | run: docker build . -f ./docker/userspacecni/Dockerfile -t userspacecni:latest 28 | - name: run container 29 | run: docker run --name userspacecni -itd userspacecni:latest bash 30 | - name: docker copy generated bin api files 31 | run: docker cp userspacecni:/root/userspace-cni-network-plugin/cnivpp ./ 32 | 33 | - name: golangci-lint 34 | uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0 35 | with: 36 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 37 | version: v1.56.2 38 | args: --timeout=20m 39 | 40 | shellcheck: 41 | name: Shellcheck 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Harden Runner 45 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 46 | with: 47 | egress-policy: audit 48 | 49 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.5.3 50 | - name: Run ShellCheck 51 | uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # main 52 | 53 | hadolint: 54 | runs-on: ubuntu-latest 55 | name: Hadolint 56 | env: 57 | HADOLINT_RECURSIVE: "true" 58 | steps: 59 | - name: Harden Runner 60 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 61 | with: 62 | egress-policy: audit 63 | 64 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 65 | - uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 66 | name: Run Hadolint 67 | with: 68 | recursive: true 69 | ignore: DL3008,DL3059,DL3015 70 | 71 | go-check: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - name: Harden Runner 75 | uses: step-security/harden-runner@c95a14d0e5bab51a9f56296a4eb0e416910cd350 # v2.10.3 76 | with: 77 | egress-policy: audit 78 | 79 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3.5.2 80 | 81 | - name: remove deployer container from dockerfile 82 | run: sed -ie '/End of builder/,+5d' ./docker/userspacecni/Dockerfile 83 | - name: Build the Docker image 84 | run: docker build . -f ./docker/userspacecni/Dockerfile -t userspacecni:latest 85 | - name: run container 86 | run: docker run --name userspacecni -itd userspacecni:latest bash 87 | - name: docker copy generated bin api files 88 | run: docker cp userspacecni:/root/userspace-cni-network-plugin/cnivpp ./ 89 | 90 | - name: Set up Go 91 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 92 | with: 93 | go-version: 1.22.3 94 | 95 | - name: Checkout dockerfile to avoid false diff 96 | run: git checkout ./docker/userspacecni/Dockerfile 97 | # if this fails, run go mod tidy 98 | - name: Tidy 99 | run: go mod tidy 100 | - name: Check if module files are consistent with code 101 | run: git diff --exit-code 102 | 103 | # if this fails, run go mod vendor 104 | - name: Check if vendor directory is consistent with go modules 105 | run: go mod vendor && git diff --exit-code 106 | -------------------------------------------------------------------------------- /cniovs/localdb_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cniovs 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "path" 22 | "testing" 23 | 24 | "github.com/intel/userspace-cni-network-plugin/pkg/annotations" 25 | "github.com/intel/userspace-cni-network-plugin/userspace/testdata" 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestSaveConfig(t *testing.T) { 31 | args := testdata.GetTestArgs() 32 | 33 | testCases := []struct { 34 | name string 35 | data *OvsSavedData 36 | }{ 37 | { 38 | name: "save and load data 1", 39 | data: &OvsSavedData{Vhostname: "vhost0", VhostMac: "fe:ed:de:ad:be:ef", IfMac: "co:co:ca:fe:da:da"}, 40 | }, 41 | { 42 | name: "save and load data 2", 43 | data: &OvsSavedData{VhostMac: "fe:ed:de:ad:be:ef", IfMac: "co:co:ca:fe:da:da"}, 44 | }, 45 | { 46 | name: "save and load data 3", 47 | data: &OvsSavedData{VhostMac: "fe:ed:de:ad:be:ef"}, 48 | }, 49 | { 50 | name: "save and load data 4", 51 | data: &OvsSavedData{}, 52 | }, 53 | } 54 | for _, tc := range testCases { 55 | t.Run(tc.name, func(t *testing.T) { 56 | var data OvsSavedData 57 | require.NoError(t, SaveConfig(nil, args, tc.data), "Unexpected error") 58 | require.NoError(t, LoadConfig(nil, args, &data), "Can't read stored data") 59 | assert.Equal(t, tc.data, &data, "Unexpected data retrieved") 60 | }) 61 | 62 | } 63 | } 64 | 65 | func TestLoadConfig(t *testing.T) { 66 | args := testdata.GetTestArgs() 67 | 68 | testCases := []struct { 69 | name string 70 | jsonFile string 71 | expErr error 72 | expData *OvsSavedData 73 | }{ 74 | // test error cases; Successful config load is tested by TestSaveConfig 75 | { 76 | name: "no file with saved data", 77 | jsonFile: "none", 78 | expErr: nil, 79 | expData: &OvsSavedData{}, 80 | }, 81 | { 82 | name: "fail to load corrupted JSON", 83 | jsonFile: "corrupted", 84 | expErr: errors.New("ERROR: Failed to parse"), 85 | expData: &OvsSavedData{}, 86 | }, 87 | { 88 | name: "fail to read broken file", 89 | jsonFile: "directory", 90 | expErr: errors.New("ERROR: Failed to read"), 91 | expData: &OvsSavedData{}, 92 | }, 93 | } 94 | for _, tc := range testCases { 95 | t.Run(tc.name, func(t *testing.T) { 96 | localDir := annotations.DefaultLocalCNIDir 97 | fileName := fmt.Sprintf("local-%s-%s.json", args.ContainerID[:12], args.IfName) 98 | if _, err := os.Stat(localDir); err != nil { 99 | require.NoError(t, os.MkdirAll(localDir, 0700), "Can't create data dir") 100 | defer os.RemoveAll(localDir) 101 | } 102 | path := path.Join(localDir, fileName) 103 | 104 | switch tc.jsonFile { 105 | case "none": 106 | require.NoFileExists(t, path, "Saved configuration shall not exist") 107 | case "corrupted": 108 | require.NoError(t, os.WriteFile(path, []byte("{"), 0644), "Can't create test file") 109 | defer os.Remove(path) 110 | case "directory": 111 | require.NoError(t, os.Mkdir(path, 0700), "Can't create test dir") 112 | defer os.RemoveAll(path) 113 | } 114 | var data OvsSavedData 115 | err := LoadConfig(nil, args, &data) 116 | if tc.expErr == nil { 117 | assert.Equal(t, tc.expErr, err, "Unexpected result") 118 | } else { 119 | require.Error(t, err, "Unexpected result") 120 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected result") 121 | } 122 | assert.Equal(t, tc.expData, &data, "Unexpected result") 123 | }) 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /cnivpp/api/vhostuser/vhostuser.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package vppvhostuser 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 26 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/vhost_user" 27 | "go.fd.io/govpp/api" 28 | ) 29 | 30 | // 31 | // Constants 32 | // 33 | 34 | const debugVhost = false 35 | 36 | type VhostUserMode bool 37 | 38 | const ( 39 | ModeClient VhostUserMode = false 40 | ModeServer VhostUserMode = true 41 | ) 42 | 43 | // 44 | // API Functions 45 | // 46 | 47 | // Attempt to create a Vhost-User Interface. 48 | // Input: 49 | // 50 | // ch api.Channel 51 | // mode VhostUserMode - ModeClient or ModeServer 52 | // socketFile string - Directory and Filename of socket file 53 | func CreateVhostUserInterface(ch api.Channel, mode bool, socketFile string) (swIfIndex interface_types.InterfaceIndex, err error) { 54 | 55 | // Populate the Add Structure 56 | req := &vhost_user.CreateVhostUserIf{ 57 | IsServer: mode, 58 | SockFilename: socketFile, 59 | Renumber: false, 60 | CustomDevInstance: 0, 61 | UseCustomMac: false, 62 | //MacAddress: "", 63 | //Tag: "", 64 | } 65 | 66 | reply := &vhost_user.CreateVhostUserIfReply{} 67 | 68 | err = ch.SendRequest(req).ReceiveReply(reply) 69 | 70 | if err != nil { 71 | if debugVhost { 72 | fmt.Println("Error creating vhostUser interface:", err) 73 | } 74 | return 75 | } else { 76 | swIfIndex = reply.SwIfIndex 77 | } 78 | 79 | return 80 | } 81 | 82 | // Attempt to delete a Vhost-User interface. 83 | func DeleteVhostUserInterface(ch api.Channel, swIfIndex interface_types.InterfaceIndex) (err error) { 84 | 85 | // Populate the Delete Structure 86 | req := &vhost_user.DeleteVhostUserIf{ 87 | SwIfIndex: swIfIndex, 88 | } 89 | 90 | reply := &vhost_user.DeleteVhostUserIfReply{} 91 | 92 | err = ch.SendRequest(req).ReceiveReply(reply) 93 | 94 | if err != nil { 95 | if debugVhost { 96 | fmt.Println("Error deleting vhostUser interface:", err) 97 | } 98 | return err 99 | } 100 | 101 | return err 102 | } 103 | 104 | // Dump the set of existing Vhost-User interfaces to stdout. 105 | func DumpVhostUser(ch api.Channel) { 106 | var count int 107 | 108 | // Populate the Message Structure 109 | req := &vhost_user.SwInterfaceVhostUserDump{} 110 | reqCtx := ch.SendMultiRequest(req) 111 | 112 | fmt.Printf("Vhost-User Interface List:\n") 113 | for { 114 | reply := &vhost_user.SwInterfaceVhostUserDetails{} 115 | stop, err := reqCtx.ReceiveReply(reply) 116 | if stop { 117 | break // break out of the loop 118 | } 119 | if err != nil { 120 | fmt.Println("Error dumping vhostUser interface:", err) 121 | } 122 | //fmt.Printf("%+v\n", reply) 123 | 124 | fmt.Printf(" SwIfId=%d Mode=%t IfName=%s NumReg=%d SockErrno=%d FeaturesFirst32=%d FeaturesLast32=%d HdrSz=%d SockFile=%s\n", 125 | reply.SwIfIndex, 126 | reply.IsServer, 127 | string(reply.InterfaceName), 128 | reply.NumRegions, 129 | reply.SockErrno, 130 | reply.FeaturesFirst32, 131 | reply.FeaturesLast32, 132 | reply.VirtioNetHdrSz, 133 | string(reply.SockFilename)) 134 | 135 | count++ 136 | } 137 | 138 | fmt.Printf(" Interface Count: %d\n", count) 139 | } 140 | -------------------------------------------------------------------------------- /cnivpp/localdb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Red Hat. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // This module provides the library functions to implement the 17 | // VPP UserSpace CNI implementation. The input to the library is json 18 | // data defined in pkg/types. If the configuration contains local data, 19 | // the 'api' library is used to send the request to the local govpp-agent, 20 | // which provisions the local VPP instance. If the configuration contains 21 | // remote data, the database library is used to store the data, which is 22 | // later read and processed locally by the remotes agent (usrsp-app running 23 | // in the container) 24 | // 25 | 26 | package cnivpp 27 | 28 | import ( 29 | "encoding/json" 30 | "fmt" 31 | "os" 32 | "path/filepath" 33 | 34 | "github.com/containernetworking/cni/pkg/skel" 35 | 36 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 37 | "github.com/intel/userspace-cni-network-plugin/pkg/annotations" 38 | "github.com/intel/userspace-cni-network-plugin/pkg/configdata" 39 | "github.com/intel/userspace-cni-network-plugin/pkg/types" 40 | ) 41 | 42 | // Constants 43 | const debugVppDb = false 44 | 45 | // 46 | // Types 47 | // 48 | 49 | // This structure is a union of all the VPP data (for all types of 50 | // interfaces) that need to be preserved for later use. 51 | type VppSavedData struct { 52 | InterfaceSwIfIndex interface_types.InterfaceIndex `json:"swIfIndex"` // Software Index, used to access the created interface, needed to delete interface. 53 | MemifSocketId uint32 `json:"memifSocketId"` // Memif SocketId, used to access the created memif Socket File, used for debug only. 54 | } 55 | 56 | // 57 | // API Functions 58 | // 59 | 60 | // saveVppConfig() - Some data needs to be saved, like the swIfIndex, for cmdDel(). 61 | // 62 | // This function squirrels the data away to be retrieved later. 63 | func SaveVppConfig(conf *types.NetConf, args *skel.CmdArgs, data *VppSavedData) error { 64 | 65 | // Current implementation is to write data to a file with the name: 66 | // /var/run/vpp/cni/data/local--.json 67 | 68 | fileName := fmt.Sprintf("local-%s-%s.json", args.ContainerID[:12], args.IfName) 69 | if dataBytes, err := json.Marshal(data); err == nil { 70 | localDir := annotations.DefaultLocalCNIDir 71 | 72 | if _, err := os.Stat(localDir); err != nil { 73 | if os.IsNotExist(err) { 74 | if err := os.MkdirAll(localDir, 0700); err != nil { 75 | return err 76 | } 77 | } else { 78 | return err 79 | } 80 | } 81 | 82 | path := filepath.Join(localDir, fileName) 83 | 84 | if debugVppDb { 85 | fmt.Printf("SAVE FILE: swIfIndex=%d path=%s dataBytes=%s\n", data.InterfaceSwIfIndex, path, dataBytes) 86 | } 87 | return os.WriteFile(path, dataBytes, 0644) 88 | } else { 89 | return fmt.Errorf("ERROR: serializing delegate VPP saved data: %v", err) 90 | } 91 | } 92 | 93 | func LoadVppConfig(conf *types.NetConf, args *skel.CmdArgs, data *VppSavedData) error { 94 | 95 | fileName := fmt.Sprintf("local-%s-%s.json", args.ContainerID[:12], args.IfName) 96 | localDir := annotations.DefaultLocalCNIDir 97 | path := filepath.Join(localDir, fileName) 98 | 99 | if _, err := os.Stat(path); err == nil { 100 | if dataBytes, err := os.ReadFile(path); err == nil { 101 | if err = json.Unmarshal(dataBytes, data); err != nil { 102 | return fmt.Errorf("ERROR: Failed to parse VPP saved data: %v", err) 103 | } 104 | } else { 105 | return fmt.Errorf("ERROR: Failed to read VPP saved data: %v", err) 106 | } 107 | 108 | } else { 109 | path = "" 110 | } 111 | 112 | // Delete file (and directory if empty) 113 | _ = configdata.FileCleanup(localDir, path) 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /cnivpp/test/vhostUserAddDel/vhostUserAddDel.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package main 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | _ "net" 25 | "os" 26 | "runtime" 27 | "time" 28 | 29 | _ "github.com/sirupsen/logrus" 30 | _ "go.fd.io/govpp/core" 31 | 32 | vppbridge "github.com/intel/userspace-cni-network-plugin/cnivpp/api/bridge" 33 | vppinfra "github.com/intel/userspace-cni-network-plugin/cnivpp/api/infra" 34 | vppvhostuser "github.com/intel/userspace-cni-network-plugin/cnivpp/api/vhostuser" 35 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 36 | ) 37 | 38 | // Constants 39 | const ( 40 | dbgBridge = true 41 | dbgVhostUser = true 42 | ) 43 | 44 | // Functions 45 | func init() { 46 | // this ensures that main runs only on main thread (thread group leader). 47 | // since namespace ops (unshare, setns) are done for a single thread, we 48 | // must ensure that the goroutine does not jump from OS thread to thread 49 | runtime.LockOSThread() 50 | } 51 | 52 | func main() { 53 | var vppCh vppinfra.ConnectionData 54 | var err error 55 | var swIfIndex interface_types.InterfaceIndex 56 | 57 | // Dummy Input Data 58 | var bridgeDomain uint32 = 4 59 | var vhostUserSocketFile string = "/var/run/vpp/123456/vhost3.sock" 60 | var vhostUserMode bool = true 61 | // Set log level 62 | // Logrus has six logging levels: DebugLevel, InfoLevel, WarningLevel, ErrorLevel, FatalLevel and PanicLevel. 63 | //core.SetLogger(&logrus.Logger{Level: logrus.InfoLevel}) 64 | 65 | fmt.Println("Starting Vhost-User Test client...") 66 | 67 | // Create Channel to pass requests to VPP 68 | vppCh, err = vppinfra.VppOpenCh() 69 | if err != nil { 70 | fmt.Println("Error:", err) 71 | os.Exit(1) 72 | } 73 | defer vppinfra.VppCloseCh(vppCh) 74 | 75 | // Create Vhost-User Interface 76 | swIfIndex, err = vppvhostuser.CreateVhostUserInterface(vppCh.Ch, vhostUserMode, vhostUserSocketFile) 77 | if err != nil { 78 | fmt.Println("Error:", err) 79 | os.Exit(1) 80 | } else { 81 | fmt.Println("Vhost-User", swIfIndex, "created") 82 | if dbgVhostUser { 83 | vppvhostuser.DumpVhostUser(vppCh.Ch) 84 | } 85 | } 86 | 87 | // Add Vhost-User to Bridge. If Bridge does not exist, AddBridgeInterface() 88 | // will create. 89 | err = vppbridge.AddBridgeInterface(vppCh.Ch, bridgeDomain, swIfIndex) 90 | if err != nil { 91 | fmt.Println("Error:", err) 92 | os.Exit(1) 93 | } else { 94 | fmt.Printf("INTERFACE %d add to BRIDGE %d\n", swIfIndex, bridgeDomain) 95 | if dbgBridge { 96 | vppbridge.DumpBridge(vppCh.Ch, bridgeDomain) 97 | } 98 | } 99 | 100 | fmt.Println("Sleeping for 30 seconds...") 101 | time.Sleep(30 * time.Second) 102 | fmt.Println("User Space VPP client wakeup.") 103 | 104 | // Remove Vhost-User from Bridge. RemoveBridgeInterface() will delete Bridge if 105 | // no more interfaces are associated with the Bridge. 106 | err = vppbridge.RemoveBridgeInterface(vppCh.Ch, bridgeDomain, swIfIndex) 107 | 108 | if err != nil { 109 | fmt.Println("Error:", err) 110 | os.Exit(1) 111 | } else { 112 | fmt.Printf("INTERFACE %d removed from BRIDGE %d\n", swIfIndex, bridgeDomain) 113 | if dbgBridge { 114 | vppbridge.DumpBridge(vppCh.Ch, bridgeDomain) 115 | } 116 | } 117 | 118 | fmt.Println("Sleeping for 30 seconds...") 119 | time.Sleep(30 * time.Second) 120 | fmt.Println("User Space VPP client wakeup.") 121 | 122 | fmt.Println("Delete Vhost-User interface.") 123 | err = vppvhostuser.DeleteVhostUserInterface(vppCh.Ch, swIfIndex) 124 | if err != nil { 125 | fmt.Println("Error:", err) 126 | os.Exit(1) 127 | } else { 128 | fmt.Printf("INTERFACE %d deleted\n", swIfIndex) 129 | if dbgVhostUser { 130 | vppvhostuser.DumpVhostUser(vppCh.Ch) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /logging/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Intel Corporation 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logging 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strings" 21 | "time" 22 | 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | // Level type 27 | type Level uint32 28 | 29 | // Common use of different level: 30 | // "panic": Code crash. 31 | // "error": Unusual event occurred (invalid input or system issue), 32 | // 33 | // so exiting code prematurely. 34 | // 35 | // "warning": Unusual event occurred (invalid input or system issue), 36 | // 37 | // but continuing. 38 | // 39 | // "info": Basic information, indication of major code paths. 40 | // "debug": Additional information, indication of minor code branches. 41 | // "verbose": Output of larger variables in code and debug of low level 42 | // 43 | // functions. 44 | const ( 45 | PanicLevel Level = iota 46 | ErrorLevel 47 | WarningLevel 48 | InfoLevel 49 | DebugLevel 50 | VerboseLevel 51 | MaxLevel 52 | UnknownLevel 53 | ) 54 | 55 | var loggingStderr bool 56 | var loggingFp *os.File 57 | var loggingLevel Level 58 | 59 | const defaultTimestampFormat = time.RFC3339 60 | 61 | func (l Level) String() string { 62 | switch l { 63 | case PanicLevel: 64 | return "panic" 65 | case ErrorLevel: 66 | return "error" 67 | case WarningLevel: 68 | return "warning" 69 | case InfoLevel: 70 | return "info" 71 | case DebugLevel: 72 | return "debug" 73 | case VerboseLevel: 74 | return "verbose" 75 | } 76 | return "unknown" 77 | } 78 | 79 | func Printf(level Level, format string, a ...interface{}) { 80 | header := "%s [%s] " 81 | t := time.Now() 82 | if level > loggingLevel { 83 | return 84 | } 85 | 86 | if loggingStderr { 87 | fmt.Fprintf(os.Stderr, header, t.Format(defaultTimestampFormat), level) 88 | fmt.Fprintf(os.Stderr, format, a...) 89 | fmt.Fprintf(os.Stderr, "\n") 90 | } 91 | 92 | if loggingFp != nil { 93 | fmt.Fprintf(loggingFp, header, t.Format(defaultTimestampFormat), level) 94 | fmt.Fprintf(loggingFp, format, a...) 95 | fmt.Fprintf(loggingFp, "\n") 96 | } 97 | } 98 | 99 | func Verbosef(format string, a ...interface{}) { 100 | Printf(VerboseLevel, format, a...) 101 | } 102 | 103 | func Debugf(format string, a ...interface{}) { 104 | Printf(DebugLevel, format, a...) 105 | } 106 | 107 | func Infof(format string, a ...interface{}) { 108 | Printf(InfoLevel, format, a...) 109 | } 110 | 111 | func Warningf(format string, a ...interface{}) { 112 | Printf(WarningLevel, format, a...) 113 | } 114 | 115 | func Errorf(format string, a ...interface{}) error { 116 | Printf(ErrorLevel, format, a...) 117 | return fmt.Errorf(format, a...) 118 | } 119 | 120 | func Panicf(format string, a ...interface{}) { 121 | Printf(PanicLevel, format, a...) 122 | Printf(PanicLevel, "========= Stack trace output ========") 123 | Printf(PanicLevel, "%+v", errors.New("Userspace CNI Panic")) 124 | Printf(PanicLevel, "========= Stack trace output end ========") 125 | } 126 | 127 | func GetLoggingLevel(levelStr string) Level { 128 | switch strings.ToLower(levelStr) { 129 | case "verbose": 130 | return VerboseLevel 131 | case "debug": 132 | return DebugLevel 133 | case "info": 134 | return InfoLevel 135 | case "warning": 136 | return WarningLevel 137 | case "error": 138 | return ErrorLevel 139 | case "panic": 140 | return PanicLevel 141 | } 142 | fmt.Fprintf(os.Stderr, "Userspace-CNI logging: cannot set logging level to %s\n", levelStr) 143 | return UnknownLevel 144 | } 145 | 146 | func SetLogLevel(levelStr string) { 147 | level := GetLoggingLevel(levelStr) 148 | if level < MaxLevel { 149 | loggingLevel = level 150 | } 151 | } 152 | 153 | func SetLogStderr(enable bool) { 154 | loggingStderr = enable 155 | } 156 | 157 | func SetLogFile(filename string) { 158 | if filename == "" { 159 | return 160 | } 161 | 162 | fp, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 163 | if err != nil { 164 | loggingFp = nil 165 | fmt.Fprintf(os.Stderr, "Userspace-CNI logging: cannot open %s", filename) 166 | } 167 | loggingFp = fp 168 | } 169 | 170 | func init() { 171 | loggingStderr = true 172 | loggingFp = nil 173 | loggingLevel = WarningLevel 174 | } 175 | -------------------------------------------------------------------------------- /cnivpp/test/ipAddDel/ipAddDel.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package main 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate binapi-generator --input-dir=../../bin_api --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | _ "net" 25 | "os" 26 | "runtime" 27 | "time" 28 | 29 | current "github.com/containernetworking/cni/pkg/types/100" 30 | _ "github.com/sirupsen/logrus" 31 | _ "go.fd.io/govpp/core" 32 | 33 | vppinfra "github.com/intel/userspace-cni-network-plugin/cnivpp/api/infra" 34 | vppinterface "github.com/intel/userspace-cni-network-plugin/cnivpp/api/interface" 35 | vppmemif "github.com/intel/userspace-cni-network-plugin/cnivpp/api/memif" 36 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 37 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/memif" 38 | ) 39 | 40 | // Constants 41 | const ( 42 | dbgIp = true 43 | dbgMemif = true 44 | ) 45 | 46 | // Functions 47 | func init() { 48 | // this ensures that main runs only on main thread (thread group leader). 49 | // since namespace ops (unshare, setns) are done for a single thread, we 50 | // must ensure that the goroutine does not jump from OS thread to thread 51 | runtime.LockOSThread() 52 | } 53 | 54 | func main() { 55 | var vppCh vppinfra.ConnectionData 56 | var err error 57 | var swIfIndex interface_types.InterfaceIndex 58 | 59 | // Dummy Input Data 60 | var ipString string = "192.168.172.100/24" 61 | var ipResult *current.Result 62 | var memifSocketId uint32 63 | var memifSocketFile string = "/var/run/vpp/123456/memif-3.sock" 64 | var memifRole memif.MemifRole = 0 65 | var memifMode memif.MemifMode = 0 66 | 67 | // Set log level 68 | // Logrus has six logging levels: DebugLevel, InfoLevel, WarningLevel, ErrorLevel, FatalLevel and PanicLevel. 69 | //core.SetLogger(&logrus.Logger{Level: logrus.InfoLevel}) 70 | 71 | fmt.Println("Starting User Space client...") 72 | 73 | // Create Channel to pass requests to VPP 74 | vppCh, err = vppinfra.VppOpenCh() 75 | if err != nil { 76 | fmt.Println("Error:", err) 77 | os.Exit(1) 78 | } 79 | defer vppinfra.VppCloseCh(vppCh) 80 | 81 | // Create Memif Socket 82 | memifSocketId, err = vppmemif.CreateMemifSocket(vppCh.Ch, memifSocketFile) 83 | if err != nil { 84 | fmt.Println("Error:", err) 85 | os.Exit(1) 86 | } else { 87 | fmt.Println("MEMIF SOCKET", memifSocketId, memifSocketFile, "created") 88 | if dbgMemif { 89 | vppmemif.DumpMemifSocket(vppCh.Ch) 90 | } 91 | } 92 | 93 | // Create MemIf Interface 94 | swIfIndex, err = vppmemif.CreateMemifInterface(vppCh.Ch, memifSocketId, memifRole, memifMode) 95 | if err != nil { 96 | fmt.Println("Error:", err) 97 | os.Exit(1) 98 | } else { 99 | fmt.Println("MEMIF", swIfIndex, "created") 100 | if dbgMemif { 101 | vppmemif.DumpMemif(vppCh.Ch) 102 | } 103 | } 104 | 105 | // Set interface to up (1) 106 | err = vppinterface.SetState(vppCh.Ch, swIfIndex, 1) 107 | if err != nil { 108 | fmt.Println("Error bringing interface UP:", err) 109 | os.Exit(1) 110 | } 111 | 112 | // Add IP to MemIf to Bridge. 113 | err = vppinterface.AddDelIpAddress(vppCh.Ch, swIfIndex, true, ipResult) 114 | if err != nil { 115 | fmt.Println("Error:", err) 116 | os.Exit(1) 117 | } else { 118 | fmt.Printf("IP %s added to INTERFACE %d\n", ipString, swIfIndex) 119 | } 120 | 121 | fmt.Println("Sleeping for 30 seconds...") 122 | time.Sleep(30 * time.Second) 123 | fmt.Println("User Space VPP client wakeup.") 124 | 125 | // Remove IP from MemIf. 126 | err = vppinterface.AddDelIpAddress(vppCh.Ch, swIfIndex, false, ipResult) 127 | 128 | if err != nil { 129 | fmt.Println("Error:", err) 130 | os.Exit(1) 131 | } else { 132 | fmt.Printf("IP %s removed from INTERFACE %d\n", ipString, swIfIndex) 133 | } 134 | 135 | fmt.Println("Sleeping for 30 seconds...") 136 | time.Sleep(30 * time.Second) 137 | fmt.Println("User Space VPP client wakeup.") 138 | 139 | fmt.Println("Delete memif interface.") 140 | err = vppmemif.DeleteMemifInterface(vppCh.Ch, swIfIndex) 141 | if err != nil { 142 | fmt.Println("Error:", err) 143 | os.Exit(1) 144 | } else { 145 | fmt.Printf("INTERFACE %d deleted\n", swIfIndex) 146 | if dbgMemif { 147 | vppmemif.DumpMemif(vppCh.Ch) 148 | vppmemif.DumpMemifSocket(vppCh.Ch) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /cnivpp/test/memifAddDel/memifAddDel.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package main 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | _ "net" 25 | "os" 26 | "runtime" 27 | "time" 28 | 29 | _ "github.com/sirupsen/logrus" 30 | _ "go.fd.io/govpp/core" 31 | 32 | vppbridge "github.com/intel/userspace-cni-network-plugin/cnivpp/api/bridge" 33 | vppinfra "github.com/intel/userspace-cni-network-plugin/cnivpp/api/infra" 34 | vppmemif "github.com/intel/userspace-cni-network-plugin/cnivpp/api/memif" 35 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 36 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/memif" 37 | ) 38 | 39 | // Constants 40 | const ( 41 | dbgBridge = true 42 | dbgMemif = true 43 | ) 44 | 45 | // Functions 46 | func init() { 47 | // this ensures that main runs only on main thread (thread group leader). 48 | // since namespace ops (unshare, setns) are done for a single thread, we 49 | // must ensure that the goroutine does not jump from OS thread to thread 50 | runtime.LockOSThread() 51 | } 52 | 53 | func main() { 54 | var vppCh vppinfra.ConnectionData 55 | var err error 56 | var swIfIndex interface_types.InterfaceIndex 57 | 58 | // Dummy Input Data 59 | var bridgeDomain uint32 = 4 60 | var memifSocketId uint32 61 | var memifSocketFile string = "/var/run/vpp/123456/memif-3.sock" 62 | var memifRole memif.MemifRole = 0 63 | var memifMode memif.MemifMode = 0 64 | 65 | // Set log level 66 | // Logrus has six logging levels: DebugLevel, InfoLevel, WarningLevel, ErrorLevel, FatalLevel and PanicLevel. 67 | //core.SetLogger(&logrus.Logger{Level: logrus.InfoLevel}) 68 | 69 | fmt.Println("Starting User Space client...") 70 | 71 | // Create Channel to pass requests to VPP 72 | vppCh, err = vppinfra.VppOpenCh() 73 | if err != nil { 74 | fmt.Println("Error:", err) 75 | os.Exit(1) 76 | } 77 | defer vppinfra.VppCloseCh(vppCh) 78 | 79 | // Create Memif Socket 80 | memifSocketId, err = vppmemif.CreateMemifSocket(vppCh.Ch, memifSocketFile) 81 | if err != nil { 82 | fmt.Println("Error:", err) 83 | os.Exit(1) 84 | } else { 85 | fmt.Println("MEMIF SOCKET", memifSocketId, memifSocketFile, "created") 86 | if dbgMemif { 87 | vppmemif.DumpMemifSocket(vppCh.Ch) 88 | } 89 | } 90 | 91 | // Create MemIf Interface 92 | swIfIndex, err = vppmemif.CreateMemifInterface(vppCh.Ch, memifSocketId, memifRole, memifMode) 93 | if err != nil { 94 | fmt.Println("Error:", err) 95 | os.Exit(1) 96 | } else { 97 | fmt.Println("MEMIF", swIfIndex, "created") 98 | if dbgMemif { 99 | vppmemif.DumpMemif(vppCh.Ch) 100 | } 101 | } 102 | 103 | // Add MemIf to Bridge. If Bridge does not exist, AddBridgeInterface() 104 | // will create. 105 | err = vppbridge.AddBridgeInterface(vppCh.Ch, bridgeDomain, swIfIndex) 106 | if err != nil { 107 | fmt.Println("Error:", err) 108 | os.Exit(1) 109 | } else { 110 | fmt.Printf("INTERFACE %d add to BRIDGE %d\n", swIfIndex, bridgeDomain) 111 | if dbgBridge { 112 | vppbridge.DumpBridge(vppCh.Ch, bridgeDomain) 113 | } 114 | } 115 | 116 | fmt.Println("Sleeping for 30 seconds...") 117 | time.Sleep(30 * time.Second) 118 | fmt.Println("User Space VPP client wakeup.") 119 | 120 | // Remove MemIf from Bridge. RemoveBridgeInterface() will delete Bridge if 121 | // no more interfaces are associated with the Bridge. 122 | err = vppbridge.RemoveBridgeInterface(vppCh.Ch, bridgeDomain, swIfIndex) 123 | 124 | if err != nil { 125 | fmt.Println("Error:", err) 126 | os.Exit(1) 127 | } else { 128 | fmt.Printf("INTERFACE %d removed from BRIDGE %d\n", swIfIndex, bridgeDomain) 129 | if dbgBridge { 130 | vppbridge.DumpBridge(vppCh.Ch, bridgeDomain) 131 | } 132 | } 133 | 134 | fmt.Println("Sleeping for 30 seconds...") 135 | time.Sleep(30 * time.Second) 136 | fmt.Println("User Space VPP client wakeup.") 137 | 138 | fmt.Println("Delete memif interface.") 139 | err = vppmemif.DeleteMemifInterface(vppCh.Ch, swIfIndex) 140 | if err != nil { 141 | fmt.Println("Error:", err) 142 | os.Exit(1) 143 | } else { 144 | fmt.Printf("INTERFACE %d deleted\n", swIfIndex) 145 | if dbgMemif { 146 | vppmemif.DumpMemif(vppCh.Ch) 147 | vppmemif.DumpMemifSocket(vppCh.Ch) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /ci/vpp_test_setup/startup.conf: -------------------------------------------------------------------------------- 1 | 2 | unix { 3 | nodaemon 4 | log /var/log/vpp/vpp-app.log 5 | full-coredump 6 | cli-listen /run/vpp/cli1.sock 7 | gid vpp 8 | } 9 | 10 | api-trace { 11 | ## This stanza controls binary API tracing. Unless there is a very strong reason, 12 | ## please leave this feature enabled. 13 | on 14 | ## Additional parameters: 15 | ## 16 | ## To set the number of binary API trace records in the circular buffer, configure nitems 17 | ## 18 | ## nitems 19 | ## 20 | ## To save the api message table decode tables, configure a filename. Results in /tmp/ 21 | ## Very handy for understanding api message changes between versions, identifying missing 22 | ## plugins, and so forth. 23 | ## 24 | ## save-api-table 25 | } 26 | 27 | api-segment { 28 | gid vpp 29 | } 30 | 31 | cpu { 32 | ## In the VPP there is one main thread and optionally the user can create worker(s) 33 | ## The main thread and worker thread(s) can be pinned to CPU core(s) manually or automatically 34 | 35 | ## Manual pinning of thread(s) to CPU core(s) 36 | 37 | ## Set logical CPU core where main thread runs 38 | # main-core 1 39 | 40 | ## Set logical CPU core(s) where worker threads are running 41 | # corelist-workers 2-3,18-19 42 | 43 | ## Automatic pinning of thread(s) to CPU core(s) 44 | 45 | ## Sets number of CPU core(s) to be skipped (1 ... N-1) 46 | ## Skipped CPU core(s) are not used for pinning main thread and working thread(s). 47 | ## The main thread is automatically pinned to the first available CPU core and worker(s) 48 | ## are pinned to next free CPU core(s) after core assigned to main thread 49 | # skip-cores 4 50 | 51 | ## Specify a number of workers to be created 52 | ## Workers are pinned to N consecutive CPU cores while skipping "skip-cores" CPU core(s) 53 | ## and main thread's CPU core 54 | # workers 2 55 | 56 | ## Set scheduling policy and priority of main and worker threads 57 | 58 | ## Scheduling policy options are: other (SCHED_OTHER), batch (SCHED_BATCH) 59 | ## idle (SCHED_IDLE), fifo (SCHED_FIFO), rr (SCHED_RR) 60 | # scheduler-policy fifo 61 | 62 | ## Scheduling priority is used only for "real-time policies (fifo and rr), 63 | ## and has to be in the range of priorities supported for a particular policy 64 | # scheduler-priority 50 65 | } 66 | 67 | dpdk { 68 | no-pci 69 | 70 | ## Change default settings for all intefaces 71 | # dev default { 72 | ## Number of receive queues, enables RSS 73 | ## Default is 1 74 | # num-rx-queues 3 75 | 76 | ## Number of transmit queues, Default is equal 77 | ## to number of worker threads or 1 if no workers treads 78 | # num-tx-queues 3 79 | 80 | ## Number of descriptors in transmit and receive rings 81 | ## increasing or reducing number can impact performance 82 | ## Default is 1024 for both rx and tx 83 | # num-rx-desc 512 84 | # num-tx-desc 512 85 | 86 | ## VLAN strip offload mode for interface 87 | ## Default is off 88 | # vlan-strip-offload on 89 | # } 90 | 91 | ## Allowlist specific interface by specifying PCI address 92 | # dev 0000:02:00.0 93 | 94 | ## Allowlist specific interface by specifying PCI address and in 95 | ## addition specify custom parameters for this interface 96 | # dev 0000:02:00.1 { 97 | # num-rx-queues 2 98 | # } 99 | 100 | ## Specify bonded interface and its slaves via PCI addresses 101 | ## 102 | ## Bonded interface in XOR load balance mode (mode 2) with L3 and L4 headers 103 | # vdev eth_bond0,mode=2,slave=0000:02:00.0,slave=0000:03:00.0,xmit_policy=l34 104 | # vdev eth_bond1,mode=2,slave=0000:02:00.1,slave=0000:03:00.1,xmit_policy=l34 105 | ## 106 | ## Bonded interface in Active-Back up mode (mode 1) 107 | # vdev eth_bond0,mode=1,slave=0000:02:00.0,slave=0000:03:00.0 108 | # vdev eth_bond1,mode=1,slave=0000:02:00.1,slave=0000:03:00.1 109 | 110 | ## Change UIO driver used by VPP, Options are: igb_uio, vfio-pci, 111 | ## uio_pci_generic or auto (default) 112 | # uio-driver vfio-pci 113 | 114 | ## Disable mutli-segment buffers, improves performance but 115 | ## disables Jumbo MTU support 116 | # no-multi-seg 117 | 118 | ## Increase number of buffers allocated, needed only in scenarios with 119 | ## large number of interfaces and worker threads. Value is per CPU socket. 120 | ## Default is 16384 121 | # num-mbufs 128000 122 | 123 | ## Change hugepages allocation per-socket, needed only if there is need for 124 | ## larger number of mbufs. Default is 256M on each detected CPU socket 125 | # socket-mem 2048,2048 126 | 127 | ## Disables UDP / TCP TX checksum offload. Typically needed for use 128 | ## faster vector PMDs (together with no-multi-seg) 129 | # no-tx-checksum-offload 130 | } 131 | 132 | 133 | # plugins { 134 | ## Adjusting the plugin path depending on where the VPP plugins are 135 | # path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 136 | 137 | ## Disable all plugins by default and then selectively enable specific plugins 138 | # plugin default { disable } 139 | # plugin dpdk_plugin.so { enable } 140 | # plugin acl_plugin.so { enable } 141 | 142 | ## Enable all plugins by default and then selectively disable specific plugins 143 | # plugin dpdk_plugin.so { disable } 144 | # plugin acl_plugin.so { disable } 145 | # } 146 | 147 | ## Alternate syntax to choose plugin path 148 | # plugin_path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 149 | 150 | socksvr { 151 | socket-name /run/vpp/api1.sock 152 | } 153 | -------------------------------------------------------------------------------- /examples/sample-vpp-host-config/startup.conf: -------------------------------------------------------------------------------- 1 | 2 | unix { 3 | nodaemon 4 | log /var/log/vpp/vpp.log 5 | full-coredump 6 | cli-listen /run/vpp/cli.sock 7 | gid vpp 8 | } 9 | 10 | api-trace { 11 | ## This stanza controls binary API tracing. Unless there is a very strong reason, 12 | ## please leave this feature enabled. 13 | on 14 | ## Additional parameters: 15 | ## 16 | ## To set the number of binary API trace records in the circular buffer, configure nitems 17 | ## 18 | ## nitems 19 | ## 20 | ## To save the api message table decode tables, configure a filename. Results in /tmp/ 21 | ## Very handy for understanding api message changes between versions, identifying missing 22 | ## plugins, and so forth. 23 | ## 24 | ## save-api-table 25 | } 26 | 27 | api-segment { 28 | gid vpp 29 | } 30 | 31 | cpu { 32 | ## In the VPP there is one main thread and optionally the user can create worker(s) 33 | ## The main thread and worker thread(s) can be pinned to CPU core(s) manually or automatically 34 | 35 | ## Manual pinning of thread(s) to CPU core(s) 36 | 37 | ## Set logical CPU core where main thread runs 38 | # main-core 1 39 | 40 | ## Set logical CPU core(s) where worker threads are running 41 | # corelist-workers 2-3,18-19 42 | 43 | ## Automatic pinning of thread(s) to CPU core(s) 44 | 45 | ## Sets number of CPU core(s) to be skipped (1 ... N-1) 46 | ## Skipped CPU core(s) are not used for pinning main thread and working thread(s). 47 | ## The main thread is automatically pinned to the first available CPU core and worker(s) 48 | ## are pinned to next free CPU core(s) after core assigned to main thread 49 | # skip-cores 4 50 | 51 | ## Specify a number of workers to be created 52 | ## Workers are pinned to N consecutive CPU cores while skipping "skip-cores" CPU core(s) 53 | ## and main thread's CPU core 54 | # workers 2 55 | 56 | ## Set scheduling policy and priority of main and worker threads 57 | 58 | ## Scheduling policy options are: other (SCHED_OTHER), batch (SCHED_BATCH) 59 | ## idle (SCHED_IDLE), fifo (SCHED_FIFO), rr (SCHED_RR) 60 | # scheduler-policy fifo 61 | 62 | ## Scheduling priority is used only for "real-time policies (fifo and rr), 63 | ## and has to be in the range of priorities supported for a particular policy 64 | # scheduler-priority 50 65 | } 66 | 67 | dpdk { 68 | no-pci 69 | 70 | ## Change default settings for all intefaces 71 | # dev default { 72 | ## Number of receive queues, enables RSS 73 | ## Default is 1 74 | # num-rx-queues 3 75 | 76 | ## Number of transmit queues, Default is equal 77 | ## to number of worker threads or 1 if no workers treads 78 | # num-tx-queues 3 79 | 80 | ## Number of descriptors in transmit and receive rings 81 | ## increasing or reducing number can impact performance 82 | ## Default is 1024 for both rx and tx 83 | # num-rx-desc 512 84 | # num-tx-desc 512 85 | 86 | ## VLAN strip offload mode for interface 87 | ## Default is off 88 | # vlan-strip-offload on 89 | # } 90 | 91 | ## Allowlist specific interface by specifying PCI address 92 | # dev 0000:02:00.0 93 | 94 | ## Allowlist specific interface by specifying PCI address and in 95 | ## addition specify custom parameters for this interface 96 | # dev 0000:02:00.1 { 97 | # num-rx-queues 2 98 | # } 99 | 100 | ## Specify bonded interface and its slaves via PCI addresses 101 | ## 102 | ## Bonded interface in XOR load balance mode (mode 2) with L3 and L4 headers 103 | # vdev eth_bond0,mode=2,slave=0000:02:00.0,slave=0000:03:00.0,xmit_policy=l34 104 | # vdev eth_bond1,mode=2,slave=0000:02:00.1,slave=0000:03:00.1,xmit_policy=l34 105 | ## 106 | ## Bonded interface in Active-Back up mode (mode 1) 107 | # vdev eth_bond0,mode=1,slave=0000:02:00.0,slave=0000:03:00.0 108 | # vdev eth_bond1,mode=1,slave=0000:02:00.1,slave=0000:03:00.1 109 | 110 | ## Change UIO driver used by VPP, Options are: igb_uio, vfio-pci, 111 | ## uio_pci_generic or auto (default) 112 | # uio-driver vfio-pci 113 | 114 | ## Disable mutli-segment buffers, improves performance but 115 | ## disables Jumbo MTU support 116 | # no-multi-seg 117 | 118 | ## Increase number of buffers allocated, needed only in scenarios with 119 | ## large number of interfaces and worker threads. Value is per CPU socket. 120 | ## Default is 16384 121 | # num-mbufs 128000 122 | 123 | ## Change hugepages allocation per-socket, needed only if there is need for 124 | ## larger number of mbufs. Default is 256M on each detected CPU socket 125 | # socket-mem 2048,2048 126 | 127 | ## Disables UDP / TCP TX checksum offload. Typically needed for use 128 | ## faster vector PMDs (together with no-multi-seg) 129 | # no-tx-checksum-offload 130 | } 131 | 132 | 133 | # plugins { 134 | ## Adjusting the plugin path depending on where the VPP plugins are 135 | # path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 136 | 137 | ## Disable all plugins by default and then selectively enable specific plugins 138 | # plugin default { disable } 139 | # plugin dpdk_plugin.so { enable } 140 | # plugin acl_plugin.so { enable } 141 | 142 | ## Enable all plugins by default and then selectively disable specific plugins 143 | # plugin dpdk_plugin.so { disable } 144 | # plugin acl_plugin.so { disable } 145 | # } 146 | 147 | ## Alternate syntax to choose plugin path 148 | # plugin_path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 149 | 150 | socksvr { 151 | socket-name /run/vpp/api.sock 152 | } 153 | -------------------------------------------------------------------------------- /examples/vpp-memif-ping/startup.conf: -------------------------------------------------------------------------------- 1 | 2 | unix { 3 | nodaemon 4 | log /var/log/vpp/vpp-app.log 5 | full-coredump 6 | cli-listen /run/vpp/cli1.sock 7 | gid vpp 8 | } 9 | 10 | api-trace { 11 | ## This stanza controls binary API tracing. Unless there is a very strong reason, 12 | ## please leave this feature enabled. 13 | on 14 | ## Additional parameters: 15 | ## 16 | ## To set the number of binary API trace records in the circular buffer, configure nitems 17 | ## 18 | ## nitems 19 | ## 20 | ## To save the api message table decode tables, configure a filename. Results in /tmp/ 21 | ## Very handy for understanding api message changes between versions, identifying missing 22 | ## plugins, and so forth. 23 | ## 24 | ## save-api-table 25 | } 26 | 27 | api-segment { 28 | gid vpp 29 | } 30 | 31 | cpu { 32 | ## In the VPP there is one main thread and optionally the user can create worker(s) 33 | ## The main thread and worker thread(s) can be pinned to CPU core(s) manually or automatically 34 | 35 | ## Manual pinning of thread(s) to CPU core(s) 36 | 37 | ## Set logical CPU core where main thread runs 38 | # main-core 1 39 | 40 | ## Set logical CPU core(s) where worker threads are running 41 | # corelist-workers 2-3,18-19 42 | 43 | ## Automatic pinning of thread(s) to CPU core(s) 44 | 45 | ## Sets number of CPU core(s) to be skipped (1 ... N-1) 46 | ## Skipped CPU core(s) are not used for pinning main thread and working thread(s). 47 | ## The main thread is automatically pinned to the first available CPU core and worker(s) 48 | ## are pinned to next free CPU core(s) after core assigned to main thread 49 | # skip-cores 4 50 | 51 | ## Specify a number of workers to be created 52 | ## Workers are pinned to N consecutive CPU cores while skipping "skip-cores" CPU core(s) 53 | ## and main thread's CPU core 54 | # workers 2 55 | 56 | ## Set scheduling policy and priority of main and worker threads 57 | 58 | ## Scheduling policy options are: other (SCHED_OTHER), batch (SCHED_BATCH) 59 | ## idle (SCHED_IDLE), fifo (SCHED_FIFO), rr (SCHED_RR) 60 | # scheduler-policy fifo 61 | 62 | ## Scheduling priority is used only for "real-time policies (fifo and rr), 63 | ## and has to be in the range of priorities supported for a particular policy 64 | # scheduler-priority 50 65 | } 66 | 67 | dpdk { 68 | no-pci 69 | 70 | ## Change default settings for all intefaces 71 | # dev default { 72 | ## Number of receive queues, enables RSS 73 | ## Default is 1 74 | # num-rx-queues 3 75 | 76 | ## Number of transmit queues, Default is equal 77 | ## to number of worker threads or 1 if no workers treads 78 | # num-tx-queues 3 79 | 80 | ## Number of descriptors in transmit and receive rings 81 | ## increasing or reducing number can impact performance 82 | ## Default is 1024 for both rx and tx 83 | # num-rx-desc 512 84 | # num-tx-desc 512 85 | 86 | ## VLAN strip offload mode for interface 87 | ## Default is off 88 | # vlan-strip-offload on 89 | # } 90 | 91 | ## Allowlist specific interface by specifying PCI address 92 | # dev 0000:02:00.0 93 | 94 | ## Allowlist specific interface by specifying PCI address and in 95 | ## addition specify custom parameters for this interface 96 | # dev 0000:02:00.1 { 97 | # num-rx-queues 2 98 | # } 99 | 100 | ## Specify bonded interface and its slaves via PCI addresses 101 | ## 102 | ## Bonded interface in XOR load balance mode (mode 2) with L3 and L4 headers 103 | # vdev eth_bond0,mode=2,slave=0000:02:00.0,slave=0000:03:00.0,xmit_policy=l34 104 | # vdev eth_bond1,mode=2,slave=0000:02:00.1,slave=0000:03:00.1,xmit_policy=l34 105 | ## 106 | ## Bonded interface in Active-Back up mode (mode 1) 107 | # vdev eth_bond0,mode=1,slave=0000:02:00.0,slave=0000:03:00.0 108 | # vdev eth_bond1,mode=1,slave=0000:02:00.1,slave=0000:03:00.1 109 | 110 | ## Change UIO driver used by VPP, Options are: igb_uio, vfio-pci, 111 | ## uio_pci_generic or auto (default) 112 | # uio-driver vfio-pci 113 | 114 | ## Disable mutli-segment buffers, improves performance but 115 | ## disables Jumbo MTU support 116 | # no-multi-seg 117 | 118 | ## Increase number of buffers allocated, needed only in scenarios with 119 | ## large number of interfaces and worker threads. Value is per CPU socket. 120 | ## Default is 16384 121 | # num-mbufs 128000 122 | 123 | ## Change hugepages allocation per-socket, needed only if there is need for 124 | ## larger number of mbufs. Default is 256M on each detected CPU socket 125 | # socket-mem 2048,2048 126 | 127 | ## Disables UDP / TCP TX checksum offload. Typically needed for use 128 | ## faster vector PMDs (together with no-multi-seg) 129 | # no-tx-checksum-offload 130 | } 131 | 132 | 133 | # plugins { 134 | ## Adjusting the plugin path depending on where the VPP plugins are 135 | # path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 136 | 137 | ## Disable all plugins by default and then selectively enable specific plugins 138 | # plugin default { disable } 139 | # plugin dpdk_plugin.so { enable } 140 | # plugin acl_plugin.so { enable } 141 | 142 | ## Enable all plugins by default and then selectively disable specific plugins 143 | # plugin dpdk_plugin.so { disable } 144 | # plugin acl_plugin.so { disable } 145 | # } 146 | 147 | ## Alternate syntax to choose plugin path 148 | # plugin_path /home/bms/vpp/build-root/install-vpp-native/vpp/lib64/vpp_plugins 149 | 150 | socksvr { 151 | socket-name /run/vpp/api1.sock 152 | } 153 | -------------------------------------------------------------------------------- /pkg/k8sclient/k8sclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2020 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package k8sclient 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "os" 21 | 22 | v1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/client-go/kubernetes" 25 | "k8s.io/client-go/rest" 26 | "k8s.io/client-go/tools/clientcmd" 27 | "k8s.io/client-go/util/retry" 28 | 29 | "github.com/containernetworking/cni/pkg/skel" 30 | cnitypes "github.com/containernetworking/cni/pkg/types" 31 | 32 | "github.com/intel/userspace-cni-network-plugin/logging" 33 | ) 34 | 35 | // k8sArgs is the valid CNI_ARGS used for Kubernetes 36 | type k8sArgs struct { 37 | cnitypes.CommonArgs 38 | IP net.IP 39 | K8S_POD_NAME cnitypes.UnmarshallableString 40 | K8S_POD_NAMESPACE cnitypes.UnmarshallableString 41 | K8S_POD_INFRA_CONTAINER_ID cnitypes.UnmarshallableString 42 | } 43 | 44 | func getK8sArgs(args *skel.CmdArgs) (*k8sArgs, error) { 45 | k8sArgs := &k8sArgs{} 46 | 47 | logging.Verbosef("getK8sArgs: %v", args) 48 | 49 | if args == nil { 50 | return nil, logging.Errorf("getK8sArgs: failed to get k8s args for CmdArgs set to %v", args) 51 | } 52 | err := cnitypes.LoadArgs(args.Args, k8sArgs) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return k8sArgs, nil 58 | } 59 | 60 | func getK8sClient(kubeClient kubernetes.Interface, kubeConfig string) (kubernetes.Interface, error) { 61 | logging.Verbosef("getK8sClient: %s, %v", kubeClient, kubeConfig) 62 | 63 | // If we get a valid kubeClient (eg from testcases) just return that 64 | // one. 65 | if kubeClient != nil { 66 | return kubeClient, nil 67 | } 68 | 69 | var err error 70 | var config *rest.Config 71 | 72 | // Otherwise try to create a kubeClient from a given kubeConfig 73 | if kubeConfig != "" { 74 | // uses the current context in kubeConfig 75 | config, err = clientcmd.BuildConfigFromFlags("", kubeConfig) 76 | if err != nil { 77 | return nil, logging.Errorf("getK8sClient: failed to get context for the kubeConfig %v, refer Multus README.md for the usage guide: %v", kubeConfig, err) 78 | } 79 | } else if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" { 80 | // Try in-cluster config where multus might be running in a kubernetes pod 81 | config, err = rest.InClusterConfig() 82 | if err != nil { 83 | return nil, logging.Errorf("createK8sClient: failed to get context for in-cluster kube config, refer Multus README.md for the usage guide: %v", err) 84 | } 85 | } else { 86 | // No kubernetes config; assume we shouldn't talk to Kube at all 87 | return nil, nil 88 | } 89 | 90 | // Specify that we use gRPC 91 | config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json" 92 | config.ContentType = "application/vnd.kubernetes.protobuf" 93 | 94 | // Create a new clientset (Interface) 95 | return kubernetes.NewForConfig(config) 96 | } 97 | 98 | func GetPod(args *skel.CmdArgs, 99 | kubeClient kubernetes.Interface, 100 | kubeConfig string) (*v1.Pod, kubernetes.Interface, error) { 101 | var err error 102 | 103 | logging.Verbosef("GetPod: ENTER - %v, %v, %v", args, kubeClient, kubeConfig) 104 | 105 | // Get k8sArgs 106 | k8sArgs, err := getK8sArgs(args) 107 | if err != nil { 108 | _ = logging.Errorf("GetPod: Err in getting k8s args: %v", err) 109 | return nil, kubeClient, err 110 | } 111 | 112 | // Get kubeClient. If passed in, GetK8sClient() will just return it back. 113 | kubeClient, err = getK8sClient(kubeClient, kubeConfig) 114 | if err != nil { 115 | _ = logging.Errorf("GetPod: Err in getting kubeClient: %v", err) 116 | return nil, kubeClient, err 117 | } 118 | 119 | if kubeClient == nil { 120 | return nil, nil, logging.Errorf("GetPod: No kubeClient: %v", err) 121 | } 122 | 123 | // Get the pod info. If cannot get it, we use cached delegates 124 | //pod, err := kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME)) 125 | pod, err := kubeClient.CoreV1().Pods(string(k8sArgs.K8S_POD_NAMESPACE)).Get(context.TODO(), string(k8sArgs.K8S_POD_NAME), metav1.GetOptions{}) 126 | 127 | if err != nil { 128 | logging.Debugf("GetPod: Err in loading K8s cluster default network from pod annotation: %v, use cached delegates", err) 129 | return nil, kubeClient, err 130 | } 131 | 132 | logging.Verbosef("pod.Annotations: %v", pod.Annotations) 133 | 134 | return pod, kubeClient, err 135 | } 136 | 137 | func WritePodAnnotation(kubeClient kubernetes.Interface, pod *v1.Pod) (*v1.Pod, error) { 138 | var err error 139 | 140 | if kubeClient == nil { 141 | return pod, logging.Errorf("WritePodAnnotation: No kubeClient: %v", err) 142 | } 143 | if pod == nil { 144 | return pod, logging.Errorf("WritePodAnnotation: No pod: %v", err) 145 | } 146 | 147 | // Keep original pod info for log message in case of failure 148 | origPod := pod 149 | // Update the pod 150 | pod = pod.DeepCopy() 151 | if resultErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 152 | if err != nil { 153 | // Re-get the pod unless it's the first attempt to update 154 | pod, err = kubeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) 155 | if err != nil { 156 | return err 157 | } 158 | } 159 | 160 | pod, err = kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}) 161 | return err 162 | }); resultErr != nil { 163 | return nil, logging.Errorf("status update failed for pod %s/%s: %v", origPod.Namespace, origPod.Name, resultErr) 164 | } 165 | return pod, nil 166 | } 167 | -------------------------------------------------------------------------------- /cniovs/ovsctrl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2020 Red Hat, Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cniovs 16 | 17 | import ( 18 | "os" 19 | "os/exec" 20 | "path/filepath" 21 | "strings" 22 | 23 | "github.com/intel/userspace-cni-network-plugin/logging" 24 | ) 25 | 26 | const defaultOvSSocketDir = "/usr/local/var/run/openvswitch/" 27 | 28 | /* 29 | OVS command execution handling and its public interface 30 | */ 31 | 32 | type ExecCommandInterface interface { 33 | execCommand(cmd string, args []string) ([]byte, error) 34 | } 35 | 36 | type realExecCommand struct{} 37 | 38 | func (e *realExecCommand) execCommand(cmd string, args []string) ([]byte, error) { 39 | return exec.Command(cmd, args...).Output() 40 | } 41 | 42 | var ovsCommand ExecCommandInterface = &realExecCommand{} 43 | 44 | func SetExecCommand(o ExecCommandInterface) { 45 | ovsCommand = o 46 | } 47 | 48 | func SetDefaultExecCommand() { 49 | ovsCommand = &realExecCommand{} 50 | } 51 | 52 | func execCommand(cmd string, args []string) ([]byte, error) { 53 | return ovsCommand.execCommand(cmd, args) 54 | } 55 | 56 | /* 57 | Functions to control OVS by using the ovs-vsctl cmdline client. 58 | */ 59 | 60 | func createVhostPort(sock_dir string, sock_name string, client bool, bridge_name string) (string, error) { 61 | var err error 62 | 63 | type_str := "type=dpdkvhostuser" 64 | if client { 65 | type_str = "type=dpdkvhostuserclient" 66 | } 67 | 68 | // COMMAND: ovs-vsctl add-port -- set Interface type= 69 | cmd := "ovs-vsctl" 70 | args := []string{"add-port", bridge_name, sock_name, "--", "set", "Interface", sock_name, type_str} 71 | 72 | if client { 73 | socketarg := "options:vhost-server-path=" + filepath.Join(sock_dir, sock_name) 74 | logging.Debugf("Additional string: %s", socketarg) 75 | 76 | args = append(args, socketarg) 77 | } 78 | 79 | if _, err = execCommand(cmd, args); err != nil { 80 | return "", err 81 | } 82 | 83 | if !client { 84 | // Determine the location OvS uses for Sockets. Default location can be 85 | // overwritten with environmental variable: OVS_SOCKDIR 86 | ovs_socket_dir, ok := os.LookupEnv("OVS_SOCKDIR") 87 | if !ok { 88 | ovs_socket_dir = defaultOvSSocketDir 89 | } 90 | 91 | // Move socket to defined dir for easier mounting 92 | err = os.Rename(filepath.Join(ovs_socket_dir, sock_name), filepath.Join(sock_dir, sock_name)) 93 | if err != nil { 94 | _ = logging.Errorf("Rename ERROR: %v", err) 95 | err = nil 96 | 97 | //deleteVhostPort(sock_name, bridge_name) 98 | } 99 | } 100 | 101 | return sock_name, err 102 | } 103 | 104 | func deleteVhostPort(sock_name string, bridge_name string) error { 105 | // COMMAND: ovs-vsctl del-port 106 | cmd := "ovs-vsctl" 107 | args := []string{"--if-exists", "del-port", bridge_name, sock_name} 108 | _, err := execCommand(cmd, args) 109 | logging.Verbosef("ovsctl.deleteVhostPort(): return=%v", err) 110 | return err 111 | } 112 | 113 | func createBridge(bridge_name string) error { 114 | // COMMAND: ovs-vsctl add-br -- set bridge datapath_type=netdev 115 | cmd := "ovs-vsctl" 116 | args := []string{"add-br", bridge_name, "--", "set", "bridge", bridge_name, "datapath_type=netdev"} 117 | _, err := execCommand(cmd, args) 118 | logging.Verbosef("ovsctl.createBridge(): return=%v", err) 119 | return err 120 | } 121 | 122 | func configL2Bridge(bridge_name string) error { 123 | // COMMAND: ovs-ofctl add-flow actions=NORMAL 124 | cmd := "ovs-ofctl" 125 | args := []string{"add-flow", bridge_name, "actions=NORMAL"} 126 | _, err := execCommand(cmd, args) 127 | logging.Verbosef("ovsctl.configL2Bridge(): return=%v", err) 128 | return err 129 | } 130 | 131 | func deleteBridge(bridge_name string) error { 132 | // COMMAND: ovs-vsctl del-br 133 | cmd := "ovs-vsctl" 134 | args := []string{"del-br", bridge_name} 135 | 136 | _, err := execCommand(cmd, args) 137 | logging.Verbosef("ovsctl.deleteBridge(): return=%v", err) 138 | return err 139 | } 140 | 141 | func getVhostPortMac(sock_name string) (string, error) { 142 | // COMMAND: ovs-vsctl --bare --columns=mac find port name= 143 | cmd := "ovs-vsctl" 144 | args := []string{"--bare", "--columns=mac", "find", "port", "name=" + sock_name} 145 | if mac_b, err := execCommand(cmd, args); err != nil { 146 | return "", err 147 | } else { 148 | return strings.Replace(string(mac_b), "\n", "", -1), nil 149 | } 150 | } 151 | 152 | func findBridge(bridge_name string) bool { 153 | found := false 154 | 155 | // COMMAND: ovs-vsctl --bare --columns=name find bridge name= 156 | cmd := "ovs-vsctl" 157 | args := []string{"--bare", "--columns=name", "find", "bridge", "name=" + bridge_name} 158 | //if name, err := execCommand(cmd, args); err != nil { 159 | name, err := execCommand(cmd, args) 160 | logging.Verbosef("ovsctl.findBridge(): return name=%v err=%v", name, err) 161 | if err == nil { 162 | if len(name) != 0 { 163 | found = true 164 | } 165 | } 166 | 167 | return found 168 | } 169 | 170 | func doesBridgeContainInterfaces(bridge_name string) bool { 171 | found := false 172 | 173 | // ovs-vsctl list-ports 174 | cmd := "ovs-vsctl" 175 | args := []string{"list-ports", bridge_name} 176 | //if name, err := execCommand(cmd, args); err != nil { 177 | name, err := execCommand(cmd, args) 178 | logging.Verbosef("ovsctl.doesBridgeContainInterfaces(): return name=%v err=%v", name, err) 179 | if err == nil { 180 | if len(name) != 0 { 181 | found = true 182 | } 183 | } 184 | 185 | return found 186 | } 187 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package types 16 | 17 | import ( 18 | "github.com/containernetworking/cni/pkg/types" 19 | current "github.com/containernetworking/cni/pkg/types/100" 20 | ) 21 | 22 | // Exported Types 23 | type MemifConf struct { 24 | Role string `json:"role,omitempty"` // Role of memif: master|slave 25 | Mode string `json:"mode,omitempty"` // Mode of memif: ip|ethernet|inject-punt 26 | 27 | // Autogenerated as memif--.sock i.e. memif-0958c8871b32-net1.sock 28 | // Filename only, no path. Will use if populated, but used to passed filename to container. 29 | Socketfile string `json:"socketfile,omitempty"` 30 | } 31 | 32 | type VhostConf struct { 33 | Mode string `json:"mode,omitempty"` // vhost-user mode: client|server 34 | Group string `json:"group,omitempty"` // vhost-user socket file group ownership 35 | 36 | // Autogenerated as - i.e. 0958c8871b32-net1 37 | // Filename only, no path. Will use if populated, but used to passed filename to container. 38 | Socketfile string `json:"socketfile,omitempty"` 39 | } 40 | 41 | type BridgeConf struct { 42 | // ovs-dpdk specific note: 43 | // ovs-dpdk requires a bridge to create an interfaces. So if 'NetType' is set 44 | // to something other than 'bridge', a bridge is still need and this field will 45 | // be inspected. For ovs-dpdk, if bridge data is not populated, it will default 46 | // to 'br-0'. 47 | BridgeName string `json:"bridgeName,omitempty"` // Bridge Name 48 | BridgeId int `json:"bridgeId,omitempty"` // Bridge Id - Deprecated in favor of BridgeName 49 | VlanId int `json:"vlanId,omitempty"` // Optional VLAN Id 50 | } 51 | 52 | type UserSpaceConf struct { 53 | // The Container Instance will default to the Host Instance value if a given attribute 54 | // is not provided. However, they are not required to be the same and a Container 55 | // attribute can be provided to override. All values are listed as 'omitempty' to 56 | // allow the Container struct to be empty where desired. 57 | Engine string `json:"engine,omitempty"` // CNI Implementation {vpp|ovs-dpdk} 58 | IfType string `json:"iftype,omitempty"` // Type of interface {memif|vhostuser} 59 | NetType string `json:"netType,omitempty"` // Interface network type {none|bridge|interface} 60 | MemifConf MemifConf `json:"memif,omitempty"` 61 | VhostConf VhostConf `json:"vhost,omitempty"` 62 | BridgeConf BridgeConf `json:"bridge,omitempty"` 63 | } 64 | 65 | type NetConf struct { 66 | types.NetConf 67 | 68 | /* 69 | // Support chaining 70 | RawPrevResult *map[string]interface{} `json:"prevResult"` 71 | PrevResult *current.Result `json:"-"` 72 | */ 73 | 74 | // One of the following two must be provided: KubeConfig or SharedDir 75 | // 76 | // KubeConfig: 77 | // Example: "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", 78 | // Provides credentials for Userspace CNI to call KubeAPI to: 79 | // - Read Volume Mounts: 80 | // - "shared-dir": Directory on host socketfiles are created in 81 | // - Write annotations: 82 | // - "userspace/configuration-data": Configuration data passed 83 | // to containe in JSON format. 84 | // - "userspace/mapped-dir": Directory in container socketfiles 85 | // are created in. Scraped from Volume Mounts above. 86 | // 87 | // SharedDir: 88 | // Example: "sharedDir": "/usr/local/var/run/openvswitch/", 89 | // Since credentials are not provided, Userspace CNI cannot call KubeAPI 90 | // to read the Volume Mounts, so this is the same directory used in the 91 | // "hostPath". Difference from the "kubeConfig" is that with the "sharedDir" 92 | // method, the directory is not unique per POD. That is because the Network 93 | // Attachment Definition (where this is defined) is used by multiple PODs. 94 | // So this is the base directory and the CNI creates a sub-directory with 95 | // the ContainerId as the sub-directory name. 96 | // 97 | // Along the same lines, no annotations are written by Userspace CNI. 98 | // 1) Configuration data will be written to a file in the same 99 | // directory as the socketfiles instead of to an annotation. 100 | // 2) The "userspace/mapped-dir" annotation must be added to the 101 | // pod spec manually (not done by CNI) so container know where to 102 | // retrieve data. 103 | // Example: userspace/mappedDir: /var/lib/cni/usrspcni/ 104 | KubeConfig string `json:"kubeconfig,omitempty"` 105 | SharedDir string `json:"sharedDir,omitempty"` 106 | 107 | LogFile string `json:"logFile,omitempty"` 108 | LogLevel string `json:"logLevel,omitempty"` 109 | 110 | Name string `json:"name"` 111 | HostConf UserSpaceConf `json:"host,omitempty"` 112 | ContainerConf UserSpaceConf `json:"container,omitempty"` 113 | } 114 | 115 | // Defines the JSON data written to container. It is either written to: 116 | // 1. Annotation - "userspace/configuration-data" 117 | // -- OR -- 118 | // 2. a file in the directory designated by NetConf.SharedDir. 119 | type ConfigurationData struct { 120 | ContainerId string `json:"containerId"` // From args.ContainerId, used locally. Used in several place, namely in the socket filenames. 121 | IfName string `json:"ifName"` // From args.IfName, used locally. Used in several place, namely in the socket filenames. 122 | Name string `json:"name"` // From NetConf.Name 123 | Config UserSpaceConf `json:"config"` // From NetConf.ContainerConf 124 | IPResult current.Result `json:"ipResult"` // Network Status also has IP, but wrong format 125 | } 126 | 127 | const DefaultSwIfIndex = 4294967295 // vpp default interface id, used when querying bridges 128 | -------------------------------------------------------------------------------- /ci/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | USERSPACEDIR="/runner/_work/userspace-cni-network-plugin/userspace-cni-network-plugin/" 4 | CI_DIR="$USERSPACEDIR/ci/" 5 | 6 | vpp_ligato_latest_container() 7 | { 8 | IMAGE="ligato/vpp-base:latest" 9 | 10 | cd ${USERSPACEDIR} 11 | 12 | echo "Changing to latest tag in dockerfile" 13 | sed -i "s|\(FROM\).*\(as builder\)|\1 $IMAGE \2|g" ./docker/userspacecni/Dockerfile 14 | grep -n "$IMAGE" ./docker/userspacecni/Dockerfile 15 | 16 | echo "Changing to latest tag vpp pod" 17 | sed -i "s|\(image:\).*\(#imagename\)|\1 $IMAGE \2|g" ./ci/vpp_test_setup/vpp_pod.sh 18 | grep -n "$IMAGE" ./ci/vpp_test_setup/vpp_pod.sh 19 | 20 | echo "Changing to latest tag vpp host pod" 21 | sed -i "s|\(image:\).*\(#imagename\)|\1 $IMAGE \2|g" ./ci/vpp_test_setup/vpp_host.sh 22 | grep -n "$IMAGE" ./ci/vpp_test_setup/vpp_host.sh 23 | } 24 | 25 | install_go_kubectl_kind(){ 26 | wget -qO- https://golang.org/dl/go1.20.1.linux-amd64.tar.gz |tar -C "$HOME" -xz 27 | export PATH="${PATH}:${HOME}/go/bin" 28 | echo "export PATH=\"${PATH}:${HOME}/go/bin/:${HOME}.local/bin/\"" >>~/.bashrc 29 | go install sigs.k8s.io/kind@v0.20.0 30 | 31 | echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list 32 | curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg 33 | sudo apt-get update 34 | sudo apt-get install -y kubectl 35 | } 36 | 37 | create_kind_cluster(){ 38 | kubectl_version="v1.27.3" 39 | case "$1" in 40 | -v | --version ) kubectl_version="$2"; 41 | esac 42 | 43 | kind create cluster --image "kindest/node:$kubectl_version" 44 | kubectl get all --all-namespaces 45 | 46 | #docker run -itd --device=/dev/hugepages:/dev/hugepages --privileged -v "$(pwd)/example/sample-vpp-host-config:/etc/vpp/" --name vpp ligato/vpp-base 47 | sleep 10 48 | cd $USERSPACEDIR 49 | 50 | make build 51 | # gets path for one directopry above, needed for mkdir with docker cp below 52 | mkdir_var=$(dirname ${USERSPACEDIR}) 53 | kind load docker-image localhost:5000/userspacecni 54 | docker exec -i kind-control-plane bash -c "mkdir -p $mkdir_var" 55 | docker cp "${USERSPACEDIR}" "kind-control-plane:${USERSPACEDIR}" 56 | } 57 | 58 | deploy_multus(){ 59 | ## Multus main branch has major bugs so we fix version 60 | cd $CI_DIR 61 | MULTUS_VERSION="v4.0.2" 62 | wget https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/$MULTUS_VERSION/deployments/multus-daemonset.yml 63 | sed -i "s/snapshot-thick/v4.0.2/g" multus-daemonset.yml 64 | kubectl apply -f ./multus-daemonset.yml 65 | } 66 | 67 | deploy_userspace(){ 68 | cd $USERSPACEDIR 69 | #kubectl label nodes kind-control-plane app=userspace-cni 70 | kubectl label nodes --all app=userspace-cni 71 | make deploy 72 | echo "sleeping for 20 to allow userspace to deploy first" 73 | sleep 20 74 | } 75 | 76 | vpp_e2e_test(){ 77 | cd $CI_DIR/vpp_test_setup/ 78 | echo "Setting up vpp host" 79 | ./vpp_host.sh 80 | sleep 20 81 | echo "Setting up vpp pods" 82 | ./vpp_pod.sh 83 | 84 | sleep 20 85 | kubectl get all -A 86 | 87 | kubectl exec -n vpp vpp-app1-kind-control-plane -- ./vpp_pod_setup_memif.sh 88 | kubectl exec -n vpp vpp-app2-kind-control-plane -- ./vpp_pod_setup_memif.sh 89 | kubectl exec -n vpp vpp-app1-kind-control-plane -- vppctl "sh int address" 90 | kubectl exec -n vpp vpp-app2-kind-control-plane -- vppctl "sh int address" 91 | 92 | kubectl exec -n vpp vpp-app1-kind-control-plane -- vppctl "ping 192.168.1.4" 93 | if kubectl exec -n vpp vpp-app1-kind-control-plane -- vppctl "ping 192.168.1.4" |grep -q "bytes"; then 94 | echo "VPP ping test pass" 95 | else 96 | echo VPP ping test failed 97 | exit 1 98 | fi 99 | 100 | printf "\n\n Removing vpp app pods \n\n" 101 | kubectl delete -n vpp pod/vpp-app1-kind-control-plane 102 | kubectl delete -n vpp pod/vpp-app2-kind-control-plane 103 | echo "pods deleted" 104 | 105 | echo "kubectl get all, app pods should have been removed" 106 | kubectl get all -A 107 | 108 | printf "\n vppctl show interface \n\n" 109 | kubectl exec -n vpp pod/vpp-kind-control-plane -- vppctl "sh int" 110 | 111 | printf "\n vppctl show memif, only the default socket 0 should be still here \n\n" 112 | kubectl exec -n vpp pod/vpp-kind-control-plane -- vppctl "sh memif" 113 | kubectl delete -n vpp pod/vpp-kind-control-plane 114 | } 115 | 116 | build_ovs_container(){ 117 | cd $CI_DIR/ovs_test_setup 118 | docker build . -t ovs 119 | kind load docker-image ovs 120 | # set alias for ovs-vsctl and ovs-ofctl in kind container, would be better if we could just install its bin 121 | docker exec -i kind-control-plane bash -c "echo '#!/bin/bash' > /usr/bin/ovs-vsctl" 122 | docker exec -i kind-control-plane bash -c "echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /usr/bin/ovs-vsctl" 123 | docker exec -i kind-control-plane bash -c "echo 'kubectl exec -n ovs ovs-kind-control-plane -- ovs-vsctl \"\$@\"' >> /usr/bin/ovs-vsctl" 124 | docker exec -i kind-control-plane bash -c "chmod +x /usr/bin/ovs-vsctl" 125 | 126 | docker exec -i kind-control-plane bash -c "echo '#!/bin/bash' > /usr/bin/ovs-ofctl" 127 | docker exec -i kind-control-plane bash -c "echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /usr/bin/ovs-ofctl" 128 | docker exec -i kind-control-plane bash -c "echo 'kubectl exec -n ovs ovs-kind-control-plane -- ovs-ofctl \"\$@\"' >> /usr/bin/ovs-ofctl" 129 | docker exec -i kind-control-plane bash -c "chmod +x /usr/bin/ovs-ofctl" 130 | } 131 | 132 | build_testpmd_container(){ 133 | cd $CI_DIR/ovs_test_setup/testpmd_image 134 | docker build -t testpmd . 135 | kind load docker-image testpmd 136 | } 137 | 138 | ovs_e2e_test(){ 139 | cd $CI_DIR/ovs_test_setup 140 | ./ovs_host.sh 141 | sleep 20 142 | 143 | # workaround, cant create in dockerfile 144 | kubectl exec -n ovs pod/ovs-kind-control-plane -- bash -c "mkdir -p /dev/net/" 145 | kubectl exec -n ovs pod/ovs-kind-control-plane -- bash -c "mknod /dev/net/tun c 10 200" 146 | kubectl exec -n ovs pod/ovs-kind-control-plane -- bash -c 'ovs-vsctl set Open_vSwitch . "other_config:dpdk-init=true"' 147 | 148 | ./testpmd_pod.sh 149 | 150 | sleep 20 151 | kubectl get all -A 152 | kubectl logs -n ovs pod/ovs-kind-control-plane 153 | kubectl describe -n ovs pod/ovs-app1-kind-control-plane 154 | kubectl logs -n ovs pod/ovs-app1-kind-control-plane |tail -11 155 | kubectl logs -n ovs pod/ovs-app2-kind-control-plane |tail -11 156 | pps="$(kubectl logs -n ovs pod/ovs-app2-kind-control-plane |tail -11 | grep 'RX-packets'|sed 's/ * / /g' |cut -d ' ' -f 3)" 157 | echo "RX Packets: $pps" 158 | 159 | if [ "$pps" -eq "0" ] || [ -z "${pps}" ]; then 160 | echo "Test Failed: no traffic"; 161 | exit 1; 162 | else 163 | echo "OVS Test Pass"; 164 | fi 165 | } 166 | 167 | 168 | run_all(){ 169 | # theese steps are triggered by the ci by sourcing this script and running the following separately 170 | # it gives much better logging breakdown on github 171 | # the run_all function is only used for manual deployment 172 | install_go_kubectl_kind 173 | create_kind_cluster -v v1.27.3 174 | deploy_multus 175 | deploy_userspace 176 | vpp_e2e_test 177 | build_ovs_container 178 | build_testpmd_container 179 | ovs_e2e_test 180 | } 181 | #run_all 182 | -------------------------------------------------------------------------------- /cnivpp/api/bridge/bridge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Cisco and/or its affiliates. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Binary simple-client is an example VPP management application that exercises the 16 | // govpp API on real-world use-cases. 17 | package vppbridge 18 | 19 | // Generates Go bindings for all VPP APIs located in the json directory. 20 | //go:generate go run go.fd.io/govpp/cmd/binapi-generator --output-dir=../../bin_api 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/interface_types" 26 | "github.com/intel/userspace-cni-network-plugin/cnivpp/bin_api/l2" 27 | . "github.com/intel/userspace-cni-network-plugin/pkg/types" 28 | "go.fd.io/govpp/api" 29 | ) 30 | 31 | // Constants 32 | const debugBridge = false 33 | 34 | // 35 | // API Functions 36 | // 37 | 38 | // Attempt to create a Bridge Domain. 39 | func CreateBridge(ch api.Channel, bridgeDomain uint32) error { 40 | 41 | exists, _ := findBridge(ch, bridgeDomain) 42 | if exists { 43 | if debugBridge { 44 | fmt.Printf("Bridge Domain %d already exist, exit\n", bridgeDomain) 45 | } 46 | return nil 47 | } 48 | 49 | // Populate the Request Structure 50 | req := &l2.BridgeDomainAddDel{ 51 | BdID: bridgeDomain, 52 | Flood: true, 53 | UuFlood: true, 54 | Forward: true, 55 | Learn: true, 56 | ArpTerm: false, 57 | MacAge: 0, 58 | //BdTag []byte `struc:"[64]byte"` 59 | IsAdd: true, 60 | } 61 | 62 | reply := &l2.BridgeDomainAddDelReply{} 63 | 64 | err := ch.SendRequest(req).ReceiveReply(reply) 65 | 66 | if err != nil { 67 | if debugBridge { 68 | fmt.Println("Error creating bridge domain:", err) 69 | } 70 | return err 71 | } 72 | 73 | return err 74 | } 75 | 76 | // Attempt to delete a Bridge Domain. 77 | func DeleteBridge(ch api.Channel, bridgeDomain uint32) error { 78 | 79 | // Determine if bridge domain exists 80 | exists, count := findBridge(ch, bridgeDomain) 81 | if !exists || count != 0 { 82 | return nil 83 | } 84 | 85 | // Populate the Request Structure 86 | req := &l2.BridgeDomainAddDel{ 87 | BdID: bridgeDomain, 88 | IsAdd: false, 89 | } 90 | 91 | reply := &l2.BridgeDomainAddDelReply{} 92 | 93 | err := ch.SendRequest(req).ReceiveReply(reply) 94 | 95 | if err != nil { 96 | if debugBridge { 97 | fmt.Println("Error deleting Bridge Domain:", err) 98 | } 99 | return err 100 | } 101 | 102 | return err 103 | } 104 | 105 | // Attempt to add an interface to a Bridge Domain. 106 | func AddBridgeInterface(ch api.Channel, bridgeDomain uint32, swIfId interface_types.InterfaceIndex) error { 107 | var err error 108 | 109 | // Determine if bridge domain exists, and if not, create it. CreateBridge() 110 | // checks for existence. 111 | err = CreateBridge(ch, bridgeDomain) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | // Populate the Request Structure 117 | req := &l2.SwInterfaceSetL2Bridge{ 118 | BdID: bridgeDomain, 119 | RxSwIfIndex: swIfId, 120 | Shg: 0, 121 | PortType: l2.L2_API_PORT_TYPE_NORMAL, 122 | Enable: true, 123 | } 124 | 125 | reply := &l2.SwInterfaceSetL2BridgeReply{} 126 | 127 | err = ch.SendRequest(req).ReceiveReply(reply) 128 | 129 | if err != nil { 130 | if debugBridge { 131 | fmt.Println("Error adding interface to bridge domain:", err) 132 | } 133 | return err 134 | } 135 | 136 | return err 137 | } 138 | 139 | // Attempt to remove an interface from a Bridge Domain. 140 | func RemoveBridgeInterface(ch api.Channel, bridgeDomain uint32, swIfId interface_types.InterfaceIndex) error { 141 | 142 | // Populate the Request Structure 143 | req := &l2.SwInterfaceSetL2Bridge{ 144 | BdID: bridgeDomain, 145 | RxSwIfIndex: swIfId, 146 | Shg: 0, 147 | PortType: l2.L2_API_PORT_TYPE_NORMAL, 148 | Enable: false, 149 | } 150 | 151 | reply := &l2.SwInterfaceSetL2BridgeReply{} 152 | 153 | err := ch.SendRequest(req).ReceiveReply(reply) 154 | 155 | if err != nil { 156 | if debugBridge { 157 | fmt.Println("Error removing interface from bridge domain:", err) 158 | } 159 | return err 160 | } 161 | 162 | // DeleteBridge() checks to see if there are any interfaces still attached, 163 | // and if so, bail. So attempt to delete and let it validate. 164 | err = DeleteBridge(ch, bridgeDomain) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | return err 170 | } 171 | 172 | // Dump the input Bridge data to Stdout. There is not VPP API to dump 173 | // all the Bridges. 174 | func DumpBridge(ch api.Channel, bridgeDomain uint32) { 175 | 176 | // Populate the Message Structure 177 | req := &l2.BridgeDomainDump{ 178 | BdID: bridgeDomain, SwIfIndex: DefaultSwIfIndex, 179 | } 180 | 181 | reply := &l2.BridgeDomainDetails{} 182 | 183 | err := ch.SendRequest(req).ReceiveReply(reply) 184 | 185 | if err == nil { 186 | fmt.Printf(" Bridge Domain %v: Fld=%v UuFld=%v Fwd=%v Lrn=%v Arp=%v Mac=%d Bvi=%d NSwId=%d BdTag=%s\n", 187 | bridgeDomain, 188 | reply.Flood, 189 | reply.UuFlood, 190 | reply.Forward, 191 | reply.Learn, 192 | reply.ArpTerm, 193 | reply.MacAge, 194 | reply.BviSwIfIndex, 195 | reply.NSwIfs, 196 | string(reply.BdTag)) 197 | 198 | if reply.NSwIfs != 0 { 199 | for i := uint32(0); i < reply.NSwIfs; i++ { 200 | fmt.Printf(" SwId=%d Shg=%d\n", 201 | reply.SwIfDetails[i].SwIfIndex, 202 | reply.SwIfDetails[i].Shg) 203 | } 204 | } 205 | } else { 206 | fmt.Printf("Bridge Domain %d does NOT Exist.\n", bridgeDomain) 207 | } 208 | } 209 | 210 | // 211 | // Local Functions 212 | // 213 | 214 | // Determine if the input Bridge exists. 215 | // Return: true - Exists false - otherwise 216 | // 217 | // uint32 - Number of associated interfaces 218 | func findBridge(ch api.Channel, bridgeDomain uint32) (bool, uint32) { 219 | var rval bool = false 220 | var count uint32 221 | 222 | // Populate the Message Structure 223 | req := &l2.BridgeDomainDump{ 224 | BdID: bridgeDomain, SwIfIndex: DefaultSwIfIndex, 225 | } 226 | reqCtx := ch.SendMultiRequest(req) 227 | 228 | // BridgeDomainDump only returns one message, but if the Bridge Domain 229 | // doesn't exist, no response is returned and Reply times out. So use 230 | // SendMultiRequest to handle possible no response. 231 | for { 232 | reply := &l2.BridgeDomainDetails{} 233 | stop, err := reqCtx.ReceiveReply(reply) 234 | if stop { 235 | if debugBridge { 236 | fmt.Printf("Bridge Domain %d does NOT exist\n", bridgeDomain) 237 | } 238 | break // break out of the loop 239 | } else if err != nil { 240 | if debugBridge { 241 | fmt.Printf("Error searching for Bridge Domain %d\n", bridgeDomain) 242 | } 243 | break // break out of the loop 244 | } else { 245 | count = reply.NSwIfs 246 | } 247 | 248 | rval = true 249 | } 250 | 251 | return rval, count 252 | } 253 | -------------------------------------------------------------------------------- /pkg/configdata/configdata.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2020 Red Hat, Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at: 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | // This module provides the library functions to implement the 17 | // UserSpace CNI DB implementation. In this context, the DB is the 18 | // configuration being passed to the container. 19 | // 20 | 21 | package configdata 22 | 23 | import ( 24 | "encoding/json" 25 | "fmt" 26 | "io" 27 | "os" 28 | "path/filepath" 29 | 30 | v1 "k8s.io/api/core/v1" 31 | "k8s.io/client-go/kubernetes" 32 | 33 | "github.com/containernetworking/cni/pkg/skel" 34 | current "github.com/containernetworking/cni/pkg/types/100" 35 | 36 | "github.com/intel/userspace-cni-network-plugin/logging" 37 | "github.com/intel/userspace-cni-network-plugin/pkg/annotations" 38 | "github.com/intel/userspace-cni-network-plugin/pkg/types" 39 | ) 40 | 41 | // 42 | // Constants 43 | // 44 | 45 | const DefaultOvsCNIDir = "/usr/local/var/run/openvswitch" 46 | const DefaultVppCNIDir = "/var/run/vpp" 47 | 48 | // 49 | // Types 50 | // 51 | 52 | // 53 | // API Functions 54 | // 55 | 56 | // 57 | // Functions for processing Remote Configs (configs for within a Container) 58 | // 59 | 60 | // saveRemoteConfig() - When a config read on the host is for a Container, 61 | // 62 | // flip the location and write the data to a file. When the Container 63 | // comes up, it will read the file via () and delete the file. This function 64 | // writes the file. 65 | func SaveRemoteConfig(conf *types.NetConf, 66 | args *skel.CmdArgs, 67 | kubeClient kubernetes.Interface, 68 | sharedDir string, 69 | pod *v1.Pod, 70 | ipResult *current.Result) (*v1.Pod, error) { 71 | var configData types.ConfigurationData 72 | var err error 73 | 74 | // Check if required parameters are set and fail otherwise 75 | if conf == nil { 76 | return pod, logging.Errorf("SaveRemoteConfig(): Error conf is set to: %v", conf) 77 | } 78 | if args == nil { 79 | return pod, logging.Errorf("SaveRemoteConfig(): Error args is set to: %v", args) 80 | } 81 | if pod == nil { 82 | return pod, logging.Errorf("SaveRemoteConfig(): Error pod is set to: %v", pod) 83 | } 84 | // 85 | // Convert Local Data to types.ConfigurationData, which 86 | // will be written to the container. 87 | // 88 | configData.ContainerId = args.ContainerID 89 | configData.IfName = args.IfName 90 | configData.Name = conf.Name 91 | configData.Config = conf.ContainerConf 92 | 93 | if ipResult != nil { 94 | configData.IPResult = *ipResult 95 | } 96 | 97 | // Convert empty variables to valid data based on the original HostConf 98 | if configData.Config.IfType == "" { 99 | configData.Config.IfType = conf.HostConf.IfType 100 | } 101 | if configData.Config.NetType == "" { 102 | if ipResult != nil { 103 | configData.Config.NetType = "interface" 104 | } 105 | } 106 | 107 | if configData.Config.IfType == "memif" { 108 | if configData.Config.MemifConf.Role == "" { 109 | if conf.HostConf.MemifConf.Role == "master" { 110 | configData.Config.MemifConf.Role = "slave" 111 | } else { 112 | configData.Config.MemifConf.Role = "master" 113 | } 114 | } 115 | if configData.Config.MemifConf.Mode == "" { 116 | configData.Config.MemifConf.Mode = conf.HostConf.MemifConf.Mode 117 | } 118 | configData.Config.MemifConf.Socketfile = conf.HostConf.MemifConf.Socketfile 119 | } else if configData.Config.IfType == "vhostuser" { 120 | if configData.Config.VhostConf.Mode == "" { 121 | if conf.HostConf.VhostConf.Mode == "client" { 122 | configData.Config.VhostConf.Mode = "server" 123 | } else { 124 | configData.Config.VhostConf.Mode = "client" 125 | } 126 | } 127 | configData.Config.VhostConf.Socketfile = conf.HostConf.VhostConf.Socketfile 128 | } 129 | 130 | // 131 | // Write configuration data that will be consumed by container 132 | // 133 | if kubeClient != nil { 134 | // 135 | // Write configuration data into annotation 136 | // 137 | logging.Debugf("SaveRemoteConfig(): Store in PodSpec") 138 | 139 | pod, err = annotations.WritePodAnnotation(kubeClient, pod, &configData) 140 | } else { 141 | // 142 | // Write configuration data into file 143 | // 144 | 145 | // Make sure directory exists 146 | if _, err = os.Stat(sharedDir); err != nil { 147 | if os.IsNotExist(err) { 148 | if err := os.MkdirAll(sharedDir, 0700); err != nil { 149 | return pod, err 150 | } 151 | } else { 152 | return pod, err 153 | } 154 | } 155 | 156 | fileName := fmt.Sprintf("configData-%s-%s.json", args.ContainerID[:12], args.IfName) 157 | path := filepath.Join(sharedDir, fileName) 158 | 159 | dataBytes, jsonErr := json.Marshal(configData) 160 | if jsonErr == nil { 161 | err = os.WriteFile(path, dataBytes, 0644) 162 | } else { 163 | return pod, fmt.Errorf("ERROR: serializing REMOTE NetConf data: %v", err) 164 | } 165 | } 166 | 167 | return pod, err 168 | } 169 | 170 | // CleanupRemoteConfig() - This function cleans up any remaining files 171 | // 172 | // in the passed in directory. Some of these files were used to squirrel 173 | // data from the create so interface can be deleted properly. 174 | // 175 | // FIXME: parameter *conf* is not used. It shall be used or removed. 176 | func CleanupRemoteConfig(conf *types.NetConf, sharedDir string) { 177 | 178 | if err := os.RemoveAll(sharedDir); err != nil { 179 | fmt.Println(err) 180 | } 181 | } 182 | 183 | // 184 | // Utility Functions 185 | // 186 | 187 | // This function deletes the input file (if provided) and the associated 188 | // directory (if provided) if the directory is empty. 189 | // 190 | // directory string - Directory file is located in, Use "" if directory 191 | // should remain unchanged. 192 | // filepath string - File (including directory) to be deleted. Use "" if 193 | // only the directory should be deleted. 194 | func FileCleanup(directory string, filepath string) (err error) { 195 | 196 | // If File is provided, delete it. 197 | if filepath != "" { 198 | err = os.Remove(filepath) 199 | if err != nil { 200 | return fmt.Errorf("ERROR: Failed to delete file: %v", err) 201 | } 202 | } 203 | 204 | // If Directory is provided and it is empty, delete it. 205 | if directory != "" { 206 | f, dirErr := os.Open(directory) 207 | if dirErr == nil { 208 | _, dirErr = f.Readdir(1) 209 | if dirErr == io.EOF { 210 | err = os.Remove(directory) 211 | } 212 | } 213 | f.Close() 214 | } 215 | 216 | return 217 | } 218 | 219 | type InterfaceData struct { 220 | Args skel.CmdArgs 221 | NetConf types.NetConf 222 | IPResult current.Result 223 | } 224 | 225 | func GetRemoteConfig(annotFile string) ([]*InterfaceData, string, error) { 226 | var ifaceList []*InterfaceData 227 | 228 | // Retrieve the directory that is shared between host and container. 229 | // No conversion necessary 230 | mappedDir, err := annotations.GetFileAnnotationMappedDir(annotFile) 231 | if err != nil { 232 | return ifaceList, mappedDir, err 233 | } 234 | 235 | // Retrieve the configuration data for each interface. This is a list of 1 to n interfaces. 236 | configDataList, err := annotations.GetFileAnnotationConfigData(annotFile) 237 | if err != nil { 238 | // If annotation is not found, need to see if data was written 239 | // to a file. 240 | 241 | // BILLY: Pickup here. 242 | return ifaceList, mappedDir, err 243 | } 244 | 245 | // Convert the data to types.NetConf 246 | for _, configData := range configDataList { 247 | var ifaceData InterfaceData 248 | 249 | ifaceData.NetConf = types.NetConf{} 250 | ifaceData.NetConf.Name = configData.Name 251 | ifaceData.NetConf.HostConf = configData.Config 252 | 253 | ifaceData.Args = skel.CmdArgs{} 254 | ifaceData.Args.ContainerID = configData.ContainerId 255 | ifaceData.Args.IfName = configData.IfName 256 | 257 | ifaceData.IPResult = configData.IPResult 258 | 259 | ifaceList = append(ifaceList, &ifaceData) 260 | } 261 | 262 | return ifaceList, mappedDir, err 263 | } 264 | -------------------------------------------------------------------------------- /pkg/k8sclient/k8sclient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Intel Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package k8sclient 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "net" 21 | "os" 22 | "testing" 23 | 24 | "github.com/containernetworking/cni/pkg/skel" 25 | cnitypes "github.com/containernetworking/cni/pkg/types" 26 | "github.com/intel/userspace-cni-network-plugin/userspace/testdata" 27 | "github.com/stretchr/testify/assert" 28 | "github.com/stretchr/testify/require" 29 | v1 "k8s.io/api/core/v1" 30 | "k8s.io/client-go/kubernetes" 31 | "k8s.io/client-go/kubernetes/fake" 32 | ) 33 | 34 | func TestGetK8sArgs(t *testing.T) { 35 | testCases := []struct { 36 | name string 37 | args *skel.CmdArgs 38 | expArgs *k8sArgs 39 | expErr error 40 | }{ 41 | { 42 | name: "args set to empty string", 43 | args: &skel.CmdArgs{Args: ""}, 44 | expArgs: &k8sArgs{}, 45 | }, 46 | { 47 | name: "args set correctly", 48 | args: &skel.CmdArgs{Args: "IgnoreUnknown=true;IP=127.0.0.1;K8S_POD_NAME=testpod;K8S_POD_NAMESPACE=testspace;K8S_POD_INFRA_CONTAINER_ID=0"}, 49 | expArgs: &k8sArgs{CommonArgs: cnitypes.CommonArgs{IgnoreUnknown: true}, IP: net.IPv4(127, 0, 0, 1), K8S_POD_NAME: "testpod", K8S_POD_NAMESPACE: "testspace", K8S_POD_INFRA_CONTAINER_ID: "0"}, 50 | }, 51 | { 52 | name: "ingnore unknown arg", 53 | args: &skel.CmdArgs{Args: "IgnoreUnknown=true;s0mEArG=anyValue"}, 54 | expArgs: &k8sArgs{CommonArgs: cnitypes.CommonArgs{IgnoreUnknown: true}}, 55 | }, 56 | { 57 | name: "fail with invalid IP", 58 | args: &skel.CmdArgs{Args: "IP=512.0.0.1;K8S_POD_NAME=testpod"}, 59 | expErr: errors.New("ARGS: error parsing"), 60 | }, 61 | { 62 | name: "fail with unknown arg", 63 | args: &skel.CmdArgs{Args: "s0mEArG=anyValue"}, 64 | expErr: errors.New("ARGS: unknown args"), 65 | }, 66 | { 67 | name: "fail with unknown arg 2", 68 | args: &skel.CmdArgs{Args: "IgnoreUnknown=false;s0mEArG=anyValue"}, 69 | expErr: errors.New("ARGS: unknown args"), 70 | }, 71 | { 72 | name: "fail with CmdArgs set to nil", 73 | args: nil, 74 | expErr: errors.New("getK8sArgs: failed to get k8s"), 75 | }, 76 | } 77 | for _, tc := range testCases { 78 | t.Run(tc.name, func(t *testing.T) { 79 | args, err := getK8sArgs(tc.args) 80 | 81 | if tc.expErr != nil { 82 | require.Error(t, err, "Error was expected") 83 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error") 84 | } else { 85 | require.NoError(t, err, "Unexpected error") 86 | assert.Equal(t, tc.expArgs, args, "Unexpected result") 87 | } 88 | 89 | }) 90 | } 91 | } 92 | 93 | func TestGetK8sClient(t *testing.T) { 94 | testCases := []struct { 95 | name string 96 | testType string 97 | expClient kubernetes.Interface 98 | expErr error 99 | }{ 100 | { 101 | name: "kubeClient set", 102 | testType: "client_set", 103 | }, 104 | { 105 | name: "kubeConfig set to nil", 106 | testType: "config_empty", 107 | expClient: nil, 108 | }, 109 | { 110 | name: "kubeConfig set to wrong file", 111 | testType: "config_invalid", 112 | expErr: errors.New("getK8sClient: failed to get context for the kubeConfig"), 113 | }, 114 | { 115 | name: "kube env variables set", 116 | testType: "environment_set", 117 | expErr: errors.New("createK8sClient: failed to get context for in-cluster"), 118 | }, 119 | } 120 | for _, tc := range testCases { 121 | t.Run(tc.name, func(t *testing.T) { 122 | var resClient, kubeClient kubernetes.Interface 123 | var kubeConfig string 124 | var err error 125 | 126 | switch tc.testType { 127 | case "client_set": 128 | kubeClient = fake.NewSimpleClientset() 129 | tc.expClient = kubeClient 130 | case "config_empty": 131 | kubeConfig = "" 132 | case "config_invalid": 133 | kubeConfig = "/proc/kubeconfig.yaml" 134 | case "environment_set": 135 | for _, param := range []string{"KUBERNETES_SERVICE_HOST", "KUBERNETES_SERVICE_PORT"} { 136 | value, found := os.LookupEnv(param) 137 | if found { 138 | defer os.Setenv(param, value) 139 | } else { 140 | defer os.Unsetenv(param) 141 | } 142 | os.Setenv(param, param+"-test-value") 143 | } 144 | } 145 | 146 | resClient, err = getK8sClient(kubeClient, kubeConfig) 147 | 148 | if tc.expErr != nil { 149 | require.Error(t, err, "Error was expected") 150 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error") 151 | } else { 152 | require.NoError(t, err, "Unexpected error") 153 | assert.Equal(t, tc.expClient, resClient, "Unexpected result") 154 | } 155 | 156 | }) 157 | } 158 | } 159 | 160 | func TestGetPod(t *testing.T) { 161 | testCases := []struct { 162 | name string 163 | testType string 164 | expErr error 165 | }{ 166 | { 167 | name: "get pod", 168 | }, 169 | { 170 | name: "fail to get pod with nil CmdArgs", 171 | testType: "args_nil", 172 | expErr: errors.New("getK8sArgs: failed to get k8s args for CmdArgs set to "), 173 | }, 174 | { 175 | name: "fail to get pod with nil kubeClient", 176 | testType: "client_nil", 177 | expErr: errors.New("GetPod: No kubeClient: "), 178 | }, 179 | { 180 | name: "fail to get pod with bad CmdArgs", 181 | testType: "args_invalid", 182 | expErr: errors.New("ARGS: error parsing"), 183 | }, 184 | { 185 | name: "fail to get pod with bad kubeConfig", 186 | testType: "config_invalid", 187 | expErr: errors.New("getK8sClient: failed to get context for the kubeConfig"), 188 | }, 189 | { 190 | name: "fail to get pod with empty kubeConfig", 191 | testType: "config_empty", 192 | expErr: errors.New("GetPod: No kubeClient:"), 193 | }, 194 | { 195 | name: "fail to get pod from empty pod set", 196 | testType: "pods_empty", 197 | expErr: errors.New("pods \"\" not found"), 198 | }, 199 | } 200 | for _, tc := range testCases { 201 | t.Run(tc.name, func(t *testing.T) { 202 | var kubeClient kubernetes.Interface 203 | var args *skel.CmdArgs 204 | var kubeConfig string 205 | var pod *v1.Pod 206 | 207 | switch tc.testType { 208 | case "args_invalid": 209 | args = &skel.CmdArgs{Args: "IP=512.0.0.1;K8S_POD_NAME=testpod"} 210 | case "args_nil": 211 | args = nil 212 | case "client_nil": 213 | args = testdata.GetTestArgs() 214 | kubeClient = nil 215 | case "config_invalid": 216 | args = testdata.GetTestArgs() 217 | kubeConfig = "/proc/kubeconfig.yaml" 218 | case "config_empty": 219 | args = testdata.GetTestArgs() 220 | kubeConfig = "" 221 | case "pods_empty": 222 | args = testdata.GetTestArgs() 223 | kubeClient = fake.NewSimpleClientset() 224 | default: 225 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-k8sclient-") 226 | require.NoError(t, dirErr, "Can't create temporary directory") 227 | defer os.RemoveAll(sharedDir) 228 | 229 | pod = testdata.GetTestPod(sharedDir) 230 | args = &skel.CmdArgs{Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", pod.Name, pod.Namespace)} 231 | kubeClient = fake.NewSimpleClientset(pod) 232 | } 233 | 234 | resPod, resClient, err := GetPod(args, kubeClient, kubeConfig) 235 | 236 | if tc.expErr != nil { 237 | require.Error(t, err, "Error was expected") 238 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error") 239 | } else { 240 | require.NoError(t, err, "Unexpected error") 241 | require.Equal(t, kubeClient, resClient, "Unexpected result") 242 | require.Equal(t, pod, resPod, "Unexpected result") 243 | } 244 | 245 | }) 246 | } 247 | } 248 | 249 | func TestWritePodAnnotation(t *testing.T) { 250 | testCases := []struct { 251 | name string 252 | testType string 253 | expErr error 254 | }{ 255 | { 256 | name: "write annotations", 257 | }, 258 | { 259 | name: "fail with pod set to nil", 260 | testType: "pod_nil", 261 | expErr: errors.New("WritePodAnnotation: No pod:"), 262 | }, 263 | { 264 | name: "fail with kubeClient set to nil", 265 | testType: "client_nil", 266 | expErr: errors.New("WritePodAnnotation: No kubeClient:"), 267 | }, 268 | { 269 | name: "fail to get pod", 270 | testType: "pod_not_found", 271 | expErr: errors.New("status update failed for pod"), 272 | }, 273 | } 274 | for _, tc := range testCases { 275 | t.Run(tc.name, func(t *testing.T) { 276 | var kubeClient kubernetes.Interface 277 | var pod *v1.Pod 278 | 279 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-k8sclient-") 280 | require.NoError(t, dirErr, "Can't create temporary directory") 281 | defer os.RemoveAll(sharedDir) 282 | 283 | switch tc.testType { 284 | case "client_nil": 285 | kubeClient = nil 286 | case "pod_not_found": 287 | pod = testdata.GetTestPod(sharedDir) 288 | kubeClient = fake.NewSimpleClientset() 289 | case "pod_nil": 290 | pod = nil 291 | kubeClient = fake.NewSimpleClientset() 292 | default: 293 | pod = testdata.GetTestPod(sharedDir) 294 | kubeClient = fake.NewSimpleClientset(pod) 295 | } 296 | 297 | origPod := pod.DeepCopy() 298 | resPod, err := WritePodAnnotation(kubeClient, pod) 299 | 300 | if tc.expErr != nil { 301 | require.Error(t, err, "Error was expected") 302 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error") 303 | } else { 304 | require.NoError(t, err, "Unexpected error") 305 | require.Equal(t, origPod, resPod, "Unexpected result") 306 | } 307 | 308 | }) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /cnivpp/cnivpp_test.go: -------------------------------------------------------------------------------- 1 | package cnivpp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "testing" 10 | 11 | current "github.com/containernetworking/cni/pkg/types/100" 12 | "github.com/intel/userspace-cni-network-plugin/pkg/types" 13 | "github.com/intel/userspace-cni-network-plugin/userspace/testdata" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | "k8s.io/client-go/kubernetes/fake" 17 | ) 18 | 19 | func TestGetMemifSocketfileName(t *testing.T) { 20 | t.Run("get Memif Socker File Name", func(t *testing.T) { 21 | args := testdata.GetTestArgs() 22 | 23 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-") 24 | require.NoError(t, dirErr, "Can't create temporary directory") 25 | defer os.RemoveAll(sharedDir) 26 | 27 | memifSockFileName := getMemifSocketfileName(&types.NetConf{}, sharedDir, args.ContainerID, args.IfName) 28 | assert.Equal(t, filepath.Join(sharedDir, fmt.Sprintf("memif-%s-%s.sock", args.ContainerID[:12], args.IfName)), memifSockFileName, "Unexpected error") 29 | 30 | conf := &types.NetConf{} 31 | conf.HostConf.MemifConf.Socketfile = "socketFile.sock" 32 | 33 | memifSockFileName = getMemifSocketfileName(conf, sharedDir, args.ContainerID, args.IfName) 34 | assert.Equal(t, filepath.Join(sharedDir, conf.HostConf.MemifConf.Socketfile), memifSockFileName, "Unexpected error") 35 | }) 36 | } 37 | 38 | func TestAddOnContainer(t *testing.T) { 39 | t.Run("save container data to file", func(t *testing.T) { 40 | var result *current.Result 41 | args := testdata.GetTestArgs() 42 | cniVpp := CniVpp{} 43 | 44 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-") 45 | require.NoError(t, dirErr, "Can't create temporary directory") 46 | defer os.RemoveAll(sharedDir) 47 | 48 | pod := testdata.GetTestPod(sharedDir) 49 | resPod, resErr := cniVpp.AddOnContainer(&types.NetConf{}, args, nil, sharedDir, pod, result) 50 | assert.NoError(t, resErr, "Unexpected error") 51 | assert.Equal(t, pod, resPod, "Unexpected change of pod data") 52 | fileName := fmt.Sprintf("configData-%s-%s.json", args.ContainerID[:12], args.IfName) 53 | assert.FileExists(t, path.Join(sharedDir, fileName), "Container data were not saved to file") 54 | }) 55 | } 56 | 57 | func TestDelOnContainer(t *testing.T) { 58 | t.Run("remove container configuration", func(t *testing.T) { 59 | args := testdata.GetTestArgs() 60 | cniVpp := CniVpp{} 61 | 62 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cniovs-") 63 | require.NoError(t, dirErr, "Can't create temporary directory") 64 | // just in case DelFromContainer fails 65 | defer os.RemoveAll(sharedDir) 66 | 67 | err := cniVpp.DelFromContainer(&types.NetConf{}, args, sharedDir, nil) 68 | assert.NoError(t, err, "Unexpected error") 69 | assert.NoDirExists(t, sharedDir, "Container data were not removed") 70 | }) 71 | } 72 | 73 | func TestAddOnHost(t *testing.T) { 74 | cniVpp := CniVpp{} 75 | 76 | testCases := []struct { 77 | name string 78 | netConf *types.NetConf 79 | fakeErr error 80 | expErr error 81 | }{ 82 | { 83 | name: "Happy path", 84 | netConf: &types.NetConf{ 85 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface", 86 | VhostConf: types.VhostConf{Mode: "client"}, 87 | MemifConf: types.MemifConf{ 88 | Role: "master", // Role of memif: master|slave 89 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 90 | }}}, 91 | expErr: nil, 92 | }, 93 | { 94 | name: "Invalid MEMIF Role", 95 | netConf: &types.NetConf{ 96 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface", 97 | VhostConf: types.VhostConf{Mode: "client"}, 98 | MemifConf: types.MemifConf{ 99 | Role: "", // Role of memif: master|slave 100 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 101 | }}}, 102 | expErr: errors.New("ERROR: Invalid MEMIF Role"), 103 | }, 104 | { 105 | name: "Unknown IfType", 106 | netConf: &types.NetConf{ 107 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "", NetType: "interface", 108 | VhostConf: types.VhostConf{Mode: "client"}, 109 | MemifConf: types.MemifConf{ 110 | Role: "", // Role of memif: master|slave 111 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 112 | }}}, 113 | expErr: errors.New("Unknown HostConf.IfType"), 114 | }, 115 | { 116 | name: "Unknown NetType", 117 | netConf: &types.NetConf{ 118 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "UnkownNetType", 119 | VhostConf: types.VhostConf{Mode: "client"}, 120 | MemifConf: types.MemifConf{ 121 | Role: "master", // Role of memif: master|slave 122 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 123 | }}}, 124 | expErr: errors.New("Unknown HostConf.NetType"), 125 | }, 126 | { 127 | name: "Bridge already exists", 128 | netConf: &types.NetConf{ 129 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "bridge", 130 | VhostConf: types.VhostConf{Mode: "client"}, 131 | MemifConf: types.MemifConf{ 132 | Role: "master", // Role of memif: master|slave 133 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 134 | }}}, 135 | expErr: errors.New("Bridge domain already exists"), 136 | }, 137 | { 138 | name: "Create 12345 Bridge", 139 | netConf: &types.NetConf{ 140 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "bridge", 141 | BridgeConf: types.BridgeConf{ 142 | BridgeName: "12345", 143 | BridgeId: 12345, 144 | }, 145 | VhostConf: types.VhostConf{Mode: "client"}, 146 | MemifConf: types.MemifConf{ 147 | Role: "master", // Role of memif: master|slave 148 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 149 | }}}, 150 | expErr: nil, 151 | }, 152 | { 153 | name: "NetType set to empty", 154 | netConf: &types.NetConf{ 155 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "", 156 | VhostConf: types.VhostConf{Mode: "client"}, 157 | MemifConf: types.MemifConf{ 158 | Role: "master", // Role of memif: master|slave 159 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 160 | }}}, 161 | expErr: errors.New("ERROR: NetType must be provided"), 162 | }, 163 | { 164 | name: "interface slave and ip mode", 165 | netConf: &types.NetConf{ 166 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface", 167 | VhostConf: types.VhostConf{Mode: "client"}, 168 | MemifConf: types.MemifConf{ 169 | Role: "slave", // Role of memif: master|slave 170 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 171 | }}}, 172 | expErr: nil, 173 | }, 174 | } 175 | for _, tc := range testCases { 176 | t.Run(tc.name, func(t *testing.T) { 177 | var result *current.Result 178 | args := testdata.GetTestArgs() 179 | 180 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cnivpp-") 181 | require.NoError(t, dirErr, "Can't create temporary directory") 182 | defer os.RemoveAll(sharedDir) 183 | 184 | pod := testdata.GetTestPod(sharedDir) 185 | kubeClient := fake.NewSimpleClientset(pod) 186 | 187 | err := cniVpp.AddOnHost(tc.netConf, args, kubeClient, sharedDir, result) 188 | if tc.expErr == nil { 189 | assert.Equal(t, tc.expErr, err, "Unexpected result") 190 | // on success there shall be saved ovs data 191 | var data VppSavedData 192 | assert.NoError(t, LoadVppConfig(tc.netConf, args, &data)) 193 | assert.NotEmpty(t, data.MemifSocketId) 194 | } else { 195 | require.Error(t, err, "Unexpected result") 196 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected result") 197 | } 198 | }) 199 | } 200 | } 201 | 202 | func TestDelFromHost(t *testing.T) { 203 | cniVpp := CniVpp{} 204 | 205 | testCases := []struct { 206 | name string 207 | netConf *types.NetConf 208 | savedData string 209 | fakeErr error 210 | expErr error 211 | }{ 212 | { 213 | name: "Happy path", 214 | netConf: &types.NetConf{ 215 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "memif", NetType: "interface", 216 | VhostConf: types.VhostConf{Mode: "client"}, 217 | MemifConf: types.MemifConf{ 218 | Role: "master", // Role of memif: master|slave 219 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 220 | }}}, 221 | expErr: nil, 222 | }, 223 | { 224 | name: "Unknown HostConf Type", 225 | netConf: &types.NetConf{ 226 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "Unknown", NetType: "interface", 227 | VhostConf: types.VhostConf{Mode: "client"}, 228 | MemifConf: types.MemifConf{ 229 | Role: "master", // Role of memif: master|slave 230 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 231 | }}}, 232 | expErr: fmt.Errorf("ERROR: Unknown HostConf.Type"), 233 | }, 234 | { 235 | name: "Delete Bridge with IfType set to vhostUser", 236 | netConf: &types.NetConf{ 237 | HostConf: types.UserSpaceConf{Engine: "vpp", IfType: "vhostuser", NetType: "bridge", 238 | VhostConf: types.VhostConf{Mode: "client"}, 239 | BridgeConf: types.BridgeConf{ 240 | BridgeName: "12345", 241 | BridgeId: 12345, 242 | }, 243 | MemifConf: types.MemifConf{ 244 | Role: "master", // Role of memif: master|slave 245 | Mode: "ip", // Mode of memif: ip|ethernet|inject-punt 246 | }}}, 247 | expErr: fmt.Errorf("GOOD: Found HostConf.Type:vhostuser"), 248 | }, 249 | } 250 | for _, tc := range testCases { 251 | t.Run(tc.name, func(t *testing.T) { 252 | args := testdata.GetTestArgs() 253 | sharedDir, dirErr := os.MkdirTemp("/tmp", "test-cnivpp-") 254 | require.NoError(t, dirErr, "Can't create temporary directory") 255 | defer os.RemoveAll(sharedDir) 256 | 257 | var result *current.Result 258 | 259 | pod := testdata.GetTestPod(sharedDir) 260 | kubeClient := fake.NewSimpleClientset(pod) 261 | 262 | _ = cniVpp.AddOnHost(tc.netConf, args, kubeClient, sharedDir, result) 263 | 264 | err := cniVpp.DelFromHost(tc.netConf, args, sharedDir) 265 | if tc.expErr == nil { 266 | assert.Equal(t, tc.expErr, err, "Unexpected result") 267 | } else { 268 | require.Error(t, err, "Unexpected result") 269 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected result") 270 | } 271 | }) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------