Files
elmprodvpn/selective-vpn-api/app/transport_netns.go

145 lines
4.0 KiB
Go

package app
import (
"errors"
"fmt"
"net/netip"
"strconv"
"strings"
"time"
)
const (
transportNetnsNATTable = "svpn_netns"
)
type transportNetnsSpec struct {
Name string
HostVeth string
PeerVeth string
Prefix netip.Prefix
HostIP netip.Addr
PeerIP netip.Addr
Uplink string
}
func transportNetnsEnabled(client TransportClient) bool {
if !transportConfigHasKey(client.Config, "netns_enabled") {
return false
}
return transportConfigBool(client.Config, "netns_enabled")
}
func transportNetnsStrict(client TransportClient) bool {
return transportConfigBool(client.Config, "netns_setup_strict")
}
func transportNetnsAutoCleanup(client TransportClient) bool {
return transportConfigBool(client.Config, "netns_auto_cleanup")
}
func transportEnsureNetnsForClient(client TransportClient) (string, error) {
spec, err := transportBuildNetnsSpec(client)
if err != nil {
return "", err
}
created := false
exists, err := transportNetnsExists(spec.Name)
if err != nil {
return "", err
}
if !exists {
if err := transportRunMust(6*time.Second, "ip", "netns", "add", spec.Name); err != nil {
return "", err
}
created = true
}
// Recreate veth pair for deterministic state before every start/restart.
_ = transportRunSoft(3*time.Second, "ip", "link", "del", spec.HostVeth)
_ = transportRunInNetnsSoft(3*time.Second, client, spec.Name, "ip", "link", "del", spec.PeerVeth)
if err := transportRunMust(5*time.Second, "ip", "link", "add", spec.HostVeth, "type", "veth", "peer", "name", spec.PeerVeth); err != nil {
return "", err
}
if err := transportRunMust(5*time.Second, "ip", "link", "set", spec.PeerVeth, "netns", spec.Name); err != nil {
return "", err
}
mask := strconv.Itoa(spec.Prefix.Bits())
if err := transportRunMust(5*time.Second, "ip", "addr", "replace", spec.HostIP.String()+"/"+mask, "dev", spec.HostVeth); err != nil {
return "", err
}
if err := transportRunMust(5*time.Second, "ip", "link", "set", spec.HostVeth, "up"); err != nil {
return "", err
}
if err := transportRunInNetnsMust(5*time.Second, client, spec.Name, "ip", "link", "set", "lo", "up"); err != nil {
return "", err
}
if err := transportRunInNetnsMust(5*time.Second, client, spec.Name, "ip", "addr", "replace", spec.PeerIP.String()+"/"+mask, "dev", spec.PeerVeth); err != nil {
return "", err
}
if err := transportRunInNetnsMust(5*time.Second, client, spec.Name, "ip", "link", "set", spec.PeerVeth, "up"); err != nil {
return "", err
}
if err := transportRunInNetnsMust(5*time.Second, client, spec.Name, "ip", "route", "replace", "default", "via", spec.HostIP.String(), "dev", spec.PeerVeth); err != nil {
return "", err
}
if err := transportRunMust(5*time.Second, "sysctl", "-w", "net.ipv4.ip_forward=1"); err != nil {
return "", err
}
if err := transportEnsureNetnsNAT(spec); err != nil {
return "", err
}
if err := transportEnsureNetnsPolicyRoute(spec); err != nil {
return "", err
}
msg := fmt.Sprintf(
"netns ready: %s (%s<->%s, subnet=%s, uplink=%s)",
spec.Name,
spec.HostVeth,
spec.PeerVeth,
spec.Prefix.String(),
spec.Uplink,
)
if created {
msg += "; created"
}
return msg, nil
}
func transportCleanupNetnsForClient(client TransportClient) (string, error) {
spec, err := transportBuildNetnsSpec(client)
if err != nil {
return "", err
}
exists, err := transportNetnsExists(spec.Name)
if err != nil {
return "", err
}
if !exists {
_ = transportNetnsDeleteNATRule(spec.Name)
return "netns not found (skip cleanup)", nil
}
var warnings []string
if err := transportNetnsDeleteNATRule(spec.Name); err != nil {
warnings = append(warnings, err.Error())
}
if err := transportDeleteNetnsPolicyRoute(spec); err != nil {
warnings = append(warnings, err.Error())
}
if err := transportRunSoft(4*time.Second, "ip", "netns", "del", spec.Name); err != nil {
warnings = append(warnings, err.Error())
}
_ = transportRunSoft(3*time.Second, "ip", "link", "del", spec.HostVeth)
msg := "netns cleaned: " + spec.Name
if len(warnings) > 0 {
return msg, errors.New(strings.Join(warnings, "; "))
}
return msg, nil
}