platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
127
selective-vpn-api/app/transport_netns_spec.go
Normal file
127
selective-vpn-api/app/transport_netns_spec.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func transportBuildNetnsSpec(client TransportClient) (transportNetnsSpec, error) {
|
||||
ns := transportNetnsName(client)
|
||||
if ns == "" {
|
||||
return transportNetnsSpec{}, fmt.Errorf("netns name is empty")
|
||||
}
|
||||
pfx, err := transportNetnsPrefix(client)
|
||||
if err != nil {
|
||||
return transportNetnsSpec{}, err
|
||||
}
|
||||
hostIP := pfx.Addr().Next()
|
||||
peerIP := hostIP.Next()
|
||||
if !hostIP.IsValid() || !peerIP.IsValid() || !pfx.Contains(peerIP) {
|
||||
return transportNetnsSpec{}, fmt.Errorf("netns subnet has no usable host pair: %s", pfx.String())
|
||||
}
|
||||
|
||||
uplink := strings.TrimSpace(transportConfigString(client.Config, "netns_uplink_iface"))
|
||||
if uplink == "" {
|
||||
mainRoute, err := transportDetectMainIPv4Route()
|
||||
if err != nil {
|
||||
return transportNetnsSpec{}, err
|
||||
}
|
||||
uplink = strings.TrimSpace(mainRoute.Dev)
|
||||
}
|
||||
if uplink == "" {
|
||||
return transportNetnsSpec{}, fmt.Errorf("netns uplink iface is empty")
|
||||
}
|
||||
|
||||
tag := transportShortHash("netns:"+ns, 8)
|
||||
return transportNetnsSpec{
|
||||
Name: ns,
|
||||
HostVeth: "svh" + tag,
|
||||
PeerVeth: "svn" + tag,
|
||||
Prefix: pfx,
|
||||
HostIP: hostIP,
|
||||
PeerIP: peerIP,
|
||||
Uplink: uplink,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transportNetnsName(client TransportClient) string {
|
||||
raw := strings.TrimSpace(transportConfigString(client.Config, "netns_name"))
|
||||
if raw == "" {
|
||||
base := sanitizeID(client.ID)
|
||||
if base == "" {
|
||||
base = "client"
|
||||
}
|
||||
raw = "svpn-" + base
|
||||
}
|
||||
return normalizeTransportNetnsName(raw, client.ID)
|
||||
}
|
||||
|
||||
func normalizeTransportNetnsName(raw, fallbackClientID string) string {
|
||||
ns := sanitizeID(raw)
|
||||
if ns == "" {
|
||||
base := sanitizeID(fallbackClientID)
|
||||
if base == "" {
|
||||
base = "client"
|
||||
}
|
||||
ns = "svpn-" + base
|
||||
}
|
||||
if len(ns) > 63 {
|
||||
ns = ns[:63]
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func transportNetnsPrefix(client TransportClient) (netip.Prefix, error) {
|
||||
raw := strings.TrimSpace(transportConfigString(client.Config, "netns_subnet"))
|
||||
if raw == "" {
|
||||
seed := transportShortHash(client.ID, 2)
|
||||
n, _ := strconv.ParseUint(seed, 16, 8)
|
||||
raw = fmt.Sprintf("10.240.%d.0/30", 10+int(n)%200)
|
||||
}
|
||||
pfx, err := netip.ParsePrefix(raw)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("invalid netns_subnet: %q", raw)
|
||||
}
|
||||
pfx = pfx.Masked()
|
||||
if !pfx.Addr().Is4() {
|
||||
return netip.Prefix{}, fmt.Errorf("netns_subnet must be IPv4: %q", raw)
|
||||
}
|
||||
if pfx.Bits() > 30 {
|
||||
return netip.Prefix{}, fmt.Errorf("netns_subnet must provide at least 2 host addresses: %q", raw)
|
||||
}
|
||||
return pfx, nil
|
||||
}
|
||||
|
||||
func transportNetnsExists(name string) (bool, error) {
|
||||
stdout, stderr, code, err := transportRunCommand(4*time.Second, "ip", "netns", "list")
|
||||
if err != nil || code != 0 {
|
||||
return false, transportCommandError("ip netns list", stdout, stderr, code, err)
|
||||
}
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
fields := strings.Fields(strings.TrimSpace(line))
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
if fields[0] == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func transportShortHash(raw string, n int) string {
|
||||
if n <= 0 {
|
||||
n = 8
|
||||
}
|
||||
h := fnv.New32a()
|
||||
_, _ = h.Write([]byte(strings.TrimSpace(raw)))
|
||||
s := fmt.Sprintf("%08x", h.Sum32())
|
||||
if n >= len(s) {
|
||||
return s
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
Reference in New Issue
Block a user