Skip to content

Commit efa23ba

Browse files
cheina97adamjensenbot
authored andcommitted
Network: wireguard userspace implementation
1 parent 484c748 commit efa23ba

File tree

9 files changed

+134
-13
lines changed

9 files changed

+134
-13
lines changed

build/gateway/wireguard/Dockerfile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
FROM golang:1.22 as goBuilder
1+
FROM golang:1.22 as goBuilder-wg
2+
WORKDIR /tmp/builder
3+
4+
ARG ref=12269c2761734b15625017d8565745096325392f
5+
RUN git clone https://git.zx2c4.com/wireguard-go && cd wireguard-go && git checkout $ref && \
6+
CGO_ENABLED=0 GOOS=linux GOARCH=$(go env GOARCH) go build -ldflags="-s -w" -o wireguard-go
7+
8+
9+
FROM golang:1.22 as goBuilder
210
WORKDIR /tmp/builder
311

412
COPY go.mod ./go.mod
@@ -17,4 +25,6 @@ RUN apk update && \
1725

1826
COPY --from=goBuilder /tmp/builder/wireguard /usr/bin/liqo-wireguard
1927

28+
COPY --from=goBuilder-wg /tmp/builder/wireguard-go/wireguard-go /usr/bin/wireguard-go
29+
2030
ENTRYPOINT [ "/usr/bin/liqo-wireguard" ]

cmd/gateway/wireguard/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func run(cmd *cobra.Command, _ []string) error {
163163
}
164164

165165
// Create the wg-liqo interface and init the wireguard configuration depending on the mode (client/server).
166-
if err := wireguard.InitWireguardLink(options); err != nil {
166+
if err := wireguard.InitWireguardLink(cmd.Context(), options); err != nil {
167167
return fmt.Errorf("unable to init wireguard link: %w", err)
168168
}
169169

deployments/liqo/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
| nameOverride | string | `""` | Override the standard name used by Helm and associated to Kubernetes/Liqo resources. |
129129
| networking.clientResources | list | `[{"apiVersion":"networking.liqo.io/v1alpha1","resource":"wggatewayclients"}]` | Set the list of resources that implement the GatewayClient |
130130
| networking.enabled | bool | `true` | Use the default Liqo networking module. |
131-
| networking.gatewayTemplates | object | `{"container":{"gateway":{"image":{"name":"ghcr.io/liqotech/gateway","version":""}},"geneve":{"image":{"name":"ghcr.io/liqotech/gateway/geneve","version":""}},"wireguard":{"image":{"name":"ghcr.io/liqotech/gateway/wireguard","version":""}}},"ping":{"interval":"2s","lossThreshold":5,"updateStatusInterval":"10s"},"replicas":1,"server":{"service":{"allocateLoadBalancerNodePorts":"","annotations":{"service.beta.kubernetes.io/aws-load-balancer-type":"nlb"}}}}` | Set the options for the default gateway (server/client) templates. The default templates use a WireGuard implementation to connect the gateway of the clusters. These options are used to configure only the default templates and should not be considered if a custom template is used. |
131+
| networking.gatewayTemplates | object | `{"container":{"gateway":{"image":{"name":"ghcr.io/liqotech/gateway","version":""}},"geneve":{"image":{"name":"ghcr.io/liqotech/gateway/geneve","version":""}},"wireguard":{"image":{"name":"ghcr.io/liqotech/gateway/wireguard","version":""}}},"ping":{"interval":"2s","lossThreshold":5,"updateStatusInterval":"10s"},"replicas":1,"server":{"service":{"allocateLoadBalancerNodePorts":"","annotations":{"service.beta.kubernetes.io/aws-load-balancer-type":"nlb"}}},"wireguard":{"implementation":"kernel"}}` | Set the options for the default gateway (server/client) templates. The default templates use a WireGuard implementation to connect the gateway of the clusters. These options are used to configure only the default templates and should not be considered if a custom template is used. |
132132
| networking.gatewayTemplates.container.gateway.image.name | string | `"ghcr.io/liqotech/gateway"` | Image repository for the gateway container. |
133133
| networking.gatewayTemplates.container.gateway.image.version | string | `""` | Custom version for the gateway image. If not specified, the global tag is used. |
134134
| networking.gatewayTemplates.container.geneve.image.name | string | `"ghcr.io/liqotech/gateway/geneve"` | Image repository for the geneve container. |
@@ -144,6 +144,7 @@
144144
| networking.gatewayTemplates.server.service | object | `{"allocateLoadBalancerNodePorts":"","annotations":{"service.beta.kubernetes.io/aws-load-balancer-type":"nlb"}}` | Set the options to configure the server service |
145145
| networking.gatewayTemplates.server.service.allocateLoadBalancerNodePorts | string | `""` | Set to "false" if you expose the gateway service as LoadBalancer and you do not want to create also a NodePort associated to it (Note: this setting is useful only on cloud providers that support this feature). |
146146
| networking.gatewayTemplates.server.service.annotations | object | `{"service.beta.kubernetes.io/aws-load-balancer-type":"nlb"}` | Annotations for the server service. |
147+
| networking.gatewayTemplates.wireguard.implementation | string | `"kernel"` | Set the implementation used for the WireGuard connection. Possible values are "kernel" and "userspace". |
147148
| networking.iptables | object | `{"mode":"nf_tables"}` | Iptables configuration tuning. |
148149
| networking.iptables.mode | string | `"nf_tables"` | Select the iptables mode to use. Possible values are "legacy" and "nf_tables". |
149150
| networking.mtu | int | `1340` | Set the MTU for the interfaces managed by liqo: vxlan, tunnel and veth interfaces. The value is used by the gateway and route operators. The default value is configured to ensure correct behavior regardless of the combination of the underlying environments (e.g., cloud providers). This guarantees improved compatibility at the cost of possible limited performance drops. |

deployments/liqo/templates/liqo-wireguard-gateway-client-template.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,15 @@ spec:
8080
- --endpoint-port={{"{{ .Spec.Endpoint.Port }}"}}
8181
- --metrics-address=:8082
8282
- --health-probe-bind-address=:8083
83+
- --implementation={{ .Values.networking.gatewayTemplates.wireguard.implementation }}
8384
securityContext:
8485
capabilities:
8586
add:
8687
- NET_ADMIN
8788
- NET_RAW
89+
{{ if .Values.networking.gatewayTemplates.wireguard.implementation | eq "userspace" }}
90+
privileged: true
91+
{{ end }}
8892
- name: geneve
8993
image: {{ .Values.networking.gatewayTemplates.container.geneve.image.name }}{{ include "liqo.suffix" $geneveConfig }}:{{ include "liqo.version" $geneveConfig }}
9094
imagePullPolicy: {{ .Values.pullPolicy }}

deployments/liqo/templates/liqo-wireguard-gateway-server-template.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,15 @@ spec:
9797
- --listen-port={{"{{ .Spec.Endpoint.Port }}"}}
9898
- --metrics-address=:8082
9999
- --health-probe-bind-address=:8083
100+
- --implementation={{ .Values.networking.gatewayTemplates.wireguard.implementation }}
100101
securityContext:
101102
capabilities:
102103
add:
103104
- NET_ADMIN
104105
- NET_RAW
106+
{{ if .Values.networking.gatewayTemplates.wireguard.implementation | eq "userspace" }}
107+
privileged: true
108+
{{ end }}
105109
- name: geneve
106110
image: {{ .Values.networking.gatewayTemplates.container.geneve.image.name }}{{ include "liqo.suffix" $geneveConfig }}:{{ include "liqo.version" $geneveConfig }}
107111
imagePullPolicy: {{ .Values.pullPolicy }}

deployments/liqo/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ networking:
5252
# These options are used to configure only the default templates and should not be considered
5353
# if a custom template is used.
5454
gatewayTemplates:
55+
wireguard:
56+
# -- Set the implementation used for the WireGuard connection. Possible values are "kernel" and "userspace".
57+
implementation: "kernel"
5558
# -- Set the number of replicas for the gateway deployments
5659
replicas: 1
5760
# -- Set the options to configure the gateway ping used to check connection

pkg/gateway/tunnel/wireguard/flags.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ const (
4444

4545
// FlagNameDNSCheckInterval is the interval between two DNS checks.
4646
FlagNameDNSCheckInterval FlagName = "dns-check-interval"
47+
48+
// FlagNameImplementation is the implementation of the wireguard interface.
49+
FlagNameImplementation FlagName = "implementation"
4750
)
4851

4952
// ClientRequiredFlags contains the list of the mandatory flags for the client mode.
@@ -59,6 +62,8 @@ func InitFlags(flagset *pflag.FlagSet, opts *Options) {
5962
flagset.IntVar(&opts.EndpointPort, FlagNameEndpointPort.String(), 51820, "Endpoint port (client only)")
6063

6164
flagset.DurationVar(&opts.DNSCheckInterval, FlagNameDNSCheckInterval.String(), 5*time.Minute, "Interval between two DNS checks")
65+
66+
flagset.Var(&opts.Implementation, "implementation", "Implementation of the wireguard interface (kernel or userspace)")
6267
}
6368

6469
// MarkFlagsRequired marks the flags as required.

pkg/gateway/tunnel/wireguard/netlink.go

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
package wireguard
1616

1717
import (
18+
"bytes"
19+
"context"
1820
"errors"
1921
"fmt"
22+
"os/exec"
23+
"time"
2024

2125
"github.com/vishvananda/netlink"
2226
"golang.zx2c4.com/wireguard/wgctrl"
2327
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
28+
"k8s.io/apimachinery/pkg/util/wait"
2429
"k8s.io/klog/v2"
2530

2631
"github.com/liqotech/liqo/pkg/gateway"
@@ -29,7 +34,7 @@ import (
2934
)
3035

3136
// InitWireguardLink inits the Wireguard interface.
32-
func InitWireguardLink(options *Options) error {
37+
func InitWireguardLink(ctx context.Context, options *Options) error {
3338
exists, err := existsLink()
3439
if err != nil {
3540
return fmt.Errorf("cannot check if Wireguard interface exists: %w", err)
@@ -39,7 +44,7 @@ func InitWireguardLink(options *Options) error {
3944
return nil
4045
}
4146

42-
if err := createLink(options); err != nil {
47+
if err := createLink(ctx, options); err != nil {
4348
return fmt.Errorf("cannot create Wireguard interface: %w", err)
4449
}
4550

@@ -57,17 +62,21 @@ func InitWireguardLink(options *Options) error {
5762
}
5863

5964
// CreateLink creates a new Wireguard interface.
60-
func createLink(options *Options) error {
61-
link := netlink.Wireguard{
62-
LinkAttrs: netlink.LinkAttrs{
63-
MTU: options.MTU,
64-
Name: tunnel.TunnelInterfaceName,
65-
},
65+
func createLink(ctx context.Context, options *Options) error {
66+
var err error
67+
klog.Infof("Selected wireguard %s implementation", options.Implementation)
68+
69+
switch options.Implementation {
70+
case WgImplementationKernel:
71+
err = createLinkKernel(options)
72+
case WgImplementationUserspace:
73+
err = createLinkUserspace(ctx, options)
74+
default:
75+
err = fmt.Errorf("invalid wireguard implementation: %s", options.Implementation)
6676
}
6777

68-
err := netlink.LinkAdd(&link)
6978
if err != nil {
70-
return fmt.Errorf("cannot add Wireguard interface: %w", err)
79+
return fmt.Errorf("cannot create Wireguard interface: %w", err)
7180
}
7281

7382
if options.GwOptions.Mode == gateway.ModeServer {
@@ -83,6 +92,56 @@ func createLink(options *Options) error {
8392
return fmt.Errorf("cannot configure Wireguard interface: %w", err)
8493
}
8594
}
95+
96+
return nil
97+
}
98+
99+
// createLinkKernel creates a new Wireguard interface using the kernel module.
100+
func createLinkKernel(options *Options) error {
101+
link := netlink.Wireguard{
102+
LinkAttrs: netlink.LinkAttrs{
103+
MTU: options.MTU,
104+
Name: tunnel.TunnelInterfaceName,
105+
},
106+
}
107+
108+
err := netlink.LinkAdd(&link)
109+
if err != nil {
110+
return fmt.Errorf("cannot add Wireguard interface: %w", err)
111+
}
112+
return nil
113+
}
114+
115+
// runWgUserCmd runs the wg command with the given arguments.
116+
func runWgUserCmd(cmd *exec.Cmd) {
117+
var stdout, stderr bytes.Buffer
118+
cmd.Stdout = &stdout
119+
cmd.Stderr = &stderr
120+
if err := cmd.Run(); err != nil {
121+
outStr, errStr := stdout.String(), stderr.String()
122+
fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr)
123+
klog.Fatalf("failed to run '%s': %v", cmd.String(), err)
124+
}
125+
}
126+
127+
// createLinkUserspsce creates a new Wireguard interface using the userspace implementation (wireguard-go).
128+
// TODO: at the moment is not possible to override the settings of the wireguard-go implementation.
129+
// We are planning a PR to add a flag for the MTU.
130+
func createLinkUserspace(ctx context.Context, _ *Options) error {
131+
cmd := exec.Command("/usr/bin/wireguard-go", "-f", tunnel.TunnelInterfaceName) //nolint:gosec //we leave it as it is
132+
go runWgUserCmd(cmd)
133+
134+
if err := wait.PollUntilContextTimeout(ctx, time.Second, 10*time.Second, true, func(context.Context) (done bool, err error) {
135+
klog.Info("Waiting for wireguard device to be created")
136+
if _, err = netlink.LinkByName(tunnel.TunnelInterfaceName); err != nil {
137+
klog.Errorf("failed to get wireguard device '%s': %s", tunnel.TunnelInterfaceName, err)
138+
return false, nil
139+
}
140+
return true, nil
141+
}); err != nil {
142+
return fmt.Errorf("failed to create wireguard device %q: %w", tunnel.TunnelInterfaceName, err)
143+
}
144+
86145
return nil
87146
}
88147

pkg/gateway/tunnel/wireguard/options.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package wireguard
1616

1717
import (
18+
"fmt"
1819
"net"
1920
"sync"
2021
"time"
@@ -24,6 +25,38 @@ import (
2425
"github.com/liqotech/liqo/pkg/gateway"
2526
)
2627

28+
// WgImplementation represents the implementation of the wireguard interface.
29+
type WgImplementation string
30+
31+
const (
32+
// WgImplementationKernel represents the kernel implementation of the wireguard interface.
33+
WgImplementationKernel WgImplementation = "kernel"
34+
// WgImplementationUserspace represents the userspace implementation of the wireguard interface.
35+
WgImplementationUserspace WgImplementation = "userspace"
36+
)
37+
38+
// String returns the string representation of the wireguard implementation.
39+
func (wgi WgImplementation) String() string {
40+
return string(wgi)
41+
}
42+
43+
// Set parses the provided string into the wireguard implementation.
44+
func (wgi *WgImplementation) Set(s string) error {
45+
if s == "" {
46+
s = WgImplementationKernel.String()
47+
}
48+
if s != WgImplementationKernel.String() && s != WgImplementationUserspace.String() {
49+
return fmt.Errorf("invalid wireguard implementation: %s (allowed values are: %s,%s)", s, WgImplementationKernel, WgImplementationUserspace)
50+
}
51+
*wgi = WgImplementation(s)
52+
return nil
53+
}
54+
55+
// Type returns the type of the wireguard implementation.
56+
func (wgi WgImplementation) Type() string {
57+
return "string"
58+
}
59+
2760
// Options contains the options for the wireguard interface.
2861
type Options struct {
2962
GwOptions *gateway.Options
@@ -39,6 +72,8 @@ type Options struct {
3972
EndpointIPMutex *sync.Mutex
4073

4174
DNSCheckInterval time.Duration
75+
76+
Implementation WgImplementation
4277
}
4378

4479
// NewOptions returns a new Options struct.

0 commit comments

Comments
 (0)