blob: 25962bf6efde13aa4cbf45bdfb870adbb6b8c0db [file] [log] [blame]
Lorenz Brunb682ba52020-07-08 14:51:36 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// Package nfproxy is a Kubernetes Service IP proxy based exclusively on the Linux nftables interface.
18// It uses netfilter's NAT capabilities to accept traffic on service IPs and DNAT it to the respective endpoint.
19package nfproxy
20
21import (
22 "context"
23 "errors"
24 "fmt"
25 "net"
26 "os"
27 "time"
28
29 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
30
31 "github.com/sbezverk/nfproxy/pkg/controller"
32 "github.com/sbezverk/nfproxy/pkg/nftables"
33 "github.com/sbezverk/nfproxy/pkg/proxy"
34 v1 "k8s.io/api/core/v1"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/labels"
37 "k8s.io/apimachinery/pkg/selection"
38 kubeinformers "k8s.io/client-go/informers"
39 "k8s.io/client-go/kubernetes"
40 "k8s.io/client-go/kubernetes/scheme"
41 "k8s.io/client-go/tools/record"
42)
43
44type Service struct {
45 // Traffic in ClusterCIDR is assumed to be originated inside the cluster and will not be SNATed
46 ClusterCIDR net.IPNet
47 // A Kubernetes ClientSet with read access to endpoints and services
48 ClientSet kubernetes.Interface
49}
50
51func (s *Service) Run(ctx context.Context) error {
52 var ipv4ClusterCIDR string
53 var ipv6ClusterCIDR string
54 if s.ClusterCIDR.IP.To4() == nil && s.ClusterCIDR.IP.To16() != nil {
55 ipv6ClusterCIDR = s.ClusterCIDR.String()
56 } else if s.ClusterCIDR.IP.To4() != nil {
57 ipv4ClusterCIDR = s.ClusterCIDR.String()
58 } else {
59 return errors.New("invalid ClusterCIDR")
60 }
61 nfti, err := nftables.InitNFTables(ipv4ClusterCIDR, ipv6ClusterCIDR)
62 if err != nil {
63 return fmt.Errorf("failed to initialize nftables with error: %w", err)
64 }
65
66 // Create event recorder to report events into K8s
67 hostname, err := os.Hostname()
68 if err != nil {
69 return fmt.Errorf("failed to get local host name with error: %w", err)
70 }
71 eventBroadcaster := record.NewBroadcaster()
72 recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "nfproxy", Host: hostname})
73
74 // Create new proxy controller with endpoint slices enabled
75 // https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
76 nfproxy := proxy.NewProxy(nfti, hostname, recorder, true)
77
78 // Create special informer which doesn't track headless services
79 noHeadlessEndpoints, err := labels.NewRequirement(v1.IsHeadlessService, selection.DoesNotExist, nil)
80 if err != nil {
81 return fmt.Errorf("failed to create Requirement for noHeadlessEndpoints: %w", err)
82 }
83 labelSelector := labels.NewSelector()
84 labelSelector = labelSelector.Add(*noHeadlessEndpoints)
85
86 kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(s.ClientSet, time.Minute*5,
87 kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) {
88 options.LabelSelector = labelSelector.String()
89 }))
90
91 svcController := controller.NewServiceController(nfproxy, s.ClientSet, kubeInformerFactory.Core().V1().Services())
92 ep := controller.NewEndpointSliceController(nfproxy, s.ClientSet, kubeInformerFactory.Discovery().V1beta1().EndpointSlices())
93 kubeInformerFactory.Start(ctx.Done())
94
95 if err = svcController.Start(ctx.Done()); err != nil {
96 return fmt.Errorf("error running Service controller: %w", err)
97 }
98 if err = ep.Start(ctx.Done()); err != nil {
99 return fmt.Errorf("error running endpoint controller: %w", err)
100 }
101 supervisor.Signal(ctx, supervisor.SignalHealthy)
102 supervisor.Signal(ctx, supervisor.SignalDone)
103 return nil
104}