<|start_filename|>pkg/cniplugin/kube.go<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cniplugin

import (
	"context"
	"time"

	log "github.com/sirupsen/logrus"
	"istio.io/api/annotation"
	meshconfig "istio.io/api/mesh/v1alpha1"
	"istio.io/istio/cni/pkg/plugin"
	"istio.io/istio/pilot/cmd/pilot-agent/options"
	"istio.io/istio/pkg/config/mesh"
	"istio.io/istio/pkg/kube"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

var (
	injectAnnotationKey    = annotation.SidecarInject.Name
	sidecarStatusKey       = annotation.SidecarStatus.Name
	podRetrievalMaxRetries = 30
	podRetrievalInterval   = 1 * time.Second
)

// copied from https://github.com/istio/istio/blob/1.13.3/cni/pkg/plugin/plugin.go#L94-L120
// newKubeClient returns a Kubernetes client
func newKubeClient(conf plugin.Config) (*kubernetes.Clientset, error) {
	// Some config can be passed in a kubeconfig file
	kubeconfig := conf.Kubernetes.Kubeconfig

	config, err := kube.DefaultRestConfig(kubeconfig, "")
	if err != nil {
		log.Errorf("Failed setting up kubernetes client with kubeconfig %s", kubeconfig)
		return nil, err
	}

	log.Debugf("merbridge-cni set up kubernetes client with kubeconfig %s", kubeconfig)

	// Create the clientset
	return kubernetes.NewForConfig(config)
}

// references https://github.com/istio/istio/blob/1.13.3/cni/pkg/plugin/kubernetes.go#L65-L110
// getKubePodInfo returns information of a POD
func getKubePodInfo(client *kubernetes.Clientset, podName, podNamespace string) (*plugin.PodInfo, error) {
	pod, err := client.CoreV1().Pods(podNamespace).Get(context.TODO(), podName, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}

	pi := &plugin.PodInfo{
		Containers:        make([]string, len(pod.Spec.Containers)),
		Annotations:       pod.Annotations,
		ProxyEnvironments: make(map[string]string),
	}
	for containerIdx, container := range pod.Spec.Containers {
		log.Debugf("Inspecting pod %v/%v container %v", podNamespace, podName, container.Name)
		pi.Containers[containerIdx] = container.Name

		if container.Name == "istio-proxy" {
			// don't include ports from istio-proxy in the redirect ports
			// Get proxy container env variable, and extract out ProxyConfig from it.
			for _, e := range container.Env {
				pi.ProxyEnvironments[e.Name] = e.Value
				if e.Name == options.ProxyConfigEnv {
					proxyConfig := mesh.DefaultProxyConfig()
					mc := &meshconfig.MeshConfig{
						DefaultConfig: proxyConfig,
					}
					mc, err := mesh.ApplyProxyConfig(e.Value, mc)
					if err != nil {
						log.Warnf("Failed to apply proxy config for %v/%v: %+v", pod.Namespace, pod.Name, err)
					} else {
						pi.ProxyConfig = mc.DefaultConfig
					}
					break
				}
			}
			continue
		}
	}
	log.Debugf("Pod %v/%v info: \n%+v", podNamespace, podName, pi)

	return pi, nil
}


<|start_filename|>internal/cni-server/handlers.go<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cniserver

import (
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/containernetworking/cni/pkg/skel"
	log "github.com/sirupsen/logrus"
)

func (s *server) PodCreated(w http.ResponseWriter, req *http.Request) {
	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	args := skel.CmdArgs{}
	err = json.Unmarshal(bs, &args)
	log.Infof("cni called create with args: %+v", args)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	err = s.CmdAdd(&args)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	w.WriteHeader(200)
	_, _ = w.Write([]byte("ok"))
}

func (s *server) PodDeleted(w http.ResponseWriter, req *http.Request) {
	bs, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	args := skel.CmdArgs{}
	err = json.Unmarshal(bs, &args)
	log.Infof("cni called delete with args: %+v", args)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	err = s.CmdDelete(&args)
	if err != nil {
		w.WriteHeader(500)
		_, _ = w.Write([]byte(err.Error()))
	}
	w.WriteHeader(200)
	_, _ = w.Write([]byte("ok"))
}


<|start_filename|>app/cni/main.go<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
	"fmt"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/version"

	"github.com/merbridge/merbridge/pkg/cniplugin"
)

func main() {
	skel.PluginMain(cniplugin.CmdAdd, cniplugin.CmdCheck, cniplugin.CmdDelete, version.All,
		fmt.Sprintf("CNI plugin merbridge-cni %v", "0.5.0"))
}


<|start_filename|>bpf/headers/mesh.h<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#define SOCK_IP_MARK_PORT 39807

#define ISTIO 1
#define LINKERD 2

#ifndef MESH
#define MESH 1
#endif

#if MESH == ISTIO

#ifndef OUT_REDIRECT_PORT
#define OUT_REDIRECT_PORT 15001
#endif

#ifndef IN_REDIRECT_PORT
#define IN_REDIRECT_PORT 15006
#endif

#ifndef SIDECAR_USER_ID
#define SIDECAR_USER_ID 1337
#endif

#ifndef DNS_CAPTURE_PORT
#define DNS_CAPTURE_PORT 15053
#endif

#elif MESH == LINKERD

#ifndef OUT_REDIRECT_PORT
#define OUT_REDIRECT_PORT 4140
#endif

#ifndef IN_REDIRECT_PORT
#define IN_REDIRECT_PORT 4143
#endif

#ifndef SIDECAR_USER_ID
#define SIDECAR_USER_ID 2102
#endif

#ifndef DNS_CAPTURE_PORT
#define DNS_CAPTURE_PORT 0 // todo fix me
#endif

#else
#error "Mesh mode not supported yet"
#endif


<|start_filename|>pkg/cniplugin/plugin_test.go<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cniplugin

import (
	"testing"

	"istio.io/istio/cni/pkg/plugin"
)

func TestIgnorePod(t *testing.T) {
	type args struct {
		namespace string
		name      string
		pod       *plugin.PodInfo
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "uninjected pod",
			args: args{
				pod: &plugin.PodInfo{
					Containers: []string{"foo", "bar"},
				},
			},
			want: true,
		},
		{
			name: "injected pod without sidecar status",
			args: args{
				pod: &plugin.PodInfo{
					Containers: []string{"foo", "istio-proxy"},
				},
			},
			want: true,
		},
		{
			name: "injected pod",
			args: args{
				pod: &plugin.PodInfo{
					Containers: []string{"foo", "istio-proxy"},
					Annotations: map[string]string{
						sidecarStatusKey: "whatever",
					},
				},
			},
			want: false,
		},
		{
			name: "injected pod with envoy disabled",
			args: args{
				pod: &plugin.PodInfo{
					Containers: []string{"foo", "istio-proxy"},
					Annotations: map[string]string{
						sidecarStatusKey: "whatever",
					},
					ProxyEnvironments: map[string]string{
						"DISABLE_ENVOY": "true",
					},
				},
			},
			want: true,
		},
		{
			name: "injected pod with envoy enabled",
			args: args{
				pod: &plugin.PodInfo{
					Containers: []string{"foo", "istio-proxy"},
					Annotations: map[string]string{
						sidecarStatusKey: "whatever",
					},
					ProxyEnvironments: map[string]string{
						"DISABLE_ENVOY": "false",
					},
				},
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ignorePod(tt.args.namespace, tt.args.name, tt.args.pod); got != tt.want {
				t.Errorf("ignorePod() = %v, want %v", got, tt.want)
			}
		})
	}
}


<|start_filename|>pkg/cniplugin/plugin.go<|end_filename|>
/*
Copyright © 2022 Merbridge Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cniplugin

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"strconv"
	"time"

	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types"
	cniv1 "github.com/containernetworking/cni/pkg/types/100"
	"github.com/containernetworking/cni/pkg/version"
	log "github.com/sirupsen/logrus"
	"istio.io/istio/cni/pkg/plugin"

	"github.com/merbridge/merbridge/config/constants"
)

// copied from https://github.com/istio/istio/blob/1.13.3/cni/pkg/plugin/plugin.go#L94-L120
// parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte) (*plugin.Config, error) {
	conf := plugin.Config{}

	if err := json.Unmarshal(stdin, &conf); err != nil {
		return nil, fmt.Errorf("failed to parse network configuration: %v", err)
	}

	// Parse previous result. Remove this if your plugin is not chained.
	if conf.RawPrevResult != nil {
		resultBytes, err := json.Marshal(conf.RawPrevResult)
		if err != nil {
			return nil, fmt.Errorf("could not serialize prevResult: %v", err)
		}
		res, err := version.NewResult(conf.CNIVersion, resultBytes)
		if err != nil {
			return nil, fmt.Errorf("could not parse prevResult: %v", err)
		}
		conf.RawPrevResult = nil
		conf.PrevResult, err = cniv1.NewResultFromResult(res)
		if err != nil {
			return nil, fmt.Errorf("could not convert result to current version: %v", err)
		}
	}
	// End previous result parsing

	return &conf, nil
}

// references https://github.com/istio/istio/blob/1.13.3/cni/pkg/plugin/plugin.go#L205
func ignore(conf *plugin.Config, k8sArgs *plugin.K8sArgs) bool {
	ns := string(k8sArgs.K8S_POD_NAMESPACE)
	name := string(k8sArgs.K8S_POD_NAME)
	if ns != "" && name != "" {
		for _, excludeNs := range conf.Kubernetes.ExcludeNamespaces {
			if ns == excludeNs {
				log.Infof("Pod %s/%s excluded", ns, name)
				return true
			}
		}
		client, err := newKubeClient(*conf)
		if err != nil {
			log.Error(err)
			return true
		}
		pi := &plugin.PodInfo{}
		for attempt := 1; attempt <= podRetrievalMaxRetries; attempt++ {
			pi, err = getKubePodInfo(client, name, ns)
			if err == nil {
				break
			}
			log.Debugf("Failed to get %s/%s pod info: %v", ns, name, err)
			time.Sleep(podRetrievalInterval)
		}
		if err != nil {
			log.Errorf("Failed to get %s/%s pod info: %v", ns, name, err)
			return true
		}
		return ignorePod(ns, name, pi)
	}
	log.Debugf("Not a kubernetes pod")
	return true
}

func ignorePod(namespace, name string, pod *plugin.PodInfo) bool {
	if val, ok := pod.ProxyEnvironments["DISABLE_ENVOY"]; ok {
		if val, err := strconv.ParseBool(val); err == nil && val {
			log.Infof("Pod %s/%s excluded due to DISABLE_ENVOY on istio-proxy", namespace, name)
			return true
		}
	}
	if len(pod.Containers) > 1 {
		if val, ok := pod.Annotations[injectAnnotationKey]; ok {
			if injectEnabled, err := strconv.ParseBool(val); err == nil {
				if !injectEnabled {
					log.Infof("Pod %s/%s excluded due to inject-disabled annotation", namespace, name)
					return true
				}
			}
		}
		if _, ok := pod.Annotations[sidecarStatusKey]; !ok {
			log.Infof("Pod %s/%s excluded due to not containing sidecar annotation", namespace, name)
			return true
		}
		return false
	}
	log.Infof("Pod %s/%s excluded because it only has %d containers", namespace, name, len(pod.Containers))
	return true
}

func CmdAdd(args *skel.CmdArgs) (err error) {
	conf, err := parseConfig(args.StdinData)
	if err != nil {
		log.Errorf("merbridge-cni cmdAdd failed to parse config %v %v", string(args.StdinData), err)
		return err
	}
	k8sArgs := plugin.K8sArgs{}
	if err := types.LoadArgs(args.Args, &k8sArgs); err != nil {
		return err
	}

	if ignore(conf, &k8sArgs) {
		// ignore uninjected pods
		return nil
	}

	httpc := http.Client{
		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial("unix", "/var/run/merbridge-cni.sock")
			},
		},
	}
	http.DefaultClient = &httpc
	bs, _ := json.Marshal(args)
	body := bytes.NewReader(bs)
	_, err = http.Post("http://merbridge-cni"+constants.CNICreatePodURL, "application/json", body)
	if err != nil {
		return err
	}

	var result *cniv1.Result
	if conf.PrevResult == nil {
		result = &cniv1.Result{
			CNIVersion: cniv1.ImplementedSpecVersion,
		}
	} else {
		// Pass through the result for the next plugin
		result = conf.PrevResult
	}
	return types.PrintResult(result, conf.CNIVersion)
}

func CmdCheck(args *skel.CmdArgs) (err error) {
	return err
}

func CmdDelete(args *skel.CmdArgs) (err error) {
	httpc := http.Client{
		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial("unix", "/var/run/merbridge-cni.sock")
			},
		},
	}
	http.DefaultClient = &httpc
	bs, _ := json.Marshal(args)
	body := bytes.NewReader(bs)
	_, err = http.Post("http://merbridge-cni"+constants.CNIDeletePodURL, "application/json", body)
	return err
}
