Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
7.8 KB go
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2026 WireGuard LLC. All Rights Reserved.
 */

package conf

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	"net/netip"
	"slices"
	"strings"
	"time"

	"golang.org/x/crypto/curve25519"

	"golang.zx2c4.com/wireguard/windows/l18n"
	"golang.zx2c4.com/wireguard/windows/phobos"
)

const KeyLength = 32

type Endpoint struct {
	Host string
	Port uint16
}

type (
	Key           [KeyLength]byte
	HandshakeTime time.Duration
	Bytes         uint64
)

type Config struct {
	Name      string
	Interface Interface
	Peers     []Peer

	Obfuscation *Obfuscation

	TrailingComments []string
}

type ObfuscationMode int

const (
	ObfuscationModeWireGuard ObfuscationMode = iota
	ObfuscationModeSocks5
)

type Obfuscation struct {
	Mode             ObfuscationMode
	SourceListenPort uint16
	Target           Endpoint
	Key              string
	Masking          phobos.Masking
	ObfuscateBytes   uint16
	MaxDummy         uint16
	MediaPayloadType uint8
	MediaSSRC        uint32
	MediaClock       uint16
	Login            string
	Password         string

	RxBytes Bytes
	TxBytes Bytes

	Comments       SectionComments
	Socks5Comments SectionComments
}

const Socks5TunnelMTU = 1500

var (
	Socks5TunnelAddresses = []netip.Prefix{
		netip.MustParsePrefix("10.42.0.2/32"),
		netip.MustParsePrefix("fdcc:ad94:bacf:61a3::2/128"),
	}
	Socks5TunnelDNS    = []netip.Addr{netip.MustParseAddr("1.1.1.1")}
	Socks5TunnelRoutes = []netip.Prefix{
		netip.MustParsePrefix("0.0.0.0/0"),
		netip.MustParsePrefix("::/0"),
	}
)

func (conf *Config) RoutedPrefixes() []netip.Prefix {
	if conf.IsSocks5() {
		return Socks5TunnelRoutes
	}
	prefixes := make([]netip.Prefix, 0, len(conf.Peers))
	for i := range conf.Peers {
		prefixes = append(prefixes, conf.Peers[i].AllowedIPs...)
	}
	return prefixes
}

func (conf *Config) IsSocks5() bool {
	return conf.Obfuscation != nil && conf.Obfuscation.Mode == ObfuscationModeSocks5
}

func (conf *Config) applySocks5InterfaceDefaults() {
	conf.Interface.Addresses = slices.Clone(Socks5TunnelAddresses)
	conf.Interface.DNS = slices.Clone(Socks5TunnelDNS)
	conf.Interface.MTU = Socks5TunnelMTU
}

func (o *Obfuscation) MediaParams() phobos.MediaParams {
	params := phobos.MediaParams{PayloadType: o.MediaPayloadType, SSRC: o.MediaSSRC}
	if o.MediaClock > 0 {
		params.TimestampStep = uint16(90000 / uint32(o.MediaClock))
	}
	return params
}

type Interface struct {
	PrivateKey Key
	Addresses  []netip.Prefix
	ListenPort uint16
	MTU        uint16
	DNS        []netip.Addr
	DNSSearch  []string
	PreUp      string
	PostUp     string
	PreDown    string
	PostDown   string
	TableOff   bool

	Comments SectionComments
}

type Peer struct {
	PublicKey           Key
	PresharedKey        Key
	AllowedIPs          []netip.Prefix
	Endpoint            Endpoint
	PersistentKeepalive uint16

	RxBytes           Bytes
	TxBytes           Bytes
	LastHandshakeTime HandshakeTime

	Obfuscation *Obfuscation

	Comments SectionComments
}

type Comments struct {
	Before []string
	Suffix string
}

type SectionComments struct {
	Header Comments
	Lines  map[string]Comments
}

func (conf *Config) IntersectsWith(other *Config) bool {
	allRoutes := make(map[netip.Prefix]bool, len(conf.Interface.Addresses)*2+len(conf.Peers)*3)
	for _, a := range conf.Interface.Addresses {
		allRoutes[netip.PrefixFrom(a.Addr(), a.Addr().BitLen())] = true
		allRoutes[a.Masked()] = true
	}
	for i := range conf.Peers {
		for _, a := range conf.Peers[i].AllowedIPs {
			allRoutes[a.Masked()] = true
		}
	}
	for _, a := range other.Interface.Addresses {
		if allRoutes[netip.PrefixFrom(a.Addr(), a.Addr().BitLen())] {
			return true
		}
		if allRoutes[a.Masked()] {
			return true
		}
	}
	for i := range other.Peers {
		for _, a := range other.Peers[i].AllowedIPs {
			if allRoutes[a.Masked()] {
				return true
			}
		}
	}
	return false
}

func (e *Endpoint) String() string {
	if strings.IndexByte(e.Host, ':') != -1 {
		return fmt.Sprintf("[%s]:%d", e.Host, e.Port)
	}
	return fmt.Sprintf("%s:%d", e.Host, e.Port)
}

func (e *Endpoint) IsEmpty() bool {
	return len(e.Host) == 0
}

func (k *Key) String() string {
	return base64.StdEncoding.EncodeToString(k[:])
}

func (k *Key) IsZero() bool {
	var zeros Key
	return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
}

func (k *Key) Public() *Key {
	var p [KeyLength]byte
	curve25519.ScalarBaseMult(&p, (*[KeyLength]byte)(k))
	return (*Key)(&p)
}

func NewPresharedKey() (*Key, error) {
	var k [KeyLength]byte
	_, err := rand.Read(k[:])
	if err != nil {
		return nil, err
	}
	return (*Key)(&k), nil
}

func NewPrivateKey() (*Key, error) {
	k, err := NewPresharedKey()
	if err != nil {
		return nil, err
	}
	k[0] &= 248
	k[31] = (k[31] & 127) | 64
	return k, nil
}

func NewPrivateKeyFromString(b64 string) (*Key, error) {
	return parseKeyBase64(b64)
}

func (t HandshakeTime) IsEmpty() bool {
	return t == HandshakeTime(0)
}

func (t HandshakeTime) String() string {
	u := time.Unix(0, 0).Add(time.Duration(t)).Unix()
	n := time.Now().Unix()
	if u == n {
		return l18n.Sprintf("Now")
	} else if u > n {
		return l18n.Sprintf("System clock wound backward!")
	}
	left := n - u
	years := left / (365 * 24 * 60 * 60)
	left = left % (365 * 24 * 60 * 60)
	days := left / (24 * 60 * 60)
	left = left % (24 * 60 * 60)
	hours := left / (60 * 60)
	left = left % (60 * 60)
	minutes := left / 60
	seconds := left % 60
	s := make([]string, 0, 5)
	if years > 0 {
		s = append(s, l18n.Sprintf("%d year(s)", years))
	}
	if days > 0 {
		s = append(s, l18n.Sprintf("%d day(s)", days))
	}
	if hours > 0 {
		s = append(s, l18n.Sprintf("%d hour(s)", hours))
	}
	if minutes > 0 {
		s = append(s, l18n.Sprintf("%d minute(s)", minutes))
	}
	if seconds > 0 {
		s = append(s, l18n.Sprintf("%d second(s)", seconds))
	}
	timestamp := strings.Join(s, l18n.UnitSeparator())
	return l18n.Sprintf("%s ago", timestamp)
}

func (b Bytes) String() string {
	if b < 1024 {
		return l18n.Sprintf("%d\u00a0B", b)
	} else if b < 1024*1024 {
		return l18n.Sprintf("%.2f\u00a0KiB", float64(b)/1024)
	} else if b < 1024*1024*1024 {
		return l18n.Sprintf("%.2f\u00a0MiB", float64(b)/(1024*1024))
	} else if b < 1024*1024*1024*1024 {
		return l18n.Sprintf("%.2f\u00a0GiB", float64(b)/(1024*1024*1024))
	}
	return l18n.Sprintf("%.2f\u00a0TiB", float64(b)/(1024*1024*1024)/1024)
}

func (conf *Config) DeduplicateNetworkEntries() {
	m := make(map[string]bool, len(conf.Interface.Addresses))
	i := 0
	for _, addr := range conf.Interface.Addresses {
		s := addr.String()
		if m[s] {
			continue
		}
		m[s] = true
		conf.Interface.Addresses[i] = addr
		i++
	}
	conf.Interface.Addresses = conf.Interface.Addresses[:i]

	m = make(map[string]bool, len(conf.Interface.DNS))
	i = 0
	for _, addr := range conf.Interface.DNS {
		s := addr.String()
		if m[s] {
			continue
		}
		m[s] = true
		conf.Interface.DNS[i] = addr
		i++
	}
	conf.Interface.DNS = conf.Interface.DNS[:i]

	for peerIdx := range conf.Peers {
		peer := &conf.Peers[peerIdx]
		m = make(map[string]bool, len(peer.AllowedIPs))
		i = 0
		for _, addr := range peer.AllowedIPs {
			s := addr.String()
			if m[s] {
				continue
			}
			m[s] = true
			peer.AllowedIPs[i] = addr
			i++
		}
		peer.AllowedIPs = peer.AllowedIPs[:i]
	}
}

func (o *Obfuscation) redact() {
	o.Key = ""
	o.Login = ""
	o.Password = ""
	o.Comments = SectionComments{}
	o.Socks5Comments = SectionComments{}
}

func (conf *Config) Redact() {
	conf.Interface.PrivateKey = Key{}
	conf.Interface.PreUp = ""
	conf.Interface.PostUp = ""
	conf.Interface.PreDown = ""
	conf.Interface.PostDown = ""
	conf.Interface.Comments = SectionComments{}
	for i := range conf.Peers {
		conf.Peers[i].PublicKey = Key{}
		binary.LittleEndian.PutUint64(conf.Peers[i].PublicKey[:8], uint64(i))
		conf.Peers[i].PresharedKey = Key{}
		conf.Peers[i].Comments = SectionComments{}
		if o := conf.Peers[i].Obfuscation; o != nil {
			o.redact()
		}
	}
	if conf.Obfuscation != nil {
		conf.Obfuscation.redact()
	}
	conf.TrailingComments = nil
}