mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
- added dnstapEncoder object which incapsulates marshalling of dnstap
messages to protobuf and writing data to connection
- dnstapEncoder writes data directly to connection object. It doesn't
use the framestream's "write" method, because it writes data to
intermediate buffer (bufio.Writer) which leads to unnecessary
data copying and drops the performance
- dnstapEncoder reuses a preallocated buffer for marshalling dnstap
messages. Many messages are added to the same buffer. They are
separated with a "frame length" 4-byte values, so the buffer content
is writen to connection object in the format compatible with
framestream library
- added test which guarantees that dnstapEncoder output is the same
as framestream Encoder output
- the performance increase is about 50% in (dio *dnstapIO) serve() method
of dnstap plugin. The overall coredns performance increase is about 10%
in the following configuration:
.:1053 {
erratic {
drop 0
truncate 0
delay 0
}
dnstap tcp://127.0.0.1:6000 full
errors stdout
}
tested with dnsperf tool
146 lines
2.9 KiB
Go
146 lines
2.9 KiB
Go
package dnstapio
|
|
|
|
import (
|
|
"log"
|
|
"net"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
tap "github.com/dnstap/golang-dnstap"
|
|
fs "github.com/farsightsec/golang-framestream"
|
|
)
|
|
|
|
const (
|
|
tcpWriteBufSize = 1024 * 1024
|
|
tcpTimeout = 4 * time.Second
|
|
flushTimeout = 1 * time.Second
|
|
queueSize = 10000
|
|
)
|
|
|
|
type dnstapIO struct {
|
|
endpoint string
|
|
socket bool
|
|
conn net.Conn
|
|
enc *dnstapEncoder
|
|
queue chan tap.Dnstap
|
|
dropped uint32
|
|
}
|
|
|
|
// New returns a new and initialized DnstapIO.
|
|
func New(endpoint string, socket bool) DnstapIO {
|
|
return &dnstapIO{
|
|
endpoint: endpoint,
|
|
socket: socket,
|
|
enc: newDnstapEncoder(&fs.EncoderOptions{
|
|
ContentType: []byte("protobuf:dnstap.Dnstap"),
|
|
Bidirectional: true,
|
|
}),
|
|
queue: make(chan tap.Dnstap, queueSize),
|
|
}
|
|
}
|
|
|
|
// DnstapIO interface
|
|
type DnstapIO interface {
|
|
Connect()
|
|
Dnstap(payload tap.Dnstap)
|
|
Close()
|
|
}
|
|
|
|
func (dio *dnstapIO) newConnect() error {
|
|
var err error
|
|
if dio.socket {
|
|
if dio.conn, err = net.Dial("unix", dio.endpoint); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if dio.conn, err = net.DialTimeout("tcp", dio.endpoint, tcpTimeout); err != nil {
|
|
return err
|
|
}
|
|
if tcpConn, ok := dio.conn.(*net.TCPConn); ok {
|
|
tcpConn.SetWriteBuffer(tcpWriteBufSize)
|
|
tcpConn.SetNoDelay(false)
|
|
}
|
|
}
|
|
|
|
if err = dio.enc.resetWriter(dio.conn); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Connect connects to the dnstop endpoint.
|
|
func (dio *dnstapIO) Connect() {
|
|
if err := dio.newConnect(); err != nil {
|
|
log.Printf("[ERROR] No connection to dnstap endpoint")
|
|
}
|
|
go dio.serve()
|
|
}
|
|
|
|
// Dnstap enqueues the payload for log.
|
|
func (dio *dnstapIO) Dnstap(payload tap.Dnstap) {
|
|
select {
|
|
case dio.queue <- payload:
|
|
default:
|
|
atomic.AddUint32(&dio.dropped, 1)
|
|
}
|
|
}
|
|
|
|
func (dio *dnstapIO) closeConnection() {
|
|
dio.enc.close()
|
|
if dio.conn != nil {
|
|
dio.conn.Close()
|
|
dio.conn = nil
|
|
}
|
|
}
|
|
|
|
// Close waits until the I/O routine is finished to return.
|
|
func (dio *dnstapIO) Close() {
|
|
close(dio.queue)
|
|
}
|
|
|
|
func (dio *dnstapIO) flushBuffer() {
|
|
if dio.conn == nil {
|
|
if err := dio.newConnect(); err != nil {
|
|
return
|
|
}
|
|
log.Printf("[INFO] Reconnected to dnstap")
|
|
}
|
|
|
|
if err := dio.enc.flushBuffer(); err != nil {
|
|
log.Printf("[WARN] Connection lost: %s", err)
|
|
dio.closeConnection()
|
|
if err := dio.newConnect(); err != nil {
|
|
log.Printf("[ERROR] Cannot connect to dnstap: %s", err)
|
|
} else {
|
|
log.Printf("[INFO] Reconnected to dnstap")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dio *dnstapIO) write(payload *tap.Dnstap) {
|
|
if err := dio.enc.writeMsg(payload); err != nil {
|
|
atomic.AddUint32(&dio.dropped, 1)
|
|
}
|
|
}
|
|
|
|
func (dio *dnstapIO) serve() {
|
|
timeout := time.After(flushTimeout)
|
|
for {
|
|
select {
|
|
case payload, ok := <-dio.queue:
|
|
if !ok {
|
|
dio.flushBuffer()
|
|
dio.closeConnection()
|
|
return
|
|
}
|
|
dio.write(&payload)
|
|
case <-timeout:
|
|
if dropped := atomic.SwapUint32(&dio.dropped, 0); dropped > 0 {
|
|
log.Printf("[WARN] Dropped dnstap messages: %d", dropped)
|
|
}
|
|
dio.flushBuffer()
|
|
timeout = time.After(flushTimeout)
|
|
}
|
|
}
|
|
}
|