Skip to content

Commit afe36e4

Browse files
committed
feat(agent/syslog): add RFC 5424 octet counting framing support and improve message handling
1 parent 6f6f844 commit afe36e4

File tree

1 file changed

+110
-6
lines changed

1 file changed

+110
-6
lines changed

agent/modules/syslog.go

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"net"
1111
"os"
12+
"strconv"
1213
"strings"
1314
"time"
1415

@@ -19,6 +20,20 @@ import (
1920
"github.com/utmstack/UTMStack/agent/utils"
2021
)
2122

23+
const (
24+
MinBufferSize = 480
25+
RecommendedBufferSize = 2048
26+
MaxBufferSize = 8192
27+
UDPBufferSize = 2048
28+
)
29+
30+
type FramingMethod int
31+
32+
const (
33+
FramingNewline FramingMethod = iota
34+
FramingOctetCounting
35+
)
36+
2237
type SyslogModule struct {
2338
DataType string
2439
TCPListener listenerTCP
@@ -206,7 +221,7 @@ func (m *SyslogModule) enableUDP() {
206221
m.UDPListener.Listener = listener
207222
m.UDPListener.CTX, m.UDPListener.Cancel = context.WithCancel(context.Background())
208223

209-
buffer := make([]byte, 1024)
224+
buffer := make([]byte, UDPBufferSize)
210225
msgChannel := make(chan string)
211226

212227
go m.handleConnectionUDP(msgChannel)
@@ -291,6 +306,88 @@ func (m *SyslogModule) disableUDP() {
291306
}
292307
}
293308

309+
// detectFramingMethod detects the syslog framing method by peeking at the first byte
310+
func detectFramingMethod(reader *bufio.Reader) (FramingMethod, error) {
311+
firstByte, err := reader.Peek(1)
312+
if err != nil {
313+
utils.Logger.ErrorF("failed to peek first byte for framing detection: %v", err)
314+
return 0, fmt.Errorf("failed to peek first byte: %w", err)
315+
}
316+
317+
if firstByte[0] >= '0' && firstByte[0] <= '9' {
318+
return FramingOctetCounting, nil
319+
}
320+
321+
if firstByte[0] == '<' {
322+
return FramingNewline, nil
323+
}
324+
325+
utils.Logger.ErrorF("unknown framing method detected, first byte: 0x%02x", firstByte[0])
326+
return 0, fmt.Errorf("unknown framing method, first byte: 0x%02x", firstByte[0])
327+
}
328+
329+
// readOctetCountingFrame reads a syslog message using octet counting framing method
330+
func readOctetCountingFrame(reader *bufio.Reader) (string, error) {
331+
lengthStr, err := reader.ReadString(' ')
332+
if err != nil {
333+
utils.Logger.ErrorF("failed to read message length in octet counting frame: %v", err)
334+
return "", fmt.Errorf("failed to read message length: %w", err)
335+
}
336+
337+
lengthStr = strings.TrimSuffix(lengthStr, " ")
338+
msgLen, err := strconv.Atoi(lengthStr)
339+
if err != nil {
340+
utils.Logger.ErrorF("invalid message length '%s' in octet counting frame: %v", lengthStr, err)
341+
return "", fmt.Errorf("invalid message length '%s': %w", lengthStr, err)
342+
}
343+
344+
if msgLen < 1 {
345+
utils.Logger.ErrorF("message length %d is too small (minimum 1 byte)", msgLen)
346+
return "", fmt.Errorf("message length %d is too small (minimum 1)", msgLen)
347+
}
348+
if msgLen > MaxBufferSize {
349+
utils.Logger.ErrorF("message length %d exceeds maximum %d bytes", msgLen, MaxBufferSize)
350+
return "", fmt.Errorf("message length %d exceeds maximum %d", msgLen, MaxBufferSize)
351+
}
352+
353+
msgBytes := make([]byte, msgLen)
354+
_, err = io.ReadFull(reader, msgBytes)
355+
if err != nil {
356+
utils.Logger.ErrorF("failed to read %d byte message body: %v", msgLen, err)
357+
return "", fmt.Errorf("failed to read %d byte message body: %w", msgLen, err)
358+
}
359+
360+
return string(msgBytes), nil
361+
}
362+
363+
// readNewlineFrame reads a syslog message using newline-delimited framing method
364+
func readNewlineFrame(reader *bufio.Reader) (string, error) {
365+
message, err := reader.ReadString('\n')
366+
if err != nil {
367+
utils.Logger.ErrorF("failed to read newline-delimited message: %v", err)
368+
return "", fmt.Errorf("failed to read newline-delimited message: %w", err)
369+
}
370+
return message, nil
371+
}
372+
373+
// readSyslogMessage reads a syslog message with automatic framing detection
374+
func readSyslogMessage(reader *bufio.Reader) (string, error) {
375+
method, err := detectFramingMethod(reader)
376+
if err != nil {
377+
return "", err
378+
}
379+
380+
switch method {
381+
case FramingOctetCounting:
382+
return readOctetCountingFrame(reader)
383+
case FramingNewline:
384+
return readNewlineFrame(reader)
385+
default:
386+
utils.Logger.ErrorF("unsupported framing method: %d", method)
387+
return "", fmt.Errorf("unsupported framing method: %d", method)
388+
}
389+
}
390+
294391
func (m *SyslogModule) handleConnectionTCP(c net.Conn) {
295392
defer c.Close()
296393
reader := bufio.NewReader(c)
@@ -336,12 +433,17 @@ func (m *SyslogModule) handleConnectionTCP(c net.Conn) {
336433
case <-m.TCPListener.CTX.Done():
337434
return
338435
default:
339-
message, err := reader.ReadString('\n')
436+
message, err := readSyslogMessage(reader)
340437
if err != nil {
341-
if err == io.EOF || err.(net.Error).Timeout() {
438+
if err == io.EOF {
439+
utils.Logger.Info("TCP connection closed by %s", remoteAddr)
440+
return
441+
}
442+
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
443+
utils.Logger.Info("TCP connection timeout from %s", remoteAddr)
342444
return
343445
}
344-
utils.Logger.ErrorF("error reading tcp data: %v", err)
446+
utils.Logger.ErrorF("error reading syslog message from %s: %v", remoteAddr, err)
345447
return
346448
}
347449
message = config.GetMessageFormated(remoteAddr, message)
@@ -396,15 +498,17 @@ func (m *SyslogModule) handleTLSConnection(conn net.Conn) {
396498
default:
397499
// Set read timeout for each message
398500
conn.SetDeadline(time.Now().Add(30 * time.Second))
399-
message, err := reader.ReadString('\n')
501+
message, err := readSyslogMessage(reader)
400502
if err != nil {
401503
if err == io.EOF {
504+
utils.Logger.Info("TLS connection closed by %s", remoteAddr)
402505
return
403506
}
404507
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
508+
utils.Logger.Info("TLS connection timeout from %s", remoteAddr)
405509
return
406510
}
407-
utils.Logger.ErrorF("error reading TLS data from %s: %v", remoteAddr, err)
511+
utils.Logger.ErrorF("error reading syslog message from %s via TLS: %v", remoteAddr, err)
408512
return
409513
}
410514
message = config.GetMessageFormated(remoteAddr, message)

0 commit comments

Comments
 (0)