Skip to content

Commit

Permalink
Fix the recursor unix sock messaging
Browse files Browse the repository at this point in the history
Honor the status messages requirements and message length
preamble.
  • Loading branch information
cvaroqui committed Nov 26, 2024
1 parent f84cd5b commit 853754f
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 74 deletions.
7 changes: 7 additions & 0 deletions host_endianness_be.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build armbe || arm64be || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || sparc || sparc64

package main

import "encoding/binary"

var HostEndianness = binary.BigEndian
7 changes: 7 additions & 0 deletions host_endianness_le.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || ppc64le || riscv || riscv64 || wasm

package main

import "encoding/binary"

var HostEndianness = binary.LittleEndian
76 changes: 2 additions & 74 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"net"
"os"
"sync/atomic"
"time"

"github.com/opensvc/om3/core/client"
Expand All @@ -29,11 +26,8 @@ var (
lastDial time.Time
osvcSock string
pdnsSock string
tempSock string
connectInterval time.Duration

pdnsIsConnected atomic.Bool
pdnsConn net.Conn
evHandlingTimeout = 300 * time.Millisecond
logLevel string

Expand Down Expand Up @@ -69,11 +63,6 @@ func main() {
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
}

defer func() {
_ = pdnsConn.Close()
_ = os.Remove(tempSock)
}()

for {
if err := watch(); err != nil {
log.Error().Err(err).Msg("watch")
Expand All @@ -82,42 +71,6 @@ func main() {
}
}

func pdnsRedial() error {
minDialTime := lastDial.Add(connectInterval)
if time.Now().Before(minDialTime) {
time.Sleep(minDialTime.Sub(time.Now()))
}
lastDial = time.Now()
if pdnsConn != nil {
if err := pdnsConn.Close(); err != nil {
log.Error().Err(err).Msg("redial: pdns connection close")
}
pdnsConn = nil
}
if tempSock != "" {
if err := os.Remove(tempSock); errors.Is(err, os.ErrExist) {
// pass
} else if err != nil {
log.Error().Err(err).Msg("redial: pdns client socket file remove")
}
}
if err := pdnsDial(); err != nil {
return err
}
return nil
}

func pdnsDial() error {
if conn, err := net.Dial("unix", pdnsSock); err != nil {
return err
} else {
log.Info().Msg("pdns recursor connected")
pdnsConn = conn
pdnsIsConnected.Store(true)
}
return nil
}

func reGetEventReader() (event.ReadCloser, error) {
minGetEventReaderTime := lastGetEventReader.Add(connectInterval)
if time.Now().Before(minGetEventReaderTime) {
Expand Down Expand Up @@ -191,39 +144,14 @@ func watch() error {
}

func onEvent(evData zoneRecordEvent) error {
msg := fmt.Sprintf("wipe-cache %s", evData.Name)
wipe := func() error {
deadline := time.Now().Add(evHandlingTimeout)
if err := pdnsConn.SetDeadline(deadline); err != nil {
return err
}
log.Info().Msgf(">>> %s", msg)
if _, err := pdnsConn.Write([]byte(msg + "\n")); err != nil {
return err
}
var buff [1024]byte
n, err := pdnsConn.Read(buff[:])
if err != nil {
return err
}
if n > 0 {
log.Info().Msgf("<<< %s", string(buff[:n-1]))
}
return nil
}
wiper := func() error {
for {
if !pdnsIsConnected.Load() {
if err := pdnsRedial(); err != nil {
return err
}
}
err := wipe()
err := wipe(evData.Name)
switch {
case errors.Is(err, os.ErrDeadlineExceeded):
log.Error().Err(err).Msg("pdns control socket")
case err != nil:
pdnsIsConnected.Store(false)
log.Error().Err(err).Msg("wipe error")
default:
return nil
}
Expand Down
62 changes: 62 additions & 0 deletions proto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

// derived from https://raw.githubusercontent.com/OrfeasZ/telegraf/b8a4e6cb7aef4134bd506cb873c8040c084a2406/plugins/inputs/powerdns_recursor/protocol_commons.go
// license: MIT

import (
"fmt"
"net"
"strconv"
)

// This below is generally unsafe but necessary in this case
// since the powerdns protocol encoding is host dependent.
// The C implementation uses size_t as the size type for the
// command length. The size and endianness of size_t change
// depending on the platform the program is being run on.
// Using the target architecture endianness and the known
// integer size, we can "recreate" the corresponding C
// behavior in an effort to maintain compatibility. Of course
// in cases where one program is compiled for i386 and the
// other for amd64 (and similar), this method will fail.

const uintSizeInBytes = strconv.IntSize / 8

func writeNativeUIntToConn(conn net.Conn, value uint) error {
intData := make([]byte, uintSizeInBytes)

switch uintSizeInBytes {
case 4:
HostEndianness.PutUint32(intData, uint32(value))
case 8:
HostEndianness.PutUint64(intData, uint64(value))
default:
return fmt.Errorf("unsupported system configuration")
}

_, err := conn.Write(intData)
return err
}

func readNativeUIntFromConn(conn net.Conn) (uint, error) {
intData := make([]byte, uintSizeInBytes)

n, err := conn.Read(intData)

if err != nil {
return 0, err
}

if n != uintSizeInBytes {
return 0, fmt.Errorf("did not read enough data for native uint: read '%v' bytes, expected '%v'", n, uintSizeInBytes)
}

switch uintSizeInBytes {
case 4:
return uint(HostEndianness.Uint32(intData)), nil
case 8:
return uint(HostEndianness.Uint64(intData)), nil
default:
return 0, fmt.Errorf("unsupported system configuration")
}
}
87 changes: 87 additions & 0 deletions proto3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

// derived from https://raw.githubusercontent.com/OrfeasZ/telegraf/b8a4e6cb7aef4134bd506cb873c8040c084a2406/plugins/inputs/powerdns_recursor/protocol_v3.go
// license: MIT

import (
"fmt"
"log"
"net"
"time"
)

// V3 (4.6.0+) Protocol:
// Standard unix stream socket
// Synchronous request / response
// Data structure:
// status: uint32
// dataLength: size_t
// data: byte[dataLength]
func wipe(name string) error {
conn, err := net.Dial("unix", pdnsSock)
if err != nil {
return err
}

defer conn.Close()

if err := conn.SetDeadline(time.Now().Add(evHandlingTimeout)); err != nil {
return err
}

// Write 4-byte response code.
if _, err = conn.Write([]byte{0, 0, 0, 0}); err != nil {
return err
}

command := []byte("wipe-cache " + name + "\n")
log.Printf(">>> %s", command)

if err = writeNativeUIntToConn(conn, uint(len(command))); err != nil {
return err
}

if _, err = conn.Write(command); err != nil {
return err
}

// Now read the response.
status := make([]byte, 4)
n, err := conn.Read(status)
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("no status code received")
}

responseLength, err := readNativeUIntFromConn(conn)
if err != nil {
return err
}
if responseLength == 0 {
return fmt.Errorf("received data length was '0'")
}

// Don't allow more than 64kb of data to prevent DOS / issues
// with architecture mismatch. V2 protocol allowed for up to
// 16kb, so 64kb should give us a pretty good margin for anything
// that has been added since.
if responseLength > 64*1024 {
return fmt.Errorf("received data length was '%d', we only allow up to '%d'", responseLength, 64*1024)
}

data := make([]byte, responseLength)
n, err = conn.Read(data)
if err != nil {
return err
}
if uint(n) != responseLength {
return fmt.Errorf("no data received, expected '%v' bytes but got '%v'", responseLength, n)
}

// Process data
log.Print(string(data))

return nil
}

0 comments on commit 853754f

Please sign in to comment.