From 86f3dc9272ddd84cc578588ef7c6b92aa0ce9a22 Mon Sep 17 00:00:00 2001 From: Martin Eskdale Moen Date: Thu, 6 Jan 2022 21:15:05 +0000 Subject: [PATCH] Moved report command to new CLI --- reporttypes.go => cmd/cli/report.go | 108 +++++---- cmd/cli/types.go | 37 --- cmd/cli/util.go | 14 ++ cmd/root.go | 16 +- configtypes.go | 334 ---------------------------- const.go | 26 --- exttypes.go | 85 ------- report.go | 23 -- sync.go | 75 ------- util.go | 71 ------ 10 files changed, 93 insertions(+), 696 deletions(-) rename reporttypes.go => cmd/cli/report.go (76%) delete mode 100644 configtypes.go delete mode 100644 exttypes.go delete mode 100644 report.go delete mode 100644 sync.go delete mode 100644 util.go diff --git a/reporttypes.go b/cmd/cli/report.go similarity index 76% rename from reporttypes.go rename to cmd/cli/report.go index 63af1f5..2cd1a4a 100644 --- a/reporttypes.go +++ b/cmd/cli/report.go @@ -1,4 +1,4 @@ -package dsnet +package cli import ( "encoding/json" @@ -7,8 +7,11 @@ import ( "os" "time" - "github.com/go-playground/validator/v10" + "github.com/go-playground/validator" + "github.com/naggie/dsnet/lib" + "github.com/spf13/viper" "github.com/vishvananda/netlink" + "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) @@ -23,8 +26,8 @@ type DsnetReport struct { IP6 net.IP // IP network from which to allocate automatic sequential addresses // Network is chosen randomly when not specified - Network JSONIPNet - Network6 JSONIPNet + Network lib.JSONIPNet + Network6 lib.JSONIPNet DNS net.IP PeersOnline int PeersTotal int @@ -37,7 +40,56 @@ type DsnetReport struct { Timestamp time.Time } -func GenerateReport(dev *wgtypes.Device, conf *DsnetConfig, oldReport *DsnetReport) DsnetReport { +type PeerReport struct { + // Used to update DNS + Hostname string + // username of person running this host/router + Owner string + // Description of what the host is and/or does + Description string + // Has a handshake occurred in the last 3 mins? + Online bool + // No handshake for 28 days + Dormant bool + // date peer was added to dsnet config + Added time.Time + // Internal VPN IP address. Added to AllowedIPs in server config as a /32 + IP net.IP + IP6 net.IP + // Last known external IP + ExternalIP net.IP + // TODO ExternalIP support (Endpoint) + //ExternalIP net.UDPAddr `validate:"required,udp4_addr"` + // TODO support routing additional networks (AllowedIPs) + Networks []lib.JSONIPNet + LastHandshakeTime time.Time + ReceiveBytes uint64 + TransmitBytes uint64 + ReceiveBytesSI string + TransmitBytesSI string +} + +func GenerateReport() { + conf := MustLoadConfigFile() + + wg, err := wgctrl.New() + check(err) + defer wg.Close() + + dev, err := wg.Device(conf.InterfaceName) + + if err != nil { + ExitFail("Could not retrieve device '%s' (%v)", conf.InterfaceName, err) + } + + oldReport := MustLoadDsnetReport() + report := GetReport(dev, conf, oldReport) + report.MustSave() +} + +func GetReport(dev *wgtypes.Device, conf *DsnetConfig, oldReport *DsnetReport) DsnetReport { + peerTimeout := viper.GetDuration("peer_timeout") + peerExpiry := viper.GetDuration("peer_expiry") wgPeerIndex := make(map[wgtypes.Key]wgtypes.Peer) peerReports := make([]PeerReport, len(conf.Peers)) oldPeerReportIndex := make(map[string]PeerReport) @@ -67,8 +119,8 @@ func GenerateReport(dev *wgtypes.Device, conf *DsnetConfig, oldReport *DsnetRepo continue } - online := time.Since(wgPeer.LastHandshakeTime) < TIMEOUT - dormant := !wgPeer.LastHandshakeTime.IsZero() && time.Since(wgPeer.LastHandshakeTime) > EXPIRY + online := time.Since(wgPeer.LastHandshakeTime) < peerTimeout + dormant := !wgPeer.LastHandshakeTime.IsZero() && time.Since(wgPeer.LastHandshakeTime) > peerExpiry if online { peersOnline++ @@ -122,19 +174,24 @@ func GenerateReport(dev *wgtypes.Device, conf *DsnetConfig, oldReport *DsnetRepo } } -func (report *DsnetReport) MustSave(filename string) { +func (report *DsnetReport) MustSave() { + reportFilePath := viper.GetString("report_file") + _json, _ := json.MarshalIndent(report, "", " ") - err := ioutil.WriteFile(filename, _json, 0644) + _json = append(_json, '\n') + + err := ioutil.WriteFile(reportFilePath, _json, 0644) check(err) } func MustLoadDsnetReport() *DsnetReport { - raw, err := ioutil.ReadFile(CONFIG_FILE) + reportFilePath := viper.GetString("report_file_path") + raw, err := ioutil.ReadFile(reportFilePath) if os.IsNotExist(err) { return nil } else if os.IsPermission(err) { - ExitFail("%s cannot be accessed. Check read permissions.", CONFIG_FILE) + ExitFail("%s cannot be accessed. Check read permissions.", reportFilePath) } else { check(err) } @@ -148,32 +205,3 @@ func MustLoadDsnetReport() *DsnetReport { return &report } - -type PeerReport struct { - // Used to update DNS - Hostname string - // username of person running this host/router - Owner string - // Description of what the host is and/or does - Description string - // Has a handshake occurred in the last 3 mins? - Online bool - // No handshake for 28 days - Dormant bool - // date peer was added to dsnet config - Added time.Time - // Internal VPN IP address. Added to AllowedIPs in server config as a /32 - IP net.IP - IP6 net.IP - // Last known external IP - ExternalIP net.IP - // TODO ExternalIP support (Endpoint) - //ExternalIP net.UDPAddr `validate:"required,udp4_addr"` - // TODO support routing additional networks (AllowedIPs) - Networks []JSONIPNet - LastHandshakeTime time.Time - ReceiveBytes uint64 - TransmitBytes uint64 - ReceiveBytesSI string - TransmitBytesSI string -} diff --git a/cmd/cli/types.go b/cmd/cli/types.go index e78c409..cb84166 100644 --- a/cmd/cli/types.go +++ b/cmd/cli/types.go @@ -1,48 +1,11 @@ package cli import ( - "net" "strings" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type JSONIPNet struct { - IPNet net.IPNet -} - -func (n JSONIPNet) MarshalJSON() ([]byte, error) { - if len(n.IPNet.IP) == 0 { - return []byte("\"\""), nil - } else { - return []byte("\"" + n.IPNet.String() + "\""), nil - } -} - -func (n *JSONIPNet) UnmarshalJSON(b []byte) error { - cidr := strings.Trim(string(b), "\"") - - if cidr == "" { - // Leave as empty/uninitialised IPNet. A bit like omitempty behaviour, - // but we can leave the field there and blank which is useful if the - // user wishes to add the cidr manually. - return nil - } - - IP, IPNet, err := net.ParseCIDR(cidr) - - if err == nil { - IPNet.IP = IP - n.IPNet = *IPNet - } - - return err -} - -func (n *JSONIPNet) String() string { - return n.IPNet.String() -} - type JSONKey struct { Key wgtypes.Key } diff --git a/cmd/cli/util.go b/cmd/cli/util.go index 8182c7e..461f3bb 100644 --- a/cmd/cli/util.go +++ b/cmd/cli/util.go @@ -72,3 +72,17 @@ func ConfirmOrAbort(format string, a ...interface{}) { ExitFail("Aborted.") } } + +func BytesToSI(b uint64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} diff --git a/cmd/root.go b/cmd/root.go index 3db97ae..528d2ec 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/naggie/dsnet" "github.com/naggie/dsnet/cmd/cli" @@ -92,11 +93,11 @@ var ( } reportCmd = &cobra.Command{ - Run: func(cmd *cobra.Command, args []string) { - dsnet.Report() - }, Use: "report", Short: fmt.Sprintf("Generate a JSON status report to the location configured in %s.", viper.GetString("config_file")), + Run: func(cmd *cobra.Command, args []string) { + cli.GenerateReport() + }, } removeCmd = &cobra.Command{ @@ -107,7 +108,6 @@ var ( if len(args) != 1 { return errors.New("Missing hostname argument") } - viper.Set("hostname", args[0]) return nil }, @@ -139,7 +139,7 @@ func init() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) if err := viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output")); err != nil { - dsnet.ExitFail(err.Error()) + cli.ExitFail(err.Error()) } viper.SetDefault("config_file", "/etc/dsnetconfig.json") @@ -148,6 +148,12 @@ func init() { viper.SetDefault("report_file", "/var/lib/dsnetreport.json") viper.SetDefault("interface_name", "dsnet") + // if last handshake (different from keepalive, see https://www.wireguard.com/protocol/) + viper.SetDefault("peer_timeout", 3*time.Minute) + + // when is a peer considered gone forever? (could remove) + viper.SetDefault("peer_expiry", 28*time.Hour*24) + // Adds subcommands. rootCmd.AddCommand(initCmd) rootCmd.AddCommand(addCmd) diff --git a/configtypes.go b/configtypes.go deleted file mode 100644 index 8b24e2e..0000000 --- a/configtypes.go +++ /dev/null @@ -1,334 +0,0 @@ -package dsnet - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net" - "os" - "time" - - "github.com/go-playground/validator/v10" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -// see https://github.com/WireGuard/wgctrl-go/blob/master/wgtypes/types.go for definitions -type PeerConfig struct { - // Used to update DNS - Hostname string `validate:"required,gte=1,lte=255"` - // username of person running this host/router - Owner string `validate:"required,gte=1,lte=255"` - // Description of what the host is and/or does - Description string `validate:"required,gte=1,lte=255"` - // Internal VPN IP address. Added to AllowedIPs in server config as a /32 - IP net.IP - IP6 net.IP - Added time.Time `validate:"required"` - // TODO ExternalIP support (Endpoint) - //ExternalIP net.UDPAddr `validate:"required,udp4_addr"` - // TODO support routing additional networks (AllowedIPs) - Networks []JSONIPNet `validate:"required"` - PublicKey JSONKey `validate:"required,len=44"` - PrivateKey JSONKey `json:"-"` // omitted from config! - PresharedKey JSONKey `validate:"required,len=44"` -} - -type DsnetConfig struct { - // When generating configs, the ExternalHostname has precendence for the - // server Endpoint, followed by ExternalIP (IPv4) and ExternalIP6 (IPv6) - // The IPs are discovered automatically on init. Define an ExternalHostname - // if you're using dynamic DNS, want to change IPs without updating - // configs, or want wireguard to be able to choose between IPv4/IPv6. It is - // only possible to specify one Endpoint per peer entry in wireguard. - ExternalHostname string - ExternalIP net.IP - ExternalIP6 net.IP - ListenPort int `validate:"gte=1,lte=65535"` - // domain to append to hostnames. Relies on separate DNS server for - // resolution. Informational only. - Domain string `validate:"required,gte=1,lte=255"` - InterfaceName string `validate:"required,gte=1,lte=255"` - // IP network from which to allocate automatic sequential addresses - // Network is chosen randomly when not specified - Network JSONIPNet `validate:"required"` - Network6 JSONIPNet `validate:"required"` - IP net.IP - IP6 net.IP - DNS net.IP - // extra networks available, will be added to AllowedIPs - Networks []JSONIPNet `validate:"required"` - // TODO Default subnets to route via VPN - ReportFile string `validate:"required"` - PrivateKey JSONKey `validate:"required,len=44"` - PostUp string - PostDown string - Peers []PeerConfig `validate:"dive"` -} - -// MustLoadDsnetConfig is like LoadDsnetConfig, except it exits on error -func MustLoadDsnetConfig() *DsnetConfig { - conf, err := LoadDsnetConfig() - check(err) - return conf -} - -// LoadDsnetConfig parses the json config file, validates and stuffs -// it in to a struct -func LoadDsnetConfig() (*DsnetConfig, error) { - raw, err := ioutil.ReadFile(CONFIG_FILE) - - if os.IsNotExist(err) { - return nil, fmt.Errorf("%s does not exist. `dsnet init` may be required.", CONFIG_FILE) - } else if os.IsPermission(err) { - return nil, fmt.Errorf("%s cannot be accessed. Sudo may be required.", CONFIG_FILE) - } else if err != nil { - return nil, err - } - - conf := DsnetConfig{} - err = json.Unmarshal(raw, &conf) - if err != nil { - return nil, err - } - - err = validator.New().Struct(conf) - if err != nil { - return nil, err - } - - if conf.ExternalHostname == "" && len(conf.ExternalIP) == 0 && len(conf.ExternalIP6) == 0 { - return nil, fmt.Errorf("Config does not contain ExternalIP, ExternalIP6 or ExternalHostname") - } - - return &conf, nil -} - -// Save writes the configuration to disk -func (conf *DsnetConfig) Save() error { - _json, _ := json.MarshalIndent(conf, "", " ") - err := ioutil.WriteFile(CONFIG_FILE, _json, 0600) - if err != nil { - return err - } - return nil -} - -// MustSave is like Save except it exits on error -func (conf *DsnetConfig) MustSave() { - err := conf.Save() - check(err) -} - -// AddPeer adds a provided peer to the Peers list in the conf -func (conf *DsnetConfig) AddPeer(peer PeerConfig) error { - // TODO validate all PeerConfig (keys etc) - - for _, p := range conf.Peers { - if peer.Hostname == p.Hostname { - return fmt.Errorf("%s is not an unique hostname", peer.Hostname) - } - } - - for _, p := range conf.Peers { - if peer.PublicKey.Key == p.PublicKey.Key { - return fmt.Errorf("%s is not an unique public key", peer.Hostname) - } - } - - for _, p := range conf.Peers { - if peer.PresharedKey.Key == p.PresharedKey.Key { - return fmt.Errorf("%s is not an unique preshared key", peer.Hostname) - } - } - - if conf.IPAllocated(peer.IP) { - return fmt.Errorf("%s is already allocated", peer.IP) - } - - for _, peerIPNet := range peer.Networks { - if conf.IPAllocated(peerIPNet.IPNet.IP) { - return fmt.Errorf("%s is already allocated", peerIPNet) - } - } - - conf.Peers = append(conf.Peers, peer) - return nil -} - -// MustAddPeer is like AddPeer, except it exist on error -func (conf *DsnetConfig) MustAddPeer(peer PeerConfig) { - err := conf.AddPeer(peer) - check(err) -} - -// RemovePeer removes a peer from the peer list based on hostname -func (conf *DsnetConfig) RemovePeer(hostname string) error { - peerIndex := -1 - - for i, peer := range conf.Peers { - if peer.Hostname == hostname { - peerIndex = i - } - } - - if peerIndex == -1 { - return fmt.Errorf("Could not find peer with hostname %s", hostname) - } - - // remove peer from slice, retaining order - copy(conf.Peers[peerIndex:], conf.Peers[peerIndex+1:]) // shift left - conf.Peers = conf.Peers[:len(conf.Peers)-1] // truncate - return nil -} - -// MustRemovePeer is like RemovePeer, except it exits on error -func (conf *DsnetConfig) MustRemovePeer(hostname string) { - err := conf.RemovePeer(hostname) - check(err) -} - -// IPAllocated checks the existing used ips and returns bool -// depending on if the IP is in use -func (conf DsnetConfig) IPAllocated(IP net.IP) bool { - if IP.Equal(conf.IP) || IP.Equal(conf.IP6) { - return true - } - - for _, peer := range conf.Peers { - if IP.Equal(peer.IP) || IP.Equal(peer.IP6) { - return true - } - - for _, peerIPNet := range peer.Networks { - if IP.Equal(peerIPNet.IPNet.IP) { - return true - } - } - } - - return false -} - -// AllocateIP finds a free IPv4 for a new Peer (sequential allocation) -func (conf DsnetConfig) AllocateIP() (net.IP, error) { - network := conf.Network.IPNet - ones, bits := network.Mask.Size() - zeros := bits - ones - - // avoids network addr - min := 1 - // avoids broadcast addr + overflow - max := (1 << zeros) - 2 - - IP := make(net.IP, len(network.IP)) - - for i := min; i <= max; i++ { - // dst, src! - copy(IP, network.IP) - - // OR the host part with the network part - for j := 0; j < len(IP); j++ { - shift := (len(IP) - j - 1) * 8 - IP[j] = IP[j] | byte(i>>shift) - } - - if !conf.IPAllocated(IP) { - return IP, nil - } - } - - return nil, fmt.Errorf("IP range exhausted") -} - -// MustAllocateIP is like AllocateIP, except it exits on error -func (conf DsnetConfig) MustAllocateIP() net.IP { - ip, err := conf.AllocateIP() - check(err) - return ip -} - -// AllocateIP6 finds a free IPv6 for a new Peer (pseudorandom allocation) -func (conf DsnetConfig) AllocateIP6() (net.IP, error) { - network := conf.Network6.IPNet - ones, bits := network.Mask.Size() - zeros := bits - ones - - rbs := make([]byte, zeros) - rand.Seed(time.Now().UTC().UnixNano()) - - IP := make(net.IP, len(network.IP)) - - for i := 0; i <= 10000; i++ { - rand.Read(rbs) - // dst, src! Copy prefix of IP - copy(IP, network.IP) - - // OR the host part with the network part - for j := ones / 8; j < len(IP); j++ { - IP[j] = IP[j] | rbs[j] - } - - if !conf.IPAllocated(IP) { - return IP, nil - } - } - - return nil, fmt.Errorf("Could not allocate random IPv6 after 10000 tries. This was highly unlikely!") -} - -// MustAllocateIP6 is like AllocateIP6, except it exits on error -func (conf DsnetConfig) MustAllocateIP6() net.IP { - ip, err := conf.AllocateIP6() - check(err) - return ip -} - -func (conf DsnetConfig) GetWgPeerConfigs() []wgtypes.PeerConfig { - wgPeers := make([]wgtypes.PeerConfig, 0, len(conf.Peers)) - - for _, peer := range conf.Peers { - // create a new PSK in memory to avoid passing the same value by - // pointer to each peer (d'oh) - presharedKey := peer.PresharedKey.Key - - // AllowedIPs = private IP + defined networks - allowedIPs := make([]net.IPNet, 0, len(peer.Networks)+2) - - if len(peer.IP) > 0 { - allowedIPs = append( - allowedIPs, - net.IPNet{ - IP: peer.IP, - Mask: net.IPMask{255, 255, 255, 255}, - }, - ) - } - - if len(peer.IP6) > 0 { - allowedIPs = append( - allowedIPs, - net.IPNet{ - IP: peer.IP6, - Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - ) - } - - for _, net := range peer.Networks { - allowedIPs = append(allowedIPs, net.IPNet) - } - - wgPeers = append(wgPeers, wgtypes.PeerConfig{ - PublicKey: peer.PublicKey.Key, - Remove: false, - UpdateOnly: false, - PresharedKey: &presharedKey, - Endpoint: nil, - ReplaceAllowedIPs: true, - AllowedIPs: allowedIPs, - }) - } - - return wgPeers -} diff --git a/const.go b/const.go index ebf1b0d..307ff90 100644 --- a/const.go +++ b/const.go @@ -1,31 +1,5 @@ package dsnet -import ( - "time" -) - -const ( - // could be overridden in future via env - CONFIG_FILE = "/etc/dsnetconfig.json" - WG_USERSPACE_IMPLEMENTATION = "wireguard-go" - - // these end up in the config file - DEFAULT_INTERFACE_NAME = "dsnet" - DEFAULT_REPORT_FILE = "/var/lib/dsnetreport.json" - DEFAULT_LISTEN_PORT = 51820 - - // keepalive always configured for clients. Set to a value likely to - // stop most NATs from dropping the connection. Wireguard docs recommend 25 - // for most NATs - KEEPALIVE = 25 * time.Second - - // if last handshake (different from keepalive, see https://www.wireguard.com/protocol/) - TIMEOUT = 3 * time.Minute - - // when is a peer considered gone forever? (could remove) - EXPIRY = 28 * time.Hour * 24 -) - var ( // populated with LDFLAGS, see do-release.sh VERSION = "unknown" diff --git a/exttypes.go b/exttypes.go deleted file mode 100644 index 85bfd1f..0000000 --- a/exttypes.go +++ /dev/null @@ -1,85 +0,0 @@ -package dsnet - -import ( - "net" - "strings" - - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -type JSONIPNet struct { - IPNet net.IPNet -} - -func (n JSONIPNet) MarshalJSON() ([]byte, error) { - if len(n.IPNet.IP) == 0 { - return []byte("\"\""), nil - } else { - return []byte("\"" + n.IPNet.String() + "\""), nil - } -} - -func (n *JSONIPNet) UnmarshalJSON(b []byte) error { - cidr := strings.Trim(string(b), "\"") - - if cidr == "" { - // Leave as empty/uninitialised IPNet. A bit like omitempty behaviour, - // but we can leave the field there and blank which is useful if the - // user wishes to add the cidr manually. - return nil - } - - IP, IPNet, err := net.ParseCIDR(cidr) - - if err == nil { - IPNet.IP = IP - n.IPNet = *IPNet - } - - return err -} - -func (n *JSONIPNet) String() string { - return n.IPNet.String() -} - -type JSONKey struct { - Key wgtypes.Key -} - -func (k JSONKey) MarshalJSON() ([]byte, error) { - return []byte("\"" + k.Key.String() + "\""), nil -} - -func (k JSONKey) PublicKey() JSONKey { - return JSONKey{ - Key: k.Key.PublicKey(), - } -} - -func (k *JSONKey) UnmarshalJSON(b []byte) error { - b64Key := strings.Trim(string(b), "\"") - key, err := wgtypes.ParseKey(b64Key) - k.Key = key - return err -} - -func GenerateJSONPrivateKey() JSONKey { - privateKey, err := wgtypes.GeneratePrivateKey() - - check(err) - - return JSONKey{ - Key: privateKey, - } -} - -func GenerateJSONKey() JSONKey { - privateKey, err := wgtypes.GenerateKey() - - check(err) - - return JSONKey{ - Key: privateKey, - } -} diff --git a/report.go b/report.go deleted file mode 100644 index 0712aa7..0000000 --- a/report.go +++ /dev/null @@ -1,23 +0,0 @@ -package dsnet - -import ( - "golang.zx2c4.com/wireguard/wgctrl" -) - -func Report() { - conf := MustLoadDsnetConfig() - - wg, err := wgctrl.New() - check(err) - defer wg.Close() - - dev, err := wg.Device(conf.InterfaceName) - - if err != nil { - ExitFail("Could not retrieve device '%s' (%v)", conf.InterfaceName, err) - } - - oldReport := MustLoadDsnetReport() - report := GenerateReport(dev, conf, oldReport) - report.MustSave(conf.ReportFile) -} diff --git a/sync.go b/sync.go deleted file mode 100644 index 22721c6..0000000 --- a/sync.go +++ /dev/null @@ -1,75 +0,0 @@ -package dsnet - -import ( - "fmt" - - "golang.zx2c4.com/wireguard/wgctrl" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -func Sync() { - // TODO check device settings first - conf := MustLoadDsnetConfig() - MustConfigureDevice(conf) -} - -// MustConfigureDevice is like ConfigureDevice, except it exits on -// error -func MustConfigureDevice(conf *DsnetConfig) { - err := ConfigureDevice(conf) - check(err) -} - -// ConfigureDevice sets up the WG interface -func ConfigureDevice(conf *DsnetConfig) error { - wg, err := wgctrl.New() - if err != nil { - return err - } - defer wg.Close() - - dev, err := wg.Device(conf.InterfaceName) - - if err != nil { - return fmt.Errorf("Could not retrieve device '%s' (%v)", conf.InterfaceName, err) - } - - peers := conf.GetWgPeerConfigs() - - // compare peers to see if any exist on the device and not the config. If - // so, they should be removed by appending a dummy peer with Remove:true + pubkey. - knownKeys := make(map[wgtypes.Key]bool) - - for _, peer := range peers { - knownKeys[peer.PublicKey] = true - } - - // find deleted peers, and append dummy "remove" peers - for _, peer := range dev.Peers { - if !knownKeys[peer.PublicKey] { - peers = append(peers, wgtypes.PeerConfig{ - PublicKey: peer.PublicKey, - Remove: true, - }) - } - } - - wgConfig := wgtypes.Config{ - PrivateKey: &conf.PrivateKey.Key, - ListenPort: &conf.ListenPort, - // ReplacePeers with the same peers results in those peers losing - // connection, so it's not possible to do declarative configuration - // idempotently with ReplacePeers like I had assumed. Instead, peers - // must be removed imperatively with Remove:true. Peers can still be - // added/updated with ConfigureDevice declaratively. - ReplacePeers: false, - Peers: peers, - } - - err = wg.ConfigureDevice(conf.InterfaceName, wgConfig) - - if err != nil { - return fmt.Errorf("Could not configure device '%s' (%v)", conf.InterfaceName, err) - } - return nil -} diff --git a/util.go b/util.go deleted file mode 100644 index 50a75e7..0000000 --- a/util.go +++ /dev/null @@ -1,71 +0,0 @@ -package dsnet - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -func check(e error, optMsg ...string) { - if e != nil { - if len(optMsg) > 0 { - ExitFail("%s - %s", e, strings.Join(optMsg, " ")) - } - ExitFail("%s", e) - } -} - -func MustPromptString(prompt string, required bool) string { - reader := bufio.NewReader(os.Stdin) - var text string - var err error - - for text == "" { - fmt.Fprintf(os.Stderr, "%s: ", prompt) - text, err = reader.ReadString('\n') - check(err) - text = strings.TrimSpace(text) - } - return text -} - -func FailWithoutExit(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, "\033[31m"+format+"\033[0m\n", a...) -} - -func ExitFail(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, "\033[31m"+format+"\033[0m\n", a...) - os.Exit(1) -} - -func ConfirmOrAbort(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, format+" [y/n] ", a...) - - reader := bufio.NewReader(os.Stdin) - - input, err := reader.ReadString('\n') - if err != nil { - panic(err) - } - - if input == "y\n" { - return - } else { - ExitFail("Aborted.") - } -} - -func BytesToSI(b uint64) string { - const unit = 1000 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := int64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", - float64(b)/float64(div), "kMGTPE"[exp]) -}