Funkensturm: transparent proxy example
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
}
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="127.0.0.1:8053": Set the listener address
-verbose=false: Print packet as the flow through
-rserver="127.0.0.1:53": 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 @127.0.0.1 www.example.com
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57601
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2
;; ANSWER SECTION:
www.example.com. 169603 IN A 192.0.32.10
;; AUTHORITY SECTION:
example.com. 169603 IN NS a.iana-servers.net.
example.com. 169603 IN NS b.iana-servers.net.
;; ADDITIONAL SECTION:
a.iana-servers.net. 25602 IN A 192.0.34.43
b.iana-servers.net. 25603 IN A 193.0.0.236
;; Query time: 1 msec
;; SERVER: 127.0.0.1#8053(127.0.0.1)
;; WHEN: Sun Jan 23 18:52:22 2011
;; MSG SIZE rcvd: 218