diff --git a/.gitignore b/.gitignore index ada68ff..92b4dd7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ go.work *.swp *.swo *~ + +# protobuf downloaded files +include diff --git a/Makefile b/Makefile index 7ea3294..60ee695 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,15 @@ IMG_TAG ?= dev IMG_CONTROLLER ?= egress-controller:$(IMG_TAG) IMG_GATEWAY ?= nat-gateway:$(IMG_TAG) +IMG_PONAD ?= ponad:$(IMG_TAG) +PONA_VERSION ?= dev-$(shell git rev-parse --short HEAD) + # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.30.0 +SUDO ?= sudo +PROTOC_OUTPUTS = pkg/cnirpc/cni.pb.go pkg/cnirpc/cni_grpc.pb.go docs/cni-grpc.md + # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin @@ -52,6 +58,7 @@ manifests: controller-gen yq ## Generate WebhookConfiguration, ClusterRole and C .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(MAKE) $(PROTOC_OUTPUTS) $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: fmt @@ -71,7 +78,9 @@ test: envtest manifests generate fmt vet mod ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out .PHONY: check-generate -check-generate: manifests generate fmt mod +check-generate: setup manifests fmt mod + -rm $(ROLES) $(PROTOC_OUTPUTS) + $(MAKE) generate git diff --exit-code --name-only # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. @@ -102,13 +111,21 @@ run: manifests generate fmt vet mod ## Run a controller from your host. # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: docker-build docker-build: ## Build docker image with the manager. - $(CONTAINER_TOOL) build -t ${IMG_CONTROLLER} -f ./dockerfiles/Dockerfile.egress-controller . - $(CONTAINER_TOOL) build -t ${IMG_GATEWAY} -f ./dockerfiles/Dockerfile.nat-gateway . + $(CONTAINER_TOOL) build -t ${IMG_CONTROLLER} --build-arg PONA_VERSION=${PONA_VERSION} -f ./dockerfiles/Dockerfile.egress-controller . + $(CONTAINER_TOOL) build -t ${IMG_GATEWAY} --build-arg PONA_VERSION=${PONA_VERSION} -f ./dockerfiles/Dockerfile.nat-gateway . + $(CONTAINER_TOOL) build -t ${IMG_PONAD} --build-arg PONA_VERSION=${PONA_VERSION} -f ./dockerfiles/Dockerfile.ponad . + +.PHONY: kind-load +kind-load: + kind load docker-image ${IMG_CONTROLLER} + kind load docker-image ${IMG_GATEWAY} + kind load docker-image ${IMG_PONAD} .PHONY: docker-push docker-push: ## Push docker image with the manager. $(CONTAINER_TOOL) push ${IMG_CONTROLLER} $(CONTAINER_TOOL) push ${IMG_GATEWAY} + $(CONTAINER_TOOL) push ${IMG_PONAD} # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: @@ -172,6 +189,7 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint YQ = $(LOCALBIN)/yq WGET_OPTIONS := --retry-on-http-error=503 --retry-connrefused --no-verbose WGET = wget $(WGET_OPTIONS) +PROTOC := PATH=$(LOCALBIN):'$(PATH)' $(LOCALBIN)/protoc -I=$(PWD)/include:. ## Tool Versions KUSTOMIZE_VERSION ?= v5.4.2 @@ -179,6 +197,10 @@ CONTROLLER_TOOLS_VERSION ?= v0.15.0 ENVTEST_VERSION ?= release-0.18 GOLANGCI_LINT_VERSION ?= v1.59.1 YQ_VERSION ?= 4.44.3 +PROTOC_VERSION=27.3 +PROTOC_GEN_GO_VERSION := $(shell awk '/google.golang.org\/protobuf/ {print substr($$2, 2)}' go.mod) +PROTOC_GEN_GO_GRPC_VERSON=1.5.1 +PROTOC_GEN_DOC_VERSION=1.5.1 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -200,6 +222,26 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) +.PHONY: setup +setup: + $(SUDO) apt-get update + $(SUDO) apt-get -y install --no-install-recommends unzip + + curl -sfL -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-linux-x86_64.zip + unzip -o protoc.zip bin/protoc 'include/*' + rm -f protoc.zip + go install google.golang.org/protobuf/cmd/protoc-gen-go@v$(PROTOC_GEN_GO_VERSION) + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v$(PROTOC_GEN_GO_GRPC_VERSON) + go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@v$(PROTOC_GEN_DOC_VERSION) + +pkg/cnirpc/cni.pb.go: pkg/cnirpc/cni.proto + $(PROTOC) --go_out=module=github.com/cybozu-go/pona:. $< + +pkg/cnirpc/cni_grpc.pb.go: pkg/cnirpc/cni.proto + $(PROTOC) --go-grpc_out=module=github.com/cybozu-go/pona:. $< + +docs/cni-grpc.md: pkg/cnirpc/cni.proto + $(PROTOC) --doc_out=docs --doc_opt=markdown,$@ $< .PHONY: yq yq: $(YQ) diff --git a/cmd/nat-gateway/main.go b/cmd/nat-gateway/main.go index 21ad7d9..8f6cd6c 100644 --- a/cmd/nat-gateway/main.go +++ b/cmd/nat-gateway/main.go @@ -24,8 +24,8 @@ import ( ponav1beta1 "github.com/cybozu-go/pona/api/v1beta1" "github.com/cybozu-go/pona/internal/controller" - "github.com/cybozu-go/pona/internal/nat" - "github.com/cybozu-go/pona/internal/tunnel/fou" + "github.com/cybozu-go/pona/pkg/nat" + "github.com/cybozu-go/pona/pkg/tunnel/fou" // +kubebuilder:scaffold:imports ) @@ -185,7 +185,7 @@ func main() { setupLog.Error(err, "failed to Initialize FoUTunnelController") os.Exit(1) } - nc, err := nat.NewController("eth0", ipv4, ipv6) + nc, err := nat.NewGateway("eth0", ipv4, ipv6) if err != nil { setupLog.Error(err, "unable to create nat.Controller") os.Exit(1) diff --git a/cmd/pona-installer/install.go b/cmd/pona-installer/install.go new file mode 100644 index 0000000..0951973 --- /dev/null +++ b/cmd/pona-installer/install.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +func installPona(ponaPath, cniBinDir string) error { + f, err := os.Open(ponaPath) + if err != nil { + return fmt.Errorf("failed to read pona %w", err) + } + if err := os.MkdirAll(cniBinDir, 0755); err != nil { + return fmt.Errorf("failed to MkdirAll: %w", err) + } + + g, err := os.CreateTemp(cniBinDir, ".tmp") + if err != nil { + return fmt.Errorf("failed to CreateTemp: %w", err) + } + defer func() { + g.Close() + os.Remove(g.Name()) + }() + + if _, err := io.Copy(g, f); err != nil { + return fmt.Errorf("failed to io.Copy: %w", err) + } + + if err := g.Chmod(0755); err != nil { + return fmt.Errorf("failed to chmod: %w", err) + } + + if err := g.Sync(); err != nil { + return fmt.Errorf("failed to Sync: %w", err) + } + + if err := os.Rename(g.Name(), filepath.Join(cniBinDir, "pona")); err != nil { + return fmt.Errorf("failed to rename: %w", err) + } + return nil +} diff --git a/cmd/pona-installer/main.go b/cmd/pona-installer/main.go new file mode 100644 index 0000000..8ffd1fa --- /dev/null +++ b/cmd/pona-installer/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/caarlos0/env/v10" + _ "github.com/joho/godotenv/autoload" +) + +type Config struct { + CniEtcDir string `env:"CNI_ETC_DIR" envDefault:"/host/etc/cni/net.d"` + CniBinDir string `env:"CNI_BIN_DIR" envDefault:"/host/opt/cni/bin"` + PonaPath string `env:"CNI_PATH" envDefault:"/pona"` +} + +func main() { + var cfg Config + if err := env.Parse(&cfg); err != nil { + slog.Error("failed to parse config", slog.Any("error", err)) + os.Exit(1) + } + + if err := installPona(cfg.PonaPath, cfg.CniBinDir); err != nil { + slog.Error("failed to install pona", + slog.Any("error", err), + ) + } +} diff --git a/cmd/pona/main.go b/cmd/pona/main.go new file mode 100644 index 0000000..e9e3b1a --- /dev/null +++ b/cmd/pona/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + cni100 "github.com/containernetworking/cni/pkg/types/100" + + "github.com/containernetworking/cni/pkg/version" + "github.com/cybozu-go/pona" + "github.com/cybozu-go/pona/pkg/cni" + "github.com/cybozu-go/pona/pkg/cnirpc" +) + +func cmdAdd(args *skel.CmdArgs) error { + conf, err := cni.ParseConfig(args.StdinData) + if err != nil { + return types.NewError(types.ErrDecodingFailure, "failed to parse config from stdin data", err.Error()) + } + if conf.PrevResult == nil { + return types.NewError(types.ErrInternal, "ponad must be called as chained plugin", "") + } + + cniArgs, err := makeCNIArgs(args) + if err != nil { + return types.NewError(types.ErrInvalidNetworkConfig, "failed to transform args to RPC arg", err.Error()) + } + + conn, err := connect(conf.Socket) + if err != nil { + return types.NewError(types.ErrTryAgainLater, "failed to connect to socket", err.Error()) + } + + client := cnirpc.NewCNIClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + resp, err := client.Add(ctx, cniArgs) + if err != nil { + return convertError(err) + } + + result, err := cni100.NewResult(resp.Result) + if err != nil { + return types.NewError(types.ErrDecodingFailure, "failed to unmarshal result", err.Error()) + } + + return types.PrintResult(result, conf.CNIVersion) +} + +func cmdDel(args *skel.CmdArgs) error { + return nil +} + +func cmdCheck(args *skel.CmdArgs) error { + return nil +} + +func main() { + skel.PluginMainFuncs(skel.CNIFuncs{Add: cmdAdd, Del: cmdDel, Check: cmdCheck, GC: nil, Status: nil}, version.PluginSupports("0.3.1", "0.4.0", "1.0.0", "1.1.0"), fmt.Sprintf("pona %s", pona.Version)) +} diff --git a/cmd/pona/rpc.go b/cmd/pona/rpc.go new file mode 100644 index 0000000..b1c7d1e --- /dev/null +++ b/cmd/pona/rpc.go @@ -0,0 +1,78 @@ +package main + +import ( + "context" + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/cybozu-go/pona/internal/constants" + "github.com/cybozu-go/pona/pkg/cnirpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/status" +) + +// PluginEnvArgs represents CNI_ARG +type PluginEnvArgs struct { + types.CommonArgs + K8S_POD_NAMESPACE types.UnmarshallableString + K8S_POD_NAME types.UnmarshallableString + K8S_POD_INFRA_CONTAINER_ID types.UnmarshallableString +} + +// Map returns a map[string]string +func (e PluginEnvArgs) Map() map[string]string { + return map[string]string{ + constants.PodNamespaceKey: string(e.K8S_POD_NAMESPACE), + constants.PodNameKey: string(e.K8S_POD_NAME), + constants.PodContainerKey: string(e.K8S_POD_INFRA_CONTAINER_ID), + } +} + +func makeCNIArgs(args *skel.CmdArgs) (*cnirpc.CNIArgs, error) { + a := &PluginEnvArgs{} + if err := types.LoadArgs(args.Args, a); err != nil { + return nil, fmt.Errorf("failed to load args: %w", err) + } + return &cnirpc.CNIArgs{ + ContainerId: args.ContainerID, + Netns: args.Netns, + Ifname: args.IfName, + Args: a.Map(), + Path: args.Path, + StdinData: args.StdinData, + }, nil +} + +func connect(sockPath string) (*grpc.ClientConn, error) { + dialer := &net.Dialer{} + dialFunc := func(ctx context.Context, a string) (net.Conn, error) { + return dialer.DialContext(ctx, "unix", a) + } + resolver.SetDefaultScheme("passthrough") + + conn, err := grpc.NewClient(sockPath, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialFunc)) + if err != nil { + return nil, fmt.Errorf("failed to connect to %s: %w", sockPath, err) + } + return conn, nil +} + +// convertError turns err returned from gRPC library into CNI's types.Error +func convertError(err error) error { + st := status.Convert(err) + details := st.Details() + if len(details) != 1 { + return types.NewError(types.ErrInternal, st.Message(), err.Error()) + } + + cniErr, ok := details[0].(*cnirpc.CNIError) + if !ok { + types.NewError(types.ErrInternal, st.Message(), err.Error()) + } + + return types.NewError(uint(cniErr.Code), cniErr.Msg, cniErr.Details) +} diff --git a/cmd/ponad/main.go b/cmd/ponad/main.go new file mode 100644 index 0000000..1752b4c --- /dev/null +++ b/cmd/ponad/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "flag" + "log/slog" + "net" + "os" + "time" + + ponav1beta1 "github.com/cybozu-go/pona/api/v1beta1" + "github.com/cybozu-go/pona/internal/ponad" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" +) + +type Config struct { + metricsAddr string + healthAddr string + socketPath string + egressPort int +} + +const defaultSocketPath = "/run/ponad.sock" + +const ( + gracefulTimeout = 20 * time.Second +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(ponav1beta1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var config Config + + flag.StringVar(&config.metricsAddr, "metrics-addr", ":9384", "bind address of metrics endpoint") + flag.StringVar(&config.healthAddr, "health-addr", ":9385", "bind address of health/readiness probes") + flag.StringVar(&config.socketPath, "socket", defaultSocketPath, "UNIX domain socket path") + flag.IntVar(&config.egressPort, "egress-port", 5555, "UDP port number for egress NAT") + + flag.Parse() + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + slog.SetDefault(logger) + ctrl.SetLogger(logr.FromSlogHandler(logger.Handler())) + + mgr, err := setupManager(config) + if err != nil { + setupLog.Error(err, "failed to setup manager") + os.Exit(1) + } + + if err := startPonad(config, mgr); err != nil { + setupLog.Error(err, "failed to start ponad") + os.Exit(1) + } +} + +func setupManager(config Config) (ctrl.Manager, error) { + timeout := gracefulTimeout + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + LeaderElection: false, + Metrics: metricsserver.Options{ + BindAddress: config.metricsAddr, + }, + GracefulShutdownTimeout: &timeout, + HealthProbeBindAddress: config.healthAddr, + }) + if err != nil { + return nil, err + } + + if err := mgr.AddHealthzCheck("ping", healthz.Ping); err != nil { + return nil, err + } + if err := mgr.AddReadyzCheck("ping", healthz.Ping); err != nil { + return nil, err + } + return mgr, nil +} + +func startPonad(config Config, mgr ctrl.Manager) error { + l, err := net.Listen("unix", config.socketPath) + if err != nil { + return err + } + + s := ponad.NewServer(l, mgr.GetAPIReader(), config.egressPort) + if err := mgr.Add(s); err != nil { + return err + } + + ctx := ctrl.SetupSignalHandler() + slog.Info("starting manager") + + return mgr.Start(ctx) +} diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index e639dea..643d43f 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,8 +1,9 @@ -resources: -- egress-controller.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: egress-controller newName: egress-controller newTag: dev +resources: +- egress-controller.yaml +- ponad.yaml diff --git a/config/manager/ponad.yaml b/config/manager/ponad.yaml new file mode 100644 index 0000000..905d0d6 --- /dev/null +++ b/config/manager/ponad.yaml @@ -0,0 +1,88 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: ponad + labels: + app.kubernetes.io/component: ponad +spec: + selector: + matchLabels: + app.kubernetes.io/component: ponad + template: + metadata: + labels: + app.kubernetes.io/component: ponad + spec: + hostNetwork: true + hostPID: true # to see netns file under /proc + priorityClassName: system-node-critical + tolerations: + - effect: NoSchedule + operator: Exists + - effect: NoExecute + operator: Exists + serviceAccountName: egress-controller + terminationGracePeriodSeconds: 1 + containers: + - name: ponad + image: ponad:dev + env: + - name: PONA_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + securityContext: + privileged: true + ports: + - name: metrics + containerPort: 9384 + protocol: TCP + - name: health + containerPort: 9385 + protocol: TCP + resources: + requests: + cpu: 100m + memory: 200Mi + readinessProbe: + httpGet: + path: /readyz + port: health + host: localhost + livenessProbe: + httpGet: + path: /healthz + port: health + host: localhost + volumeMounts: + - mountPath: /run + name: run + mountPropagation: HostToContainer # to see bind mount netns file under /run/netns + - mountPath: /lib/modules + name: modules + readOnly: true + initContainers: + - name: pona-installer + image: ponad:dev + command: + - "/pona-installer" + securityContext: + privileged: true + volumeMounts: + - mountPath: /host/opt/cni/bin + name: cni-bin-dir + - mountPath: /host/etc/cni/net.d + name: cni-net-dir + volumes: + - name: run + hostPath: + path: /run + - name: modules + hostPath: + path: /lib/modules + - name: cni-bin-dir + hostPath: + path: /opt/cni/bin + - name: cni-net-dir + hostPath: + path: /etc/cni/net.d diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3ba8c4d..4408309 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,21 @@ kind: ClusterRole metadata: name: egress-controller-role rules: +- apiGroups: + - "" + resources: + - namespaces + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get - apiGroups: - apps resources: diff --git a/dockerfiles/Dockerfile.egress-controller b/dockerfiles/Dockerfile.egress-controller index bdf1001..7c5b471 100644 --- a/dockerfiles/Dockerfile.egress-controller +++ b/dockerfiles/Dockerfile.egress-controller @@ -2,22 +2,20 @@ FROM ghcr.io/cybozu/golang:1.22-jammy AS builder ARG TARGETOS ARG TARGETARCH +ARG PONA_VERSION WORKDIR /workspace -# Copy the Go Modules manifests -COPY go.mod go.mod -COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN go mod download + +RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x # Copy the go source -COPY cmd/egress-controller/main.go cmd/egress-controller/main.go -COPY api/ api/ -COPY internal/ internal/ +COPY . . RUN --mount=type=cache,target=/go/pkg/mod/ \ - CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o egress-controller cmd/egress-controller/main.go + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o egress-controller -ldflags "-X github.com/cybozu-go/pona.Version=${PONA_VERSION}" cmd/egress-controller/main.go FROM ghcr.io/cybozu/ubuntu:22.04 WORKDIR / diff --git a/dockerfiles/Dockerfile.nat-gateway b/dockerfiles/Dockerfile.nat-gateway index 2e10461..96708c1 100644 --- a/dockerfiles/Dockerfile.nat-gateway +++ b/dockerfiles/Dockerfile.nat-gateway @@ -2,22 +2,20 @@ FROM ghcr.io/cybozu/golang:1.22-jammy AS builder ARG TARGETOS ARG TARGETARCH +ARG PONA_VERSION WORKDIR /workspace -# Copy the Go Modules manifests -COPY go.mod go.mod -COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN go mod download + +RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x # Copy the go source -COPY cmd/nat-gateway/main.go cmd/nat-gateway/main.go -COPY api/ api/ -COPY internal/ internal/ +COPY . . RUN --mount=type=cache,target=/go/pkg/mod/ \ - CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o nat-gateway cmd/nat-gateway/main.go + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o nat-gateway -ldflags "-X github.com/cybozu-go/pona.Version=${PONA_VERSION}" cmd/nat-gateway/main.go FROM ghcr.io/cybozu/ubuntu:22.04 WORKDIR / diff --git a/dockerfiles/Dockerfile.ponad b/dockerfiles/Dockerfile.ponad new file mode 100644 index 0000000..c04595f --- /dev/null +++ b/dockerfiles/Dockerfile.ponad @@ -0,0 +1,32 @@ +# Build the manager binary +FROM ghcr.io/cybozu/golang:1.22-jammy AS builder +ARG TARGETOS +ARG TARGETARCH +ARG PONA_VERSION + +WORKDIR /workspace + +RUN --mount=type=cache,target=/go/pkg/mod/,sharing=locked \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x + +# Copy the go source +COPY . . + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o ponad -ldflags "-X github.com/cybozu-go/pona.Version=${PONA_VERSION}" ./cmd/ponad/main.go && \ + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o pona -ldflags "-X github.com/cybozu-go/pona.Version=${PONA_VERSION}" ./cmd/pona && \ + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o pona-installer -ldflags "-X github.com/cybozu-go/pona.Version=${PONA_VERSION}" ./cmd/pona-installer + + +FROM ghcr.io/cybozu/ubuntu:22.04 +WORKDIR / +RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get -yy update && apt-get install -yy netbase kmod iptables iproute2 + +COPY --from=builder /workspace/ponad /workspace/pona /workspace/pona-installer / +USER 0:0 + +ENTRYPOINT ["/ponad"] diff --git a/docs/cni-grpc.md b/docs/cni-grpc.md new file mode 100644 index 0000000..954c3ba --- /dev/null +++ b/docs/cni-grpc.md @@ -0,0 +1,163 @@ +# Protocol Documentation + + +## Table of Contents + +- [pkg/cnirpc/cni.proto](#pkg_cnirpc_cni-proto) + - [AddResponse](#pkg-cnirpc-AddResponse) + - [CNIArgs](#pkg-cnirpc-CNIArgs) + - [CNIArgs.ArgsEntry](#pkg-cnirpc-CNIArgs-ArgsEntry) + - [CNIError](#pkg-cnirpc-CNIError) + + - [ErrorCode](#pkg-cnirpc-ErrorCode) + + - [CNI](#pkg-cnirpc-CNI) + +- [Scalar Value Types](#scalar-value-types) + + + + +

Top

+ +## pkg/cnirpc/cni.proto + + + + + +### AddResponse +AddResponse represents the response for ADD command. + +`result` is a types.current.Result serialized into JSON. +https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types/current?tab=doc#Result + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| result | [bytes](#bytes) | | | + + + + + + + + +### CNIArgs +CNIArgs is a mirror of cni.pkg.skel.CmdArgs struct. +https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/skel?tab=doc#CmdArgs + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| container_id | [string](#string) | | | +| netns | [string](#string) | | | +| ifname | [string](#string) | | | +| args | [CNIArgs.ArgsEntry](#pkg-cnirpc-CNIArgs-ArgsEntry) | repeated | Key-Value pairs parsed from CNI_ARGS | +| path | [string](#string) | | | +| stdin_data | [bytes](#bytes) | | | + + + + + + + + +### CNIArgs.ArgsEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### CNIError +CNIError is a mirror of cin.pkg.types.Error struct. +https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types?tab=doc#Error + +This should be added to *grpc.Status by WithDetails() +https://pkg.go.dev/google.golang.org/grpc@v1.31.0/internal/status?tab=doc#Status.WithDetails + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| code | [ErrorCode](#pkg-cnirpc-ErrorCode) | | | +| msg | [string](#string) | | | +| details | [string](#string) | | | + + + + + + + + + + +### ErrorCode +ErrorCode enumerates errors for CNIError + +| Name | Number | Description | +| ---- | ------ | ----------- | +| UNKNOWN | 0 | | +| INCOMPATIBLE_CNI_VERSION | 1 | | +| UNSUPPORTED_FIELD | 2 | | +| UNKNOWN_CONTAINER | 3 | | +| INVALID_ENVIRONMENT_VARIABLES | 4 | | +| IO_FAILURE | 5 | | +| DECODING_FAILURE | 6 | | +| INVALID_NETWORK_CONFIG | 7 | | +| TRY_AGAIN_LATER | 11 | | +| INTERNAL | 999 | | + + + + + + + + + +### CNI +CNI implements CNI commands over gRPC. + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| Add | [CNIArgs](#pkg-cnirpc-CNIArgs) | [AddResponse](#pkg-cnirpc-AddResponse) | | +| Del | [CNIArgs](#pkg-cnirpc-CNIArgs) | [.google.protobuf.Empty](#google-protobuf-Empty) | | +| Check | [CNIArgs](#pkg-cnirpc-CNIArgs) | [.google.protobuf.Empty](#google-protobuf-Empty) | | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | +| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | +| double | | double | double | float | float64 | double | float | Float | +| float | | float | float | float | float32 | float | float | Float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | +| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) | + diff --git a/go.mod b/go.mod index 23e4161..825c794 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,16 @@ module github.com/cybozu-go/pona go 1.22.6 require ( + github.com/caarlos0/env/v10 v10.0.0 + github.com/containernetworking/cni v1.2.3 github.com/containernetworking/plugins v1.5.1 github.com/coreos/go-iptables v0.7.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 + github.com/joho/godotenv v1.5.1 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 k8s.io/client-go v0.30.1 @@ -15,24 +21,23 @@ require ( ) require ( - github.com/containernetworking/cni v1.1.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/safchain/ethtool v0.4.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect ) require ( - github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.1 github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -62,7 +67,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/vishvananda/netlink v1.2.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect @@ -74,21 +79,18 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.21.0 golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.21.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5171c35..95f9b1d 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,17 @@ -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= -github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= +github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ= github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= @@ -29,8 +28,6 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -46,51 +43,39 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -115,17 +100,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -146,13 +122,12 @@ github.com/safchain/ethtool v0.4.0 h1:vq1i2HCjshJNywOXFZ1BpwIjyeFR/kvNdHiRzqSElD github.com/safchain/ethtool v0.4.0/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -164,7 +139,6 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= @@ -190,61 +164,35 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -252,9 +200,7 @@ golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -263,37 +209,20 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..233f1bd --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,12 @@ +package constants + +const ( + EgressAnnotationPrefix = "egress.pona.cybozu.com/" +) + +// Keys in CNI_ARGS +const ( + PodNameKey = "K8S_POD_NAME" + PodNamespaceKey = "K8S_POD_NAMESPACE" + PodContainerKey = "K8S_POD_INFRA_CONTAINER_ID" +) diff --git a/internal/controller/egress_controller.go b/internal/controller/egress_controller.go index fc34416..4e65205 100644 --- a/internal/controller/egress_controller.go +++ b/internal/controller/egress_controller.go @@ -39,11 +39,11 @@ const ( // TODO: Change this const ( - EnvNode = "COIL_NODE_NAME" - EnvAddresses = "COIL_POD_ADDRESSES" - EnvPodNamespace = "COIL_POD_NAMESPACE" - EnvPodName = "COIL_POD_NAME" - EnvEgressName = "COIL_EGRESS_NAME" + EnvNode = "PONA_NODE_NAME" + EnvAddresses = "PONA_POD_ADDRESSES" + EnvPodNamespace = "PONA_POD_NAMESPACE" + EnvPodName = "PONA_POD_NAME" + EnvEgressName = "PONA_EGRESS_NAME" ) // EgressReconciler reconciles a Egress object @@ -549,7 +549,7 @@ func (r *EgressReconciler) updateStatus(ctx context.Context, eg *ponav1beta1.Egr return r.Status().Update(ctx, eg) } -// addVolumes adds volumes required by coil +// addVolumes adds volumes required by pona // TODO: change this func (r *EgressReconciler) addVolumes(vols []corev1.Volume) []corev1.Volume { noRun := true diff --git a/internal/controller/pod_watcher.go b/internal/controller/pod_watcher.go index 8a56837..10c44b2 100644 --- a/internal/controller/pod_watcher.go +++ b/internal/controller/pod_watcher.go @@ -9,8 +9,9 @@ import ( "strings" "sync" - "github.com/cybozu-go/pona/internal/nat" - "github.com/cybozu-go/pona/internal/tunnel" + "github.com/cybozu-go/pona/internal/constants" + "github.com/cybozu-go/pona/pkg/nat" + "github.com/cybozu-go/pona/pkg/tunnel" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -20,10 +21,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -const ( - EgressAnnotationPrefix = "egress.pona.cybozu.com/" -) - // PodWatcher reconciles a Pod object type PodWatcher struct { client.Client @@ -38,12 +35,12 @@ type PodWatcher struct { podIPToPod map[netip.Addr]Set[types.NamespacedName] tun tunnel.Controller - nat nat.Controller + nat nat.Gateway } type Set[T comparable] map[T]struct{} -func NewPodWatcher(client client.Client, scheme *runtime.Scheme, egressName, egressNamespace string, t tunnel.Controller, n nat.Controller) *PodWatcher { +func NewPodWatcher(client client.Client, scheme *runtime.Scheme, egressName, egressNamespace string, t tunnel.Controller, n nat.Gateway) *PodWatcher { return &PodWatcher{ Client: client, Scheme: scheme, @@ -238,11 +235,11 @@ func (r *PodWatcher) existsOtherLiveTunnels(namespacedName types.NamespacedName, func (r *PodWatcher) hasEgressAnnotation(pod *corev1.Pod) bool { for k, name := range pod.Annotations { - if !strings.HasPrefix(k, EgressAnnotationPrefix) { + if !strings.HasPrefix(k, constants.EgressAnnotationPrefix) { continue } - if k[len(EgressAnnotationPrefix):] != r.EgressNamespace { + if k[len(constants.EgressAnnotationPrefix):] != r.EgressNamespace { continue } diff --git a/internal/controller/pod_watcher_test.go b/internal/controller/pod_watcher_test.go index 1db774c..5c9d5cb 100644 --- a/internal/controller/pod_watcher_test.go +++ b/internal/controller/pod_watcher_test.go @@ -5,8 +5,9 @@ import ( "net/netip" "path/filepath" - natmock "github.com/cybozu-go/pona/internal/nat/mock" - tunnelmock "github.com/cybozu-go/pona/internal/tunnel/mock" + "github.com/cybozu-go/pona/internal/constants" + natmock "github.com/cybozu-go/pona/pkg/nat/mock" + tunnelmock "github.com/cybozu-go/pona/pkg/tunnel/mock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -49,7 +50,7 @@ var _ = Describe("Pod Watcher", func() { } pod.Annotations = map[string]string{ - filepath.Join(EgressAnnotationPrefix, egressNamespace): egressName, + filepath.Join(constants.EgressAnnotationPrefix, egressNamespace): egressName, } By("create pod") diff --git a/internal/ponad/server.go b/internal/ponad/server.go new file mode 100644 index 0000000..ff1b0b5 --- /dev/null +++ b/internal/ponad/server.go @@ -0,0 +1,259 @@ +package ponad + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "net" + "net/netip" + "strings" + + "github.com/containernetworking/plugins/pkg/ns" + ponav1beta1 "github.com/cybozu-go/pona/api/v1beta1" + "github.com/cybozu-go/pona/internal/constants" + "github.com/cybozu-go/pona/pkg/cni" + "github.com/cybozu-go/pona/pkg/cnirpc" + "github.com/cybozu-go/pona/pkg/nat" + "github.com/cybozu-go/pona/pkg/tunnel/fou" + "github.com/cybozu-go/pona/pkg/util/netiputil" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func newError(c codes.Code, cniCode cnirpc.ErrorCode, msg, details string) error { + st := status.New(c, msg) + st, err := st.WithDetails(&cnirpc.CNIError{Code: cniCode, Msg: msg + details, Details: details}) + if err != nil { + panic(err) + } + + return st.Err() +} + +func newInternalError(err error, msg string) error { + return newError(codes.Internal, cnirpc.ErrorCode_INTERNAL, msg+err.Error(), err.Error()) +} + +// InterceptorLogger adapts slog logger to interceptor logger. +// This code is simple enough to be copied and not imported. +func InterceptorLogger(l *slog.Logger) logging.Logger { + return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) { + l.Log(ctx, slog.Level(lvl), msg, fields...) + }) +} + +// +kubebuilder:rbac:groups="",resources=pods,verbs=get +// +kubebuilder:rbac:groups="",resources=namespaces;services,verbs=get;list;watch +// +kubebuilder:rbac:groups=pona.cybozu.com,resources=egresses,verbs=get;list;watch + +type server struct { + cnirpc.UnimplementedCNIServer + + listener net.Listener + apiReader client.Reader + egressPort int +} + +func NewServer(l net.Listener, r client.Reader, egressPort int) *server { + return &server{ + listener: l, + apiReader: r, + egressPort: egressPort, + } +} + +var _ cnirpc.CNIServer = &server{} + +func (s *server) Start(ctx context.Context) error { + grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor( + logging.UnaryServerInterceptor(InterceptorLogger(slog.Default())), + )) + cnirpc.RegisterCNIServer(grpcServer, s) + + go func() { + <-ctx.Done() + grpcServer.GracefulStop() + }() + + return grpcServer.Serve(s.listener) +} + +func (s *server) Add(ctx context.Context, args *cnirpc.CNIArgs) (*cnirpc.AddResponse, error) { + podName := args.Args[constants.PodNameKey] + podNS := args.Args[constants.PodNamespaceKey] + if podName == "" || podNS == "" { + return nil, fmt.Errorf("missing pod name or namespace, args: %#v", args.Args) + } + + pod := &corev1.Pod{} + if err := s.apiReader.Get(ctx, client.ObjectKey{Namespace: podNS, Name: podName}, pod); err != nil { + if apierrors.IsNotFound(err) { + return nil, newError(codes.NotFound, cnirpc.ErrorCode_UNKNOWN_CONTAINER, "pod not found", err.Error()) + } + return nil, newInternalError(err, "failed to get pod") + } + + p, err := cni.GetPrevResult(args) + if err != nil { + return nil, newInternalError(err, "failed to get previous result") + } + + b, err := json.Marshal(p) + if err != nil { + return nil, newInternalError(err, "failed to marshal result") + } + + egNames, err := s.listEgress(pod) + if err != nil { + return nil, newInternalError(err, "failed to list eggress from annotations") + } + if len(egNames) == 0 { + return &cnirpc.AddResponse{Result: b}, nil + } + + var local4, local6 *netip.Addr + for _, ipc := range p.IPs { + ip, ok := netiputil.ToAddr(ipc.Address.IP) + if !ok { + return nil, newInternalError(errors.New("failed to parse ip"), "failed to parse ip") + } + if local4 == nil && ip.Is4() { + local4 = &ip + } + if local6 == nil && ip.Is6() { + local6 = &ip + } + } + + containerNS, err := ns.GetNS(args.Netns) + if err != nil { + return nil, fmt.Errorf("failed to open netns path %s: %w", args.Netns, err) + } + defer containerNS.Close() + + if err := containerNS.Do(func(hostNS ns.NetNS) error { + ft, err := fou.NewFoUTunnelController(s.egressPort, local4, local6) + if err != nil { + return newInternalError(err, "failed to create FoUTunnelController") + } + if err := ft.Init(); err != nil { + return newInternalError(err, "failed to initialize FoUTunnel") + } + nt, err := nat.NewNatClient(local4 != nil, local6 != nil) + if err != nil { + return newInternalError(err, "failed to create Nat client") + } + if err := nt.Init(); err != nil { + return newInternalError(err, "failed to initialize Nat client") + } + + for _, egName := range egNames { + g, ds, err := s.collectDestinationsForEgress(ctx, egName) + if err != nil { + return newInternalError(err, "failed to collect destinations for egress") + } + + link, err := ft.AddPeer(netip.Addr(g)) + if err != nil { + return newInternalError(err, fmt.Sprintf("failed to add peer for %v", g)) + } + if err := nt.UpdateRoutes(link, ds); err != nil { + return newInternalError(err, "failed to update routes") + } + } + return nil + }); err != nil { + return nil, err + } + + return &cnirpc.AddResponse{Result: b}, nil +} + +func (s *server) listEgress(pod *corev1.Pod) ([]client.ObjectKey, error) { + if pod.Spec.HostNetwork { + // pods running in the host network cannot use egress NAT. + // In fact, such a pod won't call CNI, so this is just a safeguard. + return nil, nil + } + + var egNames []client.ObjectKey + + for k, v := range pod.Annotations { + if !strings.HasPrefix(k, constants.EgressAnnotationPrefix) { + continue + } + + ns := k[len(constants.EgressAnnotationPrefix):] + for _, name := range strings.Split(v, ",") { + egNames = append(egNames, client.ObjectKey{Namespace: ns, Name: name}) + } + } + return egNames, nil +} + +func (s *server) collectDestinationsForEgress(ctx context.Context, egName client.ObjectKey) (netip.Addr, []netip.Prefix, error) { + eg := &ponav1beta1.Egress{} + svc := &corev1.Service{} + + if err := s.apiReader.Get(ctx, egName, eg); err != nil { + return netip.Addr{}, nil, newError(codes.FailedPrecondition, cnirpc.ErrorCode_INTERNAL, + "failed to get Egress "+egName.String(), err.Error()) + } + + if err := s.apiReader.Get(ctx, egName, svc); err != nil { + return netip.Addr{}, nil, newError(codes.FailedPrecondition, cnirpc.ErrorCode_INTERNAL, + "failed to get Service "+egName.String(), err.Error()) + } + + // pona doesn't support dual stack services for now, although it's stable from k8s 1.23 + // https://kubernetes.io/docs/concepts/services-networking/dual-stack/ + svcIP, err := netip.ParseAddr(svc.Spec.ClusterIP) + if err != nil { + return netip.Addr{}, nil, newError(codes.Internal, cnirpc.ErrorCode_INTERNAL, + "invalid ClusterIP in Service "+egName.String(), svc.Spec.ClusterIP) + } + + var subnets []netip.Prefix + if svcIP.Is4() { + for _, sn := range eg.Spec.Destinations { + prefix, err := netip.ParsePrefix(sn) + if err != nil { + return netip.Addr{}, nil, newInternalError(err, "invalid network in Egress "+egName.String()) + } + + if prefix.Addr().Is4() { + subnets = append(subnets, prefix) + } + } + } else if svcIP.Is6() { + for _, sn := range eg.Spec.Destinations { + prefix, err := netip.ParsePrefix(sn) + if err != nil { + return netip.Addr{}, nil, newInternalError(err, "invalid network in Egress "+egName.String()) + } + + if prefix.Addr().Is6() { + subnets = append(subnets, prefix) + } + } + } else { + return netip.Addr{}, []netip.Prefix{}, errors.New("invalid service ip") + } + return svcIP, subnets, nil +} + +func (s *server) Del(ctx context.Context, args *cnirpc.CNIArgs) (*emptypb.Empty, error) { + return nil, nil +} + +func (s *server) Check(ctx context.Context, args *cnirpc.CNIArgs) (*emptypb.Empty, error) { + return nil, nil +} diff --git a/internal/util/netiputil/netiputil.go b/internal/util/netiputil/netiputil.go deleted file mode 100644 index 5f78955..0000000 --- a/internal/util/netiputil/netiputil.go +++ /dev/null @@ -1,10 +0,0 @@ -package netiputil - -import ( - "net" - "net/netip" -) - -func ConvNetIP(addr netip.Addr) net.IP { - return net.IP(addr.AsSlice()) -} diff --git a/netconf.json b/netconf.json new file mode 100644 index 0000000..7734d6d --- /dev/null +++ b/netconf.json @@ -0,0 +1,37 @@ +{ + "cniVersion": "0.3.1", + "name": "kindnet", + "plugins": [ + { + "type": "ptp", + "ipMasq": false, + "ipam": { + "type": "host-local", + "dataDir": "/run/cni-ipam-state", + "routes": [ + + + { "dst": "0.0.0.0/0" } + ], + "ranges": [ + + + [ { "subnet": "10.244.0.0/24" } ] + ] + } + , + "mtu": 1500 + + }, + { + "type": "pona", + "socket": "/run/ponad.sock" + }, + { + "type": "portmap", + "capabilities": { + "portMappings": true + } + } + ] +} diff --git a/pkg/cni/cni.go b/pkg/cni/cni.go new file mode 100644 index 0000000..412f18f --- /dev/null +++ b/pkg/cni/cni.go @@ -0,0 +1,45 @@ +package cni + +import ( + "encoding/json" + "fmt" + + "github.com/containernetworking/cni/pkg/types" + cni100 "github.com/containernetworking/cni/pkg/types/100" + "github.com/containernetworking/cni/pkg/version" + "github.com/cybozu-go/pona/pkg/cnirpc" +) + +type PluginConf struct { + types.NetConf + + // Socket contains unix domain socket to communicate with ponad + Socket string `json:"socket"` +} + +func GetPrevResult(cniargs *cnirpc.CNIArgs) (*cni100.Result, error) { + conf, err := ParseConfig(cniargs.StdinData) + if err != nil { + return nil, fmt.Errorf("failed to parse config") + } + r, err := cni100.GetResult(conf.NetConf.PrevResult) + if err != nil { + return nil, fmt.Errorf("failed to get prevresult") + } + + return r, nil +} + +func ParseConfig(stdin []byte) (*PluginConf, error) { + conf := &PluginConf{} + + if err := json.Unmarshal(stdin, conf); err != nil { + return nil, fmt.Errorf("failed to parse network configuration: %w", err) + } + + if err := version.ParsePrevResult(&conf.NetConf); err != nil { + return nil, fmt.Errorf("failed to parse prev result: %w", err) + } + + return conf, nil +} diff --git a/pkg/cnirpc/cni.pb.go b/pkg/cnirpc/cni.pb.go new file mode 100644 index 0000000..0900049 --- /dev/null +++ b/pkg/cnirpc/cni.pb.go @@ -0,0 +1,465 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.3 +// source: pkg/cnirpc/cni.proto + +package cnirpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ErrorCode enumerates errors for CNIError +type ErrorCode int32 + +const ( + ErrorCode_UNKNOWN ErrorCode = 0 + ErrorCode_INCOMPATIBLE_CNI_VERSION ErrorCode = 1 + ErrorCode_UNSUPPORTED_FIELD ErrorCode = 2 + ErrorCode_UNKNOWN_CONTAINER ErrorCode = 3 + ErrorCode_INVALID_ENVIRONMENT_VARIABLES ErrorCode = 4 + ErrorCode_IO_FAILURE ErrorCode = 5 + ErrorCode_DECODING_FAILURE ErrorCode = 6 + ErrorCode_INVALID_NETWORK_CONFIG ErrorCode = 7 + ErrorCode_TRY_AGAIN_LATER ErrorCode = 11 + ErrorCode_INTERNAL ErrorCode = 999 +) + +// Enum value maps for ErrorCode. +var ( + ErrorCode_name = map[int32]string{ + 0: "UNKNOWN", + 1: "INCOMPATIBLE_CNI_VERSION", + 2: "UNSUPPORTED_FIELD", + 3: "UNKNOWN_CONTAINER", + 4: "INVALID_ENVIRONMENT_VARIABLES", + 5: "IO_FAILURE", + 6: "DECODING_FAILURE", + 7: "INVALID_NETWORK_CONFIG", + 11: "TRY_AGAIN_LATER", + 999: "INTERNAL", + } + ErrorCode_value = map[string]int32{ + "UNKNOWN": 0, + "INCOMPATIBLE_CNI_VERSION": 1, + "UNSUPPORTED_FIELD": 2, + "UNKNOWN_CONTAINER": 3, + "INVALID_ENVIRONMENT_VARIABLES": 4, + "IO_FAILURE": 5, + "DECODING_FAILURE": 6, + "INVALID_NETWORK_CONFIG": 7, + "TRY_AGAIN_LATER": 11, + "INTERNAL": 999, + } +) + +func (x ErrorCode) Enum() *ErrorCode { + p := new(ErrorCode) + *p = x + return p +} + +func (x ErrorCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ErrorCode) Descriptor() protoreflect.EnumDescriptor { + return file_pkg_cnirpc_cni_proto_enumTypes[0].Descriptor() +} + +func (ErrorCode) Type() protoreflect.EnumType { + return &file_pkg_cnirpc_cni_proto_enumTypes[0] +} + +func (x ErrorCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ErrorCode.Descriptor instead. +func (ErrorCode) EnumDescriptor() ([]byte, []int) { + return file_pkg_cnirpc_cni_proto_rawDescGZIP(), []int{0} +} + +// CNIArgs is a mirror of cni.pkg.skel.CmdArgs struct. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/skel?tab=doc#CmdArgs +type CNIArgs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ContainerId string `protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + Netns string `protobuf:"bytes,2,opt,name=netns,proto3" json:"netns,omitempty"` + Ifname string `protobuf:"bytes,3,opt,name=ifname,proto3" json:"ifname,omitempty"` + Args map[string]string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Key-Value pairs parsed from CNI_ARGS + Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` + StdinData []byte `protobuf:"bytes,6,opt,name=stdin_data,json=stdinData,proto3" json:"stdin_data,omitempty"` +} + +func (x *CNIArgs) Reset() { + *x = CNIArgs{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CNIArgs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CNIArgs) ProtoMessage() {} + +func (x *CNIArgs) ProtoReflect() protoreflect.Message { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CNIArgs.ProtoReflect.Descriptor instead. +func (*CNIArgs) Descriptor() ([]byte, []int) { + return file_pkg_cnirpc_cni_proto_rawDescGZIP(), []int{0} +} + +func (x *CNIArgs) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +func (x *CNIArgs) GetNetns() string { + if x != nil { + return x.Netns + } + return "" +} + +func (x *CNIArgs) GetIfname() string { + if x != nil { + return x.Ifname + } + return "" +} + +func (x *CNIArgs) GetArgs() map[string]string { + if x != nil { + return x.Args + } + return nil +} + +func (x *CNIArgs) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CNIArgs) GetStdinData() []byte { + if x != nil { + return x.StdinData + } + return nil +} + +// CNIError is a mirror of cin.pkg.types.Error struct. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types?tab=doc#Error +// +// This should be added to *grpc.Status by WithDetails() +// https://pkg.go.dev/google.golang.org/grpc@v1.31.0/internal/status?tab=doc#Status.WithDetails +type CNIError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code ErrorCode `protobuf:"varint,1,opt,name=code,proto3,enum=pkg.cnirpc.ErrorCode" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + Details string `protobuf:"bytes,3,opt,name=details,proto3" json:"details,omitempty"` +} + +func (x *CNIError) Reset() { + *x = CNIError{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CNIError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CNIError) ProtoMessage() {} + +func (x *CNIError) ProtoReflect() protoreflect.Message { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CNIError.ProtoReflect.Descriptor instead. +func (*CNIError) Descriptor() ([]byte, []int) { + return file_pkg_cnirpc_cni_proto_rawDescGZIP(), []int{1} +} + +func (x *CNIError) GetCode() ErrorCode { + if x != nil { + return x.Code + } + return ErrorCode_UNKNOWN +} + +func (x *CNIError) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +func (x *CNIError) GetDetails() string { + if x != nil { + return x.Details + } + return "" +} + +// AddResponse represents the response for ADD command. +// +// `result` is a types.current.Result serialized into JSON. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types/current?tab=doc#Result +type AddResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Result []byte `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` +} + +func (x *AddResponse) Reset() { + *x = AddResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddResponse) ProtoMessage() {} + +func (x *AddResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_cnirpc_cni_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddResponse.ProtoReflect.Descriptor instead. +func (*AddResponse) Descriptor() ([]byte, []int) { + return file_pkg_cnirpc_cni_proto_rawDescGZIP(), []int{2} +} + +func (x *AddResponse) GetResult() []byte { + if x != nil { + return x.Result + } + return nil +} + +var File_pkg_cnirpc_cni_proto protoreflect.FileDescriptor + +var file_pkg_cnirpc_cni_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6e, 0x69, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6e, 0x69, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x70, 0x6b, 0x67, 0x2e, 0x63, 0x6e, 0x69, 0x72, + 0x70, 0x63, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xf9, 0x01, 0x0a, 0x07, 0x43, 0x4e, 0x49, 0x41, 0x72, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x6e, 0x65, 0x74, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, + 0x65, 0x74, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x66, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x66, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, + 0x61, 0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6b, 0x67, + 0x2e, 0x63, 0x6e, 0x69, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x4e, 0x49, 0x41, 0x72, 0x67, 0x73, 0x2e, + 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x44, 0x61, + 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x61, 0x0a, 0x08, 0x43, + 0x4e, 0x49, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x63, 0x6e, 0x69, 0x72, + 0x70, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6d, 0x73, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x25, + 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, 0xed, 0x01, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, + 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x54, 0x49, 0x42, 0x4c, 0x45, + 0x5f, 0x43, 0x4e, 0x49, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x15, + 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, + 0x45, 0x4c, 0x44, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x41, 0x49, 0x4e, 0x45, 0x52, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, + 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x45, 0x4e, 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, 0x04, 0x12, + 0x0e, 0x0a, 0x0a, 0x49, 0x4f, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x05, 0x12, + 0x14, 0x0a, 0x10, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x10, 0x06, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, + 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x52, 0x59, 0x5f, 0x41, 0x47, 0x41, 0x49, 0x4e, 0x5f, 0x4c, + 0x41, 0x54, 0x45, 0x52, 0x10, 0x0b, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, + 0x41, 0x4c, 0x10, 0xe7, 0x07, 0x32, 0xa4, 0x01, 0x0a, 0x03, 0x43, 0x4e, 0x49, 0x12, 0x33, 0x0a, + 0x03, 0x41, 0x64, 0x64, 0x12, 0x13, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x63, 0x6e, 0x69, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x4e, 0x49, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x17, 0x2e, 0x70, 0x6b, 0x67, 0x2e, + 0x63, 0x6e, 0x69, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x32, 0x0a, 0x03, 0x44, 0x65, 0x6c, 0x12, 0x13, 0x2e, 0x70, 0x6b, 0x67, 0x2e, + 0x63, 0x6e, 0x69, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x4e, 0x49, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, + 0x13, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x63, 0x6e, 0x69, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x4e, 0x49, + 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x26, 0x5a, 0x24, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x79, 0x62, 0x6f, 0x7a, + 0x75, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x6f, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6e, + 0x69, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_cnirpc_cni_proto_rawDescOnce sync.Once + file_pkg_cnirpc_cni_proto_rawDescData = file_pkg_cnirpc_cni_proto_rawDesc +) + +func file_pkg_cnirpc_cni_proto_rawDescGZIP() []byte { + file_pkg_cnirpc_cni_proto_rawDescOnce.Do(func() { + file_pkg_cnirpc_cni_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_cnirpc_cni_proto_rawDescData) + }) + return file_pkg_cnirpc_cni_proto_rawDescData +} + +var file_pkg_cnirpc_cni_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_pkg_cnirpc_cni_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_pkg_cnirpc_cni_proto_goTypes = []any{ + (ErrorCode)(0), // 0: pkg.cnirpc.ErrorCode + (*CNIArgs)(nil), // 1: pkg.cnirpc.CNIArgs + (*CNIError)(nil), // 2: pkg.cnirpc.CNIError + (*AddResponse)(nil), // 3: pkg.cnirpc.AddResponse + nil, // 4: pkg.cnirpc.CNIArgs.ArgsEntry + (*emptypb.Empty)(nil), // 5: google.protobuf.Empty +} +var file_pkg_cnirpc_cni_proto_depIdxs = []int32{ + 4, // 0: pkg.cnirpc.CNIArgs.args:type_name -> pkg.cnirpc.CNIArgs.ArgsEntry + 0, // 1: pkg.cnirpc.CNIError.code:type_name -> pkg.cnirpc.ErrorCode + 1, // 2: pkg.cnirpc.CNI.Add:input_type -> pkg.cnirpc.CNIArgs + 1, // 3: pkg.cnirpc.CNI.Del:input_type -> pkg.cnirpc.CNIArgs + 1, // 4: pkg.cnirpc.CNI.Check:input_type -> pkg.cnirpc.CNIArgs + 3, // 5: pkg.cnirpc.CNI.Add:output_type -> pkg.cnirpc.AddResponse + 5, // 6: pkg.cnirpc.CNI.Del:output_type -> google.protobuf.Empty + 5, // 7: pkg.cnirpc.CNI.Check:output_type -> google.protobuf.Empty + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_pkg_cnirpc_cni_proto_init() } +func file_pkg_cnirpc_cni_proto_init() { + if File_pkg_cnirpc_cni_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_cnirpc_cni_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*CNIArgs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_cnirpc_cni_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*CNIError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_cnirpc_cni_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*AddResponse); 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_pkg_cnirpc_cni_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_pkg_cnirpc_cni_proto_goTypes, + DependencyIndexes: file_pkg_cnirpc_cni_proto_depIdxs, + EnumInfos: file_pkg_cnirpc_cni_proto_enumTypes, + MessageInfos: file_pkg_cnirpc_cni_proto_msgTypes, + }.Build() + File_pkg_cnirpc_cni_proto = out.File + file_pkg_cnirpc_cni_proto_rawDesc = nil + file_pkg_cnirpc_cni_proto_goTypes = nil + file_pkg_cnirpc_cni_proto_depIdxs = nil +} diff --git a/pkg/cnirpc/cni.proto b/pkg/cnirpc/cni.proto new file mode 100644 index 0000000..c7b3efc --- /dev/null +++ b/pkg/cnirpc/cni.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; +package pkg.cnirpc; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/cybozu-go/pona/pkg/cnirpc"; + +// CNIArgs is a mirror of cni.pkg.skel.CmdArgs struct. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/skel?tab=doc#CmdArgs +message CNIArgs { + string container_id = 1; + string netns = 2; + string ifname = 3; + map args = 4; // Key-Value pairs parsed from CNI_ARGS + string path = 5; + bytes stdin_data = 6; +} + +// ErrorCode enumerates errors for CNIError +enum ErrorCode { + UNKNOWN = 0; + INCOMPATIBLE_CNI_VERSION = 1; + UNSUPPORTED_FIELD = 2; + UNKNOWN_CONTAINER = 3; + INVALID_ENVIRONMENT_VARIABLES = 4; + IO_FAILURE = 5; + DECODING_FAILURE = 6; + INVALID_NETWORK_CONFIG = 7; + TRY_AGAIN_LATER = 11; + INTERNAL = 999; +} + +// CNIError is a mirror of cin.pkg.types.Error struct. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types?tab=doc#Error +// +// This should be added to *grpc.Status by WithDetails() +// https://pkg.go.dev/google.golang.org/grpc@v1.31.0/internal/status?tab=doc#Status.WithDetails +message CNIError { + ErrorCode code = 1; + string msg = 2; + string details = 3; +} + +// AddResponse represents the response for ADD command. +// +// `result` is a types.current.Result serialized into JSON. +// https://pkg.go.dev/github.com/containernetworking/cni@v0.8.0/pkg/types/current?tab=doc#Result +message AddResponse { + bytes result = 1; +} + +// CNI implements CNI commands over gRPC. +service CNI { + rpc Add(CNIArgs) returns (AddResponse); + rpc Del(CNIArgs) returns (google.protobuf.Empty); + rpc Check(CNIArgs) returns (google.protobuf.Empty); +} diff --git a/pkg/cnirpc/cni_grpc.pb.go b/pkg/cnirpc/cni_grpc.pb.go new file mode 100644 index 0000000..4b57081 --- /dev/null +++ b/pkg/cnirpc/cni_grpc.pb.go @@ -0,0 +1,202 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.27.3 +// source: pkg/cnirpc/cni.proto + +package cnirpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CNI_Add_FullMethodName = "/pkg.cnirpc.CNI/Add" + CNI_Del_FullMethodName = "/pkg.cnirpc.CNI/Del" + CNI_Check_FullMethodName = "/pkg.cnirpc.CNI/Check" +) + +// CNIClient is the client API for CNI service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// CNI implements CNI commands over gRPC. +type CNIClient interface { + Add(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*AddResponse, error) + Del(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*emptypb.Empty, error) + Check(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type cNIClient struct { + cc grpc.ClientConnInterface +} + +func NewCNIClient(cc grpc.ClientConnInterface) CNIClient { + return &cNIClient{cc} +} + +func (c *cNIClient) Add(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*AddResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AddResponse) + err := c.cc.Invoke(ctx, CNI_Add_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cNIClient) Del(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CNI_Del_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cNIClient) Check(ctx context.Context, in *CNIArgs, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CNI_Check_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CNIServer is the server API for CNI service. +// All implementations must embed UnimplementedCNIServer +// for forward compatibility. +// +// CNI implements CNI commands over gRPC. +type CNIServer interface { + Add(context.Context, *CNIArgs) (*AddResponse, error) + Del(context.Context, *CNIArgs) (*emptypb.Empty, error) + Check(context.Context, *CNIArgs) (*emptypb.Empty, error) + mustEmbedUnimplementedCNIServer() +} + +// UnimplementedCNIServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCNIServer struct{} + +func (UnimplementedCNIServer) Add(context.Context, *CNIArgs) (*AddResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Add not implemented") +} +func (UnimplementedCNIServer) Del(context.Context, *CNIArgs) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Del not implemented") +} +func (UnimplementedCNIServer) Check(context.Context, *CNIArgs) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") +} +func (UnimplementedCNIServer) mustEmbedUnimplementedCNIServer() {} +func (UnimplementedCNIServer) testEmbeddedByValue() {} + +// UnsafeCNIServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CNIServer will +// result in compilation errors. +type UnsafeCNIServer interface { + mustEmbedUnimplementedCNIServer() +} + +func RegisterCNIServer(s grpc.ServiceRegistrar, srv CNIServer) { + // If the following call pancis, it indicates UnimplementedCNIServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CNI_ServiceDesc, srv) +} + +func _CNI_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CNIArgs) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CNIServer).Add(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CNI_Add_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CNIServer).Add(ctx, req.(*CNIArgs)) + } + return interceptor(ctx, in, info, handler) +} + +func _CNI_Del_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CNIArgs) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CNIServer).Del(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CNI_Del_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CNIServer).Del(ctx, req.(*CNIArgs)) + } + return interceptor(ctx, in, info, handler) +} + +func _CNI_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CNIArgs) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CNIServer).Check(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CNI_Check_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CNIServer).Check(ctx, req.(*CNIArgs)) + } + return interceptor(ctx, in, info, handler) +} + +// CNI_ServiceDesc is the grpc.ServiceDesc for CNI service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CNI_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pkg.cnirpc.CNI", + HandlerType: (*CNIServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Add", + Handler: _CNI_Add_Handler, + }, + { + MethodName: "Del", + Handler: _CNI_Del_Handler, + }, + { + MethodName: "Check", + Handler: _CNI_Check_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "pkg/cnirpc/cni.proto", +} diff --git a/pkg/nat/client.go b/pkg/nat/client.go new file mode 100644 index 0000000..86bb5ca --- /dev/null +++ b/pkg/nat/client.go @@ -0,0 +1,291 @@ +package nat + +import ( + "fmt" + "maps" + "net/netip" + "slices" + + "github.com/cybozu-go/pona/pkg/util/netiputil" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +// IDs +const ( + ncProtocolID = 30 + + ncTableID = 117 + mainTableID = 254 + throwMetric = 500 + routeMetric = 100 +) + +// rule priorities +const ( + ncPrio = 1900 +) + +// special subnets +var ( + v4PrivateList = []netip.Prefix{ + netip.MustParsePrefix("10.0.0.0/8"), + netip.MustParsePrefix("172.16.0.0/12"), + netip.MustParsePrefix("192.168.0.0/16"), + } + + v6PrivateList = []netip.Prefix{ + netip.MustParsePrefix("fc00::/7"), + } + + v4LinkLocal = netip.MustParsePrefix("169.254.0.0/16") + v6LinkLocal = netip.MustParsePrefix("fe80::/10") + + v4DefaultGW = netip.MustParsePrefix("0.0.0.0/0") + v6DefaultGW = netip.MustParsePrefix("::/0") +) + +// Client configures routes for NAT Gateway +type Client interface { + Init() error + IsInitialized() (bool, error) + UpdateRoutes(link netlink.Link, subnets []netip.Prefix) error +} + +type natClient struct { + useipv4 bool + useipv6 bool +} + +func NewNatClient(useipv4, useipv6 bool) (Client, error) { + return &natClient{ + useipv4: useipv4, + useipv6: useipv6, + }, nil +} + +func newRuleForClient(family, table, prio int) *netlink.Rule { + r := netlink.NewRule() + r.Family = family + r.Table = table + r.Priority = prio + return r +} + +func (c *natClient) clear(family int) error { + var defaultGW netip.Prefix + if family == netlink.FAMILY_V4 { + defaultGW = v4DefaultGW + } else { + defaultGW = v6DefaultGW + } + + rules, err := netlink.RuleList(family) + if err != nil { + return fmt.Errorf("netlink: rule list failed: %w", err) + } + for _, r := range rules { + if r.Priority != ncPrio { + continue + } + if r.Dst == nil { + // workaround for a library issue + n := netiputil.ToIPNet(defaultGW) + r.Dst = &n + } + if err := netlink.RuleDel(&r); err != nil { + return fmt.Errorf("netlink: failed to delete a rule: %+v, %w", r, err) + } + } + + routes, err := netlink.RouteListFiltered(family, &netlink.Route{Table: ncTableID}, netlink.RT_FILTER_TABLE) + if err != nil { + return fmt.Errorf("netlink: route list failed: %w", err) + } + for _, r := range routes { + if r.Dst == nil { + // workaround for a library issue + n := netiputil.ToIPNet(defaultGW) + r.Dst = &n + } + if err := netlink.RouteDel(&r); err != nil { + return fmt.Errorf("netlink: failed to delete a route in table %d: %+v, %w", ncTableID, r, err) + } + } + return nil +} + +func (c *natClient) Init() error { + if c.useipv4 { + if err := c.clear(netlink.FAMILY_V4); err != nil { + return err + } + rule := newRuleForClient(netlink.FAMILY_V4, ncTableID, ncPrio) + if err := netlink.RuleAdd(rule); err != nil { + return fmt.Errorf("netlink: failed to add v4 natclient rule: %w", err) + } + } + + if c.useipv6 { + if err := c.clear(netlink.FAMILY_V6); err != nil { + return err + } + + rule := newRuleForClient(netlink.FAMILY_V6, ncTableID, ncPrio) + if err := netlink.RuleAdd(rule); err != nil { + return fmt.Errorf("netlink: failed to add v6 natclient rule: %w", err) + } + } + return nil +} + +func (c *natClient) IsInitialized() (bool, error) { + if c.useipv4 { + // check whether exact one rule exists + rules, err := netlink.RuleListFiltered(netlink.FAMILY_V4, &netlink.Rule{Table: ncTableID}, netlink.RT_FILTER_TABLE) + if err != nil { + return false, fmt.Errorf("netlink: failed to list v4 rule: %w", err) + } + if len(rules) != 1 { + return false, nil + } + return true, nil + } + if c.useipv6 { + // check whether exact one rule exists + rules, err := netlink.RuleListFiltered(netlink.FAMILY_V6, &netlink.Rule{Table: ncTableID}, netlink.RT_FILTER_TABLE) + if err != nil { + return false, fmt.Errorf("netlink: failed to list v6 rule: %w", err) + } + if len(rules) != 1 { + return false, nil + } + return true, nil + } + return true, nil +} + +func (c *natClient) UpdateRoutes(link netlink.Link, subnets []netip.Prefix) error { + current, err := collectRoutes(link.Attrs().Index) + if err != nil { + return fmt.Errorf("failed to collect routes: %w", err) + } + + var adds []netip.Prefix + var dels []netlink.Route + + for _, n := range subnets { + if _, ok := current[n]; !ok { + adds = append(adds, n) + } + } + + subnetsSet := subnetsSet(subnets) + for subnet, route := range current { + if _, ok := subnetsSet[subnet]; !ok { + dels = append(dels, route) + } + } + + // link up here to minimize the down time + // See https://github.com/cybozu-go/coil/issues/287. + if err := netlink.LinkSetUp(link); err != nil { + return fmt.Errorf("netlink: failed to link up %s: %w", link.Attrs().Name, err) + } + + for _, v := range slices.Concat(v4PrivateList, v6PrivateList, + []netip.Prefix{v4LinkLocal, v6LinkLocal}, + ) { + if err := c.addThrow(v); err != nil { + return err + } + } + + for _, r := range adds { + if err := c.addRoute(link, r); err != nil { + return err + } + } + for _, r := range dels { + if err := c.delRoute(r); err != nil { + return err + } + } + return nil +} + +func collectRoutes(linkIndex int) (map[netip.Prefix]netlink.Route, error) { + r4, err := collectRoute1(linkIndex, netlink.FAMILY_V4) + if err != nil { + return nil, fmt.Errorf("failed to collect routes: %w", err) + } + r6, err := collectRoute1(linkIndex, netlink.FAMILY_V6) + if err != nil { + return nil, fmt.Errorf("failed to collect routes: %w", err) + } + maps.Copy(r4, r6) + return r4, nil +} + +func collectRoute1(linkIndex, family int) (map[netip.Prefix]netlink.Route, error) { + routes := make(map[netip.Prefix]netlink.Route) + + ro, err := netlink.RouteListFiltered(family, &netlink.Route{Table: ncTableID}, netlink.RT_FILTER_TABLE) + if err != nil { + return nil, fmt.Errorf("netlink: failed to list v4 routes: %w", err) + } + for _, r := range ro { + if r.LinkIndex == linkIndex && r.Dst != nil { + d, ok := netiputil.FromIPNet(*r.Dst) + if !ok { + return nil, fmt.Errorf("failed to convert to netip.Addr from net.IP: %w", err) + } + routes[d] = r + } + } + return routes, nil +} + +func subnetsSet(subnets []netip.Prefix) map[netip.Prefix]struct{} { + subnetsSet := make(map[netip.Prefix]struct{}) + for _, subnet := range subnets { + subnetsSet[subnet] = struct{}{} + } + return subnetsSet +} + +func (c *natClient) addThrow(n netip.Prefix) error { + dst := netiputil.ToIPNet(n) + err := netlink.RouteAdd(&netlink.Route{ + Table: ncTableID, + Dst: &dst, + Type: unix.RTN_THROW, + Protocol: ncProtocolID, + Priority: throwMetric, + }) + if err != nil { + return fmt.Errorf("netlink: failed to add route(table %d) to %s: %w", ncTableID, n.String(), err) + } + + return nil +} + +func (c *natClient) addRoute(link netlink.Link, n netip.Prefix) error { + a := netiputil.ToIPNet(n) + + err := netlink.RouteAdd(&netlink.Route{ + Table: ncTableID, + Dst: &a, + LinkIndex: link.Attrs().Index, + Protocol: ncProtocolID, + Priority: routeMetric, + }) + if err != nil { + return fmt.Errorf("netlink: failed to add route(table %d) to %s: %w", ncTableID, n.String(), err) + } + return nil +} + +func (c *natClient) delRoute(n netlink.Route) error { + return netlink.RouteDel(&n) +} diff --git a/internal/nat/nat.go b/pkg/nat/gateway.go similarity index 85% rename from internal/nat/nat.go rename to pkg/nat/gateway.go index 5dd8c37..c8653de 100644 --- a/internal/nat/nat.go +++ b/pkg/nat/gateway.go @@ -6,7 +6,7 @@ import ( "net/netip" "github.com/coreos/go-iptables/iptables" - "github.com/cybozu-go/pona/internal/util/netiputil" + "github.com/cybozu-go/pona/pkg/util/netiputil" "github.com/vishvananda/netlink" ) @@ -18,12 +18,12 @@ const ( egressDummy = "nat-dummy" ) -type Controller interface { +type Gateway interface { Init() error AddClient(netip.Addr, netlink.Link) error } -type controller struct { +type gateway struct { iface string ipv4 *netip.Addr ipv6 *netip.Addr @@ -31,7 +31,7 @@ type controller struct { var ErrIPFamilyMismatch = errors.New("no matching IP family") -func NewController(iface string, ipv4, ipv6 *netip.Addr) (Controller, error) { +func NewGateway(iface string, ipv4, ipv6 *netip.Addr) (Gateway, error) { if ipv4 != nil && !ipv4.Is4() { return nil, fmt.Errorf("invalid IPv4 address, ip=%s", ipv4.String()) } @@ -39,14 +39,14 @@ func NewController(iface string, ipv4, ipv6 *netip.Addr) (Controller, error) { return nil, fmt.Errorf("invalid IPv6 address, ip=%s", ipv6.String()) } - return &controller{ + return &gateway{ iface: iface, ipv4: ipv4, ipv6: ipv6, }, nil } -func (c *controller) newRule(family int) *netlink.Rule { +func (c *gateway) newRule(family int) *netlink.Rule { r := netlink.NewRule() r.Family = family r.IifName = c.iface @@ -55,7 +55,7 @@ func (c *controller) newRule(family int) *netlink.Rule { return r } -func (c *controller) Init() error { +func (c *gateway) Init() error { // avoid double initialization in case the program restarts _, err := netlink.LinkByName(egressDummy) if err == nil { @@ -70,7 +70,7 @@ func (c *controller) Init() error { if err != nil { return err } - ipn := netlink.NewIPNet(netiputil.ConvNetIP(*c.ipv4)) + ipn := netlink.NewIPNet(netiputil.FromAddr(*c.ipv4)) err = ipt.Append("nat", "POSTROUTING", "!", "-s", ipn.String(), "-o", c.iface, "-j", "MASQUERADE") if err != nil { return fmt.Errorf("failed to setup masquerade rule for IPv4: %w", err) @@ -86,7 +86,7 @@ func (c *controller) Init() error { if err != nil { return err } - ipn := netlink.NewIPNet(netiputil.ConvNetIP(*c.ipv6)) + ipn := netlink.NewIPNet(netiputil.FromAddr(*c.ipv6)) err = ipt.Append("nat", "POSTROUTING", "!", "-s", ipn.String(), "-o", c.iface, "-j", "MASQUERADE") if err != nil { return fmt.Errorf("failed to setup masquerade rule for IPv6: %w", err) @@ -107,7 +107,7 @@ func (c *controller) Init() error { return nil } -func (c *controller) AddClient(addr netip.Addr, link netlink.Link) error { +func (c *gateway) AddClient(addr netip.Addr, link netlink.Link) error { // Note: // The following checks are not necessary in fact because, // prior to this point, the support for the IP family is tested @@ -135,7 +135,7 @@ func (c *controller) AddClient(addr netip.Addr, link netlink.Link) error { if r.Dst == nil { continue } - if r.Dst.IP.Equal(netiputil.ConvNetIP(addr)) { + if r.Dst.IP.Equal(netiputil.FromAddr(addr)) { return nil } } @@ -146,7 +146,7 @@ func (c *controller) AddClient(addr netip.Addr, link netlink.Link) error { return fmt.Errorf("netlink: failed to link up %s: %w", link.Attrs().Name, err) } if err := netlink.RouteAdd(&netlink.Route{ - Dst: netlink.NewIPNet(netiputil.ConvNetIP(addr)), + Dst: netlink.NewIPNet(netiputil.FromAddr(addr)), LinkIndex: link.Attrs().Index, Table: egressTableID, Protocol: egressProtocolID, diff --git a/internal/nat/mock/mock.go b/pkg/nat/mock/mock.go similarity index 100% rename from internal/nat/mock/mock.go rename to pkg/nat/mock/mock.go diff --git a/internal/tunnel/fou/fou.go b/pkg/tunnel/fou/fou.go similarity index 95% rename from internal/tunnel/fou/fou.go rename to pkg/tunnel/fou/fou.go index 2013d03..77f5e39 100644 --- a/internal/tunnel/fou/fou.go +++ b/pkg/tunnel/fou/fou.go @@ -11,8 +11,8 @@ import ( "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/utils/sysctl" "github.com/coreos/go-iptables/iptables" - "github.com/cybozu-go/pona/internal/tunnel" - "github.com/cybozu-go/pona/internal/util/netiputil" + "github.com/cybozu-go/pona/pkg/tunnel" + "github.com/cybozu-go/pona/pkg/util/netiputil" "github.com/vishvananda/netlink" ) @@ -70,6 +70,9 @@ func NewFoUTunnelController(port int, localIPv4, localIPv6 *netip.Addr) (*FouTun if localIPv6 != nil && !localIPv6.Is6() { return nil, tunnel.ErrIPFamilyMismatch } + if localIPv4 == nil && localIPv6 == nil { + return nil, tunnel.ErrNoIPProvided + } return &FouTunnelController{ port: port, local4: localIPv4, @@ -202,8 +205,8 @@ func (t *FouTunnelController) addPeer4(addr netip.Addr) (netlink.Link, error) { EncapType: netlink.FOU_ENCAP_DIRECT, EncapDport: uint16(t.port), EncapSport: 0, // sportauto is always on - Remote: netiputil.ConvNetIP(addr), - Local: netiputil.ConvNetIP(*t.local4), + Remote: netiputil.FromAddr(addr), + Local: netiputil.FromAddr(*t.local4), } if err := netlink.LinkAdd(link); err != nil { return nil, fmt.Errorf("netlink: failed to add fou link: %w", err) @@ -244,8 +247,8 @@ func (t *FouTunnelController) addPeer6(addr netip.Addr) (netlink.Link, error) { EncapType: netlink.FOU_ENCAP_DIRECT, EncapDport: uint16(t.port), EncapSport: 0, // sportauto is always on - Remote: netiputil.ConvNetIP(addr), - Local: netiputil.ConvNetIP(*t.local6), + Remote: netiputil.FromAddr(addr), + Local: netiputil.FromAddr(*t.local6), } if err := netlink.LinkAdd(link); err != nil { return nil, fmt.Errorf("netlink: failed to add fou link: %w", err) @@ -281,7 +284,7 @@ func (t *FouTunnelController) DelPeer(addr netip.Addr) error { // the router Pods. // // Calling this function may result in tunl0 (v4) or ip6tnl0 (v6) -// fallback interface being renamed to coil_tunl or coil_ip6tnl. +// fallback interface being renamed to pona_tunl or pona_ip6tnl. // This is to communicate to the user that this plugin has taken // control of the encapsulation stack on the netns, as it currently // doesn't explicitly support sharing it with other tools/CNIs. @@ -294,7 +297,7 @@ func (t *FouTunnelController) DelPeer(addr netip.Addr) error { // By default, these interfaces will be created in new network namespaces, // but this behavior can be disabled by setting net.core.fb_tunnels_only_for_init_net = 2. func setupFlowBasedIP4TunDevice() error { - ipip4Device := "coil_ipip4" + ipip4Device := "pona_ipip4" // Set up IPv4 tunnel device if requested. if err := setupDevice(&netlink.Iptun{ LinkAttrs: netlink.LinkAttrs{Name: ipip4Device}, @@ -314,7 +317,7 @@ func setupFlowBasedIP4TunDevice() error { // See setupFlowBasedIP4TunDevice func setupFlowBasedIP6TunDevice() error { - ipip6Device := "coil_ipip6" + ipip6Device := "pona_ipip6" // Set up IPv6 tunnel device if requested. if err := setupDevice(&netlink.Ip6tnl{ @@ -326,7 +329,7 @@ func setupFlowBasedIP6TunDevice() error { // Rename fallback device created by potential kernel module load after // creating tunnel interface. - if err := renameDevice("ip6tnl0", "coil_ip6tnl"); err != nil { + if err := renameDevice("ip6tnl0", "pona_ip6tnl"); err != nil { return fmt.Errorf("renaming fallback device %s: %w", "tunl0", err) } diff --git a/internal/tunnel/mock/mock.go b/pkg/tunnel/mock/mock.go similarity index 100% rename from internal/tunnel/mock/mock.go rename to pkg/tunnel/mock/mock.go diff --git a/internal/tunnel/tunnel.go b/pkg/tunnel/tunnel.go similarity index 91% rename from internal/tunnel/tunnel.go rename to pkg/tunnel/tunnel.go index be3d3fb..840b2aa 100644 --- a/internal/tunnel/tunnel.go +++ b/pkg/tunnel/tunnel.go @@ -24,3 +24,4 @@ type Controller interface { } var ErrIPFamilyMismatch = errors.New("no matching IP family") +var ErrNoIPProvided = errors.New("both of IPs are nil") diff --git a/pkg/util/netiputil/netiputil.go b/pkg/util/netiputil/netiputil.go new file mode 100644 index 0000000..031ab72 --- /dev/null +++ b/pkg/util/netiputil/netiputil.go @@ -0,0 +1,36 @@ +package netiputil + +import ( + "net" + "net/netip" +) + +func FromAddr(addr netip.Addr) net.IP { + return net.IP(addr.AsSlice()) +} + +func ToAddr(ip net.IP) (netip.Addr, bool) { + if f := ip.To4(); f != nil { + return netip.AddrFromSlice(f) + } else if f := ip.To16(); f != nil { + return netip.AddrFromSlice(f) + } + return netip.Addr{}, false +} + +func IsFamilyMatched(a, b netip.Addr) bool { + return (a.Is4() && b.Is4()) || (a.Is6() && b.Is6()) +} + +func ToIPNet(prefix netip.Prefix) net.IPNet { + ip := FromAddr(prefix.Addr()) + return net.IPNet{IP: ip, Mask: net.CIDRMask(prefix.Bits(), prefix.Addr().BitLen())} +} +func FromIPNet(from net.IPNet) (to netip.Prefix, ok bool) { + ip, ok := ToAddr(from.IP) + if !ok { + return netip.Prefix{}, false + } + ones, _ := from.Mask.Size() + return netip.PrefixFrom(ip, ones), true +} diff --git a/pkg/util/netiputil/netiputil_test.go b/pkg/util/netiputil/netiputil_test.go new file mode 100644 index 0000000..6e1b04f --- /dev/null +++ b/pkg/util/netiputil/netiputil_test.go @@ -0,0 +1,169 @@ +package netiputil + +import ( + "net" + "net/netip" + "reflect" + "testing" +) + +func TestToIPNet(t *testing.T) { + type args struct { + prefix netip.Prefix + } + tests := []struct { + name string + args args + want net.IPNet + }{ + { + name: "192.168.0.0/16", + args: args{ + prefix: netip.MustParsePrefix("192.168.0.0/16"), + }, + want: net.IPNet{ + IP: net.ParseIP("192.168.0.0").To4(), Mask: net.CIDRMask(16, 32), + }, + }, + { + name: "fc00::/7", + args: args{ + prefix: netip.MustParsePrefix("fc00::/7"), + }, + want: net.IPNet{ + IP: net.ParseIP("fc00::"), + Mask: net.CIDRMask(7, 128), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToIPNet(tt.args.prefix); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToIPNet() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFromIPNet(t *testing.T) { + type args struct { + from net.IPNet + } + tests := []struct { + name string + args args + wantTo netip.Prefix + wantOk bool + }{ + { + name: "192.168.0.0/16", + args: args{ + from: net.IPNet{ + IP: net.ParseIP("192.168.0.0").To4(), Mask: net.CIDRMask(16, 32), + }, + }, + wantTo: netip.MustParsePrefix("192.168.0.0/16"), + wantOk: true, + }, + { + name: "fc00::/7", + args: args{ + from: net.IPNet{ + IP: net.ParseIP("fc00::"), + Mask: net.CIDRMask(7, 128), + }, + }, + wantTo: netip.MustParsePrefix("fc00::/7"), + wantOk: true, + }, + { + name: "invalid", + args: args{ + from: net.IPNet{}, + }, + wantTo: netip.Prefix{}, + wantOk: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTo, gotOk := FromIPNet(tt.args.from) + if !reflect.DeepEqual(gotTo, tt.wantTo) { + t.Errorf("FromIPNet() gotTo = %v, want %v", gotTo, tt.wantTo) + } + if gotOk != tt.wantOk { + t.Errorf("FromIPNet() gotOk = %v, want %v", gotOk, tt.wantOk) + } + }) + } +} + +func TestFromAddr(t *testing.T) { + type args struct { + addr netip.Addr + } + tests := []struct { + name string + args args + want net.IP + }{ + { + name: "1", + args: args{ + addr: netip.MustParseAddr("10.244.0.1"), + }, + want: net.ParseIP("10.244.0.1").To4(), + }, + { + name: "2", + args: args{ + addr: netip.MustParseAddr("ffcc::1"), + }, + want: net.ParseIP("ffcc::1").To16(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FromAddr(tt.args.addr); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FromAddr() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestToAddr(t *testing.T) { + type args struct { + ip net.IP + } + tests := []struct { + name string + args args + want netip.Addr + }{ + { + name: "1", + args: args{ + ip: net.ParseIP("10.244.0.1"), + }, + want: netip.MustParseAddr("10.244.0.1"), + }, + { + name: "2", + args: args{ + ip: net.ParseIP("ffcc::1"), + }, + want: netip.MustParseAddr("ffcc::1"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := ToAddr(tt.args.ip) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToAddr() got = %v, want %v", got, tt.want) + } + if !got1 { + t.Errorf("ToAddr() got1 = %v, want true", got1) + } + }) + } +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..ca33a2f --- /dev/null +++ b/version.go @@ -0,0 +1,5 @@ +package pona + +var ( + Version = "" // set at compile time with -ldflags "-X github.com/cybozu-go/pona.Version=x.y.yz" +)