Funkensturm: transparent proxy example

January 23, 2011


A transparent proxy is a proxy that does nothing, but it serves as a nice introduction into Funkensturm.

See this post for an architectural overview of Funkensturm.

Currently a configuration is compiled into the Funkensturm binary. As such it must look like a normal Go program.

Matching function

The matching function checks a packet for features and can optionally modify it.

This is the prototype of a matching function:

func match(m *dns.Msg, d int) (*dns.Msg, bool)

It receives a *dns.Msg which is a packet in Go DNS and a direction d which can be IN (first incoming packet) or OUT when the packet is on its way back to the client. It returns a DNS packet and a boolean value signaling a match.

This being a transparant proxy, we don’t do anything with the packet, but I’ve left the structure of the function intact:

func match(m *dns.Msg, d int) (*dns.Msg, bool) {
        // Matching criteria
        switch d {
        case IN:
                // nothing
        case OUT:
                // nothing

        // Packet Mangling functions
        switch d {
        case IN:
                // nothing
        case OUT:
                // nothing
        return m, true

Action function

The only thing the action function needs to do is forwarding the packet to the server.

The prototype of a matching function is:

func send(m *dns.Msg, ok bool) *dns.Msg

It receives a DNS packet and a boolean. The boolean comes from the match function. An action function needs to return a packet. In this case the returned packet is the reply from the server.

func send(m *dns.Msg, ok bool) *dns.Msg {
        switch ok {
        case true, false:
                qr[0] <- resolver.Msg{m, nil, nil}
                in := <-qr[0]
                return in.Dns
        return nil

The variable qr is global and is setup by Funkensturm: it is the communication channel for the default resolver. There can be multiple of these (Funkensturm can be used as a duplicator), but here we use the first: qr[0].

Configure Funkensturm

Once these functions are defined we can create a Funkensturm configuration. By default it will execute a function named funkensturm() which should return a pointer to a Funkensturm type:

type Funkensturm struct {
        Setup   func() bool
        Matches []Match
        Actions []Action

So let’s setup the function that returns a pointer to the structure:

func funkensturm() *Funkensturm {
        f := new(Funkensturm)

        // Nothing to set up.
        f.Setup = func() bool { return true }

        // 1 match function, use AND as op (doesn't matter in this case)
        f.Matches = make([]Match, 1)    // make room for 1 function
        f.Matches[0].Op = AND           // set the chaining boolean
        f.Matches[0].Func = match       // set the match function

        // 1 action
        f.Actions = make([]Action, 1)   // make room for 1 function
        f.Actions[0].Func = send        // set the action function
        return f                        // return the pointer 

Full code listing.

Start me up

The default setup compiles the above config. Funkensturm allows for some configuration on top of that:

% ./funkensturm -h
flag provided but not defined: -h
Usage: ./funkensturm
  -sserver="": Set the listener address
  -verbose=false: Print packet as the flow through
  -rserver="": Remote server address(es)

The defaults look good, so we use those:

% ./funkensturm

We can now use dig to ask questions on port 8053, which get forwarded to my recursive server on port 53 and are then returned to dig:

% dig +nocmd +noquestion +stats +noidentify -p 8053 @
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57601
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2

;; ANSWER SECTION:        169603  IN      A

;; AUTHORITY SECTION:            169603  IN      NS            169603  IN      NS

;; ADDITIONAL SECTION:     25602   IN      A     25603   IN      A

;; Query time: 1 msec
;; WHEN: Sun Jan 23 18:52:22 2011
;; MSG SIZE  rcvd: 218