diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index c777332dba..bfb7f03f8e 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,11 +8,14 @@ on: - network-general - network-external - network-internal +<<<<<<< HEAD +======= + - frc/connectioncontroller +>>>>>>> 2e426155 (External Network: connection controller) repository_dispatch: types: - test-command - build-command - jobs: configure: name: Preliminary configuration @@ -90,6 +93,7 @@ jobs: - metric-agent - telemetry - proxy + - gateway - gateway/wireguard steps: - name: Set up QEMU diff --git a/build/gateway/Dockerfile b/build/gateway/Dockerfile new file mode 100644 index 0000000000..37a475dd31 --- /dev/null +++ b/build/gateway/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.21 as goBuilder +WORKDIR /tmp/builder + +COPY go.mod ./go.mod +COPY go.sum ./go.sum +RUN go mod download + +COPY . ./ +RUN CGO_ENABLED=0 GOOS=linux GOARCH=$(go env GOARCH) go build -ldflags="-s -w" ./cmd/gateway + + +FROM alpine:3.18 + +RUN apk update && \ + apk add iptables bash tcpdump conntrack-tools curl iputils && \ + rm -rf /var/cache/apk/* + +COPY --from=goBuilder /tmp/builder/gateway /usr/bin/liqo-gateway + +ENTRYPOINT [ "/usr/bin/liqo-gateway" ] diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go new file mode 100644 index 0000000000..36c48a6f0d --- /dev/null +++ b/cmd/gateway/main.go @@ -0,0 +1,128 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package wireguard contains the logic to configure the Wireguard interface. +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/log" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/gateway/connection" + flagsutils "github.com/liqotech/liqo/pkg/utils/flags" + "github.com/liqotech/liqo/pkg/utils/mapper" + "github.com/liqotech/liqo/pkg/utils/restcfg" +) + +var ( + addToSchemeFunctions = []func(*runtime.Scheme) error{ + networkingv1alpha1.AddToScheme, + } + options = connection.NewOptions() +) + +func main() { + var cmd = cobra.Command{ + Use: "liqo-gateway", + RunE: run, + } + + legacyflags := flag.NewFlagSet("legacy", flag.ExitOnError) + restcfg.InitFlags(legacyflags) + klog.InitFlags(legacyflags) + flagsutils.FromFlagToPflag(legacyflags, cmd.Flags()) + + connection.InitFlags(cmd.Flags(), options) + if err := connection.MarkFlagsRequired(&cmd); err != nil { + klog.Error(err) + os.Exit(1) + } + + if err := cmd.Execute(); err != nil { + klog.Error(err) + os.Exit(1) + } +} + +func run(_ *cobra.Command, _ []string) error { + var err error + ctx := ctrl.SetupSignalHandler() + scheme := runtime.NewScheme() + + // Adds the APIs to the scheme. + for _, addToScheme := range addToSchemeFunctions { + if err = addToScheme(scheme); err != nil { + return fmt.Errorf("unable to add scheme: %w", err) + } + } + + // Set controller-runtime logger. + log.SetLogger(klog.NewKlogr()) + + // Get the rest config. + cfg := config.GetConfigOrDie() + + // Create the manager. + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + MapperProvider: mapper.LiqoMapperProvider(scheme), + Scheme: scheme, + Namespace: options.Namespace, + MetricsBindAddress: "0", // Metrics are exposed by "connection" container. + HealthProbeBindAddress: options.ProbeAddr, + LeaderElection: options.LeaderElection, + LeaderElectionID: fmt.Sprintf( + "%s.%s.%s.connections.liqo.io", + options.Name, options.Namespace, options.Mode, + ), + LeaderElectionNamespace: options.Namespace, + LeaderElectionReleaseOnCancel: true, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + LeaseDuration: &options.LeaderElectionLeaseDuration, + RenewDeadline: &options.LeaderElectionRenewDeadline, + RetryPeriod: &options.LeaderElectionRetryPeriod, + }) + if err != nil { + return fmt.Errorf("unable to create manager: %w", err) + } + + // Setup the controller. + connr, err := connection.NewConnectionsReconciler( + ctx, + mgr.GetClient(), + mgr.GetScheme(), + mgr.GetEventRecorderFor("connections-controller"), + options, + ) + if err != nil { + return fmt.Errorf("unable to create connectioons reconciler: %w", err) + } + + // Setup the controller. + if err = connr.SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to setup connections reconciler: %w", err) + } + + // Start the manager. + return mgr.Start(ctx) +} diff --git a/cmd/liqonet/gateway-operator.go b/cmd/liqonet/gateway-operator.go index 468574e893..b416fde7b7 100644 --- a/cmd/liqonet/gateway-operator.go +++ b/cmd/liqonet/gateway-operator.go @@ -34,7 +34,7 @@ import ( tunneloperator "github.com/liqotech/liqo/internal/liqonet/tunnel-operator" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" liqonetns "github.com/liqotech/liqo/pkg/liqonet/netns" liqonetutils "github.com/liqotech/liqo/pkg/liqonet/utils" "github.com/liqotech/liqo/pkg/liqonet/utils/links" diff --git a/internal/liqonet/tunnel-operator/tunnel-operator.go b/internal/liqonet/tunnel-operator/tunnel-operator.go index 9c7e34e208..76db5c4580 100644 --- a/internal/liqonet/tunnel-operator/tunnel-operator.go +++ b/internal/liqonet/tunnel-operator/tunnel-operator.go @@ -41,7 +41,7 @@ import ( netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" "github.com/liqotech/liqo/pkg/liqonet/iptables" liqonetns "github.com/liqotech/liqo/pkg/liqonet/netns" liqorouting "github.com/liqotech/liqo/pkg/liqonet/routing" diff --git a/pkg/liqonet/conncheck/common.go b/pkg/gateway/connection/conncheck/common.go similarity index 100% rename from pkg/liqonet/conncheck/common.go rename to pkg/gateway/connection/conncheck/common.go diff --git a/pkg/liqonet/conncheck/conncheck.go b/pkg/gateway/connection/conncheck/conncheck.go similarity index 95% rename from pkg/liqonet/conncheck/conncheck.go rename to pkg/gateway/connection/conncheck/conncheck.go index 543392c76e..fa9b9f8ee9 100644 --- a/pkg/liqonet/conncheck/conncheck.go +++ b/pkg/gateway/connection/conncheck/conncheck.go @@ -139,3 +139,11 @@ func (c *ConnChecker) GetConnected(clusterID string) (bool, error) { } return false, fmt.Errorf("sender %s not found", clusterID) } + +// IsSenderRunning returns true if the sender is running. +func (c *ConnChecker) IsSenderRunning(clusterID string) bool { + c.sm.RLock() + defer c.sm.RUnlock() + _, ok := c.senders[clusterID] + return ok +} diff --git a/pkg/liqonet/conncheck/consts.go b/pkg/gateway/connection/conncheck/consts.go similarity index 100% rename from pkg/liqonet/conncheck/consts.go rename to pkg/gateway/connection/conncheck/consts.go diff --git a/pkg/liqonet/conncheck/doc.go b/pkg/gateway/connection/conncheck/doc.go similarity index 100% rename from pkg/liqonet/conncheck/doc.go rename to pkg/gateway/connection/conncheck/doc.go diff --git a/pkg/liqonet/conncheck/receiver.go b/pkg/gateway/connection/conncheck/receiver.go similarity index 100% rename from pkg/liqonet/conncheck/receiver.go rename to pkg/gateway/connection/conncheck/receiver.go diff --git a/pkg/liqonet/conncheck/sender.go b/pkg/gateway/connection/conncheck/sender.go similarity index 100% rename from pkg/liqonet/conncheck/sender.go rename to pkg/gateway/connection/conncheck/sender.go diff --git a/pkg/gateway/connection/connections-controller.go b/pkg/gateway/connection/connections-controller.go new file mode 100644 index 0000000000..c6f309180f --- /dev/null +++ b/pkg/gateway/connection/connections-controller.go @@ -0,0 +1,121 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package connection + +import ( + "context" + "fmt" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// cluster-role +// +kubebuilder:rbac:groups=networking.liqo.io,resources=connections,verbs=get;list;create;delete;update + +// ConnectionsReconciler updates the PublicKey resource used to establish the Wireguard connection. +type ConnectionsReconciler struct { + ConnChecker *conncheck.ConnChecker + Client client.Client + Scheme *runtime.Scheme + EventsRecorder record.EventRecorder + Options *Options +} + +// NewConnectionsReconciler returns a new PublicKeysReconciler. +func NewConnectionsReconciler(ctx context.Context, cl client.Client, + s *runtime.Scheme, er record.EventRecorder, options *Options) (*ConnectionsReconciler, error) { + connchecker, err := conncheck.NewConnChecker() + if err != nil { + return nil, fmt.Errorf("unable to create the connection checker: %w", err) + } + go connchecker.RunReceiver(ctx) + go connchecker.RunReceiverDisconnectObserver(ctx) + return &ConnectionsReconciler{ + ConnChecker: connchecker, + Client: cl, + Scheme: s, + EventsRecorder: er, + Options: options, + }, nil +} + +// Reconcile manage PublicKey resources. +func (r *ConnectionsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + connection := &networkingv1alpha1.Connection{} + if err := r.Client.Get(ctx, req.NamespacedName, connection); err != nil { + if apierrors.IsNotFound(err) { + klog.Infof("There is no connection %s", req.String()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("unable to get the connection %q: %w", req.NamespacedName, err) + } + + if r.ConnChecker.IsSenderRunning(r.Options.RemoteClusterID) { + return ctrl.Result{}, nil + } + + go r.ConnChecker.AddAndRunSender( + ctx, r.Options.RemoteClusterID, + common.GetRemoteInterfaceIP(r.Options.Mode), + ForgeUpdateConnectionCallback(ctx, r.Client, req), + ) + + return ctrl.Result{}, nil +} + +// SetupWithManager register the ConfigurationReconciler to the manager. +func (r *ConnectionsReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1alpha1.Connection{}, r.Predicates()). + Complete(r) +} + +// Predicates returns the predicates required for the PublicKey controller. +func (r *ConnectionsReconciler) Predicates() builder.Predicates { + return builder.WithPredicates( + predicate.NewPredicateFuncs(func(object client.Object) bool { + return true + })) +} + +// ForgeUpdateConnectionCallback forges the UpdateConnectionStatus function. +func ForgeUpdateConnectionCallback(ctx context.Context, cl client.Client, req ctrl.Request) conncheck.UpdateFunc { + return func(connected bool, latency time.Duration, timestamp time.Time) error { + connection := &networkingv1alpha1.Connection{} + if err := cl.Get(ctx, req.NamespacedName, connection); err != nil { + return err + } + var connStatusValue networkingv1alpha1.ConnectionStatusValue + switch connected { + case true: + connStatusValue = networkingv1alpha1.Connected + case false: + connStatusValue = networkingv1alpha1.ConnectionError + } + return UpdateConnectionStatus(ctx, cl, connection, connStatusValue, latency, timestamp) + } +} diff --git a/pkg/gateway/connection/doc.go b/pkg/gateway/connection/doc.go new file mode 100644 index 0000000000..c27703910c --- /dev/null +++ b/pkg/gateway/connection/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package connection menages the connection resource. +package connection diff --git a/pkg/gateway/connection/k8s.go b/pkg/gateway/connection/k8s.go new file mode 100644 index 0000000000..ac6886172d --- /dev/null +++ b/pkg/gateway/connection/k8s.go @@ -0,0 +1,36 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package connection + +import ( + "context" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" +) + +// UpdateConnectionStatus updates the status of a connection. +func UpdateConnectionStatus(ctx context.Context, cl client.Client, connection *networkingv1alpha1.Connection, + connected networkingv1alpha1.ConnectionStatusValue, latency time.Duration, timestamp time.Time) error { + connection.Status.Value = connected + connection.Status.Latency = networkingv1alpha1.ConnectionLatency{ + Value: latency.String(), + Timestamp: metav1.NewTime(timestamp), + } + return cl.Status().Update(ctx, connection) +} diff --git a/pkg/gateway/connection/options.go b/pkg/gateway/connection/options.go new file mode 100644 index 0000000000..ae73a431d2 --- /dev/null +++ b/pkg/gateway/connection/options.go @@ -0,0 +1,45 @@ +// Copyright 2019-2023 The Liqo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package connection + +import ( + "time" + + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// Options contains the options for the wireguard interface. +type Options struct { + Name string + Namespace string + RemoteClusterID string + + Mode common.Mode + + LeaderElection bool + LeaderElectionLeaseDuration time.Duration + LeaderElectionRenewDeadline time.Duration + LeaderElectionRetryPeriod time.Duration + + PingLatencyUpdateInterval time.Duration + + MetricsAddress string + ProbeAddr string +} + +// NewOptions returns a new Options struct. +func NewOptions() *Options { + return &Options{} +} diff --git a/pkg/gateway/tunnel/common/netlink.go b/pkg/gateway/tunnel/common/netlink.go index f784928059..8a41102cd1 100644 --- a/pkg/gateway/tunnel/common/netlink.go +++ b/pkg/gateway/tunnel/common/netlink.go @@ -16,6 +16,13 @@ package common import "github.com/vishvananda/netlink" +const ( + // ServerInterfaceIP is the IP address of the Wireguard interface in server mode. + ServerInterfaceIP = "169.254.0.1/30" + // ClientInterfaceIP is the IP address of the Wireguard interface in client mode. + ClientInterfaceIP = "169.254.0.2/30" +) + // AddAddress adds an IP address to the Wireguard interface. func AddAddress(link netlink.Link, ip string) error { addr, err := netlink.ParseAddr(ip) @@ -30,3 +37,25 @@ func AddAddress(link netlink.Link, ip string) error { func GetLink(name string) (netlink.Link, error) { return netlink.LinkByName(name) } + +// GetInterfaceIP returns the IP address of the Wireguard interface. +func GetInterfaceIP(mode Mode) string { + switch mode { + case ModeServer: + return ServerInterfaceIP + case ModeClient: + return ClientInterfaceIP + } + return "" +} + +// GetRemoteInterfaceIP returns the IP address of the remote Wireguard interface. +func GetRemoteInterfaceIP(mode Mode) string { + switch mode { + case ModeServer: + return ClientInterfaceIP + case ModeClient: + return ServerInterfaceIP + } + return "" +} diff --git a/pkg/gateway/tunnel/wireguard/netlink.go b/pkg/gateway/tunnel/wireguard/netlink.go index b4070987f4..5321b7a67a 100644 --- a/pkg/gateway/tunnel/wireguard/netlink.go +++ b/pkg/gateway/tunnel/wireguard/netlink.go @@ -23,13 +23,6 @@ import ( "github.com/liqotech/liqo/pkg/gateway/tunnel/common" ) -const ( - // ServerInterfaceIP is the IP address of the Wireguard interface in server mode. - ServerInterfaceIP = "169.254.0.1/30" - // ClientInterfaceIP is the IP address of the Wireguard interface in client mode. - ClientInterfaceIP = "169.254.0.2/30" -) - // InitWireguardLink inits the Wireguard interface. func InitWireguardLink(options *Options) error { if err := createLink(options); err != nil { @@ -41,25 +34,14 @@ func InitWireguardLink(options *Options) error { return err } - klog.Infof("Setting up Wireguard interface %q with IP %q", options.InterfaceName, GetInterfaceIP(options.Mode)) - if err := common.AddAddress(link, GetInterfaceIP(options.Mode)); err != nil { + klog.Infof("Setting up Wireguard interface %q with IP %q", options.InterfaceName, common.GetInterfaceIP(options.Mode)) + if err := common.AddAddress(link, common.GetInterfaceIP(options.Mode)); err != nil { return err } return netlink.LinkSetUp(link) } -// GetInterfaceIP returns the IP address of the Wireguard interface. -func GetInterfaceIP(mode common.Mode) string { - switch mode { - case common.ModeServer: - return ServerInterfaceIP - case common.ModeClient: - return ClientInterfaceIP - } - return "" -} - // CreateLink creates a new Wireguard interface. func createLink(options *Options) error { link := netlink.Wireguard{ diff --git a/pkg/liqonet/tunnel/driver.go b/pkg/liqonet/tunnel/driver.go index 9a9bc8b4e5..3a26dedea4 100644 --- a/pkg/liqonet/tunnel/driver.go +++ b/pkg/liqonet/tunnel/driver.go @@ -23,7 +23,7 @@ import ( "k8s.io/klog/v2" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" ) // DriverCreateFunc function prototype to create a new driver. diff --git a/pkg/liqonet/tunnel/wireguard/driver.go b/pkg/liqonet/tunnel/wireguard/driver.go index d582830ea7..65e88d9e60 100644 --- a/pkg/liqonet/tunnel/wireguard/driver.go +++ b/pkg/liqonet/tunnel/wireguard/driver.go @@ -42,7 +42,7 @@ import ( discv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" "github.com/liqotech/liqo/pkg/liqonet/tunnel" "github.com/liqotech/liqo/pkg/liqonet/tunnel/metrics" "github.com/liqotech/liqo/pkg/liqonet/tunnel/resolver"