145 lines
4.0 KiB
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
|
|
}
|