[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())
+}