[VOL-3610] implementing a fake DHCP server within BBSim
Change-Id: If291a0ca7f78909c3713ef0e6831e381304fc2c9
diff --git a/internal/bbsim/responders/dhcp/dhcp.go b/internal/bbsim/responders/dhcp/dhcp.go
index f3c7e72..850d160 100644
--- a/internal/bbsim/responders/dhcp/dhcp.go
+++ b/internal/bbsim/responders/dhcp/dhcp.go
@@ -158,7 +158,7 @@
return &dhcpLayer
}
-func serializeDHCPPacket(intfId uint32, onuId uint32, cTag int, srcMac net.HardwareAddr, dhcp *layers.DHCPv4, pbit uint8) ([]byte, error) {
+func serializeDHCPPacket(cTag int, srcMac net.HardwareAddr, dhcp *layers.DHCPv4, pbit uint8) (gopacket.Packet, error) {
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
@@ -200,7 +200,7 @@
return nil, err
}
- return gopacket.Payload(taggedPkt.Data()), nil
+ return taggedPkt, nil
}
func GetDhcpLayer(pkt gopacket.Packet) (*layers.DHCPv4, error) {
@@ -277,7 +277,7 @@
cTag int, gemPortId uint32, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr,
offeredIp net.IP, pbit uint8, stream bbsim.Stream) error {
dhcp := createDHCPReq(ponPortId, onuId, onuHwAddress, offeredIp, gemPortId)
- pkt, err := serializeDHCPPacket(ponPortId, onuId, cTag, onuHwAddress, dhcp, pbit)
+ pkt, err := serializeDHCPPacket(cTag, onuHwAddress, dhcp, pbit)
if err != nil {
dhcpLogger.WithFields(log.Fields{
@@ -297,7 +297,7 @@
msg := bbsim.ByteMsg{
IntfId: ponPortId,
OnuId: onuId,
- Bytes: pkt,
+ Bytes: pkt.Data(),
}
if err := sendDHCPPktIn(msg, portNo, gemPortId, stream); err != nil {
@@ -339,7 +339,7 @@
pbit uint8, stream bbsim.Stream) error {
dhcp := createDHCPDisc(ponPortId, onuId, gemPortId, onuHwAddress)
- pkt, err := serializeDHCPPacket(ponPortId, onuId, cTag, onuHwAddress, dhcp, pbit)
+ pkt, err := serializeDHCPPacket(cTag, onuHwAddress, dhcp, pbit)
if err != nil {
dhcpLogger.WithFields(log.Fields{
@@ -359,7 +359,7 @@
msg := bbsim.ByteMsg{
IntfId: ponPortId,
OnuId: onuId,
- Bytes: pkt,
+ Bytes: pkt.Data(),
}
if err := sendDHCPPktIn(msg, portNo, gemPortId, stream); err != nil {
@@ -574,6 +574,7 @@
"Type": dhcpType,
"error": err,
}).Error("Failed to send DHCP packet out of the NNI Port")
+ return err
}
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
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())
+}
diff --git a/internal/bbsim/responders/dhcp/dhcp_server_test.go b/internal/bbsim/responders/dhcp/dhcp_server_test.go
new file mode 100644
index 0000000..fa4a58d
--- /dev/null
+++ b/internal/bbsim/responders/dhcp/dhcp_server_test.go
@@ -0,0 +1,35 @@
+/*
+ * 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 (
+ "gotest.tools/assert"
+ "net"
+ "testing"
+)
+
+func TestCreateIpFromMacAddress(t *testing.T) {
+ dhcpServer := NewDHCPServer()
+
+ mac1 := net.HardwareAddr{0x2e, 0x60, 0x00, 0x0c, 0x0f, 0x02}
+ ip1 := dhcpServer.createIpFromMacAddress(mac1)
+ assert.Equal(t, "10.12.15.2", ip1.String())
+
+ mac2 := net.HardwareAddr{0x2e, 0x60, 0x00, 0x00, 0x00, 0x00}
+ ip2 := dhcpServer.createIpFromMacAddress(mac2)
+ assert.Equal(t, "10.0.0.0", ip2.String())
+}