Skip to content

Commit

Permalink
handshakes, finalizing code
Browse files Browse the repository at this point in the history
  • Loading branch information
wardviaene committed Aug 26, 2024
1 parent 820078e commit b500768
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/config/*
/stats/*
/templates/*
/rest-server
/o-rest-server
Expand Down
59 changes: 56 additions & 3 deletions pkg/rest/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
// calculate stats
var userStatsResponse UserStatsResponse
statsFiles := []string{
c.Storage.Client.ConfigPath(path.Join(wireguard.VPN_STATS_DIR, "user-"+date.AddDate(0, 0, -1).Format("2006-01-02")+".log")),
c.Storage.Client.ConfigPath(path.Join(wireguard.VPN_STATS_DIR, "user-"+date.Format("2006-01-02")+".log")),
c.Storage.Client.ConfigPath(path.Join(wireguard.VPN_STATS_DIR, "user-"+date.AddDate(0, 0, 1).Format("2006-01-02")+".log")),
path.Join(wireguard.VPN_STATS_DIR, "user-"+date.AddDate(0, 0, -1).Format("2006-01-02")+".log"),
path.Join(wireguard.VPN_STATS_DIR, "user-"+date.Format("2006-01-02")+".log"),
}
if !dateEqual(time.Now(), date) {
statsFiles = append(statsFiles, path.Join(wireguard.VPN_STATS_DIR, "user-"+date.AddDate(0, 0, 1).Format("2006-01-02")+".log"))
}
logData := bytes.NewBuffer([]byte{})
for _, statsFile := range statsFiles {
Expand All @@ -73,6 +75,8 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
transmitBytesLast := make(map[string]int64)
receiveBytesData := make(map[string][]UserStatsDataPoint)
transmitBytesData := make(map[string][]UserStatsDataPoint)
handshakeLast := make(map[string]time.Time)
handshakeData := make(map[string][]UserStatsDataPoint)
for scanner.Scan() { // all other entries
inputSplit := strings.Split(scanner.Text(), ",")
userID := inputSplit[1]
Expand All @@ -92,6 +96,9 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
transmitBytesLast[userID] = 0
}
}
if _, ok := handshakeLast[userID]; !ok {
handshakeLast[userID] = time.Time{}
}
receiveBytes, err := strconv.ParseInt(inputSplit[3], 10, 64)
if err == nil {
if _, ok := receiveBytesData[userID]; !ok {
Expand Down Expand Up @@ -120,8 +127,19 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
}
}
}
handshake, err := time.Parse(wireguard.TIMESTAMP_FORMAT, inputSplit[5])
if err == nil {
if _, ok := handshakeData[userID]; !ok {
handshakeData[userID] = []UserStatsDataPoint{}
}
handshake = handshake.Add(time.Duration(offset) * time.Minute)
if dateEqual(handshake, date) && !handshake.Equal(handshakeLast[userID]) {
handshakeData[userID] = append(handshakeData[userID], UserStatsDataPoint{X: handshake.Format(wireguard.TIMESTAMP_FORMAT), Y: 1})
}
}
receiveBytesLast[userID] = receiveBytes
transmitBytesLast[userID] = transmitBytes
handshakeLast[userID] = handshake
}

if err := scanner.Err(); err != nil {
Expand All @@ -134,6 +152,9 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
userStatsResponse.TransmitBytes = UserStatsData{
Datasets: []UserStatsDataset{},
}
userStatsResponse.Handshakes = UserStatsData{
Datasets: []UserStatsDataset{},
}
for userID, data := range receiveBytesData {
login, ok := userMap[userID]
if !ok {
Expand All @@ -145,6 +166,7 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
Label: login,
Data: data,
Tension: 0.1,
ShowLine: true,
})
}
for userID, data := range transmitBytesData {
Expand All @@ -158,11 +180,27 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
Label: login,
Data: data,
Tension: 0.1,
ShowLine: true,
})
}
for userID, data := range handshakeData {
login, ok := userMap[userID]
if !ok {
login = "unknown"
}
userStatsResponse.Handshakes.Datasets = append(userStatsResponse.Handshakes.Datasets, UserStatsDataset{
BorderColor: getColor(len(userStatsResponse.Handshakes.Datasets)),
BackgroundColor: getColor(len(userStatsResponse.Handshakes.Datasets)),
Label: login,
Data: data,
Tension: 0.1,
ShowLine: false,
})
}

sort.Sort(userStatsResponse.ReceiveBytes.Datasets)
sort.Sort(userStatsResponse.TransmitBytes.Datasets)
sort.Sort(userStatsResponse.Handshakes.Datasets)

out, err := json.Marshal(userStatsResponse)
if err != nil {
Expand All @@ -179,6 +217,21 @@ func getColor(i int) string {
"#5FB49C",
"#414288",
"#682D63",
"#b45f5f",
"#b49f5f",
"#8ab45f",
"#5fb475",
"#5f8ab4",
"#755fb4",
"#b45fb4",
"#b45f75",
"#b45f5f",
"#0066cc",
"#cc0000",
"#33cc00",
"#00cc99",
"#cc00cc",
"#00cc99",
}
return colors[i%len(colors)]
}
Expand Down
11 changes: 7 additions & 4 deletions pkg/rest/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package rest

import (
"encoding/json"
"fmt"
"net/http/httptest"
"path"
"strings"
"testing"
"time"

Expand All @@ -30,12 +32,13 @@ func TestUserStatsHandler(t *testing.T) {
2024-08-23T20:09:03,3df97301-5f73-407a-a26b-91829f1e7f48,1,39928520,85171728,2024-08-23T20:08:54`

statsFile := c.Storage.Client.ConfigPath(path.Join(wireguard.VPN_STATS_DIR, "user-"+time.Now().Format("2006-01-02")) + ".log")
err = c.Storage.Client.WriteFile(statsFile, []byte(testData))
err = c.Storage.Client.WriteFile(statsFile, []byte(strings.ReplaceAll(testData, "2024-08-23", time.Now().Format("2006-01-02"))))
if err != nil {
t.Fatalf("Cannot write test file")
}

req := httptest.NewRequest("GET", "http://example.com/stats/user", nil)
req.SetPathValue("date", time.Now().Format("2006-01-02"))
w := httptest.NewRecorder()
c.userStatsHandler(w, req)

Expand All @@ -55,10 +58,10 @@ func TestUserStatsHandler(t *testing.T) {
}

if userStatsResponse.ReceiveBytes.Datasets[0].Data[1].Y != 662580 {
t.Fatalf("unexpected data: %d", userStatsResponse.ReceiveBytes.Datasets[0].Data[1].Y)
t.Fatalf("unexpected data: %f", userStatsResponse.ReceiveBytes.Datasets[0].Data[1].Y)
}
if userStatsResponse.TransmitBytes.Datasets[0].Data[1].Y != 813588 {
t.Fatalf("unexpected data: %d", userStatsResponse.TransmitBytes.Datasets[0].Data[1].Y)
t.Fatalf("unexpected data: %f", userStatsResponse.TransmitBytes.Datasets[0].Data[1].Y)
}

fmt.Printf("%+v\n", userStatsResponse.Handshakes)
}
2 changes: 2 additions & 0 deletions pkg/rest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ type SAMLSetup struct {
type UserStatsResponse struct {
ReceiveBytes UserStatsData `json:"receivedBytes"`
TransmitBytes UserStatsData `json:"transmitBytes"`
Handshakes UserStatsData `json:"handshakes"`
}
type UserStatsData struct {
Datasets UserStatsDatasets `json:"datasets"`
Expand All @@ -190,6 +191,7 @@ type UserStatsDataset struct {
BorderColor string `json:"borderColor"`
BackgroundColor string `json:"backgroundColor"`
Tension float64 `json:"tension"`
ShowLine bool `json:"showLine"`
}

type UserStatsDataPoint struct {
Expand Down
6 changes: 3 additions & 3 deletions pkg/wireguard/stats_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
const RUN_STATS_INTERVAL = 5

func RunStats(storage storage.Iface) {
err := storage.EnsurePath(storage.ConfigPath(VPN_STATS_DIR))
err := storage.EnsurePath(VPN_STATS_DIR)
if err != nil {
logging.ErrorLog(fmt.Errorf("could not create stats path: %s. Stats disabled", err))
return
}
err = storage.EnsureOwnership(storage.ConfigPath(VPN_STATS_DIR), "vpn")
err = storage.EnsureOwnership(VPN_STATS_DIR, "vpn")
if err != nil {
logging.ErrorLog(fmt.Errorf("could not ensure ownership of stats path: %s. Stats disabled", err))
return
Expand Down Expand Up @@ -67,7 +67,7 @@ func runStats(storage storage.Iface) error {
if len(statsEntries) > 0 {
statsCsv := statsToCsv(statsEntries)

statsPath := storage.ConfigPath(path.Join(VPN_STATS_DIR, "user-"+time.Now().Format("2006-01-02")) + ".log")
statsPath := path.Join(VPN_STATS_DIR, "user-"+time.Now().Format("2006-01-02")) + ".log"
err = storage.AppendFile(statsPath, statsCsv)
if err != nil {
return fmt.Errorf("could not append stats to file (%s): %s", statsPath, err)
Expand Down
26 changes: 23 additions & 3 deletions webapp/src/Routes/Home/UserStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { AppSettings } from '../../Constants/Constants';
import { format } from "date-fns";
import { Chart } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Chart as ChartJS, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, TimeScale, ChartOptions, Legend } from 'chart.js';
import { Chart as ChartJS, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, TimeScale, ChartOptions, Legend, Tooltip } from 'chart.js';
import { useState } from "react";

export function UserStats() {
ChartJS.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, TimeScale, Legend);
ChartJS.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, TimeScale, Legend, Tooltip);
const timezoneOffset = new Date().getTimezoneOffset() * -1
const {authInfo} = useAuthContext()
const [statsDate, setStatsDate] = useState<Date | null>(new Date());
Expand Down Expand Up @@ -38,6 +38,12 @@ export function UserStats() {
position: 'right' as const,
display: true,
},
tooltip: {
callbacks: {
//title: (xDatapoint) => {return "this is the data: " + xDatapoint.},
label: (yDatapoint) => {return " "+yDatapoint.formattedValue + " " + unit},
}
}
},
scales: {
x: {
Expand All @@ -46,7 +52,12 @@ export function UserStats() {
y: {
min: 0
}
}
},

hover: {
mode: 'index',
intersect: false
}
}

if (isPending) return ''
Expand All @@ -58,6 +69,9 @@ export function UserStats() {
if(data.transmitBytes.datasets === null) {
data.transmitBytes.datasets = [{ data: [0], label: "no data"}]
}
if(data.handshakes.datasets === null) {
data.handshakes.datasets = [{ data: [0], label: "no data"}]
}

return (
<>
Expand Down Expand Up @@ -97,6 +111,12 @@ export function UserStats() {
</Center>
<Chart type="line" data={data.transmitBytes} options={options} />
</Card>
<Card withBorder radius="md" bg="var(--mantine-color-body)" mt={20}>
<Center>
<Text fw={500} size="lg">User Handshakes</Text>
</Center>
<Chart type="line" data={data.handshakes} options={{...options, plugins: {...options.plugins, tooltip: { ...options.plugins?.tooltip, callbacks: {label: (yDatapoint) => {return " "+yDatapoint.formattedValue }} }} }} />
</Card>
</>
)
}

0 comments on commit b500768

Please sign in to comment.