diff --git a/README.md b/README.md index e80d038..a02ce1a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # hvpn-node3 - +hvpn is a basic HTTP API service that manages wireguard VPN. Can be part of part of +a larger system of services. The program itself does not route and manage the +VPN traffic; but the underlying host and kernel wiregaurd driver. hvpn only +exposes the state and allows changes to be applied. ## Build ```bash cd cmd/hvpn-node && go build . diff --git a/cmd/hvpn-node/hvpn-node.go b/cmd/hvpn-node/hvpn-node.go index fbc1654..51580c2 100644 --- a/cmd/hvpn-node/hvpn-node.go +++ b/cmd/hvpn-node/hvpn-node.go @@ -55,6 +55,8 @@ func main() { func run(ctx *cli.Context) { slog.Debug("Starting run()") + hvpnnode3.StartMonitor(wgLink, *slog.Default()) + apiMux := http.NewServeMux() apiMux.HandleFunc("GET /node", hvpnnode3.HandleGetNodeInfo(wgLink)) apiMux.HandleFunc("GET /peer/{pubkey}", hvpnnode3.HandleGetPeer(wgLink)) diff --git a/const.go b/const.go new file mode 100644 index 0000000..0b8f1c5 --- /dev/null +++ b/const.go @@ -0,0 +1,5 @@ +package hvpnnode3 + +const ( + CONTENT_JSON = "application/json" +) diff --git a/handlers.go b/handlers.go index 2040a5e..6b22a4b 100644 --- a/handlers.go +++ b/handlers.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "github.com/felixge/httpsnoop" "github.com/google/uuid" @@ -119,15 +120,8 @@ func HandlePostPeer(wgLink *WGLink) http.HandlerFunc { } if exists { debugf("Peer %s already exists", reqID, peerRequest.PublicKey) - w.WriteHeader(http.StatusFound) - peer, err := wgLink.GetPeer(peerRequest.PublicKey) - if err != nil { - slog.Error(err.Error()) - return - } - json.NewEncoder(w).Encode( - proto.WgPeerToPeer(*peer), - ) + w.Header().Add("Location", fmt.Sprintf("/peer/%s", url.QueryEscape(peerRequest.PublicKey))) + w.WriteHeader(http.StatusConflict) return } @@ -140,7 +134,9 @@ func HandlePostPeer(wgLink *WGLink) http.HandlerFunc { ) return } - debugf("Allocated IP: %s", reqID, peer.AllowedIPs[0].IP.String()) + infof("Allocated IP: %s", reqID, peer.AllowedIPs[0].IP.String()) + infof("Peer %s added", reqID, peerRequest.PublicKey) + w.Header().Set("Content-Type", CONTENT_JSON) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode( proto.WgPeerToPeer(*peer), @@ -174,6 +170,7 @@ func HandleDeletePeer(wg *WGLink) http.HandlerFunc { ) slog.Error(err.Error()) } + w.WriteHeader(http.StatusNoContent) } } diff --git a/monitor.go b/monitor.go new file mode 100644 index 0000000..aca52c2 --- /dev/null +++ b/monitor.go @@ -0,0 +1,61 @@ +package hvpnnode3 + +import ( + "fmt" + "log/slog" + "strings" + "time" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +var log slog.Logger + +func StartMonitor(wg *WGLink, log slog.Logger) { + go monitor(wg, log) +} + +func monitor(wg *WGLink, log slog.Logger) { + log.Info("[WGMonitor] Starting") + for { + time.Sleep(time.Second * 5) + peers, err := wg.GetAllPeers() + if err != nil { + log.Error("[WGMonitor] " + err.Error()) + continue + } + + activepeers, err := getActivePeers(peers) + if err != nil { + log.Error("[WGMonitor] " + err.Error()) + continue + } + + sb := strings.Builder{} + sb.WriteString(fmt.Sprintf("Peers: %d ", len(peers))) + sb.WriteString(fmt.Sprintf("Active peers: %d ", len(activepeers))) + + var totalRx int64 + var totalTx int64 + for _, peer := range activepeers { + totalRx += peer.ReceiveBytes + totalTx += peer.TransmitBytes + } + sb.WriteString(fmt.Sprintf("TX: %d RX: %d", totalTx, totalRx)) + + log.Info("[WGMonitor] " + sb.String()) + } +} + +func getActivePeers(peers []wgtypes.Peer) ([]wgtypes.Peer, error) { + active := make([]wgtypes.Peer, 0) + for _, peer := range peers { + if peer.LastHandshakeTime.IsZero() { + continue + } else if time.Now().UTC().Sub(peer.LastHandshakeTime.UTC()) < time.Duration(4 * time.Minute) { + active = append(active, peer) + } + } + return active, nil +} +