From b1cbf4c2a1b6c58e888ef2023b17d2da55cdf46d Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Thu, 25 Jul 2024 00:20:49 +0800 Subject: [PATCH 1/9] Add access log support. Just support access log for accounting. --- Dockerfile | 4 ++- src/go-socks5/socks5.go | 54 ++++++++++++++++++++++++++++++++++++++--- src/main.go | 30 +++++++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 85022ae..37a3c7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,5 +13,7 @@ ENV GANTED_LISTEN=:6626 \ GANTED_ACL=91.108.4.0/22,91.108.8.0/21,91.108.16.0/21,91.108.36.0/22,91.108.56.0/22,149.154.160.0/20,2001:67c:4e8::/48,2001:b28:f23c::/46 \ GANTED_BIND_OUTPUT=0.0.0.0 \ GANTED_AUTH_CACHE_RETENTION=10m \ - GANTED_AUTH_CACHE_GC=10m + GANTED_AUTH_CACHE_GC=10m \ + GANTED_ACCESS_LOG=/var/log/ganted/access.log \ + GANTED_ERROR_LOG=/var/log/ganted/error.log CMD ["./ganted"] diff --git a/src/go-socks5/socks5.go b/src/go-socks5/socks5.go index e5c5182..dfc8799 100644 --- a/src/go-socks5/socks5.go +++ b/src/go-socks5/socks5.go @@ -6,8 +6,10 @@ import ( "log" "net" "os" + "time" "golang.org/x/net/context" + "sync/atomic" ) const ( @@ -46,10 +48,41 @@ type Config struct { // Defaults to stdout. Logger *log.Logger + // AccessLogger can be used to provide a custom access log target. + // Defaults to stdout. + AccessLogger *log.Logger + + // ErrorLogger can be used to provide a custom error log target. + // Defaults to stdout. + ErrorLogger *log.Logger + // Optional function for dialing out Dial func(ctx context.Context, network, addr string) (net.Conn, error) } +// ConnWrapper is a wrapper around a net.Conn that provides a way to log read/write bytes +type ConnWrapper struct { + net.Conn + ReadBytes int64 + WriteBytes int64 +} + +// Read reads data from the connection +func (c *ConnWrapper) Read(b []byte) (int, error) { + n, err := c.Conn.Read(b) + // c.readBytes += int64(n) is not atomic + atomic.AddInt64(&c.ReadBytes, int64(n)) + return n, err +} + +// Write writes data to the connection +func (c *ConnWrapper) Write(b []byte) (int, error) { + n, err := c.Conn.Write(b) + // c.writeBytes += int64(n) is not atomic + atomic.AddInt64(&c.WriteBytes, int64(n)) + return n, err +} + // Server is reponsible for accepting connections and handling // the details of the SOCKS5 protocol type Server struct { @@ -120,11 +153,15 @@ func (s *Server) Serve(l net.Listener) error { // ServeConn is used to serve a single connection. func (s *Server) ServeConn(conn net.Conn) error { defer conn.Close() + + // Wrap the connection to log read/write bytes + wrappedConn := &ConnWrapper{Conn: conn} + remoteAddr, ok := conn.RemoteAddr().(*net.TCPAddr) if !ok { return fmt.Errorf("Invalid remote address type: %T", conn.RemoteAddr()) } - bufConn := bufio.NewReader(conn) + bufConn := bufio.NewReader(wrappedConn) // Read the version byte version := []byte{0} @@ -151,7 +188,7 @@ func (s *Server) ServeConn(conn net.Conn) error { request, err := NewRequest(bufConn) if err != nil { if err == unrecognizedAddrType { - if err := sendReply(conn, addrTypeNotSupported, nil); err != nil { + if err := sendReply(wrappedConn, addrTypeNotSupported, nil); err != nil { return fmt.Errorf("Failed to send reply: %v", err) } } @@ -161,11 +198,22 @@ func (s *Server) ServeConn(conn net.Conn) error { request.RemoteAddr = &AddrSpec{IP: remoteAddr.IP, Port: remoteAddr.Port} // Process the client request - if err := s.handleRequest(request, conn); err != nil { + if err := s.handleRequest(request, wrappedConn); err != nil { err = fmt.Errorf("Failed to handle request: %v", err) s.config.Logger.Printf("[ERR] socks %s: %v", remoteAddr, err) return err } + // log access + // remoteAddr, identity, time_now, request, bytes_in, bytes_out + s.config.AccessLogger.Printf("%s %s %s %s %d %d", + remoteAddr, + authContext.Payload["Username"], + time.Now().Format(time.RFC3339), + request.DestAddr.String(), + wrappedConn.ReadBytes, + wrappedConn.WriteBytes, + ) + return nil } diff --git a/src/main.go b/src/main.go index fd75490..2b49911 100644 --- a/src/main.go +++ b/src/main.go @@ -131,6 +131,26 @@ func init() { } } +func init() { + // init dir /var/log/ganted + if _, err := os.Stat("/var/log/ganted"); os.IsNotExist(err) { + os.Mkdir("/var/log/ganted", 0755) + } +} + +func initFileLogger(filePath string) (*log.Logger, error) { + if filePath == "" { + return nil, nil + } + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + log.Fatalf("[ERR] Open log file: %s", err) + return nil, err + } + logger := log.New(file, "", log.LstdFlags) + return logger, nil +} + func main() { listenAddr := getEnv("GANTED_LISTEN", "127.0.0.1:6626") radiusAddr := getEnv("RADIUS_SERVER", "127.0.0.1:1812") @@ -165,10 +185,20 @@ func main() { } credentials.StartGCWorker() + accessLogger, err := initFileLogger(getEnv("GANTED_ACCESS_LOG", "")) + if err != nil { + log.Fatalf("[ERR] Failed to init access log: %s", err) + } + errorLogger, err := initFileLogger(getEnv("GANTED_ERROR_LOG", "")) + if err != nil { + log.Fatalf("[ERR] Failed to init error log: %s", err) + } server, err := socks5.New(&socks5.Config{ Credentials: credentials, Rules: serverACL, Logger: log.Default(), + AccessLogger: accessLogger, + ErrorLogger: errorLogger, Dial: dialer.DialContext, }) if err != nil { From 1237d6fd58e6ac2975cca619de5717d647f41731 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:37:30 +0800 Subject: [PATCH 2/9] Add `ganted2radius` to parse and send accounting data to radius. Use crond to do it hourly. --- Dockerfile | 6 +- src/Makefile | 10 +++- src/ganted2radius.go | 139 +++++++++++++++++++++++++++++++++++++++++++ src/ganted2radius.sh | 13 ++++ 4 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 src/ganted2radius.go create mode 100755 src/ganted2radius.sh diff --git a/Dockerfile b/Dockerfile index 37a3c7e..16e8ddf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,11 @@ RUN make FROM alpine:latest WORKDIR /app -COPY --from=builder /usr/src/app/ganted ./ +COPY --from=builder /usr/src/app/ganted /usr/src/app/ganted2radius ./ +COPY src/ganted2radius.sh /etc/periodic/hourly/ ENV GANTED_LISTEN=:6626 \ RADIUS_SERVER=light-freeradius:1812 \ + RADIUS_ACCOUNTING_SERVER=light-freeradius:1813 \ RADIUS_SECRET=testing123 \ GANTED_ACL=91.108.4.0/22,91.108.8.0/21,91.108.16.0/21,91.108.36.0/22,91.108.56.0/22,149.154.160.0/20,2001:67c:4e8::/48,2001:b28:f23c::/46 \ GANTED_BIND_OUTPUT=0.0.0.0 \ @@ -16,4 +18,4 @@ ENV GANTED_LISTEN=:6626 \ GANTED_AUTH_CACHE_GC=10m \ GANTED_ACCESS_LOG=/var/log/ganted/access.log \ GANTED_ERROR_LOG=/var/log/ganted/error.log -CMD ["./ganted"] +CMD ["/bin/sh", "-c", "/usr/sbin/crond -l 2 -L /var/log/cron.log && ./ganted"] diff --git a/src/Makefile b/src/Makefile index aa225e2..6c322ba 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,9 +1,13 @@ BIN := ganted +GANTED2RADIUS_BIN := ganted2radius GOFLAGS := -ldflags='-s -w' .PHONY: all -all: $(BIN) +all: $(BIN) $(GANTED2RADIUS_BIN) -$(BIN): $(wildcard *.go) go.mod go.sum - go build $(GOFLAGS) -o "$@" +$(BIN): $(filter-out ganted2radius.go, $(wildcard *.go)) go.mod go.sum + go build $(GOFLAGS) -o "$@" "$<" + +$(GANTED2RADIUS_BIN): ganted2radius.go + go build $(GOFLAGS) -o "$@" "$<" diff --git a/src/ganted2radius.go b/src/ganted2radius.go new file mode 100644 index 0000000..e0e923a --- /dev/null +++ b/src/ganted2radius.go @@ -0,0 +1,139 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + "time" + + "golang.org/x/net/context" + "layeh.com/radius" + "layeh.com/radius/rfc2865" + "layeh.com/radius/rfc2866" +) + +func getEnv(key, def string) string { + if v, ok := os.LookupEnv(key); ok { + return v + } + return def +} + +type RadiusAccountingCredentials struct { + Server string + Secret []byte + NASIdentifier string +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: log_parser ") + os.Exit(1) + } + + radiusServer := getEnv("RADIUS_ACCOUNTING_SERVER", "127.0.0.1:1813") + radiusSecret := getEnv("RADIUS_SECRET", "") + nasIdentifier := getEnv("NAS_IDENTIFIER", "ganted") + + if radiusServer == "" || radiusSecret == "" { + fmt.Println("RADIUS_SERVER and RADIUS_SECRET environment variables must be set") + os.Exit(1) + } + + logfile := os.Args[1] + stats, err := parseLogFile(logfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing log file: %v\n", err) + os.Exit(1) + } + fmt.Printf("Parsed %d records from log file\n", len(stats)) + + creds := RadiusAccountingCredentials{ + Server: radiusServer, + Secret: []byte(radiusSecret), + NASIdentifier: nasIdentifier, + } + + for identity, bytes := range stats { + err := sendAccountingData(creds, identity, bytes) + if err != nil { + fmt.Fprintf(os.Stderr, "Error sending accounting data for identity %s: %v\n", identity, err) + } else { + fmt.Printf("Sent accounting data for identity %s\n", identity) + } + } +} + +func parseLogFile(filename string) (map[string]int, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + stats := make(map[string]int) + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 8 { + fmt.Printf("Skipping malformed line: %s\n", line) + continue + } + + identity := fields[3] + bytesIn, _ := strconv.Atoi(fields[6]) + bytesOut, _ := strconv.Atoi(fields[7]) + totalBytes := bytesIn + bytesOut + + stats[identity] += totalBytes + } + + return stats, scanner.Err() +} + +func sendAccountingData(creds RadiusAccountingCredentials, identity string, bytes int) error { + // send an CodeAccessRequest for test + sessionID := strconv.FormatInt(time.Now().Unix(), 10) + fmt.Printf("Sending accounting data for identity %s, session ID %s, bytes %d\n", identity, sessionID, bytes) + + // Send start accounting packet + startPacket := radius.New(radius.CodeAccountingRequest, []byte(creds.Secret)) + rfc2865.UserName_SetString(startPacket, identity) + // rfc2865.NASIdentifier_SetString(startPacket, creds.NASIdentifier) + rfc2866.AcctSessionID_Set(startPacket, []byte(sessionID)) + rfc2866.AcctStatusType_Set(startPacket, rfc2866.AcctStatusType_Value_Start) + fmt.Printf("Sending start packet\n") + + startReply, err := radius.Exchange(context.Background(), startPacket, creds.Server) + if err != nil { + return err + } + if startReply.Code != radius.CodeAccountingResponse { + return fmt.Errorf("unexpected response from RADIUS server") + } + fmt.Printf("Received start reply\n") + + // Send stop accounting packet + stopPacket := radius.New(radius.CodeAccountingRequest, []byte(creds.Secret)) + rfc2865.UserName_SetString(stopPacket, identity) + rfc2865.NASIdentifier_SetString(stopPacket, creds.NASIdentifier) + rfc2866.AcctSessionID_Set(stopPacket, []byte(sessionID)) + rfc2866.AcctStatusType_Set(stopPacket, rfc2866.AcctStatusType_Value_Stop) + rfc2866.AcctOutputOctets_Set(stopPacket, rfc2866.AcctOutputOctets(bytes)) + fmt.Printf("Sending stop packet\n") + + stopReply, err := radius.Exchange(context.Background(), stopPacket, creds.Server) + if err != nil { + return err + } + if stopReply.Code != radius.CodeAccountingResponse { + return fmt.Errorf("unexpected response from RADIUS server") + } + fmt.Printf("Received stop reply\n") + + return nil +} diff --git a/src/ganted2radius.sh b/src/ganted2radius.sh new file mode 100755 index 0000000..c6051ea --- /dev/null +++ b/src/ganted2radius.sh @@ -0,0 +1,13 @@ +#! /bin/sh + +DEFAULT_LOG_PATH="/var/log/ganted/access.log" + +log_path="${GANTED_ACCESS_LOG:-$DEFAULT_LOG_PATH}" +log_dir=$(dirname "$log_path") + +cd "$log_dir" || exit 1 + +cat "$log_path" > archive.log +truncate -s 0 "$log_path" + +/app/ganted2radius archive.log From e50f92cd3c2ef8f97b6da476cc89c6951c3bf997 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Sun, 28 Jul 2024 18:15:03 +0800 Subject: [PATCH 3/9] Use github.com/robfig/cron instead of crond. --- Dockerfile | 5 +-- src/Makefile | 10 ++--- src/ganted2radius.go | 103 +++++++++++++++++++++---------------------- src/ganted2radius.sh | 13 ------ src/go.mod | 5 ++- src/go.sum | 2 + src/main.go | 23 ++++++++++ 7 files changed, 85 insertions(+), 76 deletions(-) delete mode 100755 src/ganted2radius.sh diff --git a/Dockerfile b/Dockerfile index 16e8ddf..b1386ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,7 @@ RUN make FROM alpine:latest WORKDIR /app -COPY --from=builder /usr/src/app/ganted /usr/src/app/ganted2radius ./ -COPY src/ganted2radius.sh /etc/periodic/hourly/ +COPY --from=builder /usr/src/app/ganted ./ ENV GANTED_LISTEN=:6626 \ RADIUS_SERVER=light-freeradius:1812 \ RADIUS_ACCOUNTING_SERVER=light-freeradius:1813 \ @@ -18,4 +17,4 @@ ENV GANTED_LISTEN=:6626 \ GANTED_AUTH_CACHE_GC=10m \ GANTED_ACCESS_LOG=/var/log/ganted/access.log \ GANTED_ERROR_LOG=/var/log/ganted/error.log -CMD ["/bin/sh", "-c", "/usr/sbin/crond -l 2 -L /var/log/cron.log && ./ganted"] +CMD ["./ganted"] diff --git a/src/Makefile b/src/Makefile index 6c322ba..aa225e2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,13 +1,9 @@ BIN := ganted -GANTED2RADIUS_BIN := ganted2radius GOFLAGS := -ldflags='-s -w' .PHONY: all -all: $(BIN) $(GANTED2RADIUS_BIN) +all: $(BIN) -$(BIN): $(filter-out ganted2radius.go, $(wildcard *.go)) go.mod go.sum - go build $(GOFLAGS) -o "$@" "$<" - -$(GANTED2RADIUS_BIN): ganted2radius.go - go build $(GOFLAGS) -o "$@" "$<" +$(BIN): $(wildcard *.go) go.mod go.sum + go build $(GOFLAGS) -o "$@" diff --git a/src/ganted2radius.go b/src/ganted2radius.go index e0e923a..4a63aeb 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -3,10 +3,12 @@ package main import ( "bufio" "fmt" + "io" "os" "strconv" "strings" "time" + "path/filepath" "golang.org/x/net/context" "layeh.com/radius" @@ -14,56 +16,29 @@ import ( "layeh.com/radius/rfc2866" ) -func getEnv(key, def string) string { - if v, ok := os.LookupEnv(key); ok { - return v - } - return def -} - -type RadiusAccountingCredentials struct { - Server string - Secret []byte - NASIdentifier string -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: log_parser ") - os.Exit(1) - } - - radiusServer := getEnv("RADIUS_ACCOUNTING_SERVER", "127.0.0.1:1813") - radiusSecret := getEnv("RADIUS_SECRET", "") - nasIdentifier := getEnv("NAS_IDENTIFIER", "ganted") - - if radiusServer == "" || radiusSecret == "" { - fmt.Println("RADIUS_SERVER and RADIUS_SECRET environment variables must be set") - os.Exit(1) +func archiveLog(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err } + defer srcFile.Close() - logfile := os.Args[1] - stats, err := parseLogFile(logfile) + dstFile, err := os.Create(dst) if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing log file: %v\n", err) - os.Exit(1) + return err } - fmt.Printf("Parsed %d records from log file\n", len(stats)) - - creds := RadiusAccountingCredentials{ - Server: radiusServer, - Secret: []byte(radiusSecret), - NASIdentifier: nasIdentifier, + defer dstFile.Close() + // Copy the source file to the destination file + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err } - - for identity, bytes := range stats { - err := sendAccountingData(creds, identity, bytes) - if err != nil { - fmt.Fprintf(os.Stderr, "Error sending accounting data for identity %s: %v\n", identity, err) - } else { - fmt.Printf("Sent accounting data for identity %s\n", identity) - } + // Clear the source file + err = os.Truncate(src, 0) + if err != nil { + return err } + return nil } func parseLogFile(filename string) (map[string]int, error) { @@ -95,20 +70,20 @@ func parseLogFile(filename string) (map[string]int, error) { return stats, scanner.Err() } -func sendAccountingData(creds RadiusAccountingCredentials, identity string, bytes int) error { +func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error { // send an CodeAccessRequest for test sessionID := strconv.FormatInt(time.Now().Unix(), 10) fmt.Printf("Sending accounting data for identity %s, session ID %s, bytes %d\n", identity, sessionID, bytes) // Send start accounting packet - startPacket := radius.New(radius.CodeAccountingRequest, []byte(creds.Secret)) + startPacket := radius.New(radius.CodeAccountingRequest, []byte(r.Secret)) rfc2865.UserName_SetString(startPacket, identity) - // rfc2865.NASIdentifier_SetString(startPacket, creds.NASIdentifier) + rfc2865.NASIdentifier_SetString(startPacket, r.NASIdentifier) rfc2866.AcctSessionID_Set(startPacket, []byte(sessionID)) rfc2866.AcctStatusType_Set(startPacket, rfc2866.AcctStatusType_Value_Start) fmt.Printf("Sending start packet\n") - startReply, err := radius.Exchange(context.Background(), startPacket, creds.Server) + startReply, err := radius.Exchange(context.Background(), startPacket, r.AccountingServer) if err != nil { return err } @@ -118,15 +93,15 @@ func sendAccountingData(creds RadiusAccountingCredentials, identity string, byte fmt.Printf("Received start reply\n") // Send stop accounting packet - stopPacket := radius.New(radius.CodeAccountingRequest, []byte(creds.Secret)) + stopPacket := radius.New(radius.CodeAccountingRequest, r.Secret) rfc2865.UserName_SetString(stopPacket, identity) - rfc2865.NASIdentifier_SetString(stopPacket, creds.NASIdentifier) - rfc2866.AcctSessionID_Set(stopPacket, []byte(sessionID)) + rfc2865.NASIdentifier_SetString(stopPacket, r.NASIdentifier) + rfc2866.AcctSessionID_SetString(stopPacket, sessionID) rfc2866.AcctStatusType_Set(stopPacket, rfc2866.AcctStatusType_Value_Stop) rfc2866.AcctOutputOctets_Set(stopPacket, rfc2866.AcctOutputOctets(bytes)) fmt.Printf("Sending stop packet\n") - stopReply, err := radius.Exchange(context.Background(), stopPacket, creds.Server) + stopReply, err := radius.Exchange(context.Background(), stopPacket, r.AccountingServer) if err != nil { return err } @@ -137,3 +112,27 @@ func sendAccountingData(creds RadiusAccountingCredentials, identity string, byte return nil } + +func (r *RadiusCredentials) accounting(accessLogFile string, archiveLogFile string) error { + if archiveLogFile == "" { + archiveLogFile = filepath.Join(filepath.Dir(accessLogFile), "archive.log") + } + if err := archiveLog(accessLogFile, archiveLogFile); err != nil { + return err + } + stats, err := parseLogFile(archiveLogFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing log file: %v\n", err) + return err + } + + for identity, bytes := range stats { + err := r.sendAccountingData(identity, bytes) + if err != nil { + fmt.Fprintf(os.Stderr, "Error sending accounting data for identity %s: %v\n", identity, err) + } else { + fmt.Printf("Sent accounting data for identity %s\n", identity) + } + } + return nil +} diff --git a/src/ganted2radius.sh b/src/ganted2radius.sh deleted file mode 100755 index c6051ea..0000000 --- a/src/ganted2radius.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/sh - -DEFAULT_LOG_PATH="/var/log/ganted/access.log" - -log_path="${GANTED_ACCESS_LOG:-$DEFAULT_LOG_PATH}" -log_dir=$(dirname "$log_path") - -cd "$log_dir" || exit 1 - -cat "$log_path" > archive.log -truncate -s 0 "$log_path" - -/app/ganted2radius archive.log diff --git a/src/go.mod b/src/go.mod index 7ac5511..15221e4 100644 --- a/src/go.mod +++ b/src/go.mod @@ -10,4 +10,7 @@ require ( layeh.com/radius v0.0.0-20231213012653-1006025d24f8 ) -require golang.org/x/net v0.25.0 // indirect +require ( + github.com/robfig/cron v1.2.0 // indirect + golang.org/x/net v0.25.0 // indirect +) diff --git a/src/go.sum b/src/go.sum index 10a2c0f..21505b7 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,5 +1,7 @@ github.com/kisom/netallow v0.0.0-20200609175051-08f6b004e41a h1:4T7cUpk4OIqaxn7i41yVYEU7/4gjTKuvqlrSqbBwwe0= github.com/kisom/netallow v0.0.0-20200609175051-08f6b004e41a/go.mod h1:fHrMiR3Isu09r3MBg9oqlp0E+qBtRp6qsig0hpmgXYg= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/src/main.go b/src/main.go index 2b49911..61ccd37 100644 --- a/src/main.go +++ b/src/main.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/robfig/cron" "github.com/armon/go-socks5" "github.com/kisom/netallow" "layeh.com/radius" @@ -47,7 +48,9 @@ func (acl *ACL) Set(s string) error { type RadiusCredentials struct { Server string + AccountingServer string Secret []byte + NASIdentifier string Cache RadiusCache } @@ -116,6 +119,20 @@ func (r *RadiusCredentials) StartGCWorker() { go r.gcworker() } +func (r *RadiusCredentials) accountingCron() *cron.Cron { + // hourly accounting cron job + c := cron.New() + c.AddFunc("0 0 * * * *", func() { + // accounting + err := r.accounting("/var/log/ganted/access.log", "/var/log/ganted/archive.log") + if err != nil { + log.Printf("[ERR] Accounting error: %s\n", err) + } + }) + c.Start() + return c +} + func getEnv(key, def string) string { if v, ok := os.LookupEnv(key); ok { return v @@ -155,6 +172,8 @@ func main() { listenAddr := getEnv("GANTED_LISTEN", "127.0.0.1:6626") radiusAddr := getEnv("RADIUS_SERVER", "127.0.0.1:1812") radiusSecret := getEnv("RADIUS_SECRET", "") + radiusAccountingAddr := getEnv("RADIUS_ACCOUNTING_SERVER", "127.0.0.1:1813") + nasIdentifier := getEnv("NAS_IDENTIFIER", "ganted") serverACL := &ACL{BasicNet: netallow.NewBasicNet()} err := serverACL.Set(getEnv("GANTED_ACL", "")) if err != nil { @@ -177,13 +196,17 @@ func main() { credentials := &RadiusCredentials{ Server: radiusAddr, + AccountingServer: radiusAccountingAddr, Secret: []byte(radiusSecret), + NASIdentifier: nasIdentifier, Cache: RadiusCache{ Retention: authCacheRetention, GC: authCacheGC, }, } credentials.StartGCWorker() + c :=credentials.accountingCron() + defer c.Stop() accessLogger, err := initFileLogger(getEnv("GANTED_ACCESS_LOG", "")) if err != nil { From c4178c6881b9a7dfc9a637dceebf0b5ee8462537 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:28:13 +0800 Subject: [PATCH 4/9] Remove hard-coded paths, and go fmt. --- Dockerfile | 3 +-- src/ganted2radius.go | 6 ++--- src/main.go | 53 +++++++++++++++++++++++--------------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index b1386ad..8766fda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,5 @@ ENV GANTED_LISTEN=:6626 \ GANTED_BIND_OUTPUT=0.0.0.0 \ GANTED_AUTH_CACHE_RETENTION=10m \ GANTED_AUTH_CACHE_GC=10m \ - GANTED_ACCESS_LOG=/var/log/ganted/access.log \ - GANTED_ERROR_LOG=/var/log/ganted/error.log + GANTED_LOG_DIR=/var/log/ganted CMD ["./ganted"] diff --git a/src/ganted2radius.go b/src/ganted2radius.go index 4a63aeb..e0193d3 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -5,10 +5,10 @@ import ( "fmt" "io" "os" + "path/filepath" "strconv" "strings" "time" - "path/filepath" "golang.org/x/net/context" "layeh.com/radius" @@ -71,7 +71,7 @@ func parseLogFile(filename string) (map[string]int, error) { } func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error { - // send an CodeAccessRequest for test + // send an CodeAccessRequest for test sessionID := strconv.FormatInt(time.Now().Unix(), 10) fmt.Printf("Sending accounting data for identity %s, session ID %s, bytes %d\n", identity, sessionID, bytes) @@ -115,7 +115,7 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error func (r *RadiusCredentials) accounting(accessLogFile string, archiveLogFile string) error { if archiveLogFile == "" { - archiveLogFile = filepath.Join(filepath.Dir(accessLogFile), "archive.log") + archiveLogFile = filepath.Join(filepath.Dir(accessLogFile), "archive.log") } if err := archiveLog(accessLogFile, archiveLogFile); err != nil { return err diff --git a/src/main.go b/src/main.go index 61ccd37..4fb2d8a 100644 --- a/src/main.go +++ b/src/main.go @@ -10,11 +10,12 @@ import ( "sync" "time" - "github.com/robfig/cron" "github.com/armon/go-socks5" "github.com/kisom/netallow" + "github.com/robfig/cron" "layeh.com/radius" "layeh.com/radius/rfc2865" + "path/filepath" ) type ACL struct { @@ -47,11 +48,11 @@ func (acl *ACL) Set(s string) error { } type RadiusCredentials struct { - Server string + Server string AccountingServer string - Secret []byte - NASIdentifier string - Cache RadiusCache + Secret []byte + NASIdentifier string + Cache RadiusCache } type RadiusCache struct { @@ -119,12 +120,12 @@ func (r *RadiusCredentials) StartGCWorker() { go r.gcworker() } -func (r *RadiusCredentials) accountingCron() *cron.Cron { - // hourly accounting cron job - c := cron.New() +func (r *RadiusCredentials) accountingCron(accessLogFile, archiveLogFile string) *cron.Cron { + // hourly accounting cron job + c := cron.New() c.AddFunc("0 0 * * * *", func() { // accounting - err := r.accounting("/var/log/ganted/access.log", "/var/log/ganted/archive.log") + err := r.accounting(accessLogFile, archiveLogFile) if err != nil { log.Printf("[ERR] Accounting error: %s\n", err) } @@ -149,15 +150,16 @@ func init() { } func init() { - // init dir /var/log/ganted - if _, err := os.Stat("/var/log/ganted"); os.IsNotExist(err) { - os.Mkdir("/var/log/ganted", 0755) + // init dir GANTED_LOG_DIR + gantedLogDir := getEnv("GANTED_LOG_DIR", "/var/log/ganted") + if _, err := os.Stat(gantedLogDir); os.IsNotExist(err) { + os.Mkdir(gantedLogDir, 0755) } } func initFileLogger(filePath string) (*log.Logger, error) { - if filePath == "" { - return nil, nil + if filePath == "" { + return nil, nil } file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { @@ -195,34 +197,35 @@ func main() { } credentials := &RadiusCredentials{ - Server: radiusAddr, + Server: radiusAddr, AccountingServer: radiusAccountingAddr, - Secret: []byte(radiusSecret), - NASIdentifier: nasIdentifier, + Secret: []byte(radiusSecret), + NASIdentifier: nasIdentifier, Cache: RadiusCache{ Retention: authCacheRetention, GC: authCacheGC, }, } + gantedLogDir := getEnv("GANTED_LOG_DIR", "/var/log/ganted") credentials.StartGCWorker() - c :=credentials.accountingCron() + c := credentials.accountingCron(filepath.Join(gantedLogDir, "access.log"), filepath.Join(gantedLogDir, "archive.log")) defer c.Stop() - accessLogger, err := initFileLogger(getEnv("GANTED_ACCESS_LOG", "")) + accessLogger, err := initFileLogger(filepath.Join(gantedLogDir, "access.log")) if err != nil { log.Fatalf("[ERR] Failed to init access log: %s", err) } - errorLogger, err := initFileLogger(getEnv("GANTED_ERROR_LOG", "")) + errorLogger, err := initFileLogger(filepath.Join(gantedLogDir, "error.log")) if err != nil { log.Fatalf("[ERR] Failed to init error log: %s", err) } server, err := socks5.New(&socks5.Config{ - Credentials: credentials, - Rules: serverACL, - Logger: log.Default(), + Credentials: credentials, + Rules: serverACL, + Logger: log.Default(), AccessLogger: accessLogger, - ErrorLogger: errorLogger, - Dial: dialer.DialContext, + ErrorLogger: errorLogger, + Dial: dialer.DialContext, }) if err != nil { log.Fatalf("[ERR] Create socks5 server: %s", err) From 7bd530d1aa73d791acd72d83fd81ef6f55fe8121 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:50:38 +0800 Subject: [PATCH 5/9] Upgrade cron to v3, defer write the access log, rotate the log files --- src/ganted2radius.go | 94 +++++++++++++++++++++++++++++++++-------- src/go-socks5/socks5.go | 26 ++++++------ src/go.mod | 8 ++-- src/go.sum | 6 ++- src/main.go | 36 +++++++++++----- 5 files changed, 124 insertions(+), 46 deletions(-) diff --git a/src/ganted2radius.go b/src/ganted2radius.go index e0193d3..4646add 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -4,43 +4,79 @@ import ( "bufio" "fmt" "io" + "log" "os" "path/filepath" + "regexp" "strconv" "strings" "time" + "github.com/klauspost/compress/zstd" "golang.org/x/net/context" "layeh.com/radius" "layeh.com/radius/rfc2865" "layeh.com/radius/rfc2866" ) -func archiveLog(src, dst string) error { - srcFile, err := os.Open(src) +func compressFile(filename string) error { + // open the file + file, err := os.Open(filename) if err != nil { return err } - defer srcFile.Close() - - dstFile, err := os.Create(dst) + defer file.Close() + // create the compressed file, err if it already exists + compressedFilename := filename + ".zst" + compressedFile, err := os.Create(compressedFilename) if err != nil { return err } - defer dstFile.Close() - // Copy the source file to the destination file - _, err = io.Copy(dstFile, srcFile) + defer compressedFile.Close() + // create the zstd writer + zw, err := zstd.NewWriter(compressedFile) if err != nil { return err } - // Clear the source file - err = os.Truncate(src, 0) + // copy the file to the zstd writer + _, err = io.Copy(zw, file) if err != nil { return err } + // flush the zstd writer + if err := zw.Close(); err != nil { + return err + } + // remove the original file + if err := os.Remove(filename); err != nil { + return err + } return nil } +func archiveLogs(logDir string) error { + logPattern := regexp.MustCompile(`^access-\d{14}\.log$`) + // compress all access-.log files to access-.log.zst + err := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // check if the file is a access-.log file + if !info.IsDir() && logPattern.MatchString(info.Name()) { + // compress the file + if err := compressFile(path); err != nil { + fmt.Fprintf(os.Stderr, "Error compressing file %s: %v\n", path, err) + return err + } else { + fmt.Printf("Compressed file %s\n", path) + return nil + } + } + return nil + }) + return err +} + func parseLogFile(filename string) (map[string]int, error) { file, err := os.Open(filename) if err != nil { @@ -54,14 +90,22 @@ func parseLogFile(filename string) (map[string]int, error) { for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) - if len(fields) < 8 { + if len(fields) != 8 { fmt.Printf("Skipping malformed line: %s\n", line) continue } identity := fields[3] - bytesIn, _ := strconv.Atoi(fields[6]) - bytesOut, _ := strconv.Atoi(fields[7]) + bytesIn, err := strconv.Atoi(fields[6]) + if err != nil { + fmt.Printf("Error parsing bytes in: %v\n", err) + continue + } + bytesOut, err := strconv.Atoi(fields[7]) + if err != nil { + fmt.Printf("Error parsing bytes out: %v\n", err) + continue + } totalBytes := bytesIn + bytesOut stats[identity] += totalBytes @@ -113,11 +157,27 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error return nil } -func (r *RadiusCredentials) accounting(accessLogFile string, archiveLogFile string) error { - if archiveLogFile == "" { - archiveLogFile = filepath.Join(filepath.Dir(accessLogFile), "archive.log") +func (r *RadiusCredentials) accounting(accessLogger *log.Logger) error { + // Get the log directory + accessLogFileHandler, ok := accessLogger.Writer().(*os.File) + if !ok { + return fmt.Errorf("access log file is not a file") + } + accessLogFile := accessLogFileHandler.Name() + logDir := filepath.Dir(accessLogFile) + // Compress all access-.log files in the log directory + if err := archiveLogs(logDir); err != nil { + return err + } + // rename the access.log file to access-.log + now := time.Now() + dotIndex := strings.LastIndex(accessLogFile, ".") + archiveLogFile := accessLogFile[:dotIndex] + "-" + now.Format("20060102150405") + accessLogFile[dotIndex:] + if err := os.Rename(accessLogFile, archiveLogFile); err != nil { + return err } - if err := archiveLog(accessLogFile, archiveLogFile); err != nil { + // ask accessLogger to reopen the access.log file + if err := setFileLoggerOutput(accessLogger, accessLogFile); err != nil { return err } stats, err := parseLogFile(archiveLogFile) diff --git a/src/go-socks5/socks5.go b/src/go-socks5/socks5.go index dfc8799..357d134 100644 --- a/src/go-socks5/socks5.go +++ b/src/go-socks5/socks5.go @@ -63,7 +63,7 @@ type Config struct { // ConnWrapper is a wrapper around a net.Conn that provides a way to log read/write bytes type ConnWrapper struct { net.Conn - ReadBytes int64 + ReadBytes int64 WriteBytes int64 } @@ -197,6 +197,19 @@ func (s *Server) ServeConn(conn net.Conn) error { request.AuthContext = authContext request.RemoteAddr = &AddrSpec{IP: remoteAddr.IP, Port: remoteAddr.Port} + // log access + // remoteAddr, identity, time_now, request, bytes_in, bytes_out + defer func() { + s.config.AccessLogger.Printf("%s %s %s %s %d %d", + remoteAddr, + authContext.Payload["Username"], + time.Now().Format(time.RFC3339), + request.DestAddr.String(), + wrappedConn.ReadBytes, + wrappedConn.WriteBytes, + ) + }() + // Process the client request if err := s.handleRequest(request, wrappedConn); err != nil { err = fmt.Errorf("Failed to handle request: %v", err) @@ -204,16 +217,5 @@ func (s *Server) ServeConn(conn net.Conn) error { return err } - // log access - // remoteAddr, identity, time_now, request, bytes_in, bytes_out - s.config.AccessLogger.Printf("%s %s %s %s %d %d", - remoteAddr, - authContext.Payload["Username"], - time.Now().Format(time.RFC3339), - request.DestAddr.String(), - wrappedConn.ReadBytes, - wrappedConn.WriteBytes, - ) - return nil } diff --git a/src/go.mod b/src/go.mod index 15221e4..67643d6 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,10 +7,8 @@ replace github.com/armon/go-socks5 => ./go-socks5 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/kisom/netallow v0.0.0-20200609175051-08f6b004e41a + github.com/klauspost/compress v1.17.9 + github.com/robfig/cron/v3 v3.0.1 + golang.org/x/net v0.25.0 layeh.com/radius v0.0.0-20231213012653-1006025d24f8 ) - -require ( - github.com/robfig/cron v1.2.0 // indirect - golang.org/x/net v0.25.0 // indirect -) diff --git a/src/go.sum b/src/go.sum index 21505b7..546dde8 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,7 +1,9 @@ github.com/kisom/netallow v0.0.0-20200609175051-08f6b004e41a h1:4T7cUpk4OIqaxn7i41yVYEU7/4gjTKuvqlrSqbBwwe0= github.com/kisom/netallow v0.0.0-20200609175051-08f6b004e41a/go.mod h1:fHrMiR3Isu09r3MBg9oqlp0E+qBtRp6qsig0hpmgXYg= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/src/main.go b/src/main.go index 4fb2d8a..7df4e99 100644 --- a/src/main.go +++ b/src/main.go @@ -12,7 +12,7 @@ import ( "github.com/armon/go-socks5" "github.com/kisom/netallow" - "github.com/robfig/cron" + "github.com/robfig/cron/v3" "layeh.com/radius" "layeh.com/radius/rfc2865" "path/filepath" @@ -120,16 +120,20 @@ func (r *RadiusCredentials) StartGCWorker() { go r.gcworker() } -func (r *RadiusCredentials) accountingCron(accessLogFile, archiveLogFile string) *cron.Cron { +func (r *RadiusCredentials) accountingCron(accessLogger, errorLogger *log.Logger) *cron.Cron { // hourly accounting cron job c := cron.New() - c.AddFunc("0 0 * * * *", func() { + _, err := c.AddFunc("@hourly", func() { // accounting - err := r.accounting(accessLogFile, archiveLogFile) + err := r.accounting(accessLogger) if err != nil { - log.Printf("[ERR] Accounting error: %s\n", err) + errorLogger.Printf("Accounting error: %s\n", err) } }) + if err != nil { + log.Fatalf("[ERR] Add accounting cron job: %s", err) + return nil + } c.Start() return c } @@ -157,16 +161,24 @@ func init() { } } -func initFileLogger(filePath string) (*log.Logger, error) { +func setFileLoggerOutput(logger *log.Logger, filePath string) error { if filePath == "" { - return nil, nil + return nil } file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { log.Fatalf("[ERR] Open log file: %s", err) + return err + } + logger.SetOutput(file) + return nil +} + +func initFileLogger(filePath string) (*log.Logger, error) { + logger := log.New(os.Stdout, "", log.LstdFlags) + if err := setFileLoggerOutput(logger, filePath); err != nil { return nil, err } - logger := log.New(file, "", log.LstdFlags) return logger, nil } @@ -208,8 +220,6 @@ func main() { } gantedLogDir := getEnv("GANTED_LOG_DIR", "/var/log/ganted") credentials.StartGCWorker() - c := credentials.accountingCron(filepath.Join(gantedLogDir, "access.log"), filepath.Join(gantedLogDir, "archive.log")) - defer c.Stop() accessLogger, err := initFileLogger(filepath.Join(gantedLogDir, "access.log")) if err != nil { @@ -219,6 +229,12 @@ func main() { if err != nil { log.Fatalf("[ERR] Failed to init error log: %s", err) } + c := credentials.accountingCron(accessLogger, errorLogger) + if c == nil { + log.Fatalf("[ERR] Failed to start accounting cron job") + } else { + defer c.Stop() + } server, err := socks5.New(&socks5.Config{ Credentials: credentials, Rules: serverACL, From 3d4c6e2b8288ec12fab8bd871d652764e233edf8 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:13:31 +0800 Subject: [PATCH 6/9] Just rotate when accounting, and archive every 24 logfiles(daily) --- src/ganted2radius.go | 105 ++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/src/ganted2radius.go b/src/ganted2radius.go index 4646add..3d79869 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -4,10 +4,12 @@ import ( "bufio" "fmt" "io" + "io/fs" "log" "os" "path/filepath" "regexp" + "sort" "strconv" "strings" "time" @@ -19,16 +21,16 @@ import ( "layeh.com/radius/rfc2866" ) -func compressFile(filename string) error { +func compressFile(filepath string) error { // open the file - file, err := os.Open(filename) + file, err := os.Open(filepath) if err != nil { return err } defer file.Close() // create the compressed file, err if it already exists - compressedFilename := filename + ".zst" - compressedFile, err := os.Create(compressedFilename) + compressedFilepath := filepath + ".zst" + compressedFile, err := os.Create(compressedFilepath) if err != nil { return err } @@ -48,33 +50,82 @@ func compressFile(filename string) error { return err } // remove the original file - if err := os.Remove(filename); err != nil { + if err := os.Remove(filepath); err != nil { return err } return nil } -func archiveLogs(logDir string) error { +func findLogs(logDir string, logPattern *regexp.Regexp) ([]string, error) { + var logFiles []string + err := filepath.WalkDir(logDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && logPattern.MatchString(d.Name()) { + logFiles = append(logFiles, path) + } + return nil + }) + return logFiles, err +} + +func archiveLogs(logDir string, maxBackup int) error { logPattern := regexp.MustCompile(`^access-\d{14}\.log$`) - // compress all access-.log files to access-.log.zst - err := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error { + logFiles, err := findLogs(logDir, logPattern) + if err != nil { + return err + } + if len(logFiles) >= maxBackup { + sort.Strings(logFiles) + logFiles = logFiles[:maxBackup-1] + // create archive log file with current date + date := time.Now().Format("20060102") + archiveFileName := fmt.Sprintf("access-%s.log", date) + archiveFilePath := filepath.Join(logDir, archiveFileName) + // err if access-.log.zst exists, and do nothing + if _, err := os.Stat(archiveFilePath + ".zst"); !os.IsNotExist(err) { + return fmt.Errorf("File %s exists.", archiveFileName+".zst") + } + archiveFile, err := os.Create(archiveFilePath) if err != nil { return err } - // check if the file is a access-.log file - if !info.IsDir() && logPattern.MatchString(info.Name()) { - // compress the file - if err := compressFile(path); err != nil { - fmt.Fprintf(os.Stderr, "Error compressing file %s: %v\n", path, err) - return err + defer archiveFile.Close() + defer func() { + // If the archiveFile exists, some error occur, and archiveFile need to delete + // As the compressFile will remove the original archiveFile + // Else, everything is ok, delete the original log files + if _, err := os.Stat(archiveFilePath); !os.IsNotExist(err) { + if err := os.Remove(archiveFilePath); err != nil { + fmt.Errorf("err when removing file %s", archiveFilePath) + } } else { - fmt.Printf("Compressed file %s\n", path) - return nil + for _, logFile := range logFiles { + if err := os.Remove(logFile); err != nil { + fmt.Errorf("err when removing file %s", archiveFilePath) + } + } } + }() + // concatenate `maxBackup` access-.log files to access-.log + for _, logFile := range logFiles { + src, err := os.Open(logFile) + if err != nil { + return err + } + _, err = io.Copy(archiveFile, src) + if err != nil { + return err + } + src.Close() } - return nil - }) - return err + // compress access-.log + if err := compressFile(archiveFilePath); err != nil { + return err + } + } + return nil } func parseLogFile(filename string) (map[string]int, error) { @@ -165,27 +216,23 @@ func (r *RadiusCredentials) accounting(accessLogger *log.Logger) error { } accessLogFile := accessLogFileHandler.Name() logDir := filepath.Dir(accessLogFile) - // Compress all access-.log files in the log directory - if err := archiveLogs(logDir); err != nil { - return err - } // rename the access.log file to access-.log now := time.Now() dotIndex := strings.LastIndex(accessLogFile, ".") - archiveLogFile := accessLogFile[:dotIndex] + "-" + now.Format("20060102150405") + accessLogFile[dotIndex:] - if err := os.Rename(accessLogFile, archiveLogFile); err != nil { + accountingLogFile := accessLogFile[:dotIndex] + "-" + now.Format("20060102150405") + accessLogFile[dotIndex:] + if err := os.Rename(accessLogFile, accountingLogFile); err != nil { return err } // ask accessLogger to reopen the access.log file if err := setFileLoggerOutput(accessLogger, accessLogFile); err != nil { return err } - stats, err := parseLogFile(archiveLogFile) + stats, err := parseLogFile(accountingLogFile) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing log file: %v\n", err) return err } - + // Sending accounting data for identity, bytes := range stats { err := r.sendAccountingData(identity, bytes) if err != nil { @@ -194,5 +241,9 @@ func (r *RadiusCredentials) accounting(accessLogger *log.Logger) error { fmt.Printf("Sent accounting data for identity %s\n", identity) } } + // Compress all(actually 24) access-.log files in the log directory + if err := archiveLogs(logDir, 24); err != nil { + return err + } return nil } From a6dc8521bce03489d76d4e46954279f46aca0d19 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:49:12 +0800 Subject: [PATCH 7/9] rename archive file, make sure err during removing could be handled Delete `logFiles = logFiles[:maxBackup-1]` --- src/ganted2radius.go | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/ganted2radius.go b/src/ganted2radius.go index 3d79869..d8b8af2 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -78,36 +78,18 @@ func archiveLogs(logDir string, maxBackup int) error { } if len(logFiles) >= maxBackup { sort.Strings(logFiles) - logFiles = logFiles[:maxBackup-1] // create archive log file with current date date := time.Now().Format("20060102") - archiveFileName := fmt.Sprintf("access-%s.log", date) + archiveFileName := fmt.Sprintf("archived-access-%s.log", date) archiveFilePath := filepath.Join(logDir, archiveFileName) // err if access-.log.zst exists, and do nothing if _, err := os.Stat(archiveFilePath + ".zst"); !os.IsNotExist(err) { - return fmt.Errorf("File %s exists.", archiveFileName+".zst") + return fmt.Errorf("file %s exists", archiveFileName+".zst") } archiveFile, err := os.Create(archiveFilePath) if err != nil { return err } - defer archiveFile.Close() - defer func() { - // If the archiveFile exists, some error occur, and archiveFile need to delete - // As the compressFile will remove the original archiveFile - // Else, everything is ok, delete the original log files - if _, err := os.Stat(archiveFilePath); !os.IsNotExist(err) { - if err := os.Remove(archiveFilePath); err != nil { - fmt.Errorf("err when removing file %s", archiveFilePath) - } - } else { - for _, logFile := range logFiles { - if err := os.Remove(logFile); err != nil { - fmt.Errorf("err when removing file %s", archiveFilePath) - } - } - } - }() // concatenate `maxBackup` access-.log files to access-.log for _, logFile := range logFiles { src, err := os.Open(logFile) @@ -124,6 +106,21 @@ func archiveLogs(logDir string, maxBackup int) error { if err := compressFile(archiveFilePath); err != nil { return err } + archiveFile.Close() + // If the archiveFile exists, some error occur, and archiveFile need to delete + // As the compressFile will remove the original archiveFile + // Else, everything is ok, delete the original log files + if _, err := os.Stat(archiveFilePath); !os.IsNotExist(err) { + if err := os.Remove(archiveFilePath); err != nil { + return fmt.Errorf("err when removing file %s", archiveFilePath) + } + } else { + for _, logFile := range logFiles { + if err := os.Remove(logFile); err != nil { + return fmt.Errorf("err when removing file %s", archiveFilePath) + } + } + } } return nil } @@ -241,7 +238,7 @@ func (r *RadiusCredentials) accounting(accessLogger *log.Logger) error { fmt.Printf("Sent accounting data for identity %s\n", identity) } } - // Compress all(actually 24) access-.log files in the log directory + // Compress all access-.log files in the log directory if err := archiveLogs(logDir, 24); err != nil { return err } From bc71a8fbfc02248706f5364755c3a25da1d62e30 Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:39:16 +0800 Subject: [PATCH 8/9] Requested changes of https://github.com/ustclug/light-socks5/pull/3 1. defer the close of `archiveFile` and `src` 2. use `log.Printf` to replace `fmt.Printf` and `fmt.Fprintf` 3. `panic()` if the log directory does not exist and can not be created either 4. fetching the previous writer and close it if valid --- src/ganted2radius.go | 27 ++++++++++++++------------- src/main.go | 12 ++++++++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/ganted2radius.go b/src/ganted2radius.go index d8b8af2..9bcf18e 100644 --- a/src/ganted2radius.go +++ b/src/ganted2radius.go @@ -90,23 +90,24 @@ func archiveLogs(logDir string, maxBackup int) error { if err != nil { return err } + defer archiveFile.Close() // concatenate `maxBackup` access-.log files to access-.log for _, logFile := range logFiles { src, err := os.Open(logFile) if err != nil { return err } + defer src.Close() _, err = io.Copy(archiveFile, src) if err != nil { return err } - src.Close() + } // compress access-.log if err := compressFile(archiveFilePath); err != nil { return err } - archiveFile.Close() // If the archiveFile exists, some error occur, and archiveFile need to delete // As the compressFile will remove the original archiveFile // Else, everything is ok, delete the original log files @@ -139,19 +140,19 @@ func parseLogFile(filename string) (map[string]int, error) { line := scanner.Text() fields := strings.Fields(line) if len(fields) != 8 { - fmt.Printf("Skipping malformed line: %s\n", line) + log.Printf("Skipping malformed line: %s\n", line) continue } identity := fields[3] bytesIn, err := strconv.Atoi(fields[6]) if err != nil { - fmt.Printf("Error parsing bytes in: %v\n", err) + log.Printf("Error parsing bytes in: %v\n", err) continue } bytesOut, err := strconv.Atoi(fields[7]) if err != nil { - fmt.Printf("Error parsing bytes out: %v\n", err) + log.Printf("Error parsing bytes out: %v\n", err) continue } totalBytes := bytesIn + bytesOut @@ -165,7 +166,7 @@ func parseLogFile(filename string) (map[string]int, error) { func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error { // send an CodeAccessRequest for test sessionID := strconv.FormatInt(time.Now().Unix(), 10) - fmt.Printf("Sending accounting data for identity %s, session ID %s, bytes %d\n", identity, sessionID, bytes) + log.Printf("Sending accounting data for identity %s, session ID %s, bytes %d\n", identity, sessionID, bytes) // Send start accounting packet startPacket := radius.New(radius.CodeAccountingRequest, []byte(r.Secret)) @@ -173,7 +174,7 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error rfc2865.NASIdentifier_SetString(startPacket, r.NASIdentifier) rfc2866.AcctSessionID_Set(startPacket, []byte(sessionID)) rfc2866.AcctStatusType_Set(startPacket, rfc2866.AcctStatusType_Value_Start) - fmt.Printf("Sending start packet\n") + // log.Printf("Sending start packet\n") startReply, err := radius.Exchange(context.Background(), startPacket, r.AccountingServer) if err != nil { @@ -182,7 +183,7 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error if startReply.Code != radius.CodeAccountingResponse { return fmt.Errorf("unexpected response from RADIUS server") } - fmt.Printf("Received start reply\n") + // log.Printf("Received start reply\n") // Send stop accounting packet stopPacket := radius.New(radius.CodeAccountingRequest, r.Secret) @@ -191,7 +192,7 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error rfc2866.AcctSessionID_SetString(stopPacket, sessionID) rfc2866.AcctStatusType_Set(stopPacket, rfc2866.AcctStatusType_Value_Stop) rfc2866.AcctOutputOctets_Set(stopPacket, rfc2866.AcctOutputOctets(bytes)) - fmt.Printf("Sending stop packet\n") + // log.Printf("Sending stop packet\n") stopReply, err := radius.Exchange(context.Background(), stopPacket, r.AccountingServer) if err != nil { @@ -200,7 +201,7 @@ func (r *RadiusCredentials) sendAccountingData(identity string, bytes int) error if stopReply.Code != radius.CodeAccountingResponse { return fmt.Errorf("unexpected response from RADIUS server") } - fmt.Printf("Received stop reply\n") + // log.Printf("Received stop reply\n") return nil } @@ -226,16 +227,16 @@ func (r *RadiusCredentials) accounting(accessLogger *log.Logger) error { } stats, err := parseLogFile(accountingLogFile) if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing log file: %v\n", err) + log.Printf("[ERR] Failed to parse log file %s: %v\n", accountingLogFile, err) return err } // Sending accounting data for identity, bytes := range stats { err := r.sendAccountingData(identity, bytes) if err != nil { - fmt.Fprintf(os.Stderr, "Error sending accounting data for identity %s: %v\n", identity, err) + log.Printf("[ERR] Failed to send accounting data for identity %s: %v\n", identity, err) } else { - fmt.Printf("Sent accounting data for identity %s\n", identity) + log.Printf("Sent accounting data for identity %s\n", identity) } } // Compress all access-.log files in the log directory diff --git a/src/main.go b/src/main.go index 7df4e99..58ad23d 100644 --- a/src/main.go +++ b/src/main.go @@ -132,7 +132,6 @@ func (r *RadiusCredentials) accountingCron(accessLogger, errorLogger *log.Logger }) if err != nil { log.Fatalf("[ERR] Add accounting cron job: %s", err) - return nil } c.Start() return c @@ -157,7 +156,9 @@ func init() { // init dir GANTED_LOG_DIR gantedLogDir := getEnv("GANTED_LOG_DIR", "/var/log/ganted") if _, err := os.Stat(gantedLogDir); os.IsNotExist(err) { - os.Mkdir(gantedLogDir, 0755) + if err := os.Mkdir(gantedLogDir, 0755); err != nil { + panic(err) + } } } @@ -165,12 +166,15 @@ func setFileLoggerOutput(logger *log.Logger, filePath string) error { if filePath == "" { return nil } - file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { log.Fatalf("[ERR] Open log file: %s", err) - return err } + prevWriter := logger.Writer() logger.SetOutput(file) + if closer, ok := prevWriter.(io.Closer); ok { + closer.Close() + } return nil } From ff51f6867bce2d7a48663d874f1c2b359264b98b Mon Sep 17 00:00:00 2001 From: MirageTurtle <60972592+MirageTurtle@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:40:58 +0800 Subject: [PATCH 9/9] Format error message, use `log.Fatalf` for panic message of `Mkdir` --- src/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.go b/src/main.go index 58ad23d..472785a 100644 --- a/src/main.go +++ b/src/main.go @@ -131,7 +131,7 @@ func (r *RadiusCredentials) accountingCron(accessLogger, errorLogger *log.Logger } }) if err != nil { - log.Fatalf("[ERR] Add accounting cron job: %s", err) + log.Fatalf("[ERR] Failed to add accounting cron job: %s", err) } c.Start() return c @@ -157,7 +157,7 @@ func init() { gantedLogDir := getEnv("GANTED_LOG_DIR", "/var/log/ganted") if _, err := os.Stat(gantedLogDir); os.IsNotExist(err) { if err := os.Mkdir(gantedLogDir, 0755); err != nil { - panic(err) + log.Fatalf("[ERR] Failed to create ganted log directory %s: %s", gantedLogDir, err) } } } @@ -168,7 +168,7 @@ func setFileLoggerOutput(logger *log.Logger, filePath string) error { } file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { - log.Fatalf("[ERR] Open log file: %s", err) + log.Fatalf("[ERR] Failed to open file %s: %s", filePath, err) } prevWriter := logger.Writer() logger.SetOutput(file)