From 6f918ae9d99262ce9af6e2a476984c51fac2fc61 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 00:05:38 +0100 Subject: Implemented basic DHCPv6 server handler --- dhcpv6/server.go | 89 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 19 deletions(-) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index e12136a..786d369 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -6,38 +6,82 @@ import ( "net" ) -type ResponseWriter interface { - LocalAddr() net.Addr - RemoteAddr() net.Addr - WriteMsg(DHCPv6) error - Write([]byte) (int, error) - Close() error +/* + To use the DHCPv6 server code you have to call NewServer with two arguments: + - a handler function, that will be called every time a valid DHCPv6 packet is + received, and + - an address to listen on. + + The handler is a function that takes as input a packet connection, that can be + used to reply to the client; a peer address, that identifies the client sending + the request, and the DHCPv6 packet itself. Just implement your custom logic in + the handler. + + The address to listen on is used to know IP address, port and optionally the + scope to create and UDP6 socket to listen on for DHCPv6 traffic. + + Example program: + + +package main + +import ( + "log" + "net" + + "github.com/insomniacslk/dhcp/dhcpv6" +) + +func handler(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) { + // this function will just print the received DHCPv6 message, without replying + log.Print(m.Summary()) } -type Handler interface { - ServeDHCP(w ResponseWriter, m *DHCPv6) +func main() { + laddr := net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 547, + } + server := dhcpv6.NewServer(laddr, handler) + + if err := server.ActivateAndServe(); err != nil { + log.Fatal(err) + } } +*/ + +type Handler func(conn net.PacketConn, peer net.Addr, m DHCPv6) + type Server struct { - PacketConn net.PacketConn - Handler Handler + conn net.PacketConn + LocalAddr net.UDPAddr + Handler Handler } func (s *Server) ActivateAndServe() error { - if s.PacketConn == nil { - return fmt.Errorf("Error: no packet connection specified") + if s.conn == nil { + conn, err := net.ListenUDP("udp6", &s.LocalAddr) + if err != nil { + return err + } + s.conn = conn } - var pc *net.UDPConn - var ok bool - if pc, ok = s.PacketConn.(*net.UDPConn); !ok { + var ( + pc *net.UDPConn + ok bool + ) + if pc, ok = s.conn.(*net.UDPConn); !ok { return fmt.Errorf("Error: not an UDPConn") } if pc == nil { return fmt.Errorf("ActivateAndServe: Invalid nil PacketConn") } - log.Print("Handling requests") + log.Printf("Server listening on %s", pc.LocalAddr()) + log.Print("Ready to handle requests") for { - rbuf := make([]byte, 1024) // FIXME this is bad + log.Printf("Waiting..") + rbuf := make([]byte, 4096) // FIXME this is bad n, peer, err := pc.ReadFrom(rbuf) if err != nil { log.Printf("Error reading from packet conn: %v", err) @@ -49,8 +93,15 @@ func (s *Server) ActivateAndServe() error { log.Printf("Error parsing DHCPv6 request: %v", err) continue } - log.Print(m.Summary()) - // FIXME use s.Handler + s.Handler(pc, peer, m) } + s.conn.Close() return nil } + +func NewServer(addr net.UDPAddr, handler Handler) *Server { + return &Server{ + LocalAddr: addr, + Handler: handler, + } +} -- cgit v1.2.3 From 0845afe72e425c66b9dd591c375e810cfb85de47 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 00:09:24 +0100 Subject: linter --- dhcpv6/server.go | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index 786d369..b832a8a 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -51,14 +51,18 @@ func main() { */ +// Handler is a type that defines the handler function to be called every time a +// valid DHCPv6 message is received type Handler func(conn net.PacketConn, peer net.Addr, m DHCPv6) +// Server represents a DHCPv6 server object type Server struct { conn net.PacketConn LocalAddr net.UDPAddr Handler Handler } +// ActivateAndServe starts the DHCPv6 server func (s *Server) ActivateAndServe() error { if s.conn == nil { conn, err := net.ListenUDP("udp6", &s.LocalAddr) @@ -99,6 +103,7 @@ func (s *Server) ActivateAndServe() error { return nil } +// NewServer initializes and returns a new Server object func NewServer(addr net.UDPAddr, handler Handler) *Server { return &Server{ LocalAddr: addr, -- cgit v1.2.3 From de8157f8da4ae7516c90d7846cb327daa209f579 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 13:19:11 +0100 Subject: Added client and server constructor tests --- dhcpv6/client_test.go | 14 ++++++++++++++ dhcpv6/server_test.go | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 dhcpv6/client_test.go create mode 100644 dhcpv6/server_test.go (limited to 'dhcpv6') diff --git a/dhcpv6/client_test.go b/dhcpv6/client_test.go new file mode 100644 index 0000000..d2efa1f --- /dev/null +++ b/dhcpv6/client_test.go @@ -0,0 +1,14 @@ +package dhcpv6 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewClient(t *testing.T) { + c := NewClient() + require.NotNil(t, c) + require.Equal(t, DefaultReadTimeout, c.ReadTimeout) + require.Equal(t, DefaultWriteTimeout, c.WriteTimeout) +} diff --git a/dhcpv6/server_test.go b/dhcpv6/server_test.go new file mode 100644 index 0000000..3794a4f --- /dev/null +++ b/dhcpv6/server_test.go @@ -0,0 +1,21 @@ +package dhcpv6 + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewServer(t *testing.T) { + laddr := net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 0, + } + handler := func(conn net.PacketConn, peer net.Addr, m DHCPv6) {} + s := NewServer(laddr, handler) + require.NotNil(t, s) + require.Nil(t, s.conn) + require.Equal(t, laddr, s.LocalAddr) + require.NotNil(t, s.Handler) +} -- cgit v1.2.3 From 11ae3a9e108ac03ff64d2156af4127ed503c28ee Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 14:26:43 +0100 Subject: Async read and tests --- dhcpv6/server.go | 53 ++++++++++++++++++++++++++++++++++++++++++-------- dhcpv6/server_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 9 deletions(-) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index b832a8a..15edd3f 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "time" ) /* @@ -44,8 +45,9 @@ func main() { } server := dhcpv6.NewServer(laddr, handler) + defer server.Close() if err := server.ActivateAndServe(); err != nil { - log.Fatal(err) + log.Panic(err) } } @@ -57,15 +59,25 @@ type Handler func(conn net.PacketConn, peer net.Addr, m DHCPv6) // Server represents a DHCPv6 server object type Server struct { - conn net.PacketConn - LocalAddr net.UDPAddr - Handler Handler + conn net.PacketConn + shouldStop bool + running bool + Handler Handler + localAddr net.UDPAddr +} + +func (s *Server) LocalAddr() net.Addr { + if s.conn == nil { + return nil + } + return s.conn.LocalAddr() } // ActivateAndServe starts the DHCPv6 server func (s *Server) ActivateAndServe() error { + s.shouldStop = false if s.conn == nil { - conn, err := net.ListenUDP("udp6", &s.LocalAddr) + conn, err := net.ListenUDP("udp6", &s.localAddr) if err != nil { return err } @@ -83,12 +95,23 @@ func (s *Server) ActivateAndServe() error { } log.Printf("Server listening on %s", pc.LocalAddr()) log.Print("Ready to handle requests") + s.running = true for { - log.Printf("Waiting..") + if s.shouldStop { + s.running = false + break + } + pc.SetReadDeadline(time.Now().Add(time.Second)) rbuf := make([]byte, 4096) // FIXME this is bad n, peer, err := pc.ReadFrom(rbuf) if err != nil { - log.Printf("Error reading from packet conn: %v", err) + switch err.(type) { + case net.Error: + // silently skip and continue + default: + //complain and continue + log.Printf("Error reading from packet conn: %v", err) + } continue } log.Printf("Handling request from %v", peer) @@ -103,10 +126,24 @@ func (s *Server) ActivateAndServe() error { return nil } +func (s *Server) Close() error { + s.shouldStop = true + for { + if !s.running { + break + } + time.Sleep(100 * time.Millisecond) + } + if s.conn != nil { + return s.conn.Close() + } + return nil +} + // NewServer initializes and returns a new Server object func NewServer(addr net.UDPAddr, handler Handler) *Server { return &Server{ - LocalAddr: addr, + localAddr: addr, Handler: handler, } } diff --git a/dhcpv6/server_test.go b/dhcpv6/server_test.go index 3794a4f..4fc919b 100644 --- a/dhcpv6/server_test.go +++ b/dhcpv6/server_test.go @@ -1,21 +1,73 @@ package dhcpv6 import ( + "log" "net" "testing" + "time" "github.com/stretchr/testify/require" ) +// 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) (*Client, *Server) { + laddr := net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: 0, + Zone: "lo", + } + s := NewServer(laddr, handler) + go s.ActivateAndServe() + + c := NewClient() + c.LocalAddr = &net.UDPAddr{ + IP: net.ParseIP("::1"), + Zone: "lo", + } + for { + if s.LocalAddr() != nil { + break + } + time.Sleep(10 * time.Millisecond) + log.Printf("Waiting for server to run...") + } + c.RemoteAddr = &net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: s.LocalAddr().(*net.UDPAddr).Port, + Zone: "lo", + } + + return c, s +} + func TestNewServer(t *testing.T) { laddr := net.UDPAddr{ IP: net.ParseIP("::1"), Port: 0, + Zone: "lo", } handler := func(conn net.PacketConn, peer net.Addr, m DHCPv6) {} s := NewServer(laddr, handler) + defer s.Close() + require.NotNil(t, s) require.Nil(t, s.conn) - require.Equal(t, laddr, s.LocalAddr) + require.Equal(t, laddr, s.localAddr) require.NotNil(t, s.Handler) } + +func TestServerActivateAndServe(t *testing.T) { + handler := func(conn net.PacketConn, peer net.Addr, m DHCPv6) { + log.Printf("MESSAGE from %s, reply with %v", peer, m.ToBytes()) + if _, err := conn.WriteTo(m.ToBytes(), peer); err != nil { + log.Printf("Cannot reply to client: %v", err) + } + } + c, s := setUpClientAndServer(handler) + defer s.Close() + + _, _, err := c.Solicit("lo", nil) + + require.NoError(t, err) +} -- cgit v1.2.3 From 6f2077f56ae6cbf8a8f69e49049b1a697ff62106 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 14:29:47 +0100 Subject: Linter --- dhcpv6/server.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index 15edd3f..2f3e631 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -66,6 +66,8 @@ type Server struct { localAddr net.UDPAddr } +// LocalAddr returns the local address of the listening socked, or nil if not +// listening func (s *Server) LocalAddr() net.Addr { if s.conn == nil { return nil -- cgit v1.2.3 From bda9d2f5ec68226158387d2bc8ac8f4fde0d1a91 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 14:40:14 +0100 Subject: Proper response handler in test --- dhcpv6/client.go | 7 +++++++ dhcpv6/server_test.go | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'dhcpv6') diff --git a/dhcpv6/client.go b/dhcpv6/client.go index 10a20c9..24b6e0d 100644 --- a/dhcpv6/client.go +++ b/dhcpv6/client.go @@ -127,6 +127,13 @@ func (c *Client) sendReceive(ifname string, packet DHCPv6, expectedType MessageT return nil, err } defer conn.Close() + // wait for the listener to be ready + for { + if conn.LocalAddr() != nil { + break + } + time.Sleep(10 * time.Millisecond) + } // send the packet out conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout)) diff --git a/dhcpv6/server_test.go b/dhcpv6/server_test.go index 4fc919b..066fe3a 100644 --- a/dhcpv6/server_test.go +++ b/dhcpv6/server_test.go @@ -59,8 +59,12 @@ func TestNewServer(t *testing.T) { func TestServerActivateAndServe(t *testing.T) { handler := func(conn net.PacketConn, peer net.Addr, m DHCPv6) { - log.Printf("MESSAGE from %s, reply with %v", peer, m.ToBytes()) - if _, err := conn.WriteTo(m.ToBytes(), peer); err != nil { + adv, err := NewAdvertiseFromSolicit(m) + 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) } } -- cgit v1.2.3 From 8ff474648128703c117aaf5cb1afc73be1295bec Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 14:41:13 +0100 Subject: linter --- dhcpv6/server.go | 1 + 1 file changed, 1 insertion(+) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index 2f3e631..3871387 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -128,6 +128,7 @@ func (s *Server) ActivateAndServe() error { return nil } +// Close sends a termination request to the server, and closes the UDP listener func (s *Server) Close() error { s.shouldStop = true for { -- cgit v1.2.3 From b293b7a48193366eb21f2d3d747ea039f08ff837 Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 15:55:10 +0100 Subject: fix race conditions --- dhcpv6/server.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index 3871387..4dbb0e1 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "sync" "time" ) @@ -60,8 +61,8 @@ type Handler func(conn net.PacketConn, peer net.Addr, m DHCPv6) // Server represents a DHCPv6 server object type Server struct { conn net.PacketConn - shouldStop bool - running bool + connMutex sync.Mutex + shouldStop chan bool Handler Handler localAddr net.UDPAddr } @@ -69,6 +70,8 @@ type Server struct { // LocalAddr returns the local address of the listening socked, or nil if not // listening func (s *Server) LocalAddr() net.Addr { + s.connMutex.Lock() + defer s.connMutex.Unlock() if s.conn == nil { return nil } @@ -77,7 +80,7 @@ func (s *Server) LocalAddr() net.Addr { // ActivateAndServe starts the DHCPv6 server func (s *Server) ActivateAndServe() error { - s.shouldStop = false + s.connMutex.Lock() if s.conn == nil { conn, err := net.ListenUDP("udp6", &s.localAddr) if err != nil { @@ -85,6 +88,7 @@ func (s *Server) ActivateAndServe() error { } s.conn = conn } + s.connMutex.Unlock() var ( pc *net.UDPConn ok bool @@ -97,11 +101,12 @@ func (s *Server) ActivateAndServe() error { } log.Printf("Server listening on %s", pc.LocalAddr()) log.Print("Ready to handle requests") - s.running = true for { - if s.shouldStop { - s.running = false + log.Printf("CHECK") + select { + case <-s.shouldStop: break + case <-time.After(time.Millisecond): } pc.SetReadDeadline(time.Now().Add(time.Second)) rbuf := make([]byte, 4096) // FIXME this is bad @@ -130,13 +135,9 @@ func (s *Server) ActivateAndServe() error { // Close sends a termination request to the server, and closes the UDP listener func (s *Server) Close() error { - s.shouldStop = true - for { - if !s.running { - break - } - time.Sleep(100 * time.Millisecond) - } + s.shouldStop <- true + s.connMutex.Lock() + defer s.connMutex.Unlock() if s.conn != nil { return s.conn.Close() } @@ -146,7 +147,8 @@ func (s *Server) Close() error { // NewServer initializes and returns a new Server object func NewServer(addr net.UDPAddr, handler Handler) *Server { return &Server{ - localAddr: addr, - Handler: handler, + localAddr: addr, + Handler: handler, + shouldStop: make(chan bool, 1), } } -- cgit v1.2.3 From 93e07e83bb197ce356a596dc8e57f134c3ce5fef Mon Sep 17 00:00:00 2001 From: Andrea Barberio Date: Thu, 27 Sep 2018 19:54:19 +0100 Subject: Addressed feedback --- dhcpv6/server.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'dhcpv6') diff --git a/dhcpv6/server.go b/dhcpv6/server.go index 4dbb0e1..3fade87 100644 --- a/dhcpv6/server.go +++ b/dhcpv6/server.go @@ -67,7 +67,7 @@ type Server struct { localAddr net.UDPAddr } -// LocalAddr returns the local address of the listening socked, or nil if not +// LocalAddr returns the local address of the listening socket, or nil if not // listening func (s *Server) LocalAddr() net.Addr { s.connMutex.Lock() @@ -88,6 +88,10 @@ func (s *Server) ActivateAndServe() error { } s.conn = conn } + defer func() { + s.conn.Close() + s.conn = nil + }() s.connMutex.Unlock() var ( pc *net.UDPConn @@ -102,7 +106,6 @@ func (s *Server) ActivateAndServe() error { log.Printf("Server listening on %s", pc.LocalAddr()) log.Print("Ready to handle requests") for { - log.Printf("CHECK") select { case <-s.shouldStop: break @@ -129,7 +132,6 @@ func (s *Server) ActivateAndServe() error { } s.Handler(pc, peer, m) } - s.conn.Close() return nil } -- cgit v1.2.3