From 0444670a63335cc631222e99f59f4c7bfdff83bd Mon Sep 17 00:00:00 2001 From: Valerio Santinelli Date: Wed, 11 Mar 2020 15:58:27 +0100 Subject: Added support for a custom logger when instantiating the server4 or server6 object Signed-off-by: Valerio Santinelli --- dhcpv6/server6/logger.go | 63 +++++++++++++++++++ dhcpv6/server6/logger_test.go | 42 +++++++++++++ dhcpv6/server6/server.go | 38 ++++++++++-- dhcpv6/server6/server_test.go | 139 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 275 insertions(+), 7 deletions(-) create mode 100644 dhcpv6/server6/logger.go create mode 100644 dhcpv6/server6/logger_test.go (limited to 'dhcpv6') diff --git a/dhcpv6/server6/logger.go b/dhcpv6/server6/logger.go new file mode 100644 index 0000000..54538b3 --- /dev/null +++ b/dhcpv6/server6/logger.go @@ -0,0 +1,63 @@ +package server6 + +import ( + "github.com/insomniacslk/dhcp/dhcpv6" +) + +// Logger is a handler which will be used to output logging messages +type Logger interface { + // PrintMessage print _all_ DHCP messages + PrintMessage(prefix string, message *dhcpv6.Message) + + // Printf is use to print the rest debugging information + Printf(format string, v ...interface{}) +} + +// EmptyLogger prints nothing +type EmptyLogger struct{} + +// Printf is just a dummy function that does nothing +func (e EmptyLogger) Printf(format string, v ...interface{}) {} + +// PrintMessage is just a dummy function that does nothing +func (e EmptyLogger) PrintMessage(prefix string, message *dhcpv6.Message) {} + +// Printfer is used for actual output of the logger. For example *log.Logger is a Printfer. +type Printfer interface { + // Printf is the function for logging output. Arguments are handled in the manner of fmt.Printf. + Printf(format string, v ...interface{}) +} + +// ShortSummaryLogger is a wrapper for Printfer to implement interface Logger. +// DHCP messages are printed in the short format. +type ShortSummaryLogger struct { + // Printfer is used for actual output of the logger + Printfer +} + +// Printf prints a log message as-is via predefined Printfer +func (s ShortSummaryLogger) Printf(format string, v ...interface{}) { + s.Printfer.Printf(format, v...) +} + +// PrintMessage prints a DHCP message in the short format via predefined Printfer +func (s ShortSummaryLogger) PrintMessage(prefix string, message *dhcpv6.Message) { + s.Printf("%s: %s", prefix, message) +} + +// DebugLogger is a wrapper for Printfer to implement interface Logger. +// DHCP messages are printed in the long format. +type DebugLogger struct { + // Printfer is used for actual output of the logger + Printfer +} + +// Printf prints a log message as-is via predefined Printfer +func (d DebugLogger) Printf(format string, v ...interface{}) { + d.Printfer.Printf(format, v...) +} + +// PrintMessage prints a DHCP message in the long format via predefined Printfer +func (d DebugLogger) PrintMessage(prefix string, message *dhcpv6.Message) { + d.Printf("%s: %s", prefix, message.Summary()) +} diff --git a/dhcpv6/server6/logger_test.go b/dhcpv6/server6/logger_test.go new file mode 100644 index 0000000..b31a5e6 --- /dev/null +++ b/dhcpv6/server6/logger_test.go @@ -0,0 +1,42 @@ +// +build go1.12 + +package server6 + +import( + "log" + "os" + "testing" + + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/stretchr/testify/require" +) + +func TestEmptyLogger(t *testing.T) { + l := EmptyLogger{} + msg, err := dhcpv6.NewMessage() + require.Nil(t, err) + l.Printf("test") + l.PrintMessage("prefix", msg) +} + +func TestShortSummaryLogger(t *testing.T) { + l := ShortSummaryLogger{ + Printfer: log.New(os.Stderr, "[dhcpv6] ", log.LstdFlags), + } + msg, err := dhcpv6.NewMessage() + require.Nil(t, err) + require.NotNil(t, msg) + l.Printf("test") + l.PrintMessage("prefix", msg) +} + +func TestDebugLogger(t *testing.T) { + l := DebugLogger{ + Printfer: log.New(os.Stderr, "[dhcpv6] ", log.LstdFlags), + } + msg, err := dhcpv6.NewMessage() + require.Nil(t, err) + require.NotNil(t, msg) + l.Printf("test") + l.PrintMessage("prefix", msg) +} \ No newline at end of file diff --git a/dhcpv6/server6/server.go b/dhcpv6/server6/server.go index 1f4d8a0..058b0af 100644 --- a/dhcpv6/server6/server.go +++ b/dhcpv6/server6/server.go @@ -56,6 +56,7 @@ func main() { import ( "log" "net" + "os" "github.com/insomniacslk/dhcp/dhcpv6" "golang.org/x/net/ipv6" @@ -69,27 +70,28 @@ type Handler func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) type Server struct { conn net.PacketConn handler Handler + logger Logger } // Serve starts the DHCPv6 server. The listener will run in background, and can // be interrupted with `Server.Close`. func (s *Server) Serve() error { - log.Printf("Server listening on %s", s.conn.LocalAddr()) - log.Print("Ready to handle requests") + s.logger.Printf("Server listening on %s", s.conn.LocalAddr()) + s.logger.Printf("Ready to handle requests") defer s.Close() for { rbuf := make([]byte, 4096) // FIXME this is bad n, peer, err := s.conn.ReadFrom(rbuf) if err != nil { - log.Printf("Error reading from packet conn: %v", err) + s.logger.Printf("Error reading from packet conn: %v", err) return err } - log.Printf("Handling request from %v", peer) + s.logger.Printf("Handling request from %v", peer) d, err := dhcpv6.FromBytes(rbuf[:n]) if err != nil { - log.Printf("Error parsing DHCPv6 request: %v", err) + s.logger.Printf("Error parsing DHCPv6 request: %v", err) continue } @@ -125,6 +127,7 @@ func WithConn(conn net.PacketConn) ServerOpt { func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerOpt) (*Server, error) { s := &Server{ handler: handler, + logger: EmptyLogger{}, } for _, o := range opt { @@ -181,3 +184,28 @@ func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerO return s, nil } + +// WithSummaryLogger logs one-line DHCPv6 message summaries when sent & received. +func WithSummaryLogger() ServerOpt { + return func(s *Server) { + s.logger = ShortSummaryLogger{ + Printfer: log.New(os.Stderr, "[dhcpv6] ", log.LstdFlags), + } + } +} + +// WithDebugLogger logs multi-line full DHCPv6 messages when sent & received. +func WithDebugLogger() ServerOpt { + return func(s *Server) { + s.logger = DebugLogger{ + Printfer: log.New(os.Stderr, "[dhcpv6] ", log.LstdFlags), + } + } +} + +// WithLogger set the logger (see interface Logger). +func WithLogger(newLogger Logger) ServerOpt { + return func(s *Server) { + s.logger = newLogger + } +} diff --git a/dhcpv6/server6/server_test.go b/dhcpv6/server6/server_test.go index 09cde4c..f7f7008 100644 --- a/dhcpv6/server6/server_test.go +++ b/dhcpv6/server6/server_test.go @@ -4,7 +4,9 @@ import ( "context" "log" "net" + "sync" "testing" + "time" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6/nclient6" @@ -28,7 +30,7 @@ func (f unconnectedConn) ReadFrom(b []byte) (int, net.Addr, error) { // utility function to set up a client and a server instance and run it in // background. The caller needs to call Server.Close() once finished. -func setUpClientAndServer(handler Handler) (*nclient6.Client, *Server) { +func setUpClientAndServer(handler Handler, logger *customLogger) (*nclient6.Client, *Server) { laddr := &net.UDPAddr{ IP: net.ParseIP("::1"), Port: 0, @@ -37,6 +39,11 @@ func setUpClientAndServer(handler Handler) (*nclient6.Client, *Server) { if err != nil { panic(err) } + + if logger != nil { + s.logger = logger + } + go func() { _ = s.Serve() }() @@ -53,6 +60,30 @@ func setUpClientAndServer(handler Handler) (*nclient6.Client, *Server) { return c, s } +type customLogger struct { + tb testing.TB + called bool + mux sync.Mutex +} + +func (s *customLogger) Printf(format string, v ...interface{}) { + s.mux.Lock() + s.called = true + s.mux.Unlock() + s.tb.Logf("===CustomLogger BEGIN===") + s.tb.Logf(format, v...) + s.tb.Logf("===CustomLogger END===") +} + +func (s *customLogger) PrintMessage(prefix string, message *dhcpv6.Message) { + s.mux.Lock() + s.called = true + s.mux.Unlock() + s.tb.Logf("===CustomLogger BEGIN===") + s.tb.Logf("%s: %s", prefix, message) + s.tb.Logf("===CustomLogger END===") +} + func TestServer(t *testing.T) { handler := func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { msg := m.(*dhcpv6.Message) @@ -66,7 +97,7 @@ func TestServer(t *testing.T) { } } - c, s := setUpClientAndServer(handler) + c, s := setUpClientAndServer(handler, nil) defer s.Close() ifaces, err := interfaces.GetLoopbackInterfaces() @@ -76,3 +107,107 @@ func TestServer(t *testing.T) { _, err = c.Solicit(context.Background(), dhcpv6.WithRapidCommit) require.NoError(t, err) } + +func TestCustomLoggerForServer(t *testing.T) { + handler := func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { + msg := m.(*dhcpv6.Message) + adv, err := dhcpv6.NewAdvertiseFromSolicit(msg) + if err != nil { + log.Printf("NewAdvertiseFromSolicit failed: %v", err) + return + } + if _, err := conn.WriteTo(adv.ToBytes(), peer); err != nil { + log.Printf("Cannot reply to client: %v", err) + } + } + + c, s := setUpClientAndServer(handler, &customLogger{ + tb: t, + }) + defer s.Close() + + ifaces, err := interfaces.GetLoopbackInterfaces() + require.NoError(t, err) + require.NotEqual(t, 0, len(ifaces)) + + _, err = c.Solicit(context.Background(), dhcpv6.WithRapidCommit) + require.NoError(t, err) + go func() { + time.Sleep(time.Second * 5) + require.Equal(t, true, s.logger.(*customLogger).called) + }() +} + +func TestServerInstantiationWithCustomLogger(t *testing.T) { + handler := func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { + msg := m.(*dhcpv6.Message) + adv, err := dhcpv6.NewAdvertiseFromSolicit(msg) + if err != nil { + log.Printf("NewAdvertiseFromSolicit failed: %v", err) + return + } + if _, err := conn.WriteTo(adv.ToBytes(), peer); err != nil { + log.Printf("Cannot reply to client: %v", err) + } + } + + laddr := &net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 0, + } + s, err := NewServer("", laddr, handler, WithLogger(&customLogger{ + tb: t, + })) + if err != nil { + t.Fatal(err) + } + require.NotNil(t, s) +} + +func TestServerInstantiationWithSummaryLogger(t *testing.T) { + handler := func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { + msg := m.(*dhcpv6.Message) + adv, err := dhcpv6.NewAdvertiseFromSolicit(msg) + if err != nil { + log.Printf("NewAdvertiseFromSolicit failed: %v", err) + return + } + if _, err := conn.WriteTo(adv.ToBytes(), peer); err != nil { + log.Printf("Cannot reply to client: %v", err) + } + } + + laddr := &net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 0, + } + s, err := NewServer("", laddr, handler, WithSummaryLogger()) + if err != nil { + t.Fatal(err) + } + require.NotNil(t, s) +} + +func TestServerInstantiationWithDebugLogger(t *testing.T) { + handler := func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { + msg := m.(*dhcpv6.Message) + adv, err := dhcpv6.NewAdvertiseFromSolicit(msg) + if err != nil { + log.Printf("NewAdvertiseFromSolicit failed: %v", err) + return + } + if _, err := conn.WriteTo(adv.ToBytes(), peer); err != nil { + log.Printf("Cannot reply to client: %v", err) + } + } + + laddr := &net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 0, + } + s, err := NewServer("", laddr, handler, WithDebugLogger()) + if err != nil { + t.Fatal(err) + } + require.NotNil(t, s) +} -- cgit v1.2.3