From e0f83dee9a3c55205da9ea376c016668a2a35b1a Mon Sep 17 00:00:00 2001 From: James DeFelice Date: Tue, 4 Aug 2015 01:17:14 -0400 Subject: [PATCH 1/3] add interfaces to allow packet-level inspection for pre/post processing --- server.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 5e4ec92b..36fee173 100644 --- a/server.go +++ b/server.go @@ -47,6 +47,7 @@ type response struct { tcp *net.TCPConn // i/o connection if TCP was used udpSession *SessionUDP // oob data to get egress interface right remoteAddr net.Addr // address of the client + writer Writer // writer to output the raw DNS bits } // ServeMux is an DNS request multiplexer. It matches the @@ -197,6 +198,31 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { DefaultServeMux.HandleFunc(pattern, handler) } +type Writer interface { + io.Writer +} + +type Reader interface { + ReadTCP(conn *net.TCPConn, timeout time.Duration) ([]byte, error) + ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) +} + +type defaultReader struct { + *Server +} + +func (dr *defaultReader) ReadTCP(conn *net.TCPConn, timeout time.Duration) ([]byte, error) { + return dr.readTCP(conn, timeout) +} + +func (dr *defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { + return dr.readUDP(conn, timeout) +} + +type ReaderBuilder func(Reader) Reader + +type WriterBuilder func(Writer) Writer + // A Server defines parameters for running an DNS server. type Server struct { // Address to listen on, ":dns" if empty. @@ -225,6 +251,10 @@ type Server struct { Unsafe bool // If NotifyStartedFunc is set is is called, once the server has started listening. NotifyStartedFunc func() + // ReaderBuilder is optional, allows customization of the process that reads DNS frames + ReaderBuilder ReaderBuilder + // WriterBuilder is optional, allows customization of the process that writes DNS frames + WriterBuilder WriterBuilder // For graceful shutdown. stopUDP chan bool @@ -382,6 +412,11 @@ func (srv *Server) serveTCP(l *net.TCPListener) error { srv.NotifyStartedFunc() } + reader := Reader(&defaultReader{srv}) + if srv.ReaderBuilder != nil { + reader = srv.ReaderBuilder(reader) + } + handler := srv.Handler if handler == nil { handler = DefaultServeMux @@ -393,7 +428,7 @@ func (srv *Server) serveTCP(l *net.TCPListener) error { if e != nil { continue } - m, e := srv.readTCP(rw, rtimeout) + m, e := reader.ReadTCP(rw, rtimeout) select { case <-srv.stopTCP: return nil @@ -417,6 +452,11 @@ func (srv *Server) serveUDP(l *net.UDPConn) error { srv.NotifyStartedFunc() } + reader := Reader(&defaultReader{srv}) + if srv.ReaderBuilder != nil { + reader = srv.ReaderBuilder(reader) + } + handler := srv.Handler if handler == nil { handler = DefaultServeMux @@ -424,7 +464,7 @@ func (srv *Server) serveUDP(l *net.UDPConn) error { rtimeout := srv.getReadTimeout() // deadline is not used here for { - m, s, e := srv.readUDP(l, rtimeout) + m, s, e := reader.ReadUDP(l, rtimeout) select { case <-srv.stopUDP: return nil @@ -442,6 +482,12 @@ func (srv *Server) serveUDP(l *net.UDPConn) error { // Serve a new connection. func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *SessionUDP, t *net.TCPConn) { w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s} + if srv.WriterBuilder != nil { + w.writer = srv.WriterBuilder(w) + } else { + w.writer = w + } + q := 0 defer func() { if u != nil { @@ -451,6 +497,11 @@ func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *Ses srv.wgTCP.Done() } }() + + reader := Reader(&defaultReader{srv}) + if srv.ReaderBuilder != nil { + reader = srv.ReaderBuilder(reader) + } Redo: req := new(Msg) err := req.Unpack(m) @@ -490,7 +541,7 @@ Exit: if srv.IdleTimeout != nil { idleTimeout = srv.IdleTimeout() } - m, e := srv.readTCP(w.tcp, idleTimeout) + m, e := reader.ReadTCP(w.tcp, idleTimeout) if e == nil { q++ // TODO(miek): make this number configurable? @@ -562,7 +613,7 @@ func (w *response) WriteMsg(m *Msg) (err error) { if err != nil { return err } - _, err = w.Write(data) + _, err = w.writer.Write(data) return err } } @@ -570,7 +621,7 @@ func (w *response) WriteMsg(m *Msg) (err error) { if err != nil { return err } - _, err = w.Write(data) + _, err = w.writer.Write(data) return err } From e148c23156ae45c7a51c29ad417558736e2629ae Mon Sep 17 00:00:00 2001 From: James DeFelice Date: Tue, 4 Aug 2015 09:04:40 -0400 Subject: [PATCH 2/3] add docs and example for DNS frame read/write decorators --- server.go | 40 +++++++++++++++++++++++++-------------- server_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/server.go b/server.go index 36fee173..2ca613a8 100644 --- a/server.go +++ b/server.go @@ -198,15 +198,23 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { DefaultServeMux.HandleFunc(pattern, handler) } +// Writer writes DNS data frames; each call to Write should send an entire frame. type Writer interface { io.Writer } +// Reader reads DNS data frames; each call to ReadTCP or ReadUDP should return an entire frame. type Reader interface { + // ReadTCP reads a data frame from a TCP connection. Implementations may alter + // connection properties, for example the read-deadline. ReadTCP(conn *net.TCPConn, timeout time.Duration) ([]byte, error) + // ReadUDP reads a data frame from a UDP connection. Implementations may alter + // connection properties, for example the read-deadline. ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) } +// defaultReader is an adapter for the Server struct that implements the Reader interface +// using the readTCP and readUDP func of the embedded Server. type defaultReader struct { *Server } @@ -219,9 +227,13 @@ func (dr *defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]by return dr.readUDP(conn, timeout) } -type ReaderBuilder func(Reader) Reader +// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader. +// Implementations should never return a nil Reader. +type DecorateReader func(Reader) Reader -type WriterBuilder func(Writer) Writer +// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer. +// Implementations should never return a nil Writer. +type DecorateWriter func(Writer) Writer // A Server defines parameters for running an DNS server. type Server struct { @@ -251,10 +263,10 @@ type Server struct { Unsafe bool // If NotifyStartedFunc is set is is called, once the server has started listening. NotifyStartedFunc func() - // ReaderBuilder is optional, allows customization of the process that reads DNS frames - ReaderBuilder ReaderBuilder - // WriterBuilder is optional, allows customization of the process that writes DNS frames - WriterBuilder WriterBuilder + // DecorateReader is optional, allows customization of the process that reads DNS frames. + DecorateReader DecorateReader + // DecorateWriter is optional, allows customization of the process that writes DNS frames. + DecorateWriter DecorateWriter // For graceful shutdown. stopUDP chan bool @@ -413,8 +425,8 @@ func (srv *Server) serveTCP(l *net.TCPListener) error { } reader := Reader(&defaultReader{srv}) - if srv.ReaderBuilder != nil { - reader = srv.ReaderBuilder(reader) + if srv.DecorateReader != nil { + reader = srv.DecorateReader(reader) } handler := srv.Handler @@ -453,8 +465,8 @@ func (srv *Server) serveUDP(l *net.UDPConn) error { } reader := Reader(&defaultReader{srv}) - if srv.ReaderBuilder != nil { - reader = srv.ReaderBuilder(reader) + if srv.DecorateReader != nil { + reader = srv.DecorateReader(reader) } handler := srv.Handler @@ -482,8 +494,8 @@ func (srv *Server) serveUDP(l *net.UDPConn) error { // Serve a new connection. func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *SessionUDP, t *net.TCPConn) { w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s} - if srv.WriterBuilder != nil { - w.writer = srv.WriterBuilder(w) + if srv.DecorateWriter != nil { + w.writer = srv.DecorateWriter(w) } else { w.writer = w } @@ -499,8 +511,8 @@ func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *Ses }() reader := Reader(&defaultReader{srv}) - if srv.ReaderBuilder != nil { - reader = srv.ReaderBuilder(reader) + if srv.DecorateReader != nil { + reader = srv.DecorateReader(reader) } Redo: req := new(Msg) diff --git a/server_test.go b/server_test.go index dff0fb52..c9853857 100644 --- a/server_test.go +++ b/server_test.go @@ -397,3 +397,54 @@ func TestShutdownUDP(t *testing.T) { t.Errorf("Could not shutdown test UDP server, %v", err) } } + +type ExampleFrameLengthWriter struct { + Writer +} + +func (e *ExampleFrameLengthWriter) Write(m []byte) (int, error) { + fmt.Println("writing DNS data frame of length", len(m)) + return e.Writer.Write(m) +} + +func ExampleDecorateWriter() { + // instrument DNS data frame writing + wf := DecorateWriter(func(w Writer) Writer { + return &ExampleFrameLengthWriter{w} + }) + + // simple UDP server + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + fmt.Println(err.Error()) + return + } + server := &Server{ + PacketConn: pc, + DecorateWriter: wf, + } + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + defer server.Shutdown() + + go func() { + server.ActivateAndServe() + pc.Close() + }() + + waitLock.Lock() + + HandleFunc("miek.nl.", HelloServer) + + c := new(Client) + m := new(Msg) + m.SetQuestion("miek.nl.", TypeTXT) + _, _, err = c.Exchange(m, pc.LocalAddr().String()) + if err != nil { + fmt.Println("failed to exchange", err.Error()) + return + } + // Output: writing DNS data frame of length 56 +} From 8255b4a03f0a389c322d6ba929a59238d94f73c6 Mon Sep 17 00:00:00 2001 From: James DeFelice Date: Thu, 6 Aug 2015 17:55:37 -0400 Subject: [PATCH 3/3] update docs; replace "data frame" refs with "raw message" --- server.go | 12 ++++++------ server_test.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server.go b/server.go index 2ca613a8..6a74ea87 100644 --- a/server.go +++ b/server.go @@ -198,17 +198,17 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { DefaultServeMux.HandleFunc(pattern, handler) } -// Writer writes DNS data frames; each call to Write should send an entire frame. +// Writer writes raw DNS messages; each call to Write should send an entire message. type Writer interface { io.Writer } -// Reader reads DNS data frames; each call to ReadTCP or ReadUDP should return an entire frame. +// Reader reads raw DNS messages; each call to ReadTCP or ReadUDP should return an entire message. type Reader interface { - // ReadTCP reads a data frame from a TCP connection. Implementations may alter + // ReadTCP reads a raw message from a TCP connection. Implementations may alter // connection properties, for example the read-deadline. ReadTCP(conn *net.TCPConn, timeout time.Duration) ([]byte, error) - // ReadUDP reads a data frame from a UDP connection. Implementations may alter + // ReadUDP reads a raw message from a UDP connection. Implementations may alter // connection properties, for example the read-deadline. ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) } @@ -263,9 +263,9 @@ type Server struct { Unsafe bool // If NotifyStartedFunc is set is is called, once the server has started listening. NotifyStartedFunc func() - // DecorateReader is optional, allows customization of the process that reads DNS frames. + // DecorateReader is optional, allows customization of the process that reads raw DNS messages. DecorateReader DecorateReader - // DecorateWriter is optional, allows customization of the process that writes DNS frames. + // DecorateWriter is optional, allows customization of the process that writes raw DNS messages. DecorateWriter DecorateWriter // For graceful shutdown. diff --git a/server_test.go b/server_test.go index c9853857..2ff606ac 100644 --- a/server_test.go +++ b/server_test.go @@ -403,12 +403,12 @@ type ExampleFrameLengthWriter struct { } func (e *ExampleFrameLengthWriter) Write(m []byte) (int, error) { - fmt.Println("writing DNS data frame of length", len(m)) + fmt.Println("writing raw DNS message of length", len(m)) return e.Writer.Write(m) } func ExampleDecorateWriter() { - // instrument DNS data frame writing + // instrument raw DNS message writing wf := DecorateWriter(func(w Writer) Writer { return &ExampleFrameLengthWriter{w} }) @@ -446,5 +446,5 @@ func ExampleDecorateWriter() { fmt.Println("failed to exchange", err.Error()) return } - // Output: writing DNS data frame of length 56 + // Output: writing raw DNS message of length 56 }