Another application for Funkensturm is: delaying packets. Here we only delay packets with the recursion desired bit (RD) set, but it can be easily be changed to check for other properties of a packet, see godoc dns for all elements of DNS packets.

The configuration is similar as described here.

Matching pkts with RD bit set

The matching function becomes:

// the only matching we do is on the RD bit
// for incoming packets.
func match(m *dns.Msg, d int) (*dns.Msg, bool) {
        // Matching criteria
        var ok bool
        switch d {
        case IN:
                // only delay pkts with RD bit 
                ok = m.MsgHdr.RecursionDesired == true
        case OUT:
                // nothing
        }

        // Packet Mangling
        switch d {
        case IN:
                // nothing
        case OUT:
                // nothing
        }
        return m, ok
}

Action function

First a delay helper function. As shown here it returns true if the delay time isn’t reached, and false if a something should be delayed.

const NSECDELAY = 1 * 1e9 // 1 second, meaning 1 qps (smaller means higher qps)
var previous int64 // previous tick

// returns false if we hit the limit set by NSECDELAY
func checkDelay() (ti int64, limitok bool) {
        current := time.Nanoseconds()
        tdiff := (current - previous)
        if tdiff < NSECDELAY {
                // too often
                return previous, false
        }   
        return current, true
}

In the action function we check the delay with checkDelay, but only if the match function returned true. If checkDelay returns false, all is OK and we return the packet. When it returns false we’ve reached the threshold, so we sleep for some time and return a nil packet.

func delay(m *dns.Msg, ok bool) *dns.Msg {
        var ok1 bool
        switch ok {
        case true:
                previous, ok1 = checkDelay()
                if !ok1 {
                        fmt.Fprintf(os.Stderr, "Info: Dropping: too often")
                        time.Sleep(NSECDELAY)
                        return nil 
                } else {
                        fmt.Fprintf(os.Stderr, "Info: Dropping: too often")
                        qr[0] <- resolver.Msg{m, nil, nil}
                        in := <-qr[0]
                        return in.Dns
                }   
        case false:
                qr[0] <- resolver.Msg{m, nil, nil}
                in := <-qr[0]
                return in.Dns
        }   
        return nil 
}

Preparing the funkensturm struct

// Return the configration
func funkensturm() *Funkensturm {
        f := new(Funkensturm)

        f.Setup = func() bool { previous = time.Nanoseconds(); return true }

        f.Matches = make([]Match, 1)
        f.Matches[0].Op = AND
        f.Matches[0].Func = match

        f.Actions = make([]Action, 1)
        f.Actions[0].Func = delay
        return f
}

Full source of config_delay.go

Trying it!

The following queries are performed in quick succession:

% dig +short +norec -p 8053 @127.0.0.1 www.example.com
192.0.32.10

Which works OK and quick, unlike in the following example, where we fire off queries with the RD bit:

% dig +short -p 8053 @127.0.0.1 www.example.com
<quick reply>
192.0.32.10

% dig +short -p 8053 @127.0.0.1 www.example.com
<quick reply>
192.0.32.10

% dig +short -p 8053 @127.0.0.1 www.example.com
<long wait>
<long wait>
192.0.32.10

In the terminal where Funkensturm runs, we see:

Info: Ok: let it through
Info: Ok: let it through
Info: Dropping: too often
Info: Ok: let it through

It works!