From 7eb9e4d9510f6348387485b74bf0659d8d863da4 Mon Sep 17 00:00:00 2001 From: Edward Viaene Date: Tue, 10 Sep 2024 11:49:25 -0500 Subject: [PATCH 1/2] compression, fixes, wording changes --- pkg/rest/setup.go | 5 + pkg/rest/stats.go | 46 ++++++- pkg/rest/stats_test.go | 43 +++++++ pkg/storage/memory/storage.go | 9 -- pkg/wireguard/constants.go | 1 + pkg/wireguard/packetlogger.go | 175 +++++++++++++++------------ pkg/wireguard/packetlogger_test.go | 107 +++++++++++++++- pkg/wireguard/vpnconfig.go | 5 +- webapp/src/Routes/Setup/VPNSetup.tsx | 5 +- 9 files changed, 297 insertions(+), 99 deletions(-) diff --git a/pkg/rest/setup.go b/pkg/rest/setup.go index 52f51d3..d100c2c 100644 --- a/pkg/rest/setup.go +++ b/pkg/rest/setup.go @@ -312,6 +312,11 @@ func (c *Context) vpnSetupHandler(w http.ResponseWriter, r *http.Request) { c.returnError(w, fmt.Errorf("could write vpn config: %s", err), http.StatusBadRequest) return } + err = wireguard.ReloadVPNServerConfig() + if err != nil { + c.returnError(w, fmt.Errorf("unable to reload server config: %s", err), http.StatusBadRequest) + return + } } if rewriteClientConfigs { // rewrite client configs diff --git a/pkg/rest/stats.go b/pkg/rest/stats.go index fc66f4a..e7d6abc 100644 --- a/pkg/rest/stats.go +++ b/pkg/rest/stats.go @@ -3,8 +3,10 @@ package rest import ( "bufio" "bytes" + "compress/gzip" "encoding/json" "fmt" + "io" "math" "net/http" "path" @@ -282,13 +284,19 @@ func (c *Context) packetLogsHandler(w http.ResponseWriter, r *http.Request) { Data: []LogRow{}, } // logs - statsFiles := []string{ - path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.Format("2006-01-02")+".log"), + statsFiles := []string{} + if offset > 0 { + statsFiles = append(statsFiles, path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.AddDate(0, 0, -1).Format("2006-01-02")+".log")) } + statsFiles = append(statsFiles, path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.Format("2006-01-02")+".log")) if !dateutils.DateEqual(time.Now(), date) { // date is in local timezone, and we are UTC, so also read next file statsFiles = append(statsFiles, path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.AddDate(0, 0, 1).Format("2006-01-02")+".log")) } - statsFiles = filterNonExistentFiles(c.Storage.Client, statsFiles) + statsFiles, err = getCompressedFilesAndRemoveNonExistent(c.Storage.Client, statsFiles) + if err != nil { + c.returnError(w, fmt.Errorf("unable to get files for reading: %s", err), http.StatusBadRequest) + return + } fileReaders, err := c.Storage.Client.OpenFilesFromPos(statsFiles, pos) if err != nil { c.returnError(w, fmt.Errorf("error while reading files: %s", err), http.StatusBadRequest) @@ -353,14 +361,42 @@ func (c *Context) packetLogsHandler(w http.ResponseWriter, r *http.Request) { c.write(w, out) } -func filterNonExistentFiles(storage storage.Iface, files []string) []string { +func getCompressedFilesAndRemoveNonExistent(storage storage.Iface, files []string) ([]string, error) { res := []string{} for _, file := range files { + tmpFile := path.Join(wireguard.VPN_PACKETLOGGER_TMP_DIR, path.Base(file)) if storage.FileExists(file) { res = append(res, file) + } else if storage.FileExists(tmpFile) { // temporary file exists + res = append(res, tmpFile) + } else if storage.FileExists(file + ".gz") { // uncompress log file for random access + err := storage.EnsurePath(wireguard.VPN_PACKETLOGGER_TMP_DIR) + if err != nil { + return res, fmt.Errorf("ensure path error: %s", err) + } + compressedFile, err := storage.OpenFile(file + ".gz") + if err != nil { + return res, fmt.Errorf("unable to open compress filed (%s): %s", file+".gz", err) + } + gzipReader, err := gzip.NewReader(compressedFile) + if err != nil { + return res, fmt.Errorf("unable to open gzip reader (%s): %s", file+".gz", err) + } + fileWriter, err := storage.OpenFileForWriting(tmpFile) + if err != nil { + return res, fmt.Errorf("unable to open tmp file writer (%s): %s", tmpFile, err) + } + _, err = io.Copy(fileWriter, gzipReader) + if err != nil { + return res, fmt.Errorf("unable to uncompress to file (%s): %s", tmpFile, err) + } + fileWriter.Close() + gzipReader.Close() + compressedFile.Close() + res = append(res, tmpFile) } } - return res + return res, nil } func getColor(i int) string { diff --git a/pkg/rest/stats_test.go b/pkg/rest/stats_test.go index b1da1fc..b484606 100644 --- a/pkg/rest/stats_test.go +++ b/pkg/rest/stats_test.go @@ -1,7 +1,10 @@ package rest import ( + "bytes" + "compress/gzip" "encoding/json" + "io" "net/http/httptest" "path" "strings" @@ -78,3 +81,43 @@ func TestFilterLogRecord(t *testing.T) { } } } + +func TestGetCompressedFilesAndRemoveNonExistent(t *testing.T) { + now := time.Now() + storage := &memorystorage.MockMemoryStorage{} + testData := now.Format(wireguard.TIMESTAMP_FORMAT) + ",3df97301-5f73-407a-a26b-91829f1e7f48,1,12729136,24348520,2024-08-23T18:30:42\n" + files := []string{ + path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, "1-2-3-4-"+now.AddDate(0, 0, -1).Format("2006-01-02")+".log"), + path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, "1-2-3-4-"+now.Format("2006-01-02")+".log"), + } + for k, file := range files { + if k != len(files)-1 { // only the last file is not compressed + fileWriter, err := storage.OpenFileForWriting(file + ".gz") + if err != nil { + t.Fatalf("open file for wring error: %s", err) + } + writer := gzip.NewWriter(fileWriter) + io.Copy(writer, bytes.NewReader([]byte(testData))) + writer.Close() + fileWriter.Close() + } else { + storage.WriteFile(file, []byte(testData)) + } + } + outFiles, err := getCompressedFilesAndRemoveNonExistent(storage, files) + if err != nil { + t.Fatalf("get files error: %s", err) + } + if len(outFiles) != 2 { + t.Fatalf("expected 2 files, got: %d", len(outFiles)) + } + for _, file := range outFiles { + body, err := storage.ReadFile(file) + if err != nil { + t.Fatalf("readfile error: %s", err) + } + if string(body) != string(testData) { + t.Fatalf("mismatch: got: %s vs expected: %s\n", string(body), string(testData)) + } + } +} diff --git a/pkg/storage/memory/storage.go b/pkg/storage/memory/storage.go index 3114181..74674f6 100644 --- a/pkg/storage/memory/storage.go +++ b/pkg/storage/memory/storage.go @@ -1,7 +1,6 @@ package memorystorage import ( - "bufio" "bytes" "fmt" "io" @@ -11,14 +10,6 @@ import ( "strings" ) -type MyWriteCloser struct { - *bufio.Writer -} - -func (mwc *MyWriteCloser) Close() error { - return nil -} - type MockReadWriterData []byte func (m *MockReadWriterData) Close() error { diff --git a/pkg/wireguard/constants.go b/pkg/wireguard/constants.go index 26beba2..46fea88 100644 --- a/pkg/wireguard/constants.go +++ b/pkg/wireguard/constants.go @@ -9,6 +9,7 @@ const IP_LIST_PATH = "config/iplist.json" const VPN_CLIENTS_DIR = "clients" const VPN_STATS_DIR = "stats" const VPN_PACKETLOGGER_DIR = "packetlogs" +const VPN_PACKETLOGGER_TMP_DIR = "tmp" const VPN_SERVER_SECRETS_PATH = "secrets" const VPN_PRIVATE_KEY_FILENAME = "priv.key" const PRESHARED_KEY_FILENAME = "preshared.key" diff --git a/pkg/wireguard/packetlogger.go b/pkg/wireguard/packetlogger.go index 5415a8e..f813ee5 100644 --- a/pkg/wireguard/packetlogger.go +++ b/pkg/wireguard/packetlogger.go @@ -78,7 +78,7 @@ func RunPacketLogger(storage storage.Iface, clientCache *ClientCache, vpnConfig i := 0 openFiles := make(PacketLoggerOpenFiles) for { - err := readPacket(storage, handle, clientCache, openFiles) + err := readPacket(storage, handle, clientCache, openFiles, vpnConfig.PacketLogsTypes) if err != nil { logging.DebugLog(fmt.Errorf("readPacket error: %s", err)) } @@ -102,14 +102,14 @@ func RunPacketLogger(storage storage.Iface, clientCache *ClientCache, vpnConfig i++ } } -func readPacket(storage storage.Iface, handle *pcap.Handle, clientCache *ClientCache, openFiles PacketLoggerOpenFiles) error { +func readPacket(storage storage.Iface, handle *pcap.Handle, clientCache *ClientCache, openFiles PacketLoggerOpenFiles, packetLogsTypes map[string]bool) error { data, _, err := handle.ReadPacketData() if err != nil { return fmt.Errorf("read packet error: %s", err) } - return parsePacket(storage, data, clientCache, openFiles, time.Now()) + return parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, time.Now()) } -func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache, openFiles PacketLoggerOpenFiles, now time.Time) error { +func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache, openFiles PacketLoggerOpenFiles, packetLogsTypes map[string]bool, now time.Time) error { packet := gopacket.NewPacket(data, layers.IPProtocolIPv4, gopacket.DecodeOptions{Lazy: true, DecodeStreamsAsDatagrams: true}) var ( ip4 *layers.IPv4 @@ -180,89 +180,108 @@ func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache, o openFiles[clientID+"-"+now.Format("2006-01-02")] = logWriter } - if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { - tcpPacket, _ := tcpLayer.(*layers.TCP) - if tcpPacket.SYN { - logWriter.Write([]byte(strings.Join([]string{ - now.Format(TIMESTAMP_FORMAT), - "tcp", - srcIP.String(), - dstIP.String(), - strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), - strconv.FormatUint(uint64(tcpPacket.DstPort), 10)}, - ",") + "\n", - )) - } - switch tcpPacket.DstPort { - case 80: - if tcpPacket.DstPort == 80 { - appLayer := packet.ApplicationLayer() - if appLayer != nil { - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(appLayer.Payload()))) - if err != nil { - fmt.Printf("debug: can't parse http packet: %s", err) - } else { - logWriter.Write([]byte(strings.Join([]string{ - now.Format(TIMESTAMP_FORMAT), - "http", - srcIP.String(), - dstIP.String(), - strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), - strconv.FormatUint(uint64(tcpPacket.DstPort), 10), - "http://" + req.Host + req.URL.RequestURI()}, - ",") + "\n", - )) - } - } + logTcpVal, logTCP := packetLogsTypes["tcp"] + logHttpVal, logHttp := packetLogsTypes["http+https"] + logDnsVal, logDns := packetLogsTypes["dns"] + if logTCP && !logTcpVal { + logTCP = false + } + if logHttp && !logHttpVal { + logHttp = false + } + if logDns && !logDnsVal { + logDns = false + } + + if logTCP || logHttp { + if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { + tcpPacket, _ := tcpLayer.(*layers.TCP) + if tcpPacket.SYN && logTCP { + logWriter.Write([]byte(strings.Join([]string{ + now.Format(TIMESTAMP_FORMAT), + "tcp", + srcIP.String(), + dstIP.String(), + strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), + strconv.FormatUint(uint64(tcpPacket.DstPort), 10)}, + ",") + "\n", + )) } - case 443: - if tls, ok := packet.Layer(layers.LayerTypeTLS).(*layers.TLS); ok { - for _, handshake := range tls.Handshake { - if sni := parseTLSExtensionSNI([]byte(handshake.ClientHello.Extensions)); sni != nil { - logWriter.Write([]byte(strings.Join([]string{ - now.Format(TIMESTAMP_FORMAT), - "https", - srcIP.String(), - dstIP.String(), - strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), - strconv.FormatUint(uint64(tcpPacket.DstPort), 10), - string(sni)}, - ",") + "\n", - )) + if logHttp { + switch tcpPacket.DstPort { + case 80: + if tcpPacket.DstPort == 80 { + appLayer := packet.ApplicationLayer() + if appLayer != nil { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(appLayer.Payload()))) + if err != nil { + fmt.Printf("debug: can't parse http packet: %s", err) + } else { + logWriter.Write([]byte(strings.Join([]string{ + now.Format(TIMESTAMP_FORMAT), + "http", + srcIP.String(), + dstIP.String(), + strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), + strconv.FormatUint(uint64(tcpPacket.DstPort), 10), + "http://" + req.Host + req.URL.RequestURI()}, + ",") + "\n", + )) + } + } + } + case 443: + if tls, ok := packet.Layer(layers.LayerTypeTLS).(*layers.TLS); ok { + for _, handshake := range tls.Handshake { + if sni := parseTLSExtensionSNI([]byte(handshake.ClientHello.Extensions)); sni != nil { + logWriter.Write([]byte(strings.Join([]string{ + now.Format(TIMESTAMP_FORMAT), + "https", + srcIP.String(), + dstIP.String(), + strconv.FormatUint(uint64(tcpPacket.SrcPort), 10), + strconv.FormatUint(uint64(tcpPacket.DstPort), 10), + string(sni)}, + ",") + "\n", + )) + } + } } } } } } - if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil { - udp, _ := udpLayer.(*layers.UDP) + if logDns { + if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil { + udp, _ := udpLayer.(*layers.UDP) - if udp.NextLayerType().Contains(layers.LayerTypeDNS) { - dnsPacket := packet.Layer(layers.LayerTypeDNS) - if dnsPacket != nil { - udpDNS := dnsPacket.(*layers.DNS) - questions := []string{} - for k := range udpDNS.Questions { - found := false - for _, question := range questions { - if question == string(udpDNS.Questions[k].Name) { - found = true + if udp.NextLayerType().Contains(layers.LayerTypeDNS) { + dnsPacket := packet.Layer(layers.LayerTypeDNS) + if dnsPacket != nil { + udpDNS := dnsPacket.(*layers.DNS) + questions := []string{} + for k := range udpDNS.Questions { + found := false + for _, question := range questions { + if question == string(udpDNS.Questions[k].Name) { + found = true + } + } + if !found { + questions = append(questions, string(udpDNS.Questions[k].Name)) } - } - if !found { - questions = append(questions, string(udpDNS.Questions[k].Name)) - } + } + logWriter.Write([]byte(strings.Join([]string{ + now.Format(TIMESTAMP_FORMAT), + "udp", + srcIP.String(), + dstIP.String(), + strconv.FormatUint(uint64(udp.SrcPort), 10), + strconv.FormatUint(uint64(udp.DstPort), 10), + strings.Join(questions, "#")}, + ",") + "\n")) } - logWriter.Write([]byte(strings.Join([]string{ - now.Format(TIMESTAMP_FORMAT), - "udp", - srcIP.String(), - dstIP.String(), - strconv.FormatUint(uint64(udp.SrcPort), 10), - strconv.FormatUint(uint64(udp.DstPort), 10), - strings.Join(questions, "#")}, - ",") + "\n")) } } } @@ -351,11 +370,11 @@ func checkDiskSpace() error { // Packet log rotation func PacketLoggerLogRotation(storage storage.Iface) { for { + time.Sleep(getTimeUntilTomorrowStartOfDay()) // sleep until tomorrow err := packetLoggerLogRotation(storage) if err != nil { logging.ErrorLog(fmt.Errorf("packet logger log rotation error: %s", err)) } - time.Sleep(getTimeUntilTomorrowStartOfDay()) // sleep until tomorrow } } diff --git a/pkg/wireguard/packetlogger_test.go b/pkg/wireguard/packetlogger_test.go index dbd3cab..4936d4e 100644 --- a/pkg/wireguard/packetlogger_test.go +++ b/pkg/wireguard/packetlogger_test.go @@ -38,6 +38,11 @@ func TestParsePacket(t *testing.T) { }, }, } + packetLogsTypes := map[string]bool{ + "tcp": true, + "dns": true, + "http+https": true, + } input := []string{ // DNS reqs "45000037e04900004011cdab0abdb8020a000002e60d00350023d6861e1501000001000000000000056170706c6503636f6d0000010001", @@ -64,7 +69,7 @@ func TestParsePacket(t *testing.T) { if err != nil { t.Fatalf("hex decode error: %s", err) } - err = parsePacket(storage, data, clientCache, openFiles, now) + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, now) if err != nil { t.Fatalf("parse error: %s", err) } @@ -102,6 +107,11 @@ func TestParsePacketSNI(t *testing.T) { }, }, } + packetLogsTypes := map[string]bool{ + "tcp": true, + "dns": true, + "http+https": true, + } input := []string{ `450000d100004000400682160abdb80240e9b468ec5001bb4f71ed891a93673d8018080468f400000101080a1329f7772c5410131603010098010000940301f1d62f57f05cc00fc8fb984e7fc381a26adc301ec143b9bab6d36f3f1b15c97200002ec014c00a0039ff850088008100350084c013c00900330045002f0041c011c00700050004c012c0080016000a00ff0100003d00000013001100000e7777772e676f6f676c652e636f6d000b00020100000a000a0008001d0017001800190010000e000c02683208687474702f312e31`, } @@ -113,7 +123,7 @@ func TestParsePacketSNI(t *testing.T) { if err != nil { t.Fatalf("hex decode error: %s", err) } - err = parsePacket(storage, data, clientCache, openFiles, now) + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, now) if err != nil { t.Fatalf("parse error: %s", err) } @@ -148,6 +158,11 @@ func TestParsePacketOpenFiles(t *testing.T) { }, }, } + packetLogsTypes := map[string]bool{ + "tcp": true, + "dns": true, + "http+https": true, + } input := []string{ // DNS reqs "45000037e04900004011cdab0abdb8030a000002e60d00350023d6861e1501000001000000000000056170706c6503636f6d0000010001", @@ -172,7 +187,7 @@ func TestParsePacketOpenFiles(t *testing.T) { if err != nil { t.Fatalf("hex decode error: %s", err) } - err = parsePacket(storage, data, clientCache, openFiles, nowMinusOneDay) + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, nowMinusOneDay) if err != nil { t.Fatalf("parse error: %s", err) } @@ -200,7 +215,7 @@ func TestParsePacketOpenFiles(t *testing.T) { if err != nil { t.Fatalf("hex decode error: %s", err) } - err = parsePacket(storage, data, clientCache, openFiles, now) + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, now) if err != nil { t.Fatalf("parse error: %s", err) } @@ -416,3 +431,87 @@ func TestPacketLoggerLogRotationDeletion(t *testing.T) { t.Fatalf("only expected 7 days of retention. Got: %d", len(after)) } } + +func TestParsePacketSwitchPacketLogTypes(t *testing.T) { + storage := &memorystorage.MockMemoryStorage{} + clientCache := &ClientCache{ + Addresses: []ClientCacheAddresses{ + { + Address: net.IPNet{ + IP: net.ParseIP("10.189.184.2"), + Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), + }, + ClientID: "1-2-3-4", + }, + { + Address: net.IPNet{ + IP: net.ParseIP("10.189.184.3"), + Mask: net.IPMask(net.ParseIP("255.255.255.255").To4()), + }, + ClientID: "1-2-3-5", + }, + }, + } + packetLogsTypes := map[string]bool{ + "tcp": true, + "dns": true, + "http+https": true, + } + input := []string{ + // DNS reqs + "45000037e04900004011cdab0abdb8020a000002e60d00350023d6861e1501000001000000000000056170706c6503636f6d0000010001", + "4500004092d1000040111b1b0abdb8020a000002c73b0035002c4223b28e01000001000000000000037777770a676f6f676c656170697303636f6d0000410001", + "450000e300004000fe11af480a0000020abdb8020035dbb500cffccbad65818000010000000100000975732d656173742d310470726f6402707209616e616c797469637307636f6e736f6c65036177730361327a03636f6d00001c00010975732d656173742d310470726f6402707209616e616c797469637307636f6e736f6c65036177730361327a03636f6d00000600010000014b004b076e732d3136333709617773646e732d313202636f02756b0011617773646e732d686f73746d617374657206616d617a6f6e03636f6d000000000100001c20000003840012750000015180", + "450000a100004000fe11af8a0a0000020abdb8020035e136008db8bd155f81830001000000010000026462075f646e732d7364045f756470086174746c6f63616c036e657400000c0001c01c00060001000003c0004b046f726375026f72026272026e7007656c732d676d7303617474c0250d726d2d686f73746d617374657203656d730361747403636f6d0000000001000151800000271000093a8000015180", + // http req (SYN + Data) + "450000400000400040066ced0abdb8020a00010cc7b000507216cbdd00000000b0c2ffff008f000002040564010303060101080a69fbf8410000000004020000", + "450200810000400040066caa0abdb8020a00010cc7b000507216cbde4845afad80180804449900000101080a69fbf873eddf46d7474554202f6c6f67696e20485454502f312e310d0a486f73743a2031302e302e312e31320d0a557365722d4167656e743a206375726c2f382e372e310d0a4163636570743a202a2f2a0d0a0d0a", + } + now := time.Now() + nowMinusOneDay := now.AddDate(0, 0, -1) + openFiles := make(PacketLoggerOpenFiles) + for _, s := range input { + data, err := hex.DecodeString(s) + if err != nil { + t.Fatalf("hex decode error: %s", err) + } + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes, nowMinusOneDay) + if err != nil { + t.Fatalf("parse error: %s", err) + } + } + + out1, err := storage.ReadFile(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, "1-2-3-4-"+nowMinusOneDay.Format("2006-01-02")+".log")) + if err != nil { + t.Fatalf("read file error: %s", err) + } + + if !strings.Contains(string(out1), `,udp,10.189.184.2,10.0.0.2,58893,53,apple.com`) { + t.Fatalf("unexpected output. Expected udp record") + } + + packetLogsTypes2 := map[string]bool{ + "tcp": false, + "dns": false, + "http+https": true, + } + for _, s := range input { + data, err := hex.DecodeString(s) + if err != nil { + t.Fatalf("hex decode error: %s", err) + } + err = parsePacket(storage, data, clientCache, openFiles, packetLogsTypes2, now) + if err != nil { + t.Fatalf("parse error: %s", err) + } + } + + out2, err := storage.ReadFile(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, "1-2-3-4-"+now.Format("2006-01-02")+".log")) + if err != nil { + t.Fatalf("read file error: %s", err) + } + + if strings.Contains(string(out2), `,udp,10.189.184.2,10.0.0.2,58893,53,apple.com`) { + t.Fatalf("unexpected output. Expected no udp record. Out: %s\n", out2) + } +} diff --git a/pkg/wireguard/vpnconfig.go b/pkg/wireguard/vpnconfig.go index e56fe3b..8b11516 100644 --- a/pkg/wireguard/vpnconfig.go +++ b/pkg/wireguard/vpnconfig.go @@ -145,6 +145,10 @@ func WriteVPNConfig(storage storage.Iface, vpnConfig VPNConfig) error { } } + return nil +} + +func ReloadVPNServerConfig() error { // notify configmanager client := http.Client{ Timeout: 10 * time.Second, @@ -157,7 +161,6 @@ func WriteVPNConfig(storage storage.Iface, vpnConfig VPNConfig) error { if resp.StatusCode != http.StatusAccepted { return fmt.Errorf("configmanager post error: received status code %d", resp.StatusCode) } - return nil } diff --git a/webapp/src/Routes/Setup/VPNSetup.tsx b/webapp/src/Routes/Setup/VPNSetup.tsx index ca26665..ff2a55b 100644 --- a/webapp/src/Routes/Setup/VPNSetup.tsx +++ b/webapp/src/Routes/Setup/VPNSetup.tsx @@ -239,7 +239,7 @@ export function VPNSetup() { Enable IP Packet logging - Metadata of IP packets passing the VPN can be logged and displayed in this admin portal. Useful if you want to see TCP connection requests, DNS requests, or http/https requests passing the VPN. + Metadata of IP packets passing the VPN can be logged and displayed. Useful if you want to see TCP connection requests, DNS requests, or http/https requests passing the VPN. Can generate a lot of logging data when all traffic is routed over the VPN (0.0.0.0/0 route), or when DNS requests are being logged. @@ -248,10 +248,11 @@ export function VPNSetup() { Date: Tue, 10 Sep 2024 13:28:22 -0500 Subject: [PATCH 2/2] cleanup tmp files --- pkg/storage/iface.go | 1 + pkg/storage/local/path.go | 4 +++ pkg/storage/memory/fileinfo.go | 34 +++++++++++++++++++++++++ pkg/storage/memory/storage.go | 10 +++++++- pkg/wireguard/packetlogger.go | 26 +++++++++++++++++++ pkg/wireguard/packetlogger_test.go | 41 ++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 pkg/storage/memory/fileinfo.go diff --git a/pkg/storage/iface.go b/pkg/storage/iface.go index 3bb3461..f412fff 100644 --- a/pkg/storage/iface.go +++ b/pkg/storage/iface.go @@ -14,6 +14,7 @@ type Iface interface { Rename(oldName, newName string) error AppendFile(name string, data []byte) error EnsurePermissions(name string, mode fs.FileMode) error + FileInfo(name string) (fs.FileInfo, error) ReadWriter Seeker } diff --git a/pkg/storage/local/path.go b/pkg/storage/local/path.go index de52e4f..214b341 100644 --- a/pkg/storage/local/path.go +++ b/pkg/storage/local/path.go @@ -90,3 +90,7 @@ func (l *LocalStorage) Rename(oldName, newName string) error { func (l *LocalStorage) EnsurePermissions(name string, mode fs.FileMode) error { return os.Chmod(path.Join(l.path, name), mode) } + +func (l *LocalStorage) FileInfo(name string) (fs.FileInfo, error) { + return os.Stat(name) +} diff --git a/pkg/storage/memory/fileinfo.go b/pkg/storage/memory/fileinfo.go new file mode 100644 index 0000000..b9fc5dd --- /dev/null +++ b/pkg/storage/memory/fileinfo.go @@ -0,0 +1,34 @@ +package memorystorage + +import ( + "io/fs" + "time" +) + +type FileInfo struct { + NameOut string // base name of the file + SizeOut int64 // length in bytes for regular files; system-dependent for others + ModeOut fs.FileMode // file mode bits + ModTimeOut time.Time // modification time + IsDirOut bool // abbreviation for Mode().IsDir() + SysOut any // underlying data source (can return nil) +} + +func (f FileInfo) Name() string { + return f.NameOut +} +func (f FileInfo) Size() int64 { + return f.SizeOut +} +func (f FileInfo) Mode() fs.FileMode { + return f.ModeOut +} +func (f FileInfo) ModTime() time.Time { + return f.ModTimeOut +} +func (f FileInfo) IsDir() bool { + return f.IsDirOut +} +func (f FileInfo) Sys() any { + return nil +} diff --git a/pkg/storage/memory/storage.go b/pkg/storage/memory/storage.go index 74674f6..ef5734e 100644 --- a/pkg/storage/memory/storage.go +++ b/pkg/storage/memory/storage.go @@ -21,7 +21,8 @@ func (m *MockReadWriterData) Write(p []byte) (nn int, err error) { } type MockMemoryStorage struct { - Data map[string]*MockReadWriterData + FileInfoData map[string]*FileInfo + Data map[string]*MockReadWriterData } func (m *MockMemoryStorage) ConfigPath(filename string) string { @@ -151,3 +152,10 @@ func (m *MockMemoryStorage) OpenFileForAppending(name string) (io.WriteCloser, e func (m *MockMemoryStorage) EnsurePermissions(name string, mode fs.FileMode) error { return nil } +func (m *MockMemoryStorage) FileInfo(name string) (fs.FileInfo, error) { + val, ok := m.FileInfoData[name] + if !ok { + return FileInfo{}, fmt.Errorf("couldn't get file info for: %s", name) + } + return val, nil +} diff --git a/pkg/wireguard/packetlogger.go b/pkg/wireguard/packetlogger.go index f813ee5..978acfd 100644 --- a/pkg/wireguard/packetlogger.go +++ b/pkg/wireguard/packetlogger.go @@ -375,6 +375,10 @@ func PacketLoggerLogRotation(storage storage.Iface) { if err != nil { logging.ErrorLog(fmt.Errorf("packet logger log rotation error: %s", err)) } + err = packetLoggerRemoveTmpFiles(storage) + if err != nil { + logging.ErrorLog(fmt.Errorf("packet logger remove tmp files error: %s", err)) + } } } @@ -431,6 +435,28 @@ func packetLoggerLogRotation(storage storage.Iface) error { return nil } +func packetLoggerRemoveTmpFiles(storage storage.Iface) error { + files, err := storage.ReadDir(VPN_PACKETLOGGER_TMP_DIR) + if err != nil { + return fmt.Errorf("readDir error: %s", err) + } + for _, filename := range files { + if strings.HasSuffix(filename, ".log") { + fileInfo, err := storage.FileInfo(path.Join(VPN_PACKETLOGGER_TMP_DIR, filename)) + if err != nil { + return fmt.Errorf("file info error (%s): %s", filename, err) + } + if time.Since(fileInfo.ModTime()) > (24 * time.Hour) { + err = storage.Remove(path.Join(VPN_PACKETLOGGER_TMP_DIR, filename)) + if err != nil { + return fmt.Errorf("file remove error (%s): %s", filename, err) + } + } + } + } + return nil +} + func packetLoggerCompressLog(storage storage.Iface, filename string) error { reader, err := storage.OpenFile(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, filename)) if err != nil { diff --git a/pkg/wireguard/packetlogger_test.go b/pkg/wireguard/packetlogger_test.go index 4936d4e..a89c047 100644 --- a/pkg/wireguard/packetlogger_test.go +++ b/pkg/wireguard/packetlogger_test.go @@ -432,6 +432,47 @@ func TestPacketLoggerLogRotationDeletion(t *testing.T) { } } +func TestPacketLoggerRemoveTmpFiles(t *testing.T) { + storage := &memorystorage.MockMemoryStorage{ + Data: map[string]*memorystorage.MockReadWriterData{}, + FileInfoData: map[string]*memorystorage.FileInfo{}, + } + for i := 0; i < 20; i++ { + timestamp := time.Now().AddDate(0, 0, -1*i) + suffix := ".log" + key1 := path.Join(VPN_PACKETLOGGER_TMP_DIR, fmt.Sprintf("1-2-3-4-%s%s", timestamp.Format("2006-01-02"), suffix)) + value1 := []byte(timestamp.Format(TIMESTAMP_FORMAT) + `,https,10.189.184.2,64.233.180.104,60496,443,www.google.com`) + err := storage.WriteFile(key1, value1) + if err != nil { + t.Fatalf("write file error: %s", err) + } + storage.FileInfoData[key1] = &memorystorage.FileInfo{ + ModTimeOut: timestamp, + } + } + + before, err := storage.ReadDir(VPN_PACKETLOGGER_TMP_DIR) + if err != nil { + t.Fatalf("readdir error: %s", err) + } + + err = packetLoggerRemoveTmpFiles(storage) + if err != nil { + t.Fatalf("packetLoggerRotation error: %s", err) + } + + after, err := storage.ReadDir(VPN_PACKETLOGGER_TMP_DIR) + if err != nil { + t.Fatalf("readdir error: %s", err) + } + if len(before) != 20 { + t.Fatalf("expected to have written 20 files. Got: %d", len(before)) + } + if len(after) != 1 { + t.Fatalf("only expected 1 tmp files. Got: %d", len(after)) + } +} + func TestParsePacketSwitchPacketLogTypes(t *testing.T) { storage := &memorystorage.MockMemoryStorage{} clientCache := &ClientCache{