mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-18 20:49:56 +00:00
* [iface] Drop redundant device dump in kernel configure() wgctrl.ConfigureDevice already returns an error when the interface is missing, so the preceding wg.Device() existence check is redundant. That check dumps the entire device (all peers) on every configure() call, making it O(peers) per call and turning bulk peer insertion into O(peers^2): inserting N peers one by one re-parsed the whole growing peer list N times. Removing it keeps each peer write constant-time regardless of how many peers are already configured. * [iface] Cache WireGuard stats to collapse per-peer device dumps Each peer runs a WGWatcher that polls GetStats(), and every call dumps the whole device, so with N peers the watchers perform O(N) full dumps per poll cycle (O(N^2) work) while each keeps only its own peer's entry. Wrap the kernel and userspace configurer GetStats() in a short-TTL cache with singleflight: the staggered per-peer calls share a single device dump per window and concurrent misses collapse into one dump. The kernel and userspace WireGuard APIs have no per-peer stats query (a get always returns the whole device), so a shared cached snapshot avoids the repeated full dumps. * Ignore .claude directory
644 lines
16 KiB
Go
644 lines
16 KiB
Go
package configurer
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.zx2c4.com/wireguard/device"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
|
|
"github.com/netbirdio/netbird/client/iface/bind"
|
|
nbnet "github.com/netbirdio/netbird/client/net"
|
|
"github.com/netbirdio/netbird/monotime"
|
|
)
|
|
|
|
const (
|
|
privateKey = "private_key"
|
|
ipcKeyLastHandshakeTimeSec = "last_handshake_time_sec"
|
|
ipcKeyTxBytes = "tx_bytes"
|
|
ipcKeyRxBytes = "rx_bytes"
|
|
allowedIP = "allowed_ip"
|
|
endpoint = "endpoint"
|
|
fwmark = "fwmark"
|
|
listenPort = "listen_port"
|
|
publicKey = "public_key"
|
|
presharedKey = "preshared_key"
|
|
)
|
|
|
|
var ErrAllowedIPNotFound = fmt.Errorf("allowed IP not found")
|
|
|
|
type WGUSPConfigurer struct {
|
|
device *device.Device
|
|
deviceName string
|
|
activityRecorder *bind.ActivityRecorder
|
|
statsCache *statsCache
|
|
|
|
uapiListener net.Listener
|
|
}
|
|
|
|
func NewUSPConfigurer(device *device.Device, deviceName string, activityRecorder *bind.ActivityRecorder) *WGUSPConfigurer {
|
|
wgCfg := &WGUSPConfigurer{
|
|
device: device,
|
|
deviceName: deviceName,
|
|
activityRecorder: activityRecorder,
|
|
}
|
|
wgCfg.statsCache = newStatsCache(statsCacheTTL, wgCfg.fetchStats)
|
|
wgCfg.startUAPI()
|
|
return wgCfg
|
|
}
|
|
|
|
func NewUSPConfigurerNoUAPI(device *device.Device, deviceName string, activityRecorder *bind.ActivityRecorder) *WGUSPConfigurer {
|
|
wgCfg := &WGUSPConfigurer{
|
|
device: device,
|
|
deviceName: deviceName,
|
|
activityRecorder: activityRecorder,
|
|
}
|
|
wgCfg.statsCache = newStatsCache(statsCacheTTL, wgCfg.fetchStats)
|
|
return wgCfg
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) ConfigureInterface(privateKey string, port int) error {
|
|
log.Debugf("adding Wireguard private key")
|
|
key, err := wgtypes.ParseKey(privateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fwmark := getFwmark()
|
|
config := wgtypes.Config{
|
|
PrivateKey: &key,
|
|
ReplacePeers: true,
|
|
FirewallMark: &fwmark,
|
|
ListenPort: &port,
|
|
}
|
|
|
|
return c.device.IpcSet(toWgUserspaceString(config))
|
|
}
|
|
|
|
// SetPresharedKey sets the preshared key for a peer.
|
|
// If updateOnly is true, only updates the existing peer; if false, creates or updates.
|
|
func (c *WGUSPConfigurer) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
|
parsedPeerKey, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg := buildPresharedKeyConfig(parsedPeerKey, psk, updateOnly)
|
|
return c.device.IpcSet(toWgUserspaceString(cfg))
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peer := wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
ReplaceAllowedIPs: false,
|
|
// don't replace allowed ips, wg will handle duplicated peer IP
|
|
AllowedIPs: prefixesToIPNets(allowedIps),
|
|
PersistentKeepaliveInterval: &keepAlive,
|
|
PresharedKey: preSharedKey,
|
|
Endpoint: endpoint,
|
|
}
|
|
|
|
config := wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
|
|
if ipcErr := c.device.IpcSet(toWgUserspaceString(config)); ipcErr != nil {
|
|
return ipcErr
|
|
}
|
|
|
|
if endpoint != nil {
|
|
addr, err := netip.ParseAddr(endpoint.IP.String())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse endpoint address: %w", err)
|
|
}
|
|
addrPort := netip.AddrPortFrom(addr.Unmap(), uint16(endpoint.Port))
|
|
c.activityRecorder.UpsertAddress(peerKey, addrPort)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) RemoveEndpointAddress(peerKey string) error {
|
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return fmt.Errorf("parse peer key: %w", err)
|
|
}
|
|
|
|
ipcStr, err := c.device.IpcGet()
|
|
if err != nil {
|
|
return fmt.Errorf("get IPC config: %w", err)
|
|
}
|
|
|
|
// Parse current status to get allowed IPs for the peer
|
|
stats, err := parseStatus(c.deviceName, ipcStr)
|
|
if err != nil {
|
|
return fmt.Errorf("parse IPC config: %w", err)
|
|
}
|
|
|
|
var allowedIPs []net.IPNet
|
|
found := false
|
|
for _, peer := range stats.Peers {
|
|
if peer.PublicKey == peerKey {
|
|
allowedIPs = peer.AllowedIPs
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("peer %s not found", peerKey)
|
|
}
|
|
|
|
// remove the peer from the WireGuard configuration
|
|
peer := wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
Remove: true,
|
|
}
|
|
|
|
config := wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
if ipcErr := c.device.IpcSet(toWgUserspaceString(config)); ipcErr != nil {
|
|
return fmt.Errorf("failed to remove peer: %s", ipcErr)
|
|
}
|
|
|
|
// Build the peer config
|
|
peer = wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
ReplaceAllowedIPs: true,
|
|
AllowedIPs: allowedIPs,
|
|
}
|
|
|
|
config = wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
|
|
if err := c.device.IpcSet(toWgUserspaceString(config)); err != nil {
|
|
return fmt.Errorf("remove endpoint address: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) RemovePeer(peerKey string) error {
|
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peer := wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
Remove: true,
|
|
}
|
|
|
|
config := wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
ipcErr := c.device.IpcSet(toWgUserspaceString(config))
|
|
|
|
c.activityRecorder.Remove(peerKey)
|
|
return ipcErr
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) AddAllowedIP(peerKey string, allowedIP netip.Prefix) error {
|
|
ipNet := net.IPNet{
|
|
IP: allowedIP.Addr().AsSlice(),
|
|
Mask: net.CIDRMask(allowedIP.Bits(), allowedIP.Addr().BitLen()),
|
|
}
|
|
|
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peer := wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
UpdateOnly: true,
|
|
ReplaceAllowedIPs: false,
|
|
AllowedIPs: []net.IPNet{ipNet},
|
|
}
|
|
|
|
config := wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
|
|
return c.device.IpcSet(toWgUserspaceString(config))
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) RemoveAllowedIP(peerKey string, allowedIP netip.Prefix) error {
|
|
ipc, err := c.device.IpcGet()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hexKey := hex.EncodeToString(peerKeyParsed[:])
|
|
|
|
lines := strings.Split(ipc, "\n")
|
|
|
|
peer := wgtypes.PeerConfig{
|
|
PublicKey: peerKeyParsed,
|
|
UpdateOnly: true,
|
|
ReplaceAllowedIPs: true,
|
|
AllowedIPs: []net.IPNet{},
|
|
}
|
|
|
|
foundPeer := false
|
|
removedAllowedIP := false
|
|
ip := allowedIP.String()
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
|
|
// If we're within the details of the found peer and encounter another public key,
|
|
// this means we're starting another peer's details. So, reset the flag.
|
|
if strings.HasPrefix(line, "public_key=") && foundPeer {
|
|
foundPeer = false
|
|
}
|
|
|
|
// Identify the peer with the specific public key
|
|
if line == fmt.Sprintf("public_key=%s", hexKey) {
|
|
foundPeer = true
|
|
}
|
|
|
|
// If we're within the details of the found peer and find the specific allowed IP, skip this line
|
|
if foundPeer && line == "allowed_ip="+ip {
|
|
removedAllowedIP = true
|
|
continue
|
|
}
|
|
|
|
// Append the line to the output string
|
|
if foundPeer && strings.HasPrefix(line, "allowed_ip=") {
|
|
allowedIPStr := strings.TrimPrefix(line, "allowed_ip=")
|
|
_, ipNet, err := net.ParseCIDR(allowedIPStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peer.AllowedIPs = append(peer.AllowedIPs, *ipNet)
|
|
}
|
|
}
|
|
|
|
if !removedAllowedIP {
|
|
return ErrAllowedIPNotFound
|
|
}
|
|
config := wgtypes.Config{
|
|
Peers: []wgtypes.PeerConfig{peer},
|
|
}
|
|
return c.device.IpcSet(toWgUserspaceString(config))
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) FullStats() (*Stats, error) {
|
|
ipcStr, err := c.device.IpcGet()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("IpcGet failed: %w", err)
|
|
}
|
|
|
|
return parseStatus(c.deviceName, ipcStr)
|
|
}
|
|
|
|
func (c *WGUSPConfigurer) LastActivities() map[string]monotime.Time {
|
|
return c.activityRecorder.GetLastActivities()
|
|
}
|
|
|
|
// startUAPI starts the UAPI listener for managing the WireGuard interface via external tool
|
|
func (t *WGUSPConfigurer) startUAPI() {
|
|
var err error
|
|
t.uapiListener, err = openUAPI(t.deviceName)
|
|
if err != nil {
|
|
log.Errorf("failed to open uapi listener: %v", err)
|
|
return
|
|
}
|
|
|
|
go func(uapi net.Listener) {
|
|
for {
|
|
uapiConn, uapiErr := uapi.Accept()
|
|
if uapiErr != nil {
|
|
log.Tracef("%s", uapiErr)
|
|
return
|
|
}
|
|
go func() {
|
|
t.device.IpcHandle(uapiConn)
|
|
}()
|
|
}
|
|
}(t.uapiListener)
|
|
}
|
|
|
|
func (t *WGUSPConfigurer) Close() {
|
|
if t.uapiListener != nil {
|
|
err := t.uapiListener.Close()
|
|
if err != nil {
|
|
log.Errorf("failed to close uapi listener: %v", err)
|
|
}
|
|
}
|
|
|
|
if runtime.GOOS == "linux" {
|
|
sockPath := "/var/run/wireguard/" + t.deviceName + ".sock"
|
|
if _, statErr := os.Stat(sockPath); statErr == nil {
|
|
_ = os.Remove(sockPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *WGUSPConfigurer) GetStats() (map[string]WGStats, error) {
|
|
return t.statsCache.get()
|
|
}
|
|
|
|
func (t *WGUSPConfigurer) fetchStats() (map[string]WGStats, error) {
|
|
ipc, err := t.device.IpcGet()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ipc get: %w", err)
|
|
}
|
|
|
|
return parseTransfers(ipc)
|
|
}
|
|
|
|
func parseTransfers(ipc string) (map[string]WGStats, error) {
|
|
stats := make(map[string]WGStats)
|
|
var (
|
|
currentKey string
|
|
currentStats WGStats
|
|
hasPeer bool
|
|
)
|
|
lines := strings.Split(ipc, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
|
|
// If we're within the details of the found peer and encounter another public key,
|
|
// this means we're starting another peer's details. So, stop.
|
|
if strings.HasPrefix(line, "public_key=") {
|
|
peerID := strings.TrimPrefix(line, "public_key=")
|
|
h, err := hex.DecodeString(peerID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode peerID: %w", err)
|
|
}
|
|
currentKey = base64.StdEncoding.EncodeToString(h)
|
|
currentStats = WGStats{} // Reset stats for the new peer
|
|
hasPeer = true
|
|
stats[currentKey] = currentStats
|
|
continue
|
|
}
|
|
|
|
if !hasPeer {
|
|
continue
|
|
}
|
|
|
|
key := strings.SplitN(line, "=", 2)
|
|
if len(key) != 2 {
|
|
continue
|
|
}
|
|
switch key[0] {
|
|
case ipcKeyLastHandshakeTimeSec:
|
|
hs, err := toLastHandshake(key[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
currentStats.LastHandshake = hs
|
|
stats[currentKey] = currentStats
|
|
case ipcKeyRxBytes:
|
|
rxBytes, err := toBytes(key[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse rx_bytes: %w", err)
|
|
}
|
|
currentStats.RxBytes = rxBytes
|
|
stats[currentKey] = currentStats
|
|
case ipcKeyTxBytes:
|
|
TxBytes, err := toBytes(key[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse tx_bytes: %w", err)
|
|
}
|
|
currentStats.TxBytes = TxBytes
|
|
stats[currentKey] = currentStats
|
|
}
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
func toWgUserspaceString(wgCfg wgtypes.Config) string {
|
|
var sb strings.Builder
|
|
if wgCfg.PrivateKey != nil {
|
|
hexKey := hex.EncodeToString(wgCfg.PrivateKey[:])
|
|
sb.WriteString(fmt.Sprintf("private_key=%s\n", hexKey))
|
|
}
|
|
|
|
if wgCfg.ListenPort != nil {
|
|
sb.WriteString(fmt.Sprintf("listen_port=%d\n", *wgCfg.ListenPort))
|
|
}
|
|
|
|
if wgCfg.ReplacePeers {
|
|
sb.WriteString("replace_peers=true\n")
|
|
}
|
|
|
|
if wgCfg.FirewallMark != nil {
|
|
sb.WriteString(fmt.Sprintf("fwmark=%d\n", *wgCfg.FirewallMark))
|
|
}
|
|
|
|
for _, p := range wgCfg.Peers {
|
|
hexKey := hex.EncodeToString(p.PublicKey[:])
|
|
sb.WriteString(fmt.Sprintf("public_key=%s\n", hexKey))
|
|
|
|
if p.Remove {
|
|
sb.WriteString("remove=true\n")
|
|
}
|
|
|
|
if p.UpdateOnly {
|
|
sb.WriteString("update_only=true\n")
|
|
}
|
|
|
|
if p.PresharedKey != nil {
|
|
preSharedHexKey := hex.EncodeToString(p.PresharedKey[:])
|
|
sb.WriteString(fmt.Sprintf("preshared_key=%s\n", preSharedHexKey))
|
|
}
|
|
|
|
if p.Endpoint != nil {
|
|
sb.WriteString(fmt.Sprintf("endpoint=%s\n", p.Endpoint.String()))
|
|
}
|
|
|
|
if p.PersistentKeepaliveInterval != nil {
|
|
sb.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds())))
|
|
}
|
|
|
|
if p.ReplaceAllowedIPs {
|
|
sb.WriteString("replace_allowed_ips=true\n")
|
|
}
|
|
|
|
for _, aip := range p.AllowedIPs {
|
|
sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String()))
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func toLastHandshake(stringVar string) (time.Time, error) {
|
|
sec, err := strconv.ParseInt(stringVar, 10, 64)
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("parse handshake sec: %w", err)
|
|
}
|
|
|
|
// If sec is 0 (Unix epoch), return zero time instead
|
|
// This indicates no handshake has occurred
|
|
if sec == 0 {
|
|
return time.Time{}, nil
|
|
}
|
|
|
|
return time.Unix(sec, 0), nil
|
|
}
|
|
|
|
func toBytes(s string) (int64, error) {
|
|
return strconv.ParseInt(s, 10, 64)
|
|
}
|
|
|
|
func getFwmark() int {
|
|
if nbnet.AdvancedRouting() && runtime.GOOS == "linux" {
|
|
return nbnet.ControlPlaneMark
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func hexToWireguardKey(hexKey string) (wgtypes.Key, error) {
|
|
// Decode hex string to bytes
|
|
keyBytes, err := hex.DecodeString(hexKey)
|
|
if err != nil {
|
|
return wgtypes.Key{}, fmt.Errorf("failed to decode hex key: %w", err)
|
|
}
|
|
|
|
// Check if we have the right number of bytes (WireGuard keys are 32 bytes)
|
|
if len(keyBytes) != 32 {
|
|
return wgtypes.Key{}, fmt.Errorf("invalid key length: expected 32 bytes, got %d", len(keyBytes))
|
|
}
|
|
|
|
// Convert to wgtypes.Key
|
|
var key wgtypes.Key
|
|
copy(key[:], keyBytes)
|
|
|
|
return key, nil
|
|
}
|
|
|
|
func parseStatus(deviceName, ipcStr string) (*Stats, error) {
|
|
stats := &Stats{DeviceName: deviceName}
|
|
var currentPeer *Peer
|
|
for _, line := range strings.Split(strings.TrimSpace(ipcStr), "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
key := parts[0]
|
|
val := parts[1]
|
|
|
|
switch key {
|
|
case privateKey:
|
|
key, err := hexToWireguardKey(val)
|
|
if err != nil {
|
|
log.Errorf("failed to parse private key: %v", err)
|
|
continue
|
|
}
|
|
stats.PublicKey = key.PublicKey().String()
|
|
case publicKey:
|
|
// Save previous peer
|
|
if currentPeer != nil {
|
|
stats.Peers = append(stats.Peers, *currentPeer)
|
|
}
|
|
key, err := hexToWireguardKey(val)
|
|
if err != nil {
|
|
log.Errorf("failed to parse public key: %v", err)
|
|
continue
|
|
}
|
|
currentPeer = &Peer{
|
|
PublicKey: key.String(),
|
|
}
|
|
case listenPort:
|
|
if port, err := strconv.Atoi(val); err == nil {
|
|
stats.ListenPort = port
|
|
}
|
|
case fwmark:
|
|
if fwmark, err := strconv.Atoi(val); err == nil {
|
|
stats.FWMark = fwmark
|
|
}
|
|
case endpoint:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
|
|
host, portStr, err := net.SplitHostPort(val)
|
|
if err != nil {
|
|
log.Errorf("failed to parse endpoint: %v", err)
|
|
continue
|
|
}
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
log.Errorf("failed to parse endpoint port: %v", err)
|
|
continue
|
|
}
|
|
currentPeer.Endpoint = net.UDPAddr{
|
|
IP: net.ParseIP(host),
|
|
Port: port,
|
|
}
|
|
case allowedIP:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
_, ipnet, err := net.ParseCIDR(val)
|
|
if err == nil {
|
|
currentPeer.AllowedIPs = append(currentPeer.AllowedIPs, *ipnet)
|
|
}
|
|
case ipcKeyTxBytes:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
rxBytes, err := toBytes(val)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
currentPeer.TxBytes = rxBytes
|
|
case ipcKeyRxBytes:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
rxBytes, err := toBytes(val)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
currentPeer.RxBytes = rxBytes
|
|
|
|
case ipcKeyLastHandshakeTimeSec:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
|
|
ts, err := toLastHandshake(val)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
currentPeer.LastHandshake = ts
|
|
case presharedKey:
|
|
if currentPeer == nil {
|
|
continue
|
|
}
|
|
if val != "" && val != "0000000000000000000000000000000000000000000000000000000000000000" {
|
|
if pskKey, err := hexToWireguardKey(val); err == nil {
|
|
currentPeer.PresharedKey = [32]byte(pskKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if currentPeer != nil {
|
|
stats.Peers = append(stats.Peers, *currentPeer)
|
|
}
|
|
return stats, nil
|
|
}
|