[SEBA-836] BBSim Reflector

Change-Id: Ib4ae5a2c24880dc62209bebb81188eca5f57865d
diff --git a/.gitignore b/.gitignore
index d0d8872..c904607 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
-
+/bbr
 /bbsim
 /bbsimctl
+logs/*
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 6a69c9f..9ff4ba7 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@
 dep: # @HELP Download the dependencies to the vendor folder
 	GO111MODULE=on go mod vendor
 
-build: dep protos fmt build-bbsim build-bbsimctl# @HELP Build the binary
+build: dep protos fmt build-bbsim build-bbsimctl build-bbr # @HELP Build the binary
 
 test: dep protos fmt # @HELP Execute unit tests
 	GO111MODULE=on go test -v -mod vendor ./... -covermode count -coverprofile ./tests/results/go-test-coverage.out 2>&1 | tee ./tests/results/go-test-results.out
@@ -50,6 +50,11 @@
 docker-run: # @HELP Run the container locally (intended for development purposes: DOCKER_RUN_ARGS="-pon 2 -onu 2" make docker-run)
 	docker run -p 50070:50070 -p 50060:50060 --privileged --rm --name bbsim ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}bbsim:${DOCKER_TAG} /app/bbsim ${DOCKER_RUN_ARGS}
 
+.PHONY: docs
+docs: # @HELP Generate docs and serve them in the browser
+	@echo "\033[36m Open your browser at localhost:6060 \033[0m"
+	godoc -http=localhost:6060
+
 help: # @HELP Print the command options
 	@echo
 	@echo "\033[0;31m    BroadBand Simulator (BBSim) \033[0m"
@@ -66,9 +71,17 @@
 
 
 # Internals
+build-bbr:
+	GO111MODULE=on go build -i -v -mod vendor \
+    	-ldflags "-w -X main.buildTime=$(shell date +”%Y/%m/%d-%H:%M:%S”) \
+    		-X main.commitHash=$(shell git log --pretty=format:%H -n 1) \
+    		-X main.gitStatus=${GIT_STATUS} \
+    		-X main.version=${VERSION}" \
+    	./cmd/bbr
+
 build-bbsim:
 	GO111MODULE=on go build -i -v -mod vendor \
-    	-ldflags "-X main.buildTime=$(shell date +”%Y/%m/%d-%H:%M:%S”) \
+    	-ldflags "-w -X main.buildTime=$(shell date +”%Y/%m/%d-%H:%M:%S”) \
     		-X main.commitHash=$(shell git log --pretty=format:%H -n 1) \
     		-X main.gitStatus=${GIT_STATUS} \
     		-X main.version=${VERSION}" \
@@ -76,7 +89,7 @@
 
 build-bbsimctl:
 	GO111MODULE=on go build -i -v -mod vendor \
-		-ldflags "-X github.com/opencord/bbsim/internal/bbsimctl/config.BuildTime=$(shell date +”%Y/%m/%d-%H:%M:%S”) \
+		-ldflags "-w -X github.com/opencord/bbsim/internal/bbsimctl/config.BuildTime=$(shell date +”%Y/%m/%d-%H:%M:%S”) \
 			-X github.com/opencord/bbsim/internal/bbsimctl/config.CommitHash=$(shell git log --pretty=format:%H -n 1) \
 			-X github.com/opencord/bbsim/internal/bbsimctl/config.GitStatus=${GIT_STATUS} \
 			-X github.com/opencord/bbsim/internal/bbsimctl/config.Version=${VERSION}" \
diff --git a/README.md b/README.md
index c63d5d7..b6dee44 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@
 3     up
 
 
-$ ./bbsimctl onus
+$ ./bbsimctl onu list
 PONPORTID    ID    SERIALNUMBER    STAG    CTAG    OPERSTATE    INTERNALSTATE
 0            1     BBSM00000001    900     900     up           eap_response_identity_sent
 0            2     BBSM00000002    900     901     up           eap_start_sent
diff --git a/build/package/Dockerfile b/build/package/Dockerfile
index e85888e..5ede6cc 100644
--- a/build/package/Dockerfile
+++ b/build/package/Dockerfile
@@ -47,7 +47,8 @@
 # copy and build
 COPY . ./
 RUN go mod vendor -v  # we can't vendor dependencies unless the code is there
-RUN make build
+RUN make build-bbsim
+RUN make build-bbsimctl
 
 # runtime parent
 FROM golang:1.12-stretch
@@ -60,7 +61,6 @@
  && apt-get install -y libpcap-dev isc-dhcp-server network-manager tcpdump\
  && ln -s /usr/lib/libpcap.so.1.8.1 /usr/lib/libpcap.so.0.8
 
-COPY ./configs/isc-dhcp-server /etc/default/
 COPY ./configs/dhcpd.conf /etc/dhcp/
 RUN mv /usr/sbin/dhcpd /usr/local/bin/ \
 && mv /sbin/dhclient /usr/local/bin/ \
diff --git a/cmd/bbr/bbr.go b/cmd/bbr/bbr.go
new file mode 100644
index 0000000..69b8639
--- /dev/null
+++ b/cmd/bbr/bbr.go
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+// BBR (BBSim Reflector) is a tool designed to scale test BBSim
+// It shared most of the code with BBSim itself and replies to messages
+// pretending to be VOLTHA.
+// The idea behind it is that, given that the BBSim and BBR are based on the same
+// codebase, BBR is acting as a wall for BBSim. And you can't beat the wall.
+package main
+
+import (
+	bbrdevices "github.com/opencord/bbsim/internal/bbr/devices"
+	"github.com/opencord/bbsim/internal/bbsim/devices"
+	"github.com/opencord/bbsim/internal/common"
+	log "github.com/sirupsen/logrus"
+	"os"
+	"runtime/pprof"
+	"time"
+)
+
+// usage
+func main() {
+	options := common.GetBBROpts()
+
+	common.SetLogLevel(log.StandardLogger(), options.LogLevel, options.LogCaller)
+
+	if *options.ProfileCpu != "" {
+		// start profiling
+		log.Infof("Creating profile file at: %s", *options.ProfileCpu)
+		f, err := os.Create(*options.ProfileCpu)
+		if err != nil {
+			log.Fatal(err)
+		}
+		pprof.StartCPUProfile(f)
+	}
+
+	log.WithFields(log.Fields{
+		"OltID":        options.OltID,
+		"NumNniPerOlt": options.NumNniPerOlt,
+		"NumPonPerOlt": options.NumPonPerOlt,
+		"NumOnuPerPon": options.NumOnuPerPon,
+	}).Info("BroadBand Reflector is on")
+
+	// NOTE this are probably useless in the MockOLT case, check if we can avoid using them in the CreateOlt method
+	oltDoneChannel := make(chan bool)
+	apiDoneChannel := make(chan bool)
+
+	// create the OLT device
+	olt := devices.CreateOLT(options.OltID, options.NumNniPerOlt, options.NumPonPerOlt, options.NumOnuPerPon, options.STag, options.CTagInit, &oltDoneChannel, &apiDoneChannel, true)
+	oltMock := bbrdevices.OltMock{
+		Olt:           olt,
+		TargetOnus:    options.NumPonPerOlt * options.NumOnuPerPon,
+		CompletedOnus: 0,
+		BBSimIp:       options.BBSimIp,
+		BBSimPort:     options.BBSimPort,
+		BBSimApiPort:  options.BBSimApiPort,
+	}
+
+	// start the enable sequence
+	startTime := time.Now()
+	defer func() {
+		endTime := time.Now()
+		runTime := endTime.Sub(startTime)
+		log.WithField("Duration", runTime).Info("BBR done!")
+	}()
+	oltMock.Start()
+}
diff --git a/cmd/bbsim/bbsim.go b/cmd/bbsim/bbsim.go
index f243801..2ceb50a 100644
--- a/cmd/bbsim/bbsim.go
+++ b/cmd/bbsim/bbsim.go
@@ -17,11 +17,10 @@
 package main
 
 import (
-	"flag"
 	"github.com/opencord/bbsim/api/bbsim"
 	"github.com/opencord/bbsim/internal/bbsim/api"
 	"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"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/reflection"
@@ -31,50 +30,6 @@
 	"sync"
 )
 
-type CliOptions struct {
-	OltID        int
-	NumNniPerOlt int
-	NumPonPerOlt int
-	NumOnuPerPon int
-	STag         int
-	CTagInit     int
-	profileCpu   *string
-	logLevel     string
-	logCaller    bool
-}
-
-func getOpts() *CliOptions {
-
-	olt_id := flag.Int("olt_id", 0, "Number of OLT devices to be emulated (default is 1)")
-	nni := flag.Int("nni", 1, "Number of NNI ports per OLT device to be emulated (default is 1)")
-	pon := flag.Int("pon", 1, "Number of PON ports per OLT device to be emulated (default is 1)")
-	onu := flag.Int("onu", 1, "Number of ONU devices per PON port to be emulated (default is 1)")
-
-	s_tag := flag.Int("s_tag", 900, "S-Tag value (default is 900)")
-	c_tag_init := flag.Int("c_tag", 900, "C-Tag starting value (default is 900), each ONU will get a sequentail one (targeting 1024 ONUs per BBSim instance the range is bug enough)")
-
-	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
-
-	logLevel := flag.String("logLevel", "debug", "Set the log level (trace, debug, info, warn, error)")
-	logCaller := flag.Bool("logCaller", false, "Whether to print the caller filename or not")
-
-	flag.Parse()
-
-	o := new(CliOptions)
-
-	o.OltID = int(*olt_id)
-	o.NumNniPerOlt = int(*nni)
-	o.NumPonPerOlt = int(*pon)
-	o.NumOnuPerPon = int(*onu)
-	o.STag = int(*s_tag)
-	o.CTagInit = int(*c_tag_init)
-	o.profileCpu = profileCpu
-	o.logLevel = *logLevel
-	o.logCaller = *logCaller
-
-	return o
-}
-
 func startApiServer(channel chan bool, group *sync.WaitGroup) {
 	// TODO make configurable
 	address := "0.0.0.0:50070"
@@ -110,14 +65,14 @@
 }
 
 func main() {
-	options := getOpts()
+	options := common.GetBBSimOpts()
 
-	bbsimLogger.SetLogLevel(log.StandardLogger(), options.logLevel, options.logCaller)
+	common.SetLogLevel(log.StandardLogger(), options.LogLevel, options.LogCaller)
 
-	if *options.profileCpu != "" {
+	if *options.ProfileCpu != "" {
 		// start profiling
-		log.Infof("Creating profile file at: %s", *options.profileCpu)
-		f, err := os.Create(*options.profileCpu)
+		log.Infof("Creating profile file at: %s", *options.ProfileCpu)
+		f, err := os.Create(*options.ProfileCpu)
 		if err != nil {
 			log.Fatal(err)
 		}
@@ -138,7 +93,8 @@
 	wg := sync.WaitGroup{}
 	wg.Add(2)
 
-	go devices.CreateOLT(options.OltID, options.NumNniPerOlt, options.NumPonPerOlt, options.NumOnuPerPon, options.STag, options.CTagInit, &oltDoneChannel, &apiDoneChannel, &wg)
+	olt := devices.CreateOLT(options.OltID, options.NumNniPerOlt, options.NumPonPerOlt, options.NumOnuPerPon, options.STag, options.CTagInit, &oltDoneChannel, &apiDoneChannel, false)
+	go devices.StartOlt(olt, &wg)
 	log.Debugf("Created OLT with id: %d", options.OltID)
 	go startApiServer(apiDoneChannel, &wg)
 	log.Debugf("Started APIService")
@@ -147,7 +103,7 @@
 
 	defer func() {
 		log.Info("BroadBand Simulator is off")
-		if *options.profileCpu != "" {
+		if *options.ProfileCpu != "" {
 			log.Info("Stopping profiler")
 			pprof.StopCPUProfile()
 		}
diff --git a/configs/dhcpd.conf b/configs/dhcpd.conf
index 06d366b..2ab1fe5 100644
--- a/configs/dhcpd.conf
+++ b/configs/dhcpd.conf
@@ -1,115 +1,17 @@
 #
 # Sample configuration file for ISC dhcpd for Debian
 #
-# Attention: If /etc/ltsp/dhcpd.conf exists, that will be used as
-# configuration file instead of this file.
-#
-#
 
-# The ddns-updates-style parameter controls whether or not the server will
-# attempt to do a DNS update when a lease is confirmed. We default to the
-# behavior of the version 2 packages ('none', since DHCP v2 didn't
-# have support for DDNS.)
 ddns-update-style none;
 
-# option definitions common to all supported networks...
 option domain-name "example.org";
 option domain-name-servers ns1.example.org, ns2.example.org;
 
 default-lease-time 600;
 max-lease-time 7200;
 
-# If this DHCP server is the official DHCP server for the local
-# network, the authoritative directive should be uncommented.
-#authoritative;
-
-# Use this to send dhcp log messages to a different log file (you also
-# have to hack syslog.conf to complete the redirection).
-log-facility local7;
-
-# No service will be given on this subnet, but declaring it helps the
-# DHCP server to understand the network topology.
-
-#subnet 10.152.187.0 netmask 255.255.255.0 {
-#}
-
 # This is a very basic subnet declaration.
-subnet 182.21.0.0 netmask 255.255.0.0 {
-  range 182.21.0.1 182.21.0.128;
-  option routers 182.21.0.254;
-}
-
-#subnet 10.254.239.0 netmask 255.255.255.224 {
-#  range 10.254.239.10 10.254.239.20;
-#  option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
-#}
-
-# This declaration allows BOOTP clients to get dynamic addresses,
-# which we don't really recommend.
-
-#subnet 10.254.239.32 netmask 255.255.255.224 {
-#  range dynamic-bootp 10.254.239.40 10.254.239.60;
-#  option broadcast-address 10.254.239.31;
-#  option routers rtr-239-32-1.example.org;
-#}
-
-# A slightly different configuration for an internal subnet.
-#subnet 10.5.5.0 netmask 255.255.255.224 {
-#  range 10.5.5.26 10.5.5.30;
-#  option domain-name-servers ns1.internal.example.org;
-#  option domain-name "internal.example.org";
-#  option subnet-mask 255.255.255.224;
-#  option routers 10.5.5.1;
-#  option broadcast-address 10.5.5.31;
-#  default-lease-time 600;
-#  max-lease-time 7200;
-#}
-
-# Hosts which require special configuration options can be listed in
-# host statements.   If no address is specified, the address will be
-# allocated dynamically (if possible), but the host-specific information
-# will still come from the host declaration.
-
-#host passacaglia {
-#  hardware ethernet 0:0:c0:5d:bd:95;
-#  filename "vmunix.passacaglia";
-#  server-name "toccata.fugue.com";
-#}
-
-# Fixed IP addresses can also be specified for hosts.   These addresses
-# should not also be listed as being available for dynamic assignment.
-# Hosts for which fixed IP addresses have been specified can boot using
-# BOOTP or DHCP.   Hosts for which no fixed address is specified can only
-# be booted with DHCP, unless there is an address range on the subnet
-# to which a BOOTP client is connected which has the dynamic-bootp flag
-# set.
-#host fantasia {
-#  hardware ethernet 08:00:07:26:c0:a5;
-#  fixed-address fantasia.fugue.com;
-#}
-
-# You can declare a class of clients and then do address allocation
-# based on that.   The example below shows a case where all clients
-# in a certain class get addresses on the 10.17.224/24 subnet, and all
-# other clients get addresses on the 10.0.29/24 subnet.
-
-#class "foo" {
-#  match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
-#}
-
-#shared-network 224-29 {
-#  subnet 10.17.224.0 netmask 255.255.255.0 {
-#    option routers rtr-224.example.org;
-#  }
-#  subnet 10.0.29.0 netmask 255.255.255.0 {
-#    option routers rtr-29.example.org;
-#  }
-#  pool {
-#    allow members of "foo";
-#    range 10.17.224.10 10.17.224.250;
-#  }
-#  pool {
-#    deny members of "foo";
-#    range 10.0.29.10 10.0.29.230;
-#  }
-#}
\ No newline at end of file
+subnet 192.168.0.0 netmask 255.255.0.0 {
+  range 192.168.0.1 192.168.253.254;
+  option routers 192.168.254.254;
+}
\ No newline at end of file
diff --git a/configs/isc-dhcp-server b/configs/isc-dhcp-server
deleted file mode 100644
index 0501355..0000000
--- a/configs/isc-dhcp-server
+++ /dev/null
@@ -1,22 +0,0 @@
-# Defaults for isc-dhcp-server initscript
-# sourced by /etc/init.d/isc-dhcp-server
-# installed at /etc/default/isc-dhcp-server by the maintainer scripts
-
-#
-# This is a POSIX shell fragment
-#
-
-# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
-#DHCPD_CONF=/etc/dhcp/dhcpd.conf
-
-# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
-#DHCPD_PID=/var/run/dhcpd.pid
-
-# Additional options to start dhcpd with.
-#	Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
-#OPTIONS=""
-
-# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
-#	Separate multiple interfaces with spaces, e.g. "eth0 eth1".
-INTERFACES="nni_north"
-
diff --git a/docs/README.md b/docs/README.md
index 710182d..6aed37b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,4 +1,5 @@
 # BBSim Documentation
 
 - [ONU State Machine](./onu-state-machine.md)
-- [Using development dependencies](./development-dependencies.md)
\ No newline at end of file
+- [Using development dependencies](./development-dependencies.md)
+- [BBR](./bbr.md)
\ No newline at end of file
diff --git a/docs/bbr.md b/docs/bbr.md
new file mode 100644
index 0000000..fafdf77
--- /dev/null
+++ b/docs/bbr.md
@@ -0,0 +1,54 @@
+# BBR
+
+To run `bbr` you need to have a `bbsim` instance running.
+
+You can start `bbsim` locally with:
+
+```bash
+$ DOCKER_RUN_ARGS="-onu 16 -pon 4" make docker-run
+```
+
+Once `bbsim` is up and running you'll see this on the console:
+
+```bash
+docker run -p 50070:50070 -p 50060:50060 --privileged --rm --name bbsim """"bbsim:0.0.2-dev /app/bbsim -onu 16 -pon 4
+time="2019-10-18T00:24:46Z" level=info msg="BroadBand Simulator is on" NumNniPerOlt=1 NumOnuPerPon=16 NumPonPerOlt=4 OltID=0
+time="2019-10-18T00:24:46Z" level=debug msg=CreateOLT ID=0 NumNni=1 NumOnuPerPon=16 NumPon=4 module=OLT
+time="2019-10-18T00:24:46Z" level=info msg="Successfully activated DHCP Server" module=NNI
+time="2019-10-18T00:24:46Z" level=debug msg="Created OLT with id: 0"
+time="2019-10-18T00:24:46Z" level=debug msg="Started APIService"
+time="2019-10-18T00:24:46Z" level=debug msg="APIServer Listening on: 0.0.0.0:50070"
+time="2019-10-18T00:24:46Z" level=debug msg="OLT Listening on: 0.0.0.0:50060" module=OLT
+```
+
+At this point you can start `bbr` (note that you need to pass the same number of ONUs and PON Ports to the two processes):
+
+```bash
+$ ./bbr -onu 16 -pon 4
+```
+
+`bbr` will run to completion and output the time it took to bring all the ONUs to the `dhcp_ack` state.
+If the `bbr` process doesn't exit, it means something went wrong.
+
+## Debugging and issue reporting
+
+If you are experiencing issues with `bbr` please capture:
+- `bbr` logs
+- `bbsim` logs
+- `bbsimctl onu list` output
+
+You can use these commands to capture `bbsim` and `bbr` logs during execution:
+
+```bash
+$ DOCKER_RUN_ARGS="-onu 16 -pon 4" make docker-run 2>&1 | tee bbsim.logs
+```
+
+```bash
+$ ./bbr -onu 16 -pon 4 2>&1 | tee bbr.logs
+```
+
+And this command to retrieve the list of onus in the system (run this command once `bbr` is hanging but before terminating `bbsim`):
+
+```bash
+docker exec bbsim bbsimctl onu list > onu.list
+```
\ No newline at end of file
diff --git a/go.mod b/go.mod
index a7c7b38..c3882e0 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,9 @@
 go 1.12
 
 require (
+	github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
+	github.com/cboling/omci v0.1.0
+	github.com/deckarep/golang-set v1.7.1 // indirect
 	github.com/fullstorydev/grpcurl v1.3.2 // indirect
 	github.com/golang/protobuf v1.3.2
 	github.com/google/gopacket v1.1.17
diff --git a/go.sum b/go.sum
index daa619e..e0ebc88 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,14 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
+github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
+github.com/cboling/omci v0.1.0 h1:hzsf8oomdIt6IWX6ZVj3p2zIP+GOyEEHD8b47KrA1MY=
+github.com/cboling/omci v0.1.0/go.mod h1:qE+T+qTEh/U1UaMidFdMv1eDOJ45WTKTBp2QmEvsWGQ=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
+github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
 github.com/fullstorydev/grpcurl v1.3.2 h1:cJKWsBYMocdxXQvgbnhtLG810SL5MhKT4K7BagxRih8=
 github.com/fullstorydev/grpcurl v1.3.2/go.mod h1:kvk8xPCXOrwVd9zYdjy+xSOT4YWm6kyth4Y9NMfBns4=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/internal/bbr/devices/olt.go b/internal/bbr/devices/olt.go
new file mode 100644
index 0000000..103be3b
--- /dev/null
+++ b/internal/bbr/devices/olt.go
@@ -0,0 +1,380 @@
+/*
+ * 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 devices
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/bbsim/internal/bbsim/devices"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
+	"github.com/opencord/bbsim/internal/common"
+	"github.com/opencord/voltha-protos/go/openolt"
+	log "github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+	"io"
+	"reflect"
+	"time"
+)
+
+type OltMock struct {
+	Olt          *devices.OltDevice
+	BBSimIp      string
+	BBSimPort    string
+	BBSimApiPort string
+
+	conn *grpc.ClientConn
+
+	TargetOnus    int
+	CompletedOnus int // Number of ONUs that have received a DHCPAck
+}
+
+// trigger an enable call and start the same listeners on the gRPC stream that VOLTHA would create
+// this method is blocking
+func (o *OltMock) Start() {
+	log.Info("Starting Mock OLT")
+
+	for _, pon := range o.Olt.Pons {
+		for _, onu := range pon.Onus {
+			log.Debugf("Created ONU: %s (%d:%d)", onu.Sn(), onu.STag, onu.CTag)
+		}
+	}
+
+	client, conn := Connect(o.BBSimIp, o.BBSimPort)
+	o.conn = conn
+	defer conn.Close()
+
+	deviceInfo, err := o.getDeviceInfo(client)
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"error": err,
+		}).Fatal("Can't read device info")
+	}
+
+	log.WithFields(log.Fields{
+		"Vendor":             deviceInfo.Vendor,
+		"Model":              deviceInfo.Model,
+		"DeviceSerialNumber": deviceInfo.DeviceSerialNumber,
+		"PonPorts":           deviceInfo.PonPorts,
+	}).Info("Retrieved device info")
+
+	o.readIndications(client)
+
+}
+
+func (o *OltMock) getDeviceInfo(client openolt.OpenoltClient) (*openolt.DeviceInfo, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+
+	return client.GetDeviceInfo(ctx, new(openolt.Empty))
+}
+
+func (o *OltMock) getOnuByTags(sTag int, cTag int) (*devices.Onu, error) {
+
+	for _, pon := range o.Olt.Pons {
+		for _, onu := range pon.Onus {
+			if onu.STag == sTag && onu.CTag == cTag {
+				return onu, nil
+			}
+		}
+	}
+
+	return nil, errors.New("cant-find-onu-by-c-s-tags")
+}
+
+func (o *OltMock) readIndications(client openolt.OpenoltClient) {
+	defer func() {
+		log.Info("OLT readIndications done")
+	}()
+
+	// Tell the OLT to start sending indications
+	indications, err := client.EnableIndication(context.Background(), new(openolt.Empty))
+	if err != nil {
+		log.WithFields(log.Fields{
+			"error": err,
+		}).Error("Failed to enable indication stream")
+		return
+	}
+
+	// listen for indications
+	for {
+		indication, err := indications.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+
+			// the connection is closed once we have sent the DHCP_ACK packet to all of the ONUs
+			// it means BBR completed, it's not an error
+
+			log.WithFields(log.Fields{
+				"error": err,
+			}).Debug("Failed to read from indications")
+			break
+		}
+
+		o.handleIndication(client, indication)
+	}
+}
+
+func (o *OltMock) handleIndication(client openolt.OpenoltClient, indication *openolt.Indication) {
+	switch indication.Data.(type) {
+	case *openolt.Indication_OltInd:
+		log.Info("Received Indication_OltInd")
+	case *openolt.Indication_IntfInd:
+		log.Info("Received Indication_IntfInd")
+	case *openolt.Indication_IntfOperInd:
+		log.Info("Received Indication_IntfOperInd")
+	case *openolt.Indication_OnuDiscInd:
+		onuDiscInd := indication.GetOnuDiscInd()
+		o.handleOnuDiscIndication(client, onuDiscInd)
+	case *openolt.Indication_OnuInd:
+		onuInd := indication.GetOnuInd()
+		o.handleOnuIndication(client, onuInd)
+	case *openolt.Indication_OmciInd:
+		omciIndication := indication.GetOmciInd()
+		o.handleOmciIndication(client, omciIndication)
+	case *openolt.Indication_PktInd:
+		pktIndication := indication.GetPktInd()
+		o.handlePktIndication(client, pktIndication)
+	case *openolt.Indication_PortStats:
+	case *openolt.Indication_FlowStats:
+	case *openolt.Indication_AlarmInd:
+	default:
+		log.WithFields(log.Fields{
+			"data": indication.Data,
+			"type": reflect.TypeOf(indication.Data),
+		}).Warn("Indication unsupported")
+	}
+}
+
+func (o *OltMock) handleOnuDiscIndication(client openolt.OpenoltClient, onuDiscInd *openolt.OnuDiscIndication) {
+	log.WithFields(log.Fields{
+		"IntfId":       onuDiscInd.IntfId,
+		"SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
+	}).Info("Received Onu discovery indication")
+
+	onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuDiscInd.SerialNumber))
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       onuDiscInd.IntfId,
+			"SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
+		}).Fatal("Cannot find ONU")
+	}
+
+	var pir uint32 = 1000000
+	Onu := openolt.Onu{
+		IntfId:       onu.PonPortID,
+		OnuId:        onu.ID,
+		SerialNumber: onu.SerialNumber,
+		Pir:          pir,
+	}
+
+	if _, err := client.ActivateOnu(context.Background(), &Onu); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       onuDiscInd.IntfId,
+			"SerialNumber": common.OnuSnToString(onuDiscInd.SerialNumber),
+		}).Error("Failed to activate ONU")
+	}
+}
+
+func (o *OltMock) handleOnuIndication(client openolt.OpenoltClient, onuInd *openolt.OnuIndication) {
+	log.WithFields(log.Fields{
+		"IntfId":       onuInd.IntfId,
+		"SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
+	}).Info("Received Onu indication")
+
+	onu, err := o.Olt.FindOnuBySn(common.OnuSnToString(onuInd.SerialNumber))
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       onuInd.IntfId,
+			"SerialNumber": common.OnuSnToString(onuInd.SerialNumber),
+		}).Fatal("Cannot find ONU")
+	}
+
+	go onu.ProcessOnuMessages(nil, client)
+
+	go func() {
+
+		defer func() {
+			log.WithFields(log.Fields{
+				"onuSn":         common.OnuSnToString(onuInd.SerialNumber),
+				"CompletedOnus": o.CompletedOnus,
+				"TargetOnus":    o.TargetOnus,
+			}).Debugf("Onu done")
+
+		}()
+
+		for message := range onu.DoneChannel {
+			if message == true {
+				o.CompletedOnus++
+				if o.CompletedOnus == o.TargetOnus {
+					// NOTE once all the ONUs are completed, exit
+					// closing the connection is not the most elegant way,
+					// but I haven't found any other way to stop
+					// the indications.Recv() infinite loop
+					log.Info("Simulation Done")
+					ValidateAndClose(o)
+				}
+
+				break
+			}
+		}
+
+	}()
+
+	// TODO change the state instead of calling an ONU method from here
+	onu.StartOmci(client)
+}
+
+func (o *OltMock) handleOmciIndication(client openolt.OpenoltClient, omciInd *openolt.OmciIndication) {
+
+	pon, err := o.Olt.GetPonById(omciInd.IntfId)
+	if err != nil {
+		log.WithFields(log.Fields{
+			"OnuId":  omciInd.OnuId,
+			"IntfId": omciInd.IntfId,
+			"err":    err,
+		}).Fatal("Can't find PonPort")
+	}
+	onu, _ := pon.GetOnuById(omciInd.OnuId)
+	if err != nil {
+		log.WithFields(log.Fields{
+			"OnuId":  omciInd.OnuId,
+			"IntfId": omciInd.IntfId,
+			"err":    err,
+		}).Fatal("Can't find Onu")
+	}
+
+	log.WithFields(log.Fields{
+		"IntfId": onu.PonPortID,
+		"OnuId":  onu.ID,
+		"OnuSn":  onu.Sn(),
+		"Pkt":    omciInd.Pkt,
+	}).Trace("Received Onu omci indication")
+
+	msg := devices.Message{
+		Type: devices.OmciIndication,
+		Data: devices.OmciIndicationMessage{
+			OnuSN:   onu.SerialNumber,
+			OnuID:   onu.ID,
+			OmciInd: omciInd,
+		},
+	}
+	onu.Channel <- msg
+}
+
+func (o *OltMock) handlePktIndication(client openolt.OpenoltClient, pktIndication *openolt.PacketIndication) {
+
+	pkt := gopacket.NewPacket(pktIndication.Pkt, layers.LayerTypeEthernet, gopacket.Default)
+
+	pktType, err := packetHandlers.IsEapolOrDhcp(pkt)
+
+	if err != nil {
+		log.Warnf("Ignoring packet as it's neither EAPOL or DHCP")
+		return
+	}
+
+	log.WithFields(log.Fields{
+		"IntfType":  pktIndication.IntfType,
+		"IntfId":    pktIndication.IntfId,
+		"GemportId": pktIndication.GemportId,
+		"FlowId":    pktIndication.FlowId,
+		"PortNo":    pktIndication.PortNo,
+		"Cookie":    pktIndication.Cookie,
+		"pktType":   pktType,
+	}).Trace("Received PktIndication")
+
+	msg := devices.Message{}
+	if pktIndication.IntfType == "nni" {
+		// This is an packet that is arriving from the NNI and needs to be sent to an ONU
+		// in this case we need to fin the ONU from the C/S tags
+		// TODO: handle errors in the untagging process
+		sTag, _ := packetHandlers.GetVlanTag(pkt)
+		singleTagPkt, _ := packetHandlers.PopSingleTag(pkt)
+		cTag, _ := packetHandlers.GetVlanTag(singleTagPkt)
+
+		onu, err := o.getOnuByTags(int(sTag), int(cTag))
+
+		if err != nil {
+			log.WithFields(log.Fields{
+				"sTag": sTag,
+				"cTag": cTag,
+			}).Fatalf("Can't find ONU from c/s tags")
+		}
+
+		msg = devices.Message{
+			Type: devices.OnuPacketIn,
+			Data: devices.OnuPacketMessage{
+				IntfId: pktIndication.IntfId,
+				OnuId:  onu.ID,
+				Packet: pkt,
+				Type:   pktType,
+			},
+		}
+		// NOTE we send it on the ONU channel so that is handled as all the others packets in a separate thread
+		onu.Channel <- msg
+	} else {
+		// TODO a very similar construct is used in many places,
+		// abstract this in an OLT method
+		pon, err := o.Olt.GetPonById(pktIndication.IntfId)
+		if err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  pktIndication.PortNo,
+				"IntfId": pktIndication.IntfId,
+				"err":    err,
+			}).Fatal("Can't find PonPort")
+		}
+		onu, err := pon.GetOnuById(pktIndication.PortNo)
+		if err != nil {
+			log.WithFields(log.Fields{
+				"OnuId":  pktIndication.PortNo,
+				"IntfId": pktIndication.IntfId,
+				"err":    err,
+			}).Fatal("Can't find Onu")
+		}
+		// NOTE when we push the EAPOL flow we set the PortNo = OnuId for convenience sake
+		// BBsim responds setting the port number that was sent with the flow
+		msg = devices.Message{
+			Type: devices.OnuPacketIn,
+			Data: devices.OnuPacketMessage{
+				IntfId: pktIndication.IntfId,
+				OnuId:  pktIndication.PortNo,
+				Packet: pkt,
+				Type:   pktType,
+			},
+		}
+		onu.Channel <- msg
+	}
+}
+
+// TODO Move in a different file
+func Connect(ip string, port string) (openolt.OpenoltClient, *grpc.ClientConn) {
+	server := fmt.Sprintf("%s:%s", ip, port)
+	conn, err := grpc.Dial(server, grpc.WithInsecure())
+
+	if err != nil {
+		log.Fatalf("did not connect: %v", err)
+		return nil, conn
+	}
+	return openolt.NewOpenoltClient(conn), conn
+}
diff --git a/internal/bbr/devices/validate.go b/internal/bbr/devices/validate.go
new file mode 100644
index 0000000..cb2551f
--- /dev/null
+++ b/internal/bbr/devices/validate.go
@@ -0,0 +1,78 @@
+/*
+ * 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 devices
+
+import (
+	"context"
+	"fmt"
+	"github.com/opencord/bbsim/api/bbsim"
+	log "github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+	"time"
+)
+
+func ValidateAndClose(olt *OltMock) {
+
+	// connect to the BBSim control APIs to check that all the ONUs are in the correct state
+	client, conn := ApiConnect(olt.BBSimIp, olt.BBSimApiPort)
+	defer conn.Close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+
+	onus, err := client.GetONUs(ctx, &bbsim.Empty{})
+
+	if err != nil {
+		log.WithFields(log.Fields{
+			"error": err,
+		}).Fatalf("Can't reach BBSim API")
+	}
+
+	expectedState := "dhcp_ack_received"
+
+	res := true
+	for _, onu := range onus.Items {
+		if onu.InternalState != expectedState {
+			res = false
+			log.WithFields(log.Fields{
+				"OnuSN":         onu.SerialNumber,
+				"OnuId":         onu.ID,
+				"InternalState": onu.InternalState,
+				"ExpectedSatte": expectedState,
+			}).Error("Not matching expected state")
+		}
+	}
+
+	if res == true {
+		log.WithFields(log.Fields{
+			"ExpectedState": expectedState,
+		}).Infof("%d ONUs matching expected state", len(onus.Items))
+	}
+
+	olt.conn.Close()
+}
+
+func ApiConnect(ip string, port string) (bbsim.BBSimClient, *grpc.ClientConn) {
+	server := fmt.Sprintf("%s:%s", ip, port)
+	conn, err := grpc.Dial(server, grpc.WithInsecure())
+
+	if err != nil {
+		log.Fatalf("did not connect: %v", err)
+		return nil, conn
+	}
+	return bbsim.NewBBSimClient(conn), conn
+}
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/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,
diff --git a/internal/bbsimctl/commands/onu.go b/internal/bbsimctl/commands/onu.go
index 4f222f2..716c7d3 100644
--- a/internal/bbsimctl/commands/onu.go
+++ b/internal/bbsimctl/commands/onu.go
@@ -31,7 +31,7 @@
 )
 
 const (
-	DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .STag }}\t{{ .CTag }}\t{{ .OperState }}\t{{ .InternalState }}"
+	DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .HwAddress }}\t{{ .STag }}\t{{ .CTag }}\t{{ .OperState }}\t{{ .InternalState }}"
 )
 
 type OnuSnString string
diff --git a/internal/common/helpers.go b/internal/common/helpers.go
new file mode 100644
index 0000000..6ba022f
--- /dev/null
+++ b/internal/common/helpers.go
@@ -0,0 +1,30 @@
+/*
+ * 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 common
+
+import (
+	"github.com/opencord/voltha-protos/go/openolt"
+	"strconv"
+)
+
+func OnuSnToString(sn *openolt.SerialNumber) string {
+	s := string(sn.VendorId)
+	for _, i := range sn.VendorSpecific {
+		s = s + strconv.FormatInt(int64(i/16), 16) + strconv.FormatInt(int64(i%16), 16)
+	}
+	return s
+}
diff --git a/internal/bbsim/logger/logger.go b/internal/common/logger.go
similarity index 98%
rename from internal/bbsim/logger/logger.go
rename to internal/common/logger.go
index c6036fc..cc6e48d 100644
--- a/internal/bbsim/logger/logger.go
+++ b/internal/common/logger.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package logger
+package common
 
 import log "github.com/sirupsen/logrus"
 
diff --git a/internal/bbsim/logger/logger_test.go b/internal/common/logger_test.go
similarity index 72%
rename from internal/bbsim/logger/logger_test.go
rename to internal/common/logger_test.go
index d692135..6e9de8a 100644
--- a/internal/bbsim/logger/logger_test.go
+++ b/internal/common/logger_test.go
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package logger_test
+package common_test
 
 import (
-	bbsimLogger "github.com/opencord/bbsim/internal/bbsim/logger"
+	"github.com/opencord/bbsim/internal/common"
 	"github.com/sirupsen/logrus"
 	"gotest.tools/assert"
 	"testing"
@@ -26,31 +26,31 @@
 func Test_SetLogLevel(t *testing.T) {
 	log := logrus.New()
 
-	bbsimLogger.SetLogLevel(log, "trace", false)
+	common.SetLogLevel(log, "trace", false)
 	assert.Equal(t, log.Level, logrus.TraceLevel)
 
-	bbsimLogger.SetLogLevel(log, "debug", false)
+	common.SetLogLevel(log, "debug", false)
 	assert.Equal(t, log.Level, logrus.DebugLevel)
 
-	bbsimLogger.SetLogLevel(log, "info", false)
+	common.SetLogLevel(log, "info", false)
 	assert.Equal(t, log.Level, logrus.InfoLevel)
 
-	bbsimLogger.SetLogLevel(log, "warn", false)
+	common.SetLogLevel(log, "warn", false)
 	assert.Equal(t, log.Level, logrus.WarnLevel)
 
-	bbsimLogger.SetLogLevel(log, "error", false)
+	common.SetLogLevel(log, "error", false)
 	assert.Equal(t, log.Level, logrus.ErrorLevel)
 
-	bbsimLogger.SetLogLevel(log, "foobar", false)
+	common.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)
+	common.SetLogLevel(log, "debug", true)
 	assert.Equal(t, log.ReportCaller, true)
 
-	bbsimLogger.SetLogLevel(log, "debug", false)
+	common.SetLogLevel(log, "debug", false)
 	assert.Equal(t, log.ReportCaller, false)
 }
diff --git a/internal/common/omci/mibpackets.go b/internal/common/omci/mibpackets.go
new file mode 100755
index 0000000..99ebb56
--- /dev/null
+++ b/internal/common/omci/mibpackets.go
@@ -0,0 +1,238 @@
+/*
+ * 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 omci
+
+import (
+	"encoding/hex"
+	"github.com/cboling/omci"
+	me "github.com/cboling/omci/generated"
+	"github.com/google/gopacket"
+	omcisim "github.com/opencord/omci-sim"
+	log "github.com/sirupsen/logrus"
+)
+
+var omciLogger = log.WithFields(log.Fields{
+	"module": "OMCI",
+})
+
+const galEthernetEID = uint16(1)
+const maxGemPayloadSize = uint16(48)
+const gemEID = uint16(1)
+
+type txFrameCreator func() ([]byte, error)
+type rxFrameParser func(gopacket.Packet) error
+
+type ServiceStep struct {
+	MakeTxFrame txFrameCreator
+	RxHandler   rxFrameParser
+}
+
+func serialize(msgType omci.MessageType, request gopacket.SerializableLayer, tid uint16) ([]byte, error) {
+	omciLayer := &omci.OMCI{
+		TransactionID: tid,
+		MessageType:   msgType,
+	}
+	var options gopacket.SerializeOptions
+	options.FixLengths = true
+
+	buffer := gopacket.NewSerializeBuffer()
+	err := gopacket.SerializeLayers(buffer, options, omciLayer, request)
+	if err != nil {
+		return nil, err
+	}
+	return buffer.Bytes(), nil
+}
+
+func hexEncode(omciPkt []byte) ([]byte, error) {
+	dst := make([]byte, hex.EncodedLen(len(omciPkt)))
+	hex.Encode(dst, omciPkt)
+	return dst, nil
+}
+
+func DecodeOmci(payload []byte) (omci.MessageType, gopacket.Packet) {
+	// Perform base OMCI decode (common to all requests)
+	packet := gopacket.NewPacket(payload, omci.LayerTypeOMCI, gopacket.NoCopy)
+
+	if omciLayer := packet.Layer(omci.LayerTypeOMCI); omciLayer != nil {
+
+		omciObj, omciOk := omciLayer.(*omci.OMCI)
+		if !omciOk {
+			panic("Not Expected") // TODO: Do something better or delete...
+		}
+		if byte(omciObj.MessageType) & ^me.AK == 0 {
+			// Not a response, silently discard
+			return 0, nil
+		}
+		return omciObj.MessageType, packet
+	}
+
+	// FIXME
+	// if we can't properly decode the packet, try using shad helper method
+	// most likely this won't be necessary once we move omci-sim to use cboling/omci
+	// to generate packets
+	_, _, msgType, _, _, _, err := omcisim.ParsePkt(payload)
+	if err != nil {
+		return 0, nil
+	}
+	if msgType == omcisim.MibReset {
+		return omci.MibResetResponseType, nil
+	}
+	if msgType == omcisim.MibUpload {
+		return omci.MibUploadResponseType, nil
+	}
+	if msgType == omcisim.MibUploadNext {
+		return omci.MibUploadNextResponseType, nil
+	}
+	if msgType == omcisim.Create {
+		return omci.CreateResponseType, nil
+	}
+	if msgType == omcisim.Set {
+		return omci.SetResponseType, nil
+	}
+
+	omciLogger.Warnf("omci-sim returns msgType: %d", msgType)
+
+	return 0, nil
+}
+
+func CreateMibResetRequest(tid uint16) ([]byte, error) {
+
+	request := &omci.MibResetRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass: me.OnuDataClassId,
+		},
+	}
+	pkt, err := serialize(omci.MibResetRequestType, request, tid)
+	if err != nil {
+		omciLogger.WithFields(log.Fields{
+			"Err": err,
+		}).Fatalf("Cannot serialize MibResetRequest")
+		return nil, err
+	}
+	return hexEncode(pkt)
+}
+
+func CreateMibUploadRequest(tid uint16) ([]byte, error) {
+	request := &omci.MibUploadRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass: me.OnuDataClassId,
+			// Default Instance ID is 0
+		},
+	}
+	pkt, err := serialize(omci.MibUploadRequestType, request, tid)
+	if err != nil {
+		omciLogger.WithFields(log.Fields{
+			"Err": err,
+		}).Fatalf("Cannot serialize MibUploadRequest")
+		return nil, err
+	}
+	return hexEncode(pkt)
+}
+
+func CreateMibUploadNextRequest(tid uint16, seqNumber uint16) ([]byte, error) {
+
+	request := &omci.MibUploadNextRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass: me.OnuDataClassId,
+			// Default Instance ID is 0
+		},
+		CommandSequenceNumber: seqNumber,
+	}
+	pkt, err := serialize(omci.MibUploadNextRequestType, request, tid)
+
+	if err != nil {
+		omciLogger.WithFields(log.Fields{
+			"Err": err,
+		}).Fatalf("Cannot serialize MibUploadNextRequest")
+		return nil, err
+	}
+	return hexEncode(pkt)
+}
+
+// TODO understand and refactor
+
+func CreateGalEnetRequest(tid uint16) ([]byte, error) {
+	params := me.ParamData{
+		EntityID:   galEthernetEID,
+		Attributes: me.AttributeValueMap{"MaximumGemPayloadSize": maxGemPayloadSize},
+	}
+	meDef, _ := me.NewGalEthernetProfile(params)
+	pkt, err := omci.GenFrame(meDef, omci.CreateRequestType, omci.TransactionID(tid))
+	if err != nil {
+		omciLogger.WithField("err", err).Fatalf("Can't generate GalEnetRequest")
+	}
+	return hexEncode(pkt)
+}
+
+func CreateEnableUniRequest(tid uint16, uniId uint16, enabled bool, isPtp bool) ([]byte, error) {
+
+	var _enabled uint8
+	if enabled {
+		_enabled = uint8(1)
+	} else {
+		_enabled = uint8(0)
+	}
+
+	data := me.ParamData{
+		EntityID: uniId,
+		Attributes: me.AttributeValueMap{
+			"AdministrativeState": _enabled,
+		},
+	}
+	var medef *me.ManagedEntity
+	var omciErr me.OmciErrors
+
+	if isPtp {
+		medef, omciErr = me.NewPhysicalPathTerminationPointEthernetUni(data)
+	} else {
+		medef, omciErr = me.NewVirtualEthernetInterfacePoint(data)
+	}
+	if omciErr != nil {
+		return nil, omciErr.GetError()
+	}
+	pkt, err := omci.GenFrame(medef, omci.SetRequestType, omci.TransactionID(tid))
+	if err != nil {
+		omciLogger.WithField("err", err).Fatalf("Can't generate EnableUniRequest")
+	}
+	return hexEncode(pkt)
+}
+
+func CreateGemPortRequest(tid uint16) ([]byte, error) {
+	params := me.ParamData{
+		EntityID: gemEID,
+		Attributes: me.AttributeValueMap{
+			"PortId":                              1,
+			"TContPointer":                        1,
+			"Direction":                           0,
+			"TrafficManagementPointerForUpstream": 0,
+			"TrafficDescriptorProfilePointerForUpstream": 0,
+			"UniCounter":                                   0,
+			"PriorityQueuePointerForDownStream":            0,
+			"EncryptionState":                              0,
+			"TrafficDescriptorProfilePointerForDownstream": 0,
+			"EncryptionKeyRing":                            0,
+		},
+	}
+	meDef, _ := me.NewGemPortNetworkCtp(params)
+	pkt, err := omci.GenFrame(meDef, omci.CreateRequestType, omci.TransactionID(tid))
+	if err != nil {
+		omciLogger.WithField("err", err).Fatalf("Can't generate GemPortRequest")
+	}
+	return hexEncode(pkt)
+}
+
+// END TODO
diff --git a/internal/common/options.go b/internal/common/options.go
new file mode 100644
index 0000000..46c8752
--- /dev/null
+++ b/internal/common/options.go
@@ -0,0 +1,88 @@
+/*
+ * 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 common
+
+import "flag"
+
+type BBSimCliOptions struct {
+	OltID        int
+	NumNniPerOlt int
+	NumPonPerOlt int
+	NumOnuPerPon int
+	STag         int
+	CTagInit     int
+	ProfileCpu   *string
+	LogLevel     string
+	LogCaller    bool
+}
+
+type BBRCliOptions struct {
+	*BBSimCliOptions
+	BBSimIp      string
+	BBSimPort    string
+	BBSimApiPort string
+}
+
+func GetBBSimOpts() *BBSimCliOptions {
+
+	olt_id := flag.Int("olt_id", 0, "Number of OLT devices to be emulated")
+	nni := flag.Int("nni", 1, "Number of NNI ports per OLT device to be emulated")
+	pon := flag.Int("pon", 1, "Number of PON ports per OLT device to be emulated")
+	onu := flag.Int("onu", 1, "Number of ONU devices per PON port to be emulated")
+
+	s_tag := flag.Int("s_tag", 900, "S-Tag value")
+	c_tag_init := flag.Int("c_tag", 900, "C-Tag starting value, each ONU will get a sequential one (targeting 1024 ONUs per BBSim instance the range is big enough)")
+
+	profileCpu := flag.String("cpuprofile", "", "write cpu profile to file")
+
+	logLevel := flag.String("logLevel", "debug", "Set the log level (trace, debug, info, warn, error)")
+	logCaller := flag.Bool("logCaller", false, "Whether to print the caller filename or not")
+
+	flag.Parse()
+
+	o := new(BBSimCliOptions)
+
+	o.OltID = int(*olt_id)
+	o.NumNniPerOlt = int(*nni)
+	o.NumPonPerOlt = int(*pon)
+	o.NumOnuPerPon = int(*onu)
+	o.STag = int(*s_tag)
+	o.CTagInit = int(*c_tag_init)
+	o.ProfileCpu = profileCpu
+	o.LogLevel = *logLevel
+	o.LogCaller = *logCaller
+
+	return o
+}
+
+func GetBBROpts() BBRCliOptions {
+
+	bbsimIp := flag.String("bbsimIp", "127.0.0.1", "BBSim IP")
+	bbsimPort := flag.String("bbsimPort", "50060", "BBSim Port")
+	bbsimApiPort := flag.String("bbsimApiPort", "50070", "BBSim API Port")
+
+	options := GetBBSimOpts()
+
+	bbrOptions := BBRCliOptions{
+		options,
+		*bbsimIp,
+		*bbsimPort,
+		*bbsimApiPort,
+	}
+
+	return bbrOptions
+}