[SEBA-836] BBSim Reflector

Change-Id: Ib4ae5a2c24880dc62209bebb81188eca5f57865d
diff --git a/internal/bbsim/api/grpc_api_server.go b/internal/bbsim/api/grpc_api_server.go
index caa60e8..0c209f8 100644
--- a/internal/bbsim/api/grpc_api_server.go
+++ b/internal/bbsim/api/grpc_api_server.go
@@ -20,7 +20,7 @@
 	"context"
 	"github.com/opencord/bbsim/api/bbsim"
 	"github.com/opencord/bbsim/internal/bbsim/devices"
-	bbsimLogger "github.com/opencord/bbsim/internal/bbsim/logger"
+	"github.com/opencord/bbsim/internal/common"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -82,7 +82,7 @@
 
 func (s BBSimServer) SetLogLevel(ctx context.Context, req *bbsim.LogLevel) (*bbsim.LogLevel, error) {
 
-	bbsimLogger.SetLogLevel(log.StandardLogger(), req.Level, req.Caller)
+	common.SetLogLevel(log.StandardLogger(), req.Level, req.Caller)
 
 	return &bbsim.LogLevel{
 		Level:  log.StandardLogger().Level.String(),
diff --git a/internal/bbsim/devices/helpers.go b/internal/bbsim/devices/helpers.go
index 5a56825..a00931d 100644
--- a/internal/bbsim/devices/helpers.go
+++ b/internal/bbsim/devices/helpers.go
@@ -39,6 +39,7 @@
 	)
 }
 
+// deprecated
 func onuSnToString(sn *openolt.SerialNumber) string {
 	s := string(sn.VendorId)
 	for _, i := range sn.VendorSpecific {
diff --git a/internal/bbsim/devices/messageTypes.go b/internal/bbsim/devices/messageTypes.go
index fe6abef..b61ac18 100644
--- a/internal/bbsim/devices/messageTypes.go
+++ b/internal/bbsim/devices/messageTypes.go
@@ -18,6 +18,7 @@
 
 import (
 	"github.com/google/gopacket"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	"github.com/opencord/voltha-protos/go/openolt"
 )
 
@@ -35,6 +36,12 @@
 	StartDHCP           MessageType = 8
 	OnuPacketOut        MessageType = 9
 	DyingGaspIndication MessageType = 10
+
+	// BBR messages
+	OmciIndication MessageType = 11 // this are OMCI messages going from the OLT to VOLTHA
+	SendEapolFlow  MessageType = 12
+	SendDhcpFlow   MessageType = 13
+	OnuPacketIn    MessageType = 14
 )
 
 func (m MessageType) String() string {
@@ -50,6 +57,10 @@
 		"StartDHCP",
 		"OnuPacketOut",
 		"DyingGaspIndication",
+		"OmciIndication",
+		"SendEapolFlow",
+		"SendDhcpFlow",
+		"OnuPacketIn",
 	}
 	return names[m]
 }
@@ -91,6 +102,12 @@
 	omciMsg *openolt.OmciMsg
 }
 
+type OmciIndicationMessage struct {
+	OnuSN   *openolt.SerialNumber
+	OnuID   uint32
+	OmciInd *openolt.OmciIndication
+}
+
 type OnuFlowUpdateMessage struct {
 	PonPortID uint32
 	OnuID     uint32
@@ -102,10 +119,11 @@
 	OnuID     uint32
 }
 
-type OnuPacketOutMessage struct {
+type OnuPacketMessage struct {
 	IntfId uint32
 	OnuId  uint32
 	Packet gopacket.Packet
+	Type   packetHandlers.PacketType
 }
 
 type DyingGaspIndicationMessage struct {
diff --git a/internal/bbsim/devices/nni.go b/internal/bbsim/devices/nni.go
index 30104fc..f5503cd 100644
--- a/internal/bbsim/devices/nni.go
+++ b/internal/bbsim/devices/nni.go
@@ -31,7 +31,7 @@
 	nniLogger    = log.WithFields(log.Fields{"module": "NNI"})
 	nniVeth      = "nni"
 	upstreamVeth = "upstream"
-	dhcpServerIp = "182.21.0.128"
+	dhcpServerIp = "192.168.254.1"
 )
 
 type Executor interface {
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index c3a62ff..27f4e78 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -63,17 +63,17 @@
 	return &olt
 }
 
-func CreateOLT(seq int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, oltDoneChannel *chan bool, apiDoneChannel *chan bool, group *sync.WaitGroup) OltDevice {
+func CreateOLT(oltId int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, oltDoneChannel *chan bool, apiDoneChannel *chan bool, isMock bool) *OltDevice {
 	oltLogger.WithFields(log.Fields{
-		"ID":           seq,
+		"ID":           oltId,
 		"NumNni":       nni,
 		"NumPon":       pon,
 		"NumOnuPerPon": onuPerPon,
 	}).Debug("CreateOLT")
 
 	olt = OltDevice{
-		ID:           seq,
-		SerialNumber: fmt.Sprintf("BBSIM_OLT_%d", seq),
+		ID:           oltId,
+		SerialNumber: fmt.Sprintf("BBSIM_OLT_%d", oltId),
 		OperState: getOperStateFSM(func(e *fsm.Event) {
 			oltLogger.Debugf("Changing OLT OperState from %s to %s", e.Src, e.Dst)
 		}),
@@ -103,15 +103,16 @@
 		},
 	)
 
-	// create NNI Port
-	nniPort, err := CreateNNI(&olt)
+	if isMock != true {
+		// create NNI Port
+		nniPort, err := CreateNNI(&olt)
+		if err != nil {
+			oltLogger.Fatalf("Couldn't create NNI Port: %v", err)
+		}
 
-	if err != nil {
-		oltLogger.Fatalf("Couldn't create NNI Port: %v", err)
+		olt.Nnis = append(olt.Nnis, &nniPort)
 	}
 
-	olt.Nnis = append(olt.Nnis, &nniPort)
-
 	// create PON ports
 	availableCTag := cTagInit
 	for i := 0; i < pon; i++ {
@@ -137,11 +138,13 @@
 
 		olt.Pons = append(olt.Pons, &p)
 	}
+	return &olt
+}
 
-	newOltServer(olt)
-
+// this function start the OLT gRPC server and blocks until it's done
+func StartOlt(olt *OltDevice, group *sync.WaitGroup) {
+	newOltServer(*olt)
 	group.Done()
-	return olt
 }
 
 func newOltServer(o OltDevice) error {
@@ -222,7 +225,7 @@
 		o.channel <- msg
 
 		for _, onu := range pon.Onus {
-			go onu.processOnuMessages(stream)
+			go onu.ProcessOnuMessages(stream, nil)
 			go onu.processOmciMessages(stream)
 			// FIXME move the message generation in the state transition
 			// from here only invoke the state transition
@@ -243,7 +246,7 @@
 
 // Helpers method
 
-func (o OltDevice) getPonById(id uint32) (*PonPort, error) {
+func (o OltDevice) GetPonById(id uint32) (*PonPort, error) {
 	for _, pon := range o.Pons {
 		if pon.ID == id {
 			return pon, nil
@@ -294,7 +297,7 @@
 }
 
 func (o OltDevice) sendPonIndication(msg PonIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
-	pon, _ := o.getPonById(msg.PonPortID)
+	pon, _ := o.GetPonById(msg.PonPortID)
 	pon.OperState.Event("enable")
 	discoverData := &openolt.Indication_IntfInd{IntfInd: &openolt.IntfIndication{
 		IntfId:    pon.ID,
@@ -410,6 +413,7 @@
 			"IntfType": data.PktInd.IntfType,
 			"IntfId":   nniId,
 			"Pkt":      doubleTaggedPkt.Data(),
+			"OnuSn":    onu.Sn(),
 		}).Tracef("Sent PktInd indication")
 	}
 }
@@ -451,8 +455,8 @@
 		"OnuSn": onuSnToString(onu.SerialNumber),
 	}).Info("Received ActivateOnu call from VOLTHA")
 
-	pon, _ := o.getPonById(onu.IntfId)
-	_onu, _ := pon.getOnuBySn(onu.SerialNumber)
+	pon, _ := o.GetPonById(onu.IntfId)
+	_onu, _ := pon.GetOnuBySn(onu.SerialNumber)
 
 	if err := _onu.OperState.Event("enable"); err != nil {
 		oltLogger.WithFields(log.Fields{
@@ -531,7 +535,7 @@
 			"FlowId": flow.FlowId,
 		}).Debugf("This is an OLT flow")
 	} else {
-		pon, err := o.getPonById(uint32(flow.AccessIntfId))
+		pon, err := o.GetPonById(uint32(flow.AccessIntfId))
 		if err != nil {
 			oltLogger.WithFields(log.Fields{
 				"OnuId":  flow.OnuId,
@@ -539,7 +543,7 @@
 				"err":    err,
 			}).Error("Can't find PonPort")
 		}
-		onu, err := pon.getOnuById(uint32(flow.OnuId))
+		onu, err := pon.GetOnuById(uint32(flow.OnuId))
 		if err != nil {
 			oltLogger.WithFields(log.Fields{
 				"OnuId":  flow.OnuId,
@@ -600,8 +604,8 @@
 }
 
 func (o OltDevice) OmciMsgOut(ctx context.Context, omci_msg *openolt.OmciMsg) (*openolt.Empty, error) {
-	pon, _ := o.getPonById(omci_msg.IntfId)
-	onu, _ := pon.getOnuById(omci_msg.OnuId)
+	pon, _ := o.GetPonById(omci_msg.IntfId)
+	onu, _ := pon.GetOnuById(omci_msg.OnuId)
 	oltLogger.WithFields(log.Fields{
 		"IntfId": onu.PonPortID,
 		"OnuId":  onu.ID,
@@ -620,7 +624,7 @@
 }
 
 func (o OltDevice) OnuPacketOut(ctx context.Context, onuPkt *openolt.OnuPacket) (*openolt.Empty, error) {
-	pon, err := o.getPonById(onuPkt.IntfId)
+	pon, err := o.GetPonById(onuPkt.IntfId)
 	if err != nil {
 		oltLogger.WithFields(log.Fields{
 			"OnuId":  onuPkt.OnuId,
@@ -628,7 +632,7 @@
 			"err":    err,
 		}).Error("Can't find PonPort")
 	}
-	onu, err := pon.getOnuById(onuPkt.OnuId)
+	onu, err := pon.GetOnuById(onuPkt.OnuId)
 	if err != nil {
 		oltLogger.WithFields(log.Fields{
 			"OnuId":  onuPkt.OnuId,
@@ -644,13 +648,15 @@
 	}).Tracef("Received OnuPacketOut")
 
 	rawpkt := gopacket.NewPacket(onuPkt.Pkt, layers.LayerTypeEthernet, gopacket.Default)
+	pktType, err := packetHandlers.IsEapolOrDhcp(rawpkt)
 
 	msg := Message{
 		Type: OnuPacketOut,
-		Data: OnuPacketOutMessage{
+		Data: OnuPacketMessage{
 			IntfId: onuPkt.IntfId,
 			OnuId:  onuPkt.OnuId,
 			Packet: rawpkt,
+			Type:   pktType,
 		},
 	}
 	onu.Channel <- msg
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index 7d36bfd..f4ef84b 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -17,13 +17,17 @@
 package devices
 
 import (
+	"context"
 	"fmt"
+	"github.com/cboling/omci"
 	"github.com/google/gopacket/layers"
 	"github.com/looplab/fsm"
 	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
 	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
-	omci "github.com/opencord/omci-sim"
+	"github.com/opencord/bbsim/internal/common"
+	omcilib "github.com/opencord/bbsim/internal/common/omci"
+	omcisim "github.com/opencord/omci-sim"
 	"github.com/opencord/voltha-protos/go/openolt"
 	log "github.com/sirupsen/logrus"
 	"net"
@@ -50,23 +54,35 @@
 	SerialNumber *openolt.SerialNumber
 
 	Channel chan Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
+
+	// OMCI params
+	tid        uint16
+	hpTid      uint16
+	seqNumber  uint16
+	HasGemPort bool
+
+	DoneChannel chan bool // this channel is used to signal once the onu is complete (when the struct is used by BBR)
 }
 
 func (o Onu) Sn() string {
-	return onuSnToString(o.SerialNumber)
+	return common.OnuSnToString(o.SerialNumber)
 }
 
 func CreateONU(olt OltDevice, pon PonPort, id uint32, sTag int, cTag int) *Onu {
 
 	o := Onu{
-		ID:        id,
-		PonPortID: pon.ID,
-		PonPort:   pon,
-		STag:      sTag,
-		CTag:      cTag,
-		HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(id)},
-		PortNo:    0,
-		Channel:   make(chan Message, 2048),
+		ID:          id,
+		PonPortID:   pon.ID,
+		PonPort:     pon,
+		STag:        sTag,
+		CTag:        cTag,
+		HwAddress:   net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(id)},
+		PortNo:      0,
+		Channel:     make(chan Message, 2048),
+		tid:         0x1,
+		hpTid:       0x8000,
+		seqNumber:   0,
+		DoneChannel: make(chan bool, 1),
 	}
 	o.SerialNumber = o.NewSN(olt.ID, pon.ID, o.ID)
 
@@ -102,6 +118,10 @@
 			{Name: "dhcp_request_sent", Src: []string{"dhcp_discovery_sent"}, Dst: "dhcp_request_sent"},
 			{Name: "dhcp_ack_received", Src: []string{"dhcp_request_sent"}, Dst: "dhcp_ack_received"},
 			{Name: "dhcp_failed", Src: []string{"dhcp_started", "dhcp_discovery_sent", "dhcp_request_sent"}, Dst: "dhcp_failed"},
+			// BBR States
+			// TODO add start OMCI state
+			{Name: "send_eapol_flow", Src: []string{"created"}, Dst: "eapol_flow_sent"},
+			{Name: "send_dhcp_flow", Src: []string{"eapol_flow_sent"}, Dst: "dhcp_flow_sent"},
 		},
 		fsm.Callbacks{
 			"enter_state": func(e *fsm.Event) {
@@ -164,6 +184,18 @@
 					"OnuSn":  o.Sn(),
 				}).Errorf("ONU failed to DHCP!")
 			},
+			"enter_eapol_flow_sent": func(e *fsm.Event) {
+				msg := Message{
+					Type: SendEapolFlow,
+				}
+				o.Channel <- msg
+			},
+			"enter_dhcp_flow_sent": func(e *fsm.Event) {
+				msg := Message{
+					Type: SendDhcpFlow,
+				}
+				o.Channel <- msg
+			},
 		},
 	)
 	return &o
@@ -177,7 +209,7 @@
 	}).Debugf("Changing ONU InternalState from %s to %s", src, dst)
 }
 
-func (o Onu) processOnuMessages(stream openolt.Openolt_EnableIndicationServer) {
+func (o Onu) ProcessOnuMessages(stream openolt.Openolt_EnableIndicationServer, client openolt.OpenoltClient) {
 	onuLogger.WithFields(log.Fields{
 		"onuID": o.ID,
 		"onuSN": o.Sn(),
@@ -211,20 +243,51 @@
 			// FIXME use id, ponId as SendEapStart
 			dhcp.SendDHCPDiscovery(o.PonPortID, o.ID, o.Sn(), o.PortNo, o.InternalState, o.HwAddress, o.CTag, stream)
 		case OnuPacketOut:
-			msg, _ := message.Data.(OnuPacketOutMessage)
-			pkt := msg.Packet
-			etherType := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet).EthernetType
 
-			if etherType == layers.EthernetTypeEAPOL {
-				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream)
-			} else if packetHandlers.IsDhcpPacket(pkt) {
+			msg, _ := message.Data.(OnuPacketMessage)
+
+			log.WithFields(log.Fields{
+				"IntfId":  msg.IntfId,
+				"OnuId":   msg.OnuId,
+				"pktType": msg.Type,
+			}).Trace("Received OnuPacketOut Message")
+
+			if msg.Type == packetHandlers.EAPOL {
+				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
+			} else if msg.Type == packetHandlers.DHCP {
 				// NOTE here we receive packets going from the DHCP Server to the ONU
 				// for now we expect them to be double-tagged, but ideally the should be single tagged
 				dhcp.HandleNextPacket(o.ID, o.PonPortID, o.Sn(), o.PortNo, o.HwAddress, o.CTag, o.InternalState, msg.Packet, stream)
 			}
+		case OnuPacketIn:
+			// NOTE we only receive BBR packets here.
+			// Eapol.HandleNextPacket can handle both BBSim and BBr cases so the call is the same
+			// in the DHCP case VOLTHA only act as a proxy, the behaviour is completely different thus we have a dhcp.HandleNextBbrPacket
+			msg, _ := message.Data.(OnuPacketMessage)
+
+			log.WithFields(log.Fields{
+				"IntfId":  msg.IntfId,
+				"OnuId":   msg.OnuId,
+				"pktType": msg.Type,
+			}).Trace("Received OnuPacketIn Message")
+
+			if msg.Type == packetHandlers.EAPOL {
+				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
+			} else if msg.Type == packetHandlers.DHCP {
+				dhcp.HandleNextBbrPacket(o.ID, o.PonPortID, o.Sn(), o.STag, o.HwAddress, o.DoneChannel, msg.Packet, client)
+			}
 		case DyingGaspIndication:
 			msg, _ := message.Data.(DyingGaspIndicationMessage)
 			o.sendDyingGaspInd(msg, stream)
+		case OmciIndication:
+			// TODO handle me!
+			// here https://gerrit.opencord.org/#/c/15521/11/internal/bbr/onu.go in startOmci
+			msg, _ := message.Data.(OmciIndicationMessage)
+			o.handleOmci(msg, client)
+		case SendEapolFlow:
+			o.sendEapolFlow(client)
+		case SendDhcpFlow:
+			o.sendDhcpFlow(client)
 		default:
 			onuLogger.Warnf("Received unknown message data %v for type %v in OLT Channel", message.Data, message.Type)
 		}
@@ -232,7 +295,7 @@
 }
 
 func (o Onu) processOmciMessages(stream openolt.Openolt_EnableIndicationServer) {
-	ch := omci.GetChannel()
+	ch := omcisim.GetChannel()
 
 	onuLogger.WithFields(log.Fields{
 		"onuID": o.ID,
@@ -241,7 +304,7 @@
 
 	for message := range ch {
 		switch message.Type {
-		case omci.GemPortAdded:
+		case omcisim.GemPortAdded:
 			log.WithFields(log.Fields{
 				"OnuId":  message.Data.OnuId,
 				"IntfId": message.Data.IntfId,
@@ -359,7 +422,7 @@
 	}).Tracef("Received OMCI message")
 
 	var omciInd openolt.OmciIndication
-	respPkt, err := omci.OmciSim(o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
+	respPkt, err := omcisim.OmciSim(o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
 	if err != nil {
 		onuLogger.WithFields(log.Fields{
 			"IntfId":       o.PonPortID,
@@ -381,7 +444,7 @@
 			"SerialNumber": o.Sn(),
 			"omciPacket":   omciInd.Pkt,
 			"msg":          msg,
-		}).Errorf("send omci indication failed: %v", err)
+		}).Errorf("send omcisim indication failed: %v", err)
 		return
 	}
 	onuLogger.WithFields(log.Fields{
@@ -395,10 +458,10 @@
 	// FIXME this is a workaround to always use the SN-1 entry in sadis,
 	// we need to add support for multiple UNIs
 	// the action plan is:
-	// - refactor the omci-sim library to use https://github.com/cboling/omci instead of canned messages
+	// - refactor the omcisim-sim library to use https://github.com/cboling/omci instead of canned messages
 	// - change the library so that it reports a single UNI and remove this workaroung
 	// - add support for multiple UNIs in BBSim
-	if portNo < o.PortNo {
+	if o.PortNo == 0 || portNo < o.PortNo {
 		o.PortNo = portNo
 	}
 }
@@ -459,3 +522,193 @@
 	onuLogger.Tracef("Omci decoded: %x.", p)
 	return p
 }
+
+// BBR methods
+
+func sendOmciMsg(pktBytes []byte, intfId uint32, onuId uint32, sn *openolt.SerialNumber, msgType string, client openolt.OpenoltClient) {
+	omciMsg := openolt.OmciMsg{
+		IntfId: intfId,
+		OnuId:  onuId,
+		Pkt:    pktBytes,
+	}
+
+	if _, err := client.OmciMsgOut(context.Background(), &omciMsg); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       intfId,
+			"OnuId":        onuId,
+			"SerialNumber": common.OnuSnToString(sn),
+			"Pkt":          omciMsg.Pkt,
+		}).Fatalf("Failed to send MIB Reset")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       intfId,
+		"OnuId":        onuId,
+		"SerialNumber": common.OnuSnToString(sn),
+		"Pkt":          omciMsg.Pkt,
+	}).Tracef("Sent OMCI message %s", msgType)
+}
+
+func (onu *Onu) getNextTid(highPriority ...bool) uint16 {
+	var next uint16
+	if len(highPriority) > 0 && highPriority[0] {
+		next = onu.hpTid
+		onu.hpTid += 1
+		if onu.hpTid < 0x8000 {
+			onu.hpTid = 0x8000
+		}
+	} else {
+		next = onu.tid
+		onu.tid += 1
+		if onu.tid >= 0x8000 {
+			onu.tid = 1
+		}
+	}
+	return next
+}
+
+// TODO move this method in responders/omcisim
+func (o *Onu) StartOmci(client openolt.OpenoltClient) {
+	mibReset, _ := omcilib.CreateMibResetRequest(o.getNextTid(false))
+	sendOmciMsg(mibReset, o.PonPortID, o.ID, o.SerialNumber, "mibReset", client)
+}
+
+func (o *Onu) handleOmci(msg OmciIndicationMessage, client openolt.OpenoltClient) {
+	msgType, packet := omcilib.DecodeOmci(msg.OmciInd.Pkt)
+
+	log.WithFields(log.Fields{
+		"IntfId":  msg.OmciInd.IntfId,
+		"OnuId":   msg.OmciInd.OnuId,
+		"OnuSn":   common.OnuSnToString(o.SerialNumber),
+		"Pkt":     msg.OmciInd.Pkt,
+		"msgType": msgType,
+	}).Trace("ONU Receveives OMCI Msg")
+	switch msgType {
+	default:
+		log.Fatalf("unexpected frame: %v", packet)
+	case omci.MibResetResponseType:
+		mibUpload, _ := omcilib.CreateMibUploadRequest(o.getNextTid(false))
+		sendOmciMsg(mibUpload, o.PonPortID, o.ID, o.SerialNumber, "mibUpload", client)
+	case omci.MibUploadResponseType:
+		mibUploadNext, _ := omcilib.CreateMibUploadNextRequest(o.getNextTid(false), o.seqNumber)
+		sendOmciMsg(mibUploadNext, o.PonPortID, o.ID, o.SerialNumber, "mibUploadNext", client)
+	case omci.MibUploadNextResponseType:
+		o.seqNumber++
+
+		if o.seqNumber > 290 {
+			// NOTE we are done with the MIB Upload (290 is the number of messages the omci-sim library will respond to)
+			galEnet, _ := omcilib.CreateGalEnetRequest(o.getNextTid(false))
+			sendOmciMsg(galEnet, o.PonPortID, o.ID, o.SerialNumber, "CreateGalEnetRequest", client)
+		} else {
+			mibUploadNext, _ := omcilib.CreateMibUploadNextRequest(o.getNextTid(false), o.seqNumber)
+			sendOmciMsg(mibUploadNext, o.PonPortID, o.ID, o.SerialNumber, "mibUploadNext", client)
+		}
+	case omci.CreateResponseType:
+		// NOTE Creating a GemPort,
+		// BBsim actually doesn't care about the values, so we can do we want with the parameters
+		// In the same way we can create a GemPort even without setting up UNIs/TConts/...
+		// but we need the GemPort to trigger the state change
+
+		if !o.HasGemPort {
+			// NOTE this sends a CreateRequestType and BBSim replies with a CreateResponseType
+			// thus we send this request only once
+			gemReq, _ := omcilib.CreateGemPortRequest(o.getNextTid(false))
+			sendOmciMsg(gemReq, o.PonPortID, o.ID, o.SerialNumber, "CreateGemPortRequest", client)
+			o.HasGemPort = true
+		} else {
+			if err := o.InternalState.Event("send_eapol_flow"); err != nil {
+				onuLogger.WithFields(log.Fields{
+					"OnuId":  o.ID,
+					"IntfId": o.PonPortID,
+					"OnuSn":  o.Sn(),
+				}).Errorf("Error while transitioning ONU State %v", err)
+			}
+		}
+
+	}
+}
+
+func (o *Onu) sendEapolFlow(client openolt.OpenoltClient) {
+
+	classifierProto := openolt.Classifier{
+		EthType: uint32(layers.EthernetTypeEAPOL),
+		OVid:    4091,
+	}
+
+	actionProto := openolt.Action{}
+
+	downstreamFlow := openolt.Flow{
+		AccessIntfId:  int32(o.PonPortID),
+		OnuId:         int32(o.ID),
+		UniId:         int32(0x101), // FIXME do not hardcode this
+		FlowId:        uint32(o.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		GemportId:     int32(1), // FIXME use the same value as CreateGemPortRequest PortID, do not hardcode
+		Classifier:    &classifierProto,
+		Action:        &actionProto,
+		Priority:      int32(100),
+		Cookie:        uint64(o.ID),
+		PortNo:        uint32(o.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	if _, err := client.FlowAdd(context.Background(), &downstreamFlow); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"OnuId":        o.ID,
+			"FlowId":       downstreamFlow.FlowId,
+			"PortNo":       downstreamFlow.PortNo,
+			"SerialNumber": common.OnuSnToString(o.SerialNumber),
+		}).Fatalf("Failed to EAPOL Flow")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       o.PonPortID,
+		"OnuId":        o.ID,
+		"FlowId":       downstreamFlow.FlowId,
+		"PortNo":       downstreamFlow.PortNo,
+		"SerialNumber": common.OnuSnToString(o.SerialNumber),
+	}).Info("Sent EAPOL Flow")
+}
+
+func (o *Onu) sendDhcpFlow(client openolt.OpenoltClient) {
+	classifierProto := openolt.Classifier{
+		EthType: uint32(layers.EthernetTypeIPv4),
+		SrcPort: uint32(68),
+		DstPort: uint32(67),
+	}
+
+	actionProto := openolt.Action{}
+
+	downstreamFlow := openolt.Flow{
+		AccessIntfId:  int32(o.PonPortID),
+		OnuId:         int32(o.ID),
+		UniId:         int32(0x101), // FIXME do not hardcode this
+		FlowId:        uint32(o.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		GemportId:     int32(1), // FIXME use the same value as CreateGemPortRequest PortID, do not hardcode
+		Classifier:    &classifierProto,
+		Action:        &actionProto,
+		Priority:      int32(100),
+		Cookie:        uint64(o.ID),
+		PortNo:        uint32(o.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	if _, err := client.FlowAdd(context.Background(), &downstreamFlow); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"OnuId":        o.ID,
+			"FlowId":       downstreamFlow.FlowId,
+			"PortNo":       downstreamFlow.PortNo,
+			"SerialNumber": common.OnuSnToString(o.SerialNumber),
+		}).Fatalf("Failed to send DHCP Flow")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       o.PonPortID,
+		"OnuId":        o.ID,
+		"FlowId":       downstreamFlow.FlowId,
+		"PortNo":       downstreamFlow.PortNo,
+		"SerialNumber": common.OnuSnToString(o.SerialNumber),
+	}).Info("Sent DHCP Flow")
+}
diff --git a/internal/bbsim/devices/pon.go b/internal/bbsim/devices/pon.go
index 374b1b9..f27510f 100644
--- a/internal/bbsim/devices/pon.go
+++ b/internal/bbsim/devices/pon.go
@@ -38,7 +38,7 @@
 	// NOTE do we need a state machine for the PON Ports?
 }
 
-func (p PonPort) getOnuBySn(sn *openolt.SerialNumber) (*Onu, error) {
+func (p PonPort) GetOnuBySn(sn *openolt.SerialNumber) (*Onu, error) {
 	for _, onu := range p.Onus {
 		if bytes.Equal(onu.SerialNumber.VendorSpecific, sn.VendorSpecific) {
 			return onu, nil
@@ -47,7 +47,7 @@
 	return nil, errors.New(fmt.Sprintf("Cannot find Onu with serial number %d in PonPort %d", sn, p.ID))
 }
 
-func (p PonPort) getOnuById(id uint32) (*Onu, error) {
+func (p PonPort) GetOnuById(id uint32) (*Onu, error) {
 	for _, onu := range p.Onus {
 		if onu.ID == id {
 			return onu, nil
diff --git a/internal/bbsim/logger/logger.go b/internal/bbsim/logger/logger.go
deleted file mode 100644
index c6036fc..0000000
--- a/internal/bbsim/logger/logger.go
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 logger
-
-import log "github.com/sirupsen/logrus"
-
-func SetLogLevel(logger *log.Logger, level string, caller bool) {
-
-	logger.SetReportCaller(caller)
-
-	switch level {
-	case "trace":
-		logger.SetLevel(log.TraceLevel)
-	case "debug":
-		logger.SetLevel(log.DebugLevel)
-	case "info":
-		logger.SetLevel(log.InfoLevel)
-	case "warn":
-		logger.SetLevel(log.WarnLevel)
-	case "error":
-		logger.SetLevel(log.ErrorLevel)
-	default:
-		logger.SetLevel(log.DebugLevel)
-		logger.WithFields(log.Fields{
-			"level": level,
-		}).Warn("The provided level is unknown. Defaulting to 'debug'")
-	}
-
-}
diff --git a/internal/bbsim/logger/logger_test.go b/internal/bbsim/logger/logger_test.go
deleted file mode 100644
index d692135..0000000
--- a/internal/bbsim/logger/logger_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 logger_test
-
-import (
-	bbsimLogger "github.com/opencord/bbsim/internal/bbsim/logger"
-	"github.com/sirupsen/logrus"
-	"gotest.tools/assert"
-	"testing"
-)
-
-func Test_SetLogLevel(t *testing.T) {
-	log := logrus.New()
-
-	bbsimLogger.SetLogLevel(log, "trace", false)
-	assert.Equal(t, log.Level, logrus.TraceLevel)
-
-	bbsimLogger.SetLogLevel(log, "debug", false)
-	assert.Equal(t, log.Level, logrus.DebugLevel)
-
-	bbsimLogger.SetLogLevel(log, "info", false)
-	assert.Equal(t, log.Level, logrus.InfoLevel)
-
-	bbsimLogger.SetLogLevel(log, "warn", false)
-	assert.Equal(t, log.Level, logrus.WarnLevel)
-
-	bbsimLogger.SetLogLevel(log, "error", false)
-	assert.Equal(t, log.Level, logrus.ErrorLevel)
-
-	bbsimLogger.SetLogLevel(log, "foobar", false)
-	assert.Equal(t, log.Level, logrus.DebugLevel)
-}
-
-func Test_SetLogLevelCaller(t *testing.T) {
-	log := logrus.New()
-
-	bbsimLogger.SetLogLevel(log, "debug", true)
-	assert.Equal(t, log.ReportCaller, true)
-
-	bbsimLogger.SetLogLevel(log, "debug", false)
-	assert.Equal(t, log.ReportCaller, false)
-}
diff --git a/internal/bbsim/packetHandlers/filters.go b/internal/bbsim/packetHandlers/filters.go
index 0748839..b43b677 100644
--- a/internal/bbsim/packetHandlers/filters.go
+++ b/internal/bbsim/packetHandlers/filters.go
@@ -39,14 +39,14 @@
 		ip, _ := ipLayer.(*layers.IPv4)
 
 		// FIXME find a better way to filter outgoing packets
-		if ip.SrcIP.Equal(net.ParseIP("182.21.0.128")) {
+		if ip.SrcIP.Equal(net.ParseIP("192.168.254.1")) {
 			return true
 		}
 	}
 	return false
 }
 
-// returns the Desctination Mac Address contained in the packet
+// returns the Destination Mac Address contained in the packet
 func GetDstMacAddressFromPacket(packet gopacket.Packet) (net.HardwareAddr, error) {
 	if ethLayer := packet.Layer(layers.LayerTypeEthernet); ethLayer != nil {
 		eth, _ := ethLayer.(*layers.Ethernet)
@@ -57,3 +57,25 @@
 	}
 	return nil, errors.New("cant-find-mac-address")
 }
+
+// returns the Source Mac Address contained in the packet
+func GetSrcMacAddressFromPacket(packet gopacket.Packet) (net.HardwareAddr, error) {
+	if ethLayer := packet.Layer(layers.LayerTypeEthernet); ethLayer != nil {
+		eth, _ := ethLayer.(*layers.Ethernet)
+
+		if eth.DstMAC != nil {
+			return eth.SrcMAC, nil
+		}
+	}
+	return nil, errors.New("cant-find-mac-address")
+}
+
+// returns wether it's an EAPOL or DHCP packet, error if it's none
+func IsEapolOrDhcp(pkt gopacket.Packet) (PacketType, error) {
+	if pkt.Layer(layers.LayerTypeEAP) != nil || pkt.Layer(layers.LayerTypeEAPOL) != nil {
+		return EAPOL, nil
+	} else if IsDhcpPacket(pkt) {
+		return DHCP, nil
+	}
+	return UNKNOWN, errors.New("packet-is-neither-eapol-or-dhcp")
+}
diff --git a/internal/bbsim/packetHandlers/filters_test.go b/internal/bbsim/packetHandlers/filters_test.go
index 6d82495..6064e68 100644
--- a/internal/bbsim/packetHandlers/filters_test.go
+++ b/internal/bbsim/packetHandlers/filters_test.go
@@ -64,7 +64,7 @@
 
 func Test_IsIncomingPacket_True(t *testing.T) {
 	eth := &layers.IPv4{
-		SrcIP: net.ParseIP("182.21.0.128"),
+		SrcIP: net.ParseIP("192.168.254.1"),
 		DstIP: net.ParseIP("182.21.0.122"),
 	}
 
@@ -84,7 +84,7 @@
 func Test_IsIncomingPacket_False(t *testing.T) {
 	eth := &layers.IPv4{
 		SrcIP: net.ParseIP("182.21.0.122"),
-		DstIP: net.ParseIP("182.21.0.128"),
+		DstIP: net.ParseIP("192.168.254.1"),
 	}
 
 	buffer := gopacket.NewSerializeBuffer()
diff --git a/internal/bbsim/packetHandlers/packetTypes.go b/internal/bbsim/packetHandlers/packetTypes.go
new file mode 100644
index 0000000..fe424f0
--- /dev/null
+++ b/internal/bbsim/packetHandlers/packetTypes.go
@@ -0,0 +1,34 @@
+/*
+ * 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 packetHandlers
+
+type PacketType int
+
+const (
+	UNKNOWN PacketType = iota
+	EAPOL
+	DHCP
+)
+
+func (t PacketType) String() string {
+	names := [...]string{
+		"UNKNOWN",
+		"EAPOL",
+		"DHCP",
+	}
+	return names[t]
+}
diff --git a/internal/bbsim/responders/dhcp/dhcp.go b/internal/bbsim/responders/dhcp/dhcp.go
index c7ae75c..25f5210 100644
--- a/internal/bbsim/responders/dhcp/dhcp.go
+++ b/internal/bbsim/responders/dhcp/dhcp.go
@@ -17,11 +17,13 @@
 package dhcp
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"github.com/google/gopacket"
 	"github.com/google/gopacket/layers"
 	"github.com/looplab/fsm"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	bbsim "github.com/opencord/bbsim/internal/bbsim/types"
 	omci "github.com/opencord/omci-sim"
 	"github.com/opencord/voltha-protos/go/openolt"
@@ -52,14 +54,14 @@
 	layers.DHCPOptNTPServers,
 }
 
-func createDefaultDHCPReq(intfId uint32, onuId uint32) layers.DHCPv4 {
+func createDefaultDHCPReq(intfId uint32, onuId uint32, mac net.HardwareAddr) layers.DHCPv4 {
 	return layers.DHCPv4{
 		Operation:    layers.DHCPOpRequest,
 		HardwareType: layers.LinkTypeEthernet,
 		HardwareLen:  6,
 		HardwareOpts: 0,
 		Xid:          onuId,
-		ClientHWAddr: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(intfId), byte(onuId)},
+		ClientHWAddr: mac,
 	}
 }
 
@@ -85,8 +87,8 @@
 	return opts
 }
 
-func createDHCPDisc(intfId uint32, onuId uint32) *layers.DHCPv4 {
-	dhcpLayer := createDefaultDHCPReq(intfId, onuId)
+func createDHCPDisc(intfId uint32, onuId uint32, macAddress net.HardwareAddr) *layers.DHCPv4 {
+	dhcpLayer := createDefaultDHCPReq(intfId, onuId, macAddress)
 	defaultOpts := createDefaultOpts()
 	dhcpLayer.Options = append([]layers.DHCPOption{layers.DHCPOption{
 		Type:   layers.DHCPOptMessageType,
@@ -94,11 +96,19 @@
 		Length: 1,
 	}}, defaultOpts...)
 
+	//data := []byte{0xcd, 0x28, 0xcb, 0xcc, 0x00, 0x01, 0x00, 0x01,
+	//	0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, 0x28, byte(intfId), byte(onuId)}
+	//dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+	//	Type:   layers.DHCPOptClientID,
+	//	Data:   data,
+	//	Length: uint8(len(data)),
+	//})
+
 	return &dhcpLayer
 }
 
-func createDHCPReq(intfId uint32, onuId uint32) *layers.DHCPv4 {
-	dhcpLayer := createDefaultDHCPReq(intfId, onuId)
+func createDHCPReq(intfId uint32, onuId uint32, macAddress net.HardwareAddr, offeredIp net.IP) *layers.DHCPv4 {
+	dhcpLayer := createDefaultDHCPReq(intfId, onuId, macAddress)
 	defaultOpts := createDefaultOpts()
 
 	dhcpLayer.Options = append(defaultOpts, layers.DHCPOption{
@@ -115,18 +125,18 @@
 	})
 
 	data = []byte{0xcd, 0x28, 0xcb, 0xcc, 0x00, 0x01, 0x00, 0x01,
-		0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, 0x28, 0xcb, 0xcc}
+		0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, 0x28, byte(intfId), byte(onuId)}
 	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
 		Type:   layers.DHCPOptClientID,
 		Data:   data,
 		Length: uint8(len(data)),
 	})
 
-	data = []byte{182, 21, 0, byte(onuId)}
+	// NOTE we should not request a specific IP, or we should request the one that has been offered
 	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
 		Type:   layers.DHCPOptRequestIP,
-		Data:   data,
-		Length: uint8(len(data)),
+		Data:   offeredIp,
+		Length: uint8(len(offeredIp)),
 	})
 	return &dhcpLayer
 }
@@ -167,7 +177,7 @@
 	return bytes, nil
 }
 
-func getDhcpLayer(pkt gopacket.Packet) (*layers.DHCPv4, error) {
+func GetDhcpLayer(pkt gopacket.Packet) (*layers.DHCPv4, error) {
 	layerDHCP := pkt.Layer(layers.LayerTypeDHCPv4)
 	dhcp, _ := layerDHCP.(*layers.DHCPv4)
 	if dhcp == nil {
@@ -176,11 +186,15 @@
 	return dhcp, nil
 }
 
-func getDhcpMessageType(dhcp *layers.DHCPv4) (layers.DHCPMsgType, error) {
+func GetDhcpMessageType(dhcp *layers.DHCPv4) (layers.DHCPMsgType, error) {
 	for _, option := range dhcp.Options {
 		if option.Type == layers.DHCPOptMessageType {
-			if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeOffer)}) {
+			if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeDiscover)}) {
+				return layers.DHCPMsgTypeDiscover, nil
+			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeOffer)}) {
 				return layers.DHCPMsgTypeOffer, nil
+			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeRequest)}) {
+				return layers.DHCPMsgTypeRequest, nil
 			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeAck)}) {
 				return layers.DHCPMsgTypeAck, nil
 			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeRelease)}) {
@@ -194,6 +208,20 @@
 	return 0, errors.New("Failed to extract MsgType from dhcp")
 }
 
+// returns the DHCP Layer type or error if it's not a DHCP Packet
+func GetDhcpPacketType(pkt gopacket.Packet) (string, error) {
+	dhcpLayer, err := GetDhcpLayer(pkt)
+	if err != nil {
+		return "", err
+	}
+	dhcpMessageType, err := GetDhcpMessageType(dhcpLayer)
+	if err != nil {
+		return "", err
+	}
+
+	return dhcpMessageType.String(), nil
+}
+
 func sendDHCPPktIn(msg bbsim.ByteMsg, portNo uint32, stream bbsim.Stream) error {
 	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
 	gemid, err := GetGemPortId(msg.IntfId, msg.OnuId)
@@ -219,8 +247,8 @@
 	return nil
 }
 
-func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
-	dhcp := createDHCPReq(ponPortId, onuId)
+func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, offeredIp net.IP, stream openolt.Openolt_EnableIndicationServer) error {
+	dhcp := createDHCPReq(ponPortId, onuId, onuHwAddress, offeredIp)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
 
 	if err != nil {
@@ -239,11 +267,13 @@
 	if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
 		return err
 	}
+
 	dhcpLogger.WithFields(log.Fields{
-		"OnuId":  onuId,
-		"IntfId": ponPortId,
-		"OnuSn":  serialNumber,
-	}).Infof("DHCPDiscovery Sent")
+		"OnuId":     onuId,
+		"IntfId":    ponPortId,
+		"OnuSn":     serialNumber,
+		"OfferedIp": offeredIp.String(),
+	}).Infof("DHCPRequest Sent")
 	return nil
 }
 
@@ -260,7 +290,7 @@
 }
 
 func SendDHCPDiscovery(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, cTag int, stream bbsim.Stream) error {
-	dhcp := createDHCPDisc(ponPortId, onuId)
+	dhcp := createDHCPDisc(ponPortId, onuId, onuHwAddress)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
 	if err != nil {
 		dhcpLogger.Errorf("Cannot serializeDHCPPacket: %s", err)
@@ -299,7 +329,7 @@
 
 func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, cTag int, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) error {
 
-	dhcpLayer, err := getDhcpLayer(pkt)
+	dhcpLayer, err := GetDhcpLayer(pkt)
 	if err != nil {
 		dhcpLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
@@ -311,7 +341,7 @@
 		}
 		return err
 	}
-	dhcpMessageType, err := getDhcpMessageType(dhcpLayer)
+	dhcpMessageType, err := GetDhcpMessageType(dhcpLayer)
 	if err != nil {
 		dhcpLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
@@ -326,7 +356,8 @@
 
 	if dhcpLayer.Operation == layers.DHCPOpReply {
 		if dhcpMessageType == layers.DHCPMsgTypeOffer {
-			if err := sendDHCPRequest(ponPortId, onuId, serialNumber, portNo, onuHwAddress, cTag, stream); err != nil {
+			offeredIp := dhcpLayer.YourClientIP
+			if err := sendDHCPRequest(ponPortId, onuId, serialNumber, portNo, onuHwAddress, offeredIp, stream); err != nil {
 				dhcpLogger.WithFields(log.Fields{
 					"OnuId":  onuId,
 					"IntfId": ponPortId,
@@ -370,3 +401,94 @@
 	}
 	return nil
 }
+
+// This method handle the BBR DHCP Packets
+// BBR does not need to do anything but forward the packets in the correct direction
+func HandleNextBbrPacket(onuId uint32, ponPortId uint32, serialNumber string, sTag int, macAddress net.HardwareAddr, doneChannel chan bool, pkt gopacket.Packet, client openolt.OpenoltClient) error {
+
+	// check if the packet is going:
+	// - outgouing: toward the DHCP
+	// - incoming: toward the ONU
+	isIncoming := packetHandlers.IsIncomingPacket(pkt)
+	log.Tracef("Is Incoming: %t", isIncoming)
+
+	dhcpType, err := GetDhcpPacketType(pkt)
+	if err != nil {
+		log.WithFields(log.Fields{
+			"err": err,
+		}).Fatalf("Can't find DHCP type for packet")
+	}
+
+	srcMac, _ := packetHandlers.GetSrcMacAddressFromPacket(pkt)
+	dstMac, _ := packetHandlers.GetDstMacAddressFromPacket(pkt)
+
+	if isIncoming == true {
+
+		onuPacket := openolt.OnuPacket{
+			IntfId:    ponPortId,
+			OnuId:     onuId,
+			PortNo:    onuId,
+			GemportId: 1,
+			Pkt:       pkt.Data(),
+		}
+
+		if _, err := client.OnuPacketOut(context.Background(), &onuPacket); err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+				"Type":   dhcpType,
+				"error":  err,
+			}).Error("Failed to send DHCP packet to the ONU")
+		}
+
+		log.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+			"Type":   dhcpType,
+			"DstMac": dstMac,
+			"SrcMac": srcMac,
+			"OnuMac": macAddress,
+		}).Infof("Sent DHCP packet to the ONU")
+
+		// TODO: signal that the ONU has completed
+		dhcpLayer, _ := GetDhcpLayer(pkt)
+		dhcpMessageType, _ := GetDhcpMessageType(dhcpLayer)
+		if dhcpMessageType == layers.DHCPMsgTypeAck {
+			doneChannel <- true
+		}
+
+	} else {
+		// double tag the packet and send it to the NNI
+		// NOTE do we need this in the HandleDHCP Packet?
+		doubleTaggedPkt, err := packetHandlers.PushDoubleTag(sTag, sTag, pkt)
+		if err != nil {
+			log.Error("Failt to add double tag to packet")
+		}
+
+		pkt := openolt.UplinkPacket{
+			IntfId: 0, // BBSim does not care about which NNI, it has only one
+			Pkt:    doubleTaggedPkt.Data(),
+		}
+		if _, err := client.UplinkPacketOut(context.Background(), &pkt); err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+				"Type":   dhcpType,
+				"error":  err,
+			}).Error("Failed to send DHCP packet out of the NNI Port")
+		}
+		log.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+			"Type":   dhcpType,
+			"DstMac": dstMac,
+			"SrcMac": srcMac,
+			"OnuMac": macAddress,
+		}).Infof("Sent DHCP packet out of the NNI Port")
+	}
+	return nil
+}
diff --git a/internal/bbsim/responders/eapol/eapol.go b/internal/bbsim/responders/eapol/eapol.go
index f31a068..c13aa69 100644
--- a/internal/bbsim/responders/eapol/eapol.go
+++ b/internal/bbsim/responders/eapol/eapol.go
@@ -17,6 +17,7 @@
 package eapol
 
 import (
+	"context"
 	"crypto/md5"
 	"errors"
 	"github.com/google/gopacket"
@@ -72,6 +73,17 @@
 	return ret
 }
 
+func createEAPChallengeRequest(eapId uint8, payload []byte) *layers.EAP {
+	eap := layers.EAP{
+		Code:     layers.EAPCodeRequest,
+		Id:       eapId,
+		Length:   22,
+		Type:     layers.EAPTypeOTP,
+		TypeData: payload,
+	}
+	return &eap
+}
+
 func createEAPChallengeResponse(eapId uint8, payload []byte) *layers.EAP {
 	eap := layers.EAP{
 		Code:     layers.EAPCodeResponse,
@@ -83,6 +95,15 @@
 	return &eap
 }
 
+func createEAPIdentityRequest(eapId uint8) *layers.EAP {
+	eap := layers.EAP{Code: layers.EAPCodeRequest,
+		Id:       eapId,
+		Length:   9,
+		Type:     layers.EAPTypeIdentity,
+		TypeData: []byte{0x75, 0x73, 0x65, 0x72}}
+	return &eap
+}
+
 func createEAPIdentityResponse(eapId uint8) *layers.EAP {
 	eap := layers.EAP{Code: layers.EAPCodeResponse,
 		Id:       eapId,
@@ -92,6 +113,16 @@
 	return &eap
 }
 
+func createEAPSuccess(eapId uint8) *layers.EAP {
+	eap := layers.EAP{
+		Code:     layers.EAPCodeSuccess,
+		Id:       eapId,
+		Length:   9,
+		Type:     layers.EAPTypeNone,
+		TypeData: []byte{0x75, 0x73, 0x65, 0x72}}
+	return &eap
+}
+
 func createEAPOLPkt(eap *layers.EAP, onuId uint32, intfId uint32) []byte {
 	buffer := gopacket.NewSerializeBuffer()
 	options := gopacket.SerializeOptions{}
@@ -121,6 +152,30 @@
 	return eap, nil
 }
 
+func extractEAPOL(pkt gopacket.Packet) (*layers.EAPOL, error) {
+	layerEAPOL := pkt.Layer(layers.LayerTypeEAPOL)
+	eap, _ := layerEAPOL.(*layers.EAPOL)
+	if eap == nil {
+		return nil, errors.New("Cannot extract EAPOL")
+	}
+	return eap, nil
+}
+
+func sendEapolPktOut(client openolt.OpenoltClient, intfId uint32, onuId uint32, pkt []byte) error {
+	onuPacket := openolt.OnuPacket{
+		IntfId:    intfId,
+		OnuId:     onuId,
+		PortNo:    onuId,
+		GemportId: 1,
+		Pkt:       pkt,
+	}
+
+	if _, err := client.OnuPacketOut(context.Background(), &onuPacket); err != nil {
+		return err
+	}
+	return nil
+}
+
 func updateAuthFailed(onuId uint32, ponPortId uint32, serialNumber string, onuStateMachine *fsm.FSM) error {
 	if err := onuStateMachine.Event("auth_failed"); err != nil {
 		eapolLogger.WithFields(log.Fields{
@@ -210,22 +265,58 @@
 	return nil
 }
 
-func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, recvpkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) {
+func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer, client openolt.OpenoltClient) {
 
-	eap, err := extractEAP(recvpkt)
-	if err != nil {
-		eapolLogger.Errorf("%s", err)
+	eap, eapErr := extractEAP(pkt)
+
+	eapol, eapolErr := extractEAPOL(pkt)
+
+	if eapErr != nil && eapolErr != nil {
+		log.Fatalf("Failed to Extract EAP: %v - %v", eapErr, eapolErr)
+		return
 	}
 
-	log.WithFields(log.Fields{
-		"eap.Code": eap.Code,
-		"eap.Type": eap.Type,
-		"OnuId":    onuId,
-		"IntfId":   ponPortId,
-		"OnuSn":    serialNumber,
-	}).Tracef("HandleNextPacket")
+	fields := log.Fields{}
+	if eap != nil {
+		fields = log.Fields{
+			"Code":   eap.Code,
+			"Type":   eap.Type,
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}
+	} else if eapol != nil {
+		fields = log.Fields{
+			"Type":   eapol.Type,
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}
+	}
 
-	if eap.Code == layers.EAPCodeRequest && eap.Type == layers.EAPTypeIdentity {
+	log.WithFields(fields).Tracef("Handle Next EAPOL Packet")
+
+	if eapol != nil && eapol.Type == layers.EAPOLTypeStart {
+		identityRequest := createEAPIdentityRequest(1)
+		pkt := createEAPOLPkt(identityRequest, onuId, ponPortId)
+
+		if err := sendEapolPktOut(client, ponPortId, onuId, pkt); err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+				"error":  err,
+			}).Errorf("Error while sending EAPIdentityRequest packet")
+			return
+		}
+
+		log.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}).Infof("Sent EAPIdentityRequest packet")
+		return
+	} else if eap.Code == layers.EAPCodeRequest && eap.Type == layers.EAPTypeIdentity {
 		reseap := createEAPIdentityResponse(eap.Id)
 		pkt := createEAPOLPkt(reseap, onuId, ponPortId)
 
@@ -250,6 +341,27 @@
 			}).Errorf("Error while transitioning ONU State %v", err)
 		}
 
+	} else if eap.Code == layers.EAPCodeResponse && eap.Type == layers.EAPTypeIdentity {
+		senddata := getMD5Data(eap)
+		senddata = append([]byte{0x10}, senddata...)
+		challengeRequest := createEAPChallengeRequest(eap.Id, senddata)
+		pkt := createEAPOLPkt(challengeRequest, onuId, ponPortId)
+
+		if err := sendEapolPktOut(client, ponPortId, onuId, pkt); err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+				"error":  err,
+			}).Errorf("Error while sending EAPChallengeRequest packet")
+			return
+		}
+		log.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}).Infof("Sent EAPChallengeRequest packet")
+		return
 	} else if eap.Code == layers.EAPCodeRequest && eap.Type == layers.EAPTypeOTP {
 		senddata := getMD5Data(eap)
 		senddata = append([]byte{0x10}, senddata...)
@@ -276,6 +388,33 @@
 				"OnuSn":  serialNumber,
 			}).Errorf("Error while transitioning ONU State %v", err)
 		}
+	} else if eap.Code == layers.EAPCodeResponse && eap.Type == layers.EAPTypeOTP {
+		eapSuccess := createEAPSuccess(eap.Id)
+		pkt := createEAPOLPkt(eapSuccess, onuId, ponPortId)
+
+		if err := sendEapolPktOut(client, ponPortId, onuId, pkt); err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+				"error":  err,
+			}).Errorf("Error while sending EAPSuccess packet")
+			return
+		}
+
+		log.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}).Infof("Sent EAP Success packet")
+
+		if err := onuStateMachine.Event("send_dhcp_flow"); err != nil {
+			eapolLogger.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+			}).Errorf("Error while transitioning ONU State %v", err)
+		}
 	} else if eap.Code == layers.EAPCodeSuccess && eap.Type == layers.EAPTypeNone {
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,