diff --git a/.github/workflows/testing-go.yml b/.github/workflows/testing-go.yml index 46eac5c6..92901f58 100644 --- a/.github/workflows/testing-go.yml +++ b/.github/workflows/testing-go.yml @@ -32,7 +32,7 @@ jobs: - 'collectors' - 'loggers' - 'transformers' - - 'netlib' + - 'netutils' - 'processors' # exclude: # - os-version: macos-latest @@ -148,7 +148,7 @@ jobs: - id: count_tests run: | - data=$(sudo go test -timeout 360s -v ./collectors ./processors ./dnsutils ./netlib ./loggers ./transformers ./pkgconfig ./pkglinker ./pkgutils ././ 2>&1 | grep -c RUN) + data=$(sudo go test -timeout 360s -v ./collectors ./processors ./dnsutils ./netutils ./loggers ./transformers ./pkgconfig ./pkglinker ./pkgutils ././ 2>&1 | grep -c RUN) echo "Count of Tests: $data" echo "data=$data" >> $GITHUB_OUTPUT diff --git a/Makefile b/Makefile index a14be79e..1b7ab5e9 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ tests: check-go @go test -race -cover -v @go test ./pkgconfig/ -race -cover -v @go test ./pkglinker/ -race -cover -v - @go test ./netlib/ -race -cover -v + @go test ./netutils/ -race -cover -v @go test -timeout 90s ./dnsutils/ -race -cover -v @go test -timeout 90s ./transformers/ -race -cover -v @go test -timeout 90s ./collectors/ -race -cover -v diff --git a/collectors/dnsmessage.go b/collectors/dnsmessage.go index e61829c8..b017b899 100644 --- a/collectors/dnsmessage.go +++ b/collectors/dnsmessage.go @@ -8,7 +8,6 @@ import ( "reflect" "regexp" "strings" - "time" "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-dnscollector/pkgconfig" @@ -31,58 +30,20 @@ type MatchSource struct { } type DNSMessage struct { - doneRun, stopRun chan bool - doneMonitor, stopMonitor chan bool - config *pkgconfig.Config - configChan chan *pkgconfig.Config - inputChan chan dnsutils.DNSMessage - logger *logger.Logger - name string - droppedRoutes, defaultRoutes []pkgutils.Worker - dropped chan string - droppedCount map[string]int + *pkgutils.Collector + inputChan chan dnsutils.DNSMessage } -func NewDNSMessage(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DNSMessage { - logger.Info(pkgutils.PrefixLogCollector+"[%s] dnsmessage - enabled", name) +func NewDNSMessage(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DNSMessage { s := &DNSMessage{ - doneRun: make(chan bool), - doneMonitor: make(chan bool), - stopRun: make(chan bool), - stopMonitor: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - inputChan: make(chan dnsutils.DNSMessage, config.Collectors.DNSMessage.ChannelBufferSize), - logger: logger, - name: name, - // RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name), - dropped: make(chan string), - droppedCount: map[string]int{}, + Collector: pkgutils.NewCollector(config, logger, name, "dnsmessage"), + inputChan: make(chan dnsutils.DNSMessage, config.Collectors.DNSMessage.ChannelBufferSize), } + s.SetDefaultRoutes(next) s.ReadConfig() return s } -func (c *DNSMessage) GetName() string { return c.name } - -func (c *DNSMessage) AddDroppedRoute(wrk pkgutils.Worker) { - // c.RoutingHandler.AddDroppedRoute(wrk) - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *DNSMessage) AddDefaultRoute(wrk pkgutils.Worker) { - // c.RoutingHandler.AddDefaultRoute(wrk) - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -// deprecated function -func (c *DNSMessage) SetLoggers(loggers []pkgutils.Worker) {} - -// deprecated function -func (c *DNSMessage) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return nil, nil -} - func (c *DNSMessage) ReadConfigMatching(value interface{}) { reflectedValue := reflect.ValueOf(value) if reflectedValue.Kind() == reflect.Map { @@ -101,7 +62,7 @@ func (c *DNSMessage) ReadConfigMatching(value interface{}) { if len(matchSrc) > 0 { sourceData, err := c.LoadData(matchSrc, srcKind) if err != nil { - c.logger.Fatal(err) + c.LogFatal(err) } if len(sourceData.regexList) > 0 { value.(map[interface{}]interface{})[srcKind] = sourceData.regexList @@ -113,36 +74,36 @@ func (c *DNSMessage) ReadConfigMatching(value interface{}) { } } -func (c *DNSMessage) GetInputChannel() chan dnsutils.DNSMessage { - return c.inputChan -} - func (c *DNSMessage) ReadConfig() { // load external file for include - if len(c.config.Collectors.DNSMessage.Matching.Include) > 0 { - for _, value := range c.config.Collectors.DNSMessage.Matching.Include { + if len(c.GetConfig().Collectors.DNSMessage.Matching.Include) > 0 { + for _, value := range c.GetConfig().Collectors.DNSMessage.Matching.Include { c.ReadConfigMatching(value) } } // load external file for exclude - if len(c.config.Collectors.DNSMessage.Matching.Exclude) > 0 { - for _, value := range c.config.Collectors.DNSMessage.Matching.Exclude { + if len(c.GetConfig().Collectors.DNSMessage.Matching.Exclude) > 0 { + for _, value := range c.GetConfig().Collectors.DNSMessage.Matching.Exclude { c.ReadConfigMatching(value) } } } +func (c *DNSMessage) GetInputChannel() chan dnsutils.DNSMessage { + return c.inputChan +} + func (c *DNSMessage) LoadData(matchSource string, srcKind string) (MatchSource, error) { if isFileSource(matchSource) { dataSource, err := c.LoadFromFile(matchSource, srcKind) if err != nil { - c.logger.Fatal(err) + c.LogFatal(err) } return dataSource, nil } else if isURLSource(matchSource) { dataSource, err := c.LoadFromURL(matchSource, srcKind) if err != nil { - c.logger.Fatal(err) + c.LogFatal(err) } return dataSource, nil } @@ -208,61 +169,32 @@ func (c *DNSMessage) LoadFromFile(filePath string, srcKind string) (MatchSource, return matchSources, nil } -func (c *DNSMessage) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *DNSMessage) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] dnsmessage - "+msg, v...) -} - -func (c *DNSMessage) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] dnsmessage - "+msg, v...) -} - -func (c *DNSMessage) Stop() { - c.LogInfo("stopping collector...") - - // read done channel and block until run is terminated - c.LogInfo("stopping to run...") - c.stopRun <- true - <-c.doneRun - - c.LogInfo("stopping monitor...") - c.stopMonitor <- true - <-c.doneMonitor -} - func (c *DNSMessage) Run() { - c.LogInfo("starting collector...") + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() + var err error // prepare next channels - // defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes() - // droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes() - defaultRoutes, defaultNames := pkgutils.GetRoutes(c.defaultRoutes) - droppedRoutes, droppedNames := pkgutils.GetRoutes(c.droppedRoutes) + defaultRoutes, defaultNames := pkgutils.GetRoutes(c.GetDefaultRoutes()) + droppedRoutes, droppedNames := pkgutils.GetRoutes(c.GetDefaultRoutes()) // prepare transforms - subprocessors := transformers.NewTransforms(&c.config.IngoingTransformers, c.logger, c.name, defaultRoutes, 0) - - // start goroutine to count dropped messsages - go c.MonitorNextStanzas() + subprocessors := transformers.NewTransforms(&c.GetConfig().IngoingTransformers, c.GetLogger(), c.GetName(), defaultRoutes, 0) // read incoming dns message c.LogInfo("waiting dns message to process...") -RUN_LOOP: for { select { - case <-c.stopRun: - c.doneRun <- true - break RUN_LOOP + case <-c.OnStop(): + return - case cfg := <-c.configChan: - - // save the new config - c.config = cfg + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) c.ReadConfig() case dm, opened := <-c.inputChan: @@ -276,8 +208,8 @@ RUN_LOOP: matchedInclude := false matchedExclude := false - if len(c.config.Collectors.DNSMessage.Matching.Include) > 0 { - err, matchedInclude = dm.Matching(c.config.Collectors.DNSMessage.Matching.Include) + if len(c.GetConfig().Collectors.DNSMessage.Matching.Include) > 0 { + err, matchedInclude = dm.Matching(c.GetConfig().Collectors.DNSMessage.Matching.Include) if err != nil { c.LogError(err.Error()) } @@ -288,8 +220,8 @@ RUN_LOOP: } } - if len(c.config.Collectors.DNSMessage.Matching.Exclude) > 0 { - err, matchedExclude = dm.Matching(c.config.Collectors.DNSMessage.Matching.Exclude) + if len(c.GetConfig().Collectors.DNSMessage.Matching.Exclude) > 0 { + err, matchedExclude = dm.Matching(c.GetConfig().Collectors.DNSMessage.Matching.Exclude) if err != nil { c.LogError(err.Error()) } @@ -305,12 +237,11 @@ RUN_LOOP: if matched { subprocessors.InitDNSMessageFormat(&dm) if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop { - // c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm) for i := range droppedRoutes { select { case droppedRoutes[i] <- dm: default: - c.dropped <- droppedNames[i] + c.NextStanzaIsBusy(droppedNames[i]) } } continue @@ -319,63 +250,24 @@ RUN_LOOP: // drop packet ? if !matched { - // c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm) for i := range droppedRoutes { select { case droppedRoutes[i] <- dm: default: - c.dropped <- droppedNames[i] + c.NextStanzaIsBusy(droppedNames[i]) } } continue } // send to next - // c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm) for i := range defaultRoutes { select { case defaultRoutes[i] <- dm: default: - c.dropped <- defaultNames[i] + c.NextStanzaIsBusy(defaultNames[i]) } } - - } - - } - c.LogInfo("run terminated") -} - -func (c *DNSMessage) MonitorNextStanzas() { - watchInterval := 10 * time.Second - bufferFull := time.NewTimer(watchInterval) -FOLLOW_LOOP: - for { - select { - case <-c.stopMonitor: - close(c.dropped) - bufferFull.Stop() - c.doneMonitor <- true - break FOLLOW_LOOP - - case loggerName := <-c.dropped: - if _, ok := c.droppedCount[loggerName]; !ok { - c.droppedCount[loggerName] = 1 - } else { - c.droppedCount[loggerName]++ - } - - case <-bufferFull.C: - - for v, k := range c.droppedCount { - if k > 0 { - c.LogError("stanza[%s] buffer is full, %d dnsmessage(s) dropped", v, k) - c.droppedCount[v] = 0 - } - } - bufferFull.Reset(watchInterval) - } } - c.LogInfo("monitor terminated") } diff --git a/collectors/dnsmessage_test.go b/collectors/dnsmessage_test.go index 47c0e902..16de649c 100644 --- a/collectors/dnsmessage_test.go +++ b/collectors/dnsmessage_test.go @@ -15,7 +15,7 @@ import ( func Test_DnsMessage_BufferLoggerIsFull(t *testing.T) { // redirect stdout output to bytes buffer - logsChan := make(chan logger.LogEntry, 10) + logsChan := make(chan logger.LogEntry, 50) lg := logger.New(true) lg.SetOutputChannel((logsChan)) diff --git a/collectors/dnstap.go b/collectors/dnstap.go index 7bf8474a..03e088a1 100644 --- a/collectors/dnstap.go +++ b/collectors/dnstap.go @@ -2,18 +2,15 @@ package collectors import ( "bufio" - "crypto/tls" "encoding/binary" "errors" "io" "net" - "os" - "strconv" "sync" + "sync/atomic" "time" - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -23,126 +20,46 @@ import ( ) type Dnstap struct { - doneRun, stopRun chan bool - doneMonitor, stopMonitor chan bool - stopCalled bool - listen net.Listener - conns []net.Conn - sockPath string - defaultRoutes, droppedRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string - connMode string - connID int - droppedCount int - droppedProcessor chan int - tapProcessors []processors.DNSTapProcessor - sync.RWMutex + *pkgutils.Collector + connCounter uint64 } -func NewDnstap(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Dnstap { - logger.Info(pkgutils.PrefixLogCollector+"[%s] dnstap - enabled", name) - s := &Dnstap{ - doneRun: make(chan bool), - doneMonitor: make(chan bool), - stopRun: make(chan bool), - stopMonitor: make(chan bool), - droppedProcessor: make(chan int), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() +func NewDnstap(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Dnstap { + s := &Dnstap{Collector: pkgutils.NewCollector(config, logger, name, "dnstap")} + s.SetDefaultRoutes(next) + s.CheckConfig() return s } -func (c *Dnstap) GetName() string { return c.name } - -func (c *Dnstap) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *Dnstap) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *Dnstap) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *Dnstap) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *Dnstap) ReadConfig() { - if !pkgconfig.IsValidTLS(c.config.Collectors.Dnstap.TLSMinVersion) { - c.logger.Fatal("collector=dnstap - invalid tls min version") - } - - c.sockPath = c.config.Collectors.Dnstap.SockPath - c.connMode = "tcp" - - if len(c.config.Collectors.Dnstap.SockPath) > 0 { - c.connMode = "unix" - } else if c.config.Collectors.Dnstap.TLSSupport { - c.connMode = "tls" +func (c *Dnstap) CheckConfig() { + if !pkgconfig.IsValidTLS(c.GetConfig().Collectors.Dnstap.TLSMinVersion) { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] dnstap - invalid tls min version") } } -func (c *Dnstap) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *Dnstap) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] dnstap - "+msg, v...) -} - -func (c *Dnstap) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] dnstap - "+msg, v...) -} - -func (c *Dnstap) HandleConn(conn net.Conn) { +func (c *Dnstap) HandleConn(conn net.Conn, connID uint64, forceClose chan bool, wg *sync.WaitGroup) { // close connection on function exit - defer conn.Close() - - var connID int - c.Lock() - c.connID++ - connID = c.connID - c.Unlock() + defer func() { + c.LogInfo("conn #%d - connection handler terminated", connID) + netutils.Close(conn, c.GetConfig().Collectors.Dnstap.ResetConn) + wg.Done() + }() // get peer address peer := conn.RemoteAddr().String() - peerName := netlib.GetPeerName(peer) + peerName := netutils.GetPeerName(peer) c.LogInfo("new connection #%d from %s (%s)", connID, peer, peerName) - // start dnstap processor - dnstapProcessor := processors.NewDNSTapProcessor( - connID, - peerName, - c.config, - c.logger, - c.name, - c.config.Collectors.Dnstap.ChannelBufferSize, - ) - c.Lock() - c.tapProcessors = append(c.tapProcessors, dnstapProcessor) - c.Unlock() - - // run processor - go dnstapProcessor.Run(c.defaultRoutes, c.droppedRoutes) - - // frame stream library + // start dnstap processor and run it + dnstapProcessor := processors.NewDNSTapProcessor(int(connID), peerName, c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.Dnstap.ChannelBufferSize) + go dnstapProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) + + // init frame stream library r := bufio.NewReader(conn) w := bufio.NewWriter(conn) fs := framestream.NewFstrm(r, w, conn, 5*time.Second, []byte("protobuf:dnstap.Dnstap"), true) - // init framestream receiver + // framestream as receiver if err := fs.InitReceiver(); err != nil { c.LogError("conn #%d - stream initialization: %s", connID, err) } else { @@ -152,8 +69,31 @@ func (c *Dnstap) HandleConn(conn net.Conn) { // process incoming frame and send it to dnstap consumer channel var err error var frame *framestream.Frame + cleanup := make(chan struct{}) + + // goroutine to close the connection properly + go func() { + defer func() { + dnstapProcessor.Stop() + c.LogInfo("conn #%d - cleanup connection handler terminated", connID) + }() + + for { + select { + case <-forceClose: + c.LogInfo("conn #%d - force to cleanup the connection handler", connID) + netutils.Close(conn, c.GetConfig().Collectors.Dnstap.ResetConn) + return + case <-cleanup: + c.LogInfo("conn #%d - cleanup the connection handler", connID) + return + } + } + }() + + // handle incoming frame for { - if c.config.Collectors.Dnstap.Compression == pkgconfig.CompressNone { + if c.GetConfig().Collectors.Dnstap.Compression == pkgconfig.CompressNone { frame, err = fs.RecvFrame(false) } else { frame, err = fs.RecvCompressedFrame(&compress.GzipCodec, false) @@ -176,11 +116,8 @@ func (c *Dnstap) HandleConn(conn net.Conn) { } else { c.LogError("conn #%d - framestream reader error: %s", connID, err) } - - // the Stop function is already called, don't stop again - if !c.stopCalled { - dnstapProcessor.Stop() - } + // exit goroutine + close(cleanup) break } @@ -193,15 +130,18 @@ func (c *Dnstap) HandleConn(conn net.Conn) { } } + + // exit goroutine + close(cleanup) break } - if c.config.Collectors.Dnstap.Compression == pkgconfig.CompressNone { + if c.GetConfig().Collectors.Dnstap.Compression == pkgconfig.CompressNone { // send payload to the channel select { case dnstapProcessor.GetChannel() <- frame.Data(): // Successful send to channel default: - c.droppedProcessor <- 1 + c.ProcessorIsBusy() } } else { // ignore first 4 bytes @@ -221,7 +161,7 @@ func (c *Dnstap) HandleConn(conn net.Conn) { select { case dnstapProcessor.GetChannel() <- data[:payloadSize]: // Successful send to channel default: - c.droppedProcessor <- 1 + c.ProcessorIsBusy() } // continue for next @@ -229,234 +169,73 @@ func (c *Dnstap) HandleConn(conn net.Conn) { } if !validFrame { c.LogError("conn #%d - invalid compressed frame received", connID) - break // ignore the invalid frame + continue } } } - - // to avoid lock if the Stop function is already called - if c.stopCalled { - c.LogInfo("conn #%d - connection handler exited", connID) - return - } - - // to avoid lock if the Stop function is already called - if c.stopCalled { - c.LogInfo("conn #%d - connection handler exited", connID) - return - } - - // here the connection is closed, - // then removes the current tap processor from the list - c.Lock() - for i, t := range c.tapProcessors { - if t.ConnID == connID { - c.tapProcessors = append(c.tapProcessors[:i], c.tapProcessors[i+1:]...) - } - } - - // finnaly removes the current connection from the list - for j, cn := range c.conns { - if cn == conn { - c.conns = append(c.conns[:j], c.conns[j+1:]...) - conn = nil - } - } - c.Unlock() - - c.LogInfo("conn #%d - connection handler terminated", connID) -} - -func (c *Dnstap) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *Dnstap) Stop() { - c.Lock() - defer c.Unlock() - - // to avoid some lock situations when the remose side closes - // the connection at the same time of this Stop function - c.stopCalled = true - c.LogInfo("stopping collector...") - - // stop all powerdns processors - c.LogInfo("cleanup all active processors...") - for _, tapProc := range c.tapProcessors { - tapProc.Stop() - } - - // closing properly current connections if exists - c.LogInfo("closing connected peers...") - for _, conn := range c.conns { - netlib.Close(conn, c.config.Collectors.Dnstap.ResetConn) - } - - // Finally close the listener to unblock accept - c.LogInfo("stop listening...") - c.listen.Close() - - // stop monitor goroutine - c.LogInfo("stopping monitor...") - c.stopMonitor <- true - <-c.doneMonitor - - // read done channel and block until run is terminated - c.LogInfo("stopping run...") - c.stopRun <- true - <-c.doneRun } -func (c *Dnstap) Listen() error { - c.Lock() - defer c.Unlock() - - c.LogInfo("running in background...") - - var err error - var listener net.Listener - addrlisten := c.config.Collectors.Dnstap.ListenIP + ":" + strconv.Itoa(c.config.Collectors.Dnstap.ListenPort) - - if len(c.sockPath) > 0 { - _ = os.Remove(c.sockPath) - } - - // listening with tls enabled ? - if c.config.Collectors.Dnstap.TLSSupport { - c.LogInfo("tls support enabled") - var cer tls.Certificate - cer, err = tls.LoadX509KeyPair(c.config.Collectors.Dnstap.CertFile, c.config.Collectors.Dnstap.KeyFile) - if err != nil { - c.logger.Fatal("loading certificate failed:", err) - } - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cer}, - MinVersion: tls.VersionTLS12, - } - - // update tls min version according to the user config - tlsConfig.MinVersion = pkgconfig.TLSVersion[c.config.Collectors.Dnstap.TLSMinVersion] - - if len(c.sockPath) > 0 { - listener, err = tls.Listen(netlib.SocketUnix, c.sockPath, tlsConfig) - } else { - listener, err = tls.Listen(netlib.SocketTCP, addrlisten, tlsConfig) - } +func (c *Dnstap) Run() { + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() - } else { - // basic listening - if len(c.sockPath) > 0 { - listener, err = net.Listen(netlib.SocketUnix, c.sockPath) - } else { - listener, err = net.Listen(netlib.SocketTCP, addrlisten) - } - } + var connWG sync.WaitGroup + connCleanup := make(chan bool) + cfg := c.GetConfig().Collectors.Dnstap - // something is wrong ? + // start to listen + listener, err := netutils.StartToListen( + cfg.ListenIP, cfg.ListenPort, cfg.SockPath, + cfg.TLSSupport, pkgconfig.TLSVersion[cfg.TLSMinVersion], + cfg.CertFile, cfg.KeyFile) if err != nil { - return err - } - c.LogInfo("is listening on %s://%s", c.connMode, listener.Addr()) - c.listen = listener - return nil -} - -func (c *Dnstap) MonitorCollector() { - watchInterval := 10 * time.Second - bufferFull := time.NewTimer(watchInterval) -MONITOR_LOOP: - for { - select { - case <-c.droppedProcessor: - c.droppedCount++ - case <-c.stopMonitor: - close(c.droppedProcessor) - bufferFull.Stop() - c.doneMonitor <- true - break MONITOR_LOOP - case <-bufferFull.C: - if c.droppedCount > 0 { - c.LogError("processor buffer is full, %d packet(s) dropped", c.droppedCount) - c.droppedCount = 0 - } - bufferFull.Reset(watchInterval) - } - } - c.LogInfo("monitor terminated") -} - -func (c *Dnstap) Run() { - c.LogInfo("starting collector...") - if c.listen == nil { - if err := c.Listen(); err != nil { - c.logger.Fatal(pkgutils.PrefixLogCollector+"["+c.name+"] dnstap listening failed: ", err) - } + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] listen error: ", err) } - - // start goroutine to count dropped messsages - go c.MonitorCollector() + c.LogInfo("listening on %s", listener.Addr()) // goroutine to Accept() blocks waiting for new connection. acceptChan := make(chan net.Conn) - go func() { - for { - conn, err := c.listen.Accept() - if err != nil { - return - } - acceptChan <- conn - } - }() + netutils.AcceptConnections(listener, acceptChan) -RUN_LOOP: + // main loop for { select { - case <-c.stopRun: - close(acceptChan) - c.doneRun <- true - break RUN_LOOP - - case cfg := <-c.configChan: + case <-c.OnStop(): + c.LogInfo("stop to listen...") + listener.Close() - // save the new config - c.config = cfg - c.ReadConfig() + c.LogInfo("closing connected peers...") + close(connCleanup) + connWG.Wait() + return - // refresh config for all conns - for i := range c.tapProcessors { - c.tapProcessors[i].ConfigChan <- cfg - } + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + c.CheckConfig() + // new incoming connection case conn, opened := <-acceptChan: if !opened { return } - if (c.connMode == "tls" || c.connMode == "tcp") && c.config.Collectors.Dnstap.RcvBufSize > 0 { - before, actual, err := netlib.SetSockRCVBUF( - conn, - c.config.Collectors.Dnstap.RcvBufSize, - c.config.Collectors.Dnstap.TLSSupport, - ) + if len(cfg.SockPath) == 0 && cfg.RcvBufSize > 0 { + before, actual, err := netutils.SetSockRCVBUF(conn, cfg.RcvBufSize, cfg.TLSSupport) if err != nil { - c.logger.Fatal(pkgutils.PrefixLogCollector+"["+c.name+"] dnstap - unable to set SO_RCVBUF: ", err) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] unable to set SO_RCVBUF: ", err) } - c.LogInfo("set SO_RCVBUF option, value before: %d, desired: %d, actual: %d", before, - c.config.Collectors.Dnstap.RcvBufSize, actual) - } - - // to avoid lock if the Stop function is already called - if c.stopCalled { - continue + c.LogInfo("set SO_RCVBUF option, value before: %d, desired: %d, actual: %d", before, cfg.RcvBufSize, actual) } - c.Lock() - c.conns = append(c.conns, conn) - c.Unlock() - go c.HandleConn(conn) + // handle the connection + connWG.Add(1) + connID := atomic.AddUint64(&c.connCounter, 1) + go c.HandleConn(conn, connID, connCleanup, &connWG) } } - c.LogInfo("run terminated") } diff --git a/collectors/dnstap_proxifier.go b/collectors/dnstap_proxifier.go deleted file mode 100644 index 8d0c8bd4..00000000 --- a/collectors/dnstap_proxifier.go +++ /dev/null @@ -1,265 +0,0 @@ -package collectors - -import ( - "bufio" - "crypto/tls" - "net" - "os" - "strconv" - "time" - - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-framestream" - "github.com/dmachard/go-logger" -) - -type DnstapProxifier struct { - doneRun, stopRun chan bool - listen net.Listener - conns []net.Conn - sockPath string - defaultRoutes, droppedRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string - stopping bool - RoutingHandler pkgutils.RoutingHandler -} - -func NewDnstapProxifier(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DnstapProxifier { - logger.Info(pkgutils.PrefixLogCollector+"[%s] dnstaprelay - enabled", name) - s := &DnstapProxifier{ - doneRun: make(chan bool), - stopRun: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name), - } - s.ReadConfig() - return s -} - -func (c *DnstapProxifier) GetName() string { return c.name } - -func (c *DnstapProxifier) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *DnstapProxifier) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *DnstapProxifier) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *DnstapProxifier) Loggers() []chan dnsutils.DNSMessage { - channels := []chan dnsutils.DNSMessage{} - for _, p := range c.defaultRoutes { - channels = append(channels, p.GetInputChannel()) - } - return channels -} - -func (c *DnstapProxifier) ReadConfig() { - if !pkgconfig.IsValidTLS(c.config.Collectors.DnstapProxifier.TLSMinVersion) { - c.logger.Fatal(pkgutils.PrefixLogCollector + "[" + c.name + "] dnstaprelay - invalid tls min version") - } - - c.sockPath = c.config.Collectors.DnstapProxifier.SockPath -} - -func (c *DnstapProxifier) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *DnstapProxifier) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] dnstaprelay - "+msg, v...) -} - -func (c *DnstapProxifier) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] dnstaprelay - "+msg, v...) -} - -func (c *DnstapProxifier) HandleFrame(recvFrom chan []byte, sendTo []chan dnsutils.DNSMessage) { - for data := range recvFrom { - // init DNS message container - dm := dnsutils.DNSMessage{} - dm.Init() - - // register payload - dm.DNSTap.Payload = data - - // forward to outputs - for i := range sendTo { - sendTo[i] <- dm - } - } -} - -func (c *DnstapProxifier) HandleConn(conn net.Conn) { - // close connection on function exit - defer conn.Close() - - // get peer address - peer := conn.RemoteAddr().String() - c.LogInfo("new connection from %s\n", peer) - - recvChan := make(chan []byte, 512) - go c.HandleFrame(recvChan, c.Loggers()) - - // frame stream library - r := bufio.NewReader(conn) - w := bufio.NewWriter(conn) - fs := framestream.NewFstrm(r, w, conn, 5*time.Second, []byte("protobuf:dnstap.Dnstap"), true) - - // init framestream receiver - if err := fs.InitReceiver(); err != nil { - c.LogError("error stream receiver initialization: %s", err) - return - } else { - c.LogInfo("receiver framestream initialized") - } - - // process incoming frame and send it to recv channel - err := fs.ProcessFrame(recvChan) - if err != nil { - if !c.stopping { - c.LogError("transport error: %s", err) - } - } - - close(recvChan) - - c.LogInfo("%s - connection closed\n", peer) -} - -func (c *DnstapProxifier) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *DnstapProxifier) Stop() { - c.LogInfo("stopping collector...") - c.stopping = true - - // closing properly current connections if exists - for _, conn := range c.conns { - peer := conn.RemoteAddr().String() - c.LogInfo("%s - closing connection...", peer) - conn.Close() - } - // Finally close the listener to unblock accept - c.LogInfo("stop listening...") - c.listen.Close() - - // read done channel and block until run is terminated - c.stopRun <- true - <-c.doneRun -} - -func (c *DnstapProxifier) Listen() error { - c.LogInfo("running in background...") - - var err error - var listener net.Listener - addrlisten := c.config.Collectors.DnstapProxifier.ListenIP + ":" + strconv.Itoa(c.config.Collectors.DnstapProxifier.ListenPort) - - if len(c.sockPath) > 0 { - _ = os.Remove(c.sockPath) - } - - // listening with tls enabled ? - if c.config.Collectors.DnstapProxifier.TLSSupport { - c.LogInfo("tls support enabled") - var cer tls.Certificate - cer, err = tls.LoadX509KeyPair(c.config.Collectors.DnstapProxifier.CertFile, c.config.Collectors.DnstapProxifier.KeyFile) - if err != nil { - c.logger.Fatal("loading certificate failed:", err) - } - - // prepare tls configuration - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cer}, - MinVersion: tls.VersionTLS12, - } - - // update tls min version according to the user config - tlsConfig.MinVersion = pkgconfig.TLSVersion[c.config.Collectors.DnstapProxifier.TLSMinVersion] - - if len(c.sockPath) > 0 { - listener, err = tls.Listen(netlib.SocketUnix, c.sockPath, tlsConfig) - } else { - listener, err = tls.Listen(netlib.SocketTCP, addrlisten, tlsConfig) - } - } else { - // basic listening - if len(c.sockPath) > 0 { - listener, err = net.Listen(netlib.SocketUnix, c.sockPath) - } else { - listener, err = net.Listen(netlib.SocketTCP, addrlisten) - } - } - - // something is wrong ? - if err != nil { - return err - } - c.LogInfo("is listening on %s", listener.Addr()) - c.listen = listener - return nil -} - -func (c *DnstapProxifier) Run() { - c.LogInfo("starting collector...") - if c.listen == nil { - if err := c.Listen(); err != nil { - c.logger.Fatal("collector dnstap listening failed: ", err) - } - } - - // goroutine to Accept() blocks waiting for new connection. - acceptChan := make(chan net.Conn) - go func() { - for { - conn, err := c.listen.Accept() - if err != nil { - return - } - acceptChan <- conn - } - }() - -RUN_LOOP: - for { - select { - case <-c.stopRun: - close(acceptChan) - c.doneRun <- true - break RUN_LOOP - - case cfg := <-c.configChan: - - // save the new config - c.config = cfg - c.ReadConfig() - - case conn, opened := <-acceptChan: - if !opened { - return - } - - c.conns = append(c.conns, conn) - go c.HandleConn(conn) - } - } - - c.LogInfo("run terminated") -} diff --git a/collectors/dnstap_relay.go b/collectors/dnstap_relay.go new file mode 100644 index 00000000..5c955c63 --- /dev/null +++ b/collectors/dnstap_relay.go @@ -0,0 +1,170 @@ +package collectors + +import ( + "bufio" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/dmachard/go-dnscollector/dnsutils" + "github.com/dmachard/go-dnscollector/netutils" + "github.com/dmachard/go-dnscollector/pkgconfig" + "github.com/dmachard/go-dnscollector/pkgutils" + "github.com/dmachard/go-framestream" + "github.com/dmachard/go-logger" +) + +type DnstapProxifier struct { + *pkgutils.Collector + connCounter uint64 +} + +func NewDnstapProxifier(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DnstapProxifier { + s := &DnstapProxifier{Collector: pkgutils.NewCollector(config, logger, name, "dnstaprelay")} + s.SetDefaultRoutes(next) + s.CheckConfig() + return s +} + +func (c *DnstapProxifier) CheckConfig() { + if !pkgconfig.IsValidTLS(c.GetConfig().Collectors.DnstapProxifier.TLSMinVersion) { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] dnstaprelay - invalid tls min version") + } +} + +func (c *DnstapProxifier) HandleFrame(recvFrom chan []byte, sendTo []chan dnsutils.DNSMessage) { + defer c.LogInfo("frame handler terminated") + + dm := dnsutils.DNSMessage{} + + for data := range recvFrom { + // init DNS message container + dm.Init() + + // register payload + dm.DNSTap.Payload = data + + // forward to outputs + for i := range sendTo { + sendTo[i] <- dm + } + } +} + +func (c *DnstapProxifier) HandleConn(conn net.Conn, connID uint64, forceClose chan bool, wg *sync.WaitGroup) { + // close connection on function exit + defer func() { + c.LogInfo("conn #%d - connection handler terminated", connID) + conn.Close() + wg.Done() + }() + + // get peer address + peer := conn.RemoteAddr().String() + c.LogInfo("new connection from %s\n", peer) + + recvChan := make(chan []byte, 512) + defaultRoutes, _ := pkgutils.GetRoutes(c.GetDefaultRoutes()) + go c.HandleFrame(recvChan, defaultRoutes) + + // frame stream library + r := bufio.NewReader(conn) + w := bufio.NewWriter(conn) + fs := framestream.NewFstrm(r, w, conn, 5*time.Second, []byte("protobuf:dnstap.Dnstap"), true) + + // init framestream receiver + if err := fs.InitReceiver(); err != nil { + c.LogError("error stream receiver initialization: %s", err) + return + } else { + c.LogInfo("receiver framestream initialized") + } + + // goroutine to close the connection properly + cleanup := make(chan struct{}) + go func() { + defer c.LogInfo("conn #%d - cleanup connection handler terminated", connID) + + for { + select { + case <-forceClose: + c.LogInfo("conn #%d - force to cleanup the connection handler", connID) + conn.Close() + close(recvChan) + return + case <-cleanup: + c.LogInfo("conn #%d - cleanup the connection handler", connID) + close(recvChan) + return + } + } + }() + + // process incoming frame and send it to recv channel + err := fs.ProcessFrame(recvChan) + if err != nil { + if netutils.IsClosedConnectionError(err) { + c.LogInfo("conn #%d - connection closed with peer %s", connID, peer) + } else { + c.LogError("conn #%d - transport error: %s", connID, err) + } + + close(cleanup) + } +} + +func (c *DnstapProxifier) Run() { + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() + + var connWG sync.WaitGroup + connCleanup := make(chan bool) + + // start to listen + listener, err := netutils.StartToListen( + c.GetConfig().Collectors.DnstapProxifier.ListenIP, c.GetConfig().Collectors.DnstapProxifier.ListenPort, + c.GetConfig().Collectors.DnstapProxifier.SockPath, + c.GetConfig().Collectors.DnstapProxifier.TLSSupport, pkgconfig.TLSVersion[c.GetConfig().Collectors.DnstapProxifier.TLSMinVersion], + c.GetConfig().Collectors.DnstapProxifier.CertFile, c.GetConfig().Collectors.DnstapProxifier.KeyFile) + if err != nil { + c.LogFatal("collector dnstaprelay listening failed: ", err) + } + c.LogInfo("listening on %s", listener.Addr()) + + // goroutine to Accept() and blocks waiting for new connection. + acceptChan := make(chan net.Conn) + netutils.AcceptConnections(listener, acceptChan) + + // main loop + for { + select { + case <-c.OnStop(): + c.LogInfo("stop to listen...") + listener.Close() + + c.LogInfo("closing connected peers...") + close(connCleanup) + connWG.Wait() + return + + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + c.CheckConfig() + + case conn, opened := <-acceptChan: + if !opened { + return + } + + // handle the connection + connWG.Add(1) + connID := atomic.AddUint64(&c.connCounter, 1) + go c.HandleConn(conn, connID, connCleanup, &connWG) + } + } +} diff --git a/collectors/dnstap_proxifier_test.go b/collectors/dnstap_relay_test.go similarity index 87% rename from collectors/dnstap_proxifier_test.go rename to collectors/dnstap_relay_test.go index 9da01071..54008bb1 100644 --- a/collectors/dnstap_proxifier_test.go +++ b/collectors/dnstap_relay_test.go @@ -2,12 +2,11 @@ package collectors import ( "bufio" - "log" "net" "testing" "time" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -16,7 +15,7 @@ import ( "google.golang.org/protobuf/proto" ) -func Test_DnstapProxifier(t *testing.T) { +func Test_DnstapRelay(t *testing.T) { testcases := []struct { name string mode string @@ -25,19 +24,19 @@ func Test_DnstapProxifier(t *testing.T) { }{ { name: "tcp_default", - mode: netlib.SocketTCP, + mode: netutils.SocketTCP, address: ":6000", listenPort: 0, }, { name: "tcp_custom_port", - mode: netlib.SocketTCP, + mode: netutils.SocketTCP, address: ":7100", listenPort: 7100, }, { name: "unix_default", - mode: netlib.SocketUnix, + mode: netutils.SocketUnix, address: "/tmp/dnscollector_relay.sock", listenPort: 0, }, @@ -51,17 +50,16 @@ func Test_DnstapProxifier(t *testing.T) { if tc.listenPort > 0 { config.Collectors.DnstapProxifier.ListenPort = tc.listenPort } - if tc.mode == netlib.SocketUnix { + if tc.mode == netutils.SocketUnix { config.Collectors.DnstapProxifier.SockPath = tc.address } + // start collector c := NewDnstapProxifier([]pkgutils.Worker{g}, config, logger.New(false), "test") - if err := c.Listen(); err != nil { - log.Fatal("collector dnstap relay error: ", err) - } - go c.Run() + // start client + time.Sleep(1 * time.Second) conn, err := net.Dial(tc.mode, tc.address) if err != nil { t.Error("could not connect: ", err) diff --git a/collectors/dnstap_test.go b/collectors/dnstap_test.go index 7b282e72..587f42c9 100644 --- a/collectors/dnstap_test.go +++ b/collectors/dnstap_test.go @@ -3,13 +3,12 @@ package collectors import ( "bufio" "fmt" - "log" "net" "regexp" "testing" "time" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -30,7 +29,7 @@ func Test_DnstapCollector(t *testing.T) { }{ { name: "tcp_default", - mode: netlib.SocketTCP, + mode: netutils.SocketTCP, address: ":6000", listenPort: 0, operation: "CLIENT_QUERY", @@ -38,7 +37,7 @@ func Test_DnstapCollector(t *testing.T) { }, { name: "tcp_custom_port", - mode: netlib.SocketTCP, + mode: netutils.SocketTCP, address: ":7000", listenPort: 7000, operation: "CLIENT_QUERY", @@ -46,7 +45,7 @@ func Test_DnstapCollector(t *testing.T) { }, { name: "unix_default", - mode: netlib.SocketUnix, + mode: netutils.SocketUnix, address: "/tmp/dnscollector.sock", listenPort: 0, operation: "CLIENT_QUERY", @@ -54,7 +53,7 @@ func Test_DnstapCollector(t *testing.T) { }, { name: "tcp_compress_gzip", - mode: netlib.SocketTCP, + mode: netutils.SocketTCP, address: ":7000", listenPort: 7000, operation: "CLIENT_QUERY", @@ -70,18 +69,17 @@ func Test_DnstapCollector(t *testing.T) { if tc.listenPort > 0 { config.Collectors.Dnstap.ListenPort = tc.listenPort } - if tc.mode == netlib.SocketUnix { + if tc.mode == netutils.SocketUnix { config.Collectors.Dnstap.SockPath = tc.address } config.Collectors.Dnstap.Compression = tc.compression + // start the collector c := NewDnstap([]pkgutils.Worker{g}, config, logger.New(false), "test") - if err := c.Listen(); err != nil { - log.Fatal("collector listening error: ", err) - } - go c.Run() + // wait before to connect + time.Sleep(1 * time.Second) conn, err := net.Dial(tc.mode, tc.address) if err != nil { t.Error("could not connect: ", err) @@ -111,7 +109,6 @@ func Test_DnstapCollector(t *testing.T) { if err != nil { t.Fatalf("dnstap proto marshal error %s", err) } - // send query if config.Collectors.Dnstap.Compression == pkgconfig.CompressNone { @@ -148,7 +145,7 @@ func Test_DnstapCollector(t *testing.T) { // Support Bind9 with dnstap closing. func Test_DnstapCollector_CloseFrameStream(t *testing.T) { // redirect stdout output to bytes buffer - logsChan := make(chan logger.LogEntry, 10) + logsChan := make(chan logger.LogEntry, 50) lg := logger.New(true) lg.SetOutputChannel((logsChan)) @@ -158,14 +155,11 @@ func Test_DnstapCollector_CloseFrameStream(t *testing.T) { // start the collector in unix mode g := pkgutils.NewFakeLogger() c := NewDnstap([]pkgutils.Worker{g}, config, lg, "test") - if err := c.Listen(); err != nil { - log.Fatal("collector listening error: ", err) - } - go c.Run() // simulate dns server connection to collector - conn, err := net.Dial(netlib.SocketUnix, "/tmp/dnscollector.sock") + time.Sleep(1 * time.Second) + conn, err := net.Dial(netutils.SocketUnix, "/tmp/dnscollector.sock") if err != nil { t.Error("could not connect: ", err) } @@ -185,15 +179,20 @@ func Test_DnstapCollector_CloseFrameStream(t *testing.T) { } regxp := ".*framestream reseted by sender.*" + pattern := regexp.MustCompile(regxp) + + matchMsg := false for entry := range logsChan { fmt.Println(entry) - pattern := regexp.MustCompile(regxp) if pattern.MatchString(entry.Message) { + matchMsg = true break } } + if !matchMsg { + t.Errorf("reset from sender not received") + } // cleanup c.Stop() - } diff --git a/collectors/file_ingestor.go b/collectors/file_ingestor.go index b1d7b5be..f7f76044 100644 --- a/collectors/file_ingestor.go +++ b/collectors/file_ingestor.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "io" - "log" "math" "os" "path/filepath" @@ -12,7 +11,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -37,99 +36,34 @@ func IsValidMode(mode string) bool { } type FileIngestor struct { - done chan bool - exit chan bool - droppedRoutes, defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - watcher *fsnotify.Watcher - watcherTimers map[string]*time.Timer - dnsProcessor processors.DNSProcessor - dnstapProcessor processors.DNSTapProcessor - filterDNSPort int - identity string - name string - mu sync.Mutex + *pkgutils.Collector + watcherTimers map[string]*time.Timer + dnsProcessor processors.DNSProcessor + dnstapProcessor processors.DNSTapProcessor + mu sync.Mutex } -func NewFileIngestor(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *FileIngestor { - logger.Info(pkgutils.PrefixLogCollector+"[%s] fileingestor - enabled", name) +func NewFileIngestor(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *FileIngestor { s := &FileIngestor{ - done: make(chan bool), - exit: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - watcherTimers: make(map[string]*time.Timer), - } - s.ReadConfig() + Collector: pkgutils.NewCollector(config, logger, name, "fileingestor"), + watcherTimers: make(map[string]*time.Timer)} + s.SetDefaultRoutes(next) + s.CheckConfig() return s } -func (c *FileIngestor) GetName() string { return c.name } - -func (c *FileIngestor) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *FileIngestor) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *FileIngestor) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *FileIngestor) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *FileIngestor) ReadConfig() { - if !IsValidMode(c.config.Collectors.FileIngestor.WatchMode) { - c.logger.Fatal(pkgutils.PrefixLogCollector+"["+c.name+"]fileingestor - invalid mode: ", c.config.Collectors.FileIngestor.WatchMode) +func (c *FileIngestor) CheckConfig() { + if !IsValidMode(c.GetConfig().Collectors.FileIngestor.WatchMode) { + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] - invalid mode: ", c.GetConfig().Collectors.FileIngestor.WatchMode) } - c.identity = c.config.GetServerIdentity() - c.filterDNSPort = c.config.Collectors.FileIngestor.PcapDNSPort - c.LogInfo("watching directory [%s] to find [%s] files", - c.config.Collectors.FileIngestor.WatchDir, - c.config.Collectors.FileIngestor.WatchMode) -} - -func (c *FileIngestor) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *FileIngestor) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] fileingestor - "+msg, v...) -} - -func (c *FileIngestor) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] fileingestor - "+msg, v...) -} - -func (c *FileIngestor) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *FileIngestor) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) + c.GetConfig().Collectors.FileIngestor.WatchDir, + c.GetConfig().Collectors.FileIngestor.WatchMode) } func (c *FileIngestor) ProcessFile(filePath string) { - switch c.config.Collectors.FileIngestor.WatchMode { + switch c.GetConfig().Collectors.FileIngestor.WatchMode { case pkgconfig.ModePCAP: // process file with pcap extension only if filepath.Ext(filePath) == ".pcap" || filepath.Ext(filePath) == ".pcap.gz" { @@ -169,7 +103,7 @@ func (c *FileIngestor) ProcessPcap(filePath string) { return } - dnsChan := make(chan netlib.DNSPacket) + dnsChan := make(chan netutils.DNSPacket) udpChan := make(chan gopacket.Packet) tcpChan := make(chan gopacket.Packet) fragIP4Chan := make(chan gopacket.Packet) @@ -180,13 +114,13 @@ func (c *FileIngestor) ProcessPcap(filePath string) { packetSource.NoCopy = true // defrag ipv4 - go netlib.IPDefragger(fragIP4Chan, udpChan, tcpChan) + go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan) // defrag ipv6 - go netlib.IPDefragger(fragIP6Chan, udpChan, tcpChan) + go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan) // tcp assembly - go netlib.TCPAssembler(tcpChan, dnsChan, c.filterDNSPort) + go netutils.TCPAssembler(tcpChan, dnsChan, c.GetConfig().Collectors.FileIngestor.PcapDNSPort) // udp processor - go netlib.UDPProcessor(udpChan, dnsChan, c.filterDNSPort) + go netutils.UDPProcessor(udpChan, dnsChan, c.GetConfig().Collectors.FileIngestor.PcapDNSPort) go func() { nbPackets := 0 @@ -215,7 +149,7 @@ func (c *FileIngestor) ProcessPcap(filePath string) { dm.DNS.Payload = dnsPacket.Payload dm.DNS.Length = len(dnsPacket.Payload) - dm.DNSTap.Identity = c.identity + dm.DNSTap.Identity = c.GetConfig().GetServerIdentity() dm.DNSTap.TimeSec = dnsPacket.Timestamp.Second() dm.DNSTap.TimeNsec = int(dnsPacket.Timestamp.UnixNano()) @@ -288,7 +222,7 @@ func (c *FileIngestor) ProcessPcap(filePath string) { c.LogInfo("pcap file [%s] processing terminated, %d packet(s) read", fileName, nbPackets) // remove it ? - if c.config.Collectors.FileIngestor.DeleteAfter { + if c.GetConfig().Collectors.FileIngestor.DeleteAfter { c.LogInfo("delete file [%s]", fileName) os.Remove(filePath) } @@ -336,7 +270,7 @@ func (c *FileIngestor) ProcessDnstap(filePath string) error { // remove it ? c.LogInfo("processing of [%s] terminated", fileName) - if c.config.Collectors.FileIngestor.DeleteAfter { + if c.GetConfig().Collectors.FileIngestor.DeleteAfter { c.LogInfo("delete file [%s]", fileName) os.Remove(filePath) } @@ -374,24 +308,21 @@ func (c *FileIngestor) RemoveEvent(filePath string) { } func (c *FileIngestor) Run() { - c.LogInfo("starting collector...") + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() - c.dnsProcessor = processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.FileIngestor.ChannelBufferSize) - go c.dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes) + c.dnsProcessor = processors.NewDNSProcessor(c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.FileIngestor.ChannelBufferSize) + go c.dnsProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) // start dnstap subprocessor - c.dnstapProcessor = processors.NewDNSTapProcessor( - 0, - "", - c.config, - c.logger, - c.name, - c.config.Collectors.FileIngestor.ChannelBufferSize, - ) - go c.dnstapProcessor.Run(c.defaultRoutes, c.droppedRoutes) + c.dnstapProcessor = processors.NewDNSTapProcessor(0, "", c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.FileIngestor.ChannelBufferSize) + go c.dnstapProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) // read current folder content - entries, err := os.ReadDir(c.config.Collectors.FileIngestor.WatchDir) + entries, err := os.ReadDir(c.GetConfig().Collectors.FileIngestor.WatchDir) if err != nil { c.LogError("unable to read folder: %s", err) } @@ -403,9 +334,9 @@ func (c *FileIngestor) Run() { } // prepare filepath - fn := filepath.Join(c.config.Collectors.FileIngestor.WatchDir, entry.Name()) + fn := filepath.Join(c.GetConfig().Collectors.FileIngestor.WatchDir, entry.Name()) - switch c.config.Collectors.FileIngestor.WatchMode { + switch c.GetConfig().Collectors.FileIngestor.WatchMode { case pkgconfig.ModePCAP: // process file with pcap extension if filepath.Ext(fn) == ".pcap" || filepath.Ext(fn) == ".pcap.gz" { @@ -420,61 +351,55 @@ func (c *FileIngestor) Run() { } // then watch for new one - c.watcher, err = fsnotify.NewWatcher() + watcher, err := fsnotify.NewWatcher() if err != nil { - log.Fatal(err) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] new watcher: ", err) } // register the folder to watch - err = c.watcher.Add(c.config.Collectors.FileIngestor.WatchDir) + err = watcher.Add(c.GetConfig().Collectors.FileIngestor.WatchDir) if err != nil { - log.Fatal(err) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] register folder: ", err) } - go func() { - for { - select { - // new config provided? - case cfg, opened := <-c.configChan: - if !opened { - return - } - c.config = cfg - c.ReadConfig() + for { + select { + case <-c.OnStop(): + c.LogInfo("stop to listen...") - c.dnsProcessor.ConfigChan <- cfg - c.dnstapProcessor.ConfigChan <- cfg + // stop watching + watcher.Close() - case event, ok := <-c.watcher.Events: - if !ok { // Channel was closed (i.e. Watcher.Close() was called). - return - } + // stop processors + c.dnsProcessor.Stop() + c.dnstapProcessor.Stop() + return - // detect activity on file - if !event.Has(fsnotify.Create) && !event.Has(fsnotify.Write) { - continue - } + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + c.CheckConfig() - // register the event by the name - c.RegisterEvent(event.Name) + c.dnsProcessor.ConfigChan <- cfg + c.dnstapProcessor.ConfigChan <- cfg - case err, ok := <-c.watcher.Errors: - if !ok { - return - } - c.LogError("error:", err) + case event, ok := <-watcher.Events: + if !ok { // Channel was closed (i.e. Watcher.Close() was called). + return } - } - }() - - <-c.exit - // stop watching - c.watcher.Close() + // detect activity on file + if !event.Has(fsnotify.Create) && !event.Has(fsnotify.Write) { + continue + } - // stop processors - c.dnsProcessor.Stop() - c.dnstapProcessor.Stop() + // register the event by the name + c.RegisterEvent(event.Name) - c.LogInfo("run terminated") - c.done <- true + case err, ok := <-watcher.Errors: + if !ok { + return + } + c.LogError("error:", err) + } + } } diff --git a/collectors/file_tail.go b/collectors/file_tail.go index a01c0835..3be1bf50 100644 --- a/collectors/file_tail.go +++ b/collectors/file_tail.go @@ -9,7 +9,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -19,88 +19,21 @@ import ( ) type Tail struct { - doneRun, stopRun chan bool - tailf *tail.Tail - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string + *pkgutils.Collector + tailf *tail.Tail } -func NewTail(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Tail { - logger.Info(pkgutils.PrefixLogCollector+"[%s] tail - enabled", name) - s := &Tail{ - doneRun: make(chan bool), - stopRun: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() +func NewTail(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Tail { + s := &Tail{Collector: pkgutils.NewCollector(config, logger, name, "tail")} + s.SetDefaultRoutes(next) return s } -func (c *Tail) GetName() string { return c.name } - -func (c *Tail) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *Tail) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *Tail) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *Tail) Loggers() []chan dnsutils.DNSMessage { - channels := []chan dnsutils.DNSMessage{} - for _, p := range c.defaultRoutes { - channels = append(channels, p.GetInputChannel()) - } - return channels -} - -func (c *Tail) ReadConfig() {} - -func (c *Tail) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *Tail) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] tail - "+msg, v...) -} - -func (c *Tail) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] tail - "+msg, v...) -} - -func (c *Tail) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *Tail) Stop() { - c.LogInfo("stopping collector...") - - // Stop to follow file - c.LogInfo("stop following file...") - c.tailf.Stop() - - // read done channel and block until run is terminated - c.stopRun <- true - <-c.doneRun -} - func (c *Tail) Follow() error { var err error location := tail.SeekInfo{Offset: 0, Whence: io.SeekEnd} config := tail.Config{Location: &location, ReOpen: true, Follow: true, Logger: tail.DiscardingLogger, Poll: true, MustExist: true} - c.tailf, err = tail.TailFile(c.config.Collectors.Tail.FilePath, config) + c.tailf, err = tail.TailFile(c.GetConfig().Collectors.Tail.FilePath, config) if err != nil { return err } @@ -108,14 +41,20 @@ func (c *Tail) Follow() error { } func (c *Tail) Run() { - c.LogInfo("starting collector...") + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() + err := c.Follow() if err != nil { - c.logger.Fatal("collector tail - unable to follow file: ", err) + c.LogFatal("collector tail - unable to follow file: ", err) } // prepare enabled transformers - subprocessors := transformers.NewTransforms(&c.config.IngoingTransformers, c.logger, c.name, c.Loggers(), 0) + defaultRoutes, defaultNames := pkgutils.GetRoutes(c.GetDefaultRoutes()) + subprocessors := transformers.NewTransforms(&c.GetConfig().IngoingTransformers, c.GetLogger(), c.GetName(), defaultRoutes, 0) // init dns message dm := dnsutils.DNSMessage{} @@ -131,39 +70,31 @@ func (c *Tail) Run() { dm.DNSTap.Identity = "undefined" } -RUN_LOOP: for { select { - // new config provided? - case cfg, opened := <-c.configChan: - if !opened { - return - } - c.config = cfg - c.ReadConfig() - + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) subprocessors.ReloadConfig(&cfg.IngoingTransformers) - case <-c.stopRun: - // cleanup transformers + case <-c.OnStop(): + c.LogInfo("stopping...") subprocessors.Reset() - - c.doneRun <- true - break RUN_LOOP + return case line := <-c.tailf.Lines: var matches []string var re *regexp.Regexp - if len(c.config.Collectors.Tail.PatternQuery) > 0 { - re = regexp.MustCompile(c.config.Collectors.Tail.PatternQuery) + if len(c.GetConfig().Collectors.Tail.PatternQuery) > 0 { + re = regexp.MustCompile(c.GetConfig().Collectors.Tail.PatternQuery) matches = re.FindStringSubmatch(line.Text) dm.DNS.Type = dnsutils.DNSQuery dm.DNSTap.Operation = dnsutils.DNSTapOperationQuery } - if len(c.config.Collectors.Tail.PatternReply) > 0 && len(matches) == 0 { - re = regexp.MustCompile(c.config.Collectors.Tail.PatternReply) + if len(c.GetConfig().Collectors.Tail.PatternReply) > 0 && len(matches) == 0 { + re = regexp.MustCompile(c.GetConfig().Collectors.Tail.PatternReply) matches = re.FindStringSubmatch(line.Text) dm.DNS.Type = dnsutils.DNSReply dm.DNSTap.Operation = dnsutils.DNSTapOperationReply @@ -181,7 +112,7 @@ RUN_LOOP: var t time.Time timestampIndex := re.SubexpIndex("timestamp") if timestampIndex != -1 { - t, err = time.Parse(c.config.Collectors.Tail.TimeLayout, matches[timestampIndex]) + t, err = time.Parse(c.GetConfig().Collectors.Tail.TimeLayout, matches[timestampIndex]) if err != nil { continue } @@ -229,14 +160,14 @@ RUN_LOOP: if familyIndex != -1 { dm.NetworkInfo.Family = matches[familyIndex] } else { - dm.NetworkInfo.Family = netlib.ProtoIPv4 + dm.NetworkInfo.Family = netutils.ProtoIPv4 } protocolIndex := re.SubexpIndex("protocol") if protocolIndex != -1 { dm.NetworkInfo.Protocol = matches[protocolIndex] } else { - dm.NetworkInfo.Protocol = netlib.ProtoUDP + dm.NetworkInfo.Protocol = netutils.ProtoUDP } lengthIndex := re.SubexpIndex("length") @@ -298,12 +229,13 @@ RUN_LOOP: } // dispatch dns message to connected loggers - chanLoggers := c.Loggers() - for i := range chanLoggers { - chanLoggers[i] <- dm + for i := range defaultRoutes { + select { + case defaultRoutes[i] <- dm: // Successful send to logger channel + default: + c.NextStanzaIsBusy(defaultNames[i]) + } } } } - - c.LogInfo("run terminated") } diff --git a/collectors/powerdns.go b/collectors/powerdns.go index 2f06cef5..1afd3cbb 100644 --- a/collectors/powerdns.go +++ b/collectors/powerdns.go @@ -2,17 +2,14 @@ package collectors import ( "bufio" - "crypto/tls" "errors" - "fmt" "io" "net" - "strconv" "sync" + "sync/atomic" "time" - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -21,124 +18,66 @@ import ( ) type ProtobufPowerDNS struct { - doneRun, stopRun chan bool - doneMonitor, stopMonitor chan bool - cleanup chan bool - stopCalled bool - listen net.Listener - connID int - conns []net.Conn - droppedRoutes, defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string - droppedCount int - dropped chan int - pdnsProcessors []*processors.PdnsProcessor - sync.RWMutex + *pkgutils.Collector + connCounter uint64 } -func NewProtobufPowerDNS(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *ProtobufPowerDNS { - logger.Info(pkgutils.PrefixLogCollector+"[%s] powerdns - enabled", name) - s := &ProtobufPowerDNS{ - doneRun: make(chan bool), - doneMonitor: make(chan bool), - stopRun: make(chan bool), - stopMonitor: make(chan bool), - cleanup: make(chan bool), - dropped: make(chan int), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() +func NewProtobufPowerDNS(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *ProtobufPowerDNS { + s := &ProtobufPowerDNS{Collector: pkgutils.NewCollector(config, logger, name, "powerdns")} + s.SetDefaultRoutes(next) + s.CheckConfig() return s } -func (c *ProtobufPowerDNS) GetName() string { return c.name } - -func (c *ProtobufPowerDNS) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *ProtobufPowerDNS) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *ProtobufPowerDNS) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *ProtobufPowerDNS) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *ProtobufPowerDNS) ReadConfig() { - if !pkgconfig.IsValidTLS(c.config.Collectors.PowerDNS.TLSMinVersion) { - c.logger.Fatal(pkgutils.PrefixLogCollector + "[" + c.name + "] powerdns - invalid tls min version") +func (c *ProtobufPowerDNS) CheckConfig() { + if !pkgconfig.IsValidTLS(c.GetConfig().Collectors.PowerDNS.TLSMinVersion) { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] invalid tls min version") } } -func (c *ProtobufPowerDNS) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *ProtobufPowerDNS) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] powerdns - "+msg, v...) -} - -func (c *ProtobufPowerDNS) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] powerdns - "+msg, v...) -} - -// func (c *ProtobufPowerDNS) LogConnInfo(connID int, msg string, v ...interface{}) { -// prefix := fmt.Sprintf(pkgutils.PrefixLogCollector+"[%s] powerdns#%d - ", c.name, connID) -// c.logger.Info(prefix+msg, v...) -// } - -// func (c *ProtobufPowerDNS) LogConnError(connID int, msg string, v ...interface{}) { -// prefix := fmt.Sprintf(pkgutils.PrefixLogCollector+"[%s] powerdns#%d - ", c.name, connID) -// c.logger.Error(prefix+msg, v...) -// } - -func (c *ProtobufPowerDNS) HandleConn(conn net.Conn) { +func (c *ProtobufPowerDNS) HandleConn(conn net.Conn, connID uint64, forceClose chan bool, wg *sync.WaitGroup) { // close connection on function exit - defer conn.Close() - - var connID int - c.Lock() - c.connID++ - connID = c.connID - c.Unlock() + defer func() { + c.LogInfo("conn #%d - connection handler terminated", connID) + netutils.Close(conn, c.GetConfig().Collectors.Dnstap.ResetConn) + wg.Done() + }() // get peer address peer := conn.RemoteAddr().String() - peerName := netlib.GetPeerName(peer) + peerName := netutils.GetPeerName(peer) c.LogInfo("new connection #%d from %s (%s)", connID, peer, peerName) // start protobuf subprocessor - pdnsProc := processors.NewPdnsProcessor( - connID, - peerName, - c.config, - c.logger, - c.name, - c.config.Collectors.PowerDNS.ChannelBufferSize, - ) - c.Lock() - c.pdnsProcessors = append(c.pdnsProcessors, &pdnsProc) - c.Unlock() - go pdnsProc.Run(c.defaultRoutes, c.droppedRoutes) + pdnsProcessor := processors.NewPdnsProcessor(int(connID), peerName, c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.PowerDNS.ChannelBufferSize) + go pdnsProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) r := bufio.NewReader(conn) pbs := powerdns_protobuf.NewProtobufStream(r, conn, 5*time.Second) var err error var payload *powerdns_protobuf.ProtoPayload + cleanup := make(chan struct{}) + + // goroutine to close the connection properly + go func() { + defer func() { + pdnsProcessor.Stop() + c.LogInfo("conn #%d - cleanup connection handler terminated", connID) + }() + + for { + select { + case <-forceClose: + c.LogInfo("conn #%d - force to cleanup the connection handler", connID) + netutils.Close(conn, c.GetConfig().Collectors.Dnstap.ResetConn) + return + case <-cleanup: + c.LogInfo("conn #%d - cleanup the connection handler", connID) + return + } + } + }() for { payload, err = pbs.RecvPayload(false) @@ -161,233 +100,80 @@ func (c *ProtobufPowerDNS) HandleConn(conn net.Conn) { c.LogError("conn #%d - powerdns reader error: %s", connID, err) } - // stop processor - // the Stop function is already called, don't stop again - if !c.stopCalled { - pdnsProc.Stop() - } + // exit goroutine + close(cleanup) break } // send payload to the channel select { - case pdnsProc.GetChannel() <- payload.Data(): // Successful send + case pdnsProcessor.GetChannel() <- payload.Data(): // Successful send default: - c.dropped <- 1 - } - } - - // to avoid lock if the Stop function is already called - if c.stopCalled { - c.LogInfo("conn #%d - connection handler exited", connID) - return - } - - // here the connection is closed, - // then removes the current tap processor from the list - c.Lock() - for i, t := range c.pdnsProcessors { - if t.ConnID == connID { - c.pdnsProcessors = append(c.pdnsProcessors[:i], c.pdnsProcessors[i+1:]...) - } - } - - // finnaly removes the current connection from the list - for j, cn := range c.conns { - if cn == conn { - c.conns = append(c.conns[:j], c.conns[j+1:]...) - conn = nil + c.ProcessorIsBusy() } } - c.Unlock() - - c.LogInfo("conn #%d - connection handler terminated", connID) -} - -func (c *ProtobufPowerDNS) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *ProtobufPowerDNS) Stop() { - c.Lock() - defer c.Unlock() - - // to avoid some lock situations when the remose side closes - // the connection at the same time of this Stop function - c.stopCalled = true - c.LogInfo("stopping collector...") - - // stop all powerdns processors - c.LogInfo("cleanup all active processors...") - for _, pdnsProc := range c.pdnsProcessors { - pdnsProc.Stop() - } - - // closing properly current connections if exists - c.LogInfo("closing connected peers...") - for _, conn := range c.conns { - peer := conn.RemoteAddr().String() - c.LogInfo("%s - closing connection...", peer) - netlib.Close(conn, c.config.Collectors.PowerDNS.ResetConn) - } - - // Finally close the listener to unblock accept - c.LogInfo("stop listening...") - c.listen.Close() - - // stop monitor goroutine - c.LogInfo("stopping monitor...") - c.stopMonitor <- true - <-c.doneMonitor - - // read done channel and block until run is terminated - c.LogInfo("stopping run...") - c.stopRun <- true - <-c.doneRun } -func (c *ProtobufPowerDNS) Listen() error { - c.Lock() - defer c.Unlock() - - c.LogInfo("running in background...") - - var err error - var listener net.Listener - addrlisten := c.config.Collectors.PowerDNS.ListenIP + ":" + strconv.Itoa(c.config.Collectors.PowerDNS.ListenPort) - - // listening with tls enabled ? - if c.config.Collectors.PowerDNS.TLSSupport { - c.LogInfo("tls support enabled") - var cer tls.Certificate - cer, err = tls.LoadX509KeyPair(c.config.Collectors.PowerDNS.CertFile, c.config.Collectors.PowerDNS.KeyFile) - if err != nil { - c.logger.Fatal("loading certificate failed:", err) - } - - // prepare tls configuration - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cer}, - MinVersion: tls.VersionTLS12, - } +func (c *ProtobufPowerDNS) Run() { + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() - // update tls min version according to the user config - tlsConfig.MinVersion = pkgconfig.TLSVersion[c.config.Collectors.PowerDNS.TLSMinVersion] + var connWG sync.WaitGroup + connCleanup := make(chan bool) + cfg := c.GetConfig().Collectors.PowerDNS - listener, err = tls.Listen(netlib.SocketTCP, addrlisten, tlsConfig) - } else { - listener, err = net.Listen(netlib.SocketTCP, addrlisten) - } - // something is wrong ? + // start to listen + listener, err := netutils.StartToListen( + cfg.ListenIP, cfg.ListenPort, "", + cfg.TLSSupport, pkgconfig.TLSVersion[cfg.TLSMinVersion], + cfg.CertFile, cfg.KeyFile) if err != nil { - return err + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] listening failed: ", err) } - c.LogInfo("is listening on %s", listener.Addr()) - c.listen = listener - return nil -} - -func (c *ProtobufPowerDNS) MonitorCollector() { - watchInterval := 10 * time.Second - bufferFull := time.NewTimer(watchInterval) -MONITOR_LOOP: - for { - select { - case <-c.stopMonitor: - close(c.dropped) - bufferFull.Stop() - c.doneMonitor <- true - break MONITOR_LOOP - - case <-c.dropped: - c.droppedCount++ - - case <-bufferFull.C: - if c.droppedCount > 0 { - c.LogError("recv buffer is full, %d packet(s) dropped", c.droppedCount) - c.droppedCount = 0 - } - bufferFull.Reset(watchInterval) - } - } - c.LogInfo("monitor terminated") -} - -func (c *ProtobufPowerDNS) Run() { - - c.LogInfo("starting collector...") - if c.listen == nil { - if err := c.Listen(); err != nil { - prefixlog := fmt.Sprintf("[%s] ", c.name) - c.logger.Fatal(prefixlog+"collector=powerdns listening failed: ", err) - } - } - - // start goroutine to count dropped messsages - go c.MonitorCollector() + c.LogInfo("listening on %s", listener.Addr()) // goroutine to Accept() blocks waiting for new connection. acceptChan := make(chan net.Conn) - go func() { - for { - conn, err := c.listen.Accept() - if err != nil { - return - } - acceptChan <- conn - } - }() + netutils.AcceptConnections(listener, acceptChan) -RUN_LOOP: + // main loop for { select { - case <-c.stopRun: - close(acceptChan) - c.doneRun <- true - break RUN_LOOP + case <-c.OnStop(): + c.LogInfo("stop to listen...") + listener.Close() - case cfg := <-c.configChan: - // save the new config - c.config = cfg - c.ReadConfig() + c.LogInfo("closing connected peers...") + close(connCleanup) + connWG.Wait() + return - // refresh config for all conns - for i := range c.pdnsProcessors { - c.pdnsProcessors[i].ConfigChan <- cfg - } + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + c.CheckConfig() case conn, opened := <-acceptChan: if !opened { return } - if c.config.Collectors.Dnstap.RcvBufSize > 0 { - before, actual, err := netlib.SetSockRCVBUF( - conn, - c.config.Collectors.Dnstap.RcvBufSize, - c.config.Collectors.Dnstap.TLSSupport, - ) + if c.GetConfig().Collectors.Dnstap.RcvBufSize > 0 { + before, actual, err := netutils.SetSockRCVBUF(conn, cfg.RcvBufSize, cfg.TLSSupport) if err != nil { - c.logger.Fatal("Unable to set SO_RCVBUF: ", err) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] unable to set SO_RCVBUF: ", err) } - c.LogInfo("set SO_RCVBUF option, value before: %d, desired: %d, actual: %d", - before, - c.config.Collectors.Dnstap.RcvBufSize, - actual) + c.LogInfo("set SO_RCVBUF option, value before: %d, desired: %d, actual: %d", before, cfg.RcvBufSize, actual) } - // to avoid lock if the Stop function is already called - if c.stopCalled { - continue - } - - c.Lock() - c.conns = append(c.conns, conn) - c.Unlock() - go c.HandleConn(conn) + // handle the connection + connWG.Add(1) + connID := atomic.AddUint64(&c.connCounter, 1) + go c.HandleConn(conn, connID, connCleanup, &connWG) } } - - c.LogInfo("run terminated") } diff --git a/collectors/powerdns_test.go b/collectors/powerdns_test.go index 241748e3..6960ce20 100644 --- a/collectors/powerdns_test.go +++ b/collectors/powerdns_test.go @@ -1,11 +1,11 @@ package collectors import ( - "log" "net" "testing" + "time" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-logger" @@ -15,12 +15,11 @@ func TestPowerDNS_Run(t *testing.T) { g := pkgutils.NewFakeLogger() c := NewProtobufPowerDNS([]pkgutils.Worker{g}, pkgconfig.GetFakeConfig(), logger.New(false), "test") - if err := c.Listen(); err != nil { - log.Fatal("collector powerdns listening error: ", err) - } go c.Run() - conn, err := net.Dial(netlib.SocketTCP, ":6001") + // wait before to connect + time.Sleep(1 * time.Second) + conn, err := net.Dial(netutils.SocketTCP, ":6001") if err != nil { t.Error("could not connect to TCP server: ", err) } diff --git a/collectors/sniffer_afpacket.go b/collectors/sniffer_afpacket.go index 527217c8..12a36ab8 100644 --- a/collectors/sniffer_afpacket.go +++ b/collectors/sniffer_afpacket.go @@ -1,450 +1,26 @@ -//go:build linux -// +build linux +//go:build windows || darwin || freebsd +// +build windows darwin freebsd package collectors import ( - "encoding/binary" - "errors" - "net" - "os" - "syscall" - "time" - "unsafe" - - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-dnscollector/processors" "github.com/dmachard/go-logger" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "golang.org/x/net/bpf" - "golang.org/x/sys/unix" ) -// Convert a uint16 to host byte order (big endian) -func Htons(v uint16) int { - return int((v << 8) | (v >> 8)) -} - -func GetBPFFilterIngress(port int) []bpf.Instruction { - // bpf filter: (ip or ip6 ) and (udp or tcp) and port 53 - // fragmented packets are ignored - var filter = []bpf.Instruction{ - // Load eth.type (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 12, Size: 2}, - // if eth.type == IPv4 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 8}, - // Load ip.proto (1 byte at offset 23) and push-it in register A - bpf.LoadAbsolute{Off: 23, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 12}, - // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet - bpf.LoadAbsolute{Off: 20, Size: 2}, - // Only look at the last 13 bits of the data saved in regiter A - // 0x1fff == 0001 1111 1111 1111 (fragment offset) - // If any of the data in fragment offset is true, ignore the packet - bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 10, SkipFalse: 0}, - // Load ip.length - // Register X = ip header len * 4 - bpf.LoadMemShift{Off: 14}, - // Load source port in tcp or udp (2 bytes at offset x+14) - bpf.LoadIndirect{Off: 14, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 6, SkipFalse: 7}, - // if eth.type == IPv6 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 6}, - // Load ipv6.nxt (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 20, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 3}, - // Load source port tcp or udp (2 bytes at offset 54) - bpf.LoadAbsolute{Off: 54, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1}, - // Keep the packet and send up to 65k of the packet to userspace - bpf.RetConstant{Val: 0xFFFF}, - // Ignore packet - bpf.RetConstant{Val: 0}, - } - return filter -} - -func GetBpfFilter(port int) []bpf.Instruction { - // bpf filter: (ip or ip6 ) and (udp or tcp) and port 53 - // fragmented packets are ignored - var filter = []bpf.Instruction{ - // Load eth.type (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 12, Size: 2}, - // if eth.type == IPv4 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 10}, - // Load ip.proto (1 byte at offset 23) and push-it in register A - bpf.LoadAbsolute{Off: 23, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 16}, - // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet - bpf.LoadAbsolute{Off: 20, Size: 2}, - // Only look at the last 13 bits of the data saved in regiter A - // 0x1fff == 0001 1111 1111 1111 (fragment offset) - // If any of the data in fragment offset is true, ignore the packet - bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 14, SkipFalse: 0}, - // Load ip.length - // Register X = ip header len * 4 - bpf.LoadMemShift{Off: 14}, - // Load source port in tcp or udp (2 bytes at offset x+14) - bpf.LoadIndirect{Off: 14, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 10, SkipFalse: 0}, - // Load destination port in tcp or udp (2 bytes at offset x+16) - bpf.LoadIndirect{Off: 16, Size: 2}, - // destination port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 8, SkipFalse: 9}, - // if eth.type == IPv6 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 8}, - // Load ipv6.nxt (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 20, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 5}, - // Load source port tcp or udp (2 bytes at offset 54) - bpf.LoadAbsolute{Off: 54, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 2, SkipFalse: 0}, - // Load destination port tcp or udp (2 bytes at offset 56) - bpf.LoadAbsolute{Off: 56, Size: 2}, - // destination port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1}, - // Keep the packet and send up to 65k of the packet to userspace - bpf.RetConstant{Val: 0xFFFF}, - // Ignore packet - bpf.RetConstant{Val: 0}, - } - return filter -} - -func ApplyBpfFilter(filter []bpf.Instruction, fd int) (err error) { - var assembled []bpf.RawInstruction - if assembled, err = bpf.Assemble(filter); err != nil { - return err - } - - prog := &unix.SockFprog{ - Len: uint16(len(assembled)), - Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])), - } - - return unix.SetsockoptSockFprog(fd, syscall.SOL_SOCKET, syscall.SO_ATTACH_FILTER, prog) -} - -func RemoveBpfFilter(fd int) (err error) { - return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_DETACH_FILTER, 0) -} - type AfpacketSniffer struct { - done, exit chan bool - fd int - identity string - defaultRoutes, droppedRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string + *pkgutils.Collector } -func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] afpacket sniffer - enabled", name) - s := &AfpacketSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - } +func NewAfpacketSniffer(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { + s := &AfpacketSniffer{Collector: pkgutils.NewCollector(config, logger, name, "AFPACKET sniffer")} + s.SetDefaultRoutes(next) s.ReadConfig() return s } -func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] afpacket sniffer- "+msg, v...) -} - -func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] afpacket sniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) GetName() string { return c.name } - -func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *AfpacketSniffer) ReadConfig() { - c.identity = c.config.GetServerIdentity() -} - -func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *AfpacketSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *AfpacketSniffer) Listen() error { - // raw socket - fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, Htons(syscall.ETH_P_ALL)) - if err != nil { - return err - } - - // bind to device ? - if c.config.Collectors.AfpacketLiveCapture.Device != "" { - iface, err := net.InterfaceByName(c.config.Collectors.AfpacketLiveCapture.Device) - if err != nil { - return err - } - - ll := syscall.SockaddrLinklayer{ - Ifindex: iface.Index, - } - - if err := syscall.Bind(fd, &ll); err != nil { - return err - } - - c.LogInfo("binding with success to iface %q (index %d)", iface.Name, iface.Index) - } - - // set nano timestamp - err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TIMESTAMPNS, 1) - if err != nil { - return err - } - - filter := GetBpfFilter(c.config.Collectors.AfpacketLiveCapture.Port) - err = ApplyBpfFilter(filter, fd) - if err != nil { - return err - } - - c.LogInfo("BPF filter applied") - - c.fd = fd - return nil -} - func (c *AfpacketSniffer) Run() { - c.LogInfo("starting collector...") - defer RemoveBpfFilter(c.fd) - defer syscall.Close(c.fd) - - if c.fd == 0 { - if err := c.Listen(); err != nil { - c.LogError("init raw socket failed: %v\n", err) - os.Exit(1) // nolint - } - } - - dnsProcessor := processors.NewDNSProcessor( - c.config, - c.logger, - c.name, - c.config.Collectors.AfpacketLiveCapture.ChannelBufferSize, - ) - go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes) - - dnsChan := make(chan netlib.DNSPacket) - udpChan := make(chan gopacket.Packet) - tcpChan := make(chan gopacket.Packet) - fragIP4Chan := make(chan gopacket.Packet) - fragIP6Chan := make(chan gopacket.Packet) - - netDecoder := &netlib.NetDecoder{} - - // defrag ipv4 - go netlib.IPDefragger(fragIP4Chan, udpChan, tcpChan) - // defrag ipv6 - go netlib.IPDefragger(fragIP6Chan, udpChan, tcpChan) - // tcp assembly - go netlib.TCPAssembler(tcpChan, dnsChan, 0) - // udp processor - go netlib.UDPProcessor(udpChan, dnsChan, 0) - - // goroutine to read all packets reassembled - go func() { - // prepare dns message - dm := dnsutils.DNSMessage{} - - for { - select { - // new config provided? - case cfg, opened := <-c.configChan: - if !opened { - return - } - c.config = cfg - c.ReadConfig() - - // send the config to the dns processor - dnsProcessor.ConfigChan <- cfg - - // dns message to read ? - case dnsPacket := <-dnsChan: - // reset - dm.Init() - - dm.NetworkInfo.Family = dnsPacket.IPLayer.EndpointType().String() - dm.NetworkInfo.QueryIP = dnsPacket.IPLayer.Src().String() - dm.NetworkInfo.ResponseIP = dnsPacket.IPLayer.Dst().String() - dm.NetworkInfo.QueryPort = dnsPacket.TransportLayer.Src().String() - dm.NetworkInfo.ResponsePort = dnsPacket.TransportLayer.Dst().String() - dm.NetworkInfo.Protocol = dnsPacket.TransportLayer.EndpointType().String() - - dm.DNS.Payload = dnsPacket.Payload - dm.DNS.Length = len(dnsPacket.Payload) - - dm.DNSTap.Identity = c.identity - - timestamp := dnsPacket.Timestamp.UnixNano() - seconds := timestamp / int64(time.Second) - dm.DNSTap.TimeSec = int(seconds) - dm.DNSTap.TimeNsec = int(timestamp - seconds*int64(time.Second)*int64(time.Nanosecond)) - - // send DNS message to DNS processor - dnsProcessor.GetChannel() <- dm - } - } - }() - - go func() { - buf := make([]byte, 65536) - oob := make([]byte, 100) - - for { - // flags, from - bufN, oobn, _, _, err := syscall.Recvmsg(c.fd, buf, oob, 0) - if err != nil { - if errors.Is(err, syscall.EINTR) { - continue - } else { - panic(err) - } - } - if bufN == 0 { - panic("buf empty") - } - if bufN > len(buf) { - panic("buf overflow") - } - if oobn == 0 { - panic("oob missing") - } - - scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) - if err != nil { - panic(err) - } - if len(scms) != 1 { - continue - } - scm := scms[0] - if scm.Header.Type != syscall.SCM_TIMESTAMPNS { - panic("scm timestampns missing") - } - tsec := binary.LittleEndian.Uint32(scm.Data[:4]) - nsec := binary.LittleEndian.Uint32(scm.Data[8:12]) - timestamp := time.Unix(int64(tsec), int64(nsec)) - - // copy packet data from buffer - pkt := make([]byte, bufN) - copy(pkt, buf[:bufN]) - - // decode minimal layers - packet := gopacket.NewPacket(pkt, netDecoder, gopacket.NoCopy) - packet.Metadata().CaptureLength = len(packet.Data()) - packet.Metadata().Length = len(packet.Data()) - packet.Metadata().Timestamp = timestamp - - // some security checks - if packet.NetworkLayer() == nil { - continue - } - if packet.TransportLayer() == nil { - continue - } - - // ipv4 fragmented packet ? - if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv4 { - ip4 := packet.NetworkLayer().(*layers.IPv4) - if ip4.Flags&layers.IPv4MoreFragments == 1 || ip4.FragOffset > 0 { - fragIP4Chan <- packet - continue - } - } - - // ipv6 fragmented packet ? - if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv6 { - v6frag := packet.Layer(layers.LayerTypeIPv6Fragment) - if v6frag != nil { - fragIP6Chan <- packet - continue - } - } - - // tcp or udp packets ? - if packet.TransportLayer().LayerType() == layers.LayerTypeUDP { - udpChan <- packet - } - - if packet.TransportLayer().LayerType() == layers.LayerTypeTCP { - tcpChan <- packet - } - } - - }() - - <-c.exit - close(dnsChan) - close(c.configChan) - - // stop dns processor - dnsProcessor.Stop() - - c.LogInfo("run terminated") - c.done <- true + c.LogError("running collector failed...OS not supported!") + c.StopIsDone() } diff --git a/collectors/sniffer_afpacket_darwin.go b/collectors/sniffer_afpacket_darwin.go deleted file mode 100644 index 61c7145e..00000000 --- a/collectors/sniffer_afpacket_darwin.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build darwin -// +build darwin - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type AfpacketSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for macos, not yet supported -func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] AFPACKET sniffer - enabled", name) - s := &AfpacketSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *AfpacketSniffer) GetName() string { return c.name } - -func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] dnssniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] dnssniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *AfpacketSniffer) ReadConfig() {} - -func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *AfpacketSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *AfpacketSniffer) Run() { - c.LogInfo("run terminated") - c.done <- true -} diff --git a/collectors/sniffer_afpacket_freebsd.go b/collectors/sniffer_afpacket_freebsd.go deleted file mode 100644 index 04dd5aaa..00000000 --- a/collectors/sniffer_afpacket_freebsd.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build freebsd -// +build freebsd - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type AfpacketSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for freebsd, not yet supported -func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] AFPACKET sniffer - enabled", name) - s := &AfpacketSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *AfpacketSniffer) GetName() string { return c.name } - -func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] AFPACKET sniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] AFPACKET sniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *AfpacketSniffer) ReadConfig() {} - -func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *AfpacketSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *AfpacketSniffer) Run() { - c.LogInfo("Not supported") - c.done <- true -} diff --git a/collectors/sniffer_afpacket_linux.go b/collectors/sniffer_afpacket_linux.go new file mode 100644 index 00000000..14976e47 --- /dev/null +++ b/collectors/sniffer_afpacket_linux.go @@ -0,0 +1,274 @@ +//go:build linux +// +build linux + +package collectors + +import ( + "context" + "encoding/binary" + "errors" + "net" + "os" + "syscall" + "time" + + "github.com/dmachard/go-dnscollector/dnsutils" + "github.com/dmachard/go-dnscollector/netutils" + "github.com/dmachard/go-dnscollector/pkgconfig" + "github.com/dmachard/go-dnscollector/pkgutils" + "github.com/dmachard/go-dnscollector/processors" + "github.com/dmachard/go-logger" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +type AfpacketSniffer struct { + *pkgutils.Collector + fd int +} + +func NewAfpacketSniffer(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { + s := &AfpacketSniffer{Collector: pkgutils.NewCollector(config, logger, name, "afpacket sniffer")} + s.SetDefaultRoutes(next) + return s +} + +func (c *AfpacketSniffer) Listen() error { + // raw socket + fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, netutils.Htons(syscall.ETH_P_ALL)) + if err != nil { + return err + } + + // bind to device ? + if c.GetConfig().Collectors.AfpacketLiveCapture.Device != "" { + iface, err := net.InterfaceByName(c.GetConfig().Collectors.AfpacketLiveCapture.Device) + if err != nil { + return err + } + + ll := syscall.SockaddrLinklayer{ + Ifindex: iface.Index, + } + + if err := syscall.Bind(fd, &ll); err != nil { + return err + } + + c.LogInfo("binding with success to iface %q (index %d)", iface.Name, iface.Index) + } + + // set nano timestamp + err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TIMESTAMPNS, 1) + if err != nil { + return err + } + + filter := netutils.GetBpfFilter(c.GetConfig().Collectors.AfpacketLiveCapture.Port) + err = netutils.ApplyBpfFilter(filter, fd) + if err != nil { + return err + } + + c.LogInfo("BPF filter applied") + + c.fd = fd + return nil +} + +func (c *AfpacketSniffer) Run() { + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() + + if c.fd == 0 { + if err := c.Listen(); err != nil { + c.LogError("init raw socket failed: %v\n", err) + os.Exit(1) // nolint + } + } + + dnsProcessor := processors.NewDNSProcessor(c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.AfpacketLiveCapture.ChannelBufferSize) + go dnsProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) + + dnsChan := make(chan netutils.DNSPacket) + udpChan := make(chan gopacket.Packet) + tcpChan := make(chan gopacket.Packet) + fragIP4Chan := make(chan gopacket.Packet) + fragIP6Chan := make(chan gopacket.Packet) + + netDecoder := &netutils.NetDecoder{} + + // defrag ipv4 + go netutils.IPDefragger(fragIP4Chan, udpChan, tcpChan) + // defrag ipv6 + go netutils.IPDefragger(fragIP6Chan, udpChan, tcpChan) + // tcp assembly + go netutils.TCPAssembler(tcpChan, dnsChan, 0) + // udp processor + go netutils.UDPProcessor(udpChan, dnsChan, 0) + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func(ctx context.Context) { + defer func() { + dnsProcessor.Stop() + netutils.RemoveBpfFilter(c.fd) + syscall.Close(c.fd) + c.LogInfo("read data terminated") + defer close(done) + }() + + buf := make([]byte, 65536) + oob := make([]byte, 100) + + for { + select { + case <-ctx.Done(): + c.LogInfo("stopping sniffer...") + syscall.Close(c.fd) + return + default: + var fdSet syscall.FdSet + fdSet.Bits[c.fd/64] |= 1 << (uint(c.fd) % 64) + + nReady, err := syscall.Select(c.fd+1, &fdSet, nil, nil, &syscall.Timeval{Sec: 1, Usec: 0}) + if err != nil { + if errors.Is(err, syscall.EINTR) { + continue + } + panic(err) + } + if nReady == 0 { + continue + } + + bufN, oobn, _, _, err := syscall.Recvmsg(c.fd, buf, oob, syscall.MSG_DONTWAIT) + if err != nil { + if errors.Is(err, syscall.EINTR) || errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EWOULDBLOCK) { + continue + } else { + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"read data", err) + } + } + if bufN == 0 { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] buf empty") + } + if bufN > len(buf) { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] buf overflow") + } + if oobn == 0 { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] oob missing") + } + + scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] control msg", err) + } + if len(scms) != 1 { + continue + } + scm := scms[0] + if scm.Header.Type != syscall.SCM_TIMESTAMPNS { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] scm timestampns missing") + } + tsec := binary.LittleEndian.Uint32(scm.Data[:4]) + nsec := binary.LittleEndian.Uint32(scm.Data[8:12]) + timestamp := time.Unix(int64(tsec), int64(nsec)) + + // copy packet data from buffer + pkt := make([]byte, bufN) + copy(pkt, buf[:bufN]) + + // decode minimal layers + packet := gopacket.NewPacket(pkt, netDecoder, gopacket.NoCopy) + packet.Metadata().CaptureLength = len(packet.Data()) + packet.Metadata().Length = len(packet.Data()) + packet.Metadata().Timestamp = timestamp + + // some security checks + if packet.NetworkLayer() == nil { + continue + } + if packet.TransportLayer() == nil { + continue + } + + // ipv4 fragmented packet ? + if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv4 { + ip4 := packet.NetworkLayer().(*layers.IPv4) + if ip4.Flags&layers.IPv4MoreFragments == 1 || ip4.FragOffset > 0 { + fragIP4Chan <- packet + continue + } + } + + // ipv6 fragmented packet ? + if packet.NetworkLayer().LayerType() == layers.LayerTypeIPv6 { + v6frag := packet.Layer(layers.LayerTypeIPv6Fragment) + if v6frag != nil { + fragIP6Chan <- packet + continue + } + } + + // tcp or udp packets ? + if packet.TransportLayer().LayerType() == layers.LayerTypeUDP { + udpChan <- packet + } + + if packet.TransportLayer().LayerType() == layers.LayerTypeTCP { + tcpChan <- packet + } + } + } + + }(ctx) + + // prepare dns message + dm := dnsutils.DNSMessage{} + + for { + select { + case <-c.OnStop(): + c.LogInfo("stop to listen...") + cancel() + <-done + return + + // new config provided? + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + + // send the config to the dns processor + dnsProcessor.ConfigChan <- cfg + + // dns message to read ? + case dnsPacket := <-dnsChan: + // reset + dm.Init() + + dm.NetworkInfo.Family = dnsPacket.IPLayer.EndpointType().String() + dm.NetworkInfo.QueryIP = dnsPacket.IPLayer.Src().String() + dm.NetworkInfo.ResponseIP = dnsPacket.IPLayer.Dst().String() + dm.NetworkInfo.QueryPort = dnsPacket.TransportLayer.Src().String() + dm.NetworkInfo.ResponsePort = dnsPacket.TransportLayer.Dst().String() + dm.NetworkInfo.Protocol = dnsPacket.TransportLayer.EndpointType().String() + + dm.DNS.Payload = dnsPacket.Payload + dm.DNS.Length = len(dnsPacket.Payload) + + dm.DNSTap.Identity = c.GetConfig().GetServerIdentity() + + timestamp := dnsPacket.Timestamp.UnixNano() + seconds := timestamp / int64(time.Second) + dm.DNSTap.TimeSec = int(seconds) + dm.DNSTap.TimeNsec = int(timestamp - seconds*int64(time.Second)*int64(time.Nanosecond)) + + // send DNS message to DNS processor + dnsProcessor.GetChannel() <- dm + } + } +} diff --git a/collectors/sniffer_afpacket_windows.go b/collectors/sniffer_afpacket_windows.go deleted file mode 100644 index 91cd3c23..00000000 --- a/collectors/sniffer_afpacket_windows.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build windows -// +build windows - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type AfpacketSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for macos, not yet supported -func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] AFPACKET sniffer - enabled", name) - s := &AfpacketSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *AfpacketSniffer) GetName() string { return c.name } - -func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] AFPACKET sniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] AFPACKET sniffer - "+msg, v...) -} - -func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *AfpacketSniffer) ReadConfig() {} - -func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *AfpacketSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *AfpacketSniffer) Run() { - c.LogInfo("Not supported") - c.done <- true -} diff --git a/collectors/sniffer_xdp.go b/collectors/sniffer_xdp.go index 9baf2b8d..038c7b4e 100644 --- a/collectors/sniffer_xdp.go +++ b/collectors/sniffer_xdp.go @@ -5,16 +5,17 @@ package collectors import ( "bytes" + "context" "encoding/binary" + "errors" "fmt" "net" - "os" "time" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/perf" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/processors" @@ -23,122 +24,37 @@ import ( "golang.org/x/sys/unix" ) -func GetIPAddress[T uint32 | [4]uint32](ip T, mapper func(T) net.IP) net.IP { - return mapper(ip) -} - -func ConvertIP4(ip uint32) net.IP { - addr := make(net.IP, net.IPv4len) - binary.BigEndian.PutUint32(addr, ip) - return addr -} - -func ConvertIP6(ip [4]uint32) net.IP { - addr := make(net.IP, net.IPv6len) - binary.LittleEndian.PutUint32(addr[0:], ip[0]) - binary.LittleEndian.PutUint32(addr[4:], ip[1]) - binary.LittleEndian.PutUint32(addr[8:], ip[2]) - binary.LittleEndian.PutUint32(addr[12:], ip[3]) - return addr -} - type XDPSniffer struct { - done, exit chan bool - identity string - defaultRoutes, droppedRoutes []pkgutils.Worker - config *pkgconfig.Config - configChan chan *pkgconfig.Config - logger *logger.Logger - name string + *pkgutils.Collector } -func NewXDPSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] xdp sniffer - enabled", name) - s := &XDPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - configChan: make(chan *pkgconfig.Config), - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() +func NewXDPSniffer(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer { + s := &XDPSniffer{Collector: pkgutils.NewCollector(config, logger, name, "xdp sniffer")} + s.SetDefaultRoutes(next) return s } -func (c *XDPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] xdp sniffer - "+msg, v...) -} - -func (c *XDPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] xdp sniffer - "+msg, v...) -} - -func (c *XDPSniffer) GetName() string { return c.name } - -func (c *XDPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *XDPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *XDPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *XDPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *XDPSniffer) ReadConfig() { - c.identity = c.config.GetServerIdentity() -} - -func (c *XDPSniffer) ReloadConfig(config *pkgconfig.Config) { - c.LogInfo("reload configuration...") - c.configChan <- config -} - -func (c *XDPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *XDPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - func (c *XDPSniffer) Run() { - c.LogInfo("starting collector...") + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() - dnsProcessor := processors.NewDNSProcessor( - c.config, - c.logger, - c.name, - c.config.Collectors.XdpLiveCapture.ChannelBufferSize, - ) - go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes) + // init dns processor + dnsProcessor := processors.NewDNSProcessor(c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.XdpLiveCapture.ChannelBufferSize) + go dnsProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) - iface, err := net.InterfaceByName(c.config.Collectors.XdpLiveCapture.Device) + // get network interface by name + iface, err := net.InterfaceByName(c.GetConfig().Collectors.XdpLiveCapture.Device) if err != nil { - c.LogError("lookup network iface: %s", err) - os.Exit(1) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] lookup network iface: ", err) } // Load pre-compiled programs into the kernel. objs := xdp.BpfObjects{} if err := xdp.LoadBpfObjects(&objs, nil); err != nil { - c.LogError("loading BPF objects: %s", err) - os.Exit(1) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] loading BPF objects: ", err) } defer objs.Close() @@ -148,8 +64,7 @@ func (c *XDPSniffer) Run() { Interface: iface.Index, }) if err != nil { - c.LogError("could not attach XDP program: %s", err) - os.Exit(1) // nolint + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] could not attach XDP program: ", err) } defer l.Close() @@ -157,122 +72,131 @@ func (c *XDPSniffer) Run() { perfEvent, err := perf.NewReader(objs.Pkts, 1<<24) if err != nil { - panic(err) + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] read event: ", err) } dnsChan := make(chan dnsutils.DNSMessage) - // goroutine to read all packets reassembled - go func() { + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + + go func(ctx context.Context) { + defer func() { + dnsProcessor.Stop() + c.LogInfo("read data terminated") + defer close(done) + }() + + var pkt xdp.BpfPktEvent + var netErr net.Error for { select { - // new config provided? - case cfg, opened := <-c.configChan: - if !opened { - return + case <-ctx.Done(): + c.LogInfo("stopping sniffer...") + perfEvent.Close() + return + default: + // The data submitted via bpf_perf_event_output. + perfEvent.SetDeadline(time.Now().Add(1 * time.Second)) + record, err := perfEvent.Read() + if err != nil { + if errors.As(err, &netErr) && netErr.Timeout() { + continue + } + c.LogError("BPF reading map: %s", err) + break + } + if record.LostSamples != 0 { + c.LogError("BPF dump: Dropped %d samples from kernel perf buffer", record.LostSamples) + continue } - c.config = cfg - c.ReadConfig() - // send the config to the dns processor - dnsProcessor.ConfigChan <- cfg + reader := bytes.NewReader(record.RawSample) + if err := binary.Read(reader, binary.LittleEndian, &pkt); err != nil { + c.LogError("BPF reading sample: %s", err) + break + } - // dns message to read ? - case dm := <-dnsChan: + // adjust arrival time + timenow := time.Now().UTC() + var ts unix.Timespec + unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts) + elapsed := time.Since(timenow) * time.Nanosecond + delta3 := time.Duration(uint64(unix.TimespecToNsec(ts))-pkt.Timestamp) * time.Nanosecond + tsAdjusted := timenow.Add(-(delta3 + elapsed)) + + // convert ip + var saddr, daddr net.IP + if pkt.IpVersion == 0x0800 { + saddr = netutils.GetIPAddress(pkt.SrcAddr, netutils.ConvertIP4) + daddr = netutils.GetIPAddress(pkt.DstAddr, netutils.ConvertIP4) + } else { + saddr = netutils.GetIPAddress(pkt.SrcAddr6, netutils.ConvertIP6) + daddr = netutils.GetIPAddress(pkt.DstAddr6, netutils.ConvertIP6) + } - // update identity with config ? - dm.DNSTap.Identity = c.identity + // prepare DnsMessage + dm := dnsutils.DNSMessage{} + dm.Init() - dnsProcessor.GetChannel() <- dm + dm.DNSTap.TimeSec = int(tsAdjusted.Unix()) + dm.DNSTap.TimeNsec = int(tsAdjusted.UnixNano() - tsAdjusted.Unix()*1e9) - } - } - }() + if pkt.SrcPort == 53 { + dm.DNSTap.Operation = dnsutils.DNSTapClientResponse + } else { + dm.DNSTap.Operation = dnsutils.DNSTapClientQuery + } - go func() { - var pkt xdp.BpfPktEvent - for { - // The data submitted via bpf_perf_event_output. - record, err := perfEvent.Read() - if err != nil { - c.LogError("BPF reading map: %s", err) - break - } + dm.NetworkInfo.QueryIP = saddr.String() + dm.NetworkInfo.QueryPort = fmt.Sprint(pkt.SrcPort) + dm.NetworkInfo.ResponseIP = daddr.String() + dm.NetworkInfo.ResponsePort = fmt.Sprint(pkt.DstPort) - if record.LostSamples != 0 { - c.LogError("BPF dump: Dropped %d samples from kernel perf buffer", record.LostSamples) - continue - } + if pkt.IpVersion == 0x0800 { + dm.NetworkInfo.Family = netutils.ProtoIPv4 + } else { + dm.NetworkInfo.Family = netutils.ProtoIPv6 + } - reader := bytes.NewReader(record.RawSample) - if err := binary.Read(reader, binary.LittleEndian, &pkt); err != nil { - c.LogError("BPF reading sample: %s", err) - break - } + if pkt.IpProto == 0x11 { + dm.NetworkInfo.Protocol = netutils.ProtoUDP + dm.DNS.Payload = record.RawSample[int(pkt.PktOffset)+int(pkt.PayloadOffset):] + dm.DNS.Length = len(dm.DNS.Payload) + } else { + dm.NetworkInfo.Protocol = netutils.ProtoTCP + dm.DNS.Payload = record.RawSample[int(pkt.PktOffset)+int(pkt.PayloadOffset)+2:] + dm.DNS.Length = len(dm.DNS.Payload) + } - // adjust arrival time - timenow := time.Now().UTC() - var ts unix.Timespec - unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts) - elapsed := time.Since(timenow) * time.Nanosecond - delta3 := time.Duration(uint64(unix.TimespecToNsec(ts))-pkt.Timestamp) * time.Nanosecond - tsAdjusted := timenow.Add(-(delta3 + elapsed)) - - // convert ip - var saddr, daddr net.IP - if pkt.IpVersion == 0x0800 { - saddr = GetIPAddress(pkt.SrcAddr, ConvertIP4) - daddr = GetIPAddress(pkt.DstAddr, ConvertIP4) - } else { - saddr = GetIPAddress(pkt.SrcAddr6, ConvertIP6) - daddr = GetIPAddress(pkt.DstAddr6, ConvertIP6) + dnsChan <- dm } + } + }(ctx) - // prepare DnsMessage - dm := dnsutils.DNSMessage{} - dm.Init() + for { + select { + case <-c.OnStop(): + c.LogInfo("stop to listen...") + cancel() + <-done + return - dm.DNSTap.TimeSec = int(tsAdjusted.Unix()) - dm.DNSTap.TimeNsec = int(tsAdjusted.UnixNano() - tsAdjusted.Unix()*1e9) + // new config provided? + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) - if pkt.SrcPort == 53 { - dm.DNSTap.Operation = dnsutils.DNSTapClientResponse - } else { - dm.DNSTap.Operation = dnsutils.DNSTapClientQuery - } + // send the config to the dns processor + dnsProcessor.ConfigChan <- cfg - dm.NetworkInfo.QueryIP = saddr.String() - dm.NetworkInfo.QueryPort = fmt.Sprint(pkt.SrcPort) - dm.NetworkInfo.ResponseIP = daddr.String() - dm.NetworkInfo.ResponsePort = fmt.Sprint(pkt.DstPort) + // dns message to read ? + case dm := <-dnsChan: - if pkt.IpVersion == 0x0800 { - dm.NetworkInfo.Family = netlib.ProtoIPv4 - } else { - dm.NetworkInfo.Family = netlib.ProtoIPv6 - } + // update identity with config ? + dm.DNSTap.Identity = c.GetConfig().GetServerIdentity() - if pkt.IpProto == 0x11 { - dm.NetworkInfo.Protocol = netlib.ProtoUDP - dm.DNS.Payload = record.RawSample[int(pkt.PktOffset)+int(pkt.PayloadOffset):] - dm.DNS.Length = len(dm.DNS.Payload) - } else { - dm.NetworkInfo.Protocol = netlib.ProtoTCP - dm.DNS.Payload = record.RawSample[int(pkt.PktOffset)+int(pkt.PayloadOffset)+2:] - dm.DNS.Length = len(dm.DNS.Payload) - } + dnsProcessor.GetChannel() <- dm - dnsChan <- dm } - }() - - <-c.exit - close(dnsChan) - close(c.configChan) - - // stop dns processor - dnsProcessor.Stop() - - c.LogInfo("run terminated") - c.done <- true + } } diff --git a/collectors/sniffer_xdp_windows.go b/collectors/sniffer_xdp_windows.go index c7df6548..ee44ef4f 100644 --- a/collectors/sniffer_xdp_windows.go +++ b/collectors/sniffer_xdp_windows.go @@ -4,80 +4,23 @@ package collectors import ( - "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-logger" ) type XDPSniffer struct { - done, exit chan bool - identity string - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string + *pkgutils.Collector } -func NewXDPSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] xdp sniffer enabled", name) - s := &XDPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } +func NewXDPSniffer(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer { + s := &XDPSniffer{Collector: pkgutils.NewCollector(config, logger, name, "xdp sniffer")} + s.SetDefaultRoutes(next) s.ReadConfig() return s } -func (c *XDPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] XDP sniffer - "+msg, v...) -} - -func (c *XDPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] XDP sniffer - "+msg, v...) -} - -func (c *XDPSniffer) GetName() string { return c.name } - -func (c *XDPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *XDPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *XDPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *XDPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *XDPSniffer) ReadConfig() {} - -func (c *XDPSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *XDPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *XDPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} func (c *XDPSniffer) Run() { - c.LogInfo("Not supported") - c.done <- true + c.LogError("running collector failed...OS not supported!") + c.StopIsDone() } diff --git a/collectors/tzsp.go b/collectors/tzsp.go index 510a94b9..76497bd7 100644 --- a/collectors/tzsp.go +++ b/collectors/tzsp.go @@ -1,265 +1,26 @@ -//go:build linux -// +build linux - -// Written by Noel Kuntze +//go:build windows || freebsd || darwin +// +build windows freebsd darwin package collectors import ( - "encoding/binary" - "fmt" - "net" - "syscall" - - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-dnscollector/processors" "github.com/dmachard/go-logger" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/rs/tzsp" ) type TZSPSniffer struct { - done, exit chan bool - listen net.UDPConn - defaultRoutes, droppedRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string - identity string + *pkgutils.Collector } -func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] tzsp - enabled", name) - s := &TZSPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } +func NewTZSP(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { + s := &TZSPSniffer{Collector: pkgutils.NewCollector(config, logger, name, "tzsp")} + s.SetDefaultRoutes(next) s.ReadConfig() return s } -func (c *TZSPSniffer) GetName() string { return c.name } - -func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - c.droppedRoutes = append(c.droppedRoutes, wrk) -} - -func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp "+msg, v...) -} - -func (c *TZSPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) ReadConfig() { - c.identity = c.config.GetServerIdentity() -} - -func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) { - // TODO implement reload configuration -} - -func (c *TZSPSniffer) Listen() error { - c.logger.Info("running in background...") - - ServerAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", c.config.Collectors.Tzsp.ListenIP, c.config.Collectors.Tzsp.ListenPort)) - if err != nil { - return err - } - - ServerConn, err := net.ListenUDP("udp", ServerAddr) - if err != nil { - return err - } - file, err := ServerConn.File() - - if err != nil { - return err - } - - err = syscall.SetsockoptInt(int(file.Fd()), syscall.SOL_SOCKET, syscall.SO_TIMESTAMPNS, 1) - - if err != nil { - return err - } - c.LogInfo("is listening on %s", ServerConn.LocalAddr()) - c.listen = *ServerConn - return nil -} - -func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *TZSPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // Finally close the listener to unblock accept - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - func (c *TZSPSniffer) Run() { - c.logger.Info("starting collector...") - - if err := c.Listen(); err != nil { - c.logger.Fatal("collector=tzsp listening failed: ", err) - } - - dnsProcessor := processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.Tzsp.ChannelBufferSize) - go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes) - - go func() { - buf := make([]byte, 65536) - oob := make([]byte, 100) - for { - // flags, from - bufN, oobn, _, _, err := c.listen.ReadMsgUDPAddrPort(buf, oob) - if err != nil { - panic(err) - } - if bufN == 0 { - panic("buf empty") - } - if bufN > len(buf) { - panic("buf overflow") - } - if oobn == 0 { - panic("oob missing") - } - c.LogInfo("Packet received") - scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) - if err != nil { - panic(err) - } - if len(scms) != 1 { - c.LogInfo("len(scms) != 1") - continue - } - scm := scms[0] - if scm.Header.Type != syscall.SCM_TIMESTAMPNS { - panic("scm timestampns missing") - } - tsec := binary.LittleEndian.Uint32(scm.Data[:4]) - nsec := binary.LittleEndian.Uint32(scm.Data[8:12]) - - // copy packet data from buffer - pkt := make([]byte, bufN) - copy(pkt, buf[:bufN]) - - tzspPacket, err := tzsp.Parse(pkt) - - if err != nil { - c.LogError("Failed to parse packet: ", err) - continue - } - - var eth layers.Ethernet - var ip4 layers.IPv4 - var ip6 layers.IPv6 - var tcp layers.TCP - var udp layers.UDP - parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &ip6, &tcp, &udp) - decodedLayers := make([]gopacket.LayerType, 0, 4) - - // decode-it - parser.DecodeLayers(tzspPacket.Data, &decodedLayers) - - dm := dnsutils.DNSMessage{} - dm.Init() - - ignorePacket := false - for _, layertyp := range decodedLayers { - switch layertyp { - case layers.LayerTypeIPv4: - dm.NetworkInfo.Family = netlib.ProtoIPv4 - dm.NetworkInfo.QueryIP = ip4.SrcIP.String() - dm.NetworkInfo.ResponseIP = ip4.DstIP.String() - - case layers.LayerTypeIPv6: - dm.NetworkInfo.QueryIP = ip6.SrcIP.String() - dm.NetworkInfo.ResponseIP = ip6.DstIP.String() - dm.NetworkInfo.Family = netlib.ProtoIPv6 - - case layers.LayerTypeUDP: - dm.NetworkInfo.QueryPort = fmt.Sprint(int(udp.SrcPort)) - dm.NetworkInfo.ResponsePort = fmt.Sprint(int(udp.DstPort)) - dm.DNS.Payload = udp.Payload - dm.DNS.Length = len(udp.Payload) - dm.NetworkInfo.Protocol = netlib.ProtoUDP - - case layers.LayerTypeTCP: - // ignore SYN/ACK packet - // Note: disabled because SYN/SYN+Ack might contain data if TCP Fast open is used - // if !tcp.PSH { - // ignore_packet = true - // continue - // } - if len(tcp.Payload) < 12 { - // packet way too short; 12 byte is the minimum size a DNS packet (header only, - // no questions, answers, authorities, or additional RRs) - continue - } - dnsLengthField := binary.BigEndian.Uint16(tcp.Payload[0:2]) - if len(tcp.Payload) < int(dnsLengthField) { - ignorePacket = true - continue - } - - dm.NetworkInfo.QueryPort = fmt.Sprint(int(tcp.SrcPort)) - dm.NetworkInfo.ResponsePort = fmt.Sprint(int(tcp.DstPort)) - dm.DNS.Payload = tcp.Payload[2:] - dm.DNS.Length = len(tcp.Payload[2:]) - dm.NetworkInfo.Protocol = netlib.ProtoTCP - } - } - - if !ignorePacket { - dm.DNSTap.Identity = c.identity - - // set timestamp - dm.DNSTap.TimeSec = int(tsec) - dm.DNSTap.TimeNsec = int(nsec) - - // just decode QR - if len(dm.DNS.Payload) < 4 { - continue - } - - dnsProcessor.GetChannel() <- dm - } - } - }() - - <-c.exit - - // stop dns processor - dnsProcessor.Stop() - - c.LogInfo("run terminated") - c.done <- true + c.LogError("running collector failed...OS not supported!") + c.StopIsDone() } diff --git a/collectors/tzsp_darwin.go b/collectors/tzsp_darwin.go deleted file mode 100644 index 8de47f49..00000000 --- a/collectors/tzsp_darwin.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build darwin -// +build darwin - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type TZSPSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for macos, not yet supported -func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] tzsp - enabled", name) - s := &TZSPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *TZSPSniffer) GetName() string { return c.name } - -func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *TZSPSniffer) ReadConfig() {} - -func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *TZSPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *TZSPSniffer) Run() { - c.LogInfo("run terminated") - c.done <- true -} diff --git a/collectors/tzsp_freebsd.go b/collectors/tzsp_freebsd.go deleted file mode 100644 index d97f8818..00000000 --- a/collectors/tzsp_freebsd.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build freebsd -// +build freebsd - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type TZSPSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for macos, not yet supported -func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] tzsp - enabled", name) - s := &TZSPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *TZSPSniffer) GetName() string { return c.name } - -func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *TZSPSniffer) ReadConfig() {} - -func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *TZSPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *TZSPSniffer) Run() { - c.LogInfo("run terminated") - c.done <- true -} diff --git a/collectors/tzsp_linux.go b/collectors/tzsp_linux.go new file mode 100644 index 00000000..0d386415 --- /dev/null +++ b/collectors/tzsp_linux.go @@ -0,0 +1,243 @@ +//go:build linux +// +build linux + +// Written by Noel Kuntze +// Updating by Denis Machard + +package collectors + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "net" + "syscall" + "time" + + "github.com/dmachard/go-dnscollector/dnsutils" + "github.com/dmachard/go-dnscollector/netutils" + "github.com/dmachard/go-dnscollector/pkgconfig" + "github.com/dmachard/go-dnscollector/pkgutils" + "github.com/dmachard/go-dnscollector/processors" + "github.com/dmachard/go-logger" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/rs/tzsp" +) + +type TZSPSniffer struct { + *pkgutils.Collector + listen net.UDPConn +} + +func NewTZSP(next []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { + s := &TZSPSniffer{Collector: pkgutils.NewCollector(config, logger, name, "tzsp")} + s.SetDefaultRoutes(next) + return s +} + +func (c *TZSPSniffer) Listen() error { + c.LogInfo("starting UDP server...") + + ServerAddr, err := net.ResolveUDPAddr("udp", + fmt.Sprintf("%s:%d", c.GetConfig().Collectors.Tzsp.ListenIP, c.GetConfig().Collectors.Tzsp.ListenPort), + ) + + if err != nil { + return err + } + + ServerConn, err := net.ListenUDP("udp", ServerAddr) + if err != nil { + return err + } + file, err := ServerConn.File() + + if err != nil { + return err + } + + err = syscall.SetsockoptInt(int(file.Fd()), syscall.SOL_SOCKET, syscall.SO_TIMESTAMPNS, 1) + if err != nil { + return err + } + + // calling File.Fd() disables the SetDeadline methods + err = syscall.SetNonblock(int(file.Fd()), true) + if err != nil { + return err + } + + c.LogInfo("is listening on %s", ServerConn.LocalAddr()) + c.listen = *ServerConn + return nil +} + +func (c *TZSPSniffer) Run() { + c.LogInfo("running collector...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() + + // start server + if err := c.Listen(); err != nil { + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] listening failed: ", err) + } + + // init dns processor + dnsProcessor := processors.NewDNSProcessor(c.GetConfig(), c.GetLogger(), c.GetName(), c.GetConfig().Collectors.Tzsp.ChannelBufferSize) + go dnsProcessor.Run(c.GetDefaultRoutes(), c.GetDroppedRoutes()) + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + + go func(ctx context.Context) { + defer func() { + dnsProcessor.Stop() + c.LogInfo("read data terminated") + defer close(done) + }() + + buf := make([]byte, 1024) + oob := make([]byte, 1024) + + var netErr net.Error + for { + select { + case <-ctx.Done(): + c.LogInfo("stopping UDP server...") + c.listen.Close() + return + default: + c.listen.SetReadDeadline(time.Now().Add(1 * time.Second)) + bufN, oobn, _, _, err := c.listen.ReadMsgUDPAddrPort(buf, oob) + if err != nil { + if errors.As(err, &netErr) && netErr.Timeout() { + continue + } + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] read msg", err) + } + if bufN == 0 { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] read msg, buffer is empty") + } + if bufN > len(buf) { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] read msg, bufer overflow") + } + if oobn == 0 { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] read msg, oob missing") + } + scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + c.LogFatal(pkgutils.PrefixLogCollector+"["+c.GetName()+"] parse control msg", err) + } + if len(scms) != 1 { + c.LogInfo("len(scms) != 1") + continue + } + scm := scms[0] + if scm.Header.Type != syscall.SCM_TIMESTAMPNS { + c.LogFatal(pkgutils.PrefixLogCollector + "[" + c.GetName() + "] scm timestampns missing") + } + tsec := binary.LittleEndian.Uint32(scm.Data[:4]) + nsec := binary.LittleEndian.Uint32(scm.Data[8:12]) + + // copy packet data from buffer + pkt := make([]byte, bufN) + copy(pkt, buf[:bufN]) + + tzspPacket, err := tzsp.Parse(pkt) + + if err != nil { + c.LogError("Failed to parse packet: ", err) + continue + } + + var eth layers.Ethernet + var ip4 layers.IPv4 + var ip6 layers.IPv6 + var tcp layers.TCP + var udp layers.UDP + parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &ip6, &tcp, &udp) + decodedLayers := make([]gopacket.LayerType, 0, 4) + + // decode-it + parser.DecodeLayers(tzspPacket.Data, &decodedLayers) + + dm := dnsutils.DNSMessage{} + dm.Init() + + ignorePacket := false + for _, layertyp := range decodedLayers { + switch layertyp { + case layers.LayerTypeIPv4: + dm.NetworkInfo.Family = netutils.ProtoIPv4 + dm.NetworkInfo.QueryIP = ip4.SrcIP.String() + dm.NetworkInfo.ResponseIP = ip4.DstIP.String() + + case layers.LayerTypeIPv6: + dm.NetworkInfo.QueryIP = ip6.SrcIP.String() + dm.NetworkInfo.ResponseIP = ip6.DstIP.String() + dm.NetworkInfo.Family = netutils.ProtoIPv6 + + case layers.LayerTypeUDP: + dm.NetworkInfo.QueryPort = fmt.Sprint(int(udp.SrcPort)) + dm.NetworkInfo.ResponsePort = fmt.Sprint(int(udp.DstPort)) + dm.DNS.Payload = udp.Payload + dm.DNS.Length = len(udp.Payload) + dm.NetworkInfo.Protocol = netutils.ProtoUDP + + case layers.LayerTypeTCP: + if len(tcp.Payload) < 12 { + // packet way too short; 12 byte is the minimum size a DNS packet (header only, + // no questions, answers, authorities, or additional RRs) + continue + } + dnsLengthField := binary.BigEndian.Uint16(tcp.Payload[0:2]) + if len(tcp.Payload) < int(dnsLengthField) { + ignorePacket = true + continue + } + + dm.NetworkInfo.QueryPort = fmt.Sprint(int(tcp.SrcPort)) + dm.NetworkInfo.ResponsePort = fmt.Sprint(int(tcp.DstPort)) + dm.DNS.Payload = tcp.Payload[2:] + dm.DNS.Length = len(tcp.Payload[2:]) + dm.NetworkInfo.Protocol = netutils.ProtoTCP + } + } + + if !ignorePacket { + dm.DNSTap.Identity = c.GetConfig().GetServerIdentity() + + // set timestamp + dm.DNSTap.TimeSec = int(tsec) + dm.DNSTap.TimeNsec = int(nsec) + + // just decode QR + if len(dm.DNS.Payload) < 4 { + continue + } + + dnsProcessor.GetChannel() <- dm + } + } + } + }(ctx) + + // main loop + for { + select { + case <-c.OnStop(): + c.LogInfo("stopping read goroutine") + cancel() + <-done + return + + // save the new config + case cfg := <-c.NewConfig(): + c.SetConfig(cfg) + } + } +} diff --git a/collectors/tzsp_windows.go b/collectors/tzsp_windows.go deleted file mode 100644 index 8ce22d55..00000000 --- a/collectors/tzsp_windows.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build windows -// +build windows - -package collectors - -import ( - "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/pkgconfig" - "github.com/dmachard/go-dnscollector/pkgutils" - "github.com/dmachard/go-logger" -) - -type TZSPSniffer struct { - done, exit chan bool - defaultRoutes []pkgutils.Worker - config *pkgconfig.Config - logger *logger.Logger - name string -} - -// workaround for macos, not yet supported -func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer { - logger.Info(pkgutils.PrefixLogCollector+"[%s] tzsp - enabled", name) - s := &TZSPSniffer{ - done: make(chan bool), - exit: make(chan bool), - config: config, - defaultRoutes: loggers, - logger: logger, - name: name, - } - s.ReadConfig() - return s -} - -func (c *TZSPSniffer) GetName() string { return c.name } - -func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) { - // TODO -} - -func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) { - c.defaultRoutes = append(c.defaultRoutes, wrk) -} - -func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) { - c.defaultRoutes = loggers -} - -func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) { - c.logger.Info(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) LogError(msg string, v ...interface{}) { - c.logger.Error(pkgutils.PrefixLogCollector+"["+c.name+"] tzsp - "+msg, v...) -} - -func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) { - return pkgutils.GetRoutes(c.defaultRoutes) -} - -func (c *TZSPSniffer) ReadConfig() {} - -func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {} - -func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage { - return nil -} - -func (c *TZSPSniffer) Stop() { - c.LogInfo("stopping collector...") - - // exit to close properly - c.exit <- true - - // read done channel and block until run is terminated - <-c.done - close(c.done) -} - -func (c *TZSPSniffer) Run() { - c.LogInfo("run terminated") - c.done <- true -} diff --git a/config.yml b/config.yml index 992ad7eb..03d47966 100644 --- a/config.yml +++ b/config.yml @@ -26,12 +26,10 @@ multiplexer: transforms: normalize: qname-lowercase: true - loggers: - name: console stdout: mode: text - routes: - from: [ tap ] to: [ console ] diff --git a/dnsutils/message.go b/dnsutils/message.go index 1634116b..3917d2bf 100644 --- a/dnsutils/message.go +++ b/dnsutils/message.go @@ -16,7 +16,7 @@ import ( "strings" "time" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnstap-protobuf" "github.com/google/gopacket" "github.com/google/gopacket/layers" @@ -885,7 +885,7 @@ func (dm *DNSMessage) ToDNSTap(extended bool) ([]byte, error) { mt := dnstap.Message_Type(dnstap.Message_Type_value[dm.DNSTap.Operation]) var sf dnstap.SocketFamily - if ipNet, valid := netlib.IPToInet[dm.NetworkInfo.Family]; valid { + if ipNet, valid := netutils.IPToInet[dm.NetworkInfo.Family]; valid { sf = dnstap.SocketFamily(dnstap.SocketFamily_value[ipNet]) } sp := dnstap.SocketProtocol(dnstap.SocketProtocol_value[dm.NetworkInfo.Protocol]) @@ -920,7 +920,7 @@ func (dm *DNSMessage) ToDNSTap(extended bool) ([]byte, error) { msg.SocketProtocol = &sp reqIP := net.ParseIP(dm.NetworkInfo.QueryIP) - if dm.NetworkInfo.Family == netlib.ProtoIPv4 { + if dm.NetworkInfo.Family == netutils.ProtoIPv4 { msg.QueryAddress = reqIP.To4() } else { msg.QueryAddress = reqIP.To16() @@ -928,7 +928,7 @@ func (dm *DNSMessage) ToDNSTap(extended bool) ([]byte, error) { msg.QueryPort = &qport rspIP := net.ParseIP(dm.NetworkInfo.ResponseIP) - if dm.NetworkInfo.Family == netlib.ProtoIPv4 { + if dm.NetworkInfo.Family == netutils.ProtoIPv4 { msg.ResponseAddress = rspIP.To4() } else { msg.ResponseAddress = rspIP.To16() @@ -1036,11 +1036,11 @@ func (dm *DNSMessage) ToPacketLayer() ([]gopacket.SerializableLayer, error) { // set source and destination IP switch dm.NetworkInfo.Family { - case netlib.ProtoIPv4: + case netutils.ProtoIPv4: eth.EthernetType = layers.EthernetTypeIPv4 ip4.SrcIP = net.ParseIP(srcIP) ip4.DstIP = net.ParseIP(dstIP) - case netlib.ProtoIPv6: + case netutils.ProtoIPv6: eth.EthernetType = layers.EthernetTypeIPv6 ip6.SrcIP = net.ParseIP(srcIP) ip6.DstIP = net.ParseIP(dstIP) @@ -1052,24 +1052,24 @@ func (dm *DNSMessage) ToPacketLayer() ([]gopacket.SerializableLayer, error) { switch dm.NetworkInfo.Protocol { // DNS over UDP - case netlib.ProtoUDP: + case netutils.ProtoUDP: udp.SrcPort = layers.UDPPort(srcPort) udp.DstPort = layers.UDPPort(dstPort) // update iplayer switch dm.NetworkInfo.Family { - case netlib.ProtoIPv4: + case netutils.ProtoIPv4: ip4.Protocol = layers.IPProtocolUDP udp.SetNetworkLayerForChecksum(ip4) pkt = append(pkt, gopacket.Payload(dm.DNS.Payload), udp, ip4) - case netlib.ProtoIPv6: + case netutils.ProtoIPv6: ip6.NextHeader = layers.IPProtocolUDP udp.SetNetworkLayerForChecksum(ip6) pkt = append(pkt, gopacket.Payload(dm.DNS.Payload), udp, ip6) } // DNS over TCP - case netlib.ProtoTCP: + case netutils.ProtoTCP: tcp.SrcPort = layers.TCPPort(srcPort) tcp.DstPort = layers.TCPPort(dstPort) tcp.PSH = true @@ -1081,11 +1081,11 @@ func (dm *DNSMessage) ToPacketLayer() ([]gopacket.SerializableLayer, error) { // update iplayer switch dm.NetworkInfo.Family { - case netlib.ProtoIPv4: + case netutils.ProtoIPv4: ip4.Protocol = layers.IPProtocolTCP tcp.SetNetworkLayerForChecksum(ip4) pkt = append(pkt, gopacket.Payload(append(dnsLengthField, dm.DNS.Payload...)), tcp, ip4) - case netlib.ProtoIPv6: + case netutils.ProtoIPv6: ip6.NextHeader = layers.IPProtocolTCP tcp.SetNetworkLayerForChecksum(ip6) pkt = append(pkt, gopacket.Payload(append(dnsLengthField, dm.DNS.Payload...)), tcp, ip6) @@ -1099,11 +1099,11 @@ func (dm *DNSMessage) ToPacketLayer() ([]gopacket.SerializableLayer, error) { // update iplayer switch dm.NetworkInfo.Family { - case netlib.ProtoIPv4: + case netutils.ProtoIPv4: ip4.Protocol = layers.IPProtocolUDP udp.SetNetworkLayerForChecksum(ip4) pkt = append(pkt, gopacket.Payload(dm.DNS.Payload), udp, ip4) - case netlib.ProtoIPv6: + case netutils.ProtoIPv6: ip6.NextHeader = layers.IPProtocolUDP udp.SetNetworkLayerForChecksum(ip6) pkt = append(pkt, gopacket.Payload(dm.DNS.Payload), udp, ip6) @@ -1841,8 +1841,8 @@ func GetFakeDNSMessageWithPayload() DNSMessage { dnsquestion, _ := dnsmsg.Pack() dm := GetFakeDNSMessage() - dm.NetworkInfo.Family = netlib.ProtoIPv4 - dm.NetworkInfo.Protocol = netlib.ProtoUDP + dm.NetworkInfo.Family = netutils.ProtoIPv4 + dm.NetworkInfo.Protocol = netutils.ProtoUDP dm.DNS.Payload = dnsquestion dm.DNS.Length = len(dnsquestion) return dm diff --git a/dnsutils/message_test.go b/dnsutils/message_test.go index 21abff7a..d580cb9d 100644 --- a/dnsutils/message_test.go +++ b/dnsutils/message_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnstap-protobuf" "github.com/miekg/dns" @@ -1454,8 +1454,8 @@ func BenchmarkDnsMessage_ToPacketLayer(b *testing.B) { dnsmsg.SetQuestion("dnscollector.dev.", dns.TypeAAAA) dnsquestion, _ := dnsmsg.Pack() - dm.NetworkInfo.Family = netlib.ProtoIPv4 - dm.NetworkInfo.Protocol = netlib.ProtoUDP + dm.NetworkInfo.Family = netutils.ProtoIPv4 + dm.NetworkInfo.Protocol = netutils.ProtoUDP dm.DNS.Payload = dnsquestion dm.DNS.Length = len(dnsquestion) diff --git a/loggers/dnstapclient.go b/loggers/dnstapclient.go index a26754a0..443d469d 100644 --- a/loggers/dnstapclient.go +++ b/loggers/dnstapclient.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -72,10 +72,10 @@ func (ds *DnstapSender) ReadConfig() { // begin backward compatibility if ds.config.Loggers.DNSTap.TLSSupport { - ds.transport = netlib.SocketTLS + ds.transport = netutils.SocketTLS } if len(ds.config.Loggers.DNSTap.SockPath) > 0 { - ds.transport = netlib.SocketUnix + ds.transport = netutils.SocketUnix } // end @@ -150,7 +150,7 @@ func (ds *DnstapSender) ConnectToRemote() { var err error switch ds.transport { - case netlib.SocketUnix: + case netutils.SocketUnix: address = ds.config.Loggers.DNSTap.RemoteAddress if len(ds.config.Loggers.DNSTap.SockPath) > 0 { address = ds.config.Loggers.DNSTap.SockPath @@ -158,11 +158,11 @@ func (ds *DnstapSender) ConnectToRemote() { ds.LogInfo("connecting to %s://%s", ds.transport, address) conn, err = net.DialTimeout(ds.transport, address, connTimeout) - case netlib.SocketTCP: + case netutils.SocketTCP: ds.LogInfo("connecting to %s://%s", ds.transport, address) conn, err = net.DialTimeout(ds.transport, address, connTimeout) - case netlib.SocketTLS: + case netutils.SocketTLS: ds.LogInfo("connecting to %s://%s", ds.transport, address) var tlsConfig *tls.Config @@ -178,7 +178,7 @@ func (ds *DnstapSender) ConnectToRemote() { tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions) if err == nil { dialer := &net.Dialer{Timeout: connTimeout} - conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig) + conn, err = tls.DialWithDialer(dialer, netutils.SocketTCP, address, tlsConfig) } default: ds.logger.Fatal("logger=dnstap - invalid transport:", ds.transport) diff --git a/loggers/fluentd.go b/loggers/fluentd.go index 582f800c..9e0750ab 100644 --- a/loggers/fluentd.go +++ b/loggers/fluentd.go @@ -8,7 +8,7 @@ import ( "github.com/IBM/fluent-forward-go/fluent/client" "github.com/IBM/fluent-forward-go/fluent/protocol" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -69,10 +69,10 @@ func (fc *FluentdClient) ReadConfig() { // begin backward compatibility if fc.config.Loggers.Fluentd.TLSSupport { - fc.transport = netlib.SocketTLS + fc.transport = netutils.SocketTLS } if len(fc.config.Loggers.Fluentd.SockPath) > 0 { - fc.transport = netlib.SocketUnix + fc.transport = netutils.SocketUnix } } @@ -128,7 +128,7 @@ func (fc *FluentdClient) ConnectToRemote() { var err error switch fc.transport { - case netlib.SocketUnix: + case netutils.SocketUnix: address = fc.config.Loggers.Fluentd.RemoteAddress if len(fc.config.Loggers.Fluentd.SockPath) > 0 { address = fc.config.Loggers.Fluentd.SockPath @@ -142,7 +142,7 @@ func (fc *FluentdClient) ConnectToRemote() { ConnectionTimeout: connTimeout, }) - case netlib.SocketTCP: + case netutils.SocketTCP: fc.LogInfo("connecting to %s://%s", fc.transport, address) c = client.New(client.ConnectionOptions{ Factory: &client.ConnFactory{ @@ -152,7 +152,7 @@ func (fc *FluentdClient) ConnectToRemote() { ConnectionTimeout: connTimeout, }) - case netlib.SocketTLS: + case netutils.SocketTLS: fc.LogInfo("connecting to %s://%s", fc.transport, address) var tlsConfig *tls.Config diff --git a/loggers/fluentd_test.go b/loggers/fluentd_test.go index 77d6e0fb..21a5fb50 100644 --- a/loggers/fluentd_test.go +++ b/loggers/fluentd_test.go @@ -8,7 +8,7 @@ import ( "github.com/IBM/fluent-forward-go/fluent/protocol" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" "github.com/tinylib/msgp/msgp" @@ -24,7 +24,7 @@ func Test_FluentdClient(t *testing.T) { }{ { name: "with_buffer", - transport: netlib.SocketTCP, + transport: netutils.SocketTCP, address: ":24224", bufferSize: 100, flushInterval: 1, diff --git a/loggers/influxdb_test.go b/loggers/influxdb_test.go index 78233cbf..c3eb1494 100644 --- a/loggers/influxdb_test.go +++ b/loggers/influxdb_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" ) @@ -18,7 +18,7 @@ func Test_InfluxDB(t *testing.T) { g := NewInfluxDBClient(pkgconfig.GetFakeConfig(), logger.New(false), "test") // fake msgpack receiver - fakeRcvr, err := net.Listen(netlib.SocketTCP, "127.0.0.1:8086") + fakeRcvr, err := net.Listen(netutils.SocketTCP, "127.0.0.1:8086") if err != nil { t.Fatal(err) } diff --git a/loggers/prometheus.go b/loggers/prometheus.go index 83d45f78..55f65a14 100644 --- a/loggers/prometheus.go +++ b/loggers/prometheus.go @@ -14,7 +14,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -1195,11 +1195,11 @@ func (c *Prometheus) ListenAndServe() { tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert } - listener, err = tls.Listen(netlib.SocketTCP, addrlisten, tlsConfig) + listener, err = tls.Listen(netutils.SocketTCP, addrlisten, tlsConfig) } else { // basic listening - listener, err = net.Listen(netlib.SocketTCP, addrlisten) + listener, err = net.Listen(netutils.SocketTCP, addrlisten) } // something wrong ? diff --git a/loggers/redispub.go b/loggers/redispub.go index 80c802fb..dd475cb8 100644 --- a/loggers/redispub.go +++ b/loggers/redispub.go @@ -13,7 +13,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -81,10 +81,10 @@ func (c *RedisPub) ReadConfig() { // begin backward compatibility if c.config.Loggers.RedisPub.TLSSupport { - c.transport = netlib.SocketTLS + c.transport = netutils.SocketTLS } if len(c.config.Loggers.RedisPub.SockPath) > 0 { - c.transport = netlib.SocketUnix + c.transport = netutils.SocketUnix } // end @@ -174,7 +174,7 @@ func (c *RedisPub) ConnectToRemote() { var err error switch c.transport { - case netlib.SocketUnix: + case netutils.SocketUnix: address = c.config.Loggers.RedisPub.RemoteAddress if len(c.config.Loggers.RedisPub.SockPath) > 0 { address = c.config.Loggers.RedisPub.SockPath @@ -182,11 +182,11 @@ func (c *RedisPub) ConnectToRemote() { c.LogInfo("connecting to %s://%s", c.transport, address) conn, err = net.DialTimeout(c.transport, address, connTimeout) - case netlib.SocketTCP: + case netutils.SocketTCP: c.LogInfo("connecting to %s://%s", c.transport, address) conn, err = net.DialTimeout(c.transport, address, connTimeout) - case netlib.SocketTLS: + case netutils.SocketTLS: c.LogInfo("connecting to %s://%s", c.transport, address) var tlsConfig *tls.Config @@ -202,7 +202,7 @@ func (c *RedisPub) ConnectToRemote() { tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions) if err == nil { dialer := &net.Dialer{Timeout: connTimeout} - conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig) + conn, err = tls.DialWithDialer(dialer, netutils.SocketTCP, address, tlsConfig) } default: diff --git a/loggers/redispub_test.go b/loggers/redispub_test.go index f832a524..34b6284a 100644 --- a/loggers/redispub_test.go +++ b/loggers/redispub_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" ) @@ -43,7 +43,7 @@ func Test_RedisPubRun(t *testing.T) { g := NewRedisPub(cfg, logger.New(false), "test") // fake json receiver - fakeRcvr, err := net.Listen(netlib.SocketTCP, ":6379") + fakeRcvr, err := net.Listen(netutils.SocketTCP, ":6379") if err != nil { t.Fatal(err) } diff --git a/loggers/restapi.go b/loggers/restapi.go index 56263398..1b4eb922 100644 --- a/loggers/restapi.go +++ b/loggers/restapi.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -685,11 +685,11 @@ func (c *RestAPI) ListenAndServe() { // update tls min version according to the user config tlsConfig.MinVersion = pkgconfig.TLSVersion[c.config.Loggers.RestAPI.TLSMinVersion] - listener, err = tls.Listen(netlib.SocketTCP, addrlisten, tlsConfig) + listener, err = tls.Listen(netutils.SocketTCP, addrlisten, tlsConfig) } else { // basic listening - listener, err = net.Listen(netlib.SocketTCP, addrlisten) + listener, err = net.Listen(netutils.SocketTCP, addrlisten) } // something wrong ? diff --git a/loggers/statsd.go b/loggers/statsd.go index c539db67..780d2901 100644 --- a/loggers/statsd.go +++ b/loggers/statsd.go @@ -10,7 +10,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -331,11 +331,11 @@ PROCESS_LOOP: var err error switch c.config.Loggers.Statsd.Transport { - case netlib.SocketTCP, netlib.SocketUDP: + case netutils.SocketTCP, netutils.SocketUDP: c.LogInfo("connecting to %s://%s", c.config.Loggers.Statsd.Transport, address) conn, err = net.DialTimeout(c.config.Loggers.Statsd.Transport, address, connTimeout) - case netlib.SocketTLS: + case netutils.SocketTLS: c.LogInfo("connecting to %s://%s", c.config.Loggers.Statsd.Transport, address) var tlsConfig *tls.Config @@ -351,7 +351,7 @@ PROCESS_LOOP: tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions) if err == nil { dialer := &net.Dialer{Timeout: connTimeout} - conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig) + conn, err = tls.DialWithDialer(dialer, netutils.SocketTCP, address, tlsConfig) } default: c.logger.Fatal("logger=statsd - invalid transport:", c.config.Loggers.Statsd.Transport) diff --git a/loggers/statsd_test.go b/loggers/statsd_test.go index 91ae02d0..1bb226e3 100644 --- a/loggers/statsd_test.go +++ b/loggers/statsd_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" ) @@ -18,7 +18,7 @@ func TestStatsdRun(t *testing.T) { g := NewStatsdClient(config, logger.New(false), "test") // fake msgpack receiver - fakeRcvr, err := net.ListenPacket(netlib.SocketUDP, "127.0.0.1:8125") + fakeRcvr, err := net.ListenPacket(netutils.SocketUDP, "127.0.0.1:8125") if err != nil { t.Fatal(err) } diff --git a/loggers/syslog.go b/loggers/syslog.go index 4d5871e2..84ea9967 100644 --- a/loggers/syslog.go +++ b/loggers/syslog.go @@ -12,7 +12,7 @@ import ( syslog "github.com/dmachard/go-clientsyslog" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -176,21 +176,21 @@ func (s *Syslog) ConnectToRemote() { case "local": s.LogInfo("connecting to local syslog...") logWriter, err = syslog.New(s.facility|s.severity, "") - case netlib.SocketUnix: + case netutils.SocketUnix: s.LogInfo("connecting to %s://%s ...", s.config.Loggers.Syslog.Transport, s.config.Loggers.Syslog.RemoteAddress) logWriter, err = syslog.Dial("", s.config.Loggers.Syslog.RemoteAddress, s.facility|s.severity, s.config.Loggers.Syslog.Tag) - case netlib.SocketUDP, netlib.SocketTCP: + case netutils.SocketUDP, netutils.SocketTCP: s.LogInfo("connecting to %s://%s ...", s.config.Loggers.Syslog.Transport, s.config.Loggers.Syslog.RemoteAddress) logWriter, err = syslog.Dial(s.config.Loggers.Syslog.Transport, s.config.Loggers.Syslog.RemoteAddress, s.facility|s.severity, s.config.Loggers.Syslog.Tag) - case netlib.SocketTLS: + case netutils.SocketTLS: s.LogInfo("connecting to %s://%s ...", s.config.Loggers.Syslog.Transport, s.config.Loggers.Syslog.RemoteAddress) diff --git a/loggers/syslog_test.go b/loggers/syslog_test.go index 9e4a2901..515b2bfd 100644 --- a/loggers/syslog_test.go +++ b/loggers/syslog_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" ) @@ -25,16 +25,16 @@ func Test_SyslogRunUdp(t *testing.T) { }{ { name: "unix_format", - transport: netlib.SocketUDP, + transport: netutils.SocketUDP, mode: pkgconfig.ModeText, - formatter: netlib.SocketUnix, + formatter: netutils.SocketUnix, framer: "", pattern: `<30>\D+ \d+ \d+:\d+:\d+.*`, listenAddr: ":4000", }, { name: "rfc3164_format", - transport: netlib.SocketUDP, + transport: netutils.SocketUDP, mode: pkgconfig.ModeText, formatter: "rfc3164", framer: "", @@ -43,7 +43,7 @@ func Test_SyslogRunUdp(t *testing.T) { }, { name: "rfc5424_format", - transport: netlib.SocketUDP, + transport: netutils.SocketUDP, mode: pkgconfig.ModeText, formatter: "rfc5424", framer: "", @@ -52,7 +52,7 @@ func Test_SyslogRunUdp(t *testing.T) { }, { name: "rfc5424_format_rfc5425_framer", - transport: netlib.SocketUDP, + transport: netutils.SocketUDP, mode: pkgconfig.ModeText, formatter: "rfc5424", framer: "rfc5425", @@ -121,16 +121,16 @@ func Test_SyslogRunTcp(t *testing.T) { }{ { name: "unix_format", - transport: netlib.SocketTCP, + transport: netutils.SocketTCP, mode: pkgconfig.ModeText, - formatter: netlib.SocketUnix, + formatter: netutils.SocketUnix, framer: "", pattern: `<30>\D+ \d+ \d+:\d+:\d+.*`, listenAddr: ":4000", }, { name: "rfc3164_format", - transport: netlib.SocketTCP, + transport: netutils.SocketTCP, mode: pkgconfig.ModeText, formatter: "rfc3164", framer: "", @@ -139,7 +139,7 @@ func Test_SyslogRunTcp(t *testing.T) { }, { name: "rfc5424_format", - transport: netlib.SocketTCP, + transport: netutils.SocketTCP, mode: pkgconfig.ModeText, formatter: "rfc5424", framer: "", @@ -148,7 +148,7 @@ func Test_SyslogRunTcp(t *testing.T) { }, { name: "rfc5425_format_rfc5425_framer", - transport: netlib.SocketTCP, + transport: netutils.SocketTCP, mode: pkgconfig.ModeText, formatter: "rfc5424", framer: "rfc5425", @@ -211,10 +211,10 @@ func Test_SyslogRunTcp(t *testing.T) { func Test_SyslogRun_RemoveNullCharacter(t *testing.T) { // init logger config := pkgconfig.GetFakeConfig() - config.Loggers.Syslog.Transport = netlib.SocketUDP + config.Loggers.Syslog.Transport = netutils.SocketUDP config.Loggers.Syslog.RemoteAddress = ":4000" config.Loggers.Syslog.Mode = pkgconfig.ModeText - config.Loggers.Syslog.Formatter = netlib.SocketUnix + config.Loggers.Syslog.Formatter = netutils.SocketUnix config.Loggers.Syslog.Framer = "" config.Loggers.Syslog.FlushInterval = 1 config.Loggers.Syslog.BufferSize = 0 diff --git a/loggers/tcpclient.go b/loggers/tcpclient.go index 3ab179b9..9db03eea 100644 --- a/loggers/tcpclient.go +++ b/loggers/tcpclient.go @@ -12,7 +12,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -79,10 +79,10 @@ func (c *TCPClient) ReadConfig() { // begin backward compatibility if c.config.Loggers.TCPClient.TLSSupport { - c.transport = netlib.SocketTLS + c.transport = netutils.SocketTLS } if len(c.config.Loggers.TCPClient.SockPath) > 0 { - c.transport = netlib.SocketUnix + c.transport = netutils.SocketUnix } // end @@ -173,7 +173,7 @@ func (c *TCPClient) ConnectToRemote() { var err error switch c.transport { - case netlib.SocketUnix: + case netutils.SocketUnix: address = c.config.Loggers.TCPClient.RemoteAddress if len(c.config.Loggers.TCPClient.SockPath) > 0 { address = c.config.Loggers.TCPClient.SockPath @@ -181,11 +181,11 @@ func (c *TCPClient) ConnectToRemote() { c.LogInfo("connecting to %s://%s", c.transport, address) conn, err = net.DialTimeout(c.transport, address, connTimeout) - case netlib.SocketTCP: + case netutils.SocketTCP: c.LogInfo("connecting to %s://%s", c.transport, address) conn, err = net.DialTimeout(c.transport, address, connTimeout) - case netlib.SocketTLS: + case netutils.SocketTLS: c.LogInfo("connecting to %s://%s", c.transport, address) var tlsConfig *tls.Config @@ -201,7 +201,7 @@ func (c *TCPClient) ConnectToRemote() { tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions) if err == nil { dialer := &net.Dialer{Timeout: connTimeout} - conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig) + conn, err = tls.DialWithDialer(dialer, netutils.SocketTCP, address, tlsConfig) } default: c.logger.Fatal("logger=tcpclient - invalid transport:", c.transport) diff --git a/loggers/tcpclient_test.go b/loggers/tcpclient_test.go index 27fb5354..b46c8150 100644 --- a/loggers/tcpclient_test.go +++ b/loggers/tcpclient_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-logger" ) @@ -44,7 +44,7 @@ func Test_TcpClientRun(t *testing.T) { g := NewTCPClient(cfg, logger.New(false), "test") // fake json receiver - fakeRcvr, err := net.Listen(netlib.SocketTCP, ":9999") + fakeRcvr, err := net.Listen(netutils.SocketTCP, ":9999") if err != nil { t.Fatal(err) } @@ -106,7 +106,7 @@ func Test_TcpClient_ConnectionAttempt(t *testing.T) { time.Sleep(time.Second * 3) // start receiver - fakeRcvr, err := net.Listen(netlib.SocketTCP, ":9999") + fakeRcvr, err := net.Listen(netutils.SocketTCP, ":9999") if err != nil { t.Fatal(err) } diff --git a/netlib/conn.go b/netlib/conn.go deleted file mode 100644 index 301efcd1..00000000 --- a/netlib/conn.go +++ /dev/null @@ -1,60 +0,0 @@ -package netlib - -import ( - "io" - "net" -) - -// thanks to https://stackoverflow.com/questions/28967701/golang-tcp-socket-cant-close-after-get-file, -// call conn.CloseRead() before calling conn.Close() -func Close(conn io.Closer, reset bool) error { - type ReadCloser interface { - CloseRead() error - } - - // Aggressive closing, send TCP RESET instead of FIN - if reset { - if tcpConn, ok := conn.(*net.TCPConn); ok { - tcpConn.SetLinger(0) - } - } - - var errs []error - if closer, ok := conn.(ReadCloser); ok { - errs = append(errs, closer.CloseRead()) - } - errs = append(errs, conn.Close()) - for _, err := range errs { - if err != nil { - return err - } - } - return nil -} - -// GetPeerName returns the hostname associated with the given peer address. -// If the peer address cannot be split into IP and port or if the hostname lookup fails, -// it returns the peer address or IP itself. -func GetPeerName(peerAddr string) string { - // Split the peer address into IP and port - peerIP, _, err := net.SplitHostPort(peerAddr) - if err != nil { - // If splitting fails, return the original peer address - return peerAddr - } - - // Lookup hostname associated with the IP address - names, err := net.LookupAddr(peerIP) - if err != nil { - // If hostname lookup fails, return the IP address - return peerIP - } - - // If hostname is found, return the first name in the list - if len(names) > 0 { - return names[0] - } - - // If no hostname is found, return the IP address - return peerIP -} diff --git a/netutils/bpf.go b/netutils/bpf.go new file mode 100644 index 00000000..a132e275 --- /dev/null +++ b/netutils/bpf.go @@ -0,0 +1,90 @@ +//go:build linux +// +build linux + +package netutils + +import ( + "syscall" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" +) + +// Convert a uint16 to host byte order (big endian) +func Htons(v uint16) int { + return int((v << 8) | (v >> 8)) +} + +func GetBpfFilter(port int) []bpf.Instruction { + // bpf filter: (ip or ip6 ) and (udp or tcp) and port 53 + // fragmented packets are ignored + var filter = []bpf.Instruction{ + // Load eth.type (2 bytes at offset 12) and push-it in register A + bpf.LoadAbsolute{Off: 12, Size: 2}, + // if eth.type == IPv4 continue with the next instruction + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 10}, + // Load ip.proto (1 byte at offset 23) and push-it in register A + bpf.LoadAbsolute{Off: 23, Size: 1}, + // ip.proto == UDP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, + // ip.proto == TCP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 16}, + // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet + bpf.LoadAbsolute{Off: 20, Size: 2}, + // Only look at the last 13 bits of the data saved in regiter A + // 0x1fff == 0001 1111 1111 1111 (fragment offset) + // If any of the data in fragment offset is true, ignore the packet + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 14, SkipFalse: 0}, + // Load ip.length + // Register X = ip header len * 4 + bpf.LoadMemShift{Off: 14}, + // Load source port in tcp or udp (2 bytes at offset x+14) + bpf.LoadIndirect{Off: 14, Size: 2}, + // source port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 10, SkipFalse: 0}, + // Load destination port in tcp or udp (2 bytes at offset x+16) + bpf.LoadIndirect{Off: 16, Size: 2}, + // destination port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 8, SkipFalse: 9}, + // if eth.type == IPv6 continue with the next instruction + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 8}, + // Load ipv6.nxt (2 bytes at offset 12) and push-it in register A + bpf.LoadAbsolute{Off: 20, Size: 1}, + // ip.proto == UDP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, + // ip.proto == TCP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 5}, + // Load source port tcp or udp (2 bytes at offset 54) + bpf.LoadAbsolute{Off: 54, Size: 2}, + // source port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 2, SkipFalse: 0}, + // Load destination port tcp or udp (2 bytes at offset 56) + bpf.LoadAbsolute{Off: 56, Size: 2}, + // destination port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1}, + // Keep the packet and send up to 65k of the packet to userspace + bpf.RetConstant{Val: 0xFFFF}, + // Ignore packet + bpf.RetConstant{Val: 0}, + } + return filter +} + +func ApplyBpfFilter(filter []bpf.Instruction, fd int) (err error) { + var assembled []bpf.RawInstruction + if assembled, err = bpf.Assemble(filter); err != nil { + return err + } + + prog := &unix.SockFprog{ + Len: uint16(len(assembled)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])), + } + + return unix.SetsockoptSockFprog(fd, syscall.SOL_SOCKET, syscall.SO_ATTACH_FILTER, prog) +} + +func RemoveBpfFilter(fd int) (err error) { + return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_DETACH_FILTER, 0) +} diff --git a/netutils/conn.go b/netutils/conn.go new file mode 100644 index 00000000..24ca517d --- /dev/null +++ b/netutils/conn.go @@ -0,0 +1,140 @@ +package netutils + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "os" + "strconv" +) + +func AcceptConnections(listener net.Listener, acceptChan chan<- net.Conn) { + go func() { + defer close(acceptChan) + for { + conn, err := listener.Accept() + if err != nil { + return + } + acceptChan <- conn + } + }() +} + +func IsClosedConnectionError(err error) bool { + var opErr *net.OpError + if errors.As(err, &opErr) { + if opErr.Err.Error() == "use of closed network connection" { + return true + } + } + return false +} + +func StartToListen(listenIP string, listenPort int, sockPath string, tlsSupport bool, tlsMin uint16, certFile, keyFile string) (net.Listener, error) { + var err error + var listener net.Listener + + // prepare address + var addr string + if len(sockPath) > 0 { + addr = sockPath + _ = os.Remove(sockPath) + } else { + addr = net.JoinHostPort(listenIP, strconv.Itoa(listenPort)) + } + + // listening with tls enabled ? + if tlsSupport { + var cer tls.Certificate + cer, err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("failed to load certificate: %w", err) + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cer}, + MinVersion: tls.VersionTLS12, + } + + // update tls min version according to the user config + tlsConfig.MinVersion = tlsMin + + // listen + if len(sockPath) > 0 { + listener, err = tls.Listen(SocketUnix, addr, tlsConfig) + } else { + listener, err = tls.Listen(SocketTCP, addr, tlsConfig) + } + + } else { + // basic listening + if len(sockPath) > 0 { + listener, err = net.Listen(SocketUnix, addr) + } else { + listener, err = net.Listen(SocketTCP, addr) + } + } + + // something is wrong ? + if err != nil { + return nil, fmt.Errorf("failed to listen: %w", err) + } + return listener, nil +} + +// thanks to https://stackoverflow.com/questions/28967701/golang-tcp-socket-cant-close-after-get-file, +// call conn.CloseRead() before calling conn.Close() +func Close(conn io.Closer, reset bool) error { + type ReadCloser interface { + CloseRead() error + } + + // Aggressive closing, send TCP RESET instead of FIN + if reset { + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetLinger(0) + } + } + + var errs []error + if closer, ok := conn.(ReadCloser); ok { + errs = append(errs, closer.CloseRead()) + } + errs = append(errs, conn.Close()) + for _, err := range errs { + if err != nil { + return err + } + } + return nil +} + +// GetPeerName returns the hostname associated with the given peer address. +// If the peer address cannot be split into IP and port or if the hostname lookup fails, +// it returns the peer address or IP itself. +func GetPeerName(peerAddr string) string { + // Split the peer address into IP and port + peerIP, _, err := net.SplitHostPort(peerAddr) + if err != nil { + // If splitting fails, return the original peer address + return peerAddr + } + + // Lookup hostname associated with the IP address + names, err := net.LookupAddr(peerIP) + if err != nil { + // If hostname lookup fails, return the IP address + return peerIP + } + + // If hostname is found, return the first name in the list + if len(names) > 0 { + return names[0] + } + + // If no hostname is found, return the IP address + return peerIP +} diff --git a/netlib/constant.go b/netutils/constant.go similarity index 95% rename from netlib/constant.go rename to netutils/constant.go index 63887994..c49ee0ff 100644 --- a/netlib/constant.go +++ b/netutils/constant.go @@ -1,4 +1,4 @@ -package netlib +package netutils const ( ProtoInet = "INET" diff --git a/netutils/ip.go b/netutils/ip.go new file mode 100644 index 00000000..13ce2801 --- /dev/null +++ b/netutils/ip.go @@ -0,0 +1,25 @@ +package netutils + +import ( + "encoding/binary" + "net" +) + +func ConvertIP4(ip uint32) net.IP { + addr := make(net.IP, net.IPv4len) + binary.BigEndian.PutUint32(addr, ip) + return addr +} + +func ConvertIP6(ip [4]uint32) net.IP { + addr := make(net.IP, net.IPv6len) + binary.LittleEndian.PutUint32(addr[0:], ip[0]) + binary.LittleEndian.PutUint32(addr[4:], ip[1]) + binary.LittleEndian.PutUint32(addr[8:], ip[2]) + binary.LittleEndian.PutUint32(addr[12:], ip[3]) + return addr +} + +func GetIPAddress[T uint32 | [4]uint32](ip T, mapper func(T) net.IP) net.IP { + return mapper(ip) +} diff --git a/netlib/ipdefrag.go b/netutils/ipdefrag.go similarity index 99% rename from netlib/ipdefrag.go rename to netutils/ipdefrag.go index a7d29f90..f1bd5e34 100644 --- a/netlib/ipdefrag.go +++ b/netutils/ipdefrag.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "container/list" diff --git a/netlib/networkdecoder.go b/netutils/networkdecoder.go similarity index 99% rename from netlib/networkdecoder.go rename to netutils/networkdecoder.go index 6d7237e0..3171316c 100644 --- a/netlib/networkdecoder.go +++ b/netutils/networkdecoder.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "fmt" diff --git a/netlib/networkdecoder_test.go b/netutils/networkdecoder_test.go similarity index 99% rename from netlib/networkdecoder_test.go rename to netutils/networkdecoder_test.go index b7f82484..126c885f 100644 --- a/netlib/networkdecoder_test.go +++ b/netutils/networkdecoder_test.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "testing" diff --git a/netlib/packetproccesor.go b/netutils/packetproccesor.go similarity index 99% rename from netlib/packetproccesor.go rename to netutils/packetproccesor.go index ab574b4a..a582bf2e 100644 --- a/netlib/packetproccesor.go +++ b/netutils/packetproccesor.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "time" diff --git a/netlib/packetprocessor_test.go b/netutils/packetprocessor_test.go similarity index 99% rename from netlib/packetprocessor_test.go rename to netutils/packetprocessor_test.go index caea7e34..e16445b5 100644 --- a/netlib/packetprocessor_test.go +++ b/netutils/packetprocessor_test.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "os" diff --git a/netlib/sock.go b/netutils/sock.go similarity index 98% rename from netlib/sock.go rename to netutils/sock.go index 1dcac03e..1afa2bbe 100644 --- a/netlib/sock.go +++ b/netutils/sock.go @@ -1,7 +1,7 @@ //go:build linux || darwin || freebsd // +build linux darwin freebsd -package netlib +package netutils import ( "crypto/tls" diff --git a/netlib/sock_windows.go b/netutils/sock_windows.go similarity index 98% rename from netlib/sock_windows.go rename to netutils/sock_windows.go index b142dcbb..bdfdee11 100644 --- a/netlib/sock_windows.go +++ b/netutils/sock_windows.go @@ -1,7 +1,7 @@ //go:build windows // +build windows -package netlib +package netutils import ( "crypto/tls" diff --git a/netlib/tcpassembly.go b/netutils/tcpassembly.go similarity index 99% rename from netlib/tcpassembly.go rename to netutils/tcpassembly.go index b6ec608a..0a167dd0 100644 --- a/netlib/tcpassembly.go +++ b/netutils/tcpassembly.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "bytes" diff --git a/netlib/tcpassembly_test.go b/netutils/tcpassembly_test.go similarity index 99% rename from netlib/tcpassembly_test.go rename to netutils/tcpassembly_test.go index 7ec2d1d8..11c7799c 100644 --- a/netlib/tcpassembly_test.go +++ b/netutils/tcpassembly_test.go @@ -1,4 +1,4 @@ -package netlib +package netutils import ( "os" diff --git a/pkgconfig/loggers.go b/pkgconfig/loggers.go index 5ed22bee..4e4e783c 100644 --- a/pkgconfig/loggers.go +++ b/pkgconfig/loggers.go @@ -3,7 +3,7 @@ package pkgconfig import ( "reflect" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/prometheus/prometheus/model/relabel" ) @@ -320,7 +320,7 @@ func (c *ConfigLoggers) SetDefault() { c.DNSTap.Enable = false c.DNSTap.RemoteAddress = LocalhostIP c.DNSTap.RemotePort = 6000 - c.DNSTap.Transport = netlib.SocketTCP + c.DNSTap.Transport = netutils.SocketTCP c.DNSTap.ConnectTimeout = 5 c.DNSTap.RetryInterval = 10 c.DNSTap.FlushInterval = 30 @@ -404,7 +404,7 @@ func (c *ConfigLoggers) SetDefault() { c.TCPClient.RemotePort = 9999 c.TCPClient.SockPath = "" c.TCPClient.RetryInterval = 10 - c.TCPClient.Transport = netlib.SocketTCP + c.TCPClient.Transport = netutils.SocketTCP c.TCPClient.TLSSupport = false c.TCPClient.TLSInsecure = false c.TCPClient.TLSMinVersion = TLSV12 @@ -449,7 +449,7 @@ func (c *ConfigLoggers) SetDefault() { c.Fluentd.RetryInterval = 10 c.Fluentd.ConnectTimeout = 5 c.Fluentd.FlushInterval = 30 - c.Fluentd.Transport = netlib.SocketTCP + c.Fluentd.Transport = netutils.SocketTCP c.Fluentd.TLSSupport = false // deprecated c.Fluentd.TLSInsecure = false c.Fluentd.TLSMinVersion = TLSV12 @@ -497,7 +497,7 @@ func (c *ConfigLoggers) SetDefault() { c.Statsd.Prefix = ProgName c.Statsd.RemoteAddress = LocalhostIP c.Statsd.RemotePort = 8125 - c.Statsd.Transport = netlib.SocketUDP + c.Statsd.Transport = netutils.SocketUDP c.Statsd.ConnectTimeout = 5 c.Statsd.FlushInterval = 10 c.Statsd.TLSSupport = false // deprecated @@ -539,7 +539,7 @@ func (c *ConfigLoggers) SetDefault() { c.RedisPub.RemotePort = 6379 c.RedisPub.SockPath = "" c.RedisPub.RetryInterval = 10 - c.RedisPub.Transport = netlib.SocketTCP + c.RedisPub.Transport = netutils.SocketTCP c.RedisPub.TLSSupport = false c.RedisPub.TLSInsecure = false c.RedisPub.TLSMinVersion = TLSV12 diff --git a/pkgutils/common.go b/pkgutils/common.go new file mode 100644 index 00000000..b9f1c93e --- /dev/null +++ b/pkgutils/common.go @@ -0,0 +1,176 @@ +package pkgutils + +import ( + "time" + + "github.com/dmachard/go-dnscollector/dnsutils" + "github.com/dmachard/go-dnscollector/pkgconfig" + "github.com/dmachard/go-logger" +) + +type Collector struct { + doneRun, stopRun, doneMonitor, stopMonitor chan bool + config *pkgconfig.Config + configChan chan *pkgconfig.Config + logger *logger.Logger + name, descr string + droppedRoutes, defaultRoutes []Worker + droppedProcessorCount int + droppedProcessor chan int + droppedStanza chan string + droppedStanzaCount map[string]int +} + +func NewCollector(config *pkgconfig.Config, logger *logger.Logger, name string, descr string) *Collector { + logger.Info(PrefixLogCollector+"[%s] %s - enabled", name, descr) + c := &Collector{ + config: config, + configChan: make(chan *pkgconfig.Config), + logger: logger, + name: name, + descr: descr, + doneRun: make(chan bool), + doneMonitor: make(chan bool), + stopRun: make(chan bool), + stopMonitor: make(chan bool), + droppedProcessor: make(chan int), + droppedStanza: make(chan string), + droppedStanzaCount: map[string]int{}, + } + go c.Monitor() + return c +} + +func (c *Collector) GetName() string { return c.name } + +func (c *Collector) GetConfig() *pkgconfig.Config { return c.config } + +func (c *Collector) SetConfig(config *pkgconfig.Config) { c.config = config } + +func (c *Collector) ReadConfig() {} + +func (c *Collector) NewConfig() chan *pkgconfig.Config { return c.configChan } + +func (c *Collector) GetLogger() *logger.Logger { return c.logger } + +func (c *Collector) GetDroppedRoutes() []Worker { return c.droppedRoutes } + +func (c *Collector) GetDefaultRoutes() []Worker { return c.defaultRoutes } + +func (c *Collector) GetInputChannel() chan dnsutils.DNSMessage { return nil } + +func (c *Collector) AddDroppedRoute(wrk Worker) { + c.droppedRoutes = append(c.droppedRoutes, wrk) +} + +func (c *Collector) AddDefaultRoute(wrk Worker) { + c.defaultRoutes = append(c.defaultRoutes, wrk) +} + +func (c *Collector) SetDefaultRoutes(next []Worker) { + c.defaultRoutes = next +} + +func (c *Collector) SetLoggers(loggers []Worker) { c.defaultRoutes = loggers } + +func (c *Collector) Loggers() ([]chan dnsutils.DNSMessage, []string) { + return GetRoutes(c.defaultRoutes) +} + +func (c *Collector) ReloadConfig(config *pkgconfig.Config) { + c.LogInfo("reload configuration...") + c.configChan <- config +} + +func (c *Collector) LogInfo(msg string, v ...interface{}) { + c.logger.Info(PrefixLogCollector+"["+c.name+"] "+c.descr+" - "+msg, v...) +} + +func (c *Collector) LogError(msg string, v ...interface{}) { + c.logger.Error(PrefixLogCollector+"["+c.name+"] "+c.descr+" - "+msg, v...) +} + +func (c *Collector) LogFatal(v ...interface{}) { + c.logger.Fatal(v...) +} + +func (c *Collector) OnStop() chan bool { + return c.stopRun +} + +func (c *Collector) StopIsDone() { + c.doneRun <- true +} + +func (c *Collector) Stop() { + // stop monitor goroutine + c.LogInfo("stopping monitor...") + c.stopMonitor <- true + <-c.doneMonitor + + // read done channel and block until run is terminated + c.LogInfo("stopping run...") + c.stopRun <- true + <-c.doneRun +} + +func (c *Collector) Monitor() { + defer func() { + c.LogInfo("monitor terminated") + c.doneMonitor <- true + }() + + c.LogInfo("start monitor") + watchInterval := 10 * time.Second + bufferFull := time.NewTimer(watchInterval) + for { + select { + case <-c.droppedProcessor: + c.droppedProcessorCount++ + + case loggerName := <-c.droppedStanza: + if _, ok := c.droppedStanzaCount[loggerName]; !ok { + c.droppedStanzaCount[loggerName] = 1 + } else { + c.droppedStanzaCount[loggerName]++ + } + + case <-c.stopMonitor: + close(c.droppedProcessor) + close(c.droppedStanza) + bufferFull.Stop() + return + + case <-bufferFull.C: + if c.droppedProcessorCount > 0 { + c.LogError("processor buffer is full, %d dnsmessage(s) dropped", c.droppedProcessorCount) + c.droppedProcessorCount = 0 + } + + for v, k := range c.droppedStanzaCount { + if k > 0 { + c.LogError("stanza[%s] buffer is full, %d dnsmessage(s) dropped", v, k) + c.droppedStanzaCount[v] = 0 + } + } + + bufferFull.Reset(watchInterval) + } + } +} + +func (c *Collector) ProcessorIsBusy() { + c.droppedProcessor <- 1 +} + +func (c *Collector) NextStanzaIsBusy(name string) { + c.droppedStanza <- name +} + +func (c *Collector) Run() { + c.LogInfo("running in background...") + defer func() { + c.LogInfo("run terminated") + c.StopIsDone() + }() +} diff --git a/processors/dnstap.go b/processors/dnstap.go index 591fd7bf..18517cd0 100644 --- a/processors/dnstap.go +++ b/processors/dnstap.go @@ -7,7 +7,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -67,17 +67,8 @@ type DNSTapProcessor struct { droppedCount map[string]int } -func NewDNSTapProcessor( - connID int, - peerName string, - config *pkgconfig.Config, - logger *logger.Logger, - name string, - size int, -) DNSTapProcessor { - +func NewDNSTapProcessor(connID int, peerName string, config *pkgconfig.Config, logger *logger.Logger, name string, size int) DNSTapProcessor { logger.Info(pkgutils.PrefixLogProcessor+"[%s] dnstap - conn #%d - initialization...", name, connID) - d := DNSTapProcessor{ ConnID: connID, PeerName: peerName, @@ -240,7 +231,7 @@ RUN_LOOP: } } - if ipVersion, valid := netlib.IPVersion[dt.GetMessage().GetSocketFamily().String()]; valid { + if ipVersion, valid := netutils.IPVersion[dt.GetMessage().GetSocketFamily().String()]; valid { dm.NetworkInfo.Family = ipVersion } else { dm.NetworkInfo.Family = pkgconfig.StrUnknown diff --git a/processors/powerdns.go b/processors/powerdns.go index c7e61727..20e0c526 100644 --- a/processors/powerdns.go +++ b/processors/powerdns.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dmachard/go-dnscollector/dnsutils" - "github.com/dmachard/go-dnscollector/netlib" + "github.com/dmachard/go-dnscollector/netutils" "github.com/dmachard/go-dnscollector/pkgconfig" "github.com/dmachard/go-dnscollector/pkgutils" "github.com/dmachard/go-dnscollector/transformers" @@ -159,7 +159,7 @@ RUN_LOOP: dm.DNSTap.Identity = string(pbdm.GetServerIdentity()) dm.DNSTap.Operation = ProtobufPowerDNSToDNSTap[pbdm.GetType().String()] - if ipVersion, valid := netlib.IPVersion[pbdm.GetSocketFamily().String()]; valid { + if ipVersion, valid := netutils.IPVersion[pbdm.GetSocketFamily().String()]; valid { dm.NetworkInfo.Family = ipVersion } else { dm.NetworkInfo.Family = pkgconfig.StrUnknown diff --git a/tests/clientquery_dnstaptcp.py b/tests/clientquery_dnstaptcp.py index 41bb8756..f987379b 100644 --- a/tests/clientquery_dnstaptcp.py +++ b/tests/clientquery_dnstaptcp.py @@ -60,7 +60,7 @@ def connection_made(self, transport): def pipe_data_received(self, fd, data): print(data.decode(), end="") - if b"is listening on" in data: + if b"listening on" in data: self.is_listening.set_result(True) if b"CLIENT_QUERY NOERROR" in data: diff --git a/tests/clientquery_dnstapunix.py b/tests/clientquery_dnstapunix.py index 763b212c..8f5bdfff 100644 --- a/tests/clientquery_dnstapunix.py +++ b/tests/clientquery_dnstapunix.py @@ -60,7 +60,7 @@ def connection_made(self, transport): def pipe_data_received(self, fd, data): print(data.decode(), end="") - if b"is listening on" in data: + if b"listening on" in data: self.is_listening.set_result(True) if b"CLIENT_QUERY NOERROR" in data: