[VOL-3610] implementing a fake DHCP server within BBSim
Change-Id: If291a0ca7f78909c3713ef0e6831e381304fc2c9
diff --git a/internal/bbsim/devices/nni.go b/internal/bbsim/devices/nni.go
index fe092e8..32fffaa 100644
--- a/internal/bbsim/devices/nni.go
+++ b/internal/bbsim/devices/nni.go
@@ -17,68 +17,42 @@
package devices
import (
- "bytes"
"encoding/hex"
- "os/exec"
-
"github.com/google/gopacket"
- "github.com/google/gopacket/pcap"
"github.com/looplab/fsm"
"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
- "github.com/opencord/bbsim/internal/bbsim/types"
+ "github.com/opencord/voltha-protos/v4/go/openolt"
log "github.com/sirupsen/logrus"
)
-var (
- nniLogger = log.WithFields(log.Fields{"module": "NNI"})
- dhcpServerIp = "192.168.254.1"
-)
-
-type Executor interface {
- Command(name string, arg ...string) Runnable
-}
-
-type DefaultExecutor struct{}
-
-func (d DefaultExecutor) Command(name string, arg ...string) Runnable {
- return exec.Command(name, arg...)
-}
-
-type Runnable interface {
- Run() error
-}
-
-var executor = DefaultExecutor{}
+var nniLogger = log.WithFields(log.Fields{"module": "NNI"})
type NniPort struct {
// BBSIM Internals
- ID uint32
- nniVeth string
- upstreamVeth string
- PacketCount uint64
+ ID uint32
+ Olt *OltDevice
// PON Attributes
- OperState *fsm.FSM
- Type string
+ OperState *fsm.FSM
+ Type string
+ PacketCount uint64 // dummy value for the stats
}
func CreateNNI(olt *OltDevice) (NniPort, error) {
nniPort := NniPort{
- ID: uint32(0),
- nniVeth: "nni",
- upstreamVeth: "upstream",
+ ID: uint32(0),
OperState: getOperStateFSM(func(e *fsm.Event) {
oltLogger.Debugf("Changing NNI OperState from %s to %s", e.Src, e.Dst)
}),
Type: "nni",
+ Olt: olt,
}
- _ = createNNIPair(executor, olt, &nniPort)
+
return nniPort, nil
}
-// sendNniPacket will send a packet out of the NNI interface.
-// We will send upstream only DHCP packets and drop anything else
-func (n *NniPort) sendNniPacket(packet gopacket.Packet) error {
+// handleNniPacket will send a packet to a fake DHCP server implementation
+func (n *NniPort) handleNniPacket(packet gopacket.Packet) error {
isDhcp := packetHandlers.IsDhcpPacket(packet)
isLldp := packetHandlers.IsLldpPacket(packet)
@@ -90,152 +64,33 @@
}
if isDhcp {
- packet, err := packetHandlers.PopDoubleTag(packet)
+
+ // get a response packet from the DHCP server
+ pkt, err := n.Olt.dhcpServer.HandleServerPacket(packet)
if err != nil {
nniLogger.WithFields(log.Fields{
- "packet": packet,
- }).Errorf("Can't remove double tags from packet: %v", err)
+ "SourcePkt": hex.EncodeToString(packet.Data()),
+ "Err": err,
+ }).Error("DHCP Server can't handle packet")
return err
}
- handle, err := getVethHandler(n.nniVeth)
- if err != nil {
+ // send packetIndication to VOLTHA
+ data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
+ IntfType: "nni",
+ IntfId: n.ID,
+ Pkt: pkt.Data()}}
+ if err := n.Olt.OpenoltStream.Send(&openolt.Indication{Data: data}); err != nil {
+ oltLogger.WithFields(log.Fields{
+ "IntfType": data.PktInd.IntfType,
+ "IntfId": n.ID,
+ "Pkt": hex.EncodeToString(pkt.Data()),
+ }).Errorf("Fail to send PktInd indication: %v", err)
return err
}
-
- err = handle.WritePacketData(packet.Data())
- if err != nil {
- nniLogger.WithFields(log.Fields{
- "packet": packet,
- }).Errorf("Failed to send packet out of the NNI: %s", err)
- return err
- }
-
- nniLogger.WithFields(log.Fields{
- "packet": hex.EncodeToString(packet.Data()),
- }).Trace("Sent packet out of NNI")
} else if isLldp {
// TODO rework this when BBSim supports data-plane packets
nniLogger.Trace("Received LLDP Packet, ignoring it")
}
return nil
}
-
-//createNNIBridge will create a veth bridge to fake the connection between the NNI port
-//and something upstream, in this case a DHCP server.
-//It is also responsible to start the DHCP server itself
-func createNNIPair(executor Executor, olt *OltDevice, nniPort *NniPort) error {
-
- if err := executor.Command("ip", "link", "add", nniPort.nniVeth, "type", "veth", "peer", "name", nniPort.upstreamVeth).Run(); err != nil {
- nniLogger.Errorf("Couldn't create veth pair between %s and %s", nniPort.nniVeth, nniPort.upstreamVeth)
- return err
- }
-
- if err := setVethUp(executor, nniPort.nniVeth); err != nil {
- return err
- }
-
- if err := setVethUp(executor, nniPort.upstreamVeth); err != nil {
- return err
- }
-
- // TODO should be moved out of this function in case there are multiple NNI interfaces.
- // Only one DHCP server should be running and listening on all NNI interfaces
- if err := startDHCPServer(nniPort.upstreamVeth, dhcpServerIp); err != nil {
- return err
- }
-
- return nil
-}
-
-// NewVethChan returns a new channel for receiving packets over the NNI interface
-func (n *NniPort) NewVethChan() (chan *types.PacketMsg, *pcap.Handle, error) {
- ch, handle, err := listenOnVeth(n.nniVeth)
- if err != nil {
- return nil, nil, err
- }
- return ch, handle, err
-}
-
-// setVethUp is responsible to activate a virtual interface
-func setVethUp(executor Executor, vethName string) error {
- if err := executor.Command("ip", "link", "set", vethName, "up").Run(); err != nil {
- nniLogger.Errorf("Couldn't change interface %s state to up: %v", vethName, err)
- return err
- }
- return nil
-}
-
-var startDHCPServer = func(upstreamVeth string, dhcpServerIp string) error {
- // TODO the DHCP server should support multiple interfaces
- if err := exec.Command("ip", "addr", "add", dhcpServerIp, "dev", upstreamVeth).Run(); err != nil {
- nniLogger.Errorf("Couldn't assing ip %s to interface %s: %v", dhcpServerIp, upstreamVeth, err)
- return err
- }
-
- if err := setVethUp(executor, upstreamVeth); err != nil {
- return err
- }
-
- dhcp := "/usr/local/bin/dhcpd"
- conf := "/etc/dhcp/dhcpd.conf" // copied in the container from configs/dhcpd.conf
- logfile := "/tmp/dhcplog"
- var stderr bytes.Buffer
- cmd := exec.Command(dhcp, "-cf", conf, upstreamVeth, "-tf", logfile, "-4")
- cmd.Stderr = &stderr
- err := cmd.Run()
- if err != nil {
- nniLogger.Errorf("Fail to start DHCP Server: %s, %s", err, stderr.String())
- return err
- }
- nniLogger.Info("Successfully activated DHCP Server")
- return nil
-}
-
-func getVethHandler(vethName string) (*pcap.Handle, error) {
- var (
- device = vethName
- snapshotLen int32 = 1518
- promiscuous = false
- timeout = pcap.BlockForever
- )
- handle, err := pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
- if err != nil {
- nniLogger.Errorf("Can't retrieve handler for interface %s", vethName)
- return nil, err
- }
- return handle, nil
-}
-
-var listenOnVeth = func(vethName string) (chan *types.PacketMsg, *pcap.Handle, error) {
-
- handle, err := getVethHandler(vethName)
- if err != nil {
- return nil, nil, err
- }
-
- channel := make(chan *types.PacketMsg, 1024)
-
- go func() {
- nniLogger.Info("Start listening on NNI for packets")
- packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
- for packet := range packetSource.Packets() {
-
- if !packetHandlers.IsIncomingPacket(packet) {
- nniLogger.Tracef("Ignoring packet as it's going out")
- continue
- }
-
- nniLogger.WithFields(log.Fields{
- "packet": packet.Dump(),
- }).Tracef("Received packet on NNI Port")
- pkt := types.PacketMsg{
- Pkt: packet,
- }
- channel <- &pkt
- }
- nniLogger.Info("Stop listening on NNI for packets")
- }()
-
- return channel, handle, nil
-}
diff --git a/internal/bbsim/devices/nni_test.go b/internal/bbsim/devices/nni_test.go
index 9ee945e..4f93bb8 100644
--- a/internal/bbsim/devices/nni_test.go
+++ b/internal/bbsim/devices/nni_test.go
@@ -19,88 +19,82 @@
import (
"errors"
- "github.com/google/gopacket/pcap"
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ "github.com/opencord/voltha-protos/v4/go/openolt"
+ "github.com/stretchr/testify/assert"
"testing"
-
- "github.com/opencord/bbsim/internal/bbsim/types"
- "gotest.tools/assert"
)
-func TestSetVethUpSuccess(t *testing.T) {
- spy := &ExecutorSpy{
- Calls: make(map[int][]string),
+func TestCreateNNI(t *testing.T) {
+ olt := OltDevice{
+ ID: 0,
}
- err := setVethUp(spy, "test_veth")
- assert.Equal(t, spy.CommandCallCount, 1)
- assert.Equal(t, spy.Calls[1][2], "test_veth")
- assert.Equal(t, err, nil)
+ nni, err := CreateNNI(&olt)
+
+ assert.Nil(t, err)
+ assert.Equal(t, "nni", nni.Type)
+ assert.Equal(t, uint32(0), nni.ID)
+ assert.Equal(t, "down", nni.OperState.Current())
}
-func TestSetVethUpFail(t *testing.T) {
- spy := &ExecutorSpy{
- failRun: true,
- Calls: make(map[int][]string),
+func TestSendNniPacket(t *testing.T) {
+
+ stream := &mockStream{
+ CallCount: 0,
+ Calls: make(map[int]*openolt.Indication),
+ fail: false,
+ channel: make(chan int, 10),
}
- err := setVethUp(spy, "test_veth")
- assert.Equal(t, spy.CommandCallCount, 1)
- assert.Equal(t, err.Error(), "fake-error")
+
+ dhcpServer := &mockDhcpServer{
+ callCount: 0,
+ fail: false,
+ }
+
+ nni := NniPort{
+ Olt: &OltDevice{
+ OpenoltStream: stream,
+ dhcpServer: dhcpServer,
+ },
+ ID: 12,
+ }
+
+ // the DHCP server is mocked, so we don't really care about the packet we send in
+ pkt := createTestDhcpPacket(t)
+ err := nni.handleNniPacket(pkt)
+ assert.Nil(t, err)
+ assert.Equal(t, stream.CallCount, 1)
+ indication := stream.Calls[1].GetPktInd()
+ assert.Equal(t, "nni", indication.IntfType)
+ assert.Equal(t, nni.ID, indication.IntfId)
+ assert.Equal(t, pkt.Data(), indication.Pkt)
}
-func TestCreateNNIPair(t *testing.T) {
-
- startDHCPServerCalled := false
- _startDHCPServer := startDHCPServer
- defer func() { startDHCPServer = _startDHCPServer }()
- startDHCPServer = func(upstreamVeth string, dhcpServerIp string) error {
- startDHCPServerCalled = true
- return nil
- }
-
- listenOnVethCalled := false
- _listenOnVeth := listenOnVeth
- defer func() { listenOnVeth = _listenOnVeth }()
- listenOnVeth = func(vethName string) (chan *types.PacketMsg, *pcap.Handle, error) {
- listenOnVethCalled = true
- return make(chan *types.PacketMsg, 1), nil, nil
- }
- spy := &ExecutorSpy{
- failRun: false,
- Calls: make(map[int][]string),
- }
-
- olt := OltDevice{}
- nni := NniPort{}
-
- err := createNNIPair(spy, &olt, &nni)
- olt.nniPktInChannel, olt.nniHandle, _ = nni.NewVethChan()
-
- assert.Equal(t, spy.CommandCallCount, 3)
- assert.Equal(t, startDHCPServerCalled, true)
- assert.Equal(t, listenOnVethCalled, true)
- assert.Equal(t, err, nil)
- assert.Assert(t, olt.nniPktInChannel != nil)
+type mockDhcpServer struct {
+ callCount int
+ fail bool
}
-type ExecutorSpy struct {
- failRun bool
-
- CommandCallCount int
- RunCallCount int
- Calls map[int][]string
-}
-
-func (s *ExecutorSpy) Command(name string, arg ...string) Runnable {
- s.CommandCallCount++
-
- s.Calls[s.CommandCallCount] = arg
-
- return s
-}
-
-func (s *ExecutorSpy) Run() error {
- s.RunCallCount++
- if s.failRun {
- return errors.New("fake-error")
+// being a Mock I just return the same packet I got
+func (s mockDhcpServer) HandleServerPacket(pkt gopacket.Packet) (gopacket.Packet, error) {
+ if s.fail {
+ return nil, errors.New("mocked-error")
}
- return nil
+ return pkt, nil
+}
+
+func createTestDhcpPacket(t *testing.T) gopacket.Packet {
+ dhcp := &layers.DHCPv4{
+ Operation: layers.DHCPOpRequest,
+ }
+
+ buffer := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{FixLengths: true}
+ err := gopacket.SerializeLayers(buffer, opts, dhcp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeDHCPv4, gopacket.DecodeOptions{})
}
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index c3e0866..beeffce 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -20,6 +20,7 @@
"context"
"encoding/hex"
"fmt"
+ "github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
"github.com/opencord/voltha-protos/v4/go/ext/config"
"net"
"sync"
@@ -27,10 +28,8 @@
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
- "github.com/google/gopacket/pcap"
"github.com/looplab/fsm"
"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
- bbsim "github.com/opencord/bbsim/internal/bbsim/types"
"github.com/opencord/bbsim/internal/common"
omcisim "github.com/opencord/omci-sim"
common_protos "github.com/opencord/voltha-protos/v4/go/common"
@@ -58,8 +57,7 @@
NumOnuPerPon int
InternalState *fsm.FSM
channel chan Message
- nniPktInChannel chan *bbsim.PacketMsg // packets coming in from the NNI and going to VOLTHA
- nniHandle *pcap.Handle // handle on the NNI interface, close it when shutting down the NNI channel
+ dhcpServer dhcp.DHCPServerIf
Flows map[FlowKey]openolt.Flow
Delay int
ControlledActivation mode
@@ -111,6 +109,7 @@
enablePerf: options.BBSim.EnablePerf,
PublishEvents: options.BBSim.Events,
PortStatsInterval: options.Olt.PortStatsInterval,
+ dhcpServer: dhcp.NewDHCPServer(),
}
if val, ok := ControlledActivationModes[options.BBSim.ControlledActivation]; ok {
@@ -235,25 +234,12 @@
// create new channel for processOltMessages Go routine
o.channel = make(chan Message)
- o.nniPktInChannel = make(chan *bbsim.PacketMsg, 1024)
// FIXME we are assuming we have only one NNI
if o.Nnis[0] != nil {
// NOTE we want to make sure the state is down when we initialize the OLT,
// the NNI may be in a bad state after a disable/reboot as we are not disabling it for
// in-band management
o.Nnis[0].OperState.SetState("down")
- ch, handle, err := o.Nnis[0].NewVethChan()
- if err == nil {
- oltLogger.WithFields(log.Fields{
- "Type": o.Nnis[0].Type,
- "IntfId": o.Nnis[0].ID,
- "OperState": o.Nnis[0].OperState.Current(),
- }).Info("NNI Channel created")
- o.nniPktInChannel = ch
- o.nniHandle = handle
- } else {
- oltLogger.Errorf("Error getting NNI channel: %v", err)
- }
}
}
@@ -319,9 +305,7 @@
// terminate the OLT's processOltMessages go routine
close(o.channel)
- // terminate the OLT's processNniPacketIns go routine
- go o.nniHandle.Close()
- close(o.nniPktInChannel)
+
o.enableContextCancel()
time.Sleep(time.Duration(rebootDelay) * time.Second)
@@ -398,7 +382,6 @@
// create Go routine to process all OLT events
go o.processOltMessages(o.enableContext, stream, &wg)
- go o.processNniPacketIns(o.enableContext, stream, &wg)
// enable the OLT
oltMsg := Message{
@@ -753,82 +736,6 @@
oltLogger.Warn("Stopped handling OLT Indication Channel")
}
-// processNniPacketIns handles messages received over the NNI interface
-func (o *OltDevice) processNniPacketIns(ctx context.Context, stream openolt.Openolt_EnableIndicationServer, wg *sync.WaitGroup) {
- oltLogger.WithFields(log.Fields{
- "nniChannel": o.nniPktInChannel,
- }).Debug("Started Processing Packets arriving from the NNI")
- nniId := o.Nnis[0].ID // FIXME we are assuming we have only one NNI
-
- ch := o.nniPktInChannel
-
-loop:
- for {
- select {
- case <-ctx.Done():
- oltLogger.Debug("NNI Indication processing canceled via context")
- break loop
- case message, ok := <-ch:
- if !ok || ctx.Err() != nil {
- oltLogger.Debug("NNI Indication processing canceled via channel closed")
- break loop
- }
- oltLogger.Tracef("Received packets on NNI Channel")
-
- onuMac, err := packetHandlers.GetDstMacAddressFromPacket(message.Pkt)
-
- if err != nil {
- log.WithFields(log.Fields{
- "IntfType": "nni",
- "IntfId": nniId,
- "Pkt": message.Pkt.Data(),
- }).Error("Can't find Dst MacAddress in packet")
- return
- }
-
- s, err := o.FindServiceByMacAddress(onuMac)
- if err != nil {
- log.WithFields(log.Fields{
- "IntfType": "nni",
- "IntfId": nniId,
- "Pkt": message.Pkt.Data(),
- "MacAddress": onuMac.String(),
- }).Error("Can't find ONU with MacAddress")
- return
- }
-
- service := s.(*Service)
-
- doubleTaggedPkt, err := packetHandlers.PushDoubleTag(service.STag, service.CTag, message.Pkt, service.UsPonCTagPriority)
- if err != nil {
- log.Error("Fail to add double tag to packet")
- }
-
- data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
- IntfType: "nni",
- IntfId: nniId,
- Pkt: doubleTaggedPkt.Data()}}
- if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
- oltLogger.WithFields(log.Fields{
- "IntfType": data.PktInd.IntfType,
- "IntfId": nniId,
- "Pkt": doubleTaggedPkt.Data(),
- }).Errorf("Fail to send PktInd indication: %v", err)
- }
- oltLogger.WithFields(log.Fields{
- "IntfType": data.PktInd.IntfType,
- "IntfId": nniId,
- "Pkt": hex.EncodeToString(doubleTaggedPkt.Data()),
- "OnuSn": service.Onu.Sn(),
- }).Trace("Sent PktInd indication (from NNI to VOLTHA)")
- }
- }
- wg.Done()
- oltLogger.WithFields(log.Fields{
- "nniChannel": o.nniPktInChannel,
- }).Warn("Stopped handling NNI Channel")
-}
-
// returns an ONU with a given Serial Number
func (o *OltDevice) FindOnuBySn(serialNumber string) (*Onu, error) {
// TODO this function can be a performance bottleneck when we have many ONUs,
@@ -1405,8 +1312,11 @@
func (o *OltDevice) UplinkPacketOut(context context.Context, packet *openolt.UplinkPacket) (*openolt.Empty, error) {
pkt := gopacket.NewPacket(packet.Pkt, layers.LayerTypeEthernet, gopacket.Default)
- _ = o.Nnis[0].sendNniPacket(pkt) // FIXME we are assuming we have only one NNI
- // NOTE should we return an error if sendNniPakcet fails?
+ err := o.Nnis[0].handleNniPacket(pkt) // FIXME we are assuming we have only one NNI
+
+ if err != nil {
+ return nil, err
+ }
return new(openolt.Empty), nil
}
diff --git a/internal/bbsim/packetHandlers/filters.go b/internal/bbsim/packetHandlers/filters.go
index c46f87d..a930b1e 100644
--- a/internal/bbsim/packetHandlers/filters.go
+++ b/internal/bbsim/packetHandlers/filters.go
@@ -44,16 +44,14 @@
return false
}
-// return true if the packet is coming in the OLT from the NNI port
-// it uses the ack to check if the source is the one we assigned to the
-// dhcp server
+// return true if the packet is coming in the OLT from the DHCP Server
+// given that we only check DHCP packets we can use the Operation
+// Request are outgoing (toward the server)
+// Replies are incoming (toward the OLT)
func IsIncomingPacket(packet gopacket.Packet) bool {
- if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
-
- ip, _ := ipLayer.(*layers.IPv4)
-
- // FIXME find a better way to filter outgoing packets
- if ip.SrcIP.Equal(net.ParseIP("192.168.254.1")) {
+ layerDHCP := packet.Layer(layers.LayerTypeDHCPv4)
+ if dhcp, ok := layerDHCP.(*layers.DHCPv4); ok {
+ if dhcp.Operation == layers.DHCPOpReply {
return true
}
}
diff --git a/internal/bbsim/packetHandlers/filters_test.go b/internal/bbsim/packetHandlers/filters_test.go
index c182e82..5be7db2 100644
--- a/internal/bbsim/packetHandlers/filters_test.go
+++ b/internal/bbsim/packetHandlers/filters_test.go
@@ -131,9 +131,8 @@
}
func Test_IsIncomingPacket_True(t *testing.T) {
- eth := &layers.IPv4{
- SrcIP: net.ParseIP("192.168.254.1"),
- DstIP: net.ParseIP("182.21.0.122"),
+ eth := &layers.DHCPv4{
+ Operation: layers.DHCPOpReply,
}
buffer := gopacket.NewSerializeBuffer()
@@ -143,16 +142,15 @@
t.Fatal(err)
}
- ethernetPkt := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeIPv4, gopacket.DecodeOptions{})
+ ethernetPkt := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeDHCPv4, gopacket.DecodeOptions{})
res := packetHandlers.IsIncomingPacket(ethernetPkt)
assert.Equal(t, res, true)
}
func Test_IsIncomingPacket_False(t *testing.T) {
- eth := &layers.IPv4{
- SrcIP: net.ParseIP("182.21.0.122"),
- DstIP: net.ParseIP("192.168.254.1"),
+ eth := &layers.DHCPv4{
+ Operation: layers.DHCPOpRequest,
}
buffer := gopacket.NewSerializeBuffer()
@@ -162,7 +160,7 @@
t.Fatal(err)
}
- ethernetPkt := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeIPv4, gopacket.DecodeOptions{})
+ ethernetPkt := gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeDHCPv4, gopacket.DecodeOptions{})
res := packetHandlers.IsIncomingPacket(ethernetPkt)
assert.Equal(t, res, false)
diff --git a/internal/bbsim/packetHandlers/packet_tags.go b/internal/bbsim/packetHandlers/packet_tags.go
index 1ea7be7..90186a8 100644
--- a/internal/bbsim/packetHandlers/packet_tags.go
+++ b/internal/bbsim/packetHandlers/packet_tags.go
@@ -144,3 +144,21 @@
}
return dot1q.Priority, nil
}
+
+// godet inner and outer tag from a packet
+// TODO unit test
+func GetTagsFromPacket(pkt gopacket.Packet) (uint16, uint16, error) {
+ sTag, err := GetVlanTag(pkt)
+ if err != nil {
+ return 0, 0, err
+ }
+ singleTagPkt, err := PopSingleTag(pkt)
+ if err != nil {
+ return 0, 0, err
+ }
+ cTag, err := GetVlanTag(singleTagPkt)
+ if err != nil {
+ return 0, 0, err
+ }
+ return sTag, cTag, nil
+}
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())
+}