This is an automated email from the ASF dual-hosted git repository.

mfordjody pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new 28cb2cc3 add dubbo mesh cni (#918)
28cb2cc3 is described below

commit 28cb2cc3b3d0dd8aa0157bd92c8aa57fba770a5b
Author: mfordjody <[email protected]>
AuthorDate: Thu Jun 18 19:59:48 2026 +0800

    add dubbo mesh cni (#918)
    
    * delete logo
    
    * Update moviereview sample
    
    * add dubbo mesh cni
---
 Makefile                                           |   7 +-
 dubbo-cni/main.go                                  | 139 ++++
 dubbod/discovery/docker/dockerfile.dubbod          |   5 +
 .../charts/dubbod/templates/cni-daemonset.yaml     | 124 ++++
 .../charts/dubbod/templates/configmap-values.yaml  |  24 +
 dubboinstaller/charts/dubbod/values.yaml           |  14 +
 dubbooperator/pkg/apis/proto/values_types.proto    |  30 +
 dubbooperator/pkg/apis/values_types.pb.go          | 743 +++++++++++----------
 dubbooperator/pkg/render/manifest_test.go          |  90 +++
 pkg/cni/config.go                                  | 182 +++++
 pkg/cni/config_test.go                             |  66 ++
 pkg/cni/install.go                                 | 497 ++++++++++++++
 pkg/cni/install_test.go                            | 149 +++++
 pkg/cni/iptables.go                                | 170 +++++
 pkg/cni/iptables_test.go                           |  72 ++
 pkg/cni/kubernetes.go                              |  72 ++
 pkg/cni/plugin.go                                  | 144 ++++
 pkg/cni/plugin_test.go                             | 139 ++++
 pkg/cni/state.go                                   |  99 +++
 pkg/kube/inject/proxyless.go                       |   2 +
 pkg/kube/inject/proxyless_test.go                  |   8 +
 pkg/kube/inject/webhook.go                         |   8 +
 release/downloadDubboCandidate.sh                  |  32 +-
 samples/app/deployment.yaml                        |  16 -
 24 files changed, 2439 insertions(+), 393 deletions(-)

diff --git a/Makefile b/Makefile
index 39b62ecf..abc13722 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,12 @@ build-dubboctl:
        -ldflags "-X 
github.com/apache/dubbo-kubernetes/pkg/version.gitTag=$(GIT_VERSION)" \
     -o bin/dubboctl dubboctl/main.go
 
+.PHONY: build-dubbo-cni
+build-dubbo-cni:
+       CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
+    -o bin/dubbo-cni dubbo-cni/main.go
+
 .PHONY: clone-sample
 clone-sample:
        mkdir -p bin
-       cp -r samples bin/samples
\ No newline at end of file
+       cp -r samples bin/samples
diff --git a/dubbo-cni/main.go b/dubbo-cni/main.go
new file mode 100644
index 00000000..7e512f4e
--- /dev/null
+++ b/dubbo-cni/main.go
@@ -0,0 +1,139 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       "context"
+       "flag"
+       "fmt"
+       "io"
+       "os"
+       "os/signal"
+       "syscall"
+       "time"
+
+       "github.com/apache/dubbo-kubernetes/pkg/cni"
+)
+
+func main() {
+       if len(os.Args) > 1 {
+               switch os.Args[1] {
+               case "install":
+                       if err := runInstall(os.Args[2:]); err != nil {
+                               exitErr(err)
+                       }
+                       return
+               case "uninstall":
+                       if err := runUninstall(os.Args[2:]); err != nil {
+                               exitErr(err)
+                       }
+                       return
+               }
+       }
+
+       stdin, err := io.ReadAll(os.Stdin)
+       if err != nil {
+               exitErr(fmt.Errorf("read stdin: %w", err))
+       }
+
+       conf, err := cni.ParseNetConf(stdin)
+       if err != nil {
+               exitErr(err)
+       }
+       env := cni.EnvFromOS()
+       if env.Command == "VERSION" {
+               out, err := cni.Plugin{}.Run(context.Background(), env, conf)
+               if err != nil {
+                       exitErr(err)
+               }
+               if _, err := os.Stdout.Write(out); err != nil {
+                       exitErr(fmt.Errorf("write stdout: %w", err))
+               }
+               return
+       }
+
+       plugin := cni.Plugin{
+               RuleManager: cni.NewIPTablesRuleManager(conf),
+               StateStore:  cni.NewFileStateStore(conf.StateDirectory()),
+       }
+       if (env.Command == "ADD" || env.Command == "CHECK") && 
cni.EnvHasKubernetesPod(env) {
+               provider, err := 
cni.NewKubernetesPodInfoProvider(conf.KubeConfigPath())
+               if err != nil {
+                       exitErr(err)
+               }
+               plugin.PodInfoProvider = provider
+       }
+       out, err := plugin.Run(context.Background(), env, conf)
+       if err != nil {
+               exitErr(err)
+       }
+       if len(out) > 0 {
+               if _, err := os.Stdout.Write(out); err != nil {
+                       exitErr(fmt.Errorf("write stdout: %w", err))
+               }
+       }
+}
+
+func runInstall(args []string) error {
+       opts := cni.DefaultInstallerOptions()
+       var watch bool
+       var interval time.Duration
+       flags := installerFlagSet("install", &opts)
+       flags.BoolVar(&watch, "watch", false, "keep installer running and 
refresh service account credentials")
+       flags.DurationVar(&interval, "refresh-interval", 
cni.DefaultInstallerInterval(), "credential refresh interval")
+       if err := flags.Parse(args); err != nil {
+               return err
+       }
+       ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, 
syscall.SIGTERM)
+       defer stop()
+       if watch {
+               return cni.InstallLoop(ctx, opts, interval)
+       }
+       return cni.Install(ctx, opts)
+}
+
+func runUninstall(args []string) error {
+       opts := cni.DefaultInstallerOptions()
+       flags := installerFlagSet("uninstall", &opts)
+       if err := flags.Parse(args); err != nil {
+               return err
+       }
+       return cni.Uninstall(opts)
+}
+
+func installerFlagSet(name string, opts *cni.InstallerOptions) *flag.FlagSet {
+       flags := flag.NewFlagSet(name, flag.ContinueOnError)
+       flags.StringVar(&opts.BinDir, "bin-dir", opts.BinDir, "host CNI binary 
directory")
+       flags.StringVar(&opts.ConfDir, "conf-dir", opts.ConfDir, "host CNI 
config directory")
+       flags.StringVar(&opts.StateDir, "state-dir", opts.StateDir, "host state 
directory")
+       flags.StringVar(&opts.KubeConfigPath, "kubeconfig", 
opts.KubeConfigPath, "host kubeconfig path used by the CNI plugin")
+       flags.StringVar(&opts.TokenFile, "token-file", opts.TokenFile, "host 
service account token file")
+       flags.StringVar(&opts.CAFile, "ca-file", opts.CAFile, "host Kubernetes 
CA file")
+       flags.StringVar(&opts.ServiceAccountTokenPath, "service-account-token", 
opts.ServiceAccountTokenPath, "mounted service account token source")
+       flags.StringVar(&opts.ServiceAccountCAPath, "service-account-ca", 
opts.ServiceAccountCAPath, "mounted service account CA source")
+       flags.StringVar(&opts.APIServer, "api-server", opts.APIServer, 
"Kubernetes API server URL")
+       flags.StringVar(&opts.ManagedLabel, "managed-label", opts.ManagedLabel, 
"label key that marks mesh-managed Pods")
+       flags.StringVar(&opts.ManagedLabelValue, "managed-label-value", 
opts.ManagedLabelValue, "label value that marks mesh-managed Pods")
+       flags.StringVar(&opts.IPTablesPath, "iptables-path", opts.IPTablesPath, 
"iptables binary used by the CNI plugin")
+       flags.StringVar(&opts.IPSetPath, "ipset-path", opts.IPSetPath, "ipset 
binary used by the CNI plugin")
+       flags.IntVar(&opts.XServerPort, "xserver-port", opts.XServerPort, 
"xserver inbound port")
+       return flags
+}
+
+func exitErr(err error) {
+       fmt.Fprintln(os.Stderr, err)
+       os.Exit(1)
+}
diff --git a/dubbod/discovery/docker/dockerfile.dubbod 
b/dubbod/discovery/docker/dockerfile.dubbod
index af1ed5d6..a1d823fa 100644
--- a/dubbod/discovery/docker/dockerfile.dubbod
+++ b/dubbod/discovery/docker/dockerfile.dubbod
@@ -33,9 +33,14 @@ RUN go build -v -trimpath -ldflags="-s -w" \
     -o /out/dubbod \
     ./dubbod/discovery/cmd/main.go
 
+RUN go build -v -trimpath -ldflags="-s -w" \
+    -o /out/dubbo-cni \
+    ./dubbo-cni/main.go
+
 FROM scratch
 
 COPY --from=builder /out/dubbod /usr/local/bin/dubbod
+COPY --from=builder /out/dubbo-cni /usr/local/bin/dubbo-cni
 
 USER 65532:65532
 ENTRYPOINT ["/usr/local/bin/dubbod"]
diff --git a/dubboinstaller/charts/dubbod/templates/cni-daemonset.yaml 
b/dubboinstaller/charts/dubbod/templates/cni-daemonset.yaml
new file mode 100644
index 00000000..a4e9a777
--- /dev/null
+++ b/dubboinstaller/charts/dubbod/templates/cni-daemonset.yaml
@@ -0,0 +1,124 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{{- $defaults := .Values._internal_default_values_not_set | default dict }}
+{{- $defaultGlobal := $defaults.global | default dict }}
+{{- $defaultProxyless := $defaultGlobal.proxyless | default dict }}
+{{- $defaultCNI := $defaultProxyless.cni | default dict }}
+{{- $global := .Values.global | default dict }}
+{{- $proxyless := $global.proxyless | default dict }}
+{{- $cni := $proxyless.cni | default dict }}
+{{- $enabled := false }}
+{{- if hasKey $defaultCNI "enabled" }}
+{{- $enabled = $defaultCNI.enabled }}
+{{- end }}
+{{- if hasKey $cni "enabled" }}
+{{- $enabled = $cni.enabled }}
+{{- end }}
+{{- if $enabled }}
+{{- $image := coalesce $cni.image $defaultCNI.image "kdubbo/dubbod:debug" }}
+{{- $binDir := coalesce $cni.binDir $defaultCNI.binDir "/opt/cni/bin" }}
+{{- $confDir := coalesce $cni.confDir $defaultCNI.confDir "/etc/cni/net.d" }}
+{{- $stateDir := coalesce $cni.stateDir $defaultCNI.stateDir 
"/var/run/dubbo-cni" }}
+{{- $xserverPort := int (coalesce $cni.xserverPort $defaultCNI.xserverPort 
15080) }}
+{{- $managedLabel := coalesce $cni.managedLabel $defaultCNI.managedLabel 
"proxyless.dubbo.apache.org/managed" }}
+{{- $managedLabelValue := coalesce $cni.managedLabelValue 
$defaultCNI.managedLabelValue "true" }}
+{{- $iptablesPath := coalesce $cni.iptablesPath $defaultCNI.iptablesPath 
"iptables" }}
+{{- $ipsetPath := coalesce $cni.ipsetPath $defaultCNI.ipsetPath "ipset" }}
+{{- $refreshInterval := coalesce $cni.refreshInterval 
$defaultCNI.refreshInterval "1m" }}
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: dubbo-cni-node
+  namespace: dubbo-system
+  labels:
+    app: dubbo-cni
+    dubbo.apache.org/rev: default
+spec:
+  selector:
+    matchLabels:
+      app: dubbo-cni
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        app: dubbo-cni
+        dubbo.apache.org/rev: default
+    spec:
+      serviceAccountName: dubbod
+      tolerations:
+        - operator: Exists
+      containers:
+        - name: install-cni
+          image: {{ $image | quote }}
+          imagePullPolicy: IfNotPresent
+          command:
+            - /usr/local/bin/dubbo-cni
+          args:
+            - install
+            - --watch
+            - --bin-dir={{ $binDir }}
+            - --conf-dir={{ $confDir }}
+            - --state-dir={{ $stateDir }}
+            - --kubeconfig={{ printf "%s/dubbo-cni-kubeconfig" $confDir }}
+            - --token-file={{ printf "%s/token" $stateDir }}
+            - --ca-file={{ printf "%s/ca.crt" $stateDir }}
+            - --xserver-port={{ $xserverPort }}
+            - --managed-label={{ $managedLabel }}
+            - --managed-label-value={{ $managedLabelValue }}
+            - --iptables-path={{ $iptablesPath }}
+            - --ipset-path={{ $ipsetPath }}
+            - --refresh-interval={{ $refreshInterval }}
+          lifecycle:
+            preStop:
+              exec:
+                command:
+                  - /usr/local/bin/dubbo-cni
+                  - uninstall
+                  - --bin-dir={{ $binDir }}
+                  - --conf-dir={{ $confDir }}
+                  - --state-dir={{ $stateDir }}
+                  - --kubeconfig={{ printf "%s/dubbo-cni-kubeconfig" $confDir 
}}
+                  - --token-file={{ printf "%s/token" $stateDir }}
+                  - --ca-file={{ printf "%s/ca.crt" $stateDir }}
+          securityContext:
+            runAsUser: 0
+            runAsGroup: 0
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop:
+                - ALL
+          volumeMounts:
+            - name: cni-bin-dir
+              mountPath: {{ $binDir | quote }}
+            - name: cni-conf-dir
+              mountPath: {{ $confDir | quote }}
+            - name: cni-state-dir
+              mountPath: {{ $stateDir | quote }}
+      volumes:
+        - name: cni-bin-dir
+          hostPath:
+            path: {{ $binDir | quote }}
+            type: DirectoryOrCreate
+        - name: cni-conf-dir
+          hostPath:
+            path: {{ $confDir | quote }}
+            type: DirectoryOrCreate
+        - name: cni-state-dir
+          hostPath:
+            path: {{ $stateDir | quote }}
+            type: DirectoryOrCreate
+{{- end }}
diff --git a/dubboinstaller/charts/dubbod/templates/configmap-values.yaml 
b/dubboinstaller/charts/dubbod/templates/configmap-values.yaml
index f3bb5129..c740eea3 100644
--- a/dubboinstaller/charts/dubbod/templates/configmap-values.yaml
+++ b/dubboinstaller/charts/dubbod/templates/configmap-values.yaml
@@ -16,15 +16,26 @@
 {{- $defaults := .Values._internal_default_values_not_set | default dict }}
 {{- $defaultGlobal := $defaults.global | default dict }}
 {{- $defaultProxy := $defaultGlobal.proxy | default dict }}
+{{- $defaultProxyless := $defaultGlobal.proxyless | default dict }}
+{{- $defaultCNI := $defaultProxyless.cni | default dict }}
 {{- $defaultGUI := $defaultGlobal.gui | default dict }}
 {{- $global := .Values.global | default dict }}
 {{- $proxy := $global.proxy | default dict }}
+{{- $proxyless := $global.proxyless | default dict }}
+{{- $cni := $proxyless.cni | default dict }}
 {{- $gui := $global.gui | default dict }}
 {{- $clusterDomain := coalesce $proxy.clusterDomain 
$defaultProxy.clusterDomain "cluster.local" }}
 {{- $revision := .Values.revision | default "default" }}
 {{- $statusPort := coalesce $global.statusPort $defaultGlobal.statusPort 26020 
}}
 {{- $guiPort := int (coalesce $gui.port $defaultGUI.port 26080) }}
 {{- $guiNodePort := int (coalesce $gui.nodePort $defaultGUI.nodePort 30080) }}
+{{- $cniEnabled := false }}
+{{- if hasKey $defaultCNI "enabled" }}
+{{- $cniEnabled = $defaultCNI.enabled }}
+{{- end }}
+{{- if hasKey $cni "enabled" }}
+{{- $cniEnabled = $cni.enabled }}
+{{- end }}
 {{- $original := omit .Values "_internal_default_values_not_set" }}
 apiVersion: v1
 kind: ConfigMap
@@ -40,6 +51,19 @@ data:
     global:
       proxy:
         clusterDomain: {{ $clusterDomain | quote }}
+      proxyless:
+        cni:
+          enabled: {{ $cniEnabled }}
+          image: {{ coalesce $cni.image $defaultCNI.image 
"kdubbo/dubbod:debug" | quote }}
+          binDir: {{ coalesce $cni.binDir $defaultCNI.binDir "/opt/cni/bin" | 
quote }}
+          confDir: {{ coalesce $cni.confDir $defaultCNI.confDir 
"/etc/cni/net.d" | quote }}
+          stateDir: {{ coalesce $cni.stateDir $defaultCNI.stateDir 
"/var/run/dubbo-cni" | quote }}
+          xserverPort: {{ int (coalesce $cni.xserverPort 
$defaultCNI.xserverPort 15080) }}
+          managedLabel: {{ coalesce $cni.managedLabel $defaultCNI.managedLabel 
"proxyless.dubbo.apache.org/managed" | quote }}
+          managedLabelValue: {{ coalesce $cni.managedLabelValue 
$defaultCNI.managedLabelValue "true" | quote }}
+          iptablesPath: {{ coalesce $cni.iptablesPath $defaultCNI.iptablesPath 
"iptables" | quote }}
+          ipsetPath: {{ coalesce $cni.ipsetPath $defaultCNI.ipsetPath "ipset" 
| quote }}
+          refreshInterval: {{ coalesce $cni.refreshInterval 
$defaultCNI.refreshInterval "1m" | quote }}
       gui:
         port: {{ $guiPort }}
         nodePort: {{ $guiNodePort }}
diff --git a/dubboinstaller/charts/dubbod/values.yaml 
b/dubboinstaller/charts/dubbod/values.yaml
index 83090e11..78b4c1cf 100644
--- a/dubboinstaller/charts/dubbod/values.yaml
+++ b/dubboinstaller/charts/dubbod/values.yaml
@@ -20,6 +20,20 @@ _internal_default_values_not_set:
     proxy:
       clusterDomain: "cluster.local"
 
+    proxyless:
+      cni:
+        enabled: true
+        image: "kdubbo/dubbod:debug"
+        binDir: "/opt/cni/bin"
+        confDir: "/etc/cni/net.d"
+        stateDir: "/var/run/dubbo-cni"
+        xserverPort: 15080
+        managedLabel: "proxyless.dubbo.apache.org/managed"
+        managedLabelValue: "true"
+        iptablesPath: "iptables"
+        ipsetPath: "ipset"
+        refreshInterval: "1m"
+
     statusPort: 26020
 
     gui:
diff --git a/dubbooperator/pkg/apis/proto/values_types.proto 
b/dubbooperator/pkg/apis/proto/values_types.proto
index 8aa6c120..a3c85162 100644
--- a/dubbooperator/pkg/apis/proto/values_types.proto
+++ b/dubbooperator/pkg/apis/proto/values_types.proto
@@ -34,6 +34,34 @@ message ProxyConfig {
   string clusterDomain = 1;
 }
 
+message ProxylessConfig {
+  MeshCNIConfig cni = 1;
+}
+
+message MeshCNIConfig {
+  bool enabled = 1;
+
+  string image = 2;
+
+  string binDir = 3;
+
+  string confDir = 4;
+
+  string stateDir = 5;
+
+  int64 xserverPort = 6;
+
+  string managedLabel = 7;
+
+  string managedLabelValue = 8;
+
+  string iptablesPath = 9;
+
+  string ipsetPath = 10;
+
+  string refreshInterval = 11;
+}
+
 message GlobalConfig {
   ProxyConfig proxy = 1;
 
@@ -44,6 +72,8 @@ message GlobalConfig {
   bool configValidation = 4;
 
   MulticlusterConfig multicluster = 5;
+
+  ProxylessConfig proxyless = 6;
 }
 
 message RemoteAccessConfig {
diff --git a/dubbooperator/pkg/apis/values_types.pb.go 
b/dubbooperator/pkg/apis/values_types.pb.go
index 0dcc0f62..bb578791 100644
--- a/dubbooperator/pkg/apis/values_types.pb.go
+++ b/dubbooperator/pkg/apis/values_types.pb.go
@@ -16,7 +16,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-//     protoc-gen-go v1.31.0
+//     protoc-gen-go v1.36.11
 //     protoc        v6.33.0
 // source: values_types.proto
 
@@ -28,6 +28,7 @@ import (
        wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
        reflect "reflect"
        sync "sync"
+       unsafe "unsafe"
 )
 
 const (
@@ -38,21 +39,18 @@ const (
 )
 
 type GUIConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+       state         protoimpl.MessageState `protogen:"open.v1"`
+       Port          int64                  
`protobuf:"varint,1,opt,name=port,proto3" json:"port,omitempty"`
+       NodePort      int64                  
`protobuf:"varint,2,opt,name=nodePort,proto3" json:"nodePort,omitempty"`
        unknownFields protoimpl.UnknownFields
-
-       Port     int64 `protobuf:"varint,1,opt,name=port,proto3" 
json:"port,omitempty"`
-       NodePort int64 `protobuf:"varint,2,opt,name=nodePort,proto3" 
json:"nodePort,omitempty"`
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *GUIConfig) Reset() {
        *x = GUIConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[0]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[0]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *GUIConfig) String() string {
@@ -63,7 +61,7 @@ func (*GUIConfig) ProtoMessage() {}
 
 func (x *GUIConfig) ProtoReflect() protoreflect.Message {
        mi := &file_values_types_proto_msgTypes[0]
-       if protoimpl.UnsafeEnabled && x != nil {
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -93,20 +91,17 @@ func (x *GUIConfig) GetNodePort() int64 {
 }
 
 type ProxyConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+       state         protoimpl.MessageState `protogen:"open.v1"`
+       ClusterDomain string                 
`protobuf:"bytes,1,opt,name=clusterDomain,proto3" 
json:"clusterDomain,omitempty"`
        unknownFields protoimpl.UnknownFields
-
-       ClusterDomain string `protobuf:"bytes,1,opt,name=clusterDomain,proto3" 
json:"clusterDomain,omitempty"`
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *ProxyConfig) Reset() {
        *x = ProxyConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[1]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[1]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *ProxyConfig) String() string {
@@ -117,7 +112,7 @@ func (*ProxyConfig) ProtoMessage() {}
 
 func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
        mi := &file_values_types_proto_msgTypes[1]
-       if protoimpl.UnsafeEnabled && x != nil {
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -139,25 +134,191 @@ func (x *ProxyConfig) GetClusterDomain() string {
        return ""
 }
 
-type GlobalConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+type ProxylessConfig struct {
+       state         protoimpl.MessageState `protogen:"open.v1"`
+       Cni           *MeshCNIConfig         
`protobuf:"bytes,1,opt,name=cni,proto3" json:"cni,omitempty"`
        unknownFields protoimpl.UnknownFields
+       sizeCache     protoimpl.SizeCache
+}
 
-       Proxy            *ProxyConfig        
`protobuf:"bytes,1,opt,name=proxy,proto3" json:"proxy,omitempty"`
-       StatusPort       int64               
`protobuf:"varint,2,opt,name=statusPort,proto3" json:"statusPort,omitempty"`
-       Gui              *GUIConfig          
`protobuf:"bytes,3,opt,name=gui,proto3" json:"gui,omitempty"`
-       ConfigValidation bool                
`protobuf:"varint,4,opt,name=configValidation,proto3" 
json:"configValidation,omitempty"`
-       Multicluster     *MulticlusterConfig 
`protobuf:"bytes,5,opt,name=multicluster,proto3" json:"multicluster,omitempty"`
+func (x *ProxylessConfig) Reset() {
+       *x = ProxylessConfig{}
+       mi := &file_values_types_proto_msgTypes[2]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
-func (x *GlobalConfig) Reset() {
-       *x = GlobalConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[2]
+func (x *ProxylessConfig) String() string {
+       return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ProxylessConfig) ProtoMessage() {}
+
+func (x *ProxylessConfig) ProtoReflect() protoreflect.Message {
+       mi := &file_values_types_proto_msgTypes[2]
+       if x != nil {
+               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+               if ms.LoadMessageInfo() == nil {
+                       ms.StoreMessageInfo(mi)
+               }
+               return ms
+       }
+       return mi.MessageOf(x)
+}
+
+// Deprecated: Use ProxylessConfig.ProtoReflect.Descriptor instead.
+func (*ProxylessConfig) Descriptor() ([]byte, []int) {
+       return file_values_types_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ProxylessConfig) GetCni() *MeshCNIConfig {
+       if x != nil {
+               return x.Cni
+       }
+       return nil
+}
+
+type MeshCNIConfig struct {
+       state             protoimpl.MessageState `protogen:"open.v1"`
+       Enabled           bool                   
`protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+       Image             string                 
`protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"`
+       BinDir            string                 
`protobuf:"bytes,3,opt,name=binDir,proto3" json:"binDir,omitempty"`
+       ConfDir           string                 
`protobuf:"bytes,4,opt,name=confDir,proto3" json:"confDir,omitempty"`
+       StateDir          string                 
`protobuf:"bytes,5,opt,name=stateDir,proto3" json:"stateDir,omitempty"`
+       XserverPort       int64                  
`protobuf:"varint,6,opt,name=xserverPort,proto3" json:"xserverPort,omitempty"`
+       ManagedLabel      string                 
`protobuf:"bytes,7,opt,name=managedLabel,proto3" json:"managedLabel,omitempty"`
+       ManagedLabelValue string                 
`protobuf:"bytes,8,opt,name=managedLabelValue,proto3" 
json:"managedLabelValue,omitempty"`
+       IptablesPath      string                 
`protobuf:"bytes,9,opt,name=iptablesPath,proto3" json:"iptablesPath,omitempty"`
+       IpsetPath         string                 
`protobuf:"bytes,10,opt,name=ipsetPath,proto3" json:"ipsetPath,omitempty"`
+       RefreshInterval   string                 
`protobuf:"bytes,11,opt,name=refreshInterval,proto3" 
json:"refreshInterval,omitempty"`
+       unknownFields     protoimpl.UnknownFields
+       sizeCache         protoimpl.SizeCache
+}
+
+func (x *MeshCNIConfig) Reset() {
+       *x = MeshCNIConfig{}
+       mi := &file_values_types_proto_msgTypes[3]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
+}
+
+func (x *MeshCNIConfig) String() string {
+       return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MeshCNIConfig) ProtoMessage() {}
+
+func (x *MeshCNIConfig) ProtoReflect() protoreflect.Message {
+       mi := &file_values_types_proto_msgTypes[3]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
+               if ms.LoadMessageInfo() == nil {
+                       ms.StoreMessageInfo(mi)
+               }
+               return ms
+       }
+       return mi.MessageOf(x)
+}
+
+// Deprecated: Use MeshCNIConfig.ProtoReflect.Descriptor instead.
+func (*MeshCNIConfig) Descriptor() ([]byte, []int) {
+       return file_values_types_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *MeshCNIConfig) GetEnabled() bool {
+       if x != nil {
+               return x.Enabled
+       }
+       return false
+}
+
+func (x *MeshCNIConfig) GetImage() string {
+       if x != nil {
+               return x.Image
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetBinDir() string {
+       if x != nil {
+               return x.BinDir
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetConfDir() string {
+       if x != nil {
+               return x.ConfDir
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetStateDir() string {
+       if x != nil {
+               return x.StateDir
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetXserverPort() int64 {
+       if x != nil {
+               return x.XserverPort
+       }
+       return 0
+}
+
+func (x *MeshCNIConfig) GetManagedLabel() string {
+       if x != nil {
+               return x.ManagedLabel
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetManagedLabelValue() string {
+       if x != nil {
+               return x.ManagedLabelValue
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetIptablesPath() string {
+       if x != nil {
+               return x.IptablesPath
+       }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetIpsetPath() string {
+       if x != nil {
+               return x.IpsetPath
        }
+       return ""
+}
+
+func (x *MeshCNIConfig) GetRefreshInterval() string {
+       if x != nil {
+               return x.RefreshInterval
+       }
+       return ""
+}
+
+type GlobalConfig struct {
+       state            protoimpl.MessageState `protogen:"open.v1"`
+       Proxy            *ProxyConfig           
`protobuf:"bytes,1,opt,name=proxy,proto3" json:"proxy,omitempty"`
+       StatusPort       int64                  
`protobuf:"varint,2,opt,name=statusPort,proto3" json:"statusPort,omitempty"`
+       Gui              *GUIConfig             
`protobuf:"bytes,3,opt,name=gui,proto3" json:"gui,omitempty"`
+       ConfigValidation bool                   
`protobuf:"varint,4,opt,name=configValidation,proto3" 
json:"configValidation,omitempty"`
+       Multicluster     *MulticlusterConfig    
`protobuf:"bytes,5,opt,name=multicluster,proto3" json:"multicluster,omitempty"`
+       Proxyless        *ProxylessConfig       
`protobuf:"bytes,6,opt,name=proxyless,proto3" json:"proxyless,omitempty"`
+       unknownFields    protoimpl.UnknownFields
+       sizeCache        protoimpl.SizeCache
+}
+
+func (x *GlobalConfig) Reset() {
+       *x = GlobalConfig{}
+       mi := &file_values_types_proto_msgTypes[4]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *GlobalConfig) String() string {
@@ -167,8 +328,8 @@ func (x *GlobalConfig) String() string {
 func (*GlobalConfig) ProtoMessage() {}
 
 func (x *GlobalConfig) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[2]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[4]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -180,7 +341,7 @@ func (x *GlobalConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use GlobalConfig.ProtoReflect.Descriptor instead.
 func (*GlobalConfig) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{2}
+       return file_values_types_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *GlobalConfig) GetProxy() *ProxyConfig {
@@ -218,26 +379,30 @@ func (x *GlobalConfig) GetMulticluster() 
*MulticlusterConfig {
        return nil
 }
 
-type RemoteAccessConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
-       unknownFields protoimpl.UnknownFields
+func (x *GlobalConfig) GetProxyless() *ProxylessConfig {
+       if x != nil {
+               return x.Proxyless
+       }
+       return nil
+}
 
-       Enabled          bool     `protobuf:"varint,1,opt,name=enabled,proto3" 
json:"enabled,omitempty"`
-       ServiceType      string   
`protobuf:"bytes,2,opt,name=serviceType,proto3" json:"serviceType,omitempty"`
-       XdsPort          int64    `protobuf:"varint,3,opt,name=xdsPort,proto3" 
json:"xdsPort,omitempty"`
-       WebhookPort      int64    
`protobuf:"varint,4,opt,name=webhookPort,proto3" json:"webhookPort,omitempty"`
-       CertificateHosts []string 
`protobuf:"bytes,5,rep,name=certificateHosts,proto3" 
json:"certificateHosts,omitempty"`
-       GrpcPort         int64    `protobuf:"varint,6,opt,name=grpcPort,proto3" 
json:"grpcPort,omitempty"`
+type RemoteAccessConfig struct {
+       state            protoimpl.MessageState `protogen:"open.v1"`
+       Enabled          bool                   
`protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+       ServiceType      string                 
`protobuf:"bytes,2,opt,name=serviceType,proto3" json:"serviceType,omitempty"`
+       XdsPort          int64                  
`protobuf:"varint,3,opt,name=xdsPort,proto3" json:"xdsPort,omitempty"`
+       WebhookPort      int64                  
`protobuf:"varint,4,opt,name=webhookPort,proto3" json:"webhookPort,omitempty"`
+       CertificateHosts []string               
`protobuf:"bytes,5,rep,name=certificateHosts,proto3" 
json:"certificateHosts,omitempty"`
+       GrpcPort         int64                  
`protobuf:"varint,6,opt,name=grpcPort,proto3" json:"grpcPort,omitempty"`
+       unknownFields    protoimpl.UnknownFields
+       sizeCache        protoimpl.SizeCache
 }
 
 func (x *RemoteAccessConfig) Reset() {
        *x = RemoteAccessConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[3]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[5]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *RemoteAccessConfig) String() string {
@@ -247,8 +412,8 @@ func (x *RemoteAccessConfig) String() string {
 func (*RemoteAccessConfig) ProtoMessage() {}
 
 func (x *RemoteAccessConfig) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[3]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[5]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -260,7 +425,7 @@ func (x *RemoteAccessConfig) ProtoReflect() 
protoreflect.Message {
 
 // Deprecated: Use RemoteAccessConfig.ProtoReflect.Descriptor instead.
 func (*RemoteAccessConfig) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{3}
+       return file_values_types_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *RemoteAccessConfig) GetEnabled() bool {
@@ -306,22 +471,19 @@ func (x *RemoteAccessConfig) GetGrpcPort() int64 {
 }
 
 type EastWestGatewayEndpoint struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+       state         protoimpl.MessageState `protogen:"open.v1"`
+       ClusterName   string                 
`protobuf:"bytes,1,opt,name=clusterName,proto3" json:"clusterName,omitempty"`
+       Address       string                 
`protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+       Port          int64                  
`protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"`
        unknownFields protoimpl.UnknownFields
-
-       ClusterName string `protobuf:"bytes,1,opt,name=clusterName,proto3" 
json:"clusterName,omitempty"`
-       Address     string `protobuf:"bytes,2,opt,name=address,proto3" 
json:"address,omitempty"`
-       Port        int64  `protobuf:"varint,3,opt,name=port,proto3" 
json:"port,omitempty"`
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *EastWestGatewayEndpoint) Reset() {
        *x = EastWestGatewayEndpoint{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[4]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[6]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *EastWestGatewayEndpoint) String() string {
@@ -331,8 +493,8 @@ func (x *EastWestGatewayEndpoint) String() string {
 func (*EastWestGatewayEndpoint) ProtoMessage() {}
 
 func (x *EastWestGatewayEndpoint) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[4]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[6]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -344,7 +506,7 @@ func (x *EastWestGatewayEndpoint) ProtoReflect() 
protoreflect.Message {
 
 // Deprecated: Use EastWestGatewayEndpoint.ProtoReflect.Descriptor instead.
 func (*EastWestGatewayEndpoint) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{4}
+       return file_values_types_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *EastWestGatewayEndpoint) GetClusterName() string {
@@ -369,26 +531,23 @@ func (x *EastWestGatewayEndpoint) GetPort() int64 {
 }
 
 type EastWestGatewayConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+       state         protoimpl.MessageState     `protogen:"open.v1"`
+       Enabled       bool                       
`protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+       ServiceType   string                     
`protobuf:"bytes,2,opt,name=serviceType,proto3" json:"serviceType,omitempty"`
+       Port          int64                      
`protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"`
+       TargetPort    int64                      
`protobuf:"varint,4,opt,name=targetPort,proto3" json:"targetPort,omitempty"`
+       NodePort      int64                      
`protobuf:"varint,5,opt,name=nodePort,proto3" json:"nodePort,omitempty"`
+       Gateways      []*EastWestGatewayEndpoint 
`protobuf:"bytes,6,rep,name=gateways,proto3" json:"gateways,omitempty"`
+       XdsAddress    string                     
`protobuf:"bytes,7,opt,name=xdsAddress,proto3" json:"xdsAddress,omitempty"`
        unknownFields protoimpl.UnknownFields
-
-       Enabled     bool                       
`protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
-       ServiceType string                     
`protobuf:"bytes,2,opt,name=serviceType,proto3" json:"serviceType,omitempty"`
-       Port        int64                      
`protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"`
-       TargetPort  int64                      
`protobuf:"varint,4,opt,name=targetPort,proto3" json:"targetPort,omitempty"`
-       NodePort    int64                      
`protobuf:"varint,5,opt,name=nodePort,proto3" json:"nodePort,omitempty"`
-       Gateways    []*EastWestGatewayEndpoint 
`protobuf:"bytes,6,rep,name=gateways,proto3" json:"gateways,omitempty"`
-       XdsAddress  string                     
`protobuf:"bytes,7,opt,name=xdsAddress,proto3" json:"xdsAddress,omitempty"`
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *EastWestGatewayConfig) Reset() {
        *x = EastWestGatewayConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[5]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[7]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *EastWestGatewayConfig) String() string {
@@ -398,8 +557,8 @@ func (x *EastWestGatewayConfig) String() string {
 func (*EastWestGatewayConfig) ProtoMessage() {}
 
 func (x *EastWestGatewayConfig) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[5]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[7]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -411,7 +570,7 @@ func (x *EastWestGatewayConfig) ProtoReflect() 
protoreflect.Message {
 
 // Deprecated: Use EastWestGatewayConfig.ProtoReflect.Descriptor instead.
 func (*EastWestGatewayConfig) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{5}
+       return file_values_types_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *EastWestGatewayConfig) GetEnabled() bool {
@@ -464,21 +623,18 @@ func (x *EastWestGatewayConfig) GetXdsAddress() string {
 }
 
 type MulticlusterConfig struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
-       unknownFields protoimpl.UnknownFields
-
+       state           protoimpl.MessageState `protogen:"open.v1"`
        RemoteAccess    *RemoteAccessConfig    
`protobuf:"bytes,1,opt,name=remoteAccess,proto3" json:"remoteAccess,omitempty"`
        EastWestGateway *EastWestGatewayConfig 
`protobuf:"bytes,2,opt,name=eastWestGateway,proto3" 
json:"eastWestGateway,omitempty"`
+       unknownFields   protoimpl.UnknownFields
+       sizeCache       protoimpl.SizeCache
 }
 
 func (x *MulticlusterConfig) Reset() {
        *x = MulticlusterConfig{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[6]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[8]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *MulticlusterConfig) String() string {
@@ -488,8 +644,8 @@ func (x *MulticlusterConfig) String() string {
 func (*MulticlusterConfig) ProtoMessage() {}
 
 func (x *MulticlusterConfig) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[6]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[8]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -501,7 +657,7 @@ func (x *MulticlusterConfig) ProtoReflect() 
protoreflect.Message {
 
 // Deprecated: Use MulticlusterConfig.ProtoReflect.Descriptor instead.
 func (*MulticlusterConfig) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{6}
+       return file_values_types_proto_rawDescGZIP(), []int{8}
 }
 
 func (x *MulticlusterConfig) GetRemoteAccess() *RemoteAccessConfig {
@@ -519,23 +675,20 @@ func (x *MulticlusterConfig) GetEastWestGateway() 
*EastWestGatewayConfig {
 }
 
 type Values struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
-       unknownFields protoimpl.UnknownFields
-
+       state protoimpl.MessageState `protogen:"open.v1"`
        // Global configuration for dubbo components.
        Global *GlobalConfig `protobuf:"bytes,1,opt,name=global,proto3" 
json:"global,omitempty"`
        // Revision for rendered control plane resources.
-       Revision string `protobuf:"bytes,2,opt,name=revision,proto3" 
json:"revision,omitempty"`
+       Revision      string `protobuf:"bytes,2,opt,name=revision,proto3" 
json:"revision,omitempty"`
+       unknownFields protoimpl.UnknownFields
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *Values) Reset() {
        *x = Values{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[7]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[9]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *Values) String() string {
@@ -545,8 +698,8 @@ func (x *Values) String() string {
 func (*Values) ProtoMessage() {}
 
 func (x *Values) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[7]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[9]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -558,7 +711,7 @@ func (x *Values) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Values.ProtoReflect.Descriptor instead.
 func (*Values) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{7}
+       return file_values_types_proto_rawDescGZIP(), []int{9}
 }
 
 func (x *Values) GetGlobal() *GlobalConfig {
@@ -585,22 +738,19 @@ func (x *Values) GetRevision() string {
 // +protobuf.options.(gogoproto.goproto_stringer)=false
 // +k8s:openapi-gen=true
 type IntOrString struct {
-       state         protoimpl.MessageState
-       sizeCache     protoimpl.SizeCache
+       state         protoimpl.MessageState  `protogen:"open.v1"`
+       Type          int64                   
`protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"`
+       IntVal        *wrapperspb.Int32Value  
`protobuf:"bytes,2,opt,name=intVal,proto3" json:"intVal,omitempty"`
+       StrVal        *wrapperspb.StringValue 
`protobuf:"bytes,3,opt,name=strVal,proto3" json:"strVal,omitempty"`
        unknownFields protoimpl.UnknownFields
-
-       Type   int64                   
`protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"`
-       IntVal *wrapperspb.Int32Value  
`protobuf:"bytes,2,opt,name=intVal,proto3" json:"intVal,omitempty"`
-       StrVal *wrapperspb.StringValue 
`protobuf:"bytes,3,opt,name=strVal,proto3" json:"strVal,omitempty"`
+       sizeCache     protoimpl.SizeCache
 }
 
 func (x *IntOrString) Reset() {
        *x = IntOrString{}
-       if protoimpl.UnsafeEnabled {
-               mi := &file_values_types_proto_msgTypes[8]
-               ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-               ms.StoreMessageInfo(mi)
-       }
+       mi := &file_values_types_proto_msgTypes[10]
+       ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+       ms.StoreMessageInfo(mi)
 }
 
 func (x *IntOrString) String() string {
@@ -610,8 +760,8 @@ func (x *IntOrString) String() string {
 func (*IntOrString) ProtoMessage() {}
 
 func (x *IntOrString) ProtoReflect() protoreflect.Message {
-       mi := &file_values_types_proto_msgTypes[8]
-       if protoimpl.UnsafeEnabled && x != nil {
+       mi := &file_values_types_proto_msgTypes[10]
+       if x != nil {
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                if ms.LoadMessageInfo() == nil {
                        ms.StoreMessageInfo(mi)
@@ -623,7 +773,7 @@ func (x *IntOrString) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IntOrString.ProtoReflect.Descriptor instead.
 func (*IntOrString) Descriptor() ([]byte, []int) {
-       return file_values_types_proto_rawDescGZIP(), []int{8}
+       return file_values_types_proto_rawDescGZIP(), []int{10}
 }
 
 func (x *IntOrString) GetType() int64 {
@@ -649,149 +799,117 @@ func (x *IntOrString) GetStrVal() 
*wrapperspb.StringValue {
 
 var File_values_types_proto protoreflect.FileDescriptor
 
-var file_values_types_proto_rawDesc = []byte{
-       0x0a, 0x12, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x74, 0x79, 0x70, 
0x65, 0x73, 0x2e, 0x70,
-       0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2e, 
0x6f, 0x70, 0x65, 0x72,
-       0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 
0x31, 0x1a, 0x1e, 0x67,
-       0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 
0x75, 0x66, 0x2f, 0x77,
-       0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 
0x6f, 0x22, 0x3b, 0x0a,
-       0x09, 0x47, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 
0x0a, 0x04, 0x70, 0x6f,
-       0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x6f, 
0x72, 0x74, 0x12, 0x1a,
-       0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 
0x20, 0x01, 0x28, 0x03,
-       0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x33, 
0x0a, 0x0b, 0x50, 0x72,
-       0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 
0x0d, 0x63, 0x6c, 0x75,
-       0x73, 0x74, 0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 
0x20, 0x01, 0x28, 0x09,
-       0x52, 0x0d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x44, 0x6f, 0x6d, 
0x61, 0x69, 0x6e, 0x22,
-       0x9d, 0x02, 0x0a, 0x0c, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x43, 0x6f, 
0x6e, 0x66, 0x69, 0x67,
-       0x12, 0x3a, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x01, 0x20, 
0x01, 0x28, 0x0b, 0x32,
-       0x24, 0x2e, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x72, 
0x61, 0x74, 0x6f, 0x72,
-       0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 
0x6f, 0x78, 0x79, 0x43,
-       0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 
0x12, 0x1e, 0x0a, 0x0a,
-       0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 
0x20, 0x01, 0x28, 0x03,
-       0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x6f, 0x72, 0x74, 
0x12, 0x34, 0x0a, 0x03,
-       0x67, 0x75, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 
0x64, 0x75, 0x62, 0x62,
-       0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 
0x31, 0x61, 0x6c, 0x70,
-       0x68, 0x61, 0x31, 0x2e, 0x47, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 
0x67, 0x52, 0x03, 0x67,
-       0x75, 0x69, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 
0x56, 0x61, 0x6c, 0x69,
-       0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 
0x52, 0x10, 0x63, 0x6f,
-       0x6e, 0x66, 0x69, 0x67, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 
0x6f, 0x6e, 0x12, 0x4f,
-       0x0a, 0x0c, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6c, 0x75, 0x73, 0x74, 
0x65, 0x72, 0x18, 0x05,
-       0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x64, 0x75, 0x62, 0x62, 0x6f, 
0x2e, 0x6f, 0x70, 0x65,
-       0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 
0x61, 0x31, 0x2e, 0x4d,
-       0x75, 0x6c, 0x74, 0x69, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x43, 
0x6f, 0x6e, 0x66, 0x69,
-       0x67, 0x52, 0x0c, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6c, 0x75, 0x73, 
0x74, 0x65, 0x72, 0x22,
-       0xd4, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x63, 
0x63, 0x65, 0x73, 0x73,
-       0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 
0x61, 0x62, 0x6c, 0x65,
-       0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 
0x62, 0x6c, 0x65, 0x64,
-       0x12, 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 
0x79, 0x70, 0x65, 0x18,
-       0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 
0x63, 0x65, 0x54, 0x79,
-       0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x78, 0x64, 0x73, 0x50, 0x6f, 0x72, 
0x74, 0x18, 0x03, 0x20,
-       0x01, 0x28, 0x03, 0x52, 0x07, 0x78, 0x64, 0x73, 0x50, 0x6f, 0x72, 0x74, 
0x12, 0x20, 0x0a, 0x0b,
-       0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x50, 0x6f, 0x72, 0x74, 0x18, 
0x04, 0x20, 0x01, 0x28,
-       0x03, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x50, 0x6f, 
0x72, 0x74, 0x12, 0x2a,
-       0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 
0x65, 0x48, 0x6f, 0x73,
-       0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x65, 
0x72, 0x74, 0x69, 0x66,
-       0x69, 0x63, 0x61, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1a, 
0x0a, 0x08, 0x67, 0x72,
-       0x70, 0x63, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 
0x52, 0x08, 0x67, 0x72,
-       0x70, 0x63, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x69, 0x0a, 0x17, 0x45, 0x61, 
0x73, 0x74, 0x57, 0x65,
-       0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x6e, 0x64, 
0x70, 0x6f, 0x69, 0x6e,
-       0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 
0x4e, 0x61, 0x6d, 0x65,
-       0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 
0x74, 0x65, 0x72, 0x4e,
-       0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 
0x73, 0x73, 0x18, 0x02,
-       0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 
0x73, 0x12, 0x12, 0x0a,
-       0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 
0x04, 0x70, 0x6f, 0x72,
-       0x74, 0x22, 0x91, 0x02, 0x0a, 0x15, 0x45, 0x61, 0x73, 0x74, 0x57, 0x65, 
0x73, 0x74, 0x47, 0x61,
-       0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 
0x18, 0x0a, 0x07, 0x65,
-       0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 
0x52, 0x07, 0x65, 0x6e,
-       0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x72, 
0x76, 0x69, 0x63, 0x65,
-       0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 
0x73, 0x65, 0x72, 0x76,
-       0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 
0x6f, 0x72, 0x74, 0x18,
-       0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 
0x1e, 0x0a, 0x0a, 0x74,
-       0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 
0x01, 0x28, 0x03, 0x52,
-       0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 
0x1a, 0x0a, 0x08, 0x6e,
-       0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 
0x03, 0x52, 0x08, 0x6e,
-       0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x4c, 0x0a, 0x08, 0x67, 
0x61, 0x74, 0x65, 0x77,
-       0x61, 0x79, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 
0x64, 0x75, 0x62, 0x62,
-       0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 
0x31, 0x61, 0x6c, 0x70,
-       0x68, 0x61, 0x31, 0x2e, 0x45, 0x61, 0x73, 0x74, 0x57, 0x65, 0x73, 0x74, 
0x47, 0x61, 0x74, 0x65,
-       0x77, 0x61, 0x79, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 
0x08, 0x67, 0x61, 0x74,
-       0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x78, 0x64, 0x73, 
0x41, 0x64, 0x64, 0x72,
-       0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x78, 
0x64, 0x73, 0x41, 0x64,
-       0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xbf, 0x01, 0x0a, 0x12, 0x4d, 0x75, 
0x6c, 0x74, 0x69, 0x63,
-       0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 
0x12, 0x4f, 0x0a, 0x0c,
-       0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 
0x18, 0x01, 0x20, 0x01,
-       0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2e, 0x6f, 
0x70, 0x65, 0x72, 0x61,
-       0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 
0x2e, 0x52, 0x65, 0x6d,
-       0x6f, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 
0x66, 0x69, 0x67, 0x52,
-       0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 
0x73, 0x12, 0x58, 0x0a,
-       0x0f, 0x65, 0x61, 0x73, 0x74, 0x57, 0x65, 0x73, 0x74, 0x47, 0x61, 0x74, 
0x65, 0x77, 0x61, 0x79,
-       0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x64, 0x75, 0x62, 
0x62, 0x6f, 0x2e, 0x6f,
-       0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 
0x70, 0x68, 0x61, 0x31,
-       0x2e, 0x45, 0x61, 0x73, 0x74, 0x57, 0x65, 0x73, 0x74, 0x47, 0x61, 0x74, 
0x65, 0x77, 0x61, 0x79,
-       0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0f, 0x65, 0x61, 0x73, 0x74, 
0x57, 0x65, 0x73, 0x74,
-       0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x63, 0x0a, 0x06, 0x56, 
0x61, 0x6c, 0x75, 0x65,
-       0x73, 0x12, 0x3d, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 
0x01, 0x20, 0x01, 0x28,
-       0x0b, 0x32, 0x25, 0x2e, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x2e, 0x6f, 0x70, 
0x65, 0x72, 0x61, 0x74,
-       0x6f, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 
0x47, 0x6c, 0x6f, 0x62,
-       0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x67, 0x6c, 
0x6f, 0x62, 0x61, 0x6c,
-       0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 
0x18, 0x02, 0x20, 0x01,
-       0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 
0x22, 0x8c, 0x01, 0x0a,
-       0x0b, 0x49, 0x6e, 0x74, 0x4f, 0x72, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 
0x12, 0x12, 0x0a, 0x04,
-       0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 
0x74, 0x79, 0x70, 0x65,
-       0x12, 0x33, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x18, 0x02, 
0x20, 0x01, 0x28, 0x0b,
-       0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 
0x6f, 0x74, 0x6f, 0x62,
-       0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 
0x65, 0x52, 0x06, 0x69,
-       0x6e, 0x74, 0x56, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x72, 
0x56, 0x61, 0x6c, 0x18,
-       0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 
0x6c, 0x65, 0x2e, 0x70,
-       0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 
0x6e, 0x67, 0x56, 0x61,
-       0x6c, 0x75, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x56, 0x61, 0x6c, 0x42, 
0x2f, 0x5a, 0x2d, 0x64,
-       0x75, 0x62, 0x62, 0x6f, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 
0x6f, 0x72, 0x67, 0x2f,
-       0x64, 0x75, 0x62, 0x62, 0x6f, 0x2f, 0x64, 0x75, 0x62, 0x62, 0x6f, 0x6f, 
0x70, 0x65, 0x72, 0x61,
-       0x74, 0x6f, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x73, 
0x62, 0x06, 0x70, 0x72,
-       0x6f, 0x74, 0x6f, 0x33,
-}
+const file_values_types_proto_rawDesc = "" +
+       "\n" +
+       
"\x12values_types.proto\x12\x17dubbo.operator.v1alpha1\x1a\x1egoogle/protobuf/wrappers.proto\";\n"
 +
+       "\tGUIConfig\x12\x12\n" +
+       "\x04port\x18\x01 \x01(\x03R\x04port\x12\x1a\n" +
+       "\bnodePort\x18\x02 \x01(\x03R\bnodePort\"3\n" +
+       "\vProxyConfig\x12$\n" +
+       "\rclusterDomain\x18\x01 \x01(\tR\rclusterDomain\"K\n" +
+       "\x0fProxylessConfig\x128\n" +
+       "\x03cni\x18\x01 
\x01(\v2&.dubbo.operator.v1alpha1.MeshCNIConfigR\x03cni\"\xed\x02\n" +
+       "\rMeshCNIConfig\x12\x18\n" +
+       "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x14\n" +
+       "\x05image\x18\x02 \x01(\tR\x05image\x12\x16\n" +
+       "\x06binDir\x18\x03 \x01(\tR\x06binDir\x12\x18\n" +
+       "\aconfDir\x18\x04 \x01(\tR\aconfDir\x12\x1a\n" +
+       "\bstateDir\x18\x05 \x01(\tR\bstateDir\x12 \n" +
+       "\vxserverPort\x18\x06 \x01(\x03R\vxserverPort\x12\"\n" +
+       "\fmanagedLabel\x18\a \x01(\tR\fmanagedLabel\x12,\n" +
+       "\x11managedLabelValue\x18\b \x01(\tR\x11managedLabelValue\x12\"\n" +
+       "\fiptablesPath\x18\t \x01(\tR\fiptablesPath\x12\x1c\n" +
+       "\tipsetPath\x18\n" +
+       " \x01(\tR\tipsetPath\x12(\n" +
+       "\x0frefreshInterval\x18\v \x01(\tR\x0frefreshInterval\"\xe5\x02\n" +
+       "\fGlobalConfig\x12:\n" +
+       "\x05proxy\x18\x01 
\x01(\v2$.dubbo.operator.v1alpha1.ProxyConfigR\x05proxy\x12\x1e\n" +
+       "\n" +
+       "statusPort\x18\x02 \x01(\x03R\n" +
+       "statusPort\x124\n" +
+       "\x03gui\x18\x03 
\x01(\v2\".dubbo.operator.v1alpha1.GUIConfigR\x03gui\x12*\n" +
+       "\x10configValidation\x18\x04 \x01(\bR\x10configValidation\x12O\n" +
+       "\fmulticluster\x18\x05 
\x01(\v2+.dubbo.operator.v1alpha1.MulticlusterConfigR\fmulticluster\x12F\n" +
+       "\tproxyless\x18\x06 
\x01(\v2(.dubbo.operator.v1alpha1.ProxylessConfigR\tproxyless\"\xd4\x01\n" +
+       "\x12RemoteAccessConfig\x12\x18\n" +
+       "\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
+       "\vserviceType\x18\x02 \x01(\tR\vserviceType\x12\x18\n" +
+       "\axdsPort\x18\x03 \x01(\x03R\axdsPort\x12 \n" +
+       "\vwebhookPort\x18\x04 \x01(\x03R\vwebhookPort\x12*\n" +
+       "\x10certificateHosts\x18\x05 \x03(\tR\x10certificateHosts\x12\x1a\n" +
+       "\bgrpcPort\x18\x06 \x01(\x03R\bgrpcPort\"i\n" +
+       "\x17EastWestGatewayEndpoint\x12 \n" +
+       "\vclusterName\x18\x01 \x01(\tR\vclusterName\x12\x18\n" +
+       "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x12\n" +
+       "\x04port\x18\x03 \x01(\x03R\x04port\"\x91\x02\n" +
+       "\x15EastWestGatewayConfig\x12\x18\n" +
+       "\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
+       "\vserviceType\x18\x02 \x01(\tR\vserviceType\x12\x12\n" +
+       "\x04port\x18\x03 \x01(\x03R\x04port\x12\x1e\n" +
+       "\n" +
+       "targetPort\x18\x04 \x01(\x03R\n" +
+       "targetPort\x12\x1a\n" +
+       "\bnodePort\x18\x05 \x01(\x03R\bnodePort\x12L\n" +
+       "\bgateways\x18\x06 
\x03(\v20.dubbo.operator.v1alpha1.EastWestGatewayEndpointR\bgateways\x12\x1e\n" 
+
+       "\n" +
+       "xdsAddress\x18\a \x01(\tR\n" +
+       "xdsAddress\"\xbf\x01\n" +
+       "\x12MulticlusterConfig\x12O\n" +
+       "\fremoteAccess\x18\x01 
\x01(\v2+.dubbo.operator.v1alpha1.RemoteAccessConfigR\fremoteAccess\x12X\n" +
+       "\x0feastWestGateway\x18\x02 
\x01(\v2..dubbo.operator.v1alpha1.EastWestGatewayConfigR\x0feastWestGateway\"c\n"
 +
+       "\x06Values\x12=\n" +
+       "\x06global\x18\x01 
\x01(\v2%.dubbo.operator.v1alpha1.GlobalConfigR\x06global\x12\x1a\n" +
+       "\brevision\x18\x02 \x01(\tR\brevision\"\x8c\x01\n" +
+       "\vIntOrString\x12\x12\n" +
+       "\x04type\x18\x01 \x01(\x03R\x04type\x123\n" +
+       "\x06intVal\x18\x02 
\x01(\v2\x1b.google.protobuf.Int32ValueR\x06intVal\x124\n" +
+       "\x06strVal\x18\x03 
\x01(\v2\x1c.google.protobuf.StringValueR\x06strValB/Z-dubbo.apache.org/dubbo/dubbooperator/pkg/apisb\x06proto3"
 
 var (
        file_values_types_proto_rawDescOnce sync.Once
-       file_values_types_proto_rawDescData = file_values_types_proto_rawDesc
+       file_values_types_proto_rawDescData []byte
 )
 
 func file_values_types_proto_rawDescGZIP() []byte {
        file_values_types_proto_rawDescOnce.Do(func() {
-               file_values_types_proto_rawDescData = 
protoimpl.X.CompressGZIP(file_values_types_proto_rawDescData)
+               file_values_types_proto_rawDescData = 
protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_values_types_proto_rawDesc),
 len(file_values_types_proto_rawDesc)))
        })
        return file_values_types_proto_rawDescData
 }
 
-var file_values_types_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
-var file_values_types_proto_goTypes = []interface{}{
+var file_values_types_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
+var file_values_types_proto_goTypes = []any{
        (*GUIConfig)(nil),               // 0: dubbo.operator.v1alpha1.GUIConfig
        (*ProxyConfig)(nil),             // 1: 
dubbo.operator.v1alpha1.ProxyConfig
-       (*GlobalConfig)(nil),            // 2: 
dubbo.operator.v1alpha1.GlobalConfig
-       (*RemoteAccessConfig)(nil),      // 3: 
dubbo.operator.v1alpha1.RemoteAccessConfig
-       (*EastWestGatewayEndpoint)(nil), // 4: 
dubbo.operator.v1alpha1.EastWestGatewayEndpoint
-       (*EastWestGatewayConfig)(nil),   // 5: 
dubbo.operator.v1alpha1.EastWestGatewayConfig
-       (*MulticlusterConfig)(nil),      // 6: 
dubbo.operator.v1alpha1.MulticlusterConfig
-       (*Values)(nil),                  // 7: dubbo.operator.v1alpha1.Values
-       (*IntOrString)(nil),             // 8: 
dubbo.operator.v1alpha1.IntOrString
-       (*wrapperspb.Int32Value)(nil),   // 9: google.protobuf.Int32Value
-       (*wrapperspb.StringValue)(nil),  // 10: google.protobuf.StringValue
+       (*ProxylessConfig)(nil),         // 2: 
dubbo.operator.v1alpha1.ProxylessConfig
+       (*MeshCNIConfig)(nil),           // 3: 
dubbo.operator.v1alpha1.MeshCNIConfig
+       (*GlobalConfig)(nil),            // 4: 
dubbo.operator.v1alpha1.GlobalConfig
+       (*RemoteAccessConfig)(nil),      // 5: 
dubbo.operator.v1alpha1.RemoteAccessConfig
+       (*EastWestGatewayEndpoint)(nil), // 6: 
dubbo.operator.v1alpha1.EastWestGatewayEndpoint
+       (*EastWestGatewayConfig)(nil),   // 7: 
dubbo.operator.v1alpha1.EastWestGatewayConfig
+       (*MulticlusterConfig)(nil),      // 8: 
dubbo.operator.v1alpha1.MulticlusterConfig
+       (*Values)(nil),                  // 9: dubbo.operator.v1alpha1.Values
+       (*IntOrString)(nil),             // 10: 
dubbo.operator.v1alpha1.IntOrString
+       (*wrapperspb.Int32Value)(nil),   // 11: google.protobuf.Int32Value
+       (*wrapperspb.StringValue)(nil),  // 12: google.protobuf.StringValue
 }
 var file_values_types_proto_depIdxs = []int32{
-       1,  // 0: dubbo.operator.v1alpha1.GlobalConfig.proxy:type_name -> 
dubbo.operator.v1alpha1.ProxyConfig
-       0,  // 1: dubbo.operator.v1alpha1.GlobalConfig.gui:type_name -> 
dubbo.operator.v1alpha1.GUIConfig
-       6,  // 2: dubbo.operator.v1alpha1.GlobalConfig.multicluster:type_name 
-> dubbo.operator.v1alpha1.MulticlusterConfig
-       4,  // 3: 
dubbo.operator.v1alpha1.EastWestGatewayConfig.gateways:type_name -> 
dubbo.operator.v1alpha1.EastWestGatewayEndpoint
-       3,  // 4: 
dubbo.operator.v1alpha1.MulticlusterConfig.remoteAccess:type_name -> 
dubbo.operator.v1alpha1.RemoteAccessConfig
-       5,  // 5: 
dubbo.operator.v1alpha1.MulticlusterConfig.eastWestGateway:type_name -> 
dubbo.operator.v1alpha1.EastWestGatewayConfig
-       2,  // 6: dubbo.operator.v1alpha1.Values.global:type_name -> 
dubbo.operator.v1alpha1.GlobalConfig
-       9,  // 7: dubbo.operator.v1alpha1.IntOrString.intVal:type_name -> 
google.protobuf.Int32Value
-       10, // 8: dubbo.operator.v1alpha1.IntOrString.strVal:type_name -> 
google.protobuf.StringValue
-       9,  // [9:9] is the sub-list for method output_type
-       9,  // [9:9] is the sub-list for method input_type
-       9,  // [9:9] is the sub-list for extension type_name
-       9,  // [9:9] is the sub-list for extension extendee
-       0,  // [0:9] is the sub-list for field type_name
+       3,  // 0: dubbo.operator.v1alpha1.ProxylessConfig.cni:type_name -> 
dubbo.operator.v1alpha1.MeshCNIConfig
+       1,  // 1: dubbo.operator.v1alpha1.GlobalConfig.proxy:type_name -> 
dubbo.operator.v1alpha1.ProxyConfig
+       0,  // 2: dubbo.operator.v1alpha1.GlobalConfig.gui:type_name -> 
dubbo.operator.v1alpha1.GUIConfig
+       8,  // 3: dubbo.operator.v1alpha1.GlobalConfig.multicluster:type_name 
-> dubbo.operator.v1alpha1.MulticlusterConfig
+       2,  // 4: dubbo.operator.v1alpha1.GlobalConfig.proxyless:type_name -> 
dubbo.operator.v1alpha1.ProxylessConfig
+       6,  // 5: 
dubbo.operator.v1alpha1.EastWestGatewayConfig.gateways:type_name -> 
dubbo.operator.v1alpha1.EastWestGatewayEndpoint
+       5,  // 6: 
dubbo.operator.v1alpha1.MulticlusterConfig.remoteAccess:type_name -> 
dubbo.operator.v1alpha1.RemoteAccessConfig
+       7,  // 7: 
dubbo.operator.v1alpha1.MulticlusterConfig.eastWestGateway:type_name -> 
dubbo.operator.v1alpha1.EastWestGatewayConfig
+       4,  // 8: dubbo.operator.v1alpha1.Values.global:type_name -> 
dubbo.operator.v1alpha1.GlobalConfig
+       11, // 9: dubbo.operator.v1alpha1.IntOrString.intVal:type_name -> 
google.protobuf.Int32Value
+       12, // 10: dubbo.operator.v1alpha1.IntOrString.strVal:type_name -> 
google.protobuf.StringValue
+       11, // [11:11] is the sub-list for method output_type
+       11, // [11:11] is the sub-list for method input_type
+       11, // [11:11] is the sub-list for extension type_name
+       11, // [11:11] is the sub-list for extension extendee
+       0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_values_types_proto_init() }
@@ -799,123 +917,13 @@ func file_values_types_proto_init() {
        if File_values_types_proto != nil {
                return
        }
-       if !protoimpl.UnsafeEnabled {
-               file_values_types_proto_msgTypes[0].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*GUIConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[1].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*ProxyConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[2].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*GlobalConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[3].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*RemoteAccessConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[4].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*EastWestGatewayEndpoint); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[5].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*EastWestGatewayConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[6].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*MulticlusterConfig); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[7].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*Values); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-               file_values_types_proto_msgTypes[8].Exporter = func(v 
interface{}, i int) interface{} {
-                       switch v := v.(*IntOrString); i {
-                       case 0:
-                               return &v.state
-                       case 1:
-                               return &v.sizeCache
-                       case 2:
-                               return &v.unknownFields
-                       default:
-                               return nil
-                       }
-               }
-       }
        type x struct{}
        out := protoimpl.TypeBuilder{
                File: protoimpl.DescBuilder{
                        GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-                       RawDescriptor: file_values_types_proto_rawDesc,
+                       RawDescriptor: 
unsafe.Slice(unsafe.StringData(file_values_types_proto_rawDesc), 
len(file_values_types_proto_rawDesc)),
                        NumEnums:      0,
-                       NumMessages:   9,
+                       NumMessages:   11,
                        NumExtensions: 0,
                        NumServices:   0,
                },
@@ -924,7 +932,6 @@ func file_values_types_proto_init() {
                MessageInfos:      file_values_types_proto_msgTypes,
        }.Build()
        File_values_types_proto = out.File
-       file_values_types_proto_rawDesc = nil
        file_values_types_proto_goTypes = nil
        file_values_types_proto_depIdxs = nil
 }
diff --git a/dubbooperator/pkg/render/manifest_test.go 
b/dubbooperator/pkg/render/manifest_test.go
index 313b19c8..076e37b9 100644
--- a/dubbooperator/pkg/render/manifest_test.go
+++ b/dubbooperator/pkg/render/manifest_test.go
@@ -191,6 +191,73 @@ func TestGenerateManifestEastWestGatewayConfig(t 
*testing.T) {
        }
 }
 
+func TestGenerateManifestProxylessCNIIsGlobalByDefault(t *testing.T) {
+       manifests, _, err := GenerateManifest(nil, nil, nil, nil)
+       if err != nil {
+               t.Fatalf("GenerateManifest() error = %v", err)
+       }
+       if !hasManifest(manifests, "DaemonSet", "dubbo-cni-node") {
+               t.Fatal("dubbo-cni-node not rendered by default")
+       }
+
+       manifests, _, err = GenerateManifest(nil, 
[]string{"values.global.proxyless.cni.enabled=false"}, nil, nil)
+       if err != nil {
+               t.Fatalf("GenerateManifest() with proxyless CNI disabled error 
= %v", err)
+       }
+       if hasManifest(manifests, "DaemonSet", "dubbo-cni-node") {
+               t.Fatal("dubbo-cni-node rendered while proxyless CNI is 
explicitly disabled")
+       }
+
+       manifests, _, err = GenerateManifest(nil, []string{
+               "values.global.proxyless.cni.enabled=true",
+               "values.global.proxyless.cni.image=kdubbo/dubbod:cni-test",
+               "values.global.proxyless.cni.binDir=/var/lib/cni/bin",
+               "values.global.proxyless.cni.confDir=/var/lib/cni/net.d",
+               "values.global.proxyless.cni.stateDir=/run/dubbo-cni",
+               "values.global.proxyless.cni.xserverPort=16080",
+               "values.global.proxyless.cni.ipsetPath=/usr/sbin/ipset",
+               "values.global.proxyless.cni.refreshInterval=30s",
+       }, nil, nil)
+       if err != nil {
+               t.Fatalf("GenerateManifest() with proxyless CNI error = %v", 
err)
+       }
+       daemonSet := findManifest(t, manifests, "DaemonSet", "dubbo-cni-node")
+       containers, ok, err := unstructured.NestedSlice(daemonSet.Object, 
"spec", "template", "spec", "containers")
+       if err != nil || !ok || len(containers) == 0 {
+               t.Fatalf("daemonset containers missing: ok=%v err=%v", ok, err)
+       }
+       installer := containers[0].(map[string]interface{})
+       image, _, _ := unstructured.NestedString(installer, "image")
+       if image != "kdubbo/dubbod:cni-test" {
+               t.Fatalf("CNI installer image = %q, want 
kdubbo/dubbod:cni-test", image)
+       }
+       args, ok, err := unstructured.NestedStringSlice(installer, "args")
+       if err != nil || !ok {
+               t.Fatalf("installer args missing: ok=%v err=%v", ok, err)
+       }
+       for _, want := range []string{
+               "--bin-dir=/var/lib/cni/bin",
+               "--conf-dir=/var/lib/cni/net.d",
+               "--state-dir=/run/dubbo-cni",
+               "--xserver-port=16080",
+               "--ipset-path=/usr/sbin/ipset",
+               "--refresh-interval=30s",
+       } {
+               if !containsString(args, want) {
+                       t.Fatalf("installer args missing %q: %v", want, args)
+               }
+       }
+       volumes, ok, err := unstructured.NestedSlice(daemonSet.Object, "spec", 
"template", "spec", "volumes")
+       if err != nil || !ok {
+               t.Fatalf("daemonset volumes missing: ok=%v err=%v", ok, err)
+       }
+       for _, want := range []string{"/var/lib/cni/bin", "/var/lib/cni/net.d", 
"/run/dubbo-cni"} {
+               if !hasHostPathVolume(volumes, want) {
+                       t.Fatalf("daemonset missing hostPath volume %s", want)
+               }
+       }
+}
+
 func TestGenerateManifestSetOverridesHelmValues(t *testing.T) {
        manifests, _, err := GenerateManifest(nil, []string{
                "values.global.gui.port=26081",
@@ -380,3 +447,26 @@ func hasContainerPort(ports []interface{}, name string, 
number int64) bool {
        }
        return false
 }
+
+func containsString(items []string, want string) bool {
+       for _, item := range items {
+               if item == want {
+                       return true
+               }
+       }
+       return false
+}
+
+func hasHostPathVolume(volumes []interface{}, path string) bool {
+       for _, item := range volumes {
+               volume, ok := item.(map[string]interface{})
+               if !ok {
+                       continue
+               }
+               got, _, _ := unstructured.NestedString(volume, "hostPath", 
"path")
+               if got == path {
+                       return true
+               }
+       }
+       return false
+}
diff --git a/pkg/cni/config.go b/pkg/cni/config.go
new file mode 100644
index 00000000..eda915fd
--- /dev/null
+++ b/pkg/cni/config.go
@@ -0,0 +1,182 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "encoding/json"
+       "fmt"
+       "net"
+       "os"
+       "strings"
+
+       "github.com/apache/dubbo-kubernetes/pkg/kube/inject"
+)
+
+const (
+       defaultCNIVersion   = "1.0.0"
+       defaultStateDir     = "/var/run/dubbo-cni"
+       defaultIPTablesPath = "iptables"
+       defaultIPSetPath    = "ipset"
+       PluginType          = "dubbo-cni"
+)
+
+type NetConf struct {
+       CNIVersion        string          `json:"cniVersion,omitempty"`
+       Name              string          `json:"name,omitempty"`
+       Type              string          `json:"type,omitempty"`
+       PrevResult        json.RawMessage `json:"prevResult,omitempty"`
+       Kubernetes        KubernetesConf  `json:"kubernetes,omitempty"`
+       KubeConfig        string          `json:"kubeconfig,omitempty"`
+       ManagedLabel      string          `json:"managedLabel,omitempty"`
+       ManagedLabelValue string          `json:"managedLabelValue,omitempty"`
+       XServerPort       int             `json:"xserverPort,omitempty"`
+       StateDir          string          `json:"stateDir,omitempty"`
+       IPTablesPath      string          `json:"iptablesPath,omitempty"`
+       IPSetPath         string          `json:"ipsetPath,omitempty"`
+       DryRun            bool            `json:"dryRun,omitempty"`
+}
+
+type KubernetesConf struct {
+       KubeConfig string `json:"kubeconfig,omitempty"`
+}
+
+func ParseNetConf(data []byte) (NetConf, error) {
+       conf := NetConf{}
+       if len(strings.TrimSpace(string(data))) > 0 {
+               if err := json.Unmarshal(data, &conf); err != nil {
+                       return conf, fmt.Errorf("parse CNI config: %w", err)
+               }
+       }
+       if conf.CNIVersion == "" {
+               conf.CNIVersion = defaultCNIVersion
+       }
+       if conf.ManagedLabel == "" {
+               conf.ManagedLabel = inject.ProxylessManagedLabel
+       }
+       if conf.ManagedLabelValue == "" {
+               conf.ManagedLabelValue = inject.ProxylessManagedLabelValue
+       }
+       if conf.XServerPort == 0 {
+               conf.XServerPort = inject.ProxylessXServerPort
+       }
+       if conf.IPTablesPath == "" {
+               conf.IPTablesPath = defaultIPTablesPath
+       }
+       if conf.IPSetPath == "" {
+               conf.IPSetPath = defaultIPSetPath
+       }
+       return conf, nil
+}
+
+func (c NetConf) KubeConfigPath() string {
+       if c.Kubernetes.KubeConfig != "" {
+               return c.Kubernetes.KubeConfig
+       }
+       return c.KubeConfig
+}
+
+func (c NetConf) StateDirectory() string {
+       if c.StateDir != "" {
+               return c.StateDir
+       }
+       return defaultStateDir
+}
+
+func ResultOutput(conf NetConf) []byte {
+       if len(conf.PrevResult) > 0 {
+               return append([]byte(nil), conf.PrevResult...)
+       }
+       result := map[string]string{"cniVersion": conf.CNIVersion}
+       out, _ := json.Marshal(result)
+       return out
+}
+
+func PodIPsFromPrevResult(prev json.RawMessage) []string {
+       if len(prev) == 0 {
+               return nil
+       }
+       var result struct {
+               IPs []struct {
+                       Address string `json:"address"`
+               } `json:"ips"`
+       }
+       if err := json.Unmarshal(prev, &result); err != nil {
+               return nil
+       }
+       ips := make([]string, 0, len(result.IPs))
+       for _, item := range result.IPs {
+               ip := strings.TrimSpace(item.Address)
+               if ip == "" {
+                       continue
+               }
+               if parsedIP, _, err := net.ParseCIDR(ip); err == nil {
+                       ips = append(ips, parsedIP.String())
+                       continue
+               }
+               if parsedIP := net.ParseIP(ip); parsedIP != nil {
+                       ips = append(ips, parsedIP.String())
+               }
+       }
+       return ips
+}
+
+type Env struct {
+       Command     string
+       ContainerID string
+       NetNS       string
+       IfName      string
+       Args        string
+}
+
+func EnvFromOS() Env {
+       return Env{
+               Command:     os.Getenv("CNI_COMMAND"),
+               ContainerID: os.Getenv("CNI_CONTAINERID"),
+               NetNS:       os.Getenv("CNI_NETNS"),
+               IfName:      os.Getenv("CNI_IFNAME"),
+               Args:        os.Getenv("CNI_ARGS"),
+       }
+}
+
+func EnvHasKubernetesPod(env Env) bool {
+       _, ok := PodRefFromCNIArgs(env.Args)
+       return ok
+}
+
+type PodRef struct {
+       Namespace string
+       Name      string
+}
+
+func PodRefFromCNIArgs(args string) (PodRef, bool) {
+       values := map[string]string{}
+       for _, item := range strings.Split(args, ";") {
+               item = strings.TrimSpace(item)
+               if item == "" {
+                       continue
+               }
+               key, value, ok := strings.Cut(item, "=")
+               if !ok {
+                       continue
+               }
+               values[key] = value
+       }
+       ref := PodRef{
+               Namespace: values["K8S_POD_NAMESPACE"],
+               Name:      values["K8S_POD_NAME"],
+       }
+       return ref, ref.Namespace != "" && ref.Name != ""
+}
diff --git a/pkg/cni/config_test.go b/pkg/cni/config_test.go
new file mode 100644
index 00000000..f4454a91
--- /dev/null
+++ b/pkg/cni/config_test.go
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "encoding/json"
+       "testing"
+
+       "github.com/apache/dubbo-kubernetes/pkg/kube/inject"
+)
+
+func TestParseNetConfDefaults(t *testing.T) {
+       conf, err := ParseNetConf([]byte(`{"name":"dubbo"}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       if conf.CNIVersion != defaultCNIVersion {
+               t.Fatalf("cniVersion = %q, want %q", conf.CNIVersion, 
defaultCNIVersion)
+       }
+       if conf.ManagedLabel != inject.ProxylessManagedLabel || 
conf.ManagedLabelValue != inject.ProxylessManagedLabelValue {
+               t.Fatalf("managed label = %s/%s, want %s/%s", 
conf.ManagedLabel, conf.ManagedLabelValue,
+                       inject.ProxylessManagedLabel, 
inject.ProxylessManagedLabelValue)
+       }
+       if conf.XServerPort != inject.ProxylessXServerPort {
+               t.Fatalf("xserverPort = %d, want %d", conf.XServerPort, 
inject.ProxylessXServerPort)
+       }
+       if conf.IPTablesPath != defaultIPTablesPath || conf.IPSetPath != 
defaultIPSetPath {
+               t.Fatalf("iptables/ipset path = %s/%s, want %s/%s", 
conf.IPTablesPath, conf.IPSetPath, defaultIPTablesPath, defaultIPSetPath)
+       }
+}
+
+func TestPodRefFromCNIArgs(t *testing.T) {
+       ref, ok := 
PodRefFromCNIArgs("IgnoreUnknown=1;K8S_POD_NAMESPACE=app;K8S_POD_NAME=nginx-v1")
+       if !ok {
+               t.Fatal("PodRefFromCNIArgs() ok = false, want true")
+       }
+       if ref.Namespace != "app" || ref.Name != "nginx-v1" {
+               t.Fatalf("pod ref = %#v, want app/nginx-v1", ref)
+       }
+}
+
+func TestPodIPsFromPrevResult(t *testing.T) {
+       prev, _ := json.Marshal(map[string]any{
+               "cniVersion": "1.0.0",
+               "ips": []map[string]string{
+                       {"address": "10.244.0.12/24"},
+               },
+       })
+       ips := PodIPsFromPrevResult(prev)
+       if len(ips) != 1 || ips[0] != "10.244.0.12" {
+               t.Fatalf("ips = %v, want [10.244.0.12]", ips)
+       }
+}
diff --git a/pkg/cni/install.go b/pkg/cni/install.go
new file mode 100644
index 00000000..86ae9829
--- /dev/null
+++ b/pkg/cni/install.go
@@ -0,0 +1,497 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "io"
+       "os"
+       "path/filepath"
+       "sort"
+       "strings"
+       "time"
+
+       "github.com/apache/dubbo-kubernetes/pkg/kube/inject"
+)
+
+const (
+       defaultCNIBinDir          = "/opt/cni/bin"
+       defaultCNIConfDir         = "/etc/cni/net.d"
+       defaultInstallerInterval  = time.Minute
+       defaultKubeConfigFileName = "dubbo-cni-kubeconfig"
+       installBackupSuffix       = ".dubbo-cni.bak"
+)
+
+type InstallerOptions struct {
+       SourceBinary            string
+       BinDir                  string
+       ConfDir                 string
+       StateDir                string
+       KubeConfigPath          string
+       TokenFile               string
+       CAFile                  string
+       ServiceAccountTokenPath string
+       ServiceAccountCAPath    string
+       APIServer               string
+       ManagedLabel            string
+       ManagedLabelValue       string
+       IPTablesPath            string
+       IPSetPath               string
+       XServerPort             int
+}
+
+func DefaultInstallerOptions() InstallerOptions {
+       return InstallerOptions{
+               BinDir:                  defaultCNIBinDir,
+               ConfDir:                 defaultCNIConfDir,
+               StateDir:                defaultStateDir,
+               ServiceAccountTokenPath: 
"/var/run/secrets/kubernetes.io/serviceaccount/token",
+               ServiceAccountCAPath:    
"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
+               ManagedLabel:            inject.ProxylessManagedLabel,
+               ManagedLabelValue:       inject.ProxylessManagedLabelValue,
+               IPTablesPath:            defaultIPTablesPath,
+               IPSetPath:               defaultIPSetPath,
+               XServerPort:             inject.ProxylessXServerPort,
+       }
+}
+
+func DefaultInstallerInterval() time.Duration {
+       return defaultInstallerInterval
+}
+
+func (o *InstallerOptions) applyDefaults(requireAPIServer bool) error {
+       defaults := DefaultInstallerOptions()
+       if o.SourceBinary == "" {
+               exe, err := os.Executable()
+               if err != nil {
+                       return fmt.Errorf("find current executable: %w", err)
+               }
+               o.SourceBinary = exe
+       }
+       if o.BinDir == "" {
+               o.BinDir = defaults.BinDir
+       }
+       if o.ConfDir == "" {
+               o.ConfDir = defaults.ConfDir
+       }
+       if o.StateDir == "" {
+               o.StateDir = defaults.StateDir
+       }
+       if o.KubeConfigPath == "" {
+               o.KubeConfigPath = filepath.Join(o.ConfDir, 
defaultKubeConfigFileName)
+       }
+       if o.TokenFile == "" {
+               o.TokenFile = filepath.Join(o.StateDir, "token")
+       }
+       if o.CAFile == "" {
+               o.CAFile = filepath.Join(o.StateDir, "ca.crt")
+       }
+       if o.ServiceAccountTokenPath == "" {
+               o.ServiceAccountTokenPath = defaults.ServiceAccountTokenPath
+       }
+       if o.ServiceAccountCAPath == "" {
+               o.ServiceAccountCAPath = defaults.ServiceAccountCAPath
+       }
+       if o.APIServer == "" {
+               o.APIServer = kubernetesServiceHost()
+       }
+       if o.ManagedLabel == "" {
+               o.ManagedLabel = defaults.ManagedLabel
+       }
+       if o.ManagedLabelValue == "" {
+               o.ManagedLabelValue = defaults.ManagedLabelValue
+       }
+       if o.IPTablesPath == "" {
+               o.IPTablesPath = defaults.IPTablesPath
+       }
+       if o.IPSetPath == "" {
+               o.IPSetPath = defaults.IPSetPath
+       }
+       if o.XServerPort == 0 {
+               o.XServerPort = defaults.XServerPort
+       }
+       if requireAPIServer && o.APIServer == "" {
+               return fmt.Errorf("kubernetes API server is required")
+       }
+       return nil
+}
+
+func Install(ctx context.Context, opts InstallerOptions) error {
+       if err := opts.applyDefaults(true); err != nil {
+               return err
+       }
+       if err := ctx.Err(); err != nil {
+               return err
+       }
+       for _, dir := range []string{opts.BinDir, opts.ConfDir, opts.StateDir} {
+               if err := os.MkdirAll(dir, 0o755); err != nil {
+                       return fmt.Errorf("create %s: %w", dir, err)
+               }
+       }
+       if err := copyFileAtomic(opts.SourceBinary, filepath.Join(opts.BinDir, 
PluginType), 0o755); err != nil {
+               return err
+       }
+       if err := copyFileAtomic(opts.ServiceAccountTokenPath, opts.TokenFile, 
0o600); err != nil {
+               return err
+       }
+       if err := copyFileAtomic(opts.ServiceAccountCAPath, opts.CAFile, 
0o644); err != nil {
+               return err
+       }
+       if err := writeKubeConfig(opts); err != nil {
+               return err
+       }
+       return installCNIConfig(opts)
+}
+
+func InstallLoop(ctx context.Context, opts InstallerOptions, interval 
time.Duration) error {
+       if interval <= 0 {
+               interval = defaultInstallerInterval
+       }
+       if err := Install(ctx, opts); err != nil {
+               return err
+       }
+       ticker := time.NewTicker(interval)
+       defer ticker.Stop()
+       for {
+               select {
+               case <-ctx.Done():
+                       return nil
+               case <-ticker.C:
+                       if err := Install(ctx, opts); err != nil {
+                               return err
+                       }
+               }
+       }
+}
+
+func Uninstall(opts InstallerOptions) error {
+       if err := opts.applyDefaults(false); err != nil {
+               return err
+       }
+       if err := uninstallCNIConfig(opts.ConfDir); err != nil {
+               return err
+       }
+       for _, path := range []string{
+               filepath.Join(opts.BinDir, PluginType),
+               opts.KubeConfigPath,
+               opts.TokenFile,
+               opts.CAFile,
+       } {
+               if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
+                       return fmt.Errorf("remove %s: %w", path, err)
+               }
+       }
+       return nil
+}
+
+func writeKubeConfig(opts InstallerOptions) error {
+       data := fmt.Sprintf(`apiVersion: v1
+kind: Config
+clusters:
+- name: kubernetes
+  cluster:
+    certificate-authority: %s
+    server: %s
+users:
+- name: dubbo-cni
+  user:
+    tokenFile: %s
+contexts:
+- name: dubbo-cni
+  context:
+    cluster: kubernetes
+    user: dubbo-cni
+current-context: dubbo-cni
+`, opts.CAFile, opts.APIServer, opts.TokenFile)
+       return writeFileAtomic(opts.KubeConfigPath, []byte(data), 0o600)
+}
+
+func installCNIConfig(opts InstallerOptions) error {
+       target, err := findPrimaryCNIConfig(opts.ConfDir)
+       if err != nil {
+               return err
+       }
+       if strings.HasSuffix(target, ".conflist") {
+               return patchConflist(target, pluginConfig(opts), true)
+       }
+       return convertConfToConflist(target, pluginConfig(opts))
+}
+
+func uninstallCNIConfig(confDir string) error {
+       entries, err := os.ReadDir(confDir)
+       if err != nil {
+               if os.IsNotExist(err) {
+                       return nil
+               }
+               return fmt.Errorf("read CNI config dir %s: %w", confDir, err)
+       }
+       sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < 
entries[j].Name() })
+       for _, entry := range entries {
+               if entry.IsDir() {
+                       continue
+               }
+               path := filepath.Join(confDir, entry.Name())
+               if _, err := os.Stat(path); os.IsNotExist(err) {
+                       continue
+               } else if err != nil {
+                       return fmt.Errorf("stat %s: %w", path, err)
+               }
+               if strings.HasSuffix(entry.Name(), installBackupSuffix) {
+                       original := strings.TrimSuffix(path, 
installBackupSuffix)
+                       if strings.HasSuffix(original, ".conf") {
+                               _ = os.Remove(strings.TrimSuffix(original, 
".conf") + ".conflist")
+                       }
+                       if err := os.Rename(path, original); err != nil {
+                               return fmt.Errorf("restore %s: %w", original, 
err)
+                       }
+                       continue
+               }
+               if strings.HasSuffix(entry.Name(), ".conflist") {
+                       if err := patchConflist(path, nil, false); err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func findPrimaryCNIConfig(confDir string) (string, error) {
+       entries, err := os.ReadDir(confDir)
+       if err != nil {
+               return "", fmt.Errorf("read CNI config dir %s: %w", confDir, 
err)
+       }
+       var conflists, confs []string
+       for _, entry := range entries {
+               if entry.IsDir() || strings.HasSuffix(entry.Name(), 
installBackupSuffix) {
+                       continue
+               }
+               path := filepath.Join(confDir, entry.Name())
+               switch {
+               case strings.HasSuffix(entry.Name(), ".conflist"):
+                       conflists = append(conflists, path)
+               case strings.HasSuffix(entry.Name(), ".conf"), 
strings.HasSuffix(entry.Name(), ".json"):
+                       confs = append(confs, path)
+               }
+       }
+       sort.Strings(conflists)
+       sort.Strings(confs)
+       for _, path := range append(conflists, confs...) {
+               if ok, err := isPrimaryCNIConfig(path); err != nil {
+                       return "", err
+               } else if ok {
+                       return path, nil
+               }
+       }
+       return "", fmt.Errorf("no primary CNI config found in %s", confDir)
+}
+
+func isPrimaryCNIConfig(path string) (bool, error) {
+       raw, err := os.ReadFile(path)
+       if err != nil {
+               return false, fmt.Errorf("read %s: %w", path, err)
+       }
+       var cfg map[string]any
+       if err := json.Unmarshal(raw, &cfg); err != nil {
+               return false, nil
+       }
+       if plugins, ok := cfg["plugins"].([]any); ok {
+               for _, item := range plugins {
+                       plugin, ok := item.(map[string]any)
+                       if !ok {
+                               continue
+                       }
+                       if plugin["type"] == PluginType {
+                               return true, nil
+                       }
+                       if plugin["type"] != "loopback" {
+                               return true, nil
+                       }
+               }
+               return false, nil
+       }
+       return cfg["type"] != PluginType && cfg["type"] != "loopback", nil
+}
+
+func patchConflist(path string, plugin map[string]any, backup bool) error {
+       raw, err := os.ReadFile(path)
+       if err != nil {
+               return fmt.Errorf("read %s: %w", path, err)
+       }
+       var cfg map[string]any
+       if err := json.Unmarshal(raw, &cfg); err != nil {
+               return fmt.Errorf("parse CNI conflist %s: %w", path, err)
+       }
+       plugins, _ := cfg["plugins"].([]any)
+       next := make([]any, 0, len(plugins)+1)
+       for _, item := range plugins {
+               existing, ok := item.(map[string]any)
+               if ok && existing["type"] == PluginType {
+                       continue
+               }
+               next = append(next, item)
+       }
+       if plugin != nil {
+               next = append(next, plugin)
+       }
+       cfg["plugins"] = next
+       if backup {
+               if err := writeBackup(path, raw); err != nil {
+                       return err
+               }
+       }
+       return writeJSONAtomic(path, cfg, 0o644)
+}
+
+func convertConfToConflist(path string, plugin map[string]any) error {
+       raw, err := os.ReadFile(path)
+       if err != nil {
+               return fmt.Errorf("read %s: %w", path, err)
+       }
+       var cfg map[string]any
+       if err := json.Unmarshal(raw, &cfg); err != nil {
+               return fmt.Errorf("parse CNI config %s: %w", path, err)
+       }
+       backupPath := path + installBackupSuffix
+       if _, err := os.Stat(backupPath); os.IsNotExist(err) {
+               if err := os.Rename(path, backupPath); err != nil {
+                       return fmt.Errorf("backup %s: %w", path, err)
+               }
+       } else if err != nil {
+               return fmt.Errorf("stat %s: %w", backupPath, err)
+       }
+       conflist := map[string]any{
+               "cniVersion": stringValue(cfg["cniVersion"], defaultCNIVersion),
+               "name":       stringValue(cfg["name"], 
strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))),
+               "plugins":    []any{cfg, plugin},
+       }
+       return writeJSONAtomic(strings.TrimSuffix(path, 
filepath.Ext(path))+".conflist", conflist, 0o644)
+}
+
+func pluginConfig(opts InstallerOptions) map[string]any {
+       return map[string]any{
+               "type":              PluginType,
+               "name":              PluginType,
+               "kubernetes":        map[string]any{"kubeconfig": 
opts.KubeConfigPath},
+               "managedLabel":      opts.ManagedLabel,
+               "managedLabelValue": opts.ManagedLabelValue,
+               "xserverPort":       opts.XServerPort,
+               "stateDir":          opts.StateDir,
+               "iptablesPath":      opts.IPTablesPath,
+               "ipsetPath":         opts.IPSetPath,
+       }
+}
+
+func writeBackup(path string, data []byte) error {
+       backup := path + installBackupSuffix
+       if _, err := os.Stat(backup); err == nil {
+               return nil
+       } else if !os.IsNotExist(err) {
+               return fmt.Errorf("stat %s: %w", backup, err)
+       }
+       return writeFileAtomic(backup, data, 0o644)
+}
+
+func writeJSONAtomic(path string, value any, mode os.FileMode) error {
+       data, err := json.MarshalIndent(value, "", "  ")
+       if err != nil {
+               return err
+       }
+       data = append(data, '\n')
+       return writeFileAtomic(path, data, mode)
+}
+
+func copyFileAtomic(src, dst string, mode os.FileMode) error {
+       in, err := os.Open(src)
+       if err != nil {
+               return fmt.Errorf("open %s: %w", src, err)
+       }
+       defer in.Close()
+       if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
+               return fmt.Errorf("create %s: %w", filepath.Dir(dst), err)
+       }
+       tmp, err := os.CreateTemp(filepath.Dir(dst), 
"."+filepath.Base(dst)+".tmp-*")
+       if err != nil {
+               return fmt.Errorf("create temp file for %s: %w", dst, err)
+       }
+       tmpName := tmp.Name()
+       defer os.Remove(tmpName)
+       if _, err := io.Copy(tmp, in); err != nil {
+               _ = tmp.Close()
+               return fmt.Errorf("copy %s to %s: %w", src, dst, err)
+       }
+       if err := tmp.Chmod(mode); err != nil {
+               _ = tmp.Close()
+               return fmt.Errorf("chmod %s: %w", tmpName, err)
+       }
+       if err := tmp.Close(); err != nil {
+               return fmt.Errorf("close %s: %w", tmpName, err)
+       }
+       if err := os.Rename(tmpName, dst); err != nil {
+               return fmt.Errorf("install %s: %w", dst, err)
+       }
+       return nil
+}
+
+func writeFileAtomic(path string, data []byte, mode os.FileMode) error {
+       if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+               return fmt.Errorf("create %s: %w", filepath.Dir(path), err)
+       }
+       tmp, err := os.CreateTemp(filepath.Dir(path), 
"."+filepath.Base(path)+".tmp-*")
+       if err != nil {
+               return fmt.Errorf("create temp file for %s: %w", path, err)
+       }
+       tmpName := tmp.Name()
+       defer os.Remove(tmpName)
+       if _, err := tmp.Write(data); err != nil {
+               _ = tmp.Close()
+               return fmt.Errorf("write %s: %w", tmpName, err)
+       }
+       if err := tmp.Chmod(mode); err != nil {
+               _ = tmp.Close()
+               return fmt.Errorf("chmod %s: %w", tmpName, err)
+       }
+       if err := tmp.Close(); err != nil {
+               return fmt.Errorf("close %s: %w", tmpName, err)
+       }
+       if err := os.Rename(tmpName, path); err != nil {
+               return fmt.Errorf("write %s: %w", path, err)
+       }
+       return nil
+}
+
+func kubernetesServiceHost() string {
+       host := os.Getenv("KUBERNETES_SERVICE_HOST")
+       if host == "" {
+               return ""
+       }
+       port := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
+       if port == "" {
+               port = os.Getenv("KUBERNETES_SERVICE_PORT")
+       }
+       if port == "" {
+               port = "443"
+       }
+       return "https://"; + host + ":" + port
+}
+
+func stringValue(value any, fallback string) string {
+       if s, ok := value.(string); ok && s != "" {
+               return s
+       }
+       return fallback
+}
diff --git a/pkg/cni/install_test.go b/pkg/cni/install_test.go
new file mode 100644
index 00000000..07a1f93e
--- /dev/null
+++ b/pkg/cni/install_test.go
@@ -0,0 +1,149 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "encoding/json"
+       "os"
+       "path/filepath"
+       "testing"
+)
+
+func TestInstallAddsPluginToConflist(t *testing.T) {
+       root := t.TempDir()
+       opts := installerTestOptions(t, root)
+       confPath := filepath.Join(opts.ConfDir, "10-primary.conflist")
+       writeTestFile(t, confPath, `{
+  "cniVersion": "1.0.0",
+  "name": "primary",
+  "plugins": [
+    {"type": "bridge", "name": "primary"}
+  ]
+}`)
+
+       if err := Install(context.Background(), opts); err != nil {
+               t.Fatalf("Install() error = %v", err)
+       }
+
+       if _, err := os.Stat(filepath.Join(opts.BinDir, PluginType)); err != 
nil {
+               t.Fatalf("plugin binary not installed: %v", err)
+       }
+       plugins := readPlugins(t, confPath)
+       if len(plugins) != 2 {
+               t.Fatalf("plugins length = %d, want 2", len(plugins))
+       }
+       if plugins[1]["type"] != PluginType {
+               t.Fatalf("last plugin type = %v, want %s", plugins[1]["type"], 
PluginType)
+       }
+       kubernetes := plugins[1]["kubernetes"].(map[string]any)
+       if kubernetes["kubeconfig"] != opts.KubeConfigPath {
+               t.Fatalf("kubeconfig = %v, want %s", kubernetes["kubeconfig"], 
opts.KubeConfigPath)
+       }
+       if plugins[1]["ipsetPath"] != defaultIPSetPath {
+               t.Fatalf("ipsetPath = %v, want %s", plugins[1]["ipsetPath"], 
defaultIPSetPath)
+       }
+       if _, err := os.Stat(confPath + installBackupSuffix); err != nil {
+               t.Fatalf("backup not written: %v", err)
+       }
+}
+
+func TestInstallConvertsConfAndUninstallRestores(t *testing.T) {
+       root := t.TempDir()
+       opts := installerTestOptions(t, root)
+       confPath := filepath.Join(opts.ConfDir, "10-primary.conf")
+       writeTestFile(t, confPath, 
`{"cniVersion":"1.0.0","name":"primary","type":"bridge"}`)
+
+       if err := Install(context.Background(), opts); err != nil {
+               t.Fatalf("Install() error = %v", err)
+       }
+       if _, err := os.Stat(confPath); !os.IsNotExist(err) {
+               t.Fatalf("primary .conf still exists: %v", err)
+       }
+       conflistPath := filepath.Join(opts.ConfDir, "10-primary.conflist")
+       plugins := readPlugins(t, conflistPath)
+       if len(plugins) != 2 || plugins[1]["type"] != PluginType {
+               t.Fatalf("converted plugins = %#v", plugins)
+       }
+
+       if err := Uninstall(opts); err != nil {
+               t.Fatalf("Uninstall() error = %v", err)
+       }
+       if _, err := os.Stat(confPath); err != nil {
+               t.Fatalf("primary .conf not restored: %v", err)
+       }
+       if _, err := os.Stat(conflistPath); !os.IsNotExist(err) {
+               t.Fatalf("converted conflist still exists: %v", err)
+       }
+       if _, err := os.Stat(filepath.Join(opts.BinDir, PluginType)); 
!os.IsNotExist(err) {
+               t.Fatalf("plugin binary still exists: %v", err)
+       }
+}
+
+func TestInstallRequiresPrimaryConfig(t *testing.T) {
+       root := t.TempDir()
+       opts := installerTestOptions(t, root)
+       if err := Install(context.Background(), opts); err == nil {
+               t.Fatal("Install() returned nil error")
+       }
+}
+
+func installerTestOptions(t *testing.T, root string) InstallerOptions {
+       t.Helper()
+       source := filepath.Join(root, "source-dubbo-cni")
+       writeTestFile(t, source, "binary")
+       token := filepath.Join(root, "service-account-token")
+       ca := filepath.Join(root, "service-account-ca.crt")
+       writeTestFile(t, token, "token")
+       writeTestFile(t, ca, "ca")
+       return InstallerOptions{
+               SourceBinary:            source,
+               BinDir:                  filepath.Join(root, "bin"),
+               ConfDir:                 filepath.Join(root, "net.d"),
+               StateDir:                filepath.Join(root, "state"),
+               KubeConfigPath:          filepath.Join(root, "net.d", 
"dubbo-cni-kubeconfig"),
+               TokenFile:               filepath.Join(root, "state", "token"),
+               CAFile:                  filepath.Join(root, "state", "ca.crt"),
+               ServiceAccountTokenPath: token,
+               ServiceAccountCAPath:    ca,
+               APIServer:               "https://10.0.0.1:443";,
+       }
+}
+
+func writeTestFile(t *testing.T, path, data string) {
+       t.Helper()
+       if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+               t.Fatalf("MkdirAll() error = %v", err)
+       }
+       if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
+               t.Fatalf("WriteFile() error = %v", err)
+       }
+}
+
+func readPlugins(t *testing.T, path string) []map[string]any {
+       t.Helper()
+       raw, err := os.ReadFile(path)
+       if err != nil {
+               t.Fatalf("ReadFile() error = %v", err)
+       }
+       var cfg struct {
+               Plugins []map[string]any `json:"plugins"`
+       }
+       if err := json.Unmarshal(raw, &cfg); err != nil {
+               t.Fatalf("Unmarshal() error = %v", err)
+       }
+       return cfg.Plugins
+}
diff --git a/pkg/cni/iptables.go b/pkg/cni/iptables.go
new file mode 100644
index 00000000..85f976f8
--- /dev/null
+++ b/pkg/cni/iptables.go
@@ -0,0 +1,170 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "bytes"
+       "context"
+       "fmt"
+       "net"
+       "os/exec"
+)
+
+const (
+       meshInboundChain = "DUBBO-XSERVER-INBOUND"
+       meshPodIPSet     = "DUBBO-XSERVER-PODS"
+)
+
+type CommandRunner interface {
+       Run(ctx context.Context, name string, args ...string) ([]byte, error)
+}
+
+type execCommandRunner struct{}
+
+func (execCommandRunner) Run(ctx context.Context, name string, args ...string) 
([]byte, error) {
+       cmd := exec.CommandContext(ctx, name, args...)
+       return cmd.CombinedOutput()
+}
+
+type IPTablesRuleManager struct {
+       path        string
+       ipsetPath   string
+       xserverPort int
+       dryRun      bool
+       runner      CommandRunner
+}
+
+func NewIPTablesRuleManager(conf NetConf) *IPTablesRuleManager {
+       return &IPTablesRuleManager{
+               path:        conf.IPTablesPath,
+               ipsetPath:   conf.IPSetPath,
+               xserverPort: conf.XServerPort,
+               dryRun:      conf.DryRun,
+               runner:      execCommandRunner{},
+       }
+}
+
+func NewIPTablesRuleManagerWithRunner(conf NetConf, runner CommandRunner) 
*IPTablesRuleManager {
+       manager := NewIPTablesRuleManager(conf)
+       manager.runner = runner
+       return manager
+}
+
+func (m *IPTablesRuleManager) AddPodRules(ctx context.Context, podIP string) 
error {
+       ip, err := normalizePodIP(podIP)
+       if err != nil {
+               return err
+       }
+       if err := m.ensureBase(ctx); err != nil {
+               return err
+       }
+       return m.runIPSet(ctx, "add", meshPodIPSet, ip, "-exist")
+}
+
+func (m *IPTablesRuleManager) DeletePodRules(ctx context.Context, podIP 
string) error {
+       ip, err := normalizePodIP(podIP)
+       if err != nil {
+               return err
+       }
+       return m.runIPSet(ctx, "del", meshPodIPSet, ip, "-exist")
+}
+
+func (m *IPTablesRuleManager) ensureBase(ctx context.Context) error {
+       if err := m.runIPSet(ctx, "create", meshPodIPSet, "hash:ip", "-exist"); 
err != nil {
+               return err
+       }
+       if err := m.runIgnoreExists(ctx, "-w", "-t", "filter", "-N", 
meshInboundChain); err != nil {
+               return err
+       }
+       for _, chain := range []string{"FORWARD", "OUTPUT"} {
+               if err := m.run(ctx, "-w", "-t", "filter", "-C", chain, "-j", 
meshInboundChain); err == nil {
+                       continue
+               }
+               if err := m.run(ctx, "-w", "-t", "filter", "-I", chain, "1", 
"-j", meshInboundChain); err != nil {
+                       return err
+               }
+       }
+       allowXServer := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "--dport", fmt.Sprint(m.xserverPort), "-j", "RETURN"}
+       rejectOtherTCP := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "-j", "REJECT"}
+       m.deleteRepeated(ctx, allowXServer...)
+       m.deleteRepeated(ctx, rejectOtherTCP...)
+       if err := m.appendRule(ctx, allowXServer...); err != nil {
+               return err
+       }
+       return m.appendRule(ctx, rejectOtherTCP...)
+}
+
+func (m *IPTablesRuleManager) deleteRepeated(ctx context.Context, args 
...string) {
+       cmd := append([]string{"-w", "-t", "filter", "-D", meshInboundChain}, 
args...)
+       for i := 0; i < 20; i++ {
+               if err := m.run(ctx, cmd...); err != nil {
+                       return
+               }
+       }
+}
+
+func (m *IPTablesRuleManager) appendRule(ctx context.Context, args ...string) 
error {
+       cmd := append([]string{"-w", "-t", "filter", "-A", meshInboundChain}, 
args...)
+       return m.run(ctx, cmd...)
+}
+
+func (m *IPTablesRuleManager) runIgnoreExists(ctx context.Context, args 
...string) error {
+       err := m.run(ctx, args...)
+       if err == nil {
+               return nil
+       }
+       if stringsContains(err.Error(), "Chain already exists") {
+               return nil
+       }
+       return err
+}
+
+func (m *IPTablesRuleManager) run(ctx context.Context, args ...string) error {
+       if m.dryRun {
+               return nil
+       }
+       out, err := m.runner.Run(ctx, m.path, args...)
+       if err != nil {
+               return fmt.Errorf("%s %v: %w: %s", m.path, args, err, 
string(bytes.TrimSpace(out)))
+       }
+       return nil
+}
+
+func (m *IPTablesRuleManager) runIPSet(ctx context.Context, args ...string) 
error {
+       if m.dryRun {
+               return nil
+       }
+       out, err := m.runner.Run(ctx, m.ipsetPath, args...)
+       if err != nil {
+               return fmt.Errorf("%s %v: %w: %s", m.ipsetPath, args, err, 
string(bytes.TrimSpace(out)))
+       }
+       return nil
+}
+
+func normalizePodIP(podIP string) (string, error) {
+       ip := net.ParseIP(podIP)
+       if ip == nil {
+               return "", fmt.Errorf("invalid pod IP %q", podIP)
+       }
+       if ip.To4() == nil {
+               return "", fmt.Errorf("IPv6 pod IP %q is not supported yet", 
podIP)
+       }
+       return ip.String(), nil
+}
+
+func stringsContains(s, substr string) bool {
+       return bytes.Contains([]byte(s), []byte(substr))
+}
diff --git a/pkg/cni/iptables_test.go b/pkg/cni/iptables_test.go
new file mode 100644
index 00000000..845104f8
--- /dev/null
+++ b/pkg/cni/iptables_test.go
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "strings"
+       "testing"
+)
+
+func TestIPTablesRuleManagerAddsXServerBoundaryRules(t *testing.T) {
+       runner := &recordingRunner{}
+       conf, err := ParseNetConf([]byte(`{"xserverPort":15080}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       manager := NewIPTablesRuleManagerWithRunner(conf, runner)
+
+       if err := manager.AddPodRules(context.Background(), "10.244.0.12"); err 
!= nil {
+               t.Fatalf("AddPodRules() failed: %v", err)
+       }
+
+       joined := strings.Join(runner.commands, "\n")
+       for _, want := range []string{
+               "ipset create DUBBO-XSERVER-PODS hash:ip -exist",
+               "-N DUBBO-XSERVER-INBOUND",
+               "-I FORWARD 1 -j DUBBO-XSERVER-INBOUND",
+               "-I OUTPUT 1 -j DUBBO-XSERVER-INBOUND",
+               "-A DUBBO-XSERVER-INBOUND -m set --match-set DUBBO-XSERVER-PODS 
dst -p tcp --dport 15080 -j RETURN",
+               "-A DUBBO-XSERVER-INBOUND -m set --match-set DUBBO-XSERVER-PODS 
dst -p tcp -j REJECT",
+               "ipset add DUBBO-XSERVER-PODS 10.244.0.12 -exist",
+       } {
+               if !strings.Contains(joined, want) {
+                       t.Fatalf("commands missing %q:\n%s", want, joined)
+               }
+       }
+}
+
+type recordingRunner struct {
+       commands []string
+}
+
+func (r *recordingRunner) Run(_ context.Context, name string, args ...string) 
([]byte, error) {
+       r.commands = append(r.commands, name+" "+strings.Join(args, " "))
+       for _, arg := range args {
+               if arg == "-C" || arg == "-D" {
+                       return []byte("not found"), errCommandFailed
+               }
+       }
+       return nil, nil
+}
+
+var errCommandFailed = commandFailedError{}
+
+type commandFailedError struct{}
+
+func (commandFailedError) Error() string {
+       return "command failed"
+}
diff --git a/pkg/cni/kubernetes.go b/pkg/cni/kubernetes.go
new file mode 100644
index 00000000..422a58af
--- /dev/null
+++ b/pkg/cni/kubernetes.go
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "fmt"
+
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/client-go/kubernetes"
+       "k8s.io/client-go/rest"
+       "k8s.io/client-go/tools/clientcmd"
+)
+
+type KubernetesPodInfoProvider struct {
+       client kubernetes.Interface
+}
+
+func NewKubernetesPodInfoProvider(kubeconfig string) 
(*KubernetesPodInfoProvider, error) {
+       var (
+               cfg *rest.Config
+               err error
+       )
+       if kubeconfig != "" {
+               cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+       } else {
+               cfg, err = rest.InClusterConfig()
+       }
+       if err != nil {
+               return nil, fmt.Errorf("build Kubernetes client config: %w", 
err)
+       }
+       client, err := kubernetes.NewForConfig(cfg)
+       if err != nil {
+               return nil, fmt.Errorf("create Kubernetes client: %w", err)
+       }
+       return &KubernetesPodInfoProvider{client: client}, nil
+}
+
+func (p *KubernetesPodInfoProvider) PodInfo(ctx context.Context, ref PodRef) 
(PodInfo, error) {
+       pod, err := p.client.CoreV1().Pods(ref.Namespace).Get(ctx, ref.Name, 
metav1.GetOptions{})
+       if err != nil {
+               return PodInfo{}, fmt.Errorf("get pod %s/%s: %w", 
ref.Namespace, ref.Name, err)
+       }
+       ips := make([]string, 0, len(pod.Status.PodIPs))
+       for _, item := range pod.Status.PodIPs {
+               if item.IP != "" {
+                       ips = append(ips, item.IP)
+               }
+       }
+       if pod.Status.PodIP != "" && len(ips) == 0 {
+               ips = append(ips, pod.Status.PodIP)
+       }
+       return PodInfo{
+               Namespace: pod.Namespace,
+               Name:      pod.Name,
+               Labels:    pod.Labels,
+               IPs:       ips,
+       }, nil
+}
diff --git a/pkg/cni/plugin.go b/pkg/cni/plugin.go
new file mode 100644
index 00000000..99818923
--- /dev/null
+++ b/pkg/cni/plugin.go
@@ -0,0 +1,144 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+)
+
+type Plugin struct {
+       PodInfoProvider PodInfoProvider
+       RuleManager     RuleManager
+       StateStore      StateStore
+}
+
+type PodInfo struct {
+       Namespace string
+       Name      string
+       Labels    map[string]string
+       IPs       []string
+}
+
+type PodInfoProvider interface {
+       PodInfo(ctx context.Context, ref PodRef) (PodInfo, error)
+}
+
+type RuleManager interface {
+       AddPodRules(ctx context.Context, podIP string) error
+       DeletePodRules(ctx context.Context, podIP string) error
+}
+
+type StateStore interface {
+       Write(PodState) error
+       Read(containerID string) (PodState, error)
+       Delete(containerID string) error
+}
+
+func (p Plugin) Run(ctx context.Context, env Env, conf NetConf) ([]byte, 
error) {
+       switch env.Command {
+       case "VERSION":
+               return versionOutput(conf.CNIVersion), nil
+       case "ADD", "CHECK":
+               return p.addOrCheck(ctx, env, conf)
+       case "DEL":
+               return nil, p.del(ctx, env)
+       case "":
+               return nil, fmt.Errorf("CNI_COMMAND is required")
+       default:
+               return nil, fmt.Errorf("unsupported CNI_COMMAND %q", 
env.Command)
+       }
+}
+
+func (p Plugin) addOrCheck(ctx context.Context, env Env, conf NetConf) 
([]byte, error) {
+       out := ResultOutput(conf)
+       ref, ok := PodRefFromCNIArgs(env.Args)
+       if !ok {
+               return out, nil
+       }
+       if p.PodInfoProvider == nil {
+               return nil, fmt.Errorf("pod info provider is required")
+       }
+       pod, err := p.PodInfoProvider.PodInfo(ctx, ref)
+       if err != nil {
+               return nil, err
+       }
+       if pod.Labels[conf.ManagedLabel] != conf.ManagedLabelValue {
+               return out, nil
+       }
+       podIP := firstPodIP(conf, pod)
+       if podIP == "" {
+               return nil, fmt.Errorf("managed pod %s/%s has no IP in CNI 
result or Kubernetes status", ref.Namespace, ref.Name)
+       }
+       if p.RuleManager == nil {
+               return nil, fmt.Errorf("rule manager is required")
+       }
+       if err := p.RuleManager.AddPodRules(ctx, podIP); err != nil {
+               return nil, err
+       }
+       if p.StateStore != nil && env.ContainerID != "" {
+               if err := p.StateStore.Write(PodState{
+                       ContainerID: env.ContainerID,
+                       Namespace:   ref.Namespace,
+                       Name:        ref.Name,
+                       IP:          podIP,
+               }); err != nil {
+                       return nil, err
+               }
+       }
+       return out, nil
+}
+
+func (p Plugin) del(ctx context.Context, env Env) error {
+       if p.StateStore == nil || env.ContainerID == "" {
+               return nil
+       }
+       state, err := p.StateStore.Read(env.ContainerID)
+       if err != nil {
+               if IsNotFound(err) {
+                       return nil
+               }
+               return err
+       }
+       if p.RuleManager != nil && state.IP != "" {
+               if err := p.RuleManager.DeletePodRules(ctx, state.IP); err != 
nil {
+                       return err
+               }
+       }
+       return p.StateStore.Delete(env.ContainerID)
+}
+
+func firstPodIP(conf NetConf, pod PodInfo) string {
+       if ips := PodIPsFromPrevResult(conf.PrevResult); len(ips) > 0 {
+               return ips[0]
+       }
+       if len(pod.IPs) > 0 {
+               return pod.IPs[0]
+       }
+       return ""
+}
+
+func versionOutput(cniVersion string) []byte {
+       if cniVersion == "" {
+               cniVersion = defaultCNIVersion
+       }
+       out, _ := json.Marshal(map[string]any{
+               "cniVersion":        cniVersion,
+               "supportedVersions": []string{"0.3.1", "0.4.0", "1.0.0", 
"1.1.0"},
+       })
+       return out
+}
diff --git a/pkg/cni/plugin_test.go b/pkg/cni/plugin_test.go
new file mode 100644
index 00000000..91904a33
--- /dev/null
+++ b/pkg/cni/plugin_test.go
@@ -0,0 +1,139 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "context"
+       "encoding/json"
+       "testing"
+
+       "github.com/apache/dubbo-kubernetes/pkg/kube/inject"
+)
+
+func TestPluginAddInstallsRulesForManagedPod(t *testing.T) {
+       conf := testConf(t)
+       conf.StateDir = t.TempDir()
+       rules := &fakeRuleManager{}
+       plugin := Plugin{
+               PodInfoProvider: fakePodInfoProvider{pod: PodInfo{Labels: 
map[string]string{
+                       inject.ProxylessManagedLabel: 
inject.ProxylessManagedLabelValue,
+               }}},
+               RuleManager: rules,
+               StateStore:  NewFileStateStore(conf.StateDirectory()),
+       }
+
+       out, err := plugin.Run(context.Background(), Env{
+               Command:     "ADD",
+               ContainerID: "container-a",
+               Args:        "K8S_POD_NAMESPACE=app;K8S_POD_NAME=nginx",
+       }, conf)
+       if err != nil {
+               t.Fatalf("Run(ADD) failed: %v", err)
+       }
+       if len(out) == 0 {
+               t.Fatal("Run(ADD) returned empty result")
+       }
+       if len(rules.added) != 1 || rules.added[0] != "10.244.0.12" {
+               t.Fatalf("added rules = %v, want [10.244.0.12]", rules.added)
+       }
+       state, err := plugin.StateStore.Read("container-a")
+       if err != nil {
+               t.Fatalf("state read failed: %v", err)
+       }
+       if state.Namespace != "app" || state.Name != "nginx" || state.IP != 
"10.244.0.12" {
+               t.Fatalf("state = %#v, want app/nginx/10.244.0.12", state)
+       }
+}
+
+func TestPluginAddSkipsUnmanagedPod(t *testing.T) {
+       conf := testConf(t)
+       rules := &fakeRuleManager{}
+       plugin := Plugin{
+               PodInfoProvider: fakePodInfoProvider{pod: PodInfo{Labels: 
map[string]string{"app": "nginx"}}},
+               RuleManager:     rules,
+               StateStore:      NewFileStateStore(t.TempDir()),
+       }
+
+       if _, err := plugin.Run(context.Background(), Env{
+               Command:     "ADD",
+               ContainerID: "container-a",
+               Args:        "K8S_POD_NAMESPACE=app;K8S_POD_NAME=nginx",
+       }, conf); err != nil {
+               t.Fatalf("Run(ADD) failed: %v", err)
+       }
+       if len(rules.added) != 0 {
+               t.Fatalf("added rules = %v, want none", rules.added)
+       }
+}
+
+func TestPluginDeleteCleansStoredRules(t *testing.T) {
+       store := NewFileStateStore(t.TempDir())
+       if err := store.Write(PodState{ContainerID: "container-a", Namespace: 
"app", Name: "nginx", IP: "10.244.0.12"}); err != nil {
+               t.Fatalf("state write failed: %v", err)
+       }
+       rules := &fakeRuleManager{}
+       plugin := Plugin{RuleManager: rules, StateStore: store}
+
+       if _, err := plugin.Run(context.Background(), Env{Command: "DEL", 
ContainerID: "container-a"}, NetConf{}); err != nil {
+               t.Fatalf("Run(DEL) failed: %v", err)
+       }
+       if len(rules.deleted) != 1 || rules.deleted[0] != "10.244.0.12" {
+               t.Fatalf("deleted rules = %v, want [10.244.0.12]", 
rules.deleted)
+       }
+       if _, err := store.Read("container-a"); !IsNotFound(err) {
+               t.Fatalf("state read err = %v, want not found", err)
+       }
+}
+
+func testConf(t *testing.T) NetConf {
+       t.Helper()
+       prev, _ := json.Marshal(map[string]any{
+               "cniVersion": "1.0.0",
+               "ips": []map[string]string{
+                       {"address": "10.244.0.12/24"},
+               },
+       })
+       conf, err := ParseNetConf([]byte(`{"cniVersion":"1.0.0"}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       conf.PrevResult = prev
+       return conf
+}
+
+type fakePodInfoProvider struct {
+       pod PodInfo
+       err error
+}
+
+func (f fakePodInfoProvider) PodInfo(context.Context, PodRef) (PodInfo, error) 
{
+       return f.pod, f.err
+}
+
+type fakeRuleManager struct {
+       added   []string
+       deleted []string
+}
+
+func (f *fakeRuleManager) AddPodRules(_ context.Context, podIP string) error {
+       f.added = append(f.added, podIP)
+       return nil
+}
+
+func (f *fakeRuleManager) DeletePodRules(_ context.Context, podIP string) 
error {
+       f.deleted = append(f.deleted, podIP)
+       return nil
+}
diff --git a/pkg/cni/state.go b/pkg/cni/state.go
new file mode 100644
index 00000000..169566d0
--- /dev/null
+++ b/pkg/cni/state.go
@@ -0,0 +1,99 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cni
+
+import (
+       "crypto/sha256"
+       "encoding/hex"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "os"
+       "path/filepath"
+)
+
+type PodState struct {
+       ContainerID string `json:"containerID"`
+       Namespace   string `json:"namespace"`
+       Name        string `json:"name"`
+       IP          string `json:"ip"`
+}
+
+type FileStateStore struct {
+       dir string
+}
+
+func NewFileStateStore(dir string) *FileStateStore {
+       return &FileStateStore{dir: dir}
+}
+
+func (s *FileStateStore) Write(state PodState) error {
+       if state.ContainerID == "" {
+               return fmt.Errorf("container ID is required")
+       }
+       if err := os.MkdirAll(s.dir, 0o700); err != nil {
+               return err
+       }
+       data, err := json.Marshal(state)
+       if err != nil {
+               return err
+       }
+       return os.WriteFile(s.path(state.ContainerID), data, 0o600)
+}
+
+func (s *FileStateStore) Read(containerID string) (PodState, error) {
+       data, err := os.ReadFile(s.path(containerID))
+       if err != nil {
+               if errors.Is(err, os.ErrNotExist) {
+                       return PodState{}, notFoundError{err}
+               }
+               return PodState{}, err
+       }
+       state := PodState{}
+       if err := json.Unmarshal(data, &state); err != nil {
+               return PodState{}, err
+       }
+       return state, nil
+}
+
+func (s *FileStateStore) Delete(containerID string) error {
+       if err := os.Remove(s.path(containerID)); err != nil && !errors.Is(err, 
os.ErrNotExist) {
+               return err
+       }
+       return nil
+}
+
+func (s *FileStateStore) path(containerID string) string {
+       sum := sha256.Sum256([]byte(containerID))
+       return filepath.Join(s.dir, hex.EncodeToString(sum[:])+".json")
+}
+
+type notFoundError struct {
+       err error
+}
+
+func (e notFoundError) Error() string {
+       return e.err.Error()
+}
+
+func (e notFoundError) Unwrap() error {
+       return e.err
+}
+
+func IsNotFound(err error) bool {
+       var nf notFoundError
+       return errors.As(err, &nf)
+}
diff --git a/pkg/kube/inject/proxyless.go b/pkg/kube/inject/proxyless.go
index c55cdb7a..3c53c77a 100644
--- a/pkg/kube/inject/proxyless.go
+++ b/pkg/kube/inject/proxyless.go
@@ -37,6 +37,8 @@ const (
        ProxylessGRPCConfigPath        = ProxylessXDSMountPath + "/" + 
ProxylessGRPCConfigFileName
        ProxylessXServerContainerName  = "dubbo-xserver"
        ProxylessXServerPort           = 15080
+       ProxylessManagedLabel          = "proxyless.dubbo.apache.org/managed"
+       ProxylessManagedLabelValue     = "true"
 )
 
 var ProxylessInjectTemplatesAnnoName = 
annotation.OrgApacheDubboInjectTemplates.Name
diff --git a/pkg/kube/inject/proxyless_test.go 
b/pkg/kube/inject/proxyless_test.go
index 4fb61bc7..4cee4167 100644
--- a/pkg/kube/inject/proxyless_test.go
+++ b/pkg/kube/inject/proxyless_test.go
@@ -525,6 +525,14 @@ func TestEnsureProxylessGRPCTemplateAnnotation(t 
*testing.T) {
        }
 }
 
+func TestEnsureProxylessManagedLabel(t *testing.T) {
+       pod := &corev1.Pod{}
+       ensureProxylessManagedLabel(pod)
+       if got := pod.Labels[ProxylessManagedLabel]; got != 
ProxylessManagedLabelValue {
+               t.Fatalf("managed label = %q, want %q", got, 
ProxylessManagedLabelValue)
+       }
+}
+
 func TestProxylessGRPCSecretNameFitsKubernetesLengthLimit(t *testing.T) {
        name := 
ProxylessGRPCSecretName("grpc-provider-012345678901234567890123456789012345678901234567890123")
        if len(name) > 63 {
diff --git a/pkg/kube/inject/webhook.go b/pkg/kube/inject/webhook.go
index cdf183dc..153ac4a0 100644
--- a/pkg/kube/inject/webhook.go
+++ b/pkg/kube/inject/webhook.go
@@ -442,6 +442,7 @@ func postProcessPod(pod *corev1.Pod, injectedPod 
corev1.Pod, req InjectionParame
        if shouldInjectProxylessGRPC(req) {
                // Add proxyless gRPC env and shared bootstrap/cert volume to 
application containers.
                ensureProxylessGRPCTemplateAnnotation(pod)
+               ensureProxylessManagedLabel(pod)
                if err := addApplicationContainerConfig(pod, req); err != nil {
                        return err
                }
@@ -654,6 +655,13 @@ func ensureProxylessGRPCTemplateAnnotation(pod 
*corev1.Pod) {
        pod.Annotations[ProxylessInjectTemplatesAnnoName] = templates + "," + 
ProxylessGRPCTemplateName
 }
 
+func ensureProxylessManagedLabel(pod *corev1.Pod) {
+       if pod.Labels == nil {
+               pod.Labels = map[string]string{}
+       }
+       pod.Labels[ProxylessManagedLabel] = ProxylessManagedLabelValue
+}
+
 func ensureEnvVar(envs []corev1.EnvVar, desired corev1.EnvVar) []corev1.EnvVar 
{
        for _, env := range envs {
                if env.Name == desired.Name {
diff --git a/release/downloadDubboCandidate.sh 
b/release/downloadDubboCandidate.sh
index 68cf9be8..40c75fa6 100644
--- a/release/downloadDubboCandidate.sh
+++ b/release/downloadDubboCandidate.sh
@@ -4,7 +4,7 @@ set -e
 # Determines the operating system.
 OS="${TARGET_OS:-$(uname)}"
 if [ "${OS}" = "Darwin" ] ; then
-  OSEXT="osx"
+  OSEXT="darwin"
 else
   OSEXT="linux"
 fi
@@ -12,18 +12,34 @@ fi
 # Package type, default to dubbo
 PACKAGE_TYPE="${PACKAGE_TYPE:-dubbo}"
 
+latest_dubbo_version_from_api() {
+  curl -fsSL \
+    -H "Accept: application/vnd.github+json" \
+    https://api.github.com/repos/apache/dubbo-kubernetes/releases 2>/dev/null 
| \
+    sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | \
+    grep -viE '(alpha|beta|rc)' | \
+    head -1
+}
+
+latest_dubbo_version_from_atom() {
+  curl -fsSL https://github.com/apache/dubbo-kubernetes/releases.atom 
2>/dev/null | \
+    sed -n 's/.*<title>\([^<][^<]*\)<\/title>.*/\1/p' | \
+    grep -v '^Release notes from ' | \
+    grep -viE '(alpha|beta|rc)' | \
+    head -1
+}
+
 # Determine the latest Dubbo version by version number ignoring alpha, beta, 
and rc versions.
 if [ "${DUBBO_VERSION}" = "" ] ; then
-  DUBBO_VERSION="$(curl -s 
https://api.github.com/repos/apache/dubbo-kubernetes/releases | \
-                       grep '"tag_name":' | \
-                       grep -vE '(alpha|beta|rc)' | \
-                       head -1 | \
-                       sed -E 's/.*"([^"]+)".*/\1/')"
+  DUBBO_VERSION="$(latest_dubbo_version_from_api)"
+  if [ "${DUBBO_VERSION}" = "" ] ; then
+    DUBBO_VERSION="$(latest_dubbo_version_from_atom)"
+  fi
   DUBBO_VERSION="${DUBBO_VERSION##*/}"
 fi
 
 if [ "${DUBBO_VERSION}" = "" ] ; then
-  printf "Unable to get latest Dubbo version.\n"
+  printf "Unable to get latest Dubbo version. Please specify DUBBO_VERSION.\n"
   exit 1;
 fi
 
@@ -91,4 +107,4 @@ printf "\nDubbo %s download complete!\n" "$DUBBO_VERSION"
 printf "\n"
 BINDIR="$(cd "$NAME/bin" && pwd)"
 printf "add the %s directory to your environment path variable with:\n" 
"$BINDIR"
-printf "\t export PATH=\"\$PATH:%s\"\n" "$BINDIR"
\ No newline at end of file
+printf "\t export PATH=\"\$PATH:%s\"\n" "$BINDIR"
diff --git a/samples/app/deployment.yaml b/samples/app/deployment.yaml
index 1c01a845..778f8e0e 100644
--- a/samples/app/deployment.yaml
+++ b/samples/app/deployment.yaml
@@ -37,22 +37,6 @@ spec:
     targetPort: 80
     protocol: TCP
 ---
-apiVersion: networking.k8s.io/v1
-kind: NetworkPolicy
-metadata:
-  name: nginx-proxyless-only
-  namespace: app
-spec:
-  podSelector:
-    matchLabels:
-      app: nginx
-  policyTypes:
-  - Ingress
-  ingress:
-  - ports:
-    - protocol: TCP
-      port: 15080
----
 apiVersion: v1
 kind: ConfigMap
 metadata:

Reply via email to