fix: sécuriser shutdown, config par défaut et reconnexion socket

Co-authored-by: aider (openrouter/openai/gpt-5.3-codex) <aider@aider.chat>
This commit is contained in:
Jacquin Antoine
2026-02-25 21:44:40 +01:00
parent 617ecd2014
commit 6cd6c4c3b8
11 changed files with 394 additions and 56 deletions

View File

@ -103,13 +103,20 @@ func (w *UnixSocketWriter) Write(rec api.LogRecord) error {
w.mutex.Lock()
defer w.mutex.Unlock()
// Connect if not already connected
if w.conn == nil {
ensureConn := func() error {
if w.conn != nil {
return nil
}
conn, err := net.Dial("unix", w.socketPath)
if err != nil {
return fmt.Errorf("failed to connect to socket %s: %w", w.socketPath, err)
}
w.conn = conn
return nil
}
if err := ensureConn(); err != nil {
return err
}
data, err := json.Marshal(rec)
@ -120,12 +127,18 @@ func (w *UnixSocketWriter) Write(rec api.LogRecord) error {
// Add newline for line-based protocols
data = append(data, '\n')
_, err = w.conn.Write(data)
if err != nil {
// Connection failed, try to reconnect
w.conn.Close()
if _, err = w.conn.Write(data); err != nil {
_ = w.conn.Close()
w.conn = nil
return fmt.Errorf("failed to write to socket: %w", err)
if err2 := ensureConn(); err2 != nil {
return fmt.Errorf("failed to write to socket and reconnect failed: %w", err2)
}
if _, err2 := w.conn.Write(data); err2 != nil {
_ = w.conn.Close()
w.conn = nil
return fmt.Errorf("failed to write to socket after reconnect: %w", err2)
}
}
return nil

View File

@ -1,10 +1,15 @@
package output
import (
"bufio"
"bytes"
"encoding/json"
"net"
"os"
"path/filepath"
"sync"
"testing"
"time"
"ja4sentinel/api"
)
@ -102,7 +107,6 @@ func TestMultiWriter(t *testing.T) {
defer fileWriter.Close()
multiWriter.Add(fileWriter)
multiWriter.Add(NewStdoutWriter())
rec := api.LogRecord{
SrcIP: "192.168.1.1",
@ -233,3 +237,123 @@ func TestUnixSocketWriter(t *testing.T) {
writer.Close()
}
type unixTestServer struct {
listener net.Listener
received chan string
mu sync.Mutex
conns map[net.Conn]struct{}
}
func newUnixTestServer(path string) (*unixTestServer, error) {
_ = os.Remove(path)
ln, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
s := &unixTestServer{
listener: ln,
received: make(chan string, 10),
conns: make(map[net.Conn]struct{}),
}
go s.serve()
return s, nil
}
func (s *unixTestServer) serve() {
for {
conn, err := s.listener.Accept()
if err != nil {
return
}
s.mu.Lock()
s.conns[conn] = struct{}{}
s.mu.Unlock()
go func(c net.Conn) {
defer func() {
s.mu.Lock()
delete(s.conns, c)
s.mu.Unlock()
_ = c.Close()
}()
scanner := bufio.NewScanner(c)
for scanner.Scan() {
s.received <- scanner.Text()
}
}(conn)
}
}
func (s *unixTestServer) close(path string) {
_ = s.listener.Close()
s.mu.Lock()
for c := range s.conns {
_ = c.Close()
}
s.mu.Unlock()
_ = os.Remove(path)
}
func TestUnixSocketWriter_ReconnectAndWrite(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "ja4sentinel.sock")
server1, err := newUnixTestServer(socketPath)
if err != nil {
t.Fatalf("failed to start first unix test server: %v", err)
}
writer, err := NewUnixSocketWriter(socketPath)
if err != nil {
t.Fatalf("NewUnixSocketWriter() error = %v", err)
}
defer writer.Close()
rec1 := api.LogRecord{
SrcIP: "192.168.1.1",
SrcPort: 11111,
DstIP: "10.0.0.1",
DstPort: 443,
JA4: "first",
}
if err := writer.Write(rec1); err != nil {
t.Fatalf("first Write() error = %v", err)
}
select {
case <-server1.received:
case <-time.After(2 * time.Second):
t.Fatal("timeout waiting first message on unix socket")
}
server1.close(socketPath)
server2, err := newUnixTestServer(socketPath)
if err != nil {
t.Fatalf("failed to restart unix test server: %v", err)
}
defer server2.close(socketPath)
rec2 := api.LogRecord{
SrcIP: "192.168.1.2",
SrcPort: 22222,
DstIP: "10.0.0.2",
DstPort: 443,
JA4: "second",
}
if err := writer.Write(rec2); err != nil {
t.Fatalf("second Write() after reconnect error = %v", err)
}
select {
case <-server2.received:
case <-time.After(2 * time.Second):
t.Fatal("timeout waiting second message after reconnect")
}
}