/* 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
}