Skip to content

Commit

Permalink
Moved report command to new CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
botto committed Jan 10, 2022
1 parent 135f07d commit 86f3dc9
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 696 deletions.
108 changes: 68 additions & 40 deletions reporttypes.go → cmd/cli/report.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dsnet
package cli

import (
"encoding/json"
Expand All @@ -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"
)

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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++
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
}
37 changes: 0 additions & 37 deletions cmd/cli/types.go
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
14 changes: 14 additions & 0 deletions cmd/cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
16 changes: 11 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"strings"
"time"

"github.com/naggie/dsnet"
"github.com/naggie/dsnet/cmd/cli"
Expand Down Expand Up @@ -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{
Expand All @@ -107,7 +108,6 @@ var (
if len(args) != 1 {
return errors.New("Missing hostname argument")
}
viper.Set("hostname", args[0])

return nil
},
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 86f3dc9

Please sign in to comment.