summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/server4/server.go
blob: 4e6796f781140b3c739e5fb687201b221496c775 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Package server4 is a basic, extensible DHCPv4 server.
//
// To use the DHCPv4 server code you have to call NewServer with two arguments:
//  - an interface to listen on,
//  - an address to listen on, and
//  - a handler function, that will be called every time a valid DHCPv4 packet is
//    received.
//
// 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.
//
// 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.
//
// Optionally, NewServer can receive options that will modify the server
// object. Some options already exist, for example WithConn. If this option is
// passed with a valid connection, the listening address argument is ignored.
//
// Example program:
//
//	package main
//
//	import (
//		"log"
//		"net"
//
//		"github.com/insomniacslk/dhcp/dhcpv4"
//		"github.com/insomniacslk/dhcp/dhcpv4/server4"
//	)
//
//	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, err := server4.NewServer("eth0", &laddr, handler)
//		if err != nil {
//			log.Fatal(err)
//		}
//
//		// This never returns. If you want to do other stuff, dump it into a
//		// goroutine.
//		server.Serve()
//	}
//
package server4

import (
	"log"
	"net"
	"os"

	"github.com/insomniacslk/dhcp/dhcpv4"
)

// 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.DHCPv4)

// Server represents a DHCPv4 server object
type Server struct {
	conn    net.PacketConn
	Handler Handler
	logger  Logger
}

// Serve serves requests.
func (s *Server) Serve() error {
	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 {
			s.logger.Printf("Error reading from packet conn: %v", err)
			return err
		}
		s.logger.Printf("Handling request from %v", peer)

		m, err := dhcpv4.FromBytes(rbuf[:n])
		if err != nil {
			s.logger.Printf("Error parsing DHCPv4 request: %v", err)
			continue
		}

		upeer, ok := peer.(*net.UDPAddr)
		if !ok {
			s.logger.Printf("Not a UDP connection? Peer is %s", peer)
			continue
		}
		// Set peer to broadcast if the client did not have an IP.
		if upeer.IP == nil || upeer.IP.To4().Equal(net.IPv4zero) {
			upeer = &net.UDPAddr{
				IP:   net.IPv4bcast,
				Port: upeer.Port,
			}
		}
		go s.Handler(s.conn, upeer, m)
	}
}

// Close sends a termination request to the server, and closes the UDP listener.
func (s *Server) Close() error {
	return s.conn.Close()
}

// ServerOpt adds optional configuration to a server.
type ServerOpt func(s *Server)

// WithConn configures the server with the given connection.
func WithConn(c net.PacketConn) ServerOpt {
	return func(s *Server) {
		s.conn = c
	}
}

// NewServer initializes and returns a new Server object
func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerOpt) (*Server, error) {
	s := &Server{
		Handler: handler,
		logger:  EmptyLogger{},
	}

	for _, o := range opt {
		o(s)
	}
	if s.conn == nil {
		var err error
		conn, err := NewIPv4UDPConn(ifname, addr)
		if err != nil {
			return nil, err
		}
		s.conn = conn
	}
	return s, nil
}

// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
func WithSummaryLogger() ServerOpt {
	return func(s *Server) {
		s.logger = ShortSummaryLogger{
			Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
		}
	}
}

// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
func WithDebugLogger() ServerOpt {
	return func(s *Server) {
		s.logger = DebugLogger{
			Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
		}
	}
}

// WithLogger set the logger (see interface Logger).
func WithLogger(newLogger Logger) ServerOpt {
	return func(s *Server) {
		s.logger = newLogger
	}
}