mirror of
https://git.zx2c4.com/wireguard-go
synced 2024-11-15 01:05:15 +01:00
wgcfg: remove for now
Going to develop this on a branch and bring it in all at once. Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
parent
3636c2ec12
commit
eb897a7dd8
@ -1,78 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// Package wgcfg has types and a parser for representing WireGuard config.
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config is a wireguard configuration.
|
||||
type Config struct {
|
||||
Name string
|
||||
PrivateKey PrivateKey
|
||||
Addresses []CIDR
|
||||
ListenPort uint16
|
||||
MTU uint16
|
||||
DNS []IP
|
||||
Peers []Peer
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
PublicKey PublicKey
|
||||
PresharedKey SymmetricKey
|
||||
AllowedIPs []CIDR
|
||||
Endpoints []Endpoint
|
||||
PersistentKeepalive uint16
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
Host string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func (e *Endpoint) String() string {
|
||||
if strings.IndexByte(e.Host, ':') > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of Config.
|
||||
// The result aliases no memory with the original.
|
||||
func (cfg Config) Copy() Config {
|
||||
res := cfg
|
||||
if res.Addresses != nil {
|
||||
res.Addresses = append([]CIDR{}, res.Addresses...)
|
||||
}
|
||||
if res.DNS != nil {
|
||||
res.DNS = append([]IP{}, res.DNS...)
|
||||
}
|
||||
peers := make([]Peer, 0, len(res.Peers))
|
||||
for _, peer := range res.Peers {
|
||||
peers = append(peers, peer.Copy())
|
||||
}
|
||||
res.Peers = peers
|
||||
return res
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of Peer.
|
||||
// The result aliases no memory with the original.
|
||||
func (peer Peer) Copy() Peer {
|
||||
res := peer
|
||||
if res.AllowedIPs != nil {
|
||||
res.AllowedIPs = append([]CIDR{}, res.AllowedIPs...)
|
||||
}
|
||||
if res.Endpoints != nil {
|
||||
res.Endpoints = append([]Endpoint{}, res.Endpoints...)
|
||||
}
|
||||
return res
|
||||
}
|
152
wgcfg/ip.go
152
wgcfg/ip.go
@ -1,152 +0,0 @@
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
)
|
||||
|
||||
// IP is an IPv4 or an IPv6 address.
|
||||
//
|
||||
// Internally the address is always represented in its IPv6 form.
|
||||
// IPv4 addresses use the IPv4-in-IPv6 syntax.
|
||||
type IP struct {
|
||||
Addr [16]byte
|
||||
}
|
||||
|
||||
func (ip IP) String() string { return net.IP(ip.Addr[:]).String() }
|
||||
|
||||
// IP converts ip into a standard library net.IP.
|
||||
func (ip IP) IP() net.IP { return net.IP(ip.Addr[:]) }
|
||||
|
||||
// Is6 reports whether ip is an IPv6 address.
|
||||
func (ip IP) Is6() bool { return !ip.Is4() }
|
||||
|
||||
// Is4 reports whether ip is an IPv4 address.
|
||||
func (ip IP) Is4() bool {
|
||||
return ip.Addr[0] == 0 && ip.Addr[1] == 0 &&
|
||||
ip.Addr[2] == 0 && ip.Addr[3] == 0 &&
|
||||
ip.Addr[4] == 0 && ip.Addr[5] == 0 &&
|
||||
ip.Addr[6] == 0 && ip.Addr[7] == 0 &&
|
||||
ip.Addr[8] == 0 && ip.Addr[9] == 0 &&
|
||||
ip.Addr[10] == 0xff && ip.Addr[11] == 0xff
|
||||
}
|
||||
|
||||
// To4 returns either a 4 byte slice for an IPv4 address, or nil if
|
||||
// it's not IPv4.
|
||||
func (ip IP) To4() []byte {
|
||||
if ip.Is4() {
|
||||
return ip.Addr[12:16]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Equal reports whether ip == x.
|
||||
func (ip IP) Equal(x IP) bool {
|
||||
return ip == x
|
||||
}
|
||||
|
||||
func (ip IP) MarshalText() ([]byte, error) {
|
||||
return []byte(ip.String()), nil
|
||||
}
|
||||
|
||||
func (ip *IP) UnmarshalText(text []byte) error {
|
||||
parsedIP, ok := ParseIP(string(text))
|
||||
if !ok {
|
||||
return fmt.Errorf("wgcfg.IP: UnmarshalText: bad IP address %q", text)
|
||||
}
|
||||
*ip = parsedIP
|
||||
return nil
|
||||
}
|
||||
|
||||
func IPv4(b0, b1, b2, b3 byte) (ip IP) {
|
||||
ip.Addr[10], ip.Addr[11] = 0xff, 0xff // IPv4-in-IPv6 prefix
|
||||
ip.Addr[12] = b0
|
||||
ip.Addr[13] = b1
|
||||
ip.Addr[14] = b2
|
||||
ip.Addr[15] = b3
|
||||
return ip
|
||||
}
|
||||
|
||||
// ParseIP parses the string representation of an address into an IP.
|
||||
//
|
||||
// It accepts IPv4 notation such as "1.2.3.4" and IPv6 notation like ""::0".
|
||||
// The ok result reports whether s was a valid IP and ip is valid.
|
||||
func ParseIP(s string) (ip IP, ok bool) {
|
||||
netIP := net.ParseIP(s)
|
||||
if netIP == nil {
|
||||
return IP{}, false
|
||||
}
|
||||
copy(ip.Addr[:], netIP.To16())
|
||||
return ip, true
|
||||
}
|
||||
|
||||
// CIDR is a compact IP address and subnet mask.
|
||||
type CIDR struct {
|
||||
IP IP
|
||||
Mask uint8 // 0-32 for IsIPv4, 4-128 for IsIPv6
|
||||
}
|
||||
|
||||
// ParseCIDR parses CIDR notation into a CIDR type.
|
||||
// Typical CIDR strings look like "192.168.1.0/24".
|
||||
func ParseCIDR(s string) (CIDR, error) {
|
||||
netIP, netAddr, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return CIDR{}, err
|
||||
}
|
||||
var cidr CIDR
|
||||
copy(cidr.IP.Addr[:], netIP.To16())
|
||||
ones, _ := netAddr.Mask.Size()
|
||||
cidr.Mask = uint8(ones)
|
||||
|
||||
return cidr, nil
|
||||
}
|
||||
|
||||
func (r CIDR) String() string { return r.IPNet().String() }
|
||||
|
||||
func (r CIDR) IPNet() *net.IPNet {
|
||||
bits := 128
|
||||
if r.IP.Is4() {
|
||||
bits = 32
|
||||
}
|
||||
return &net.IPNet{IP: r.IP.IP(), Mask: net.CIDRMask(int(r.Mask), bits)}
|
||||
}
|
||||
|
||||
func (r CIDR) Contains(ip IP) bool {
|
||||
c := int8(r.Mask)
|
||||
i := 0
|
||||
if r.IP.Is4() {
|
||||
i = 12
|
||||
if ip.Is6() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for ; i < 16 && c > 0; i++ {
|
||||
var x uint8
|
||||
if c < 8 {
|
||||
x = 8 - uint8(c)
|
||||
}
|
||||
m := uint8(math.MaxUint8) >> x << x
|
||||
a := r.IP.Addr[i] & m
|
||||
b := ip.Addr[i] & m
|
||||
if a != b {
|
||||
return false
|
||||
}
|
||||
c -= 8
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r CIDR) MarshalText() ([]byte, error) {
|
||||
return []byte(r.String()), nil
|
||||
}
|
||||
|
||||
func (r *CIDR) UnmarshalText(text []byte) error {
|
||||
cidr, err := ParseCIDR(string(text))
|
||||
if err != nil {
|
||||
return fmt.Errorf("wgcfg.CIDR: UnmarshalText: %v", err)
|
||||
}
|
||||
*r = cidr
|
||||
return nil
|
||||
}
|
106
wgcfg/ip_test.go
106
wgcfg/ip_test.go
@ -1,106 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wgcfg_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgcfg"
|
||||
)
|
||||
|
||||
func parseIP(t testing.TB, ipStr string) wgcfg.IP {
|
||||
t.Helper()
|
||||
ip, ok := wgcfg.ParseIP(ipStr)
|
||||
if !ok {
|
||||
t.Fatalf("failed to parse IP: %q", ipStr)
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
func TestCIDRContains(t *testing.T) {
|
||||
t.Run("home router test", func(t *testing.T) {
|
||||
r, err := wgcfg.ParseCIDR("192.168.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip := parseIP(t, "192.168.0.1")
|
||||
if !r.Contains(ip) {
|
||||
t.Fatalf("%q should contain %q", r, ip)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IPv4 outside network", func(t *testing.T) {
|
||||
r, err := wgcfg.ParseCIDR("192.168.0.0/30")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip := parseIP(t, "192.168.0.4")
|
||||
if r.Contains(ip) {
|
||||
t.Fatalf("%q should not contain %q", r, ip)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IPv4 does not contain IPv6", func(t *testing.T) {
|
||||
r, err := wgcfg.ParseCIDR("192.168.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip := parseIP(t, "2001:db8:85a3:0:0:8a2e:370:7334")
|
||||
if r.Contains(ip) {
|
||||
t.Fatalf("%q should not contain %q", r, ip)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IPv6 inside network", func(t *testing.T) {
|
||||
r, err := wgcfg.ParseCIDR("2001:db8:1234::/48")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip := parseIP(t, "2001:db8:1234:0000:0000:0000:0000:0001")
|
||||
if !r.Contains(ip) {
|
||||
t.Fatalf("%q should not contain %q", r, ip)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IPv6 outside network", func(t *testing.T) {
|
||||
r, err := wgcfg.ParseCIDR("2001:db8:1234:0:190b:0:1982::/126")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip := parseIP(t, "2001:db8:1234:0:190b:0:1982:4")
|
||||
if r.Contains(ip) {
|
||||
t.Fatalf("%q should not contain %q", r, ip)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCIDRContainsIPv4(b *testing.B) {
|
||||
b.Run("IPv4", func(b *testing.B) {
|
||||
r, err := wgcfg.ParseCIDR("192.168.1.0/24")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
ip := parseIP(b, "1.2.3.4")
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Contains(ip)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("IPv6", func(b *testing.B) {
|
||||
r, err := wgcfg.ParseCIDR("2001:db8:1234::/48")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
ip := parseIP(b, "2001:db8:1234:0000:0000:0000:0000:0001")
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Contains(ip)
|
||||
}
|
||||
})
|
||||
}
|
182
wgcfg/key.go
182
wgcfg/key.go
@ -1,182 +0,0 @@
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
const KeySize = 32
|
||||
|
||||
// PublicKey is curve25519 key.
|
||||
// It is used by WireGuard to represent public and preshared keys.
|
||||
type PublicKey [KeySize]byte
|
||||
|
||||
func ParseKey(b64 string) (*PublicKey, error) { return parseKeyBase64(base64.StdEncoding, b64) }
|
||||
|
||||
func ParseHexKey(s string) (PublicKey, error) {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return PublicKey{}, &ParseError{"invalid hex key: " + err.Error(), s}
|
||||
}
|
||||
if len(b) != KeySize {
|
||||
return PublicKey{}, &ParseError{fmt.Sprintf("invalid hex key length: %d", len(b)), s}
|
||||
}
|
||||
|
||||
var key PublicKey
|
||||
copy(key[:], b)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func ParsePrivateHexKey(v string) (PrivateKey, error) {
|
||||
k, err := ParseHexKey(v)
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
pk := PrivateKey(k)
|
||||
if pk.IsZero() {
|
||||
// Do not clamp a zero key, pass the zero through
|
||||
// (much like NaN propagation) so that IsZero reports
|
||||
// a useful result.
|
||||
return pk, nil
|
||||
}
|
||||
pk.clamp()
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func (k PublicKey) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) }
|
||||
func (k PublicKey) String() string { return k.ShortString() }
|
||||
func (k PublicKey) HexString() string { return hex.EncodeToString(k[:]) }
|
||||
func (k PublicKey) Equal(k2 PublicKey) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
|
||||
|
||||
func (k *PublicKey) ShortString() string {
|
||||
long := k.Base64()
|
||||
return "[" + long[0:5] + "]"
|
||||
}
|
||||
|
||||
func (k PublicKey) IsZero() bool {
|
||||
var zeros PublicKey
|
||||
return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
|
||||
}
|
||||
|
||||
// PrivateKey is curve25519 key.
|
||||
// It is used by WireGuard to represent private keys.
|
||||
type PrivateKey [KeySize]byte
|
||||
|
||||
// NewPrivateKey generates a new curve25519 secret key.
|
||||
// It conforms to the format described on https://cr.yp.to/ecdh.html.
|
||||
func NewPrivateKey() (pk PrivateKey, err error) {
|
||||
_, err = cryptorand.Read(pk[:])
|
||||
if err != nil {
|
||||
return PrivateKey{}, err
|
||||
}
|
||||
pk.clamp()
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func ParsePrivateKey(b64 string) (*PrivateKey, error) {
|
||||
k, err := parseKeyBase64(base64.StdEncoding, b64)
|
||||
return (*PrivateKey)(k), err
|
||||
}
|
||||
|
||||
func (k *PrivateKey) String() string { return base64.StdEncoding.EncodeToString(k[:]) }
|
||||
func (k *PrivateKey) HexString() string { return hex.EncodeToString(k[:]) }
|
||||
func (k *PrivateKey) Equal(k2 PrivateKey) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
|
||||
|
||||
func (k PrivateKey) IsZero() bool {
|
||||
var zeros PrivateKey
|
||||
return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
|
||||
}
|
||||
|
||||
func (k *PrivateKey) clamp() {
|
||||
k[0] &= 248
|
||||
k[31] = (k[31] & 127) | 64
|
||||
}
|
||||
|
||||
// Public computes the public key matching this curve25519 secret key.
|
||||
func (k PrivateKey) Public() PublicKey {
|
||||
if k.IsZero() {
|
||||
panic("wgcfg: tried to generate public key for a zero key")
|
||||
}
|
||||
var p [KeySize]byte
|
||||
curve25519.ScalarBaseMult(&p, (*[KeySize]byte)(&k))
|
||||
return (PublicKey)(p)
|
||||
}
|
||||
|
||||
func (k PrivateKey) MarshalText() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, `privkey:%x`, k[:])
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (k *PrivateKey) UnmarshalText(b []byte) error {
|
||||
s := string(b)
|
||||
if !strings.HasPrefix(s, `privkey:`) {
|
||||
return errors.New("wgcfg.PrivateKey: UnmarshalText not given a private-key string")
|
||||
}
|
||||
s = strings.TrimPrefix(s, `privkey:`)
|
||||
key, err := ParseHexKey(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wgcfg.PrivateKey: UnmarshalText: %v", err)
|
||||
}
|
||||
copy(k[:], key[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k PrivateKey) SharedSecret(pub PublicKey) (ss [KeySize]byte) {
|
||||
apk := (*[KeySize]byte)(&pub)
|
||||
ask := (*[KeySize]byte)(&k)
|
||||
curve25519.ScalarMult(&ss, ask, apk)
|
||||
return ss
|
||||
}
|
||||
|
||||
func parseKeyBase64(enc *base64.Encoding, s string) (*PublicKey, error) {
|
||||
k, err := enc.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, &ParseError{"Invalid key: " + err.Error(), s}
|
||||
}
|
||||
if len(k) != KeySize {
|
||||
return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
|
||||
}
|
||||
var key PublicKey
|
||||
copy(key[:], k)
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func ParseSymmetricKey(b64 string) (SymmetricKey, error) {
|
||||
k, err := parseKeyBase64(base64.StdEncoding, b64)
|
||||
if err != nil {
|
||||
return SymmetricKey{}, err
|
||||
}
|
||||
return SymmetricKey(*k), nil
|
||||
}
|
||||
|
||||
func ParseSymmetricHexKey(s string) (SymmetricKey, error) {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return SymmetricKey{}, &ParseError{"invalid symmetric hex key: " + err.Error(), s}
|
||||
}
|
||||
if len(b) != chacha20poly1305.KeySize {
|
||||
return SymmetricKey{}, &ParseError{fmt.Sprintf("invalid symmetric hex key length: %d", len(b)), s}
|
||||
}
|
||||
var key SymmetricKey
|
||||
copy(key[:], b)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// SymmetricKey is a 32-byte value used as a pre-shared key.
|
||||
type SymmetricKey [chacha20poly1305.KeySize]byte
|
||||
|
||||
func (k SymmetricKey) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) }
|
||||
func (k SymmetricKey) String() string { return "sym:" + k.Base64()[:8] }
|
||||
func (k SymmetricKey) HexString() string { return hex.EncodeToString(k[:]) }
|
||||
func (k SymmetricKey) IsZero() bool { return k.Equal(SymmetricKey{}) }
|
||||
func (k SymmetricKey) Equal(k2 SymmetricKey) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
|
@ -1,33 +0,0 @@
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyBasics(t *testing.T) {
|
||||
pk1, err := NewPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k1 := pk1.Public()
|
||||
|
||||
t.Run("second key", func(t *testing.T) {
|
||||
// Different keys should be different.
|
||||
pk2, err := NewPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k2 := pk2.Public()
|
||||
if bytes.Equal(k1[:], k2[:]) {
|
||||
t.Fatalf("k1 %v == k2 %v", k1[:], k2[:])
|
||||
}
|
||||
// Check for obvious comparables to make sure we are not generating bad strings somewhere.
|
||||
if b1, b2 := k1.String(), k2.String(); b1 == b2 {
|
||||
t.Fatalf("base64-encoded keys match: %s, %s", b1, b2)
|
||||
}
|
||||
if pub1, pub2 := pk1.Public().String(), pk2.Public().String(); pub1 == pub2 {
|
||||
t.Fatalf("base64-encoded public keys match: %s, %s", pub1, pub2)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reservedNames = []string{
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
}
|
||||
|
||||
const specialChars = "/\\<>:\"|?*\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00"
|
||||
|
||||
var allowedNameFormat *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
allowedNameFormat = regexp.MustCompile("^[a-zA-Z0-9_=+.-]{1,32}$")
|
||||
}
|
||||
|
||||
func isReserved(name string) bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, reserved := range reservedNames {
|
||||
if strings.EqualFold(name, reserved) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasSpecialChars(name string) bool {
|
||||
return strings.ContainsAny(name, specialChars)
|
||||
}
|
||||
|
||||
func TunnelNameIsValid(name string) bool {
|
||||
// Aside from our own restrictions, let's impose the Windows restrictions first
|
||||
if isReserved(name) || hasSpecialChars(name) {
|
||||
return false
|
||||
}
|
||||
return allowedNameFormat.MatchString(name)
|
||||
}
|
397
wgcfg/parser.go
397
wgcfg/parser.go
@ -1,397 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
why string
|
||||
offender string
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
return fmt.Sprintf("%s: ‘%s’", e.why, e.offender)
|
||||
}
|
||||
|
||||
func parseEndpoints(s string) ([]Endpoint, error) {
|
||||
var eps []Endpoint
|
||||
vals := strings.Split(s, ",")
|
||||
for _, val := range vals {
|
||||
e, err := parseEndpoint(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eps = append(eps, *e)
|
||||
}
|
||||
return eps, nil
|
||||
}
|
||||
|
||||
func parseEndpoint(s string) (*Endpoint, error) {
|
||||
i := strings.LastIndexByte(s, ':')
|
||||
if i < 0 {
|
||||
return nil, &ParseError{"Missing port from endpoint", s}
|
||||
}
|
||||
host, portStr := s[:i], s[i+1:]
|
||||
if len(host) < 1 {
|
||||
return nil, &ParseError{"Invalid endpoint host", host}
|
||||
}
|
||||
port, err := parsePort(portStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostColon := strings.IndexByte(host, ':')
|
||||
if host[0] == '[' || host[len(host)-1] == ']' || hostColon > 0 {
|
||||
err := &ParseError{"Brackets must contain an IPv6 address", host}
|
||||
if len(host) > 3 && host[0] == '[' && host[len(host)-1] == ']' && hostColon > 0 {
|
||||
maybeV6 := net.ParseIP(host[1 : len(host)-1])
|
||||
if maybeV6 == nil || len(maybeV6) != net.IPv6len {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
return &Endpoint{host, uint16(port)}, nil
|
||||
}
|
||||
|
||||
func parseMTU(s string) (uint16, error) {
|
||||
m, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if m < 576 || m > 65535 {
|
||||
return 0, &ParseError{"Invalid MTU", s}
|
||||
}
|
||||
return uint16(m), nil
|
||||
}
|
||||
|
||||
func parsePort(s string) (uint16, error) {
|
||||
m, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if m < 0 || m > 65535 {
|
||||
return 0, &ParseError{"Invalid port", s}
|
||||
}
|
||||
return uint16(m), nil
|
||||
}
|
||||
|
||||
func parsePersistentKeepalive(s string) (uint16, error) {
|
||||
if s == "off" {
|
||||
return 0, nil
|
||||
}
|
||||
m, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if m < 0 || m > 65535 {
|
||||
return 0, &ParseError{"Invalid persistent keepalive", s}
|
||||
}
|
||||
return uint16(m), nil
|
||||
}
|
||||
|
||||
func parseKeyHex(s string) (*PublicKey, error) {
|
||||
k, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, &ParseError{"Invalid key: " + err.Error(), s}
|
||||
}
|
||||
if len(k) != KeySize {
|
||||
return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
|
||||
}
|
||||
var key PublicKey
|
||||
copy(key[:], k)
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func parseBytesOrStamp(s string) (uint64, error) {
|
||||
b, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, &ParseError{"Number must be a number between 0 and 2^64-1: " + err.Error(), s}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func splitList(s string) ([]string, error) {
|
||||
var out []string
|
||||
for _, split := range strings.Split(s, ",") {
|
||||
trim := strings.TrimSpace(split)
|
||||
if len(trim) == 0 {
|
||||
return nil, &ParseError{"Two commas in a row", s}
|
||||
}
|
||||
out = append(out, trim)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type parserState int
|
||||
|
||||
const (
|
||||
inInterfaceSection parserState = iota
|
||||
inPeerSection
|
||||
notInASection
|
||||
)
|
||||
|
||||
func (c *Config) maybeAddPeer(p *Peer) {
|
||||
if p != nil {
|
||||
c.Peers = append(c.Peers, *p)
|
||||
}
|
||||
}
|
||||
|
||||
func FromWgQuick(s string, name string) (*Config, error) {
|
||||
if !TunnelNameIsValid(name) {
|
||||
return nil, &ParseError{"Tunnel name is not valid", name}
|
||||
}
|
||||
lines := strings.Split(s, "\n")
|
||||
parserState := notInASection
|
||||
conf := Config{Name: name}
|
||||
sawPrivateKey := false
|
||||
var peer *Peer
|
||||
for _, line := range lines {
|
||||
pound := strings.IndexByte(line, '#')
|
||||
if pound >= 0 {
|
||||
line = line[:pound]
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
lineLower := strings.ToLower(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if lineLower == "[interface]" {
|
||||
conf.maybeAddPeer(peer)
|
||||
parserState = inInterfaceSection
|
||||
continue
|
||||
}
|
||||
if lineLower == "[peer]" {
|
||||
conf.maybeAddPeer(peer)
|
||||
peer = &Peer{}
|
||||
parserState = inPeerSection
|
||||
continue
|
||||
}
|
||||
if parserState == notInASection {
|
||||
return nil, &ParseError{"Line must occur in a section", line}
|
||||
}
|
||||
equals := strings.IndexByte(line, '=')
|
||||
if equals < 0 {
|
||||
return nil, &ParseError{"Invalid config key is missing an equals separator", line}
|
||||
}
|
||||
key, val := strings.TrimSpace(lineLower[:equals]), strings.TrimSpace(line[equals+1:])
|
||||
if len(val) == 0 {
|
||||
return nil, &ParseError{"Key must have a value", line}
|
||||
}
|
||||
if parserState == inInterfaceSection {
|
||||
switch key {
|
||||
case "privatekey":
|
||||
k, err := ParseKey(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.PrivateKey = PrivateKey(*k)
|
||||
sawPrivateKey = true
|
||||
case "listenport":
|
||||
p, err := parsePort(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.ListenPort = p
|
||||
case "mtu":
|
||||
m, err := parseMTU(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.MTU = m
|
||||
case "address":
|
||||
addresses, err := splitList(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, address := range addresses {
|
||||
a, err := ParseCIDR(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.Addresses = append(conf.Addresses, a)
|
||||
}
|
||||
case "dns":
|
||||
addresses, err := splitList(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, address := range addresses {
|
||||
a, ok := ParseIP(address)
|
||||
if !ok {
|
||||
return nil, &ParseError{"Invalid IP address", address}
|
||||
}
|
||||
conf.DNS = append(conf.DNS, a)
|
||||
}
|
||||
default:
|
||||
return nil, &ParseError{"Invalid key for [Interface] section", key}
|
||||
}
|
||||
} else if parserState == inPeerSection {
|
||||
switch key {
|
||||
case "publickey":
|
||||
k, err := ParseKey(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PublicKey = *k
|
||||
case "presharedkey":
|
||||
k, err := ParseKey(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PresharedKey = SymmetricKey(*k)
|
||||
case "allowedips":
|
||||
addresses, err := splitList(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, address := range addresses {
|
||||
a, err := ParseCIDR(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.AllowedIPs = append(peer.AllowedIPs, a)
|
||||
}
|
||||
case "persistentkeepalive":
|
||||
p, err := parsePersistentKeepalive(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PersistentKeepalive = p
|
||||
case "endpoint":
|
||||
eps, err := parseEndpoints(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.Endpoints = eps
|
||||
default:
|
||||
return nil, &ParseError{"Invalid key for [Peer] section", key}
|
||||
}
|
||||
}
|
||||
}
|
||||
conf.maybeAddPeer(peer)
|
||||
|
||||
if !sawPrivateKey {
|
||||
return nil, &ParseError{"An interface must have a private key", "[none specified]"}
|
||||
}
|
||||
for _, p := range conf.Peers {
|
||||
if p.PublicKey.IsZero() {
|
||||
return nil, &ParseError{"All peers must have public keys", "[none specified]"}
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
// TODO(apenwarr): This is incompatibe with current Device.IpcSetOperation.
|
||||
// It duplicates all the parser stuff in there, but is missing some
|
||||
// keywords. Nothing useful seems to need it anymore.
|
||||
func Broken_FromUAPI(s string, existingConfig *Config) (*Config, error) {
|
||||
lines := strings.Split(s, "\n")
|
||||
parserState := inInterfaceSection
|
||||
conf := Config{
|
||||
Name: existingConfig.Name,
|
||||
Addresses: existingConfig.Addresses,
|
||||
DNS: existingConfig.DNS,
|
||||
MTU: existingConfig.MTU,
|
||||
}
|
||||
var peer *Peer
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
equals := strings.IndexByte(line, '=')
|
||||
if equals < 0 {
|
||||
return nil, &ParseError{"Invalid config key is missing an equals separator", line}
|
||||
}
|
||||
key, val := line[:equals], line[equals+1:]
|
||||
if len(val) == 0 {
|
||||
return nil, &ParseError{"Key must have a value", line}
|
||||
}
|
||||
switch key {
|
||||
case "public_key":
|
||||
conf.maybeAddPeer(peer)
|
||||
peer = &Peer{}
|
||||
parserState = inPeerSection
|
||||
case "errno":
|
||||
if val == "0" {
|
||||
continue
|
||||
} else {
|
||||
return nil, &ParseError{"Error in getting configuration", val}
|
||||
}
|
||||
}
|
||||
if parserState == inInterfaceSection {
|
||||
switch key {
|
||||
case "private_key":
|
||||
k, err := parseKeyHex(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.PrivateKey = PrivateKey(*k)
|
||||
case "listen_port":
|
||||
p, err := parsePort(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.ListenPort = p
|
||||
case "fwmark":
|
||||
// Ignored for now.
|
||||
|
||||
default:
|
||||
return nil, &ParseError{"Invalid key for interface section", key}
|
||||
}
|
||||
} else if parserState == inPeerSection {
|
||||
switch key {
|
||||
case "public_key":
|
||||
k, err := parseKeyHex(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PublicKey = *k
|
||||
case "preshared_key":
|
||||
k, err := parseKeyHex(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PresharedKey = SymmetricKey(*k)
|
||||
case "protocol_version":
|
||||
if val != "1" {
|
||||
return nil, &ParseError{"Protocol version must be 1", val}
|
||||
}
|
||||
case "allowed_ip":
|
||||
a, err := ParseCIDR(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.AllowedIPs = append(peer.AllowedIPs, a)
|
||||
case "persistent_keepalive_interval":
|
||||
p, err := parsePersistentKeepalive(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.PersistentKeepalive = p
|
||||
case "endpoint":
|
||||
eps, err := parseEndpoints(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer.Endpoints = eps
|
||||
default:
|
||||
return nil, &ParseError{"Invalid key for peer section", key}
|
||||
}
|
||||
}
|
||||
}
|
||||
conf.maybeAddPeer(peer)
|
||||
|
||||
return &conf, nil
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testInput = `
|
||||
[Interface]
|
||||
Address = 10.192.122.1/24
|
||||
Address = 10.10.0.1/16
|
||||
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
||||
ListenPort = 51820 #comments don't matter
|
||||
|
||||
[Peer]
|
||||
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
|
||||
Endpoint = 192.95.5.67:1234
|
||||
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
|
||||
|
||||
[Peer]
|
||||
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
|
||||
Endpoint = [2607:5300:60:6b0::c05f:543]:2468
|
||||
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
|
||||
PersistentKeepalive = 100
|
||||
|
||||
[Peer]
|
||||
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
|
||||
PresharedKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
|
||||
Endpoint = test.wireguard.com:18981
|
||||
AllowedIPs = 10.10.10.230/32`
|
||||
|
||||
func noError(t *testing.T, err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
t.Errorf("Error at %s:%d: %#v", fn, line, err)
|
||||
return false
|
||||
}
|
||||
|
||||
func equal(t *testing.T, expected, actual interface{}) bool {
|
||||
if reflect.DeepEqual(expected, actual) {
|
||||
return true
|
||||
}
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
t.Errorf("Failed equals at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected)
|
||||
return false
|
||||
}
|
||||
func lenTest(t *testing.T, actualO interface{}, expected int) bool {
|
||||
actual := reflect.ValueOf(actualO).Len()
|
||||
if reflect.DeepEqual(expected, actual) {
|
||||
return true
|
||||
}
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
t.Errorf("Wrong length at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected)
|
||||
return false
|
||||
}
|
||||
func contains(t *testing.T, list, element interface{}) bool {
|
||||
listValue := reflect.ValueOf(list)
|
||||
for i := 0; i < listValue.Len(); i++ {
|
||||
if reflect.DeepEqual(listValue.Index(i).Interface(), element) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
t.Errorf("Error %s:%d\nelement not found: %#v", fn, line, element)
|
||||
return false
|
||||
}
|
||||
|
||||
func TestFromWgQuick(t *testing.T) {
|
||||
conf, err := FromWgQuick(testInput, "test")
|
||||
if noError(t, err) {
|
||||
|
||||
lenTest(t, conf.Addresses, 2)
|
||||
contains(t, conf.Addresses, CIDR{IPv4(10, 10, 0, 1), 16})
|
||||
contains(t, conf.Addresses, CIDR{IPv4(10, 192, 122, 1), 24})
|
||||
equal(t, "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=", conf.PrivateKey.String())
|
||||
equal(t, uint16(51820), conf.ListenPort)
|
||||
|
||||
lenTest(t, conf.Peers, 3)
|
||||
lenTest(t, conf.Peers[0].AllowedIPs, 2)
|
||||
equal(t, Endpoint{Host: "192.95.5.67", Port: 1234}, conf.Peers[0].Endpoints[0])
|
||||
equal(t, "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=", conf.Peers[0].PublicKey.Base64())
|
||||
|
||||
lenTest(t, conf.Peers[1].AllowedIPs, 2)
|
||||
equal(t, Endpoint{Host: "2607:5300:60:6b0::c05f:543", Port: 2468}, conf.Peers[1].Endpoints[0])
|
||||
equal(t, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=", conf.Peers[1].PublicKey.Base64())
|
||||
equal(t, uint16(100), conf.Peers[1].PersistentKeepalive)
|
||||
|
||||
lenTest(t, conf.Peers[2].AllowedIPs, 1)
|
||||
equal(t, Endpoint{Host: "test.wireguard.com", Port: 18981}, conf.Peers[2].Endpoints[0])
|
||||
equal(t, "gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=", conf.Peers[2].PublicKey.Base64())
|
||||
equal(t, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=", conf.Peers[2].PresharedKey.Base64())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEndpoint(t *testing.T) {
|
||||
_, err := parseEndpoint("[192.168.42.0:]:51880")
|
||||
if err == nil {
|
||||
t.Error("Error was expected")
|
||||
}
|
||||
e, err := parseEndpoint("192.168.42.0:51880")
|
||||
if noError(t, err) {
|
||||
equal(t, "192.168.42.0", e.Host)
|
||||
equal(t, uint16(51880), e.Port)
|
||||
}
|
||||
e, err = parseEndpoint("test.wireguard.com:18981")
|
||||
if noError(t, err) {
|
||||
equal(t, "test.wireguard.com", e.Host)
|
||||
equal(t, uint16(18981), e.Port)
|
||||
}
|
||||
e, err = parseEndpoint("[2607:5300:60:6b0::c05f:543]:2468")
|
||||
if noError(t, err) {
|
||||
equal(t, "2607:5300:60:6b0::c05f:543", e.Host)
|
||||
equal(t, uint16(2468), e.Port)
|
||||
}
|
||||
_, err = parseEndpoint("[::::::invalid:18981")
|
||||
if err == nil {
|
||||
t.Error("Error was expected")
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wgcfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (conf *Config) ToUAPI() (string, error) {
|
||||
output := new(strings.Builder)
|
||||
fmt.Fprintf(output, "private_key=%s\n", conf.PrivateKey.HexString())
|
||||
|
||||
if conf.ListenPort > 0 {
|
||||
fmt.Fprintf(output, "listen_port=%d\n", conf.ListenPort)
|
||||
}
|
||||
|
||||
output.WriteString("replace_peers=true\n")
|
||||
|
||||
for _, peer := range conf.Peers {
|
||||
fmt.Fprintf(output, "public_key=%s\n", peer.PublicKey.HexString())
|
||||
fmt.Fprintf(output, "protocol_version=1\n")
|
||||
fmt.Fprintf(output, "replace_allowed_ips=true\n")
|
||||
|
||||
if !peer.PresharedKey.IsZero() {
|
||||
fmt.Fprintf(output, "preshared_key = %s\n", peer.PresharedKey.String())
|
||||
}
|
||||
|
||||
if len(peer.AllowedIPs) > 0 {
|
||||
for _, address := range peer.AllowedIPs {
|
||||
fmt.Fprintf(output, "allowed_ip=%s\n", address.String())
|
||||
}
|
||||
}
|
||||
|
||||
if len(peer.Endpoints) > 0 {
|
||||
var reps []string
|
||||
for _, ep := range peer.Endpoints {
|
||||
ips, err := net.LookupIP(ep.Host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var ip net.IP
|
||||
for _, iterip := range ips {
|
||||
if ip4 := iterip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
break
|
||||
}
|
||||
if ip == nil {
|
||||
ip = iterip
|
||||
}
|
||||
}
|
||||
if ip == nil {
|
||||
return "", fmt.Errorf("unable to resolve IP address of endpoint %q (%v)", ep.Host, ips)
|
||||
}
|
||||
resolvedEndpoint := Endpoint{ip.String(), ep.Port}
|
||||
reps = append(reps, resolvedEndpoint.String())
|
||||
}
|
||||
fmt.Fprintf(output, "endpoint=%s\n", strings.Join(reps, ","))
|
||||
} else {
|
||||
fmt.Fprint(output, "endpoint=\n")
|
||||
}
|
||||
|
||||
// Note: this needs to come *after* endpoint definitions,
|
||||
// because setting it will trigger a handshake to all
|
||||
// already-defined endpoints.
|
||||
fmt.Fprintf(output, "persistent_keepalive_interval=%d\n", peer.PersistentKeepalive)
|
||||
}
|
||||
return output.String(), nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user