[VOL-3610] implementing a fake DHCP server within BBSim

Change-Id: If291a0ca7f78909c3713ef0e6831e381304fc2c9
diff --git a/internal/bbsim/responders/dhcp/dhcp_server.go b/internal/bbsim/responders/dhcp/dhcp_server.go
new file mode 100644
index 0000000..7e7f361
--- /dev/null
+++ b/internal/bbsim/responders/dhcp/dhcp_server.go
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dhcp
+
+import (
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
+	log "github.com/sirupsen/logrus"
+	"net"
+)
+
+type DHCPServerIf interface {
+	HandleServerPacket(pkt gopacket.Packet) (gopacket.Packet, error)
+}
+
+type DHCPServer struct {
+	DHCPServerMacAddress net.HardwareAddr
+}
+
+func NewDHCPServer() *DHCPServer {
+	return &DHCPServer{
+		// NOTE we may need to make this configurable in case we'll need multiple servers
+		DHCPServerMacAddress: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
+	}
+}
+
+func (s *DHCPServer) getClientMacAddress(pkt gopacket.Packet) (net.HardwareAddr, error) {
+	dhcpLayer, err := GetDhcpLayer(pkt)
+	if err != nil {
+		return nil, err
+	}
+	return dhcpLayer.ClientHWAddr, nil
+}
+
+func (s *DHCPServer) getTxId(pkt gopacket.Packet) (uint32, error) {
+	dhcpLayer, err := GetDhcpLayer(pkt)
+	if err != nil {
+		return 0, err
+	}
+	return dhcpLayer.Xid, nil
+}
+
+func (s *DHCPServer) getPacketHostName(pkt gopacket.Packet) ([]byte, error) {
+	dhcpLayer, err := GetDhcpLayer(pkt)
+	if err != nil {
+		return nil, err
+	}
+	for _, option := range dhcpLayer.Options {
+		if option.Type == layers.DHCPOptHostname {
+			return option.Data, nil
+		}
+	}
+	return nil, errors.New("hostname-not-found")
+}
+
+func (s *DHCPServer) getOption82(pkt gopacket.Packet) ([]byte, error) {
+	dhcpLayer, err := GetDhcpLayer(pkt)
+	if err != nil {
+		return nil, err
+	}
+	for _, option := range dhcpLayer.Options {
+		if option.Type == 82 {
+			return option.Data, nil
+		}
+	}
+	log.WithFields(log.Fields{
+		"pkt": hex.EncodeToString(pkt.Data()),
+	}).Debug("option82-not-found")
+	return []byte{}, nil
+}
+
+func (s *DHCPServer) createDefaultDhcpReply(xid uint32, mac net.HardwareAddr) layers.DHCPv4 {
+	clientIp := s.createIpFromMacAddress(mac)
+	return layers.DHCPv4{
+		Operation:    layers.DHCPOpReply,
+		HardwareType: layers.LinkTypeEthernet,
+		HardwareLen:  6,
+		HardwareOpts: 0,
+		Xid:          xid,
+		ClientHWAddr: mac,
+		ClientIP:     clientIp,
+		YourClientIP: clientIp,
+	}
+}
+
+func (s *DHCPServer) createIpFromMacAddress(mac net.HardwareAddr) net.IP {
+	ip := []byte{}
+	for i := 2; i < 6; i++ {
+		ip = append(ip, mac[i])
+	}
+	return net.IPv4(10+ip[0], ip[1], ip[2], ip[3])
+}
+
+func (s *DHCPServer) serializeServerDHCPPacket(clientMac net.HardwareAddr, dhcpLayer *layers.DHCPv4) (gopacket.Packet, error) {
+	buffer := gopacket.NewSerializeBuffer()
+
+	options := gopacket.SerializeOptions{
+		ComputeChecksums: true,
+		FixLengths:       true,
+	}
+
+	ethernetLayer := &layers.Ethernet{
+		SrcMAC:       s.DHCPServerMacAddress,
+		DstMAC:       clientMac,
+		EthernetType: layers.EthernetTypeIPv4,
+	}
+
+	ipLayer := &layers.IPv4{
+		Version:  4,
+		TOS:      0x10,
+		TTL:      128,
+		SrcIP:    []byte{0, 0, 0, 0},
+		DstIP:    []byte{255, 255, 255, 255},
+		Protocol: layers.IPProtocolUDP,
+	}
+
+	udpLayer := &layers.UDP{
+		SrcPort: 67,
+		DstPort: 68,
+	}
+
+	_ = udpLayer.SetNetworkLayerForChecksum(ipLayer)
+	if err := gopacket.SerializeLayers(buffer, options, ethernetLayer, ipLayer, udpLayer, dhcpLayer); err != nil {
+		dhcpLogger.Error("SerializeLayers")
+		return nil, err
+	}
+
+	return gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, gopacket.Default), nil
+
+}
+
+func (s *DHCPServer) getDefaultDhcpServerOptions(hostname []byte, option82 []byte) []layers.DHCPOption {
+	defaultOpts := []layers.DHCPOption{}
+	defaultOpts = append(defaultOpts, layers.DHCPOption{
+		Type:   layers.DHCPOptHostname,
+		Data:   hostname,
+		Length: uint8(len(hostname)),
+	})
+
+	defaultOpts = append(defaultOpts, layers.DHCPOption{
+		Type:   82,
+		Data:   option82,
+		Length: uint8(len(option82)),
+	})
+
+	return defaultOpts
+}
+
+// get a Discover packet an return a valid Offer
+func (s *DHCPServer) handleDiscover(pkt gopacket.Packet) (gopacket.Packet, error) {
+
+	sTag, cTag, err := packetHandlers.GetTagsFromPacket(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	clientMac, err := s.getClientMacAddress(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	txId, err := s.getTxId(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	hostname, err := s.getPacketHostName(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	option82, err := s.getOption82(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	dhcpLogger.WithFields(log.Fields{
+		"sTag":      sTag,
+		"cTag":      cTag,
+		"clientMac": clientMac,
+		"txId":      txId,
+		"hostname":  string(hostname),
+		"option82":  string(option82),
+	}).Debug("Handling DHCP Discovery packet")
+
+	dhcpLayer := s.createDefaultDhcpReply(txId, clientMac)
+	defaultOpts := s.getDefaultDhcpServerOptions(hostname, option82)
+
+	dhcpLayer.Options = append([]layers.DHCPOption{{
+		Type:   layers.DHCPOptMessageType,
+		Data:   []byte{byte(layers.DHCPMsgTypeOffer)},
+		Length: 1,
+	}}, defaultOpts...)
+
+	data := []byte{01}
+	data = append(data, clientMac...)
+	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+		Type:   layers.DHCPOptClientID,
+		Data:   data,
+		Length: uint8(len(data)),
+	})
+
+	// serialize the packet
+	responsePkt, err := s.serializeServerDHCPPacket(clientMac, &dhcpLayer)
+	if err != nil {
+		return nil, err
+	}
+
+	taggedResponsePkt, err := packetHandlers.PushDoubleTag(int(sTag), int(cTag), responsePkt, 0)
+	if err != nil {
+		return nil, err
+	}
+	return taggedResponsePkt, nil
+}
+
+func (s *DHCPServer) handleRequest(pkt gopacket.Packet) (gopacket.Packet, error) {
+	sTag, cTag, err := packetHandlers.GetTagsFromPacket(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	clientMac, err := s.getClientMacAddress(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	txId, err := s.getTxId(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	hostname, err := s.getPacketHostName(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	option82, err := s.getOption82(pkt)
+	if err != nil {
+		return nil, err
+	}
+
+	dhcpLogger.WithFields(log.Fields{
+		"sTag":      sTag,
+		"cTag":      cTag,
+		"clientMac": clientMac,
+		"txId":      txId,
+		"hostname":  string(hostname),
+		"option82":  string(option82),
+	}).Debug("Handling DHCP Request packet")
+
+	dhcpLayer := s.createDefaultDhcpReply(txId, clientMac)
+	defaultOpts := s.getDefaultDhcpServerOptions(hostname, option82)
+
+	dhcpLayer.Options = append([]layers.DHCPOption{{
+		Type:   layers.DHCPOptMessageType,
+		Data:   []byte{byte(layers.DHCPMsgTypeAck)},
+		Length: 1,
+	}}, defaultOpts...)
+
+	// TODO can we move this in getDefaultDhcpServerOptions?
+	data := []byte{01}
+	data = append(data, clientMac...)
+	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+		Type:   layers.DHCPOptClientID,
+		Data:   data,
+		Length: uint8(len(data)),
+	})
+
+	// serialize the packet
+	responsePkt, err := s.serializeServerDHCPPacket(clientMac, &dhcpLayer)
+	if err != nil {
+		return nil, err
+	}
+
+	taggedResponsePkt, err := packetHandlers.PushDoubleTag(int(sTag), int(cTag), responsePkt, 0)
+	if err != nil {
+		return nil, err
+	}
+	return taggedResponsePkt, nil
+}
+
+// HandleServerPacket is a very simple implementation of a DHCP server
+// that only replies to DHCPDiscover and DHCPRequest packets
+func (s DHCPServer) HandleServerPacket(pkt gopacket.Packet) (gopacket.Packet, error) {
+	dhcpLayer, _ := GetDhcpLayer(pkt)
+
+	if dhcpLayer.Operation == layers.DHCPOpReply {
+		dhcpLogger.WithFields(log.Fields{
+			"pkt": hex.EncodeToString(pkt.Data()),
+		}).Error("Received DHCP Reply on the server. Ignoring the packet but this is a serious error.")
+	}
+
+	dhcpMessageType, _ := GetDhcpMessageType(dhcpLayer)
+
+	switch dhcpMessageType {
+	case layers.DHCPMsgTypeDiscover:
+		dhcpLogger.Info("Received DHCP Discover")
+		return s.handleDiscover(pkt)
+	case layers.DHCPMsgTypeRequest:
+		dhcpLogger.Info("Received DHCP Request")
+		return s.handleRequest(pkt)
+	}
+	return nil, fmt.Errorf("cannot-handle-dhcp-packet-of-type-%s", dhcpMessageType.String())
+}