├── docs ├── sequence.png ├── high_level_arch.png └── sequence.puml ├── pkg ├── goclient │ ├── go.mod │ └── goclient.go ├── subfunctions │ ├── go.mod │ ├── go.sum │ └── cdq.go └── cclient │ ├── README.md │ └── cclient.go ├── .clang-format ├── test ├── fuzz │ ├── deviceplugin │ │ ├── afxdp │ │ │ ├── config.json │ │ │ ├── afxdp-fuzz-pod.yaml │ │ │ ├── nad.yaml │ │ │ └── fuzz.sh │ │ ├── config │ │ │ ├── fuzz.sh │ │ │ └── config.go │ │ └── uds │ │ │ ├── fuzz.sh │ │ │ └── uds.go │ └── cni │ │ ├── outputAdd │ │ └── corpus │ │ │ ├── config1 │ │ │ ├── config3 │ │ │ └── config2 │ │ ├── outputDel │ │ └── corpus │ │ │ ├── config1 │ │ │ ├── config3 │ │ │ └── config2 │ │ ├── fuzz.sh │ │ └── cni.go └── e2e │ ├── pod-1c1d.yaml │ ├── pod-1c2d.yaml │ ├── config.json │ ├── nad.yaml │ ├── Dockerfile │ ├── pod-2c2d.yaml │ ├── daemonset.yml │ └── udsTest.go ├── .githooks └── pre-commit ├── hack └── kind-config.yaml ├── .github ├── e2e │ ├── podCdq_1_50.yaml │ ├── podPrimary_1_4.yaml │ ├── podCdq_3_10.yaml │ ├── nad.yaml │ └── daemonset.yml └── workflows │ ├── codeql.yml │ ├── scorecard.yml │ ├── internal-ci.yml │ └── public-ci.yml ├── .golangci.yml ├── .gitignore ├── examples ├── cndp-0-0.yaml ├── kind-pod-spec.yaml ├── pod-spec.yaml ├── nad_with_syncer.yaml └── network-attachment-definition.yaml ├── internal ├── bpf │ ├── xdp-pass │ │ ├── xdp_pass.c │ │ └── Makefile │ ├── bpfWrapper.h │ ├── xdp-afxdp-redirect │ │ ├── Makefile │ │ └── xdp_afxdp_redirect.c │ ├── log.c │ ├── log.h │ ├── bpfWrapper_fake.go │ ├── bpfWrapper.go │ └── bpfWrapper.c ├── dpcnisyncer │ ├── dp_cni_syncer.proto │ ├── dp_cni_syncer_grpc.pb.go │ └── dp_cni_syncer.pb.go ├── logformats │ └── logFormats.go ├── udsserver │ └── udsserver_fake.go ├── dpcnisyncerclient │ └── client.go ├── uds │ ├── udsUnit_test.go │ ├── uds_fake.go │ └── uds_fuzz.go ├── resourcesapi │ ├── resources_api_fake.go │ └── resources_api.go ├── tools │ ├── tools.go │ └── tools_test.go ├── networking │ ├── ethtool.go │ ├── veth.go │ ├── bridge.go │ ├── kind.go │ └── networking_fake.go ├── host │ └── host_fake.go ├── dpcnisyncerserver │ └── server.go └── cni │ └── cni_test.go ├── images ├── entrypoint.sh └── amd64.dockerfile ├── go.mod ├── cmd └── cni │ └── main.go ├── SECURITY.md ├── deployments ├── daemonset-pinning.yaml ├── daemonset-kind.yaml └── daemonset.yml └── Makefile /docs/sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/afxdp-plugins-for-kubernetes/HEAD/docs/sequence.png -------------------------------------------------------------------------------- /pkg/goclient/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/afxdp-plugins-for-kubernetes/pkg/goclient 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /docs/high_level_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/afxdp-plugins-for-kubernetes/HEAD/docs/high_level_arch.png -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | ColumnLimit: 100 3 | ContinuationIndentWidth: 8 4 | IndentWidth: 8 5 | TabWidth: 8 6 | UseTab: Always -------------------------------------------------------------------------------- /pkg/subfunctions/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/afxdp-plugins-for-kubernetes/pkg/subfunctions 2 | 3 | go 1.13 4 | 5 | require github.com/sirupsen/logrus v1.9.0 6 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/afxdp/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel": "debug", 3 | "mode": "primary", 4 | "udsFuzz": true, 5 | "pools" : [ 6 | { 7 | "name" : "fuzz", 8 | "drivers" : ["i40e"] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | for filename in $(git diff --cached --name-only | grep '.*\.[c|h]$'); do 4 | clang-format -style=file -i "$filename"; git add "$filename"; 5 | done 6 | 7 | for filename in $(git diff --cached --name-only | grep '.*\.go$'); do 8 | gofmt -s -w "$filename"; git add "$filename"; 9 | done 10 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputAdd/corpus/config1: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.0", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "ipam": { 6 | "type": "host-local", 7 | "subnet": "192.168.1.0/24", 8 | "rangeStart": "192.168.1.200", 9 | "rangeEnd": "192.168.1.220", 10 | "routes": [ 11 | { "dst": "0.0.0.0/0" } 12 | ], 13 | "gateway": "192.168.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputDel/corpus/config1: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.0", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "ipam": { 6 | "type": "host-local", 7 | "subnet": "192.168.1.0/24", 8 | "rangeStart": "192.168.1.200", 9 | "rangeEnd": "192.168.1.220", 10 | "routes": [ 11 | { "dst": "0.0.0.0/0" } 12 | ], 13 | "gateway": "192.168.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hack/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | 4 | nodes: 5 | - role: control-plane 6 | - role: worker 7 | extraMounts: 8 | - hostPath: /tmp/afxdp_dp/ 9 | containerPath: /tmp/afxdp_dp/ 10 | propagation: Bidirectional 11 | selinuxRelabel: false 12 | - role: worker 13 | extraMounts: 14 | - hostPath: /tmp/afxdp_dp2/ 15 | containerPath: /tmp/afxdp_dp/ 16 | propagation: Bidirectional 17 | selinuxRelabel: false 18 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/afxdp/afxdp-fuzz-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: afxdp-fuzz-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/networks: afxdp-fuzz-test 7 | spec: 8 | containers: 9 | - name: afxdp 10 | image: afxdp-fuzz-test:latest 11 | imagePullPolicy: Never 12 | command: ["tail", "-f", "/dev/null"] 13 | resources: 14 | requests: 15 | afxdp/fuzz: '1' 16 | limits: 17 | afxdp/fuzz: '1' 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /test/e2e/pod-1c1d.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: afxdp-e2e-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-test 7 | spec: 8 | securityContext: 9 | runAsUser: 1500 10 | containers: 11 | - name: afxdp 12 | image: afxdp-e2e-test:latest 13 | imagePullPolicy: Never 14 | command: ["tail", "-f", "/dev/null"] 15 | resources: 16 | requests: 17 | afxdp/e2e: '1' 18 | limits: 19 | afxdp/e2e: '1' 20 | restartPolicy: Never 21 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputAdd/corpus/config3: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.1", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "logFile": "/var/log/afxdp-k8s-plugins/fuzz.log", 6 | "logLevel": "info", 7 | "ipam": { 8 | "type": "host-local", 9 | "subnet": "192.168.1.0/24", 10 | "rangeStart": "192.168.1.220", 11 | "rangeEnd": "192.168.1.240", 12 | "routes": [ 13 | { "dst": "0.0.0.0/0" } 14 | ], 15 | "gateway": "192.168.1.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputDel/corpus/config3: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.1", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "logFile": "/var/log/afxdp-k8s-plugins/fuzz.log", 6 | "logLevel": "info", 7 | "ipam": { 8 | "type": "host-local", 9 | "subnet": "192.168.1.0/24", 10 | "rangeStart": "192.168.1.220", 11 | "rangeEnd": "192.168.1.240", 12 | "routes": [ 13 | { "dst": "0.0.0.0/0" } 14 | ], 15 | "gateway": "192.168.1.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputAdd/corpus/config2: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.0", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "logFile": "/var/log/afxdp-k8s-plugins/fuzz.log", 6 | "logLevel": "debug", 7 | "ipam": { 8 | "type": "host-local", 9 | "subnet": "192.168.1.0/24", 10 | "rangeStart": "192.168.1.200", 11 | "rangeEnd": "192.168.1.220", 12 | "routes": [ 13 | { "dst": "0.0.0.0/0" } 14 | ], 15 | "gateway": "192.168.1.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fuzz/cni/outputDel/corpus/config2: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.0", 3 | "type": "afxdp", 4 | "mode": "primary", 5 | "logFile": "/var/log/afxdp-k8s-plugins/fuzz.log", 6 | "logLevel": "debug", 7 | "ipam": { 8 | "type": "host-local", 9 | "subnet": "192.168.1.0/24", 10 | "rangeStart": "192.168.1.200", 11 | "rangeEnd": "192.168.1.220", 12 | "routes": [ 13 | { "dst": "0.0.0.0/0" } 14 | ], 15 | "gateway": "192.168.1.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/e2e/pod-1c2d.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: afxdp-e2e-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-test, afxdp-e2e-test 7 | spec: 8 | securityContext: 9 | runAsUser: 1500 10 | containers: 11 | - name: afxdp 12 | image: afxdp-e2e-test:latest 13 | imagePullPolicy: Never 14 | command: ["tail", "-f", "/dev/null"] 15 | resources: 16 | requests: 17 | afxdp/e2e: '2' 18 | limits: 19 | afxdp/e2e: '2' 20 | restartPolicy: Never 21 | -------------------------------------------------------------------------------- /test/e2e/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel":"debug", 3 | "logFile":"afxdp-dp-e2e.log", 4 | "pools":[ 5 | { 6 | "name":"e2e", 7 | "mode":"primary", 8 | "UdsTimeout":30, 9 | "uid":1500, 10 | "ethtoolCmds" : ["-X -device- equal 5 start 3","--config-ntuple -device- flow-type udp4 dst-ip -ip- action"], 11 | "drivers":[ 12 | { 13 | "name":"i40e" 14 | }, 15 | { 16 | "name":"ice" 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.github/e2e/podCdq_1_50.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | generateName: afxdp-e2e- 5 | "labels": { 6 | "app" : "afxdp-e2e" 7 | } 8 | annotations: 9 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-cdq 10 | spec: 11 | securityContext: 12 | runAsUser: 1500 13 | containers: 14 | - name: afxdp1 15 | image: afxdp-e2e-test:latest 16 | imagePullPolicy: Never 17 | command: ["/bin/sh", "-c"] 18 | args: ["udsTest &> /tmp/udsTest.txt; tail -f /dev/null"] 19 | resources: 20 | requests: 21 | afxdp/e2eCdq: '1' 22 | limits: 23 | afxdp/e2eCdq: '1' 24 | restartPolicy: Never 25 | -------------------------------------------------------------------------------- /.github/e2e/podPrimary_1_4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | generateName: afxdp-e2e- 5 | "labels": { 6 | "app" : "afxdp-e2e" 7 | } 8 | annotations: 9 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-primary 10 | spec: 11 | securityContext: 12 | runAsUser: 1500 13 | containers: 14 | - name: afxdp1 15 | image: afxdp-e2e-test:latest 16 | imagePullPolicy: Never 17 | command: ["/bin/sh", "-c"] 18 | args: ["udsTest &> /tmp/udsTest.txt; tail -f /dev/null"] 19 | resources: 20 | requests: 21 | afxdp/e2ePrimary: '1' 22 | limits: 23 | afxdp/e2ePrimary: '1' 24 | restartPolicy: Never 25 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Adding this config file to work around a false positive reported by GolangCI-Lint 2 | # It is incorrectly flagging missing types in reference to embedded structs 3 | # Our CNI NetConfig embeds types.NetConf from github.com/containernetworking/cni/pkg/types 4 | # The fields of the embedded struct - IPAM, CNIVersion, DNS, etc. are being flagged as undefined 5 | # I think this is our issue: https://github.com/golangci/golangci-lint/issues/826 6 | # We can remove this config file if fixed in a future version of GolangCI-Lint 7 | 8 | issues: 9 | exclude-rules: 10 | - path: internal/cni/cni.go 11 | text: "NetConfig has no field or method" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | *.o 3 | *.a 4 | .idea 5 | *.ll 6 | test/e2e/udsTest 7 | test/fuzz/cni/cni-fuzz.zip 8 | test/fuzz/test/fuzz/deviceplugin/uds/fuzz_error.log 9 | test/fuzz/test/fuzz/deviceplugin/uds/uds-fuzz.zip 10 | 11 | !test/fuzz/cni/outputAdd/ 12 | test/fuzz/cni/outputAdd/* 13 | !test/fuzz/cni/outputAdd/corpus/ 14 | test/fuzz/cni/outputAdd/corpus/* 15 | !test/fuzz/cni/outputAdd/corpus/config* 16 | 17 | !test/fuzz/cni/outputDel/ 18 | test/fuzz/cni/outputDel/* 19 | !test/fuzz/cni/outputDel/corpus/ 20 | test/fuzz/cni/outputDel/corpus/* 21 | !test/fuzz/cni/outputDel/corpus/config* 22 | 23 | !test/fuzz/deviceplugin/uds/outputUDS/ 24 | test/fuzz/deviceplugin/uds/outputUDS/ 25 | 26 | .vscode 27 | -------------------------------------------------------------------------------- /test/e2e/nad.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "k8s.cni.cncf.io/v1" 2 | kind: NetworkAttachmentDefinition 3 | metadata: 4 | name: afxdp-e2e-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/resourceName: afxdp/e2e 7 | spec: 8 | config: '{ 9 | "cniVersion": "0.3.0", 10 | "type": "afxdp", 11 | "mode": "primary", 12 | "logFile": "afxdp-cni-e2e.log", 13 | "logLevel": "debug", 14 | "ipam": { 15 | "type": "host-local", 16 | "subnet": "192.168.1.0/24", 17 | "rangeStart": "192.168.1.200", 18 | "rangeEnd": "192.168.1.216", 19 | "routes": [ 20 | { "dst": "0.0.0.0/0" } 21 | ], 22 | "gateway": "192.168.1.1" 23 | } 24 | }' 25 | -------------------------------------------------------------------------------- /test/e2e/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright(c) 2022 Intel Corporation. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | FROM alpine:3.14 15 | RUN apk --no-cache add -U iproute2=5.12.0-r0 16 | COPY ./udsTest /bin/udsTest 17 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/afxdp/nad.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "k8s.cni.cncf.io/v1" 2 | kind: NetworkAttachmentDefinition 3 | metadata: 4 | name: afxdp-fuzz-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/resourceName: afxdp/fuzz 7 | spec: 8 | config: '{ 9 | "cniVersion": "0.3.0", 10 | "type": "afxdp-fuzz", 11 | "mode": "afxdp", 12 | "logFile": "/var/log/afxdp-k8s-plugins/afxdp-cni-fuzz.log", 13 | "logLevel": "debug", 14 | "ipam": { 15 | "type": "host-local", 16 | "subnet": "192.168.1.0/24", 17 | "rangeStart": "192.168.1.200", 18 | "rangeEnd": "192.168.1.216", 19 | "routes": [ 20 | { "dst": "0.0.0.0/0" } 21 | ], 22 | "gateway": "192.168.1.1" 23 | } 24 | }' 25 | -------------------------------------------------------------------------------- /test/e2e/pod-2c2d.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: afxdp-e2e-test 5 | annotations: 6 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-test, afxdp-e2e-test 7 | spec: 8 | securityContext: 9 | runAsUser: 1500 10 | containers: 11 | - name: afxdp 12 | image: afxdp-e2e-test:latest 13 | imagePullPolicy: Never 14 | command: ["tail", "-f", "/dev/null"] 15 | resources: 16 | requests: 17 | afxdp/e2e: '1' 18 | limits: 19 | afxdp/e2e: '1' 20 | 21 | - name: afxdp2 22 | image: afxdp-e2e-test:latest 23 | imagePullPolicy: Never 24 | command: ["tail", "-f", "/dev/null"] 25 | resources: 26 | requests: 27 | afxdp/e2e: '1' 28 | limits: 29 | afxdp/e2e: '1' 30 | restartPolicy: Never 31 | -------------------------------------------------------------------------------- /examples/cndp-0-0.yaml: -------------------------------------------------------------------------------- 1 | # A working example of BPF MAP PINNING 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: cndp-0-0 6 | annotations: 7 | k8s.v1.cni.cncf.io/networks: afxdp-network 8 | spec: 9 | containers: 10 | - name: cndp-0 11 | command: ["/bin/bash"] 12 | args: ["-c", "./jsonc_gen.sh -kp ; cndpfwd -c config.jsonc lb;"] 13 | image: quay.io/mtahhan/cndp-map-pinning:latest 14 | imagePullPolicy: IfNotPresent 15 | securityContext: 16 | capabilities: 17 | add: 18 | - NET_RAW 19 | - IPC_LOCK 20 | #- BPF # Only needed if Kernel version <= 5.18 21 | resources: 22 | requests: 23 | afxdp/myPool: '1' 24 | limits: 25 | afxdp/myPool: '1' 26 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: 'CodeQL' 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | pull_request: 8 | branches: [ main ] 9 | permissions: # added using https://github.com/step-security/secure-repo 10 | contents: read 11 | 12 | jobs: 13 | CodeQL-Build: 14 | # CodeQL runs on ubuntu-latest 15 | runs-on: ubuntu-latest 16 | 17 | permissions: 18 | # required for all workflows 19 | security-events: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 24 | 25 | # Initializes the CodeQL tools for scanning. 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 28 | 29 | - name: Perform CodeQL Analysis 30 | uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 31 | -------------------------------------------------------------------------------- /internal/bpf/xdp-pass/xdp_pass.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 | // clang-format off 16 | #include 17 | #include 18 | #include 19 | // clang-format on 20 | SEC("xdp") 21 | int xdp_prog_pass(struct xdp_md *ctx) { return XDP_PASS; } 22 | 23 | char _license[] SEC("license") = "Dual BSD"; 24 | -------------------------------------------------------------------------------- /images/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright(c) 2022 Intel Corporation. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | BINS_DIR="/afxdp" 19 | DP_BIN="afxdp-dp" 20 | DP_CONFIG_FILE=$BINS_DIR/"config/config.json" 21 | CNI_BIN="afxdp" 22 | CNI_BIN_DIR="/opt/cni/bin" 23 | 24 | cp -f $BINS_DIR/$CNI_BIN $CNI_BIN_DIR/$CNI_BIN 25 | exec $BINS_DIR/$DP_BIN -config $DP_CONFIG_FILE 26 | -------------------------------------------------------------------------------- /internal/bpf/bpfWrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef _WRAPPER_H_ 18 | #define _WRAPPER_H_ 19 | 20 | int Load_bpf_send_xsk_map(char *ifname); 21 | int Configure_busy_poll(int fd, int busy_timeout, int busy_budget); 22 | int Clean_bpf(char *ifname); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /internal/dpcnisyncer/dp_cni_syncer.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 | syntax = "proto3"; 17 | 18 | option go_package = "github.com/intel/afxdp-plugins-for-kubernetes/internal/dpcnisyncer"; 19 | 20 | package dpcnisyncer_proto; 21 | 22 | service NetDev { 23 | rpc DelNetDev(DeleteNetDevReq) returns (DeleteNetDevResp); 24 | } 25 | 26 | message DeleteNetDevReq { 27 | string name = 1; 28 | } 29 | 30 | message DeleteNetDevResp { 31 | int32 ret = 1; 32 | } 33 | -------------------------------------------------------------------------------- /internal/bpf/xdp-pass/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright(c) Red Hat Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | LLC ?= llc 15 | CLANG ?= clang 16 | 17 | all: xdppass 18 | 19 | xdppass: 20 | $(CLANG) -S \ 21 | -target bpf \ 22 | -D __BPF_TRACING__ \ 23 | -I/usr/include/bpf \ 24 | -Wall \ 25 | -Wno-unused-value \ 26 | -Wno-pointer-sign \ 27 | -Wno-compare-distinct-pointer-types \ 28 | -Werror \ 29 | -O2 -emit-llvm -c -g -o xdp_pass.ll xdp_pass.c 30 | $(LLC) -march=bpf -filetype=obj -o xdp_pass.o xdp_pass.ll 31 | 32 | clean: 33 | rm -f *.o xdp_pass.ll 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/afxdp-plugins-for-kubernetes 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/containernetworking/cni v1.1.2 7 | github.com/containernetworking/plugins v1.1.1 8 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 9 | github.com/golang/protobuf v1.5.3 10 | github.com/google/gofuzz v1.1.0 11 | github.com/google/uuid v1.3.0 12 | github.com/intel/afxdp-plugins-for-kubernetes/pkg/goclient v0.0.0 13 | github.com/intel/afxdp-plugins-for-kubernetes/pkg/subfunctions v0.0.0 14 | github.com/moby/sys/mount v0.3.3 15 | github.com/pkg/errors v0.9.1 16 | github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1 17 | github.com/sirupsen/logrus v1.9.0 18 | github.com/stretchr/testify v1.8.3 19 | github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 20 | golang.org/x/net v0.17.0 21 | google.golang.org/grpc v1.56.3 22 | gotest.tools v2.2.0+incompatible 23 | k8s.io/apimachinery v0.25.2 24 | k8s.io/kubelet v0.25.2 25 | ) 26 | 27 | replace github.com/intel/afxdp-plugins-for-kubernetes/pkg/subfunctions => ./pkg/subfunctions 28 | 29 | replace github.com/intel/afxdp-plugins-for-kubernetes/pkg/goclient => ./pkg/goclient 30 | -------------------------------------------------------------------------------- /internal/bpf/xdp-afxdp-redirect/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright(c) Red Hat Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | LLC ?= llc 15 | CLANG ?= clang 16 | 17 | all: afxdp_redirect 18 | 19 | afxdp_redirect: 20 | $(CLANG) -S \ 21 | -target bpf \ 22 | -D __BPF_TRACING__ \ 23 | -I/usr/include/bpf \ 24 | -Wall \ 25 | -Wno-unused-value \ 26 | -Wno-pointer-sign \ 27 | -Wno-compare-distinct-pointer-types \ 28 | -Werror \ 29 | -O2 -emit-llvm -c -g -o xdp_afxdp_redirect.ll xdp_afxdp_redirect.c 30 | $(LLC) -march=bpf -filetype=obj -o xdp_afxdp_redirect.o xdp_afxdp_redirect.ll 31 | 32 | clean: 33 | rm -f *.o xdp_afxdp_redirect.ll 34 | -------------------------------------------------------------------------------- /examples/kind-pod-spec.yaml: -------------------------------------------------------------------------------- 1 | # WARNING: This is an example pod spec only. Remove all comments before use. 2 | 3 | apiVersion: v1 4 | kind: Pod 5 | metadata: 6 | name: afxdp-pod # Pod name 7 | annotations: 8 | k8s.v1.cni.cncf.io/networks: afxdp-network # List of networks to attach to this pod (i.e. network name specified in network-attachment-definition.yaml) 9 | spec: 10 | containers: 11 | - name: afxdp 12 | image: docker-image:latest # Specify your docker image here, along with PullPolicy and command 13 | imagePullPolicy: IfNotPresent 14 | command: ["tail", "-f", "/dev/null"] 15 | securityContext: 16 | capabilities: 17 | add: 18 | - NET_RAW 19 | - IPC_LOCK 20 | - BPF # Only needed if kernel version <= 5.18 21 | resources: 22 | requests: 23 | afxdp/myPool: '1' # The resource requested needs to match the device plugin pool name / resource type 24 | limits: # The number requested needs to match the amount requested in annotations list above 25 | afxdp/myPool: '1' # Requests and limits should match 26 | restartPolicy: Never 27 | -------------------------------------------------------------------------------- /cmd/cni/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/afxdp-plugins-for-kubernetes/internal/cni" 22 | ) 23 | 24 | func main() { 25 | skel.PluginMain( 26 | func(args *skel.CmdArgs) error { 27 | err := cni.CmdAdd(args) 28 | if err != nil { 29 | return err 30 | } 31 | return nil 32 | }, 33 | func(args *skel.CmdArgs) error { 34 | return cni.CmdCheck(args) 35 | }, 36 | func(args *skel.CmdArgs) error { return cni.CmdDel(args) }, 37 | cniversion.All, "AF_XDP CNI Plugin") 38 | } 39 | -------------------------------------------------------------------------------- /.github/e2e/podCdq_3_10.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | generateName: afxdp-e2e- 5 | "labels": { 6 | "app" : "afxdp-e2e" 7 | } 8 | annotations: 9 | k8s.v1.cni.cncf.io/networks: afxdp-e2e-cdq, afxdp-e2e-cdq, afxdp-e2e-cdq, afxdp-e2e-cdq 10 | spec: 11 | securityContext: 12 | runAsUser: 1500 13 | containers: 14 | - name: afxdp1 15 | image: afxdp-e2e-test:latest 16 | imagePullPolicy: Never 17 | command: ["/bin/sh", "-c"] 18 | args: ["udsTest &> /tmp/udsTest.txt; tail -f /dev/null"] 19 | resources: 20 | requests: 21 | afxdp/e2eCdq: '2' 22 | limits: 23 | afxdp/e2eCdq: '2' 24 | - name: afxdp2 25 | image: afxdp-e2e-test:latest 26 | imagePullPolicy: Never 27 | command: ["/bin/sh", "-c"] 28 | args: ["udsTest &> /tmp/udsTest.txt; tail -f /dev/null"] 29 | resources: 30 | requests: 31 | afxdp/e2eCdq: '1' 32 | limits: 33 | afxdp/e2eCdq: '1' 34 | - name: afxdp3 35 | image: afxdp-e2e-test:latest 36 | imagePullPolicy: Never 37 | command: ["/bin/sh", "-c"] 38 | args: ["udsTest &> /tmp/udsTest.txt; tail -f /dev/null"] 39 | resources: 40 | requests: 41 | afxdp/e2eCdq: '1' 42 | limits: 43 | afxdp/e2eCdq: '1' 44 | restartPolicy: Never 45 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/config/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cleanup() { 5 | echo 6 | echo "*****************************************************" 7 | echo "* Cleanup *" 8 | echo "*****************************************************" 9 | echo 10 | echo "delete remaining uds sockets" 11 | rm -f /tmp/afxdp/* 12 | echo 13 | echo "delete config temp directory" 14 | rm -rf config/ 15 | } 16 | 17 | build() { 18 | echo 19 | echo "*****************************************************" 20 | echo "* Install and Build Go-Fuzz *" 21 | echo "*****************************************************" 22 | echo 23 | echo "installing go-fuzz" 24 | go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest 25 | echo 26 | echo "building test app" 27 | go-fuzz-build 28 | echo 29 | } 30 | 31 | run() { 32 | echo 33 | echo "*****************************************************" 34 | echo "* Run Fuzz Test *" 35 | echo "*****************************************************" 36 | echo 37 | echo "running tests" 38 | go-fuzz -bin=./deviceplugin-fuzz.zip -workdir ./outputConfig -dumpcover -func Fuzz 39 | } 40 | 41 | cleanup 42 | build 43 | run 44 | trap cleanup EXIT 45 | -------------------------------------------------------------------------------- /examples/pod-spec.yaml: -------------------------------------------------------------------------------- 1 | # WARNING: This is an example pod spec only. Remove all comments before use. 2 | 3 | apiVersion: v1 4 | kind: Pod 5 | metadata: 6 | name: afxdp-pod # Pod name 7 | annotations: 8 | k8s.v1.cni.cncf.io/networks: afxdp-network # List of networks to attach to this pod (i.e. network name specified in network-attachment-definition.yaml) 9 | spec: 10 | containers: 11 | - name: afxdp 12 | image: docker-image:latest # Specify your docker image here, along with PullPolicy and command 13 | imagePullPolicy: IfNotPresent 14 | command: ["tail", "-f", "/dev/null"] 15 | # capabilities: # Should be configured if using DPDK/CNDP with BPF Map pinning. 16 | # add: 17 | # - NET_RAW 18 | # - IPC_LOCK 19 | # - BPF # Only needed if kernel version <= 5.18 20 | resources: 21 | requests: 22 | afxdp/myPool: '1' # The resource requested needs to match the device plugin pool name / resource type 23 | limits: # The number requested needs to match the amount requested in annotations list above 24 | afxdp/myPool: '1' # Requests and limits should match 25 | restartPolicy: Never 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/e2e/nad.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "k8s.cni.cncf.io/v1" 2 | kind: NetworkAttachmentDefinition 3 | metadata: 4 | name: afxdp-e2e-cdq 5 | annotations: 6 | k8s.v1.cni.cncf.io/resourceName: afxdp/e2eCdq 7 | spec: 8 | config: '{ 9 | "cniVersion": "0.3.0", 10 | "type": "afxdp", 11 | "mode": "cdq", 12 | "logFile": "afxdp-cni-e2e.log", 13 | "logLevel": "debug", 14 | "ipam": { 15 | "type": "host-local", 16 | "subnet": "192.168.0.0/20", 17 | "rangeStart": "192.168.0.1", 18 | "rangeEnd": "192.168.15.254", 19 | "routes": [ 20 | { "dst": "0.0.0.0/0" } 21 | ], 22 | "gateway": "192.168.1.1" 23 | } 24 | }' 25 | --- 26 | apiVersion: "k8s.cni.cncf.io/v1" 27 | kind: NetworkAttachmentDefinition 28 | metadata: 29 | name: afxdp-e2e-primary 30 | annotations: 31 | k8s.v1.cni.cncf.io/resourceName: afxdp/e2ePrimary 32 | spec: 33 | config: '{ 34 | "cniVersion": "0.3.0", 35 | "type": "afxdp", 36 | "mode": "primary", 37 | "logFile": "afxdp-cni-e2e.log", 38 | "logLevel": "debug", 39 | "ipam": { 40 | "type": "host-local", 41 | "subnet": "192.168.0.0/20", 42 | "rangeStart": "192.168.0.1", 43 | "rangeEnd": "192.168.15.254", 44 | "routes": [ 45 | { "dst": "0.0.0.0/0" } 46 | ], 47 | "gateway": "192.168.1.1" 48 | } 49 | }' 50 | -------------------------------------------------------------------------------- /pkg/cclient/README.md: -------------------------------------------------------------------------------- 1 | # Compiling the C Library 2 | 3 | ## Creating the shared library and header files: 4 | 5 | ```cmd 6 | go build -o lib_udsclient.so -buildmode=c-shared cclient.go 7 | ``` 8 | - this creates the header and shared library files from the cclient.go file. 9 | 10 | ## Creating the the static library and header files: 11 | 12 | ```cmd 13 | go build -o lib_udsclient.a -buildmode=c-archive ./cclient.go 14 | ``` 15 | - this creates the shared library and header files 16 | 17 | ## Usage 18 | 19 | After creating the library and header files as described in the previous steps, 20 | this is how to use the functions generated by cgo 21 | 22 | ```c 23 | char* GetUdsClientVersion() 24 | ``` 25 | This returns the version of the handshake on your local machine/client. 26 | 27 | ```c 28 | char* GetUdsServerVersion() 29 | ``` 30 | This returns the version of the handshake on the server/host. 31 | 32 | ```c 33 | int RequestXskMapFd(char* device) 34 | ``` 35 | This requests an xskmap Fd for a specified device. 36 | 37 | ```c 38 | int RequestBusyPoll(int busyTimeout, int busyBudget, int fd) 39 | ``` 40 | This requests a busy poll for a specific device by using it's fd, and specifying a timeout and a budget. 41 | 42 | ```c 43 | void CleanUpConnection() 44 | ``` 45 | After all the other functions are called, this needs to be called after all the work you need to do is done, 46 | so that the connection to the UDS can be cleaned up. -------------------------------------------------------------------------------- /examples/nad_with_syncer.yaml: -------------------------------------------------------------------------------- 1 | # WARNING: This is an example definition only. Remove all comments before use. 2 | 3 | apiVersion: "k8s.cni.cncf.io/v1" 4 | kind: NetworkAttachmentDefinition 5 | metadata: 6 | name: afxdp-network # Name of this network, pods will request this network by name 7 | annotations: 8 | k8s.v1.cni.cncf.io/resourceName: afxdp/myPool # Needs to match the device plugin pool name / resource type 9 | spec: 10 | config: '{ 11 | "cniVersion": "0.3.0", 12 | "type": "afxdp", # CNI binary, leave as afxdp 13 | "mode": "primary", # CNI mode setting (required) 14 | "logFile": "afxdp-cni.log", # CNI log file location (optional) 15 | "logLevel": "debug", # CNI logging level (optional) 16 | "dpSyncer": true, # Sync with Device Plugin over gRPC MUST BE SET for bpf map pinning 17 | "ipam": { # CNI IPAM plugin and associated config (optional) 18 | "type": "host-local", 19 | "subnet": "192.168.1.0/24", 20 | "rangeStart": "192.168.1.200", 21 | "rangeEnd": "192.168.1.220", 22 | "routes": [ 23 | { "dst": "0.0.0.0/0" } 24 | ], 25 | "gateway": "192.168.1.1" 26 | } 27 | }' 28 | -------------------------------------------------------------------------------- /internal/bpf/xdp-afxdp-redirect/xdp_afxdp_redirect.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 | // clang-format off 16 | #include 17 | #include 18 | #include 19 | // clang-format on 20 | 21 | struct { 22 | __uint(type, BPF_MAP_TYPE_XSKMAP); 23 | __type(key, __u32); 24 | __type(value, __u32); 25 | __uint(max_entries, 64); 26 | __uint(pinning, LIBBPF_PIN_BY_NAME); 27 | } xsks_map SEC(".maps"); 28 | 29 | SEC("xdp") 30 | int xdp_afxdp_redirect(struct xdp_md *ctx) { 31 | int index = ctx->rx_queue_index; 32 | 33 | /* A set entry here means that the correspnding queue_id 34 | * has an active AF_XDP socket bound to it. */ 35 | if (bpf_map_lookup_elem(&xsks_map, &index)) 36 | return bpf_redirect_map(&xsks_map, index, 0); 37 | 38 | return XDP_PASS; 39 | } 40 | 41 | char _license[] SEC("license") = "Dual BSD"; 42 | -------------------------------------------------------------------------------- /test/fuzz/cni/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright(c) 2022 Intel Corporation. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | TEST_NET_NS=fuzznet 19 | 20 | cleanup() { 21 | echo "cleanup" 22 | # delete test netNS, ignore potential "does not exist" 23 | ip netns del $TEST_NET_NS > /dev/null 2>&1 || true 24 | } 25 | trap cleanup EXIT 26 | 27 | echo "installing go-fuzz" 28 | go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest 29 | 30 | echo "building test app" 31 | go-fuzz-build 32 | 33 | echo "creating test netNS" 34 | # create test netNS, ignore potential "already exists" 35 | ip netns add $TEST_NET_NS > /dev/null 2>&1 || true 36 | ip netns exec $TEST_NET_NS mount --bind /proc/$$/ns/net /var/run/netns/$TEST_NET_NS 37 | 38 | echo "running tests" 39 | go-fuzz -bin=./cni-fuzz.zip -workdir ./outputAdd -dumpcover -func FuzzAdd & \ 40 | go-fuzz -bin=./cni-fuzz.zip -workdir ./outputDel -dumpcover -func FuzzDel 41 | -------------------------------------------------------------------------------- /pkg/subfunctions/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 7 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 10 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 12 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 16 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /internal/bpf/log.c: -------------------------------------------------------------------------------- 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 | #include "log.h" 17 | #ifndef CNDP 18 | #include "_cgo_export.h" 19 | #endif 20 | 21 | // 256 is the maximum number of characters for vsnprintf 22 | #define LOG_SIZE 256 23 | 24 | #ifdef CNDP 25 | #define Errorf(msg) printf("%s", msg) 26 | #define Infof(msg) printf("%s", msg) 27 | #define Debugf(msg) printf("%s", msg) 28 | #define Panicf(msg) printf("%s", msg) 29 | #define Warningf(msg) printf("%s", msg) 30 | #endif 31 | 32 | void log_fn(log_level_t level, const char *fmt, ...) { 33 | char msg[LOG_SIZE]; 34 | va_list args; 35 | va_start(args, fmt); 36 | vsnprintf(msg, sizeof(msg), fmt, args); 37 | va_end(args); 38 | 39 | switch (level) { 40 | case LOG_LEVEL_ERR: 41 | Errorf(msg); 42 | break; 43 | case LOG_LEVEL_INFO: 44 | Infof(msg); 45 | break; 46 | case LOG_LEVEL_DEBUG: 47 | Debugf(msg); 48 | break; 49 | case LOG_LEVEL_PANIC: 50 | Panicf(msg); 51 | break; 52 | case LOG_LEVEL_WARNING: 53 | Warningf(msg); 54 | break; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/network-attachment-definition.yaml: -------------------------------------------------------------------------------- 1 | # WARNING: This is an example definition only. Remove all comments before use. 2 | 3 | apiVersion: "k8s.cni.cncf.io/v1" 4 | kind: NetworkAttachmentDefinition 5 | metadata: 6 | name: afxdp-network # Name of this network, pods will request this network by name 7 | annotations: 8 | k8s.v1.cni.cncf.io/resourceName: afxdp/myPool # Needs to match the device plugin pool name / resource type 9 | spec: 10 | config: '{ 11 | "cniVersion": "0.3.0", 12 | "type": "afxdp", # CNI binary, leave as afxdp 13 | "mode": "primary", # CNI mode setting (required) 14 | "logFile": "afxdp-cni.log", # CNI log file location (optional) 15 | "logLevel": "debug", # CNI logging level (optional) 16 | "ethtoolCmds" : ["-X -device- equal 5 start 3", # CNI ethtool filters (optional) 17 | "--config-ntuple -device- flow-type udp4 dst-ip -ip- action" 18 | ], 19 | 20 | "ipam": { # CNI IPAM plugin and associated config (optional) 21 | "type": "host-local", 22 | "subnet": "192.168.1.0/24", 23 | "rangeStart": "192.168.1.200", 24 | "rangeEnd": "192.168.1.220", 25 | "routes": [ 26 | { "dst": "0.0.0.0/0" } 27 | ], 28 | "gateway": "192.168.1.1" 29 | } 30 | }' 31 | -------------------------------------------------------------------------------- /internal/bpf/log.h: -------------------------------------------------------------------------------- 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 | #ifndef _LOG_H_ 17 | #define _LOG_H_ 18 | 19 | #include 20 | #include 21 | 22 | #define foreach_log_level \ 23 | _(0, PANIC, panic) \ 24 | _(1, ERR, err) \ 25 | _(2, WARNING, warn) \ 26 | _(3, INFO, info) \ 27 | _(4, DEBUG, debug) 28 | 29 | typedef enum { 30 | #define _(n, uc, lc) LOG_LEVEL_##uc = n, 31 | foreach_log_level 32 | #undef _ 33 | } log_level_t; 34 | 35 | void log_fn(log_level_t level, const char *fmt, ...); 36 | 37 | #define Log_Panic(fmt, ...) log_fn(LOG_LEVEL_PANIC, fmt, __VA_ARGS__) 38 | #define Log_Error(fmt, ...) log_fn(LOG_LEVEL_ERR, fmt, __VA_ARGS__) 39 | #define Log_Warning(fmt, ...) log_fn(LOG_LEVEL_WARNING, fmt, __VA_ARGS__) 40 | #define Log_Info(fmt, ...) log_fn(LOG_LEVEL_INFO, fmt, __VA_ARGS__) 41 | #define Log_Debug(fmt, ...) log_fn(LOG_LEVEL_DEBUG, fmt, __VA_ARGS__) 42 | 43 | #endif -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/uds/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright(c) 2022 Intel Corporation. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | cleanup() { 19 | echo 20 | echo "*****************************************************" 21 | echo "* Cleanup *" 22 | echo "*****************************************************" 23 | echo 24 | echo "Delete remaining uds sockets" 25 | rm -f /tmp/afxdp/* 26 | } 27 | 28 | build() { 29 | echo 30 | echo "*****************************************************" 31 | echo "* Install and Build Go-Fuzz *" 32 | echo "*****************************************************" 33 | echo 34 | echo "installing go-fuzz" 35 | go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest 36 | echo 37 | echo "building test app" 38 | go-fuzz-build 39 | echo 40 | } 41 | 42 | run() { 43 | echo 44 | echo "*****************************************************" 45 | echo "* Run Fuzz Test *" 46 | echo "*****************************************************" 47 | echo 48 | echo "running tests" 49 | go-fuzz -bin=./uds-fuzz.zip -workdir ./outputUDS -dumpcover -func Fuzz 50 | } 51 | 52 | cleanup 53 | build 54 | run 55 | trap cleanup EXIT 56 | -------------------------------------------------------------------------------- /internal/logformats/logFormats.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 logformats 17 | 18 | import ( 19 | "path" 20 | "runtime" 21 | "strconv" 22 | "strings" 23 | 24 | logging "github.com/sirupsen/logrus" 25 | ) 26 | 27 | /* 28 | Default is the default formatter for our logs 29 | */ 30 | var Default = &logging.TextFormatter{ 31 | FullTimestamp: true, 32 | TimestampFormat: "2006-01-02 15:04:05", 33 | ForceColors: true, 34 | CallerPrettyfier: func(frame *runtime.Frame) (string, string) { return "", "" }, 35 | } 36 | 37 | /* 38 | Debug is the debug formatter for our logs, it prints aditional data to aid with debugging 39 | */ 40 | var Debug = &logging.TextFormatter{ 41 | FullTimestamp: true, 42 | TimestampFormat: "2006-01-02 15:04:05", 43 | ForceColors: true, 44 | CallerPrettyfier: func(frame *runtime.Frame) (string, string) { 45 | s := strings.Split(frame.Function, ".") 46 | funcName := "[" + s[len(s)-1] + "]" 47 | fileName := " [" + path.Base(frame.File) + ":" + strconv.Itoa(frame.Line) + "]" 48 | return funcName, fileName 49 | }, 50 | } 51 | 52 | /* 53 | Fuzz is the formatter used in our fuzz tests 54 | */ 55 | var Fuzz = &logging.TextFormatter{ 56 | DisableColors: true, 57 | FullTimestamp: true, 58 | } 59 | -------------------------------------------------------------------------------- /internal/udsserver/udsserver_fake.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 udsserver 17 | 18 | /* 19 | fakeServer is a fake implementation the Server interface. 20 | */ 21 | type fakeServer struct{} 22 | 23 | /* 24 | fakeServerFactory is a fake implementation the ServerFactory interface. 25 | */ 26 | type fakeServerFactory struct{} 27 | 28 | /* 29 | NewFakeServerFactory returns a fake implementation of the ServerFactory interface. 30 | */ 31 | func NewFakeServerFactory() ServerFactory { 32 | return &fakeServerFactory{} 33 | } 34 | 35 | /* 36 | CreateServer creates, initialises, and returns an implementation of the Server interface. 37 | In this fakeServerFactory it returnss an empty fakeServer implementation and a hardcoded 38 | fake UDS filepath. 39 | */ 40 | func (f *fakeServerFactory) CreateServer(deviceType, user string, timeout int, udsFuzz bool) (Server, string, error) { 41 | return &fakeServer{}, "/tmp/fake-socket.sock", nil 42 | } 43 | 44 | /* 45 | Start is the public facing method for starting a Server. 46 | In this fakeServer it does nothing. 47 | */ 48 | func (s *fakeServer) Start() { 49 | } 50 | 51 | /* 52 | AddDevice appends a netdev and its associated XSK file descriptor to the Servers map of devices. 53 | In this fakeServer it does nothing. 54 | */ 55 | func (s *fakeServer) AddDevice(dev string, fd int) { 56 | } 57 | -------------------------------------------------------------------------------- /internal/dpcnisyncerclient/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2023 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package dpcnisyncerclient 17 | 18 | import ( 19 | "context" 20 | "net" 21 | 22 | "github.com/intel/afxdp-plugins-for-kubernetes/constants" 23 | pb "github.com/intel/afxdp-plugins-for-kubernetes/internal/dpcnisyncer" 24 | logging "github.com/sirupsen/logrus" 25 | "google.golang.org/grpc" 26 | "google.golang.org/grpc/credentials/insecure" 27 | pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" 28 | ) 29 | 30 | const ( 31 | _proto = "unix" 32 | ) 33 | 34 | var ( 35 | sock = pluginapi.DevicePluginPath + constants.Plugins.DevicePlugin.DevicePrefix + "-" + "syncer.sock" 36 | ) 37 | 38 | func DeleteNetDev(name string) error { 39 | ctx := context.Background() 40 | conn, err := grpc.DialContext(ctx, sock, grpc.WithTransportCredentials(insecure.NewCredentials()), 41 | grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { 42 | return (&net.Dialer{}).DialContext(ctx, _proto, addr) 43 | })) 44 | if err != nil { 45 | logging.Errorf("error connecting to Server") 46 | return err 47 | } 48 | defer conn.Close() 49 | 50 | c := pb.NewNetDevClient(conn) 51 | r, err := c.DelNetDev(ctx, &pb.DeleteNetDevReq{Name: name}) 52 | if err != nil || r.Ret == -1 { 53 | logging.Errorf("error deleting netdev resources for netdev %s", name) 54 | return err 55 | } 56 | logging.Infof("Server response:%v", r) 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /internal/bpf/bpfWrapper_fake.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package bpf 18 | 19 | /* 20 | fakeHandler implements the Handler interface. 21 | */ 22 | type fakeHandler struct{} 23 | 24 | /* 25 | NewFakeHandler returns a fake implementation of the Handler interface. 26 | */ 27 | func NewFakeHandler() Handler { 28 | return &fakeHandler{} 29 | } 30 | 31 | /* 32 | LoadBpfSendXskMap is the GoLang wrapper for the C function Load_bpf_send_xsk_map 33 | In this fakeHandler it returns a hardcoded file descriptor. 34 | */ 35 | func (f *fakeHandler) LoadBpfSendXskMap(ifname string) (int, error) { 36 | var fakeFileDescriptor int = 7 37 | return fakeFileDescriptor, nil 38 | } 39 | 40 | /* 41 | LoadAttachBpfXdpPass is the GoLang wrapper for the C function Load_attach_bpf_xdp_pass 42 | In this fakeHandler it does nothing. 43 | */ 44 | func (f *fakeHandler) LoadAttachBpfXdpPass(ifname string) error { 45 | return nil 46 | } 47 | 48 | /* 49 | LoadBpfPinXskMap is the GoLang wrapper for the C function Load_bpf_pin_xsk_map 50 | In this fakeHandler it does nothing. 51 | */ 52 | func (f *fakeHandler) LoadBpfPinXskMap(ifname, pin_path string) error { 53 | return nil 54 | } 55 | 56 | /* 57 | ConfigureBusyPoll is the GoLang wrapper for the C function Configure_busy_poll 58 | In this fakeHandler it does nothing. 59 | */ 60 | func (f *fakeHandler) ConfigureBusyPoll(fd int, busyTimeout int, busyBudget int) error { 61 | return nil 62 | } 63 | 64 | /* 65 | Cleanbpf is the GoLang wrapper for the C function Clean_bpf 66 | In this fakeHandler it does nothing. 67 | */ 68 | func (f *fakeHandler) Cleanbpf(ifname string) error { 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/cclient/cclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/uds" 9 | "github.com/intel/afxdp-plugins-for-kubernetes/pkg/goclient" 10 | ) 11 | 12 | func main() { 13 | // Needed for cgo to generate the .h 14 | } 15 | 16 | var cleaner uds.CleanupFunc 17 | 18 | /* 19 | GetClientVersion is an exported version for c of the goclient GetClientVersion() 20 | */ 21 | //export GetUdsClientVersion 22 | func GetUdsClientVersion() *C.char { 23 | return C.CString(goclient.GetClientVersion()) 24 | } 25 | 26 | /* 27 | ServerVersion is an exported version for c of the goclient GetServerVersion() 28 | */ 29 | //export GetUdsServerVersion 30 | func GetUdsServerVersion() *C.char { 31 | response, function, err := goclient.GetServerVersion() 32 | if err != nil { 33 | fmt.Fprintln(os.Stderr, err.Error()) 34 | function() 35 | return C.CString("-1") 36 | } 37 | 38 | cleaner = function 39 | 40 | return C.CString(response) 41 | } 42 | 43 | /* 44 | GetXskMapFd is an exported version for c of the goclient XskMapFd() 45 | */ 46 | //export RequestXskMapFd 47 | func RequestXskMapFd(device *C.char) (fd C.int) { 48 | if device != nil { 49 | fdVal, function, err := goclient.RequestXSKmapFD(C.GoString(device)) 50 | fd = C.int(fdVal) 51 | if err != nil { 52 | fmt.Fprintln(os.Stderr, err.Error()) 53 | function() 54 | return -1 55 | } 56 | 57 | cleaner = function 58 | return fd 59 | } 60 | 61 | return -1 62 | } 63 | 64 | /* 65 | RequestBusyPoll is an exported version for c of the goclient RequestBusyPoll() 66 | */ 67 | //export RequestBusyPoll 68 | func RequestBusyPoll(busyTimeout, busyBudget, fd C.int) C.int { 69 | timeout, budget, fdInt := int(busyTimeout), int(busyBudget), int(fd) 70 | if timeout > -1 && budget > -1 && fdInt > -1 { 71 | function, err := goclient.RequestBusyPoll(timeout, budget, fdInt) 72 | if err != nil { 73 | fmt.Fprintln(os.Stderr, err.Error()) 74 | function() 75 | return -1 76 | } 77 | cleaner = function 78 | return 0 79 | } 80 | return -1 81 | } 82 | 83 | /* 84 | CleanUpConnection an explicit exported cgo function to cleanup a connection after calling any of the other functions. 85 | Pass in one of the available function names to clean up the connection after use. 86 | */ 87 | //export CleanUpConnection 88 | func CleanUpConnection() { 89 | if cleaner == nil { 90 | fmt.Println("No cleanup function available to call") 91 | } else { 92 | cleaner() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/config/config.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 deviceplugin 17 | 18 | import ( 19 | "io/ioutil" 20 | "os" 21 | 22 | dp "github.com/intel/afxdp-plugins-for-kubernetes/internal/deviceplugin" 23 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/dpcnisyncerserver" 24 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/host" 25 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/networking" 26 | ) 27 | 28 | const ( 29 | tempDirectory = "config/" //temp directory is created upon fuzz.sh execution 30 | udsDirFileMode = os.FileMode(0700) 31 | ) 32 | 33 | var firstRun bool = true 34 | 35 | /* 36 | Fuzz sends fuzzed data into the GetConfig function 37 | The input data is considered: 38 | - uninteresting if is caught by an existing error 39 | - interesting if it does not result in an error, input priority increases for subsequent fuzzing 40 | - discard if it will not unmarshall, so we don't just end up testing the json.Unmarshall function 41 | */ 42 | func Fuzz(data []byte) int { 43 | if firstRun { 44 | firstRun = false 45 | if err := os.MkdirAll(tempDirectory, udsDirFileMode); err != nil { 46 | panic(1) 47 | } 48 | } 49 | 50 | tmpfile, err := ioutil.TempFile(tempDirectory, "config_") 51 | if err != nil { 52 | os.Remove(tmpfile.Name()) 53 | panic(1) 54 | } 55 | defer os.Remove(tmpfile.Name()) 56 | 57 | if _, err := tmpfile.Write(data); err != nil { 58 | os.Remove(tmpfile.Name()) 59 | panic(1) 60 | } 61 | if err := tmpfile.Close(); err != nil { 62 | os.Remove(tmpfile.Name()) 63 | panic(1) 64 | } 65 | 66 | //START THE SYNCER SERVER TODO CHECK BPF MAP 67 | dpCniSyncerServer, err := dpcnisyncerserver.NewSyncerServer() 68 | if err != nil { 69 | panic(1) 70 | } 71 | 72 | _, err = dp.GetPoolConfigs(tmpfile.Name(), networking.NewHandler(), host.NewHandler(), dpCniSyncerServer) 73 | if err != nil { 74 | return 0 75 | } 76 | 77 | return 1 78 | 79 | } 80 | -------------------------------------------------------------------------------- /images/amd64.dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright(c) 2022 Intel Corporation. 2 | # Copyright(c) Red Hat Inc. 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 | FROM golang:1.20@sha256:efe38cb419e2b2012f66d1782d2efe2fd8884c71d9f342581e1697ba9047b5f8 as cnibuilder 16 | COPY . /usr/src/afxdp_k8s_plugins 17 | WORKDIR /usr/src/afxdp_k8s_plugins 18 | RUN apt-get update \ 19 | && apt-get -y install --no-install-recommends libxdp-dev=1.3.1-1 \ 20 | && apt-get -y install -o APT::Keep-Downloaded-Packages=false --no-install-recommends clang=1:14.0-55.7~deb12u1 \ 21 | && apt-get -y install -o APT::Keep-Downloaded-Packages=false --no-install-recommends llvm=1:14.0-55.7~deb12u1 \ 22 | && apt-get -y install -o APT::Keep-Downloaded-Packages=false --no-install-recommends gcc-multilib=4:12.2.0-3 \ 23 | && make buildcni 24 | 25 | FROM golang:1.20-alpine@sha256:ebceb16dc094769b6e2a393d51e0417c19084ba20eb8967fb3f7675c32b45774 as dpbuilder 26 | COPY . /usr/src/afxdp_k8s_plugins 27 | WORKDIR /usr/src/afxdp_k8s_plugins 28 | RUN apk add --no-cache build-base~=0.5-r3 \ 29 | && apk add --no-cache libbsd-dev~=0.11.7 \ 30 | && apk add --no-cache libxdp-dev~=1.2.10-r0 \ 31 | && apk add --no-cache libbpf-dev~=1.0.1-r0 \ 32 | && apk add --no-cache llvm15~=15.0.7-r0 \ 33 | && apk add --no-cache clang15~=15.0.7-r0 \ 34 | && make builddp 35 | 36 | FROM amd64/alpine:3.18@sha256:25fad2a32ad1f6f510e528448ae1ec69a28ef81916a004d3629874104f8a7f70 37 | RUN apk --no-cache -U add iproute2-rdma~=6.3.0-r0 acl~=2.3 \ 38 | && apk add --no-cache xdp-tools~=1.2.10-r0 39 | COPY --from=cnibuilder /usr/src/afxdp_k8s_plugins/bin/afxdp /afxdp/afxdp 40 | COPY --from=dpbuilder /usr/src/afxdp_k8s_plugins/bin/afxdp-dp /afxdp/afxdp-dp 41 | COPY --from=dpbuilder /usr/src/afxdp_k8s_plugins/images/entrypoint.sh /afxdp/entrypoint.sh 42 | COPY --from=dpbuilder /usr/src/afxdp_k8s_plugins/internal/bpf/xdp-pass/xdp_pass.o /afxdp/xdp_pass.o 43 | COPY --from=dpbuilder /usr/src/afxdp_k8s_plugins/internal/bpf/xdp-afxdp-redirect/xdp_afxdp_redirect.o /afxdp/xdp_afxdp_redirect.o 44 | ENTRYPOINT ["/afxdp/entrypoint.sh"] 45 | -------------------------------------------------------------------------------- /internal/uds/udsUnit_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 uds 17 | 18 | import ( 19 | "errors" 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/require" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | func TestCtrlBufHasValue(t *testing.T) { 27 | 28 | testCases := []struct { 29 | name string 30 | data []byte 31 | expReturn bool 32 | }{ 33 | { 34 | name: "first_test_1111", 35 | data: []byte{1, 1, 1, 1}, 36 | expReturn: true, 37 | }, 38 | 39 | { 40 | name: "second_test_0000", 41 | data: []byte{0, 0, 0, 0}, 42 | expReturn: false, 43 | }, 44 | 45 | { 46 | name: "third_test_1010", 47 | data: []byte{1, 0, 1, 0}, 48 | expReturn: true, 49 | }, 50 | } 51 | 52 | for _, tc := range testCases { 53 | t.Run(tc.name, func(t *testing.T) { 54 | 55 | actualReturn := ctrlBufHasValue(tc.data) 56 | 57 | assert.Equal(t, tc.expReturn, actualReturn, "Returned value does not match expected value") 58 | 59 | }) 60 | } 61 | } 62 | 63 | func TestInit(t *testing.T) { 64 | myUDSHandler := NewHandler() 65 | 66 | testCases := []struct { 67 | testName string 68 | socketPath string 69 | protocol string 70 | msgBufSize int 71 | ctlBufSize int 72 | timeout time.Duration 73 | expErr error 74 | uid string 75 | }{ 76 | { 77 | testName: "socket does not exist", 78 | socketPath: "/file/does/not/exist.sock", 79 | protocol: "unixpacket", 80 | msgBufSize: 64, 81 | ctlBufSize: 4, 82 | timeout: 20, 83 | expErr: errors.New("unknown network /file/does/not/exist.sock"), 84 | uid: "4192", 85 | }, 86 | } 87 | for _, tc := range testCases { 88 | t.Run(tc.testName, func(t *testing.T) { 89 | 90 | err := myUDSHandler.Init(tc.socketPath, tc.protocol, tc.msgBufSize, tc.ctlBufSize, tc.timeout, tc.uid) 91 | 92 | if err != nil { 93 | require.Error(t, tc.expErr, err, "Error was expected") 94 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error returned") 95 | } 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/afxdp/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright(c) 2022 Intel Corporation. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | pids=( ) 19 | run_dp="./../../../../bin/afxdp-dp" 20 | 21 | cleanup() { 22 | echo 23 | echo "*****************************************************" 24 | echo "* Cleanup *" 25 | echo "*****************************************************" 26 | echo "Delete Pod" 27 | kubectl delete pod --grace-period 0 --ignore-not-found=true afxdp-fuzz-test &> /dev/null 28 | echo "Delete CNI" 29 | rm -f /opt/cni/bin/afxdp-fuzz &> /dev/null 30 | echo "Delete Network Attachment Definition" 31 | kubectl delete network-attachment-definition --ignore-not-found=true afxdp-fuzz-test &> /dev/null 32 | echo "Delete Docker Image" 33 | docker 2>/dev/null rmi afxdp-fuzz-test || true 34 | echo "Stop Device Plugin on host (if running)" 35 | if [ ${#pids[@]} -eq 0 ]; then 36 | echo "No Device Plugin PID found on host" 37 | else 38 | echo "Found Device Plugin PID. Stopping..." 39 | (( ${#pids[@]} )) && kill "${pids[@]}" 40 | fi 41 | } 42 | 43 | build() { 44 | echo 45 | echo "*****************************************************" 46 | echo "* Build and Install *" 47 | echo "*****************************************************" 48 | echo "***** CNI Install *****" 49 | cp ./../../../../bin/afxdp /opt/cni/bin/afxdp-fuzz 50 | echo "***** Network Attachment Definition *****" 51 | kubectl create -f ./nad.yaml 52 | } 53 | 54 | run() { 55 | echo 56 | echo "*****************************************************" 57 | echo "* Run Device Plugin *" 58 | echo "*****************************************************" 59 | $run_dp & pids+=( "$!" ) #run the DP and save the PID 60 | sleep 10 61 | 62 | echo 63 | echo "*****************************************************" 64 | echo "* Run Pod: 1 container, 1 device *" 65 | echo "*****************************************************" 66 | echo "CNDP fuzz testing will be executed after pod is created..." 67 | kubectl create -f afxdp-fuzz-pod.yaml 68 | } 69 | 70 | cleanup 71 | build 72 | run 73 | trap cleanup EXIT 74 | -------------------------------------------------------------------------------- /test/fuzz/cni/cni.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 cni 17 | 18 | import ( 19 | "encoding/json" 20 | "fmt" 21 | "github.com/containernetworking/cni/pkg/skel" 22 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/cni" 23 | "k8s.io/apimachinery/pkg/util/uuid" 24 | ) 25 | 26 | const ( 27 | fuzzNs = "fuzznet" 28 | interesting = 1 29 | uninteresting = 0 30 | discard = -1 31 | ) 32 | 33 | /* 34 | FuzzAdd sends fuzzed data into the cni CmdAdd function 35 | The input data is considered: 36 | - uninteresting if is caught by an existing error 37 | - interesting if it does not result in an error, input priority increases for subsequent fuzzing 38 | - discard if it will not unmarshall, so we don't just end up testing the json.Unmarshall function 39 | */ 40 | func FuzzAdd(data []byte) int { 41 | tmp := &cni.NetConfig{} 42 | if err := json.Unmarshal(data, tmp); err != nil { 43 | return discard 44 | } 45 | 46 | fakeID := uuid.NewUUID() 47 | a := &skel.CmdArgs{ 48 | StdinData: data, // data prepared by go-fuzz 49 | Netns: "/var/run/netns/" + fuzzNs, 50 | ContainerID: string(fakeID), 51 | IfName: fmt.Sprintf("eth%v", int(fakeID[7])), 52 | } 53 | 54 | if err := cni.CmdAdd(a); err != nil { 55 | return uninteresting 56 | } 57 | return interesting 58 | } 59 | 60 | /* 61 | FuzzDel sends fuzzed data into the cni CmdDel function 62 | The input data is considered: 63 | - uninteresting if is caught by an exesting error 64 | - interesting if it does not result in an error, input priority increases for subsequent fuzzing 65 | - discard if it will not unmarshall, so we don't just end up testing the json.Unmarshall function 66 | */ 67 | func FuzzDel(data []byte) int { 68 | tmp := &cni.NetConfig{} 69 | if err := json.Unmarshal(data, tmp); err != nil { 70 | return discard 71 | } 72 | 73 | fakeID := uuid.NewUUID() 74 | a := &skel.CmdArgs{ 75 | StdinData: data, // data prepared by go-fuzz 76 | Netns: "/var/run/netns/" + fuzzNs, 77 | ContainerID: string(fakeID), 78 | IfName: fmt.Sprintf("eth%v", int(fakeID[7])), 79 | } 80 | 81 | if err := cni.CmdDel(a); err != nil { 82 | return uninteresting 83 | } 84 | return interesting 85 | } 86 | -------------------------------------------------------------------------------- /internal/resourcesapi/resources_api_fake.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 resourcesapi 17 | 18 | import ( 19 | api "k8s.io/kubelet/pkg/apis/podresources/v1" 20 | ) 21 | 22 | /* 23 | FakeHandler interface extends the Handler interface to provide additional testing methods. 24 | */ 25 | type FakeHandler interface { 26 | Handler 27 | CreateFakePod(podName string, namespace string, resourceName string, deviceIds []string) 28 | } 29 | 30 | /* 31 | fakeHandler implements the FakeHandler interface. 32 | */ 33 | type fakeHandler struct { 34 | podName string 35 | namespace string 36 | resourceName string 37 | deviceIds []string 38 | } 39 | 40 | /* 41 | NewFakeHandler returns an implementation of the FakeHandler interface. 42 | */ 43 | func NewFakeHandler() FakeHandler { 44 | return &fakeHandler{} 45 | } 46 | 47 | /* 48 | GetPodResources returns a map of pods and associated devices. 49 | In this FakeHandler, it returns a map containing just a single pod for testing against. 50 | This pod does not come from the pod resources API, but instead is configurable through the 51 | CreateFakePod function to give a predetermined response. 52 | */ 53 | func (f *fakeHandler) GetPodResources() (map[string]api.PodResources, error) { 54 | fakePod := api.PodResources{ 55 | Name: f.podName, 56 | Namespace: f.namespace, 57 | Containers: []*api.ContainerResources{ 58 | { 59 | Name: "container-01", 60 | Devices: []*api.ContainerDevices{ 61 | { 62 | ResourceName: f.resourceName, 63 | DeviceIds: f.deviceIds, 64 | }, 65 | }, 66 | }, 67 | }, 68 | } 69 | 70 | podResourceMap := make(map[string]api.PodResources) 71 | podResourceMap[f.podName] = fakePod 72 | 73 | return podResourceMap, nil 74 | } 75 | 76 | /* 77 | CreateFakePod allows us to configure our own fake pod and its associated devices. 78 | This pods data is what is returned when GetPodResources is called. 79 | Tweaking this pod allows us to test our code against different pods and scenarios. 80 | */ 81 | func (f *fakeHandler) CreateFakePod(podName string, namespace string, resourceName string, deviceIds []string) { 82 | f.podName = podName 83 | f.namespace = namespace 84 | f.resourceName = resourceName 85 | f.deviceIds = deviceIds 86 | } 87 | -------------------------------------------------------------------------------- /internal/tools/tools.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 tools 17 | 18 | import ( 19 | "encoding/json" 20 | "fmt" 21 | "os" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | /* 27 | ArrayContains returns true if str is an element of array. 28 | */ 29 | func ArrayContains(array []string, str string) bool { 30 | for _, s := range array { 31 | if s == str { 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | 38 | /* 39 | ArrayContainsPrefix returns true if str is prefixed with any element of array. 40 | */ 41 | func ArrayContainsPrefix(array []string, str string) bool { 42 | for _, s := range array { 43 | if strings.HasPrefix(str, s) { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | /* 51 | FilePathExists returns true if path exists, false if non-existent. 52 | */ 53 | func FilePathExists(path string) (bool, error) { 54 | _, err := os.Stat(path) 55 | if err == nil { 56 | return true, nil 57 | } 58 | if os.IsNotExist(err) { 59 | return false, nil 60 | } 61 | return false, err 62 | } 63 | 64 | /* 65 | RemoveFromArray returns array without the element rem if it is present. 66 | */ 67 | func RemoveFromArray(array []string, rem string) []string { 68 | for i, elm := range array { 69 | if elm == rem { 70 | return append(array[:i], array[i+1:]...) 71 | } 72 | } 73 | return array 74 | } 75 | 76 | /* 77 | PrettyString formats v as a string for logging purposes. 78 | */ 79 | func PrettyString(v interface{}) (string, error) { 80 | b, err := json.MarshalIndent(v, "", " ") 81 | if err != nil { 82 | return "", err 83 | } 84 | return fmt.Sprint(string(b)), nil 85 | } 86 | 87 | /* 88 | KernelVersionInt takes a kernel version as a string and returns the integer value 89 | */ 90 | func KernelVersionInt(version string) (int64, error) { // example "5.4.0-89-generic" 91 | stripped := strings.Replace(version, "+", "", -1) // "5.40-89-generic" 92 | stripped = strings.Split(stripped, "-")[0] // "5.4.0" 93 | split := strings.Split(stripped, ".") // [5 4 0] 94 | 95 | padded := "" 96 | for _, val := range split { // 000500040000 97 | padded += fmt.Sprintf("%04s", val) 98 | } 99 | 100 | value, err := strconv.ParseInt(padded, 10, 64) // 500040000 101 | if err != nil { 102 | return -1, err 103 | } 104 | 105 | return value, nil 106 | } 107 | -------------------------------------------------------------------------------- /internal/networking/ethtool.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 networking 17 | 18 | import ( 19 | logging "github.com/sirupsen/logrus" 20 | "os/exec" 21 | "strings" 22 | ) 23 | 24 | var ethtool = "ethtool" 25 | 26 | /* 27 | SetEthtool applies ethtool filters on the physical device during cmdAdd(). 28 | Ethtool filters are set via the DP config.json file. 29 | */ 30 | func (r *handler) SetEthtool(ethtoolFilters []string, interfaceName string, ipAddr string) error { 31 | fd := "on" 32 | err := flowDirector(interfaceName, fd) 33 | if err != nil { 34 | logging.Errorf("Failed to enable flow director: %s", err.Error()) 35 | return err 36 | } 37 | for _, ethtoolFilter := range ethtoolFilters { 38 | ethtoolFilter = strings.Replace(ethtoolFilter, "-device-", interfaceName, -1) 39 | 40 | ethtoolFilter = strings.Replace(ethtoolFilter, "-ip-", ipAddr, -1) 41 | 42 | cmd := exec.Command(ethtool, strings.Split(ethtoolFilter, " ")...) 43 | stdout, err := cmd.CombinedOutput() 44 | if err != nil { 45 | logging.Errorf("Error setting ethtool filter [%s]: %s", ethtoolFilter, string(stdout)) 46 | return err 47 | } 48 | 49 | logging.Debugf("Ethtool filters [%s] successfully executed", ethtoolFilter) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | /* 56 | DeleteEthtool sets the default queue size ethtool filter. 57 | It also removes perfect-flow ethtool filter entries during cmdDel() 58 | */ 59 | func (r *handler) DeleteEthtool(interfaceName string) error { 60 | defaultArg := "-X" 61 | fd := "off" 62 | 63 | ethtoolFilter := []string{defaultArg, interfaceName, "default"} 64 | cmd := exec.Command(ethtool, ethtoolFilter...) 65 | stdout, err := cmd.CombinedOutput() 66 | if err != nil { 67 | logging.Errorf("Error setting default ethtool queue size [%s]: %v", ethtoolFilter, string(stdout)) 68 | return err 69 | } 70 | 71 | err = flowDirector(interfaceName, fd) 72 | if err != nil { 73 | logging.Errorf("Error removing perfect flow entries: %v", err.Error()) 74 | return err 75 | } 76 | 77 | logging.Debugf("Ethtool filters removed on device: %s", interfaceName) 78 | 79 | return nil 80 | } 81 | 82 | /* 83 | flowDirector enables and disables the Ethernet Flow Director. It must be enabled 84 | for filter flow entries. Disabling, enables entries to be removed from device. 85 | */ 86 | func flowDirector(interfaceName string, fdStatus string) error { 87 | final := exec.Command(ethtool, "--features", interfaceName, "ntuple", fdStatus) 88 | _, err := final.CombinedOutput() 89 | if err != nil { 90 | return err 91 | } 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/networking/veth.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 networking 17 | 18 | import ( 19 | "net" 20 | "syscall" 21 | 22 | "github.com/pkg/errors" 23 | logging "github.com/sirupsen/logrus" 24 | "github.com/vishvananda/netlink" 25 | ) 26 | 27 | func CreateVeth(name, PeerName string) (*netlink.Veth, error) { 28 | 29 | v := &netlink.Veth{ 30 | LinkAttrs: netlink.LinkAttrs{ 31 | Name: name, 32 | Flags: net.FlagUp, 33 | }, 34 | PeerName: PeerName, 35 | } 36 | 37 | err := netlink.LinkAdd(v) 38 | if errors.Is(err, syscall.EEXIST) { 39 | logging.Infof("veth interface already exists. It will be recreated") 40 | 41 | // Delete the existing interface and re-create it. 42 | if err = netlink.LinkDel(v); err != nil { 43 | return nil, errors.Wrap(err, "failed to delete the existing veth interface") 44 | } 45 | 46 | if err = netlink.LinkAdd(v); err != nil { 47 | return nil, errors.Wrap(err, "failed to re-create the the veth interface") 48 | } 49 | } 50 | 51 | if err != nil { 52 | return nil, errors.Wrap(err, "failed to create the the veth interface") 53 | } 54 | 55 | logging.Infof("Successfully created veth pair %s %s", v.Name, v.PeerName) 56 | 57 | return v, nil 58 | } 59 | 60 | func DeleteVeth(v *netlink.Veth) error { 61 | _, err := netlink.LinkByName(v.Attrs().Name) 62 | if err != nil { 63 | return errors.Wrap(err, "failed to find veth interface to delete") 64 | } 65 | 66 | return netlink.LinkDel(v) 67 | } 68 | 69 | func GetVethByName(n string) (*netlink.Veth, error) { 70 | link, err := netlink.LinkByName(n) 71 | if err != nil { 72 | return nil, errors.Wrapf(err, "Didn't find the veth %s", n) 73 | } 74 | veth, ok := link.(*netlink.Veth) 75 | if !ok { 76 | return nil, errors.Wrapf(err, "Interface %s is not a veth", n) 77 | } 78 | return veth, nil 79 | 80 | } 81 | 82 | func GetPeer(v *netlink.Veth) (*netlink.Link, error) { 83 | p, err := netlink.LinkByName(v.PeerName) 84 | if err != nil { 85 | return nil, errors.Wrapf(err, "failed to find veth peer %s", v.PeerName) 86 | } 87 | 88 | return &p, nil 89 | } 90 | 91 | func CheckVethExists(name string) (bool, error) { 92 | _, err := netlink.LinkByName(name) 93 | if err != nil { 94 | return false, errors.Wrapf(err, "failed to find veth %s", name) 95 | } 96 | 97 | return true, nil 98 | } 99 | 100 | func SetVethUp(l *netlink.Link) error { 101 | err := netlink.LinkSetUp(*l) 102 | if err != nil { 103 | return errors.Wrap(err, "failed to set the veth to an up state") 104 | } 105 | 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /internal/host/host_fake.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 host 17 | 18 | /* 19 | FakeHandler interface extends the Handler interface to provide additional testing methods. 20 | */ 21 | type FakeHandler interface { 22 | Handler 23 | SetKernalVersion(version string) 24 | SetAllowsUnprivilegedBpf(allowed bool) 25 | } 26 | 27 | /* 28 | fakeHandler implements the FakeHandler interface. 29 | */ 30 | type fakeHandler struct{} 31 | 32 | var ( 33 | kernelVersion string 34 | privilegedBpfAllowed bool 35 | ) 36 | 37 | /* 38 | NewFakeHandler returns an implementation of the FakeHandler interface. 39 | */ 40 | func NewFakeHandler() FakeHandler { 41 | return &fakeHandler{} 42 | } 43 | 44 | /* 45 | KernelVersion checks the host kernel version and returns it as a string. 46 | In this FakeHandler it returns a dummy version for testing purposes. 47 | */ 48 | func (r *fakeHandler) KernelVersion() (string, error) { 49 | return kernelVersion, nil 50 | } 51 | 52 | func (r *fakeHandler) SetKernalVersion(version string) { 53 | kernelVersion = version 54 | } 55 | 56 | /* 57 | HasEthtool checks if the host has ethtool installed and returns a boolean. 58 | In this FakeHandler it returns a dummy version for testing purposes. 59 | */ 60 | func (r *fakeHandler) HasEthtool() (bool, string, error) { 61 | return true, "ethtool version 5.4", nil 62 | } 63 | 64 | //setter 65 | 66 | /* 67 | HasLibbpf checks if the host has libbpf installed and returns a boolean. 68 | In this FakeHandler it returns a dummy value. 69 | */ 70 | func (r *fakeHandler) HasLibxdp() (bool, []string, error) { 71 | return true, nil, nil 72 | } 73 | 74 | //set setter as SetHasLibBpf 75 | 76 | /* 77 | AllowsUnprivilegedBpf checks if the host allows unpriviliged bpf calls and 78 | returns a boolean. In this FakeHandler it returns a dummy value. 79 | */ 80 | func (r *fakeHandler) AllowsUnprivilegedBpf() (bool, error) { 81 | return privilegedBpfAllowed, nil 82 | } 83 | 84 | func (r *fakeHandler) SetAllowsUnprivilegedBpf(allowed bool) { 85 | privilegedBpfAllowed = allowed 86 | } 87 | 88 | /* 89 | HasDevlink checks if the host has devlink installed and returns a boolean. 90 | In this FakeHandler it returns a dummy value. 91 | */ 92 | func (r *fakeHandler) HasDevlink() (bool, string, error) { 93 | return true, "devlink utility, iproute2-ss200127", nil 94 | } 95 | 96 | /* 97 | Hostname is a wrapper function for unit testing that calls os.Hostname. 98 | */ 99 | func (r *fakeHandler) Hostname() (string, error) { 100 | return "k8sNode1", nil 101 | } 102 | 103 | //set setter for setDevLink 104 | -------------------------------------------------------------------------------- /.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: "Checkout code" 35 | uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard. 69 | - name: "Upload to code-scanning" 70 | uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27 71 | with: 72 | sarif_file: results.sarif 73 | -------------------------------------------------------------------------------- /test/fuzz/deviceplugin/uds/uds.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 uds 17 | 18 | import ( 19 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/logformats" 20 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/uds" 21 | logging "github.com/sirupsen/logrus" 22 | "os" 23 | "time" 24 | ) 25 | 26 | const ( 27 | udsMsgBufSize = 64 28 | udsCtlBufSize = 4 29 | udsProtocol = "unixpacket" // "unix"=SOCK_STREAM, "unixdomain"=SOCK_DGRAM, "unixpacket"=SOCK_SEQPACKET 30 | udsIdleTimeout = 10 * time.Second 31 | interesting = 1 32 | uninteresting = 0 33 | discard = -1 34 | 35 | logLevel = "error" 36 | udsDirFileMode = os.FileMode(0700) // drwx------ 37 | ) 38 | 39 | var ch = make(chan string) 40 | 41 | /* 42 | Fuzz seeds fuzzed data to UDS write function. 43 | The input data is considered: 44 | - uninteresting if is caught by an existing error 45 | - interesting if it does not result in an error, input priority increases for subsequent fuzzing 46 | */ 47 | func Fuzz(data []byte) int { 48 | if len(data) == 0 { 49 | return discard 50 | } 51 | 52 | fp, _ := os.OpenFile("./fuzz_"+logLevel+".log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 53 | logging.SetOutput(fp) 54 | level, _ := logging.ParseLevel(logLevel) 55 | logging.SetLevel(level) 56 | logging.SetFormatter(logformats.Fuzz) 57 | 58 | udsPath, _ := uds.GenerateRandomSocketName("/tmp/afxdp/", udsDirFileMode) 59 | go reader(udsPath, data) 60 | time.Sleep(10 * time.Millisecond) 61 | 62 | uds := uds.NewHandler() 63 | err := uds.Init(udsPath, udsProtocol, udsMsgBufSize, udsCtlBufSize, udsIdleTimeout, "") 64 | if err != nil { 65 | logging.Errorf("Error Initialising UDS: %v", err) 66 | } 67 | cleanup, _ := uds.Dial() 68 | defer cleanup() 69 | 70 | err = uds.Write(string(data), -1) 71 | if err != nil { 72 | logging.Errorf("Connection write error: %v", err) 73 | } 74 | 75 | returned := <-ch 76 | logging.Infof("Wrote: %s", string(data)) 77 | logging.Infof("Read: %s", returned) 78 | if returned != string(data) { 79 | return interesting 80 | } 81 | 82 | return uninteresting 83 | 84 | } 85 | 86 | func reader(udsPath string, data []byte) { 87 | uds := uds.NewHandler() 88 | err := uds.Init(udsPath, udsProtocol, udsMsgBufSize, udsCtlBufSize, udsIdleTimeout, "") 89 | if err != nil { 90 | logging.Errorf("Error Initialising UDS: %v", err) 91 | } 92 | cleanup, _ := uds.Listen() 93 | defer cleanup() 94 | msg, _, err := uds.Read() 95 | if err != nil { 96 | logging.Errorf("Data at time of error: %s", string(data)) 97 | } 98 | ch <- msg 99 | } 100 | -------------------------------------------------------------------------------- /internal/uds/uds_fake.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 uds 17 | 18 | import "time" 19 | 20 | /* 21 | FakeHandler interface extends the Handler interface to provide additional testing methods. 22 | */ 23 | type FakeHandler interface { 24 | Handler 25 | SetRequests(requests map[int]string) 26 | GetResponses() map[int]string 27 | } 28 | 29 | /* 30 | fakeHandler implements the Handler interface. 31 | */ 32 | type fakeHandler struct { 33 | counter int 34 | fakeRequests map[int]string 35 | actualResponses map[int]string 36 | } 37 | 38 | /* 39 | NewFakeHandler returns a fake implementation of the Handler interface. 40 | */ 41 | func NewFakeHandler() FakeHandler { 42 | return &fakeHandler{} 43 | } 44 | 45 | /* 46 | Init should initialises the Unix domain socket. 47 | In this fakeHandler it resets some counters and inits a map for recording calls to the Write() function. 48 | */ 49 | func (f *fakeHandler) Init(socketPath string, protocol string, msgbufSize int, ctlBufSize int, timeout time.Duration, uid string) error { 50 | f.actualResponses = make(map[int]string) 51 | f.counter = 0 52 | return nil 53 | } 54 | 55 | /* 56 | Listen listens for and accepts new connections. 57 | In this fakeHandler it does nothing. 58 | */ 59 | func (f *fakeHandler) Listen() (CleanupFunc, error) { 60 | return func() {}, nil 61 | } 62 | 63 | /* 64 | Dial creates a new connection. 65 | In this fakeHandler it does nothing. 66 | */ 67 | func (f *fakeHandler) Dial() (CleanupFunc, error) { 68 | return func() {}, nil 69 | } 70 | 71 | /* 72 | Read should read the incoming message from the UDS. 73 | In this fakeHandler it will sequentially return a set of predetermined strings. 74 | */ 75 | func (f *fakeHandler) Read() (string, int, error) { 76 | request := f.fakeRequests[f.counter] 77 | return request, 0, nil 78 | } 79 | 80 | /* 81 | Write should write a string to the UDS. 82 | In this fakeHandler, the string is stored in a map so we can later compare each response to each request. 83 | */ 84 | func (f *fakeHandler) Write(response string, fd int) error { 85 | f.actualResponses[f.counter] = response 86 | f.counter = f.counter + 1 87 | return nil 88 | } 89 | 90 | /* 91 | SetRequests takes a map of strings. These strings will be sequentially returned 92 | each time the Read function is called. This allows us to build a list of fake 93 | requests we want to make over the UDS. 94 | */ 95 | func (f *fakeHandler) SetRequests(requests map[int]string) { 96 | f.fakeRequests = requests 97 | f.counter = 0 98 | } 99 | 100 | /* 101 | GetResponses returns the list of responses that were made via the Write function. 102 | */ 103 | func (f *fakeHandler) GetResponses() map[int]string { 104 | return f.actualResponses 105 | } 106 | -------------------------------------------------------------------------------- /deployments/daemonset-pinning.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: afxdp-dp-config 5 | namespace: kube-system 6 | data: 7 | config.json: | 8 | { 9 | "logLevel":"debug", 10 | "logFile":"afxdp-dp.log", 11 | "pools":[ 12 | { 13 | "name":"myPool", 14 | "mode":"primary", 15 | "drivers":[ 16 | { 17 | "name":"i40e" 18 | }, 19 | { 20 | "name":"ice" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | --- 27 | apiVersion: v1 28 | kind: ServiceAccount 29 | metadata: 30 | name: afxdp-device-plugin 31 | namespace: kube-system 32 | --- 33 | apiVersion: apps/v1 34 | kind: DaemonSet 35 | metadata: 36 | name: kube-afxdp-device-plugin 37 | namespace: kube-system 38 | labels: 39 | tier: node 40 | app: afxdp 41 | spec: 42 | selector: 43 | matchLabels: 44 | name: afxdp-device-plugin 45 | template: 46 | metadata: 47 | labels: 48 | name: afxdp-device-plugin 49 | tier: node 50 | app: afxdp 51 | spec: 52 | hostNetwork: true 53 | nodeSelector: 54 | kubernetes.io/arch: amd64 55 | tolerations: 56 | - key: node-role.kubernetes.io/master 57 | operator: Exists 58 | effect: NoSchedule 59 | serviceAccountName: afxdp-device-plugin 60 | containers: 61 | - name: kube-afxdp 62 | image: afxdp-device-plugin:latest 63 | imagePullPolicy: IfNotPresent 64 | securityContext: 65 | privileged: true 66 | resources: 67 | requests: 68 | cpu: "250m" 69 | memory: "40Mi" 70 | limits: 71 | cpu: "1" 72 | memory: "200Mi" 73 | volumeMounts: 74 | - name: unixsock 75 | mountPath: /tmp/afxdp_dp/ 76 | - name: bpfmappinning 77 | mountPath: /var/run/afxdp_dp/ 78 | mountPropagation: Bidirectional 79 | - name: devicesock 80 | mountPath: /var/lib/kubelet/device-plugins/ 81 | - name: resources 82 | mountPath: /var/lib/kubelet/pod-resources/ 83 | - name: config-volume 84 | mountPath: /afxdp/config 85 | - name: log 86 | mountPath: /var/log/afxdp-k8s-plugins/ 87 | - name: cnibin 88 | mountPath: /opt/cni/bin/ 89 | volumes: 90 | - name: unixsock 91 | hostPath: 92 | path: /tmp/afxdp_dp/ 93 | - name: bpfmappinning 94 | hostPath: 95 | path: /var/run/afxdp_dp/ 96 | - name: devicesock 97 | hostPath: 98 | path: /var/lib/kubelet/device-plugins/ 99 | - name: resources 100 | hostPath: 101 | path: /var/lib/kubelet/pod-resources/ 102 | - name: config-volume 103 | configMap: 104 | name: afxdp-dp-config 105 | items: 106 | - key: config.json 107 | path: config.json 108 | - name: log 109 | hostPath: 110 | path: /var/log/afxdp-k8s-plugins/ 111 | - name: cnibin 112 | hostPath: 113 | path: /opt/cni/bin/ 114 | -------------------------------------------------------------------------------- /deployments/daemonset-kind.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: afxdp-dp-config 5 | namespace: kube-system 6 | data: 7 | config.json: | 8 | { 9 | "kindCluster": true, 10 | "logLevel":"debug", 11 | "logFile":"afxdp-dp.log", 12 | "pools":[ 13 | { 14 | "name":"myPool", 15 | "mode":"primary", 16 | "UdsServerDisable": true, 17 | "BpfMapPinningEnable": true, 18 | "drivers":[ 19 | { 20 | "name":"veth" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | --- 27 | apiVersion: v1 28 | kind: ServiceAccount 29 | metadata: 30 | name: afxdp-device-plugin 31 | namespace: kube-system 32 | --- 33 | apiVersion: apps/v1 34 | kind: DaemonSet 35 | metadata: 36 | name: kube-afxdp-device-plugin 37 | namespace: kube-system 38 | labels: 39 | tier: node 40 | app: afxdp 41 | spec: 42 | selector: 43 | matchLabels: 44 | name: afxdp-device-plugin 45 | template: 46 | metadata: 47 | labels: 48 | name: afxdp-device-plugin 49 | tier: node 50 | app: afxdp 51 | spec: 52 | hostNetwork: true 53 | nodeSelector: 54 | kubernetes.io/arch: amd64 55 | tolerations: 56 | - key: node-role.kubernetes.io/master 57 | operator: Exists 58 | effect: NoSchedule 59 | serviceAccountName: afxdp-device-plugin 60 | containers: 61 | - name: kube-afxdp 62 | image: afxdp-device-plugin:latest 63 | imagePullPolicy: IfNotPresent 64 | securityContext: 65 | privileged: true 66 | resources: 67 | requests: 68 | cpu: "250m" 69 | memory: "40Mi" 70 | limits: 71 | cpu: "1" 72 | memory: "200Mi" 73 | volumeMounts: 74 | - name: unixsock 75 | mountPath: /tmp/afxdp_dp/ 76 | - name: bpfmappinning 77 | mountPath: /var/run/afxdp_dp/ 78 | mountPropagation: Bidirectional 79 | - name: devicesock 80 | mountPath: /var/lib/kubelet/device-plugins/ 81 | - name: resources 82 | mountPath: /var/lib/kubelet/pod-resources/ 83 | - name: config-volume 84 | mountPath: /afxdp/config 85 | - name: log 86 | mountPath: /var/log/afxdp-k8s-plugins/ 87 | - name: cnibin 88 | mountPath: /opt/cni/bin/ 89 | volumes: 90 | - name: unixsock 91 | hostPath: 92 | path: /tmp/afxdp_dp/ 93 | - name: bpfmappinning 94 | hostPath: 95 | path: /var/run/afxdp_dp/ 96 | - name: devicesock 97 | hostPath: 98 | path: /var/lib/kubelet/device-plugins/ 99 | - name: resources 100 | hostPath: 101 | path: /var/lib/kubelet/pod-resources/ 102 | - name: config-volume 103 | configMap: 104 | name: afxdp-dp-config 105 | items: 106 | - key: config.json 107 | path: config.json 108 | - name: log 109 | hostPath: 110 | path: /var/log/afxdp-k8s-plugins/ 111 | - name: cnibin 112 | hostPath: 113 | path: /opt/cni/bin/ 114 | -------------------------------------------------------------------------------- /internal/resourcesapi/resources_api.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 resourcesapi 17 | 18 | import ( 19 | logging "github.com/sirupsen/logrus" 20 | "golang.org/x/net/context" 21 | "google.golang.org/grpc" 22 | "google.golang.org/grpc/credentials/insecure" 23 | api "k8s.io/kubelet/pkg/apis/podresources/v1" 24 | "net" 25 | "time" 26 | ) 27 | 28 | const ( 29 | podResSockDir = "/var/lib/kubelet/pod-resources" 30 | podResSockPath = podResSockDir + "/kubelet.sock" 31 | grpcTimeout = 5 * time.Second 32 | ) 33 | 34 | /* 35 | Handler is the device plugins interface to the K8s pod resources API. 36 | The interface exists for testing purposes, allowing unit tests to test 37 | against a fake API. 38 | */ 39 | type Handler interface { 40 | GetPodResources() (map[string]api.PodResources, error) 41 | } 42 | 43 | /* 44 | handler implements the Handler interface. 45 | */ 46 | type handler struct{} 47 | 48 | /* 49 | NewHandler returns an implementation of the Handler interface. 50 | */ 51 | func NewHandler() Handler { 52 | return &handler{} 53 | } 54 | 55 | /* 56 | GetPodResources calls the pod resources api and returns a map of pods and associated devices 57 | */ 58 | func (r *handler) GetPodResources() (map[string]api.PodResources, error) { 59 | podResourceMap := make(map[string]api.PodResources) 60 | 61 | resp, err := getPodResources(podResSockPath) 62 | if err != nil { 63 | logging.Errorf("Error Getting pod resources: %v", err) 64 | return podResourceMap, err 65 | } 66 | 67 | for _, pod := range resp.GetPodResources() { 68 | podResourceMap[pod.GetName()] = *pod 69 | } 70 | 71 | return podResourceMap, nil 72 | } 73 | 74 | func getPodResources(socket string) (*api.ListPodResourcesResponse, error) { 75 | ctx, cancel := context.WithTimeout(context.Background(), grpcTimeout) 76 | defer cancel() 77 | 78 | logging.Debugf("Opening Pod Resource API connection") 79 | conn, err := grpc.DialContext(ctx, socket, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), 80 | grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { 81 | return (&net.Dialer{}).DialContext(ctx, "unix", addr) 82 | }), 83 | ) 84 | if err != nil { 85 | logging.Errorf("Error connecting to Pod Resource API: %v", err) 86 | conn.Close() 87 | return nil, err 88 | } 89 | defer func() { 90 | logging.Debugf("Closing Pod Resource API connection") 91 | conn.Close() 92 | }() 93 | 94 | logging.Debugf("Requesting pod resource list") 95 | client := api.NewPodResourcesListerClient(conn) 96 | 97 | resp, err := client.List(ctx, &api.ListPodResourcesRequest{}) 98 | if err != nil { 99 | logging.Errorf("Error getting Pod Resource list: %v", err) 100 | return nil, err 101 | } 102 | 103 | return resp, nil 104 | } 105 | -------------------------------------------------------------------------------- /test/e2e/daemonset.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: afxdp-dp-config 5 | namespace: kube-system 6 | data: 7 | config.json: | 8 | { 9 | "logLevel":"debug", 10 | "logFile":"afxdp-dp-e2e.log", 11 | "pools":[ 12 | { 13 | "name":"e2e", 14 | "mode":"primary", 15 | "drivers":[ 16 | { 17 | "name":"i40e" 18 | }, 19 | { 20 | "name":"ice" 21 | } 22 | ], 23 | "uid":1500 24 | } 25 | ] 26 | } 27 | --- 28 | apiVersion: v1 29 | kind: ServiceAccount 30 | metadata: 31 | name: afxdp-device-plugin 32 | namespace: kube-system 33 | --- 34 | apiVersion: apps/v1 35 | kind: DaemonSet 36 | metadata: 37 | name: kube-afxdp-device-plugin-e2e 38 | namespace: kube-system 39 | labels: 40 | tier: node 41 | app: afxdp 42 | spec: 43 | selector: 44 | matchLabels: 45 | name: afxdp-device-plugin 46 | template: 47 | metadata: 48 | labels: 49 | name: afxdp-device-plugin 50 | tier: node 51 | app: afxdp 52 | spec: 53 | hostNetwork: true 54 | nodeSelector: 55 | kubernetes.io/arch: amd64 56 | tolerations: 57 | - key: node-role.kubernetes.io/master 58 | operator: Exists 59 | effect: NoSchedule 60 | serviceAccountName: afxdp-device-plugin 61 | containers: 62 | - name: kube-afxdp 63 | image: afxdp-device-plugin:latest 64 | imagePullPolicy: Never 65 | securityContext: 66 | capabilities: 67 | drop: 68 | - all 69 | add: 70 | - SYS_ADMIN 71 | - NET_ADMIN 72 | resources: 73 | requests: 74 | cpu: "250m" 75 | memory: "40Mi" 76 | limits: 77 | cpu: "1" 78 | memory: "200Mi" 79 | volumeMounts: 80 | - name: unixsock 81 | mountPath: /tmp/afxdp_dp/ 82 | - name: devicesock 83 | mountPath: /var/lib/kubelet/device-plugins/ 84 | - name: resources 85 | mountPath: /var/lib/kubelet/pod-resources/ 86 | - name: config-volume 87 | mountPath: /afxdp/config 88 | - name: log 89 | mountPath: /var/log/afxdp-k8s-plugins/ 90 | - name: cnibin 91 | mountPath: /opt/cni/bin/ 92 | volumes: 93 | - name: unixsock 94 | hostPath: 95 | path: /tmp/afxdp_dp/ 96 | - name: devicesock 97 | hostPath: 98 | path: /var/lib/kubelet/device-plugins/ 99 | - name: resources 100 | hostPath: 101 | path: /var/lib/kubelet/pod-resources/ 102 | - name: config-volume 103 | configMap: 104 | name: afxdp-dp-config 105 | items: 106 | - key: config.json 107 | path: config.json 108 | - name: log 109 | hostPath: 110 | path: /var/log/afxdp-k8s-plugins/ 111 | - name: cnibin 112 | hostPath: 113 | path: /opt/cni/bin/ 114 | -------------------------------------------------------------------------------- /internal/networking/bridge.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 networking 16 | 17 | import ( 18 | "net" 19 | "syscall" 20 | 21 | "github.com/pkg/errors" 22 | logging "github.com/sirupsen/logrus" 23 | "github.com/vishvananda/netlink" 24 | ) 25 | 26 | type Bridge interface { 27 | Attach(*netlink.Link) error 28 | GetPorts() []*netlink.Link 29 | IPAddrAdd(ip *net.IPNet) error 30 | } 31 | 32 | func NewBridge(name string) (*netlink.Bridge, error) { 33 | 34 | b := &netlink.Bridge{ 35 | LinkAttrs: netlink.LinkAttrs{ 36 | Name: name, 37 | Flags: net.FlagUp, 38 | }, 39 | } 40 | 41 | err := netlink.LinkAdd(b) 42 | if errors.Is(err, syscall.EEXIST) { 43 | logging.Infof("Bridge already exists. It will be recreated") 44 | 45 | // Delete the existing bridge and re-create it. 46 | if err = netlink.LinkDel(b); err != nil { 47 | return nil, errors.Wrap(err, "failed to delete the existing Bridge interface") 48 | } 49 | 50 | if err = netlink.LinkAdd(b); err != nil { 51 | return nil, errors.Wrap(err, "failed to re-create the the Bridge interface") 52 | } 53 | } 54 | 55 | if err != nil { 56 | return nil, errors.Wrap(err, "failed to create the the Bridge interface") 57 | } 58 | 59 | logging.Infof("Successfully created Bridge pair %s", b.Attrs().Name) 60 | 61 | err = netlink.LinkSetUp(b) 62 | if err != nil { 63 | return nil, errors.Wrap(err, "failed to set the bridge to an up state") 64 | } 65 | 66 | return b, nil 67 | } 68 | 69 | func DelBridge(BridgeName string) error { 70 | b, err := netlink.LinkByName(BridgeName) 71 | if err != nil { 72 | return errors.Wrap(err, "failed to find bridge") 73 | } 74 | 75 | return netlink.LinkDel(b) 76 | } 77 | 78 | func GetBridgeByName(n string) (*netlink.Bridge, error) { 79 | link, err := netlink.LinkByName(n) 80 | if err != nil { 81 | return nil, errors.Wrapf(err, "Didn't find the Bridge %s", n) 82 | } 83 | b, ok := link.(*netlink.Bridge) 84 | if !ok { 85 | return nil, errors.Wrapf(err, "Interface %s is not a Bridge", n) 86 | } 87 | return b, nil 88 | 89 | } 90 | 91 | func CheckBridgeExists(name string) (bool, error) { 92 | b, err := netlink.LinkByName(name) 93 | if err != nil || b == nil { 94 | return false, errors.Wrap(err, "failed to find bridge") 95 | } 96 | 97 | return true, nil 98 | } 99 | 100 | func Attach(b *netlink.Bridge, name string) error { 101 | l, err := netlink.LinkByName(name) 102 | if err != nil { 103 | return errors.Wrap(err, "failed to find interface to attach to bridge") 104 | } 105 | 106 | return netlink.LinkSetMaster(l, b) 107 | } 108 | 109 | func IPAddrAdd(b *netlink.Bridge, ip string) error { 110 | addr, err := netlink.ParseAddr(ip) 111 | if err != nil { 112 | return errors.Wrap(err, "failed to parse the ip address") 113 | } 114 | 115 | return netlink.AddrAdd(b, addr) 116 | } 117 | -------------------------------------------------------------------------------- /internal/uds/uds_fuzz.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 uds 17 | 18 | import ( 19 | "fmt" 20 | fuzz "github.com/google/gofuzz" 21 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/logformats" 22 | logging "github.com/sirupsen/logrus" 23 | "io" 24 | "os" 25 | "time" 26 | ) 27 | 28 | const ( 29 | fuzzFile = "/var/log/afxdp-k8s-plugins/fuzz.log" 30 | filePermission = 0644 31 | ) 32 | 33 | var firstCall bool = true 34 | 35 | /* 36 | FuzzHandler interface extends the Handler interface to provide additional testing methods. 37 | */ 38 | type FuzzHandler interface { 39 | Handler 40 | } 41 | 42 | /* 43 | fuzzHandler implements the Handler interface. 44 | */ 45 | type fuzzHandler struct { 46 | } 47 | 48 | /* 49 | NewFuzzHandler returns a fuzz implementation of the Handler interface. 50 | */ 51 | func NewFuzzHandler() Handler { 52 | return &fuzzHandler{} 53 | } 54 | 55 | /* 56 | Init should initialises the Unix domain socket. The fuzzlogging() function is called which creates a separate 57 | file for fuzzing logs. 58 | */ 59 | func (f *fuzzHandler) Init(socketPath string, protocol string, msgbufSize int, ctlBufSize int, timeout time.Duration, uid string) error { 60 | if err := fuzzLogging(); err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | 67 | /* 68 | Listen listens for and accepts new connections. 69 | fuzzHandler returns nil as it's functionality isn't required for fuzz testing. 70 | */ 71 | func (f *fuzzHandler) Listen() (CleanupFunc, error) { 72 | return func() {}, nil 73 | } 74 | 75 | /* 76 | Dial creates a new connection. 77 | fuzzHandler returns nil as it's functionality isn't required for fuzz testing. 78 | */ 79 | func (f *fuzzHandler) Dial() (CleanupFunc, error) { 80 | return func() {}, nil 81 | } 82 | 83 | /* 84 | Read should read the incoming message from the UDS. 85 | FuzzHandler seeds malformed fuzzing data to CNDP read(). 86 | */ 87 | func (f *fuzzHandler) Read() (string, int, error) { 88 | var fuzzResponse string 89 | var fd int = 0 90 | 91 | if firstCall { 92 | fuzzResponse = "/connect, afxdp-fuzz-test" 93 | } else { 94 | 95 | f := fuzz.New() 96 | f.Fuzz(&fuzzResponse) 97 | } 98 | 99 | firstCall = false 100 | return fuzzResponse, fd, nil 101 | } 102 | 103 | /* 104 | Write should write a string to the UDS. 105 | fuzzHandler returns nil as it's functionality isn't required for fuzz testing. 106 | */ 107 | func (f *fuzzHandler) Write(response string, fd int) error { 108 | return nil 109 | } 110 | 111 | func fuzzLogging() error { 112 | 113 | logging.SetReportCaller(true) 114 | 115 | fp, err := os.OpenFile(fuzzFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, filePermission) 116 | if err != nil { 117 | err = fmt.Errorf("fuzzlogging(): cannot open logfile %s: %w", fuzzFile, err) 118 | return err 119 | } 120 | logging.SetOutput(io.MultiWriter(fp, os.Stdout)) 121 | logging.SetFormatter(logformats.Debug) 122 | 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /deployments/daemonset.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: afxdp-dp-config 5 | namespace: kube-system 6 | data: 7 | config.json: | 8 | { 9 | "logLevel":"debug", 10 | "logFile":"afxdp-dp.log", 11 | "pools":[ 12 | { 13 | "name":"myPool", 14 | "mode":"primary", 15 | "drivers":[ 16 | { 17 | "name":"i40e" 18 | }, 19 | { 20 | "name":"ice" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | --- 27 | apiVersion: v1 28 | kind: ServiceAccount 29 | metadata: 30 | name: afxdp-device-plugin 31 | namespace: kube-system 32 | --- 33 | apiVersion: apps/v1 34 | kind: DaemonSet 35 | metadata: 36 | name: kube-afxdp-device-plugin 37 | namespace: kube-system 38 | labels: 39 | tier: node 40 | app: afxdp 41 | spec: 42 | selector: 43 | matchLabels: 44 | name: afxdp-device-plugin 45 | template: 46 | metadata: 47 | labels: 48 | name: afxdp-device-plugin 49 | tier: node 50 | app: afxdp 51 | spec: 52 | hostNetwork: true 53 | nodeSelector: 54 | kubernetes.io/arch: amd64 55 | tolerations: 56 | - key: node-role.kubernetes.io/master 57 | operator: Exists 58 | effect: NoSchedule 59 | serviceAccountName: afxdp-device-plugin 60 | containers: 61 | - name: kube-afxdp 62 | image: intel/afxdp-plugins-for-kubernetes:latest 63 | imagePullPolicy: IfNotPresent 64 | securityContext: 65 | capabilities: 66 | drop: 67 | - all 68 | add: 69 | - SYS_ADMIN 70 | - NET_ADMIN 71 | resources: 72 | requests: 73 | cpu: "250m" 74 | memory: "40Mi" 75 | limits: 76 | cpu: "1" 77 | memory: "200Mi" 78 | volumeMounts: 79 | - name: unixsock 80 | mountPath: /tmp/afxdp_dp/ 81 | - name: bpfmappinning 82 | mountPath: /var/run/afxdp_dp/ 83 | - name: devicesock 84 | mountPath: /var/lib/kubelet/device-plugins/ 85 | - name: resources 86 | mountPath: /var/lib/kubelet/pod-resources/ 87 | - name: config-volume 88 | mountPath: /afxdp/config 89 | - name: log 90 | mountPath: /var/log/afxdp-k8s-plugins/ 91 | - name: cnibin 92 | mountPath: /opt/cni/bin/ 93 | volumes: 94 | - name: unixsock 95 | hostPath: 96 | path: /tmp/afxdp_dp/ 97 | - name: bpfmappinning 98 | hostPath: 99 | path: /var/run/afxdp_dp/ 100 | - name: devicesock 101 | hostPath: 102 | path: /var/lib/kubelet/device-plugins/ 103 | - name: resources 104 | hostPath: 105 | path: /var/lib/kubelet/pod-resources/ 106 | - name: config-volume 107 | configMap: 108 | name: afxdp-dp-config 109 | items: 110 | - key: config.json 111 | path: config.json 112 | - name: log 113 | hostPath: 114 | path: /var/log/afxdp-k8s-plugins/ 115 | - name: cnibin 116 | hostPath: 117 | path: /opt/cni/bin/ 118 | -------------------------------------------------------------------------------- /.github/e2e/daemonset.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: afxdp-dp-config 5 | namespace: kube-system 6 | data: 7 | config.json: | 8 | { 9 | "logLevel":"debug", 10 | "logFile":"afxdp-dp-e2e.log", 11 | "pools":[ 12 | { 13 | "name":"e2eCdq", 14 | "mode":"cdq", 15 | "uid" : 1500, 16 | "udsTimeout":60, 17 | "drivers":[ 18 | { 19 | "name":"ice", 20 | "secondary":64 21 | } 22 | ] 23 | }, 24 | { 25 | "name":"e2ePrimary", 26 | "mode":"primary", 27 | "uid" : 1500, 28 | "drivers":[ 29 | { 30 | "name":"i40e" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | 37 | --- 38 | apiVersion: v1 39 | kind: ServiceAccount 40 | metadata: 41 | name: afxdp-device-plugin 42 | namespace: kube-system 43 | --- 44 | apiVersion: apps/v1 45 | kind: DaemonSet 46 | metadata: 47 | name: kube-afxdp-device-plugin-e2e 48 | namespace: kube-system 49 | labels: 50 | tier: node 51 | app: afxdp 52 | spec: 53 | selector: 54 | matchLabels: 55 | name: afxdp-device-plugin 56 | template: 57 | metadata: 58 | labels: 59 | name: afxdp-device-plugin 60 | tier: node 61 | app: afxdp 62 | spec: 63 | hostNetwork: true 64 | nodeSelector: 65 | kubernetes.io/arch: amd64 66 | tolerations: 67 | - key: node-role.kubernetes.io/master 68 | operator: Exists 69 | effect: NoSchedule 70 | serviceAccountName: afxdp-device-plugin 71 | containers: 72 | - name: kube-afxdp 73 | image: $DOCKER_REG/test/afxdp-device-plugin-e2e:latest 74 | imagePullPolicy: Always 75 | securityContext: 76 | capabilities: 77 | drop: 78 | - all 79 | add: 80 | - SYS_ADMIN 81 | - NET_ADMIN 82 | resources: 83 | requests: 84 | cpu: "250m" 85 | memory: "40Mi" 86 | limits: 87 | cpu: "1" 88 | memory: "200Mi" 89 | volumeMounts: 90 | - name: unixsock 91 | mountPath: /tmp/afxdp_dp/ 92 | - name: devicesock 93 | mountPath: /var/lib/kubelet/device-plugins/ 94 | - name: resources 95 | mountPath: /var/lib/kubelet/pod-resources/ 96 | - name: config-volume 97 | mountPath: /afxdp/config 98 | - name: log 99 | mountPath: /var/log/afxdp-k8s-plugins/ 100 | - name: cnibin 101 | mountPath: /opt/cni/bin/ 102 | volumes: 103 | - name: unixsock 104 | hostPath: 105 | path: /tmp/afxdp_dp/ 106 | - name: devicesock 107 | hostPath: 108 | path: /var/lib/kubelet/device-plugins/ 109 | - name: resources 110 | hostPath: 111 | path: /var/lib/kubelet/pod-resources/ 112 | - name: config-volume 113 | configMap: 114 | name: afxdp-dp-config 115 | items: 116 | - key: config.json 117 | path: config.json 118 | - name: log 119 | hostPath: 120 | path: /var/log/afxdp-k8s-plugins/ 121 | - name: cnibin 122 | hostPath: 123 | path: /opt/cni/bin/ 124 | -------------------------------------------------------------------------------- /internal/networking/kind.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 networking 17 | 18 | import ( 19 | "strconv" 20 | 21 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/bpf" 22 | "github.com/pkg/errors" 23 | logging "github.com/sirupsen/logrus" 24 | ) 25 | 26 | var ( 27 | BridgeName = "afxdp-kind-br" 28 | vEthNamePrefix = "veth" 29 | bridgeIP = "192.168.1.1/24" 30 | ) 31 | 32 | // Create a Kind Network 33 | func CreateKindNetwork(numVeths, offset int) error { 34 | 35 | kindNetworkExists, _ := CheckKindNetworkExists() 36 | if kindNetworkExists { 37 | logging.Infof("Bridge %s already exists deleting at as we don't know it's current state", BridgeName) 38 | err := DeleteKindNetwork(numVeths, offset) 39 | if err != nil { 40 | return errors.Wrapf(err, "Error Creating bridge %s", err.Error()) 41 | } 42 | } 43 | 44 | b, err := NewBridge(BridgeName) 45 | if err != nil { 46 | return errors.Wrapf(err, "Error Creating bridge %s", err.Error()) 47 | } 48 | logging.Infof("Created bridge %s", b.Attrs().Name) 49 | 50 | for i := offset; i < ((numVeths * 2) + offset); i = i + 2 { 51 | vName := vEthNamePrefix + strconv.Itoa(i) 52 | vPeer := vEthNamePrefix + strconv.Itoa(i+1) 53 | veth, err := CreateVeth(vName, vPeer) 54 | if err != nil { 55 | return errors.Wrapf(err, "Error Creating veth pair %s <===> %s", vName, vPeer) 56 | } 57 | 58 | /* Attach one end of the veth pair to the bridge */ 59 | err = Attach(b, vPeer) 60 | if err != nil { 61 | return errors.Wrapf(err, "Error attaching veth %s peer %s to bridge %s", veth.Attrs().Name, vPeer, b.Attrs().Name) 62 | } 63 | logging.Infof("Attached veth %s to bridge %s", veth.Attrs().Name, b.Attrs().Name) 64 | p, _ := GetPeer(veth) 65 | err = SetVethUp(p) 66 | if err != nil { 67 | return errors.Wrapf(err, "Error setting veth %s up", vPeer) 68 | } 69 | 70 | /* Load the xdp-pass program on that peer */ 71 | bh := bpf.NewHandler() 72 | err = bh.LoadAttachBpfXdpPass(vPeer) 73 | if err != nil { 74 | return errors.Wrapf(err, "Error loading xdp-pass Program on interface %s", vPeer) 75 | } 76 | logging.Infof("xdp-pass program loaded on: %s", vPeer) 77 | } 78 | 79 | err = IPAddrAdd(b, bridgeIP) 80 | if err != nil { 81 | return errors.Wrapf(err, "Setting the ip address for %s", b.Attrs().Name) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // CheckKindNetworkExists 88 | func CheckKindNetworkExists() (bool, error) { 89 | return CheckBridgeExists(BridgeName) 90 | } 91 | 92 | // DeleteKindNetwork 93 | func DeleteKindNetwork(numVeths, offset int) error { 94 | err := DelBridge(BridgeName) 95 | if err != nil { 96 | return errors.Wrap(err, "failed to delete bridge") 97 | } 98 | 99 | for i := offset; i < numVeths; i = i + 2 { 100 | vName := vEthNamePrefix + strconv.Itoa(i) 101 | v, err := GetVethByName(vName) 102 | if err != nil { 103 | return errors.Wrapf(err, "failed to find veth %s", vName) 104 | } 105 | err = DeleteVeth(v) 106 | if err != nil { 107 | return errors.Wrapf(err, "Error Deleting veth %s", vName) 108 | } 109 | } 110 | 111 | logging.Info("Deleted kind secondary network") 112 | 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /test/e2e/udsTest.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/intel/afxdp-plugins-for-kubernetes/constants" 20 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/uds" 21 | "os" 22 | "strconv" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | const ( 28 | udsIdleTimeout = 0 * time.Second 29 | requestDelay = 100 * time.Millisecond // not required but keeps things in nice order when DP and this test app are both printing to screen 30 | timeoutDuration = 40 // For UDS timeout test - timeoutDuration must exceed timeout value set in config.json. 31 | ) 32 | 33 | var udsHandler uds.Handler 34 | 35 | func main() { 36 | timeoutAfterConnect := false 37 | timeoutBeforeConnect := false 38 | // Command line argument to set timeout test 39 | timeoutArgs := os.Args[1:] 40 | for _, arg := range timeoutArgs { 41 | if arg == "-timeout-before-connect" { 42 | timeoutBeforeConnect = true 43 | } 44 | if arg == "-timeout-after-connect" { 45 | timeoutAfterConnect = true 46 | } 47 | } 48 | 49 | //Get environment variable device values 50 | devicesVar, exists := os.LookupEnv(constants.Devices.EnvVarList) 51 | if !exists { 52 | println("Test App Error: Devices env var does not exist") 53 | os.Exit(1) 54 | } 55 | devices := strings.Split(devicesVar, " ") 56 | 57 | hostname, exists := os.LookupEnv("HOSTNAME") 58 | if !exists { 59 | println("Test App Error: Hostname env var does not exist") 60 | os.Exit(1) 61 | } 62 | 63 | udsHandler = uds.NewHandler() 64 | 65 | // init 66 | if err := udsHandler.Init(constants.Uds.PodPath, constants.Uds.Protocol, constants.Uds.MsgBufSize, constants.Uds.CtlBufSize, udsIdleTimeout, ""); err != nil { 67 | println("Test App Error: Error Initialising UDS server: ", err) 68 | os.Exit(1) 69 | } 70 | 71 | // Execute timeoutBeforeConnect when set to true 72 | if timeoutBeforeConnect { 73 | println("Test App - Executing timeout before connect") 74 | timeout() 75 | } 76 | 77 | cleanup, err := udsHandler.Dial() 78 | if err != nil { 79 | println("Test App Error: UDS Dial error:: ", err) 80 | cleanup() 81 | os.Exit(1) 82 | } 83 | defer cleanup() 84 | 85 | // connect and verify pod hostname 86 | makeRequest("/connect, " + hostname) 87 | time.Sleep(requestDelay) 88 | 89 | // Execute timeoutAfterConnect when set to true 90 | if timeoutAfterConnect { 91 | println("Test App - Executing timeout after connect") 92 | timeout() 93 | } 94 | 95 | // request version 96 | makeRequest("/version") 97 | time.Sleep(requestDelay) 98 | 99 | // request XSK map FD for all devices 100 | for _, dev := range devices { 101 | request := "/xsk_map_fd, " + dev 102 | makeRequest(request) 103 | time.Sleep(requestDelay) 104 | } 105 | 106 | // request an unknown device 107 | makeRequest("/xsk_map_fd, bad-device") 108 | time.Sleep(requestDelay) 109 | 110 | // send a bad request 111 | makeRequest("/bad-request") 112 | time.Sleep(requestDelay) 113 | 114 | // finish 115 | makeRequest("/fin") 116 | time.Sleep(requestDelay) 117 | } 118 | 119 | func timeout() { 120 | println("Test App - Pausing for", timeoutDuration, "seconds to force timeout") 121 | println("Test App - Expecting timeout error to occur") 122 | time.Sleep(timeoutDuration * time.Second) 123 | println("Test App - Exiting") 124 | os.Exit(0) 125 | } 126 | 127 | func makeRequest(request string) { 128 | println() 129 | println("Test App - Request: " + request) 130 | 131 | if err := udsHandler.Write(request, -1); err != nil { 132 | println("Test App - Write error: ", err) 133 | } 134 | 135 | response, fd, err := udsHandler.Read() 136 | if err != nil { 137 | println("Test App - Read error: ", err) 138 | } 139 | 140 | println("Test App - Response: " + response) 141 | if fd > 0 { 142 | println("Test App - File Descriptor:", strconv.Itoa(fd)) 143 | } 144 | println() 145 | } 146 | -------------------------------------------------------------------------------- /.github/workflows/internal-ci.yml: -------------------------------------------------------------------------------- 1 | name: 'Internal-CI' 2 | on: push 3 | 4 | permissions: # added using https://github.com/step-security/secure-repo 5 | contents: read 6 | 7 | jobs: 8 | static-analysis: 9 | runs-on: self-hosted 10 | steps: 11 | - name: Checkout 12 | uses: "actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0" 13 | - name: Static Analysis 14 | run: make static 15 | 16 | build: 17 | runs-on: self-hosted 18 | steps: 19 | - name: Checkout 20 | uses: "actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0" 21 | - name: Build 22 | run: make build 23 | 24 | unit-tests: 25 | runs-on: self-hosted 26 | steps: 27 | - name: Checkout 28 | uses: "actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0" 29 | - name: Unit Tests 30 | run: make test 31 | 32 | end-to-end: 33 | runs-on: self-hosted 34 | environment: AF_XDP CI 35 | steps: 36 | - name: Checkout 37 | uses: "actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0" 38 | - name: Delete previous logs 39 | run: | 40 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 41 | do 42 | echo "Cleaning up logs on node $i" 43 | ssh $i "rm -f /var/log/afxdp-k8s-plugins/afxdp-cni-e2e.log && rm -f /var/log/afxdp-k8s-plugins/afxdp-dp-e2e.log" || true 44 | done 45 | - name: E2E Test (full host run) 46 | run: ulimit -l 65536 && make e2efull 47 | - name: E2E Test (full CI daemonset run) 48 | env: 49 | DOCKER_REG: ${{ secrets.DOCKER_REG }} 50 | run: ulimit -l 65536 && make image && cd test/e2e/ && ./e2e-test.sh --ci 51 | - name: CNI logs 52 | if: always() 53 | run: | 54 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 55 | do 56 | echo "**********************************************************************************************************************************************************" 57 | echo "CNI logs from node $i" 58 | echo "**********************************************************************************************************************************************************" 59 | echo 60 | ssh $i "cat /var/log/afxdp-k8s-plugins/afxdp-cni-e2e.log" || true 61 | echo -e "\n\n\n\n\n\n\n" 62 | done 63 | - name: Device Plugin logs 64 | if: always() 65 | run: | 66 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 67 | do 68 | echo "**********************************************************************************************************************************************************" 69 | echo "Device plugin logs from node $i" 70 | echo "**********************************************************************************************************************************************************" 71 | echo 72 | ssh $i "cat /var/log/afxdp-k8s-plugins/afxdp-dp-e2e.log" || true 73 | echo -e "\n\n\n\n\n\n\n" 74 | done 75 | - name: UDS Directory 76 | if: always() 77 | run: | 78 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 79 | do 80 | echo "UDS directory on node $i" 81 | ssh $i "ls -laR /tmp/afxdp_dp/" || true 82 | echo 83 | done 84 | - name: List Remaining Subfunctions 85 | if: always() 86 | run: | 87 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 88 | do 89 | echo "Subfunctions on node $i" 90 | ssh $i "devlink port list" || true 91 | echo 92 | done 93 | - name: Remove Remaining Subfunctions 94 | if: always() 95 | run: | 96 | for i in $(kubectl get nodes | awk '{print $1}' | awk '{if (NR!=1) {print}}') 97 | do 98 | echo "Removing subfunctions on node $i" 99 | ssh $i " 100 | devlink_output=(\$(devlink port list | awk '{print \$1}' | sed 's/.\$//')) 101 | for i in \"\${devlink_output[@]}\"; do 102 | if [[ \$i == \"pci/\"* ]] && [[ \$i != *\"/0\" ]]; then 103 | devlink port function set \$i state inactive 104 | devlink port del \$i 105 | echo \$i 106 | fi 107 | done 108 | " 109 | echo 110 | done -------------------------------------------------------------------------------- /pkg/goclient/goclient.go: -------------------------------------------------------------------------------- 1 | package goclient 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/intel/afxdp-plugins-for-kubernetes/constants" 8 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/host" 9 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/uds" 10 | ) 11 | 12 | var ( 13 | hostUds uds.Handler 14 | hostPod host.Handler 15 | cleanupGlobal uds.CleanupFunc 16 | connected bool = false 17 | ) 18 | 19 | /* 20 | GetClientVersion returns the version of our Handshake from the client 21 | */ 22 | func GetClientVersion() string { 23 | return constants.Uds.Handshake.Version 24 | } 25 | 26 | /* 27 | GetServerVersion returns the version of our Handshake from the server as a response 28 | */ 29 | func GetServerVersion() (string, uds.CleanupFunc, error) { 30 | if !connected { 31 | err := initFunc() 32 | if err != nil { 33 | return "", cleanupGlobal, err 34 | } 35 | } 36 | 37 | if err := hostUds.Write(constants.Uds.Handshake.RequestVersion, -1); err != nil { 38 | return "", cleanupGlobal, fmt.Errorf("Library Error: Writing Error: %v", err) 39 | } 40 | 41 | response, _, err := hostUds.Read() 42 | if err != nil { 43 | return "", cleanupGlobal, fmt.Errorf("Library Error: Reading Error: %v", err) 44 | } 45 | 46 | return response, 47 | cleanupGlobal, 48 | nil 49 | } 50 | 51 | /* 52 | RequestXSKmapFD requires a device name and returns a fds the device, a cleanup function to close the connection, and an error 53 | */ 54 | func RequestXSKmapFD(device string) (int, uds.CleanupFunc, error) { 55 | if !connected { 56 | err := initFunc() 57 | 58 | if err != nil { 59 | return 0, cleanupGlobal, fmt.Errorf("Library Error: Initializing Error: %v", err) 60 | } 61 | } 62 | 63 | if err := hostUds.Write(constants.Uds.Handshake.RequestFd+", "+device, -1); err != nil { 64 | return 0, cleanupGlobal, fmt.Errorf("Library Error: UDS Write error: %v", err) 65 | 66 | } 67 | 68 | response, fd, err := hostUds.Read() 69 | if err != nil { 70 | return 0, cleanupGlobal, fmt.Errorf("Library Error: UDS Read error: %v", err) 71 | 72 | } 73 | 74 | if response == constants.Uds.Handshake.ResponseFdAck { 75 | return fd, cleanupGlobal, nil 76 | } else { 77 | return 0, cleanupGlobal, fmt.Errorf("Library Error: Request for FD was not acknowledged") 78 | } 79 | 80 | } 81 | 82 | /* 83 | RequestBusyPoll takes a timeout, budget and a fd to request the busypoll for a specific device, and returns an fd, response, cleanup function and error 84 | */ 85 | func RequestBusyPoll(busyTimeout, busyBudget, fd int) (uds.CleanupFunc, error) { 86 | if !connected { 87 | err := initFunc() 88 | if err != nil { 89 | return cleanupGlobal, fmt.Errorf("Library Error: Failed to initialize UDS error: %v", err) 90 | } 91 | } 92 | 93 | pollString := fmt.Sprintf("%s, %d, %d", constants.Uds.Handshake.RequestBusyPoll, busyTimeout, busyBudget) 94 | 95 | if err := hostUds.Write(pollString, fd); err != nil { 96 | return cleanupGlobal, fmt.Errorf("Library Error: Failed to write to UDS error: %v", err) 97 | } 98 | 99 | response, _, err := hostUds.Read() 100 | if err != nil { 101 | return cleanupGlobal, fmt.Errorf("Library Error: Failed to read UDS error: %v", err) 102 | } 103 | 104 | if response == constants.Uds.Handshake.ResponseBusyPollNak { 105 | return cleanupGlobal, fmt.Errorf("Library Error: Device plugin error configuring busy poll") 106 | } 107 | 108 | return cleanupGlobal, nil 109 | } 110 | 111 | /* 112 | initFunc initializes the library, returns a cleanup function and an error 113 | */ 114 | func initFunc() error { 115 | hostUds = uds.NewHandler() 116 | hostPod = host.NewHandler() 117 | var response string 118 | 119 | // init uds Handler for reading and writing 120 | if err := hostUds.Init(constants.Uds.PodPath, constants.Uds.Protocol, constants.Uds.MsgBufSize, constants.Uds.CtlBufSize, 0*time.Second, ""); err != nil { 121 | return fmt.Errorf("Library Error: Error Initialising UDS server: %v", err) 122 | } 123 | 124 | cleanup, err := hostUds.Dial() 125 | cleanupGlobal = cleanup 126 | if err != nil { 127 | return fmt.Errorf("Library Error: UDS Dial error: %v", err) 128 | } 129 | 130 | hostname, err := hostPod.Hostname() 131 | if err != nil { 132 | return fmt.Errorf("Library Error: Failed to initialize host: %v", err) 133 | } 134 | 135 | if err = hostUds.Write(constants.Uds.Handshake.RequestConnect+", "+hostname, -1); err != nil { 136 | return fmt.Errorf("Library Error: UDS Write error: %v", err) 137 | } 138 | 139 | if response, _, err = hostUds.Read(); err != nil { 140 | return fmt.Errorf("Library Error: UDS Read error : %v", err) 141 | } 142 | 143 | if response == constants.Uds.Handshake.ResponseHostOk { 144 | connected = true 145 | } 146 | 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /internal/dpcnisyncer/dp_cni_syncer_grpc.pb.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 17 | // versions: 18 | // - protoc-gen-go-grpc v1.3.0 19 | // - protoc v3.12.4 20 | // source: dp_cni_syncer.proto 21 | 22 | package dpcnisyncer 23 | 24 | import ( 25 | context "context" 26 | grpc "google.golang.org/grpc" 27 | codes "google.golang.org/grpc/codes" 28 | status "google.golang.org/grpc/status" 29 | ) 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the grpc package it is being compiled against. 33 | // Requires gRPC-Go v1.32.0 or later. 34 | const _ = grpc.SupportPackageIsVersion7 35 | 36 | const ( 37 | NetDev_DelNetDev_FullMethodName = "/dpcnisyncer.NetDev/DelNetDev" 38 | ) 39 | 40 | // NetDevClient is the client API for NetDev service. 41 | // 42 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 43 | type NetDevClient interface { 44 | DelNetDev(ctx context.Context, in *DeleteNetDevReq, opts ...grpc.CallOption) (*DeleteNetDevResp, error) 45 | } 46 | 47 | type netDevClient struct { 48 | cc grpc.ClientConnInterface 49 | } 50 | 51 | func NewNetDevClient(cc grpc.ClientConnInterface) NetDevClient { 52 | return &netDevClient{cc} 53 | } 54 | 55 | func (c *netDevClient) DelNetDev(ctx context.Context, in *DeleteNetDevReq, opts ...grpc.CallOption) (*DeleteNetDevResp, error) { 56 | out := new(DeleteNetDevResp) 57 | err := c.cc.Invoke(ctx, NetDev_DelNetDev_FullMethodName, in, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | // NetDevServer is the server API for NetDev service. 65 | // All implementations must embed UnimplementedNetDevServer 66 | // for forward compatibility 67 | type NetDevServer interface { 68 | DelNetDev(context.Context, *DeleteNetDevReq) (*DeleteNetDevResp, error) 69 | mustEmbedUnimplementedNetDevServer() 70 | } 71 | 72 | // UnimplementedNetDevServer must be embedded to have forward compatible implementations. 73 | type UnimplementedNetDevServer struct { 74 | } 75 | 76 | func (UnimplementedNetDevServer) DelNetDev(context.Context, *DeleteNetDevReq) (*DeleteNetDevResp, error) { 77 | return nil, status.Errorf(codes.Unimplemented, "method DelNetDev not implemented") 78 | } 79 | func (UnimplementedNetDevServer) mustEmbedUnimplementedNetDevServer() {} 80 | 81 | // UnsafeNetDevServer may be embedded to opt out of forward compatibility for this service. 82 | // Use of this interface is not recommended, as added methods to NetDevServer will 83 | // result in compilation errors. 84 | type UnsafeNetDevServer interface { 85 | mustEmbedUnimplementedNetDevServer() 86 | } 87 | 88 | func RegisterNetDevServer(s grpc.ServiceRegistrar, srv NetDevServer) { 89 | s.RegisterService(&NetDev_ServiceDesc, srv) 90 | } 91 | 92 | func _NetDev_DelNetDev_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 93 | in := new(DeleteNetDevReq) 94 | if err := dec(in); err != nil { 95 | return nil, err 96 | } 97 | if interceptor == nil { 98 | return srv.(NetDevServer).DelNetDev(ctx, in) 99 | } 100 | info := &grpc.UnaryServerInfo{ 101 | Server: srv, 102 | FullMethod: NetDev_DelNetDev_FullMethodName, 103 | } 104 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 105 | return srv.(NetDevServer).DelNetDev(ctx, req.(*DeleteNetDevReq)) 106 | } 107 | return interceptor(ctx, in, info, handler) 108 | } 109 | 110 | // NetDev_ServiceDesc is the grpc.ServiceDesc for NetDev service. 111 | // It's only intended for direct use with grpc.RegisterService, 112 | // and not to be introspected or modified (even as a copy) 113 | var NetDev_ServiceDesc = grpc.ServiceDesc{ 114 | ServiceName: "dpcnisyncer.NetDev", 115 | HandlerType: (*NetDevServer)(nil), 116 | Methods: []grpc.MethodDesc{ 117 | { 118 | MethodName: "DelNetDev", 119 | Handler: _NetDev_DelNetDev_Handler, 120 | }, 121 | }, 122 | Streams: []grpc.StreamDesc{}, 123 | Metadata: "dp_cni_syncer.proto", 124 | } 125 | -------------------------------------------------------------------------------- /internal/dpcnisyncerserver/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2023 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package dpcnisyncerserver 17 | 18 | import ( 19 | "context" 20 | "net" 21 | "os" 22 | "time" 23 | 24 | "github.com/intel/afxdp-plugins-for-kubernetes/constants" 25 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/bpf" 26 | pb "github.com/intel/afxdp-plugins-for-kubernetes/internal/dpcnisyncer" 27 | "github.com/pkg/errors" 28 | logging "github.com/sirupsen/logrus" 29 | "google.golang.org/grpc" 30 | "google.golang.org/grpc/credentials/insecure" 31 | pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" 32 | ) 33 | 34 | const ( 35 | protocol = "unix" 36 | ) 37 | 38 | var ( 39 | sockAddr = pluginapi.DevicePluginPath + constants.Plugins.DevicePlugin.DevicePrefix + "-" + "syncer.sock" 40 | ) 41 | 42 | type SyncerServer struct { 43 | pb.UnimplementedNetDevServer 44 | mapManagers []bpf.PoolBpfMapManager 45 | grpcServer *grpc.Server 46 | BpfMapPinEnable bool 47 | } 48 | 49 | func (s *SyncerServer) RegisterMapManager(b bpf.PoolBpfMapManager) { 50 | 51 | if s.mapManagers != nil { 52 | for _, v := range s.mapManagers { 53 | if v.Manager.GetName() == b.Manager.GetName() { 54 | logging.Infof("%s is already registered", b.Manager.GetName()) 55 | return 56 | } 57 | } 58 | } 59 | 60 | s.mapManagers = append(s.mapManagers, b) 61 | } 62 | 63 | func (s *SyncerServer) DelNetDev(ctx context.Context, in *pb.DeleteNetDevReq) (*pb.DeleteNetDevResp, error) { 64 | 65 | if s.BpfMapPinEnable { 66 | netDevName := in.GetName() 67 | 68 | logging.Infof("Looking up Map Manager for %s", netDevName) 69 | found := false 70 | var pm bpf.PoolBpfMapManager 71 | for _, mm := range s.mapManagers { 72 | _, err := mm.Manager.GetBPFFS(netDevName) 73 | if err == nil { 74 | found = true 75 | pm = mm 76 | break 77 | } 78 | } 79 | 80 | if !found { 81 | logging.Errorf("Could NOT find the map manager for device %s", netDevName) 82 | return &pb.DeleteNetDevResp{Ret: -1}, errors.New("Could NOT find the map manager for device") 83 | } 84 | 85 | logging.Infof("Map Manager found, deleting BPFFS for %s", netDevName) 86 | err := pm.Manager.DeleteBPFFS(netDevName) 87 | if err != nil { 88 | logging.Errorf("Could NOT delete BPFFS for %s", netDevName) 89 | return &pb.DeleteNetDevResp{Ret: -1}, errors.Wrapf(err, "Could NOT delete BPFFS for %s: %v", netDevName, err.Error()) 90 | } 91 | 92 | logging.Infof("Network interface %s deleted", netDevName) 93 | return &pb.DeleteNetDevResp{Ret: 0}, nil 94 | } 95 | 96 | return &pb.DeleteNetDevResp{Ret: -1}, errors.New("BPF Map pinning is not enabled") 97 | } 98 | 99 | func (s *SyncerServer) StopGRPCSyncer() { 100 | if s.grpcServer != nil { 101 | s.grpcServer.Stop() 102 | s.grpcServer = nil 103 | } 104 | s.cleanup() 105 | } 106 | 107 | func NewSyncerServer() (*SyncerServer, error) { 108 | if _, err := os.Stat(sockAddr); !os.IsNotExist(err) { 109 | if err := os.RemoveAll(sockAddr); err != nil { 110 | logging.Errorf("sockAddr %s does not exist", sockAddr) 111 | return nil, err 112 | } 113 | } 114 | 115 | server := &SyncerServer{ 116 | grpcServer: grpc.NewServer(), 117 | BpfMapPinEnable: false, 118 | } 119 | 120 | lis, err := net.Listen(protocol, sockAddr) 121 | if err != nil { 122 | logging.Errorf("Could not listen to %s", sockAddr) 123 | return nil, err 124 | } 125 | 126 | pb.RegisterNetDevServer(server.grpcServer, server) 127 | go func() { 128 | if err := server.grpcServer.Serve(lis); err != nil { 129 | logging.Errorf("Could not RegisterNetDevServer: %v", err) 130 | } 131 | }() 132 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 133 | defer cancel() 134 | conn, err := grpc.DialContext(ctx, sockAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), 135 | grpc.WithBlock(), grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { 136 | return (&net.Dialer{}).DialContext(ctx, "unix", addr) 137 | }), 138 | ) 139 | if err != nil { 140 | logging.Errorf("Unable to establish test connection with gRPC server: %v", err) 141 | return nil, err 142 | } 143 | conn.Close() 144 | logging.Debugf("NewSyncerServer up and Running") 145 | return server, nil 146 | } 147 | 148 | func (s *SyncerServer) cleanup() error { 149 | if err := os.Remove(sockAddr); err != nil && !os.IsNotExist(err) { 150 | return err 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /internal/bpf/bpfWrapper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package bpf 18 | 19 | //#include 20 | //#include 21 | //#cgo CFLAGS: -I. 22 | //#cgo LDFLAGS: -L. -lxdp -lbpf -lelf -lz 23 | //#include "bpfWrapper.h" 24 | //#include "log.h" 25 | import "C" 26 | 27 | import ( 28 | "os/exec" 29 | "strings" 30 | 31 | "github.com/pkg/errors" 32 | logging "github.com/sirupsen/logrus" 33 | ) 34 | 35 | /* 36 | Handler is the interface to the BPF package. 37 | The interface exists for testing purposes, allowing unit tests to run 38 | without making actual BPF calls. 39 | */ 40 | type Handler interface { 41 | LoadBpfSendXskMap(ifname string) (int, error) 42 | LoadAttachBpfXdpPass(ifname string) error 43 | ConfigureBusyPoll(fd int, busyTimeout int, busyBudget int) error 44 | LoadBpfPinXskMap(ifname, pin_path string) error 45 | Cleanbpf(ifname string) error 46 | } 47 | 48 | /* 49 | handler implements the Handler interface. 50 | */ 51 | type handler struct{} 52 | 53 | /* 54 | NewHandler returns an implementation of the Handler interface. 55 | */ 56 | func NewHandler() Handler { 57 | return &handler{} 58 | } 59 | 60 | /* 61 | LoadBpfSendXskMap is the GoLang wrapper for the C function Load_bpf_send_xsk_map 62 | */ 63 | func (r *handler) LoadBpfSendXskMap(ifname string) (int, error) { 64 | fd := int(C.Load_bpf_send_xsk_map(C.CString(ifname))) 65 | 66 | if fd <= 0 { 67 | return fd, errors.New("error loading BPF program onto interface") 68 | } 69 | 70 | return fd, nil 71 | } 72 | 73 | /* 74 | LoadBpfXdpPass is the GoLang wrapper for the C function Load_bpf_send_xsk_map 75 | */ 76 | func (r *handler) LoadAttachBpfXdpPass(ifname string) error { 77 | bpfProg := "/afxdp/xdp_pass.o" 78 | 79 | if err := XdpLoaderCmd(ifname, "load", bpfProg, ""); err != nil { 80 | return errors.Wrapf(err, "Couldn't Load %s to interface %s", bpfProg, ifname) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | /* 87 | LoadBpfPinXskMap is the GoLang wrapper for the C function Load_bpf_send_xsk_map 88 | */ 89 | func (r *handler) LoadBpfPinXskMap(ifname, pin_path string) error { 90 | 91 | bpfProg := "/afxdp/xdp_afxdp_redirect.o" 92 | 93 | if err := XdpLoaderCmd(ifname, "load", bpfProg, pin_path); err != nil { 94 | return errors.Wrapf(err, "Couldn't Load and pin %s to interface %s", bpfProg, ifname) 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func XdpLoaderCmd(ifname, action, bpfProg, pin_path string) error { 101 | 102 | cmd := exec.Command("xdp-loader", "unload", ifname, "--all") 103 | 104 | if err := cmd.Run(); err != nil && err.Error() != "exit status 1" { // exit status 1 means no prog to unload 105 | logging.Errorf("Error removing BPF program from device: %v", err) 106 | return errors.New("Error removing BPF program from device") 107 | } 108 | 109 | if action == "load" { 110 | loaderArgs := action + " " + ifname + " " + bpfProg 111 | if pin_path != "" { 112 | loaderArgs += " -p " + pin_path 113 | } 114 | logging.Infof("Loading XDP program using: xdp-loader %s", loaderArgs) 115 | 116 | cmd := exec.Command("xdp-loader", strings.Split(loaderArgs, " ")...) 117 | if err := cmd.Run(); err != nil { 118 | logging.Errorf("error loading and pinning BPF program onto interface %v", err) 119 | return errors.New("error loading and pinning BPF program onto interface") 120 | } 121 | } 122 | 123 | return nil 124 | } 125 | 126 | /* 127 | ConfigureBusyPoll is the GoLang wrapper for the C function Configure_busy_poll 128 | */ 129 | func (r *handler) ConfigureBusyPoll(fd int, busyTimeout int, busyBudget int) error { 130 | ret := C.Configure_busy_poll(C.int(fd), C.int(busyTimeout), C.int(busyBudget)) 131 | 132 | if ret != 0 { 133 | return errors.New("error configuring busy poll on interface") 134 | } 135 | 136 | return nil 137 | } 138 | 139 | /* 140 | Cleanbpf is the GoLang wrapper for the C function Clean_bpf 141 | */ 142 | func (r *handler) Cleanbpf(ifname string) error { 143 | 144 | ret := C.Clean_bpf(C.CString(ifname)) 145 | 146 | if ret != 0 { 147 | return errors.New("error removing BPF program from interface") 148 | } 149 | 150 | return nil 151 | } 152 | 153 | // Debugf is exported to C, so C code can write logs to the Golang logging package 154 | // 155 | //export Debugf 156 | func Debugf(msg *C.char) { 157 | logging.Debugf(C.GoString(msg)) 158 | } 159 | 160 | // Infof is exported to C, so C code can write logs to the Golang logging package 161 | // 162 | //export Infof 163 | func Infof(msg *C.char) { 164 | logging.Infof(C.GoString(msg)) 165 | } 166 | 167 | // Warningf is exported to C, so C code can write logs to the Golang logging package 168 | // 169 | //export Warningf 170 | func Warningf(msg *C.char) { 171 | logging.Warningf(C.GoString(msg)) 172 | } 173 | 174 | // Errorf is exported to C, so C code can write logs to the Golang logging package 175 | // 176 | //export Errorf 177 | func Errorf(msg *C.char) { 178 | logging.Errorf(C.GoString(msg)) 179 | } 180 | 181 | // Panicf is exported to C, so C code can write logs to the Golang logging package 182 | // 183 | //export Panicf 184 | func Panicf(msg *C.char) { 185 | logging.Panicf(C.GoString(msg)) 186 | } 187 | -------------------------------------------------------------------------------- /pkg/subfunctions/cdq.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 subfunctions 17 | 18 | import ( 19 | "fmt" 20 | "os/exec" 21 | "strconv" 22 | "strings" 23 | 24 | logging "github.com/sirupsen/logrus" 25 | ) 26 | 27 | /* 28 | CreateCdqSubfunction takes the PCI address of a port and a subfunction number 29 | It creates that subfunction on top of that port and activates it 30 | */ 31 | func CreateCdqSubfunction(parentPci string, pfnum string, sfnum string) error { 32 | app := "devlink" 33 | args := []string{"port", "add", "pci/" + parentPci, "flavour", "pcisf", "pfnum", pfnum, "sfnum", sfnum} 34 | 35 | output, err := exec.Command(app, args...).Output() 36 | if err != nil { 37 | logging.Errorf("Error creating sub-function %s on pci %s: %v", sfnum, parentPci, err.Error()) 38 | return err 39 | } 40 | 41 | portIndex := strings.Split(string(output), ": ")[0] 42 | args = []string{"port", "function", "set", portIndex, "state", "active"} 43 | 44 | _, err = exec.Command(app, args...).Output() 45 | if err != nil { 46 | logging.Errorf("Error activating sub-function %s on pci %s: %v", sfnum, parentPci, err.Error()) 47 | return err 48 | } 49 | 50 | return nil 51 | } 52 | 53 | /* 54 | DeleteCdqSubfunction takes the port index of a subfunction, deactivates and deletes it 55 | */ 56 | func DeleteCdqSubfunction(portIndex string) error { 57 | app := "devlink" 58 | args := []string{"port", "function", "set", "pci/" + portIndex, "state", "inactive"} 59 | 60 | _, err := exec.Command(app, args...).Output() 61 | if err != nil { 62 | logging.Errorf("Error setting sub-function inactive %s: %v", portIndex, err.Error()) 63 | return err 64 | } 65 | 66 | args = []string{"port", "del", "pci/" + portIndex} 67 | 68 | _, err = exec.Command(app, args...).Output() 69 | if err != nil { 70 | logging.Errorf("Error deleting sub-function %s: %v", portIndex, err.Error()) 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | 77 | /* 78 | IsCdqSubfunction takes a netdev name and returns true if is a CDQ subfunction. 79 | */ 80 | func IsCdqSubfunction(name string) (bool, error) { 81 | portIndex, err := GetCdqPortIndex(name) 82 | if err != nil { 83 | return false, err 84 | } 85 | 86 | split := strings.Split(portIndex, "/") 87 | index := split[1] 88 | 89 | if index == "0" { 90 | return false, nil 91 | } 92 | return true, nil 93 | } 94 | 95 | /* 96 | GetCdqPortIndex takes a netdev name and returns the port index (pci/sfnum) 97 | Note this function only works on physical devices and CDQ subfunctions 98 | Other netdevs will return a "device not found by devlink" error 99 | */ 100 | func GetCdqPortIndex(netdev string) (string, error) { 101 | devlinkList := "devlink port list | grep " + `"\b` + netdev + `\b"` 102 | 103 | devList, err := exec.Command("sh", "-c", devlinkList).CombinedOutput() 104 | if err != nil { 105 | if strings.Contains(err.Error(), "exit status 1") { 106 | return "", fmt.Errorf("device %s not found by devlink (1)", netdev) 107 | } 108 | return "", err 109 | } 110 | 111 | if devList != nil { 112 | portIndex := strings.Fields(string(devList))[0] 113 | 114 | pciSplit := strings.Split(portIndex, "pci/") 115 | portIndexAddress := pciSplit[1] 116 | 117 | lastInd := strings.LastIndex(portIndexAddress, ":") 118 | portAddrIndex := portIndexAddress[:lastInd] 119 | return portAddrIndex, nil 120 | } 121 | 122 | return "", fmt.Errorf("device %s not found by devlink (2)", netdev) 123 | } 124 | 125 | /* 126 | GetCdqPfnum takes a netdev name and returns the physical port number / pfnum 127 | Note this function only works on physical devices and CDQ subfunctions 128 | Other netdevs will return a "device not found by devlink" error 129 | */ 130 | func GetCdqPfnum(netdev string) (string, error) { 131 | devlinkList := "devlink port list | grep " + `"\b` + netdev + `\b"` 132 | 133 | devList, err := exec.Command("sh", "-c", devlinkList).CombinedOutput() 134 | if err != nil { 135 | if strings.Contains(err.Error(), "exit status 1") { 136 | return "", fmt.Errorf("device %s not found by devlink (1)", netdev) 137 | } 138 | return "", err 139 | } 140 | 141 | if devList != nil { 142 | pfNum := strings.Fields(string(devList))[8] 143 | return pfNum, nil 144 | } 145 | 146 | return "", fmt.Errorf("device %s not found by devlink (2)", netdev) 147 | } 148 | 149 | /* 150 | NumAvailableCdqSubfunctions takes the PCI of a physical port and returns how 151 | many unused CDQ subfunctions are available 152 | */ 153 | func NumAvailableCdqSubfunctions(pci string) (int, error) { 154 | app := "devlink" 155 | args := []string{"resource", "show", "pci/" + pci} 156 | 157 | resourceInfo, err := exec.Command(app, args...).CombinedOutput() 158 | if err != nil { 159 | logging.Errorf("Error getting devlink resource for pci %s: %v", pci, err) 160 | return 0, err 161 | } 162 | 163 | lines := strings.Split(string(resourceInfo), "\n") 164 | totalSFs, err := strconv.Atoi(strings.Fields(lines[3])[3]) //line 3, word 3 - "size" 165 | if err != nil { 166 | logging.Errorf("Error converting total available SFs to int %s", err) 167 | return 0, err 168 | } 169 | inUseSFs, err := strconv.Atoi(strings.Fields(lines[3])[5]) //line 3, word 5 - "occ" 170 | if err != nil { 171 | logging.Errorf("Error converting in use SFs to int %s", err) 172 | return 0, err 173 | } 174 | return totalSFs - inUseSFs, nil 175 | } 176 | -------------------------------------------------------------------------------- /internal/dpcnisyncer/dp_cni_syncer.pb.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) Red Hat Inc. 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 | //lint:file-ignore SA1019 Ignore all deprecated code, it's generated 16 | // Code generated by protoc-gen-go. DO NOT EDIT. 17 | // source: dp_cni_syncer.proto 18 | 19 | package dpcnisyncer 20 | 21 | import ( 22 | fmt "fmt" 23 | proto "github.com/golang/protobuf/proto" 24 | math "math" 25 | ) 26 | 27 | // Reference imports to suppress errors if they are not otherwise used. 28 | var _ = proto.Marshal 29 | var _ = fmt.Errorf 30 | var _ = math.Inf 31 | 32 | // This is a compile-time assertion to ensure that this generated file 33 | // is compatible with the proto package it is being compiled against. 34 | // A compilation error at this line likely means your copy of the 35 | // proto package needs to be updated. 36 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 37 | 38 | type DeleteNetDevReq struct { 39 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 40 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 41 | XXX_unrecognized []byte `json:"-"` 42 | XXX_sizecache int32 `json:"-"` 43 | } 44 | 45 | func (m *DeleteNetDevReq) Reset() { *m = DeleteNetDevReq{} } 46 | func (m *DeleteNetDevReq) String() string { return proto.CompactTextString(m) } 47 | func (*DeleteNetDevReq) ProtoMessage() {} 48 | func (*DeleteNetDevReq) Descriptor() ([]byte, []int) { 49 | return fileDescriptor_f9ab154255673e38, []int{0} 50 | } 51 | 52 | func (m *DeleteNetDevReq) XXX_Unmarshal(b []byte) error { 53 | return xxx_messageInfo_DeleteNetDevReq.Unmarshal(m, b) 54 | } 55 | func (m *DeleteNetDevReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 56 | return xxx_messageInfo_DeleteNetDevReq.Marshal(b, m, deterministic) 57 | } 58 | func (m *DeleteNetDevReq) XXX_Merge(src proto.Message) { 59 | xxx_messageInfo_DeleteNetDevReq.Merge(m, src) 60 | } 61 | func (m *DeleteNetDevReq) XXX_Size() int { 62 | return xxx_messageInfo_DeleteNetDevReq.Size(m) 63 | } 64 | func (m *DeleteNetDevReq) XXX_DiscardUnknown() { 65 | xxx_messageInfo_DeleteNetDevReq.DiscardUnknown(m) 66 | } 67 | 68 | var xxx_messageInfo_DeleteNetDevReq proto.InternalMessageInfo 69 | 70 | func (m *DeleteNetDevReq) GetName() string { 71 | if m != nil { 72 | return m.Name 73 | } 74 | return "" 75 | } 76 | 77 | type DeleteNetDevResp struct { 78 | Ret int32 `protobuf:"varint,1,opt,name=ret,proto3" json:"ret,omitempty"` 79 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 80 | XXX_unrecognized []byte `json:"-"` 81 | XXX_sizecache int32 `json:"-"` 82 | } 83 | 84 | func (m *DeleteNetDevResp) Reset() { *m = DeleteNetDevResp{} } 85 | func (m *DeleteNetDevResp) String() string { return proto.CompactTextString(m) } 86 | func (*DeleteNetDevResp) ProtoMessage() {} 87 | func (*DeleteNetDevResp) Descriptor() ([]byte, []int) { 88 | return fileDescriptor_f9ab154255673e38, []int{1} 89 | } 90 | 91 | func (m *DeleteNetDevResp) XXX_Unmarshal(b []byte) error { 92 | return xxx_messageInfo_DeleteNetDevResp.Unmarshal(m, b) 93 | } 94 | func (m *DeleteNetDevResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 95 | return xxx_messageInfo_DeleteNetDevResp.Marshal(b, m, deterministic) 96 | } 97 | func (m *DeleteNetDevResp) XXX_Merge(src proto.Message) { 98 | xxx_messageInfo_DeleteNetDevResp.Merge(m, src) 99 | } 100 | func (m *DeleteNetDevResp) XXX_Size() int { 101 | return xxx_messageInfo_DeleteNetDevResp.Size(m) 102 | } 103 | func (m *DeleteNetDevResp) XXX_DiscardUnknown() { 104 | xxx_messageInfo_DeleteNetDevResp.DiscardUnknown(m) 105 | } 106 | 107 | var xxx_messageInfo_DeleteNetDevResp proto.InternalMessageInfo 108 | 109 | func (m *DeleteNetDevResp) GetRet() int32 { 110 | if m != nil { 111 | return m.Ret 112 | } 113 | return 0 114 | } 115 | 116 | func init() { 117 | proto.RegisterType((*DeleteNetDevReq)(nil), "dpcnisyncer.DeleteNetDevReq") 118 | proto.RegisterType((*DeleteNetDevResp)(nil), "dpcnisyncer.DeleteNetDevResp") 119 | } 120 | 121 | func init() { 122 | proto.RegisterFile("dp_cni_syncer.proto", fileDescriptor_f9ab154255673e38) 123 | } 124 | 125 | var fileDescriptor_f9ab154255673e38 = []byte{ 126 | // 202 bytes of a gzipped FileDescriptorProto 127 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x29, 0x88, 0x4f, 128 | 0xce, 0xcb, 0x8c, 0x2f, 0xae, 0xcc, 0x4b, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 129 | 0xe2, 0x4e, 0x29, 0x48, 0xce, 0xcb, 0x84, 0x08, 0x29, 0xa9, 0x72, 0xf1, 0xbb, 0xa4, 0xe6, 0xa4, 130 | 0x96, 0xa4, 0xfa, 0xa5, 0x96, 0xb8, 0xa4, 0x96, 0x05, 0xa5, 0x16, 0x0a, 0x09, 0x71, 0xb1, 0xe4, 131 | 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x4a, 0x2a, 0x5c, 0x02, 132 | 0xa8, 0xca, 0x8a, 0x0b, 0x84, 0x04, 0xb8, 0x98, 0x8b, 0x52, 0x4b, 0xc0, 0xca, 0x58, 0x83, 0x40, 133 | 0x4c, 0xa3, 0x20, 0x2e, 0x36, 0x88, 0xbc, 0x90, 0x07, 0x17, 0xa7, 0x4b, 0x6a, 0x0e, 0x94, 0x23, 134 | 0xa3, 0x87, 0x64, 0xa3, 0x1e, 0x9a, 0x75, 0x52, 0xb2, 0x78, 0x64, 0x8b, 0x0b, 0x9c, 0x5c, 0xa2, 135 | 0x9c, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x33, 0xf3, 0x4a, 0x52, 136 | 0x73, 0xf4, 0x13, 0xd3, 0x2a, 0x52, 0x0a, 0x74, 0x0b, 0x72, 0x4a, 0xd3, 0x33, 0xf3, 0x8a, 0x75, 137 | 0xd3, 0xf2, 0x8b, 0x74, 0xb3, 0x4b, 0x93, 0x52, 0x8b, 0xf2, 0x52, 0x4b, 0x52, 0x8b, 0xc1, 0x4a, 138 | 0x8a, 0xf2, 0x12, 0x73, 0xf4, 0x91, 0x8c, 0x4d, 0x62, 0x03, 0x7b, 0xdd, 0x18, 0x10, 0x00, 0x00, 139 | 0xff, 0xff, 0x74, 0x57, 0xca, 0xf4, 0x11, 0x01, 0x00, 0x00, 140 | } 141 | -------------------------------------------------------------------------------- /internal/bpf/bpfWrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(c) 2022 Intel Corporation. 3 | * Copyright(c) Red Hat Inc. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include // for XDP_FLAGS_DRV_MODE 20 | #include // for if_nametoindex 21 | #include 22 | #include 23 | #include 24 | #include // for xsk_setup_xdp_prog, bpf_set_link_xdp_fd 25 | 26 | #include "bpfWrapper.h" 27 | #include "log.h" 28 | 29 | #define SO_PREFER_BUSY_POLL 69 30 | #define SO_BUSY_POLL_BUDGET 70 31 | #define EBUSY_CODE_WARNING -16 32 | #define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0) 33 | 34 | int Load_bpf_send_xsk_map(char *ifname) { 35 | 36 | int fd = -1; 37 | int if_index, err; 38 | 39 | Log_Info("%s: disovering if_index for interface %s", __FUNCTION__, ifname); 40 | 41 | if_index = if_nametoindex(ifname); 42 | if (!if_index) { 43 | Log_Error("%s: if_index not valid: %s", __FUNCTION__, ifname); 44 | return -1; 45 | } else { 46 | Log_Info("%s: if_index for interface %s is %d", __FUNCTION__, ifname, if_index); 47 | } 48 | Log_Info("%s: starting setup of xdp program on " 49 | "interface %s (%d)", 50 | __FUNCTION__, ifname, if_index); 51 | 52 | err = xsk_setup_xdp_prog(if_index, &fd); 53 | Log_Info("Error value: %d", err); 54 | if (err) { 55 | Log_Error("%s: setup of xdp program failed, " 56 | "returned: %d", 57 | __FUNCTION__, err); 58 | return -1; 59 | } 60 | 61 | if (fd > 0) { 62 | Log_Info("%s: loaded xdp program on interface %s " 63 | "(%d), file descriptor %d", 64 | __FUNCTION__, ifname, if_index, fd); 65 | return fd; 66 | } 67 | Log_Info("FD value: %d", fd); 68 | return -1; 69 | } 70 | 71 | int Configure_busy_poll(int fd, int busy_timeout, int busy_budget) { 72 | 73 | int sock_opt = 1; 74 | int err; 75 | 76 | Log_Info("%s: setting SO_PREFER_BUSY_POLL on file descriptor %d", __FUNCTION__, fd); 77 | 78 | err = setsockopt(fd, SOL_SOCKET, SO_PREFER_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)); 79 | if (err < 0) { 80 | Log_Error("%s: failed to set SO_PREFER_BUSY_POLL on file " 81 | "descriptor %d, returned: %d", 82 | __FUNCTION__, fd, err); 83 | return 1; 84 | } 85 | 86 | Log_Info("%s: setting SO_BUSY_POLL to %d on file descriptor %d", __FUNCTION__, busy_timeout, 87 | fd); 88 | 89 | sock_opt = busy_timeout; 90 | err = setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)); 91 | if (err < 0) { 92 | Log_Error("%s: failed to set SO_BUSY_POLL on file descriptor " 93 | "%d, returned: %d", 94 | __FUNCTION__, fd, err); 95 | goto err_timeout; 96 | } 97 | 98 | Log_Info("%s: setting SO_BUSY_POLL_BUDGET to %d on file descriptor %d", __FUNCTION__, 99 | busy_budget, fd); 100 | 101 | sock_opt = busy_budget; 102 | err = setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL_BUDGET, (void *)&sock_opt, sizeof(sock_opt)); 103 | if (err < 0) { 104 | Log_Error("%s: failed to set SO_BUSY_POLL_BUDGET on file " 105 | "descriptor %d, returned: %d", 106 | __FUNCTION__, fd, err); 107 | } else { 108 | Log_Info("%s: busy polling budget on file descriptor %d set to " 109 | "%d", 110 | __FUNCTION__, fd, busy_budget); 111 | return 0; 112 | } 113 | 114 | Log_Warning("%s: setsockopt failure, attempting to restore xsk to default state", 115 | __FUNCTION__); 116 | 117 | Log_Warning("%s: unsetting SO_BUSY_POLL on file descriptor %d", __FUNCTION__, fd); 118 | 119 | sock_opt = 0; 120 | err = setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)); 121 | if (err < 0) { 122 | Log_Error("%s: failed to unset SO_BUSY_POLL on file descriptor " 123 | "%d, returned: %d", 124 | __FUNCTION__, fd, err); 125 | return 1; 126 | } 127 | 128 | err_timeout: 129 | Log_Warning("%s: unsetting SO_PREFER_BUSY_POLL on file descriptor %d", __FUNCTION__, fd); 130 | sock_opt = 0; 131 | err = setsockopt(fd, SOL_SOCKET, SO_PREFER_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)); 132 | if (err < 0) { 133 | Log_Error("%s: failed to unset SO_PREFER_BUSY_POLL on file " 134 | "descriptor %d, returned: %d", 135 | __FUNCTION__, fd, err); 136 | return 1; 137 | } 138 | return 0; 139 | } 140 | 141 | int Clean_bpf(char *ifname) { 142 | int if_index, err; 143 | int fd = -1; 144 | struct xdp_multiprog *mp = NULL; 145 | 146 | Log_Info("%s: disovering if_index for interface %s", __FUNCTION__, ifname); 147 | 148 | if_index = if_nametoindex(ifname); 149 | if (!if_index) { 150 | Log_Error("%s: if_index not valid: %s", __FUNCTION__, ifname); 151 | return 1; 152 | } else { 153 | Log_Info("%s: if_index for interface %s is %d", __FUNCTION__, ifname, if_index); 154 | } 155 | 156 | Log_Info("%s: starting removal of xdp program on interface %s (%d)", __FUNCTION__, ifname, 157 | if_index); 158 | 159 | mp = xdp_multiprog__get_from_ifindex(if_index); 160 | if (!mp) { 161 | Log_Info("%s: No programs loaded on : %s", __FUNCTION__, ifname); 162 | return 0; 163 | } 164 | 165 | err = xdp_multiprog__detach(mp); 166 | if (err && err != -EINVAL) { // -EINVAL == No program attached 167 | Log_Error("%s: Removal of xdp program failed, returned: " 168 | "returned: %d", 169 | __FUNCTION__, err); 170 | return -1; 171 | } 172 | 173 | Log_Info("%s: removed xdp program from interface %s (%d)", __FUNCTION__, ifname, if_index); 174 | return 0; 175 | } 176 | -------------------------------------------------------------------------------- /.github/workflows/public-ci.yml: -------------------------------------------------------------------------------- 1 | name: 'Public-CI' 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: # added using https://github.com/step-security/secure-repo 6 | contents: read 7 | 8 | jobs: 9 | 10 | # Super-linter consists of several lint tools 11 | # lint tools are assigned to the pipeline via the env field 12 | # For more information, please see https://github.com/github/super-linter 13 | super-linter: 14 | permissions: 15 | contents: read # for actions/checkout to fetch code 16 | statuses: write # for github/super-linter to mark status of each linter run 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 20 | - name: super-linter 21 | uses: github/super-linter@985ef206aaca4d560cb9ee2af2b42ba44adc1d55 # v4.10.0 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | LINTER_RULES_PATH: / 25 | VALIDATE_BASH: true 26 | VALIDATE_GO: true 27 | VALIDATE_DOCKERFILE_HADOLINT: true 28 | VALIDATE_CLANG_FORMAT: true 29 | VALIDATE_JSON: true 30 | 31 | # Build stage executes binary builds for CNI and device-plugin 32 | build: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 36 | - name: Set up Go 37 | uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0 38 | with: 39 | go-version: 1.19 40 | 41 | - name: Install libbpf and libxdp 42 | run: | 43 | sudo apt update 44 | sudo apt install -y wget build-essential golang 45 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf1_1.1.0-1_amd64.deb 46 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf-dev_1.1.0-1_amd64.deb 47 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp1_1.3.0-2ubuntu2_amd64.deb 48 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp-dev_1.3.0-2ubuntu2_amd64.deb 49 | sudo apt install -y ./libbpf1_1.1.0-1_amd64.deb 50 | sudo apt install -y ./libbpf-dev_1.1.0-1_amd64.deb 51 | sudo apt install -y ./libxdp1_1.3.0-2ubuntu2_amd64.deb 52 | sudo apt install -y ./libxdp-dev_1.3.0-2ubuntu2_amd64.deb 53 | sudo apt install -y clang 54 | sudo apt install -y llvm 55 | sudo apt install -y gcc-multilib 56 | 57 | 58 | 59 | - name: Run build 60 | run: make build 61 | 62 | # go-static-tools scans code base and packages using go-vet, go mod verify and staticcheck 63 | go-static-tools: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 67 | - name: Set up Go 68 | uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 69 | with: 70 | go-version: 1.19 71 | 72 | - name: Install libbpf and libxdp 73 | run: | 74 | sudo apt update 75 | sudo apt install -y wget build-essential golang 76 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf1_1.1.0-1_amd64.deb 77 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf-dev_1.1.0-1_amd64.deb 78 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp1_1.3.0-2ubuntu2_amd64.deb 79 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp-dev_1.3.0-2ubuntu2_amd64.deb 80 | sudo apt install -y ./libbpf1_1.1.0-1_amd64.deb 81 | sudo apt install -y ./libbpf-dev_1.1.0-1_amd64.deb 82 | sudo apt install -y ./libxdp1_1.3.0-2ubuntu2_amd64.deb 83 | sudo apt install -y ./libxdp-dev_1.3.0-2ubuntu2_amd64.deb 84 | 85 | - name: Install staticcheck 86 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 87 | 88 | - name: run static analysis 89 | run: make static-ci 90 | 91 | # unit-test stage 92 | unit-tests: 93 | runs-on: ubuntu-latest 94 | steps: 95 | - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 96 | - name: Set up Go 97 | uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0 98 | with: 99 | go-version: 1.19 100 | 101 | - name: Install libbpf and libxdp 102 | run: | 103 | sudo apt update 104 | sudo apt install -y wget build-essential golang 105 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf1_1.1.0-1_amd64.deb 106 | sudo wget http://security.ubuntu.com/ubuntu/pool/main/libb/libbpf/libbpf-dev_1.1.0-1_amd64.deb 107 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp1_1.3.0-2ubuntu2_amd64.deb 108 | sudo wget https://mirrors.edge.kernel.org/ubuntu/pool/main/x/xdp-tools/libxdp-dev_1.3.0-2ubuntu2_amd64.deb 109 | sudo apt install -y ./libbpf1_1.1.0-1_amd64.deb 110 | sudo apt install -y ./libbpf-dev_1.1.0-1_amd64.deb 111 | sudo apt install -y ./libxdp1_1.3.0-2ubuntu2_amd64.deb 112 | sudo apt install -y ./libxdp-dev_1.3.0-2ubuntu2_amd64.deb 113 | sudo apt install -y clang 114 | sudo apt install -y llvm 115 | sudo apt install -y gcc-multilib 116 | 117 | - name: unit-tests 118 | run: make test 119 | 120 | # Trivy Scan 121 | trivy-scan: 122 | runs-on: ubuntu-latest 123 | permissions: 124 | contents: read 125 | security-events: write 126 | steps: 127 | 128 | - name: Checkout code 129 | uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 130 | 131 | - name: Run Trivy vulnerability scanner in repo mode 132 | uses: aquasecurity/trivy-action@master 133 | with: 134 | scan-type: 'fs' 135 | ignore-unfixed: true 136 | format: 'sarif' 137 | output: 'trivy-results.sarif' 138 | severity: 'CRITICAL' 139 | 140 | - name: Build Docker image 141 | run: make docker 142 | 143 | - name: Generate .tar image 144 | run: docker save -o vul-image.tar afxdp-device-plugin:latest 145 | 146 | 147 | - name: Run Trivy on tarballed image 148 | uses: aquasecurity/trivy-action@master 149 | with: 150 | image-ref: /github/workspace/vul-image.tar 151 | scan-type: 'fs' 152 | ignore-unfixed: false 153 | format: 'sarif' 154 | output: 'trivy-image-results.sarif' 155 | severity: 'CRITICAL' 156 | 157 | - name: Upload Trivy scan results to GitHub Security tab 158 | uses: github/codeql-action/upload-sarif@v2 159 | with: 160 | sarif_file: 'trivy-results.sarif' 161 | 162 | - name: Print sarif file 163 | run: cat < trivy-results.sarif 164 | 165 | - name: Print image sarif file 166 | run: cat trivy-image-results.sarif -------------------------------------------------------------------------------- /docs/sequence.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam noteBorderColor Black 4 | skinparam noteBorderThickness 1 5 | skinparam noteBackgroundColor Yellow 6 | skinparam legendBackgroundColor WhiteSmoke 7 | 8 | skinparam sequence { 9 | BoxBorderColor Black 10 | BoxFontSize 20 11 | 12 | ArrowColor Black 13 | ArrowThickness 1 14 | 15 | ActorBorderColor Black 16 | ActorBorderThickness 3 17 | ActorBackgroundColor Business 18 | ActorFontSize 15 19 | 20 | ParticipantBorderColor Black 21 | ParticipantBorderThickness 1 22 | ParticipantBackgroundColor Business 23 | ParticipantFontSize 15 24 | 25 | LifeLineBorderColor Black 26 | LifeLineBorderThickness 1 27 | LifeLineBackgroundColor LightGray 28 | } 29 | 30 | 31 | legend top right 32 | |= |= Legend | 33 | | | Participants from this project - Device Plugin and CNI | 34 | | | Threads within Device Plugin | 35 | | | Participants external to this project | 36 | | | Code waits, listening on socket | 37 | | | Notes | 38 | endlegend 39 | 40 | 41 | 42 | actor "User" 43 | participant "Linux" 44 | participant "Kubelet" 45 | box "Device Plugin" #LightBlue 46 | participant "DP Main Thread" #dodgerblue 47 | participant "DP UDS Server Thread" #dodgerblue 48 | end box 49 | participant "CNI" #LightBlue 50 | participant "Pod/AF_XDP App" 51 | 52 | == Initialization == 53 | autonumber 54 | 55 | "User" -> "Kubelet": network attachment definition (CNI config) 56 | "User" -> "DP Main Thread": deploy 57 | activate "DP Main Thread" 58 | 59 | "DP Main Thread" -> "DP Main Thread": config.json 60 | "DP Main Thread" -> "Linux": create log file 61 | "DP Main Thread" -> "Linux" : Check host for requirements 62 | "DP Main Thread" -> "Linux": net.Interfaces() 63 | 64 | activate "DP Main Thread" #DarkGray 65 | note right #DarkGray: discover resources 66 | 67 | "Linux" --> "DP Main Thread": interface list 68 | 69 | "DP Main Thread" -> "Linux" : os.Readlink /sys/class/net//device/driver 70 | "Linux" --> "DP Main Thread" : driver name 71 | 72 | activate "DP Main Thread" #SlateGray 73 | note right #SlateGray: loop interfaces, build device list 74 | 75 | deactivate "DP Main Thread" 76 | 77 | autonumber stop 78 | "DP Main Thread" [hidden]-> "DP Main Thread" 79 | autonumber resume 80 | 81 | deactivate "DP Main Thread" 82 | 83 | "DP Main Thread" -> "DP Main Thread": start GRPC 84 | "DP Main Thread" -> "Kubelet": GRPC: register 85 | "DP Main Thread" -> "Kubelet": GRPC: device list 86 | 87 | deactivate "DP Main Thread" 88 | 89 | == Pod Creation == 90 | autonumber 91 | 92 | "User" -> "Kubelet": create pod 93 | "Kubelet" -> "DP Main Thread": GRPC: Allocate(subfunction) 94 | activate "DP Main Thread" 95 | 96 | "DP Main Thread" -> "Linux" : devlink port add pci/0000:3b:00.0 flavour pcisf pfnum 0 sfnum 123 97 | "Linux" --> "DP Main Thread" : pci/port/index (e.g. pci/0000:3b:00.0/12) 98 | 99 | "DP Main Thread" -> "Linux" : devlink port function set pci/0000:3b:00.0/12 state active 100 | "Linux" --> "DP Main Thread" : return 0 101 | 102 | "DP Main Thread" -> "Linux": net.if_nametoindex(subfunction) 103 | "Linux" --> "DP Main Thread": if_index 104 | "DP Main Thread" -> "Linux": bpf.xsk_setup_xdp_prog(if_index) 105 | "Linux" --> "DP Main Thread": XSK file descriptor 106 | 107 | "DP Main Thread" -> "Linux": create UDS 108 | 109 | "DP Main Thread" -> "DP UDS Server Thread" ** : create & start UDS server 110 | "DP Main Thread" -> "DP UDS Server Thread" : subfunction, XSK FD, UDS filepath 111 | 112 | hnote over "DP UDS Server Thread" #Turquoise 113 | listen for 114 | connection 115 | endhnote 116 | 117 | "DP Main Thread" --> "Kubelet": GRPC: AllocateResponse(UDS mount path, pod env vars) 118 | deactivate "DP Main Thread" 119 | 120 | autonumber stop 121 | "Kubelet" -[#Red]>> "Pod/AF_XDP App" : Kubelet starts creating the pod around now 122 | autonumber resume 123 | 124 | "Kubelet" -> "CNI" : cmdAdd(subfunction, namespace, config) 125 | activate "CNI" 126 | "CNI" -> "CNI" : cni.IPAM 127 | 128 | "CNI" -> "Pod/AF_XDP App" : place subfunction in pod netns 129 | "CNI" -> "Kubelet" : return 0 130 | 131 | deactivate "CNI" 132 | 133 | autonumber stop 134 | 135 | == Pod Running == 136 | 137 | "Kubelet" -> "Pod/AF_XDP App" : start pod 138 | "Pod/AF_XDP App" -> "Pod/AF_XDP App" : application start 139 | activate "Pod/AF_XDP App" 140 | 141 | "Pod/AF_XDP App" -> "DP UDS Server Thread": /connect,hostname 142 | note right 143 | AF_XDP application starts 144 | UDS handshake begins 145 | end note 146 | activate "DP UDS Server Thread" 147 | "DP UDS Server Thread" -> "Kubelet": podresources.ListPodResourcesRequest() 148 | "Kubelet" --> "DP UDS Server Thread": podresources.ListPodResourcesResponse() 149 | "DP UDS Server Thread" -> "DP UDS Server Thread": validate hostname against subfunction 150 | "DP UDS Server Thread" --> "Pod/AF_XDP App": /host_ok 151 | deactivate "DP UDS Server Thread" 152 | hnote over "DP UDS Server Thread" #Turquoise 153 | listen for 154 | request 155 | end note 156 | 157 | "Pod/AF_XDP App" -> "DP UDS Server Thread": /xsk_map_fd,subfunction 158 | activate "DP UDS Server Thread" 159 | "DP UDS Server Thread" -> "DP UDS Server Thread": FD for subfunction 160 | "DP UDS Server Thread" --> "Pod/AF_XDP App": /fd_ack,FD 161 | deactivate "DP UDS Server Thread" 162 | 163 | hnote over "DP UDS Server Thread" #Turquoise 164 | listen for 165 | request 166 | endhnote 167 | 168 | "Pod/AF_XDP App" -> "DP UDS Server Thread": /config_busy_poll,FD 169 | activate "DP UDS Server Thread" 170 | "DP UDS Server Thread" -> "Linux" : bpf.setsockopt(FD, BUSY_POLL) 171 | "Linux" --> "DP UDS Server Thread" : return 0 172 | "DP UDS Server Thread" --> "Pod/AF_XDP App": /config_busy_poll_ack 173 | deactivate "DP UDS Server Thread" 174 | 175 | hnote over "DP UDS Server Thread" #Turquoise 176 | listen for 177 | request 178 | endhnote 179 | 180 | "Pod/AF_XDP App" -> "DP UDS Server Thread": /fin 181 | activate "DP UDS Server Thread" 182 | "DP UDS Server Thread" --> "Pod/AF_XDP App": /fin_ack 183 | 184 | note right: UDS handshake ends 185 | deactivate "DP UDS Server Thread" 186 | 187 | destroy "DP UDS Server Thread" 188 | 189 | == Pod Deletion == 190 | autonumber 191 | 192 | "User" -> "Kubelet": delete pod 193 | "Kubelet" -> "Pod/AF_XDP App" : stop pod 194 | deactivate "Pod/AF_XDP App" 195 | "Kubelet" -> "CNI" : cmdDel(subfunction, config) 196 | activate "CNI" 197 | "CNI" -> "Pod/AF_XDP App" : subfunction from pod to host netns 198 | "Pod/AF_XDP App" --> "CNI" : subfunction 199 | "CNI" -> "Linux" : clear ethtool filters 200 | "CNI" -> "CNI" : clear IPAM 201 | "CNI" -> "Linux": net.if_nametoindex(subfunction) 202 | "Linux" --> "CNI": if_index 203 | "CNI" -> "Linux": bpf.set_link_xdp_fd(if_index, -1) 204 | "Linux" --> "CNI": return 0 205 | 206 | "CNI" -> "Linux" : devlink port list | grep subfunction 207 | "Linux" --> "CNI" : pci/port/index (e.g. pci/0000:3b:00.0/12) 208 | 209 | "CNI" -> "Linux" : devlink port function set pci/0000:3b:00.0/12 state inactive 210 | "Linux" --> "CNI" : return 0 211 | 212 | "CNI" -> "Linux" : devlink port del pci/0000:3b:00.0/12 213 | "Linux" --> "CNI" : return 0 214 | 215 | "CNI" --> "Kubelet": return 0 216 | deactivate "CNI" 217 | 218 | "Kubelet" -> "Pod/AF_XDP App" : delete pod 219 | deactivate "Pod/AF_XDP App" 220 | 221 | @enduml -------------------------------------------------------------------------------- /internal/tools/tools_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 tools 17 | 18 | import ( 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/require" 21 | "strings" 22 | "testing" 23 | ) 24 | 25 | func TestArrayContains(t *testing.T) { 26 | testCases := []struct { 27 | str string 28 | array []string 29 | expected bool 30 | }{ 31 | { 32 | str: "ens785f2", 33 | array: []string{"ens785f2", "ens785f3", "eno1", "veth7b0b36aa@if3"}, 34 | expected: true, 35 | }, 36 | { 37 | str: "cni0", 38 | array: []string{"ens785f2", "ens785f3", "eno1", "veth7b0b36aa@if3"}, 39 | expected: false, 40 | }, 41 | { 42 | str: "eth0", 43 | array: []string{"eth0"}, 44 | expected: true, 45 | }, 46 | { 47 | str: "veth0", 48 | array: []string{}, 49 | expected: false, 50 | }, 51 | { 52 | str: "veth0", 53 | array: nil, 54 | expected: false, 55 | }, 56 | } 57 | for i, tc := range testCases { 58 | assert.Equal(t, tc.expected, ArrayContains(tc.array, tc.str), "Should be equal: test case %d", i) 59 | } 60 | } 61 | 62 | func TestRemoveFromArray(t *testing.T) { 63 | testCases := []struct { 64 | rem string 65 | array []string 66 | expected []string 67 | }{ 68 | { 69 | rem: "veth0", 70 | array: []string{"ens785f2", "ens785f3", "eno1", "veth7b0b36aa@if3"}, 71 | expected: []string{"ens785f2", "ens785f3", "eno1", "veth7b0b36aa@if3"}, 72 | }, 73 | { 74 | rem: "eno1", 75 | array: []string{"ens785f2", "ens785f3", "eno1", "veth7b0b36aa@if3"}, 76 | expected: []string{"ens785f2", "ens785f3", "veth7b0b36aa@if3"}, 77 | }, 78 | { 79 | rem: "cni0", 80 | array: []string{"ens785f2", "cni0", "cni0", "veth7b0b36aa@if3"}, 81 | expected: []string{"ens785f2", "cni0", "veth7b0b36aa@if3"}, 82 | }, 83 | { 84 | rem: "", 85 | array: []string{}, 86 | expected: []string{}, 87 | }, 88 | { 89 | rem: "", 90 | array: nil, 91 | expected: nil, 92 | }, 93 | } 94 | for i, tc := range testCases { 95 | assert.Equal(t, tc.expected, RemoveFromArray(tc.array, tc.rem), "Should be equal: test case %d", i) 96 | } 97 | } 98 | 99 | func TestFilePathExists(t *testing.T) { 100 | testCases := []struct { 101 | path string 102 | expected bool 103 | }{ 104 | { 105 | path: "", 106 | expected: false, 107 | }, 108 | { 109 | path: "\n", 110 | expected: false, 111 | }, 112 | { 113 | path: "#", 114 | expected: false, 115 | }, 116 | { 117 | path: "/home", 118 | expected: true, 119 | }, 120 | { 121 | path: "./../../internal", 122 | expected: true, 123 | }, 124 | { 125 | path: "./tools_test.go", 126 | expected: true, 127 | }, 128 | } 129 | for i, tc := range testCases { 130 | output, _ := FilePathExists(tc.path) 131 | assert.Equal(t, tc.expected, output, "Should be equal: test case %d", i) 132 | } 133 | } 134 | 135 | func TestArrayContainsPrefix(t *testing.T) { 136 | testCases := []struct { 137 | str string 138 | array []string 139 | expected bool 140 | }{ 141 | { 142 | str: "", 143 | array: []string{}, 144 | expected: false, 145 | }, 146 | { 147 | str: "", 148 | array: []string{""}, 149 | expected: true, 150 | }, 151 | { 152 | str: "eno1", 153 | array: []string{"eno1"}, 154 | expected: true, 155 | }, 156 | { 157 | str: "veth0", 158 | array: []string{"cni1", "ens", "veth"}, 159 | expected: true, 160 | }, 161 | } 162 | for i, tc := range testCases { 163 | assert.Equal(t, tc.expected, ArrayContainsPrefix(tc.array, tc.str), "Should be equal: test case %d", i) 164 | } 165 | } 166 | 167 | func TestPrettyString(t *testing.T) { 168 | type TestData struct { 169 | Str string 170 | Integer int 171 | Array []string 172 | hidden string 173 | } 174 | makeTestData := func(str string, integer int, array []string, hidden string) TestData { 175 | return TestData{str, integer, array, hidden} 176 | } 177 | testCases := []struct { 178 | object TestData 179 | expected string 180 | }{ 181 | { 182 | object: makeTestData(`"`, -0, []string{""}, "hello world"), 183 | expected: strings.Replace( 184 | `{ 185 | "Str": "\"", 186 | "Integer": 0, 187 | "Array": [ 188 | "" 189 | ] 190 | }`, "\t", "", -1), 191 | }, 192 | { 193 | object: makeTestData("afxdp", 30, []string{"veth1", "cni0"}, "veth7b0b36aa@if3"), 194 | expected: strings.Replace( 195 | `{ 196 | "Str": "afxdp", 197 | "Integer": 30, 198 | "Array": [ 199 | "veth1", 200 | "cni0" 201 | ] 202 | }`, "\t", "", -1), 203 | }, 204 | { 205 | object: makeTestData("", 0, nil, ""), 206 | expected: strings.Replace( 207 | `{ 208 | "Str": "", 209 | "Integer": 0, 210 | "Array": null 211 | }`, "\t", "", -1), 212 | }, 213 | { 214 | object: makeTestData("//", 034, []string{}, ""), 215 | expected: strings.Replace( 216 | `{ 217 | "Str": "//", 218 | "Integer": 28, 219 | "Array": [] 220 | }`, "\t", "", -1), 221 | }, 222 | } 223 | for i, tc := range testCases { 224 | output, _ := PrettyString(tc.object) 225 | assert.Equal(t, tc.expected, output, "Should be equal: test case %d", i) 226 | } 227 | } 228 | 229 | func TestKernelVersionInt(t *testing.T) { 230 | testCases := []struct { 231 | name string 232 | version string 233 | expResult int64 234 | expError error 235 | }{ 236 | { 237 | name: "first_test_5.4.0-89-generic", 238 | version: "5.4.0-89-generic", 239 | expResult: 500040000, 240 | expError: nil, 241 | }, 242 | { 243 | name: "second_test_5.4.0-89", 244 | version: "5.4.0-89", 245 | expResult: 500040000, 246 | expError: nil, 247 | }, 248 | { 249 | name: "third_test_5.4.0-generic", 250 | version: "5.4.0-generic", 251 | expResult: 500040000, 252 | expError: nil, 253 | }, 254 | { 255 | name: "fourth_test_5.4.0", 256 | version: "5.4.0", 257 | expResult: 500040000, 258 | expError: nil, 259 | }, 260 | { 261 | name: "fifth_test_5.4.0--generic", 262 | version: "5.4.0--generic", 263 | expResult: 500040000, 264 | expError: nil, 265 | }, 266 | { 267 | name: "sixth_test_5.4.0-43-.generic", 268 | version: "5.4.0-43-.generic", 269 | expResult: 500040000, 270 | expError: nil, 271 | }, 272 | { 273 | name: "seventh_test_5.19.0+", 274 | version: "5.19.0+", 275 | expResult: 500190000, 276 | expError: nil, 277 | }, 278 | } 279 | 280 | for _, tc := range testCases { 281 | t.Run(tc.name, func(t *testing.T) { 282 | 283 | actualReturn, err := KernelVersionInt(tc.version) 284 | 285 | assert.Equal(t, tc.expResult, actualReturn, "Returned value does not match expected value") 286 | 287 | if err != nil { 288 | require.Error(t, tc.expError, err, "Error was expected") 289 | assert.Contains(t, err.Error(), tc.expError.Error(), "Unexpected error returned") 290 | } 291 | }) 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /internal/networking/networking_fake.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 networking 17 | 18 | /* 19 | FakeHandler interface extends the Handler interface to provide additional testing methods. 20 | */ 21 | type FakeHandler interface { 22 | Handler 23 | SetHostDevices(interfaceNames map[string][]string) 24 | } 25 | 26 | /* 27 | fakeHandler implements the FakeHandler interface. 28 | */ 29 | type fakeHandler struct{} 30 | 31 | /* 32 | interfaceList holds a map of drivers and net.Interface objects, representing fake netdev objects. 33 | */ 34 | var interfaceList map[string]*Device 35 | 36 | /* 37 | NewFakeHandler returns an implementation of the FakeHandler interface. 38 | */ 39 | func NewFakeHandler() FakeHandler { 40 | return &fakeHandler{} 41 | } 42 | 43 | /* 44 | GetHostDevices returns a map of devices on the host 45 | */ 46 | func (r *fakeHandler) GetHostDevices() (map[string]*Device, error) { 47 | 48 | return interfaceList, nil 49 | } 50 | 51 | /* 52 | SetHostDevices is a function used to dynamically setup mock devices and drivers 53 | */ 54 | func (r *fakeHandler) SetHostDevices(interfaceMap map[string][]string) { 55 | interfaceList = make(map[string]*Device) 56 | 57 | for driver, interfaceNames := range interfaceMap { 58 | for _, name := range interfaceNames { 59 | netDev, _ := newPrimaryDevice(name, driver, "1234", "1234", r) 60 | interfaceList[name] = netDev 61 | } 62 | } 63 | } 64 | 65 | /* 66 | GetDeviceDriver takes a device name and returns the driver type. 67 | In this fakeHandler it returns the driver of the fake netdev. 68 | */ 69 | func (r *fakeHandler) GetDeviceDriver(interfaceName string) (string, error) { 70 | return interfaceList[interfaceName].Driver() 71 | } 72 | 73 | /* 74 | GetDevicePci takes a device name and returns the pci address. 75 | In this fakeHandler it returns a dummy pci address. 76 | */ 77 | func (r *fakeHandler) GetDevicePci(interfaceName string) (string, error) { 78 | return "0000:18:00.3", nil 79 | } 80 | 81 | /* 82 | IPAddresses takes a netdev name and returns its IP addresses 83 | In this fakeHandler it returns the IP of the fake netdev. 84 | */ 85 | func (r *fakeHandler) GetIPAddresses(interfaceName string) ([]string, error) { 86 | var addrs []string 87 | return addrs, nil 88 | } 89 | 90 | /* 91 | CycleDevice takes a netdave name and sets the device 'UP', then 'DOWN' 92 | In this fake handler it does nothing. 93 | */ 94 | func (r *fakeHandler) CycleDevice(interfaceName string) error { 95 | return nil 96 | } 97 | 98 | /* 99 | SetQueueSize sets the queue size for the netdev. 100 | In this fake handler it does nothing. 101 | */ 102 | func (r *fakeHandler) SetQueueSize(interfaceName string, size string) error { 103 | return nil 104 | } 105 | 106 | /* 107 | SetDefaultQueueSize sets the netdev queue size back to default. 108 | In this fake handler it does nothing. 109 | */ 110 | func (r *fakeHandler) SetDefaultQueueSize(interfaceName string) error { 111 | return nil 112 | } 113 | 114 | /* 115 | GetMacAddress takes a device name and returns the MAC-address. 116 | This function uses fake handler, its purpose is for unit-testing only 117 | */ 118 | func (r *fakeHandler) GetMacAddress(device string) (string, error) { 119 | return "", nil 120 | } 121 | 122 | /* 123 | NetDevExists takes a device name and verifies if device exists on host. 124 | This function uses fake handler, its purpose is for unit-testing 125 | */ 126 | func (r *fakeHandler) NetDevExists(device string) (bool, error) { 127 | return true, nil 128 | } 129 | 130 | /* 131 | CreateCdqSubfunction takes the PCI address of a port and a subfunction number 132 | It creates that subfunction on top of that port and activates it 133 | In this fake handler it does nothing 134 | */ 135 | func (r *fakeHandler) CreateCdqSubfunction(parentPci string, pfnum string, sfnum string) error { 136 | return nil 137 | } 138 | 139 | /* 140 | DeleteCdqSubfunction takes the port index of a subfunction, deactivates and deletes it 141 | In this fake handler it does nothing 142 | */ 143 | func (r *fakeHandler) DeleteCdqSubfunction(portIndex string) error { 144 | return nil 145 | } 146 | 147 | /* 148 | IsCdqSubfunction takes a netdev name and returns true if is a CDQ subfunction 149 | In this fake handler it currently always returns true 150 | */ 151 | func (r *fakeHandler) IsCdqSubfunction(name string) (bool, error) { 152 | return true, nil 153 | } 154 | 155 | /* 156 | GetCdqPortIndex takes a netdev name and returns the port index (pci/sfnum) 157 | Note this function only works on physical devices and CDQ subfunctions 158 | Other netdevs will return a "device not found by devlink" error 159 | In this fake handler it currently returns an empty string 160 | */ 161 | func (r *fakeHandler) GetCdqPortIndex(netdev string) (string, error) { 162 | return "", nil 163 | } 164 | 165 | /* 166 | GetCdqPfnum takes a netdev name and returns the physical port number / pfnum 167 | Note this function only works on physical devices and CDQ subfunctions 168 | Other netdevs will return a "device not found by devlink" error 169 | In this fake handler it currently returns an empty string 170 | */ 171 | func (r *fakeHandler) GetCdqPfnum(netdev string) (string, error) { 172 | return "", nil 173 | } 174 | 175 | /* 176 | NumAvailableCdqSubfunctions takes the PCI of a physical port and returns how 177 | many unused CDQ subfunctions are available 178 | In this fake handler it currently returns 0 179 | */ 180 | func (r *fakeHandler) NumAvailableCdqSubfunctions(interfaceName string) (int, error) { 181 | return 0, nil 182 | } 183 | 184 | /* 185 | SetEthtool applies ethtool filters on the physical device during cmdAdd(). 186 | Ethtool filters are set via the DP config.json file. This function uses fake handler, 187 | its purpose is for unit-testing only. 188 | */ 189 | func (r *fakeHandler) SetEthtool(ethtoolCmd []string, interfaceName string, ipResult string) error { 190 | return nil 191 | } 192 | 193 | /* 194 | DeleteEthtool sets the default queue size ethtool filter. 195 | It also removes perfect-flow ethtool filter entries during cmdDel() 196 | This function uses fake handler, its purpose is for unit-testing 197 | */ 198 | func (r *fakeHandler) DeleteEthtool(interfaceName string) error { 199 | return nil 200 | } 201 | 202 | /* 203 | GetDeviceFromFile extracts device map fields from the device file (device.json). 204 | It creates and populates a new instance of the device map with the device file field values 205 | and returns the device object.This function uses fake handler, its purpose is for unit-testing 206 | */ 207 | func (r *fakeHandler) GetDeviceFromFile(deviceName string, filepath string) (*Device, error) { 208 | return &Device{name: "fakeDevice", netHandler: r}, nil 209 | } 210 | 211 | /* 212 | WriteDeviceFile creates and writes the device map fields to file, enabling the 213 | CNI to read device information.This function uses fake handler, its purpose is for unit-testing 214 | */ 215 | func (r *fakeHandler) WriteDeviceFile(device *Device, filepath string) error { 216 | return nil 217 | } 218 | 219 | func (r *fakeHandler) GetDeviceByMAC(mac string) (string, error) { 220 | return "", nil 221 | } 222 | 223 | func (r *fakeHandler) GetDeviceByPCI(pci string) (string, error) { 224 | return "", nil 225 | } 226 | 227 | func (r *fakeHandler) IsPhysicalPort(name string) (bool, error) { 228 | return false, nil 229 | } 230 | -------------------------------------------------------------------------------- /internal/cni/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 cni 17 | 18 | import ( 19 | "errors" 20 | "github.com/containernetworking/cni/pkg/skel" 21 | "github.com/containernetworking/cni/pkg/types" 22 | "github.com/intel/afxdp-plugins-for-kubernetes/internal/bpf" 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | "testing" 26 | ) 27 | 28 | func TestGetConfig(t *testing.T) { 29 | netConf := types.NetConf{ 30 | CNIVersion: "0.3.0", 31 | Name: "test-network", 32 | Type: "afxdp", 33 | Capabilities: map[string]bool(nil), 34 | IPAM: types.IPAM{Type: ""}, 35 | DNS: types.DNS{Nameservers: []string(nil), Domain: "", 36 | Search: []string(nil), 37 | Options: []string(nil)}, 38 | RawPrevResult: map[string]interface{}(nil), 39 | PrevResult: types.Result(nil), 40 | } 41 | 42 | testCases := []struct { 43 | name string 44 | config string 45 | expConfig *NetConfig 46 | expErr error 47 | }{ 48 | { 49 | name: "load good config 1", 50 | config: `{"cniVersion":"0.3.0","deviceID":"dev1","name":"test-network","pciBusID":"","type":"afxdp","mode":"cdq","Queues":"4"}`, 51 | expConfig: &NetConfig{NetConf: netConf, Device: "dev1", Mode: "cdq", Queues: "4"}, 52 | }, 53 | { 54 | name: "load no config", 55 | config: `{ }`, 56 | expConfig: nil, 57 | expErr: errors.New("validate(): no device specified"), 58 | }, 59 | { 60 | name: "load bad config 1 - no JSON Format", 61 | config: ``, 62 | expConfig: nil, 63 | expErr: errors.New("unexpected end of JSON input"), 64 | }, 65 | 66 | { 67 | name: "load bad config 2 - Missing Brace", 68 | config: `{`, 69 | expConfig: nil, 70 | expErr: errors.New("loadConf(): failed to load network configuration: unexpected end of JSON input"), 71 | }, 72 | { 73 | name: "load bad config 3 - empty braces", 74 | config: `{}`, 75 | expConfig: nil, 76 | expErr: errors.New("validate(): no device specified"), 77 | }, 78 | { 79 | name: "load bad config 4 - incorrect JSON format", 80 | config: `{"cniVersion":"0.3.0","deviceID":" },`, 81 | expConfig: nil, 82 | expErr: errors.New("loadConf(): failed to load network configuration: unexpected end of JSON input"), 83 | }, 84 | { 85 | name: "load bad config 5 - invalid character", 86 | config: `{"cniVersion":"0.3.0","deviceID":"dev_1","name":"test-network","pciBusID":"","type":"afxdp"}}`, 87 | expConfig: nil, 88 | expErr: errors.New("loadConf(): failed to load network configuration: invalid character '}' after top-level value"), 89 | }, 90 | { 91 | name: "load bad config 6 - invalid character 2", 92 | config: `{"cniVersion":"0.3.0",%"deviceID":"dev_1","name":"test-network",%"pciBusID":"","type":"afxdp"}}`, 93 | expConfig: nil, 94 | expErr: errors.New("loadConf(): failed to load network configuration: invalid character '%' looking for beginning of object key string"), 95 | }, 96 | { 97 | name: "load good config 7 - bad device name", 98 | config: `{"cniVersion":"0.3.0","deviceID":"dev1^","name":"test-network","pciBusID":"","type":"afxdp","mode":"primary","Queues":"4"}`, 99 | expConfig: nil, 100 | expErr: errors.New("loadConf(): Config validation error: deviceID: device names must only contain letters, numbers and selected symbols"), 101 | }, 102 | } 103 | 104 | for _, tc := range testCases { 105 | 106 | t.Run(tc.name, func(t *testing.T) { 107 | 108 | rawConfig := []byte(tc.config) 109 | cfg, err := loadConf(rawConfig) 110 | 111 | if err == nil { 112 | assert.Equal(t, tc.expErr, err, "Error was expected") 113 | } else { 114 | require.Error(t, tc.expErr, "Unexpected error returned") 115 | assert.Contains(t, err.Error(), tc.expErr.Error(), "Unexpected error returned") 116 | } 117 | assert.Equal(t, tc.expConfig, cfg, "Returned unexpected config") 118 | 119 | }) 120 | } 121 | } 122 | 123 | func TestCmdAdd(t *testing.T) { 124 | args := &skel.CmdArgs{} 125 | 126 | testCases := []struct { 127 | name string 128 | netConfStr string 129 | netNS string 130 | expError string 131 | fakeErr error 132 | }{ 133 | { 134 | name: "fail to parse netConf - no braces", 135 | netConfStr: "", 136 | netNS: "", 137 | expError: "loadConf(): failed to load network configuration: unexpected end of JSON input", 138 | }, 139 | { 140 | name: "fail to parse netConf - no arguments", 141 | netConfStr: "{}", 142 | netNS: "", 143 | expError: "validate(): no device specified", 144 | }, 145 | 146 | { 147 | name: "fail to parse netConf - missing brace", 148 | netConfStr: "{ ", 149 | netNS: "", 150 | expError: "loadConf(): failed to load network configuration: unexpected end of JSON input", 151 | }, 152 | 153 | { 154 | name: "no device name", 155 | netConfStr: `{"cniVersion":"0.3.0","deviceID":"","name":"test-network","pciBusID":"","type":"afxdp"}`, 156 | netNS: "", 157 | expError: "validate(): no device specified", 158 | }, 159 | 160 | { 161 | name: "fail to open netns - bad netns", 162 | netConfStr: `{"cniVersion":"0.3.0","deviceID":"dev1","name":"test-network","pciBusID":"","type":"afxdp"}`, 163 | netNS: "B@dN%eTNS", 164 | expError: "cmdAdd(): failed to open container netns \"B@dN%eTNS\": failed to Statfs \"B@dN%eTNS\": no such file or directory", 165 | }, 166 | } 167 | 168 | for _, tc := range testCases { 169 | 170 | t.Run(tc.name, func(t *testing.T) { 171 | 172 | args.StdinData = []byte(tc.netConfStr) 173 | args.Netns = tc.netNS 174 | err := CmdAdd(args) 175 | 176 | if tc.expError == " " { 177 | require.Error(t, err, "Unexpected error") 178 | } else { 179 | require.Error(t, err, "Unexpected error") 180 | assert.Contains(t, err.Error(), tc.expError, "Unexpected error") 181 | } 182 | 183 | }) 184 | } 185 | } 186 | 187 | func TestCmdDel(t *testing.T) { 188 | args := &skel.CmdArgs{} 189 | 190 | testCases := []struct { 191 | name string 192 | netConfStr string 193 | netNS string 194 | expError string 195 | fakeErr error 196 | }{ 197 | { 198 | name: "bad load configuration - empty configuration", 199 | netConfStr: "", 200 | expError: "loadConf(): failed to load network configuration: unexpected end of JSON input", 201 | }, 202 | { 203 | name: "bad load configuration - no arguments and no no device specified", 204 | netConfStr: "{} ", 205 | expError: "validate(): no device specified", 206 | }, 207 | 208 | { 209 | name: "bad load configuration - inncorrect JSON Formatting", 210 | netConfStr: "{ ", 211 | expError: "loadConf(): failed to load network configuration: unexpected end of JSON input", 212 | }, 213 | } 214 | 215 | for _, tc := range testCases { 216 | 217 | t.Run(tc.name, func(t *testing.T) { 218 | bpfHandler = bpf.NewFakeHandler() 219 | args.StdinData = []byte(tc.netConfStr) 220 | args.Netns = tc.netNS 221 | err := CmdDel(args) 222 | 223 | if tc.expError == " " { 224 | require.Error(t, err, "Unexpected error") 225 | } else { 226 | require.Error(t, err, "Unexpected error") 227 | assert.Contains(t, err.Error(), tc.expError, "Unexpected error") 228 | } 229 | }) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright(c) 2022 Intel Corporation. 2 | # Copyright(c) Red Hat Inc. 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 | .PHONY: help 16 | help: ## Display this help. 17 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 18 | 19 | excluded_from_utests = "/test/e2e|/test/fuzz" 20 | 21 | .PHONY: all e2e 22 | 23 | all: format build test static 24 | 25 | clangformat: 26 | @echo "****** Clang Format ******" 27 | @echo 28 | -clang-format -i -style=file internal/bpf/*.c internal/bpf/*.h 29 | -clang-format -i -style=file internal/bpf/xdp-pass/*.c 30 | -clang-format -i -style=file internal/bpf/xdp-afxdp-redirect/*.c 31 | @echo 32 | @echo 33 | 34 | format: clangformat 35 | @echo "****** Go Format ******" 36 | @echo 37 | -go fmt github.com/intel/afxdp-plugins-for-kubernetes/... 38 | @echo 39 | @echo 40 | 41 | buildxdp: 42 | @echo "****** Build xdp_pass ******" 43 | make -C ./internal/bpf/xdp-pass/ 44 | @echo "****** Build xdp_afxdp_redirect ******" 45 | make -C ./internal/bpf/xdp-afxdp-redirect/ 46 | @echo 47 | 48 | buildc: 49 | @echo "****** Build BPF ******" 50 | @echo 51 | gcc ./internal/bpf/bpfWrapper.c -lxdp -c -o ./internal/bpf/bpfWrapper.o 52 | ar rs ./internal/bpf/libwrapper.a ./internal/bpf/bpfWrapper.o &> /dev/null 53 | @echo 54 | @echo 55 | 56 | builddp: buildc buildxdp 57 | @echo "****** Build DP ******" 58 | @echo 59 | go build -o ./bin/afxdp-dp ./cmd/deviceplugin 60 | @echo 61 | @echo 62 | 63 | buildcni: buildc 64 | @echo "****** Build CNI ******" 65 | @echo 66 | go build -ldflags="-extldflags=-static" -tags netgo -o ./bin/afxdp ./cmd/cni 67 | @echo 68 | @echo 69 | 70 | build: builddp buildcni 71 | 72 | ##@ General Build - assumes K8s environment is already setup 73 | docker: ## Build docker image 74 | @echo "****** Docker Image ******" 75 | @echo 76 | docker build -t afxdp-device-plugin -f images/amd64.dockerfile . 77 | @echo 78 | @echo 79 | 80 | podman: ## Build podman image 81 | @echo "****** Podman Image ******" 82 | @echo 83 | podman build -t afxdp-device-plugin -f images/amd64.dockerfile . 84 | @echo 85 | @echo 86 | 87 | image: 88 | if $(MAKE) podman; then \ 89 | echo "Podman build succeeded"; \ 90 | else \ 91 | echo "Podman build failed, trying docker.."; \ 92 | $(MAKE) docker; \ 93 | fi 94 | 95 | undeploy: ## Undeploy the Deamonset 96 | @echo "****** Stop Daemonset ******" 97 | @echo 98 | kubectl delete -f ./deployments/daemonset.yml --ignore-not-found=true 99 | @echo 100 | @echo 101 | 102 | deploy: image undeploy ## Deploy the Deamonset and CNI 103 | @echo "****** Deploy Daemonset ******" 104 | @echo 105 | kubectl create -f ./deployments/daemonset.yml 106 | @echo 107 | @echo 108 | 109 | test: buildc 110 | @echo "****** Unit Tests ******" 111 | @echo 112 | go test $(shell go list ./... | grep -vE $(excluded_from_utests) | grep -v "/internal/resourcesapi") 113 | @echo 114 | @echo 115 | 116 | e2e: build 117 | @echo "****** Basic E2E ******" 118 | @echo 119 | cd test/e2e/ && ./e2e-test.sh 120 | @echo 121 | @echo 122 | 123 | e2efull: build 124 | @echo "****** Full E2E ******" 125 | @echo 126 | cd test/e2e/ && ./e2e-test.sh --full 127 | @echo 128 | @echo 129 | 130 | e2edaemon: image 131 | @echo "****** E2E Daemonset ******" 132 | @echo 133 | cd test/e2e/ && ./e2e-test.sh --daemonset 134 | @echo 135 | @echo 136 | 137 | e2efulldaemon: image 138 | @echo "****** Full E2E DaemSet ******" 139 | @echo 140 | cd test/e2e/ && ./e2e-test.sh --full --daemonset 141 | @echo 142 | @echo 143 | 144 | # static-ci: consists of static analysis tools required for the public CI 145 | # repository workflow /.github/workflows/public-ci.yml 146 | # Note: the public repository CI comprises of further static analysis tools via the 147 | # superlinter job: golangci-lint, hadolint, clang-format and shellcheck 148 | 149 | static-ci: 150 | @echo "****** Verify dependencies ******" 151 | @echo 152 | go mod verify 153 | @echo 154 | @echo 155 | @echo "****** Run staticcheck ******" 156 | @echo 157 | staticcheck ./... 158 | @echo 159 | @echo 160 | @echo "****** Go Vet ******" 161 | @echo 162 | for pkg in $$(go list github.com/intel/afxdp-plugins-for-kubernetes/...); do echo $$pkg && go vet $$pkg; done 163 | @echo 164 | @echo 165 | 166 | # static: consists of static analysis tools required for internal CI repository workflows and locally 167 | # run tests. static includes static-ci test module. 168 | static: static-ci 169 | @echo "****** GolangCI-Lint ******" 170 | @echo 171 | golangci-lint run 172 | @echo 173 | @echo 174 | @echo "****** Hadolint ******" 175 | @echo 176 | for file in $$(find . -type f -iname "*dockerfile*" -not -path "./.git/*"); do echo $$file && docker run --rm -i hadolint/hadolint < $$file; done 177 | @echo 178 | @echo 179 | @echo "****** Shellcheck ******" 180 | @echo 181 | for file in $$(find . -iname "*.sh" -not -path "./.git/*"); do echo $$file && shellcheck $$file; done 182 | @echo 183 | @echo 184 | @echo "****** Trivy ******" 185 | @echo 186 | trivy image afxdp-device-plugin --no-progress --format json 187 | trivy fs . --no-progress --format json 188 | @echo 189 | @echo 190 | 191 | cloc: format 192 | @echo "****** Update CLOC ******" 193 | @echo 194 | @cloc $(shell git ls-files) 195 | sed -i "/<\!---clocstart--->/,/<\!---clocend--->/c\<\!---clocstart--->\n\`\`\`\n$$(cloc $$(git ls-files) | sed -n '/-----/,$$p' | sed -z 's/\n/\\n/g')\n\`\`\`\n\<\!---clocend--->" README.md 196 | @echo 197 | @echo 198 | 199 | clean: 200 | @echo "****** Cleanup ******" 201 | @echo 202 | rm -f ./bin/afxdp 203 | rm -f ./bin/afxdp-dp 204 | rm -f ./internal/bpf/bpfWrapper.o 205 | rm -f ./internal/bpf/libwrapper.a 206 | @echo 207 | @echo 208 | 209 | ##@ General setup 210 | 211 | .PHONY: setup-flannel 212 | setup-flannel: ## Setup flannel 213 | kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml 214 | 215 | .PHONY: setup-multus 216 | setup-multus: ## Setup multus 217 | kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml 218 | 219 | ##@ Kind Deployment - sets up a kind cluster and deploys the plugin and CNI 220 | 221 | .PHONY: del-kind 222 | del-kind: ## Remove a kind cluster called af-xdp-deployment 223 | kind delete cluster --name af-xdp-deployment 224 | 225 | .PHONY: setup-kind 226 | setup-kind: del-kind ## Setup a kind cluster called af-xdp-deployment 227 | mkdir -p /tmp/afxdp_dp/ 228 | mkdir -p /tmp/afxdp_dp2/ 229 | kind create cluster --config hack/kind-config.yaml --name af-xdp-deployment 230 | 231 | .PHONY: label-kind-nodes 232 | label-kind-nodes: ## label the kind worker nodes with cndp="true" 233 | kubectl label node af-xdp-deployment-worker cndp="true" 234 | kubectl label node af-xdp-deployment-worker2 cndp="true" 235 | 236 | .PHONY: kind-deploy 237 | kind-deploy: image undeploy ## Deploy the Deamonset and CNI in Kind 238 | @echo "****** Deploy Daemonset ******" 239 | @echo 240 | kind load --name af-xdp-deployment docker-image afxdp-device-plugin 241 | kubectl create -f ./deployments/daemonset-kind.yaml 242 | @echo 243 | @echo 244 | 245 | .PHONY: run-on-kind 246 | run-on-kind: del-kind setup-kind label-kind-nodes setup-multus kind-deploy ## Setup a kind cluster and deploy the device plugin 247 | @echo "****** Kind Setup complete ******" 248 | --------------------------------------------------------------------------------