Compare commits
	
		
			3 Commits
		
	
	
		
			13677eaf54
			...
			d11855bfd0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | d11855bfd0 | ||
|  | 7d3a40987b | ||
|  | 001517eb15 | 
							
								
								
									
										330
									
								
								cmd/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								cmd/root.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,330 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/binary" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/adrianokf/go-dhcp/pkg/leases" | ||||
| 	"github.com/adrianokf/go-dhcp/pkg/messages" | ||||
| 	"github.com/adrianokf/go-dhcp/pkg/types" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
|  | ||||
| var listenInterface string | ||||
|  | ||||
| var manager = leases.NewLeaseManager() | ||||
|  | ||||
| var magic = [4]byte{0x63, 0x82, 0x53, 0x63} | ||||
|  | ||||
| func parseOptions(data []byte) messages.Options { | ||||
| 	s := zap.S() | ||||
| 	i := 0 | ||||
|  | ||||
| 	options := make(messages.Options) | ||||
|  | ||||
| out: | ||||
| 	for i < len(data) { | ||||
| 		code := messages.OptionCode(data[i]) | ||||
| 		switch code { | ||||
| 		case messages.OptionEnd: | ||||
| 			s.Debug("Found END option at offset ", i) | ||||
| 			break out | ||||
|  | ||||
| 		case messages.OptionPad: | ||||
| 			s.Debug("Found padding option at offset ", i) | ||||
| 			i += 1 | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		size := int(data[i+1]) | ||||
| 		payload := data[i+2 : i+2+size] | ||||
|  | ||||
| 		s.Debugf("code=%d, size=%d, payload=%x", code, size, payload) | ||||
| 		options[code] = messages.Option{Code: code, Data: data[i+1 : i+2+size]} | ||||
| 		i += size + 2 | ||||
| 	} | ||||
|  | ||||
| 	s.Debugf("Parsed options: ", options) | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| func prepareOffer(request messages.DhcpMessage, lease leases.Lease) messages.DhcpMessage { | ||||
| 	var sname [64]byte | ||||
| 	var file [128]byte | ||||
| 	var siaddr types.Ipv4Addr | ||||
| 	copy(sname[:], "go-dhcp-server") | ||||
| 	copy(siaddr[:], net.IPv4(10, 0, 0, 1).To4()) | ||||
|  | ||||
| 	dhcp := messages.DhcpMessage{ | ||||
| 		Op:     messages.BOOTREPLY, | ||||
| 		Htype:  1, // Ethernet | ||||
| 		Hlen:   6, // Ethernet address length | ||||
| 		Hops:   0, | ||||
| 		Secs:   0, | ||||
| 		Flags:  request.Flags, | ||||
| 		Xid:    lease.TransactionId, | ||||
| 		Siaddr: siaddr, | ||||
| 		Ciaddr: [4]byte{0, 0, 0, 0}, | ||||
| 		Yiaddr: lease.Address, | ||||
| 		Giaddr: request.Giaddr, | ||||
| 		Chaddr: request.Chaddr, | ||||
| 		Magic:  magic, | ||||
| 		Sname:  sname, | ||||
| 		File:   file, | ||||
| 	} | ||||
| 	return dhcp | ||||
| } | ||||
|  | ||||
| func prepareAck(request messages.DhcpMessage, lease leases.Lease) messages.DhcpMessage { | ||||
| 	var sname [64]byte | ||||
| 	var file [128]byte | ||||
| 	copy(sname[:], "go-dhcp-server") | ||||
|  | ||||
| 	dhcp := messages.DhcpMessage{ | ||||
| 		Op:     messages.BOOTREPLY, | ||||
| 		Htype:  1, // Ethernet | ||||
| 		Hlen:   6, // Ethernet address length | ||||
| 		Hops:   0, | ||||
| 		Secs:   0, | ||||
| 		Flags:  0, | ||||
| 		Xid:    request.Xid, | ||||
| 		Siaddr: lease.Address, | ||||
| 		Ciaddr: request.Ciaddr, | ||||
| 		Yiaddr: lease.Address, | ||||
| 		Giaddr: request.Giaddr, | ||||
| 		Chaddr: request.Chaddr, | ||||
| 		Magic:  magic, | ||||
| 		Sname:  sname, | ||||
| 		File:   file, | ||||
| 	} | ||||
| 	return dhcp | ||||
| } | ||||
|  | ||||
| // sendMessage transmits a DHCP message with options via a UDP connection | ||||
| // The end option (code 255) is automatically appended and does not need to | ||||
| // be passed explicitly. | ||||
| func sendMessage(conn *net.UDPConn, message messages.DhcpMessage, options []messages.Option) error { | ||||
| 	buf := make([]byte, 0) | ||||
| 	w := bytes.NewBuffer(buf) | ||||
| 	err := binary.Write(w, binary.BigEndian, message) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range options { | ||||
| 		err = w.WriteByte(byte(v.Code)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		_, err = w.Write(v.Data) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Automatically add END option, so the caller doesn't | ||||
| 	// need to specificy it for every invocation. | ||||
| 	err = w.WriteByte(byte(messages.OptionEnd)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	msg := w.Bytes() | ||||
| 	zap.S().Debug("Msg", msg) | ||||
| 	_, err = conn.Write(msg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleOffer(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	lease, err := manager.Request(dhcp.Xid, dhcp.Chaddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	offer := prepareOffer(dhcp, *lease) | ||||
| 	localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") | ||||
| 	clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") | ||||
| 	conn, err := net.DialUDP("udp", localAddr, clientAddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	s.Info("Sending DHCPOFFER...") | ||||
| 	options := []messages.Option{ | ||||
| 		{ | ||||
| 			Code: messages.OptionDHCPMessageType, | ||||
| 			Data: []byte{1, byte(messages.MessageTypeOffer)}, | ||||
| 		}, | ||||
| 	} | ||||
| 	sendMessage(conn, offer, options) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleAck(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	lease, err := manager.Lookup(dhcp.Xid) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	ack := prepareAck(dhcp, *lease) | ||||
|  | ||||
| 	options := []messages.Option{ | ||||
| 		{ | ||||
| 			Code: messages.OptionDHCPMessageType, | ||||
| 			Data: []byte{1, byte(messages.MessageTypeAck)}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Code: messages.OptionIPAddressLeaseTime, | ||||
| 			Data: append([]byte{4}, u32tob(3600)...), | ||||
| 		}, | ||||
| 	} | ||||
| 	s.Debug("Options: ", options) | ||||
|  | ||||
| 	localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") | ||||
| 	clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") | ||||
| 	conn, err := net.DialUDP("udp", localAddr, clientAddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	s.Info("Sending DHCPACK") | ||||
| 	sendMessage(conn, ack, options) | ||||
|  | ||||
| 	lease, err = manager.Request(dhcp.Xid, dhcp.Chaddr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	s.Debug("Found lease", lease) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleMsg(data []byte, remote *net.UDPAddr) { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	s.Debugf("Connection from client %v", remote.IP) | ||||
|  | ||||
| 	var dhcp messages.DhcpMessage | ||||
| 	reader := bytes.NewReader(data) | ||||
| 	binary.Read(reader, binary.BigEndian, &dhcp) | ||||
| 	dhcp.Debug(s) | ||||
|  | ||||
| 	if dhcp.Magic != [4]byte{0x63, 0x82, 0x53, 0x63} { | ||||
| 		panic("Invalid DHCP magic field") | ||||
| 	} | ||||
|  | ||||
| 	optDataOffset := int(unsafe.Sizeof(dhcp)) | ||||
| 	optData := data[optDataOffset:] | ||||
| 	s.Debug("Raw options data:", optData) | ||||
|  | ||||
| 	options := parseOptions(optData) | ||||
| 	dhcpMsgType := options[messages.OptionDHCPMessageType] | ||||
| 	s.Info("DHCP message type ", dhcpMsgType) | ||||
|  | ||||
| 	switch messages.MessageType(dhcpMsgType.Data[1]) { | ||||
| 	case messages.MessageTypeDiscover: | ||||
| 		go handleOffer(dhcp, remote) | ||||
|  | ||||
| 	case messages.MessageTypeRequest: | ||||
| 		go handleAck(dhcp, remote) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func runServer(interfaceName string) { | ||||
| 	var addr *net.UDPAddr | ||||
| 	if interfaceName == "all" { | ||||
| 		zap.L().Debug("Listening on all interfaces") | ||||
| 		addr, _ = net.ResolveUDPAddr("udp4", ":67") | ||||
| 	} else { | ||||
| 		zap.S().Debugf("Listening on interface %s", interfaceName) | ||||
| 		iface, err := net.InterfaceByName(interfaceName) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		addrs, err := iface.Addrs() | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		// Find first IPv4 address associated with the interface | ||||
| 		var ip net.IP = nil | ||||
| 		for _, ifaddr := range addrs { | ||||
| 			switch a := ifaddr.(type) { | ||||
| 			case *net.IPAddr: | ||||
| 				ip = a.IP | ||||
| 			case *net.IPNet: | ||||
| 				ip = a.IP | ||||
| 			} | ||||
|  | ||||
| 			ip = ip.To4() | ||||
| 			if ip != nil { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if ip == nil { | ||||
| 			zap.S().Panicf("No IPv4 address associated with interface %s", interfaceName) | ||||
| 		} | ||||
| 		fmt.Printf("%+v\n", ip) | ||||
| 		addr, _ = net.ResolveUDPAddr("udp4", ip.String()+":67") | ||||
| 	} | ||||
| 	conn, err := net.ListenUDP("udp4", addr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	zap.S().Infof("Listening for incoming connections on %s", addr.String()) | ||||
|  | ||||
| 	for { | ||||
| 		buf := make([]byte, 1024) | ||||
| 		rlen, remote, err := conn.ReadFrom(buf[:]) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		// Do stuff with the read bytes | ||||
| 		remoteAddr, ok := remote.(*net.UDPAddr) | ||||
| 		if !ok { | ||||
| 			zap.S().Warn("Not a valid remote IP address: ", remote) | ||||
| 			continue | ||||
| 		} | ||||
| 		go handleMsg(buf[0:rlen], remoteAddr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var rootCmd = &cobra.Command{ | ||||
| 	Use:   "go-dhcp", | ||||
| 	Short: "go-dhcp is a simple DHCP server written in Go", | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		runServer(listenInterface) | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	// Set up logging | ||||
| 	logger, _ := zap.NewDevelopment() | ||||
| 	defer logger.Sync() // flushes buffer, if any | ||||
| 	zap.ReplaceGlobals(logger) | ||||
|  | ||||
| 	rootCmd.PersistentFlags().StringVarP(&listenInterface, "interface", "i", "all", "Interface to listen on") | ||||
| } | ||||
|  | ||||
| func Execute() { | ||||
| 	if err := rootCmd.Execute(); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
| @@ -1,16 +1,9 @@ | ||||
| package main | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/binary" | ||||
| 	"net" | ||||
| ) | ||||
| 
 | ||||
| func int2ip(nn uint32) net.IP { | ||||
| 	ip := make(net.IP, 4) | ||||
| 	binary.BigEndian.PutUint32(ip, nn) | ||||
| 	return ip | ||||
| } | ||||
| 
 | ||||
| func u32tob(u uint32) []byte { | ||||
| 	buf := make([]byte, 4) | ||||
| 	binary.BigEndian.PutUint32(buf, u) | ||||
							
								
								
									
										12
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.mod
									
									
									
									
									
								
							| @@ -2,9 +2,15 @@ module github.com/adrianokf/go-dhcp | ||||
|  | ||||
| go 1.17 | ||||
|  | ||||
| require go.uber.org/zap v1.21.0 | ||||
| require go.uber.org/zap v1.26.0 | ||||
|  | ||||
| require ( | ||||
| 	go.uber.org/atomic v1.7.0 // indirect | ||||
| 	go.uber.org/multierr v1.6.0 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/spf13/cobra v1.8.0 | ||||
| 	go.uber.org/atomic v1.11.0 // indirect | ||||
| 	go.uber.org/multierr v1.11.0 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										17
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,8 +1,11 @@ | ||||
| github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= | ||||
| github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||
| github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| @@ -10,19 +13,32 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= | ||||
| github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= | ||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | ||||
| github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | ||||
| go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= | ||||
| go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||
| go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= | ||||
| go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= | ||||
| go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= | ||||
| go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= | ||||
| go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= | ||||
| go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= | ||||
| go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= | ||||
| go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= | ||||
| go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= | ||||
| go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= | ||||
| go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= | ||||
| go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= | ||||
| go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| @@ -55,3 +71,4 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|   | ||||
							
								
								
									
										278
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										278
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,283 +1,9 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/binary" | ||||
| 	"net" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"go.uber.org/zap" | ||||
| 	"github.com/adrianokf/go-dhcp/cmd" | ||||
| ) | ||||
|  | ||||
| var manager = NewLeaseManager() | ||||
|  | ||||
| var leases []Lease = make([]Lease, 0) | ||||
|  | ||||
| func (dhcp DhcpMessage) Debug(s *zap.SugaredLogger) { | ||||
| 	s.Debugf("op=%x, htype=%x, hlen=%x, hops=%x", dhcp.Op, dhcp.Htype, dhcp.Hlen, dhcp.Hops) | ||||
| 	s.Debugf("xid=%x", dhcp.Xid) | ||||
| 	s.Debugf("secs=%d, flags=%x", dhcp.Secs, dhcp.Flags) | ||||
|  | ||||
| 	s.Debugf("ciaddr=%x", dhcp.Ciaddr) | ||||
| 	s.Debugf("siaddr=%x", dhcp.Siaddr) | ||||
| 	s.Debugf("giaddr=%x", dhcp.Giaddr) | ||||
| 	s.Debugf("chaddr=%x", dhcp.Chaddr) | ||||
|  | ||||
| 	s.Debugf("sname=%s", string(dhcp.Sname[:])) | ||||
| 	s.Debugf("file=%s", string(dhcp.File[:])) | ||||
| 	s.Debugf("magic=%x", dhcp.Magic) | ||||
| } | ||||
|  | ||||
| func parseOptions(data []byte) Options { | ||||
| 	s := zap.S() | ||||
| 	i := 0 | ||||
|  | ||||
| 	options := make(Options) | ||||
|  | ||||
| out: | ||||
| 	for i < len(data) { | ||||
| 		code := OptionCode(data[i]) | ||||
| 		switch code { | ||||
| 		case OptionEnd: | ||||
| 			s.Debug("Found END option at offset ", i) | ||||
| 			break out | ||||
|  | ||||
| 		case OptionPad: | ||||
| 			s.Debug("Found padding option at offset ", i) | ||||
| 			i += 1 | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		size := int(data[i+1]) | ||||
| 		payload := data[i+2 : i+2+size] | ||||
|  | ||||
| 		s.Debugf("code=%d, size=%d, payload=%x", code, size, payload) | ||||
| 		options[code] = Option{Code: code, Data: data[i+1 : i+2+size]} | ||||
| 		i += size + 2 | ||||
| 	} | ||||
|  | ||||
| 	s.Debugf("Parsed options: ", options) | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| func prepareOffer(request DhcpMessage, lease Lease) DhcpMessage { | ||||
| 	var sname [64]byte | ||||
| 	var file [128]byte | ||||
| 	var siaddr Ipv4Addr | ||||
| 	copy(sname[:], "go-dhcp-server") | ||||
| 	copy(siaddr[:], net.IPv4(10, 0, 0, 1).To4()) | ||||
|  | ||||
| 	dhcp := DhcpMessage{ | ||||
| 		Op:     BOOTREPLY, | ||||
| 		Htype:  1, // Ethernet | ||||
| 		Hlen:   6, // Ethernet address length | ||||
| 		Hops:   0, | ||||
| 		Secs:   0, | ||||
| 		Flags:  request.Flags, | ||||
| 		Xid:    lease.TransactionId, | ||||
| 		Siaddr: siaddr, | ||||
| 		Ciaddr: [4]byte{0, 0, 0, 0}, | ||||
| 		Yiaddr: lease.Address, | ||||
| 		Giaddr: request.Giaddr, | ||||
| 		Chaddr: request.Chaddr, | ||||
| 		Magic:  magic, | ||||
| 		Sname:  sname, | ||||
| 		File:   file, | ||||
| 	} | ||||
| 	return dhcp | ||||
| } | ||||
|  | ||||
| func prepareAck(request DhcpMessage, lease Lease) DhcpMessage { | ||||
| 	var sname [64]byte | ||||
| 	var file [128]byte | ||||
| 	copy(sname[:], "go-dhcp-server") | ||||
|  | ||||
| 	dhcp := DhcpMessage{ | ||||
| 		Op:     BOOTREPLY, | ||||
| 		Htype:  1, // Ethernet | ||||
| 		Hlen:   6, // Ethernet address length | ||||
| 		Hops:   0, | ||||
| 		Secs:   0, | ||||
| 		Flags:  0, | ||||
| 		Xid:    request.Xid, | ||||
| 		Siaddr: lease.Address, | ||||
| 		Ciaddr: request.Ciaddr, | ||||
| 		Yiaddr: lease.Address, | ||||
| 		Giaddr: request.Giaddr, | ||||
| 		Chaddr: request.Chaddr, | ||||
| 		Magic:  magic, | ||||
| 		Sname:  sname, | ||||
| 		File:   file, | ||||
| 	} | ||||
| 	return dhcp | ||||
| } | ||||
|  | ||||
| // sendMessage transmits a DHCP message with options via a UDP connection | ||||
| // The end option (code 255) is automatically appended and does not need to | ||||
| // be passed explicitly. | ||||
| func sendMessage(conn *net.UDPConn, message DhcpMessage, options []Option) error { | ||||
| 	buf := make([]byte, 0) | ||||
| 	w := bytes.NewBuffer(buf) | ||||
| 	err := binary.Write(w, binary.BigEndian, message) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range options { | ||||
| 		err = w.WriteByte(byte(v.Code)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		_, err = w.Write(v.Data) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Automatically add END option, so the caller doesn't | ||||
| 	// need to specificy it for every invocation. | ||||
| 	err = w.WriteByte(byte(OptionEnd)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	msg := w.Bytes() | ||||
| 	zap.S().Debug("Msg", msg) | ||||
| 	_, err = conn.Write(msg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleOffer(dhcp DhcpMessage, remote *net.UDPAddr) error { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	lease, err := manager.Request(dhcp.Xid, dhcp.Chaddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	offer := prepareOffer(dhcp, *lease) | ||||
| 	localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") | ||||
| 	clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") | ||||
| 	conn, err := net.DialUDP("udp", localAddr, clientAddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	s.Info("Sending DHCPOFFER...") | ||||
| 	options := []Option{ | ||||
| 		{ | ||||
| 			Code: OptionDHCPMessageType, | ||||
| 			Data: []byte{1, byte(MessageTypeOffer)}, | ||||
| 		}, | ||||
| 	} | ||||
| 	sendMessage(conn, offer, options) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleAck(dhcp DhcpMessage, remote *net.UDPAddr) error { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	lease, err := manager.Lookup(dhcp.Xid) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	ack := prepareAck(dhcp, *lease) | ||||
|  | ||||
| 	options := []Option{ | ||||
| 		{ | ||||
| 			Code: OptionDHCPMessageType, | ||||
| 			Data: []byte{1, byte(MessageTypeAck)}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Code: OptionIPAddressLeaseTime, | ||||
| 			Data: append([]byte{4}, u32tob(3600)...), | ||||
| 		}, | ||||
| 	} | ||||
| 	s.Debug("Options: ", options) | ||||
|  | ||||
| 	localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") | ||||
| 	clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") | ||||
| 	conn, err := net.DialUDP("udp", localAddr, clientAddr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	s.Info("Sending DHCPACK") | ||||
| 	sendMessage(conn, ack, options) | ||||
|  | ||||
| 	lease, err = manager.Request(dhcp.Xid, dhcp.Chaddr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	s.Debug("Found lease", lease) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func handleMsg(data []byte, remote *net.UDPAddr) { | ||||
| 	s := zap.S() | ||||
|  | ||||
| 	s.Debugf("Connection from client %v", remote.IP) | ||||
|  | ||||
| 	var dhcp DhcpMessage | ||||
| 	reader := bytes.NewReader(data) | ||||
| 	binary.Read(reader, binary.BigEndian, &dhcp) | ||||
| 	dhcp.Debug(s) | ||||
|  | ||||
| 	if dhcp.Magic != [4]byte{0x63, 0x82, 0x53, 0x63} { | ||||
| 		panic("Invalid DHCP magic field") | ||||
| 	} | ||||
|  | ||||
| 	optDataOffset := int(unsafe.Sizeof(dhcp)) | ||||
| 	optData := data[optDataOffset:] | ||||
| 	s.Debug("Raw options data:", optData) | ||||
|  | ||||
| 	options := parseOptions(optData) | ||||
| 	dhcpMsgType := options[OptionDHCPMessageType] | ||||
| 	s.Info("DHCP message type ", dhcpMsgType) | ||||
|  | ||||
| 	switch MessageType(dhcpMsgType.Data[1]) { | ||||
| 	case MessageTypeDiscover: | ||||
| 		go handleOffer(dhcp, remote) | ||||
|  | ||||
| 	case MessageTypeRequest: | ||||
| 		go handleAck(dhcp, remote) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	// Set up logging | ||||
| 	logger, _ := zap.NewDevelopment() | ||||
| 	defer logger.Sync() // flushes buffer, if any | ||||
| 	zap.ReplaceGlobals(logger) | ||||
|  | ||||
| 	addr, _ := net.ResolveUDPAddr("udp4", ":67") | ||||
| 	conn, err := net.ListenUDP("udp4", addr) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	zap.L().Info("Listening for incoming connections") | ||||
|  | ||||
| 	for { | ||||
| 		buf := make([]byte, 1024) | ||||
| 		rlen, remote, err := conn.ReadFrom(buf[:]) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		// Do stuff with the read bytes | ||||
| 		remoteAddr, ok := remote.(*net.UDPAddr) | ||||
| 		if !ok { | ||||
| 			zap.S().Warn("Not a valid remote IP address: ", remote) | ||||
| 			continue | ||||
| 		} | ||||
| 		go handleMsg(buf[0:rlen], remoteAddr) | ||||
| 	} | ||||
| 	cmd.Execute() | ||||
| } | ||||
|   | ||||
| @@ -1,32 +1,48 @@ | ||||
| package main | ||||
| package leases | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/adrianokf/go-dhcp/pkg/types" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| type LeaseState uint8 | ||||
| 
 | ||||
| const ( | ||||
| 	Offered LeaseState = iota | ||||
| 	Requested | ||||
| ) | ||||
| 
 | ||||
| type Lease struct { | ||||
| 	TransactionId types.TxId | ||||
| 	TTL           time.Time | ||||
| 	State         LeaseState | ||||
| 	ClientAddr    types.HwAddr | ||||
| 	Address       types.Ipv4Addr | ||||
| } | ||||
| 
 | ||||
| type ILeaseManager interface { | ||||
| 	Request(xid TxId, clientAddr HwAddr) (*Lease, error) | ||||
| 	Request(xid types.TxId, clientAddr types.HwAddr) (*Lease, error) | ||||
| 	Release(l Lease) error | ||||
| 	Lookup(xid TxId) error | ||||
| 	Lookup(xid types.TxId) error | ||||
| } | ||||
| 
 | ||||
| type LeaseManager struct { | ||||
| 	leases map[TxId]Lease | ||||
| 	leases map[types.TxId]Lease | ||||
| 	count  byte | ||||
| } | ||||
| 
 | ||||
| func NewLeaseManager() *LeaseManager { | ||||
| 	m := &LeaseManager{ | ||||
| 		leases: make(map[TxId]Lease), | ||||
| 		leases: make(map[types.TxId]Lease), | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
| 
 | ||||
| func (m *LeaseManager) Request(xid TxId, clientAddr HwAddr) (*Lease, error) { | ||||
| func (m *LeaseManager) Request(xid types.TxId, clientAddr types.HwAddr) (*Lease, error) { | ||||
| 	zap.S().Debugf("LeaseManager.Request(%v, %v)", xid, clientAddr) | ||||
| 
 | ||||
| 	if m.count > 254 { | ||||
| @@ -58,7 +74,7 @@ func (m LeaseManager) Release(l Lease) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (m LeaseManager) Lookup(xid TxId) (*Lease, error) { | ||||
| func (m LeaseManager) Lookup(xid types.TxId) (*Lease, error) { | ||||
| 	lease, found := m.leases[xid] | ||||
| 	if found { | ||||
| 		return &lease, nil | ||||
| @@ -1,9 +1,15 @@ | ||||
| package main | ||||
| package messages | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 	"github.com/adrianokf/go-dhcp/pkg/types" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| type Message interface { | ||||
| 	Marshal() ([]byte, error) | ||||
| 	Unmarshal([]byte) (Message, error) | ||||
| } | ||||
| 
 | ||||
| type OptionCode byte | ||||
| 
 | ||||
| // DHCPv4 Options | ||||
| @@ -195,40 +201,35 @@ const ( | ||||
| 	MessageTypeInform | ||||
| ) | ||||
| 
 | ||||
| type LeaseState uint8 | ||||
| type TxId [4]byte | ||||
| type Ipv4Addr [4]byte | ||||
| type HwAddr [16]byte | ||||
| 
 | ||||
| var magic = [4]byte{0x63, 0x82, 0x53, 0x63} | ||||
| 
 | ||||
| type DhcpMessage struct { | ||||
| 	Op     DhcpOp | ||||
| 	Htype  MessageType | ||||
| 	Hlen   byte | ||||
| 	Hops   byte | ||||
| 	Xid    TxId | ||||
| 	Xid    types.TxId | ||||
| 	Secs   uint16 | ||||
| 	Flags  uint16 | ||||
| 	Ciaddr Ipv4Addr | ||||
| 	Yiaddr Ipv4Addr | ||||
| 	Siaddr Ipv4Addr | ||||
| 	Giaddr Ipv4Addr | ||||
| 	Chaddr HwAddr | ||||
| 	Ciaddr types.Ipv4Addr | ||||
| 	Yiaddr types.Ipv4Addr | ||||
| 	Siaddr types.Ipv4Addr | ||||
| 	Giaddr types.Ipv4Addr | ||||
| 	Chaddr types.HwAddr | ||||
| 	Sname  [64]byte | ||||
| 	File   [128]byte | ||||
| 	Magic  [4]byte | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	Offered LeaseState = iota | ||||
| 	Requested | ||||
| ) | ||||
| func (dhcp DhcpMessage) Debug(s *zap.SugaredLogger) { | ||||
| 	s.Debugf("op=%x, htype=%x, hlen=%x, hops=%x", dhcp.Op, dhcp.Htype, dhcp.Hlen, dhcp.Hops) | ||||
| 	s.Debugf("xid=%x", dhcp.Xid) | ||||
| 	s.Debugf("secs=%d, flags=%x", dhcp.Secs, dhcp.Flags) | ||||
| 
 | ||||
| type Lease struct { | ||||
| 	TransactionId TxId | ||||
| 	TTL           time.Time | ||||
| 	State         LeaseState | ||||
| 	ClientAddr    HwAddr | ||||
| 	Address       Ipv4Addr | ||||
| 	s.Debugf("ciaddr=%x", dhcp.Ciaddr) | ||||
| 	s.Debugf("siaddr=%x", dhcp.Siaddr) | ||||
| 	s.Debugf("giaddr=%x", dhcp.Giaddr) | ||||
| 	s.Debugf("chaddr=%x", dhcp.Chaddr) | ||||
| 
 | ||||
| 	s.Debugf("sname=%s", string(dhcp.Sname[:])) | ||||
| 	s.Debugf("file=%s", string(dhcp.File[:])) | ||||
| 	s.Debugf("magic=%x", dhcp.Magic) | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package main | ||||
| package messages | ||||
| 
 | ||||
| type Option struct { | ||||
| 	Code OptionCode | ||||
							
								
								
									
										5
									
								
								pkg/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								pkg/types/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| package types | ||||
|  | ||||
| type TxId [4]byte | ||||
| type Ipv4Addr [4]byte | ||||
| type HwAddr [16]byte | ||||
		Reference in New Issue
	
	Block a user