package aerotun import ( "bytes" "encoding/base64" "fmt" "golang.org/x/crypto/curve25519" "net" "net/netip" "regexp" "strconv" "strings" ) var interfaceName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,11}$`) func validateConfig(c *TunnelInstance) error { if c.Protocol == 0 { c.Protocol = 2 } if c.Protocol != 2 && c.Protocol != 3 { return fmt.Errorf("unsupported AeroTUN version") } if c.Protocol == 3 { if _, err := decodeKey(c.PSK); err != nil { return err } priv, err := decodeKey(c.PrivateKey) if err != nil { return fmt.Errorf("invalid local identity key") } pub, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil { return err } c.PublicKey = base64.StdEncoding.EncodeToString(pub) if c.PeerPublicKey != "" || !c.Disabled { peer, err := decodeKey(c.PeerPublicKey) if err != nil || bytes.Equal(peer, pub) { return fmt.Errorf("a different pinned peer public key is required") } } if len(c.AllowedIPs) == 0 { return fmt.Errorf("v3 requires explicit allowed source IP prefixes") } for _, prefix := range c.AllowedIPs { if _, err := netip.ParsePrefix(prefix); err != nil { return fmt.Errorf("invalid allowed source IP prefix") } } } c.Mode = strings.ToLower(strings.TrimSpace(c.Mode)) if c.Mode != "server" && c.Mode != "client" { return fmt.Errorf("mode must be server or client") } // Reserve three characters for the fallback endpoint suffix. if !interfaceName.MatchString(c.Name) { return fmt.Errorf("interface name must contain 1–12 letters, digits, dots, underscores or dashes") } if c.MTU == 0 { c.MTU = DefaultMTU } if c.MTU < 576 || c.MTU > 9000 { return fmt.Errorf("MTU must be between 576 and 9000") } if c.ListenPort == 0 && c.Mode == "server" { c.ListenPort = DefaultPort } if c.ListenPort < 0 || c.ListenPort > 65535 { return fmt.Errorf("invalid UDP listen port") } if len(c.PSK) < 32 { return fmt.Errorf("PSK must contain at least 32 characters; use Generate Key") } ip, _, err := net.ParseCIDR(c.LocalAddress) if err != nil || ip.IsUnspecified() || ip.IsMulticast() { return fmt.Errorf("local address must be a unicast IP in CIDR notation") } peer, err := peerIP(c.PeerAddress) if err != nil { return err } if peer != nil && ((ip.To4() == nil) != (peer.To4() == nil) || peer.Equal(ip)) { return fmt.Errorf("peer and local address must be distinct and use the same IP family") } if ip.To4() == nil && c.MTU < 1280 { return fmt.Errorf("IPv6 requires MTU of at least 1280") } if c.Mode == "client" && c.RemoteEndpoint == "" { return fmt.Errorf("client requires a remote endpoint") } if c.RemoteEndpoint != "" { host, port, err := net.SplitHostPort(c.RemoteEndpoint) p, portErr := strconv.Atoi(port) if err != nil || host == "" || portErr != nil || p < 1 || p > 65535 { return fmt.Errorf("remote endpoint must be host:port") } } if c.DefaultRoute && (c.Mode != "client" || c.RemoteEndpoint == "") { return fmt.Errorf("default route requires a client with a fixed remote endpoint") } return nil } func peerIP(s string) (net.IP, error) { if s == "" { return nil, nil } ip := net.ParseIP(s) if ip == nil { ip, _, _ = net.ParseCIDR(s) } if ip == nil || ip.IsUnspecified() || ip.IsMulticast() { return nil, fmt.Errorf("invalid peer IP address") } return ip, nil } func configurePeer(dev VirtualDevice, c TunnelInstance) error { peer, err := peerIP(c.PeerAddress) if err != nil { return err } if peer == nil { return nil } family := "-4" prefix := "/32" if peer.To4() == nil { family = "-6" prefix = "/128" } if _, err := runIP(family, "route", "add", peer.String()+prefix, "dev", dev.Name()); err != nil { return fmt.Errorf("peer route: %w", err) } // ARP is disabled on the veth fallback: explicitly map the peer to its local // endpoint MAC rather than relying on unresolved Ethernet neighbour discovery. if v, ok := dev.(*vethTun); ok { _, err = runIP(family, "neigh", "replace", peer.String(), "lladdr", net.HardwareAddr(v.epHwAddr[:]).String(), "nud", "permanent", "dev", dev.Name()) } return err }