Skip to content

Commit

Permalink
logpackets endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
wardviaene committed Sep 4, 2024
1 parent 72c9430 commit 1245673
Show file tree
Hide file tree
Showing 19 changed files with 381 additions and 39 deletions.
15 changes: 15 additions & 0 deletions pkg/configmanager/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ func (c *ConfigManager) refreshClients(w http.ResponseWriter, r *http.Request) {
returnError(w, fmt.Errorf("method not supported"), http.StatusBadRequest)
}
}
func (c *ConfigManager) refreshServerConfig(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
vpnConfig, err := wireguard.GetVPNConfig(c.Storage)
if err != nil {
returnError(w, fmt.Errorf("get vpn config error: %s", err), http.StatusBadRequest)
return
}
c.VPNConfig.EnablePacketLogs = vpnConfig.EnablePacketLogs
c.VPNConfig.PacketLogsTypes = vpnConfig.PacketLogsTypes
w.WriteHeader(http.StatusAccepted)
default:
returnError(w, fmt.Errorf("method not supported"), http.StatusBadRequest)
}
}

func (c *ConfigManager) upgrade(w http.ResponseWriter, r *http.Request) {
switch r.Method {
Expand Down
1 change: 1 addition & 0 deletions pkg/configmanager/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ func (c *ConfigManager) getRouter() *http.ServeMux {

mux.Handle("/pubkey", http.HandlerFunc(c.getPubKey))
mux.Handle("/refresh-clients", http.HandlerFunc(c.refreshClients))
mux.Handle("/refresh-server-config", http.HandlerFunc(c.refreshServerConfig))
mux.Handle("/upgrade", http.HandlerFunc(c.upgrade))
mux.Handle("/restart-vpn", http.HandlerFunc(c.restartVpn))
mux.Handle("/version", http.HandlerFunc(c.version))
Expand Down
6 changes: 4 additions & 2 deletions pkg/configmanager/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func StartServer(port int) {
}

// start goroutines
startStats(localStorage) // start gathering of wireguard stats
startPacketLogger(localStorage, c.ClientCache) // start packet logger (optional)
startStats(localStorage) // start gathering of wireguard stats
startPacketLogger(localStorage, c.ClientCache, c.VPNConfig) // start packet logger (optional)

log.Printf("Starting localhost http server at port %d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), c.getRouter()))
Expand Down Expand Up @@ -72,5 +72,7 @@ func initConfigManager(storage storage.Iface) (*ConfigManager, error) {
}
}

c.VPNConfig = &vpnConfig

return c, nil
}
4 changes: 2 additions & 2 deletions pkg/configmanager/start_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ func startStats(storage storage.Iface) {
fmt.Printf("Warning: startStats is not implemented in darwin\n")
}

func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache) {
go wireguard.RunPacketLogger(storage, clientCache)
func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache, vpnConfig *wireguard.VPNConfig) {
go wireguard.RunPacketLogger(storage, clientCache, vpnConfig)
}
4 changes: 2 additions & 2 deletions pkg/configmanager/start_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func startStats(storage storage.Iface) {
go wireguard.RunStats(storage)
}

func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache) {
func startPacketLogger(storage storage.Iface, clientCache *wireguard.ClientCache, vpnConfig *wireguard.VPNConfig) {
// run statistics go routine
go wireguard.RunPacketLogger(storage, clientCache)
go wireguard.RunPacketLogger(storage, clientCache, vpnConfig)
}
1 change: 1 addition & 0 deletions pkg/configmanager/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type ConfigManager struct {
PublicKey string
Storage storage.Iface
ClientCache *wireguard.ClientCache
VPNConfig *wireguard.VPNConfig
}

type UpgradeResponse struct {
Expand Down
1 change: 1 addition & 0 deletions pkg/rest/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (c *Context) getRouter(assets fs.FS, indexHtml []byte) *http.ServeMux {
mux.Handle("/api/users", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.usersHandler)))))
mux.Handle("/api/user/{id}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.userHandler)))))
mux.Handle("/api/stats/user/{date}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.userStatsHandler)))))
mux.Handle("/api/stats/{user}/{date}", c.authMiddleware(c.injectUserMiddleware(c.isAdminMiddleware(http.HandlerFunc(c.ipLogsHandler)))))

return mux
}
22 changes: 22 additions & 0 deletions pkg/rest/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"net/http"
"net/netip"
"reflect"
"slices"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -258,6 +260,26 @@ func (c *Context) vpnSetupHandler(w http.ResponseWriter, r *http.Request) {
vpnConfig.DisableNAT = setupRequest.DisableNAT
writeVPNConfig = true
}
if setupRequest.EnablePacketLogs != vpnConfig.EnablePacketLogs {
vpnConfig.EnablePacketLogs = setupRequest.EnablePacketLogs
writeVPNConfig = true
}
// packetlogtypes
packetLogTypes := []string{}
for k, enabled := range vpnConfig.PacketLogsTypes {
if enabled {
packetLogTypes = append(packetLogTypes, k)
}
}
sort.Strings(setupRequest.PacketLogsTypes)
sort.Strings(packetLogTypes)
if !slices.Equal(setupRequest.PacketLogsTypes, packetLogTypes) {
vpnConfig.PacketLogsTypes = make(map[string]bool)
for _, v := range setupRequest.PacketLogsTypes {
vpnConfig.PacketLogsTypes[v] = true
}
writeVPNConfig = true
}

// write vpn config if config has changed
if writeVPNConfig {
Expand Down
99 changes: 99 additions & 0 deletions pkg/rest/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,105 @@ func (c *Context) userStatsHandler(w http.ResponseWriter, r *http.Request) {
c.write(w, out)
}

func (c *Context) ipLogsHandler(w http.ResponseWriter, r *http.Request) {
vpnConfig, err := wireguard.GetVPNConfig(c.Storage.Client)
if err != nil {
c.returnError(w, fmt.Errorf("get vpn config error: %s", err), http.StatusBadRequest)
return
}
if !vpnConfig.EnablePacketLogs { // packet logs is disabled
out, err := json.Marshal(LogDataResponse{Enabled: false})
if err != nil {
c.returnError(w, fmt.Errorf("user stats response marshal error: %s", err), http.StatusBadRequest)
return
}
c.write(w, out)
return
}
userID := r.PathValue("user")
if userID == "" {
c.returnError(w, fmt.Errorf("no user supplied"), http.StatusBadRequest)
return
}
if r.PathValue("date") == "" {
c.returnError(w, fmt.Errorf("no date supplied"), http.StatusBadRequest)
return
}
date, err := time.Parse("2006-01-02", r.PathValue("date"))
if err != nil {
c.returnError(w, fmt.Errorf("invalid date: %s", err), http.StatusBadRequest)
return
}
offset := 0
if r.FormValue("offset") != "" {
i, err := strconv.Atoi(r.FormValue("offset"))
if err == nil {
offset = i
}
}
// get all users
users := c.UserStore.ListUsers()
userMap := make(map[string]string)
for _, user := range users {
userMap[user.ID] = user.Login
}
// calculate stats
statsFiles := []string{
path.Join(wireguard.VPN_STATS_DIR, wireguard.VPN_PACKETLOGGER_DIR, userID+"-"+date.Format("2006-01-02")+".log"),
}
logInputData := bytes.NewBuffer([]byte{})
for _, statsFile := range statsFiles {
if c.Storage.Client.FileExists(statsFile) {
fileLogData, err := c.Storage.Client.ReadFile(statsFile)
if err != nil {
c.returnError(w, fmt.Errorf("readfile error: %s", err), http.StatusBadRequest)
return
}
logInputData.Write(fileLogData)
}
}

scanner := bufio.NewScanner(logInputData)

logData := LogData{
Schema: LogSchema{
Columns: map[string]string{
"Protocol": "string",
"Source IP": "string",
"Destination IP": "string",
"Source Port": "string",
"Destination Port": "string",
"Destination": "string",
},
},
}

for scanner.Scan() { // all other entries
inputSplit := strings.Split(scanner.Text(), ",")
timestamp, err := time.Parse(wireguard.TIMESTAMP_FORMAT, inputSplit[0])
if err != nil {
continue // invalid record
}
row := LogRow{
Timestamp: timestamp.Add(time.Duration(offset) * time.Minute),
Data: inputSplit[1:],
}
logData.Data = append(logData.Data, row)
}

if err := scanner.Err(); err != nil {
c.returnError(w, fmt.Errorf("log file read (scanner) error: %s", err), http.StatusBadRequest)
return
}

out, err := json.Marshal(LogDataResponse{Enabled: true, LogData: logData})
if err != nil {
c.returnError(w, fmt.Errorf("user stats response marshal error: %s", err), http.StatusBadRequest)
return
}
c.write(w, out)
}

func getColor(i int) string {
colors := []string{
"#DEEFB7",
Expand Down
35 changes: 27 additions & 8 deletions pkg/rest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,16 @@ type GeneralSetupRequest struct {
}

type VPNSetupRequest struct {
Routes string `json:"routes"`
VPNEndpoint string `json:"vpnEndpoint"`
AddressRange string `json:"addressRange"`
ClientAddressPrefix string `json:"clientAddressPrefix"`
Port string `json:"port"`
ExternalInterface string `json:"externalInterface"`
Nameservers string `json:"nameservers"`
DisableNAT bool `json:"disableNAT"`
Routes string `json:"routes"`
VPNEndpoint string `json:"vpnEndpoint"`
AddressRange string `json:"addressRange"`
ClientAddressPrefix string `json:"clientAddressPrefix"`
Port string `json:"port"`
ExternalInterface string `json:"externalInterface"`
Nameservers string `json:"nameservers"`
DisableNAT bool `json:"disableNAT"`
EnablePacketLogs bool `json:"enablePacketLogs"`
PacketLogsTypes []string `json:"packetLogsTypes"`
}

type TemplateSetupRequest struct {
Expand Down Expand Up @@ -205,3 +207,20 @@ type NewUserRequest struct {
Role string `json:"role"`
Password string `json:"password,omitempty"`
}

type LogDataResponse struct {
LogData LogData `json:"logData"`
Enabled bool `json:"enabled"`
}

type LogData struct {
Schema LogSchema `json:"schema"`
Data []LogRow `json:"rows"`
}
type LogSchema struct {
Columns map[string]string `json:"columns"`
}
type LogRow struct {
Timestamp time.Time `json:"t"`
Data []string `json:"d"`
}
5 changes: 5 additions & 0 deletions pkg/rest/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func TestCreateUserConnectionDeleteUserFlow(t *testing.T) {
w.Write([]byte("OK"))
return
}
if r.RequestURI == "/refresh-server-config" {
w.WriteHeader(http.StatusAccepted)
w.Write([]byte("OK"))
return
}
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusBadRequest)
Expand Down
1 change: 1 addition & 0 deletions pkg/wireguard/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const VPN_CONFIG_NAME = "vpn-config.json"
const IP_LIST_PATH = "config/iplist.json"
const VPN_CLIENTS_DIR = "clients"
const VPN_STATS_DIR = "stats"
const VPN_PACKETLOGGER_DIR = "packetlogs"
const VPN_SERVER_SECRETS_PATH = "secrets"
const VPN_PRIVATE_KEY_FILENAME = "priv.key"
const PRESHARED_KEY_FILENAME = "preshared.key"
Expand Down
35 changes: 32 additions & 3 deletions pkg/wireguard/packetlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,32 @@ import (
"golang.org/x/sys/unix"
)

func RunPacketLogger(storage storage.Iface, clientCache *ClientCache) {
func RunPacketLogger(storage storage.Iface, clientCache *ClientCache, vpnConfig *VPNConfig) {
if !vpnConfig.EnablePacketLogs {
return
}
// ensure logs dir is created
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(VPN_STATS_DIR, "vpn")
if err != nil {
logging.ErrorLog(fmt.Errorf("could not ensure ownership of stats path: %s. Stats disabled", err))
return
}
err = storage.EnsurePath(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR))
if err != nil {
logging.ErrorLog(fmt.Errorf("could not create stats path: %s. Stats disabled", err))
return
}
err = storage.EnsureOwnership(path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR), "vpn")
if err != nil {
logging.ErrorLog(fmt.Errorf("could not ensure ownership of stats path: %s. Stats disabled", err))
return
}

useSyscalls := false
if runtime.GOOS == "darwin" {
useSyscalls = true
Expand All @@ -38,6 +63,10 @@ func RunPacketLogger(storage storage.Iface, clientCache *ClientCache) {
if err != nil {
logging.DebugLog(fmt.Errorf("readPacket error: %s", err))
}
if !vpnConfig.EnablePacketLogs {
logging.InfoLog("disabling packetlogs")
return
}
if i%1000 == 0 {
if err := checkDiskSpace(); err != nil {
logging.ErrorLog(fmt.Errorf("disk space error: %s", err))
Expand All @@ -56,8 +85,6 @@ func readPacket(storage storage.Iface, handle *pcap.Handle, clientCache *ClientC
return parsePacket(storage, data, clientCache)
}
func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache) error {
now := time.Now()
filename := path.Join(VPN_STATS_DIR, "ip-"+now.Format("2006-01-02.log"))
packet := gopacket.NewPacket(data, layers.IPProtocolIPv4, gopacket.DecodeOptions{Lazy: true, DecodeStreamsAsDatagrams: true})
var (
ip4 *layers.IPv4
Expand Down Expand Up @@ -89,6 +116,8 @@ func parsePacket(storage storage.Iface, data []byte, clientCache *ClientCache) e
if clientID == "" { // doesn't match a client ID
return nil
}
now := time.Now()
filename := path.Join(VPN_STATS_DIR, VPN_PACKETLOGGER_DIR, clientID+"-"+now.Format("2006-01-02")+".log")
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcpPacket, _ := tcpLayer.(*layers.TCP)
if tcpPacket.SYN {
Expand Down
Loading

0 comments on commit 1245673

Please sign in to comment.