zapret-kvn/runtime/amnezia/config.go
loop-uh d5a4715771
Some checks failed
Windows project source guards / test (push) Has been cancelled
feat: use official Amnezia transport and organize runtime modules
2026-09-06 00:14:08 +03:00

228 lines
8.1 KiB
Go

package main
// This is a transport configuration, not a routing language. Destination DNS
// and direct/proxy/block policy belong exclusively to the sing-box front.
import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/netip"
"sort"
"strconv"
"strings"
)
type peerConfig struct {
Address string `json:"address"`
Port uint16 `json:"port"`
PublicKey string `json:"public_key"`
PreSharedKey string `json:"pre_shared_key,omitempty"`
AllowedIPs []string `json:"allowed_ips"`
Keepalive json.RawMessage `json:"persistent_keepalive_interval,omitempty"`
}
type endpointConfig struct {
Address []string `json:"address"`
PrivateKey string `json:"private_key"`
MTU int `json:"mtu"`
ListenPort uint16 `json:"listen_port,omitempty"`
Peers []peerConfig `json:"peers"`
Amnezia map[string]json.RawMessage `json:"amnezia,omitempty"`
}
type config struct {
Endpoint endpointConfig `json:"endpoint"`
Listen string `json:"listen"`
DNSAddress string `json:"dns_address,omitempty"`
BootstrapDNS []string `json:"bootstrap_dns,omitempty"`
Username string `json:"username"`
Password string `json:"password"`
InterfaceIndex uint32 `json:"interface_index"`
SessionGeneration uint64 `json:"session_generation"`
TargetGeneration uint64 `json:"target_generation"`
TargetRef string `json:"target_ref"`
}
func keyHex(value string) (string, error) {
key, err := base64.StdEncoding.DecodeString(value)
if err != nil || len(key) != 32 {
return "", fmt.Errorf("key must be base64 encoding of exactly 32 bytes")
}
return hex.EncodeToString(key), nil
}
// sing-box's native range accepts a number, "from-to", or {from,to}.
// Validate before converting to UAPI so narrowing never wraps uint32 values.
func uint32Range(raw json.RawMessage) (string, error) {
var text string
if err := json.Unmarshal(raw, &text); err != nil {
var n uint32
if err := json.Unmarshal(raw, &n); err == nil && string(raw) != "null" {
return strconv.FormatUint(uint64(n), 10), nil
}
var bounds struct {
From *uint32 `json:"from"`
To *uint32 `json:"to"`
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&bounds); err != nil || bounds.From == nil || bounds.To == nil || *bounds.From > *bounds.To {
return "", fmt.Errorf("expected uint32 number or ordered range")
}
return fmt.Sprintf("%d-%d", *bounds.From, *bounds.To), nil
}
parts := strings.Split(text, "-")
if len(parts) < 1 || len(parts) > 2 {
return "", fmt.Errorf("expected uint32 number or ordered range")
}
var values []uint64
for _, part := range parts {
if part == "" || strings.Trim(part, "0123456789") != "" {
return "", fmt.Errorf("invalid uint32 range")
}
n, err := strconv.ParseUint(part, 10, 32)
if err != nil {
return "", fmt.Errorf("range exceeds uint32")
}
values = append(values, n)
}
if len(values) == 1 {
return strconv.FormatUint(values[0], 10), nil
}
if values[0] > values[1] {
return "", fmt.Errorf("reversed uint32 range")
}
return fmt.Sprintf("%d-%d", values[0], values[1]), nil
}
func (c config) validate() ([]netip.Addr, string, error) {
listen, err := netip.ParseAddrPort(c.Listen)
if err != nil || !listen.Addr().IsLoopback() {
return nil, "", fmt.Errorf("relay listen must be a literal loopback address")
}
if c.DNSAddress != "" {
dns, err := netip.ParseAddrPort(c.DNSAddress)
if err != nil || !dns.Addr().IsLoopback() || dns.Port() == 0 {
return nil, "", fmt.Errorf("DNS delegation requires the sing-box loopback DNS address")
}
}
if len(c.Username) < 16 || len(c.Username) > 255 || len(c.Password) < 32 || len(c.Password) > 255 {
return nil, "", fmt.Errorf("relay requires per-session credentials (username 16..255, password 32..255 bytes)")
}
if c.Endpoint.MTU < 576 || c.Endpoint.MTU > 65535 {
return nil, "", fmt.Errorf("MTU must be between 576 and 65535")
}
var addresses []netip.Addr
for _, raw := range c.Endpoint.Address {
prefix, err := netip.ParsePrefix(raw)
if err != nil || prefix.Addr().IsUnspecified() || prefix.Addr().IsMulticast() {
return nil, "", fmt.Errorf("invalid tunnel address")
}
if prefix.Addr().Is6() && c.Endpoint.MTU < 1280 {
return nil, "", fmt.Errorf("IPv6 tunnel requires MTU >= 1280")
}
addresses = append(addresses, prefix.Addr())
}
if len(addresses) == 0 || len(c.Endpoint.Peers) == 0 {
return nil, "", fmt.Errorf("tunnel addresses and peers are required")
}
private, err := keyHex(c.Endpoint.PrivateKey)
if err != nil {
return nil, "", fmt.Errorf("private_key: %w", err)
}
var ipc strings.Builder
fmt.Fprintf(&ipc, "private_key=%s\nlisten_port=%d\nreplace_peers=true\n", private, c.Endpoint.ListenPort)
names := make([]string, 0, len(c.Endpoint.Amnezia))
for name := range c.Endpoint.Amnezia {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
raw := c.Endpoint.Amnezia[name]
var value string
switch name {
case "jc", "jmin", "jmax", "s1", "s2", "s3", "s4":
var n uint32
if err := json.Unmarshal(raw, &n); err != nil {
return nil, "", fmt.Errorf("%s must be an unsigned integer", name)
}
if strings.HasPrefix(name, "s") && n > 65535 {
return nil, "", fmt.Errorf("%s exceeds uint16", name)
}
value = strconv.FormatUint(uint64(n), 10)
case "h1", "h2", "h3", "h4", "content_padding_addition", "rekey_after_time", "rekey_timeout", "reject_after_time", "keepalive_timeout", "max_handshake_attempts":
if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
continue
}
value, err = uint32Range(raw)
if err != nil {
return nil, "", fmt.Errorf("%s: %w", name, err)
}
case "i1", "i2", "i3", "i4", "i5", "header_protection_key":
if err := json.Unmarshal(raw, &value); err != nil {
return nil, "", fmt.Errorf("%s must be a string", name)
}
if name == "header_protection_key" {
value, err = keyHex(value)
if err != nil {
return nil, "", fmt.Errorf("header_protection_key: %w", err)
}
}
case "random_trailers", "disable_cookies":
var flag bool
if err := json.Unmarshal(raw, &flag); err != nil {
return nil, "", fmt.Errorf("%s must be boolean", name)
}
value = strconv.FormatBool(flag)
default:
return nil, "", fmt.Errorf("unsupported Amnezia parameter %q", name)
}
if strings.ContainsAny(value, "\r\n\x00") {
return nil, "", fmt.Errorf("invalid control character in %s", name)
}
fmt.Fprintf(&ipc, "%s=%s\n", name, value)
}
seen := map[string]bool{}
for _, peer := range c.Endpoint.Peers {
key, err := keyHex(peer.PublicKey)
if err != nil {
return nil, "", fmt.Errorf("public_key: %w", err)
}
if seen[key] {
return nil, "", fmt.Errorf("duplicate peer public_key")
}
seen[key] = true
address, err := netip.ParseAddr(peer.Address)
if err != nil || address.IsUnspecified() || address.IsMulticast() || peer.Port == 0 {
return nil, "", fmt.Errorf("peer endpoint must be a resolved unicast IP and nonzero port")
}
fmt.Fprintf(&ipc, "public_key=%s\nendpoint=%s\n", key, netip.AddrPortFrom(address, peer.Port))
if peer.PreSharedKey != "" {
psk, err := keyHex(peer.PreSharedKey)
if err != nil {
return nil, "", fmt.Errorf("pre_shared_key: %w", err)
}
fmt.Fprintf(&ipc, "preshared_key=%s\n", psk)
}
if len(peer.AllowedIPs) == 0 {
return nil, "", fmt.Errorf("each peer requires allowed_ips")
}
for _, raw := range peer.AllowedIPs {
prefix, err := netip.ParsePrefix(raw)
if err != nil {
return nil, "", fmt.Errorf("invalid allowed_ips prefix")
}
fmt.Fprintf(&ipc, "allowed_ip=%s\n", prefix.Masked())
}
if len(peer.Keepalive) != 0 && !bytes.Equal(bytes.TrimSpace(peer.Keepalive), []byte("null")) {
interval, err := uint32Range(peer.Keepalive)
if err != nil {
return nil, "", fmt.Errorf("persistent_keepalive_interval: %w", err)
}
fmt.Fprintf(&ipc, "persistent_keepalive_interval=%s\n", interval)
}
}
return addresses, ipc.String(), nil
}