Skip to content

Commit

Permalink
[WIP] Added DHCPv4 server (#178)
Browse files Browse the repository at this point in the history
* Added DHCPv4 server
* Added more modifiers
* Fixed some bugs
  • Loading branch information
insomniacslk committed Nov 6, 2018
1 parent df1628d commit c572359
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 10 deletions.
9 changes: 9 additions & 0 deletions .travis/tests.sh
Expand Up @@ -12,6 +12,15 @@ for d in $(go list ./... | grep -v vendor); do
cat profile.out >> coverage.txt
rm profile.out
fi
# integration tests
go test -c -tags=integration -race -coverprofile=profile.out -covermode=atomic $d
testbin="./$(basename $d).test"
# only run it if it was built - i.e. if there are integ tests
test -x "${testbin}" && sudo "./${testbin}"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done

# check that we are not breaking some projects that depend on us. Remove this after moving to
Expand Down
14 changes: 7 additions & 7 deletions dhcpv4/client.go
Expand Up @@ -3,12 +3,12 @@ package dhcpv4
import (
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"time"
"fmt"
"reflect"
"log"
"time"

"golang.org/x/net/ipv4"
"golang.org/x/sys/unix"
Expand All @@ -35,8 +35,8 @@ var (
// addresses.
type Client struct {
ReadTimeout, WriteTimeout time.Duration
RemoteAddr net.Addr
LocalAddr net.Addr
RemoteAddr net.Addr
LocalAddr net.Addr
}

// NewClient generates a new client to perform a DHCP exchange with, setting the
Expand Down Expand Up @@ -275,7 +275,7 @@ func (c *Client) sendReceive(sendFd, recvFd int, packet *DHCPv4, messageType Mes
recvErrors := make(chan error, 1)
go func(errs chan<- error) {
conn, innerErr := net.FileConn(os.NewFile(uintptr(recvFd), ""))
if err != nil {
if innerErr != nil {
errs <- innerErr
return
}
Expand All @@ -291,7 +291,7 @@ func (c *Client) sendReceive(sendFd, recvFd int, packet *DHCPv4, messageType Mes
}

response, innerErr = FromBytes(buf[:n])
if err != nil {
if innerErr != nil {
errs <- innerErr
return
}
Expand Down
4 changes: 2 additions & 2 deletions dhcpv4/dhcpv4.go
Expand Up @@ -623,8 +623,8 @@ func (d *DHCPv4) MessageType() *MessageType {
}

func (d *DHCPv4) String() string {
return fmt.Sprintf("DHCPv4(opcode=%v hwtype=%v hwaddr=%v)",
d.OpcodeToString(), d.HwTypeToString(), d.ClientHwAddr())
return fmt.Sprintf("DHCPv4(opcode=%v xid=%d hwtype=%v hwaddr=%v)",
d.OpcodeToString(), d.TransactionID(), d.HwTypeToString(), d.ClientHwAddr())
}

// Summary prints detailed information about the packet.
Expand Down
36 changes: 36 additions & 0 deletions dhcpv4/modifiers.go
Expand Up @@ -4,6 +4,42 @@ import (
"net"
)

// WithTransactionID sets the Transaction ID for the DHCPv4 packet
func WithTransactionID(xid uint32) Modifier {
return func(d *DHCPv4) *DHCPv4 {
d.SetTransactionID(xid)
return d
}
}

// WithBroadcast sets the packet to be broadcast or unicast
func WithBroadcast(broadcast bool) Modifier {
return func(d *DHCPv4) *DHCPv4 {
if broadcast {
d.SetBroadcast()
} else {
d.SetUnicast()
}
return d
}
}

// WithHwAddr sets the hardware address for a packet
func WithHwAddr(hwaddr []byte) Modifier {
return func(d *DHCPv4) *DHCPv4 {
d.SetClientHwAddr(hwaddr)
return d
}
}

// WithOption appends a DHCPv4 option provided by the user
func WithOption(opt Option) Modifier {
return func(d *DHCPv4) *DHCPv4 {
d.AddOption(opt)
return d
}
}

// WithUserClass adds a user class option to the packet.
// The rfc parameter allows you to specify if the userclass should be
// rfc compliant or not. More details in issue #113
Expand Down
39 changes: 38 additions & 1 deletion dhcpv4/modifiers_test.go
Expand Up @@ -7,8 +7,45 @@ import (
"github.com/stretchr/testify/require"
)

func TestTransactionIDModifier(t *testing.T) {
d, err := New()
require.NoError(t, err)
d = WithTransactionID(0xddccbbaa)(d)
require.Equal(t, uint32(0xddccbbaa), d.TransactionID())
}

func TestBroadcastModifier(t *testing.T) {
d, err := New()
require.NoError(t, err)
// set and test broadcast
d = WithBroadcast(true)(d)
require.Equal(t, true, d.IsBroadcast())
// set and test unicast
d = WithBroadcast(false)(d)
require.Equal(t, true, d.IsUnicast())
}

func TestHwAddrModifier(t *testing.T) {
d, err := New()
require.NoError(t, err)
hwaddr := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}
d = WithHwAddr(hwaddr[:])(d)
require.Equal(t, hwaddr, d.ClientHwAddr())
}

func TestWithOptionModifier(t *testing.T) {
d, err := New()
require.NoError(t, err)
d = WithOption(&OptDomainName{DomainName: "slackware.it"})(d)
opt := d.GetOneOption(OptionDomainName)
require.NotNil(t, opt)
dnOpt := opt.(*OptDomainName)
require.Equal(t, "slackware.it", dnOpt.DomainName)
}

func TestUserClassModifier(t *testing.T) {
d, _ := New()
d, err := New()
require.NoError(t, err)
userClass := WithUserClass([]byte("linuxboot"), false)
d = userClass(d)
expected := []byte{
Expand Down
154 changes: 154 additions & 0 deletions dhcpv4/server.go
@@ -0,0 +1,154 @@
package dhcpv4

import (
"fmt"
"log"
"net"
"sync"
"time"
)

/*
To use the DHCPv4 server code you have to call NewServer with two arguments:
- a handler function, that will be called every time a valid DHCPv4 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 DHCPv4 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 UDP socket to listen on for DHCPv4 traffic.
Example program:
package main
import (
"log"
"net"
"github.com/insomniacslk/dhcp/dhcpv4"
)
func handler(conn net.PacketConn, peer net.Addr, m dhcpv4.DHCPv4) {
// this function will just print the received DHCPv4 message, without replying
log.Print(m.Summary())
}
func main() {
laddr := net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 67,
}
server := dhcpv4.NewServer(laddr, handler)
defer server.Close()
if err := server.ActivateAndServe(); err != nil {
log.Panic(err)
}
}
*/

// Handler is a type that defines the handler function to be called every time a
// valid DHCPv4 message is received
type Handler func(conn net.PacketConn, peer net.Addr, m *DHCPv4)

// Server represents a DHCPv4 server object
type Server struct {
conn net.PacketConn
connMutex sync.Mutex
shouldStop chan bool
Handler Handler
localAddr net.UDPAddr
}

// LocalAddr returns the local address of the listening socket, or nil if not
// listening
func (s *Server) LocalAddr() net.Addr {
s.connMutex.Lock()
defer s.connMutex.Unlock()
if s.conn == nil {
return nil
}
return s.conn.LocalAddr()
}

// ActivateAndServe starts the DHCPv4 server
func (s *Server) ActivateAndServe() error {
s.connMutex.Lock()
if s.conn == nil {
conn, err := net.ListenUDP("udp4", &s.localAddr)
if err != nil {
s.connMutex.Unlock()
return err
}
s.conn = conn
}
s.connMutex.Unlock()
defer s.Close()
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.Printf("Server listening on %s", pc.LocalAddr())
log.Print("Ready to handle requests")
for {
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
n, peer, err := pc.ReadFrom(rbuf)
if err != nil {
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)
m, err := FromBytes(rbuf[:n])
if err != nil {
log.Printf("Error parsing DHCPv4 request: %v", err)
continue
}
s.Handler(pc, peer, m)
}
return nil
}

// Close sends a termination request to the server, and closes the UDP listener
func (s *Server) Close() error {
s.shouldStop <- true
s.connMutex.Lock()
defer s.connMutex.Unlock()
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,
Handler: handler,
shouldStop: make(chan bool, 1),
}
}

0 comments on commit c572359

Please sign in to comment.