blob: 4aa1c65bcbf5603a988263d1c65019db247ada21 [file] [log] [blame]
/*
* 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 (
"fmt"
"net"
"github.com/google/gopacket/layers"
"github.com/looplab/fsm"
"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
bbsimTypes "github.com/opencord/bbsim/internal/bbsim/types"
"github.com/opencord/bbsim/internal/common"
omcilib "github.com/opencord/bbsim/internal/common/omci"
log "github.com/sirupsen/logrus"
)
var uniLogger = log.WithFields(log.Fields{
"module": "UNI",
})
const (
maxUniPorts = 4
UniStateUp = "up"
UniStateDown = "down"
uniTxEnable = "enable"
uniTxDisable = "disable"
)
type UniPortIf interface {
GetID() uint32
StorePortNo(portNo uint32)
UpdateStream(stream bbsimTypes.Stream)
Enable() error
Disable() error
HandlePackets() // start listening on the PacketCh
HandleAuth() // Sends the EapoStart packet
HandleDhcp(pbit uint8, cTag int) // Sends the DHCPDiscover packet
}
type UniPort struct {
ID uint32
MeId omcilib.EntityID
PortNo uint32
OperState *fsm.FSM
Onu *Onu
Services []ServiceIf
logger *log.Entry
PacketCh chan bbsimTypes.OnuPacketMessage // handle packets
}
func NewUniPort(ID uint32, onu *Onu, nextCtag map[string]int, nextStag map[string]int) (*UniPort, error) {
// IDs starts from 0, thus the maximum UNI supported is maxUniPorts - 1
if ID > (maxUniPorts - 1) {
return nil, fmt.Errorf("%d-is-higher-than-the-maximum-supported-unis-%d", ID, maxUniPorts)
}
uni := UniPort{
ID: ID,
Onu: onu,
MeId: omcilib.GenerateUniPortEntityId(ID + 1),
}
uni.logger = uniLogger.WithFields(log.Fields{
"UniId": uni.ID,
"OnuSn": onu.Sn(),
})
uni.OperState = fsm.NewFSM(
"down",
fsm.Events{
{Name: uniTxEnable, Src: []string{UniStateDown}, Dst: UniStateUp},
{Name: uniTxDisable, Src: []string{UniStateUp}, Dst: UniStateDown},
},
fsm.Callbacks{
"enter_state": func(e *fsm.Event) {
uni.logger.Debugf("changing-uni-operstate-from-%s-to-%s", e.Src, e.Dst)
},
fmt.Sprintf("enter_%s", UniStateUp): func(e *fsm.Event) {
msg := bbsimTypes.Message{
Type: bbsimTypes.UniStatusAlarm,
Data: bbsimTypes.UniStatusAlarmMessage{
OnuSN: uni.Onu.SerialNumber,
OnuID: uni.Onu.ID,
AdminState: 0,
EntityID: uni.MeId.ToUint16(),
RaiseOMCIAlarm: false, // never raise an LOS when enabling a UNI
},
}
uni.Onu.Channel <- msg
go uni.HandlePackets()
for _, s := range uni.Services {
s.Initialize(uni.Onu.PonPort.Olt.OpenoltStream)
}
},
fmt.Sprintf("enter_%s", UniStateDown): func(e *fsm.Event) {
msg := bbsimTypes.Message{
Type: bbsimTypes.UniStatusAlarm,
Data: bbsimTypes.UniStatusAlarmMessage{
OnuSN: uni.Onu.SerialNumber,
OnuID: uni.Onu.ID,
AdminState: 1,
EntityID: uni.MeId.ToUint16(),
RaiseOMCIAlarm: true, // raise an LOS when disabling a UNI
},
}
uni.Onu.Channel <- msg
for _, s := range uni.Services {
s.Disable()
}
},
},
)
for k, s := range common.Services {
// find the correct cTag for this service
if _, ok := nextCtag[s.Name]; !ok {
// it's the first time we iterate over this service,
// so we start from the config value
nextCtag[s.Name] = s.CTag
} else {
// we have a previous value, so we check it
// if Allocation is unique, we increment,
// otherwise (shared) we do nothing
if s.CTagAllocation == common.TagAllocationUnique.String() {
nextCtag[s.Name] = nextCtag[s.Name] + 1
// the max valid value for a tag is 4096
// check we're not going over
if nextCtag[s.Name] > 4096 {
uni.logger.WithFields(log.Fields{
"cTag": nextCtag[s.Name],
"Service": s.Name,
}).Fatal("c-tag-limit-reached-too-many-subscribers")
}
}
}
// find the correct sTag for this service
if _, ok := nextStag[s.Name]; !ok {
nextStag[s.Name] = s.STag
} else {
if s.STagAllocation == common.TagAllocationUnique.String() {
nextStag[s.Name] = nextStag[s.Name] + 1
// the max valid value for a tag is 4096
// check we're not going over
if nextStag[s.Name] > 4096 {
uni.logger.WithFields(log.Fields{
"sTag": nextStag[s.Name],
"Service": s.Name,
}).Fatal("s-tag-limit-reached-too-many-subscribers")
}
}
}
mac := net.HardwareAddr{0x2e, byte(olt.ID), byte(onu.PonPortID), byte(onu.ID), byte(uni.ID), byte(k)}
service, err := NewService(uint32(k), s.Name, mac, &uni, nextCtag[s.Name], nextStag[s.Name],
s.NeedsEapol, s.NeedsDhcp, s.NeedsIgmp, s.NeedsPPPoE, s.TechnologyProfileID, s.UniTagMatch,
s.ConfigureMacAddress, s.EnableMacLearning, s.UsPonCTagPriority, s.UsPonSTagPriority,
s.DsPonCTagPriority, s.DsPonSTagPriority)
if err != nil {
oltLogger.WithFields(log.Fields{
"Err": err.Error(),
}).Fatal("Can't create Service")
}
uni.Services = append(uni.Services, service)
}
uni.PacketCh = make(chan bbsimTypes.OnuPacketMessage)
return &uni, nil
}
func (u *UniPort) GetID() uint32 {
return u.ID
}
func (u *UniPort) StorePortNo(portNo uint32) {
u.PortNo = portNo
u.logger.WithFields(log.Fields{
"PortNo": portNo,
}).Debug("logical-port-number-added-to-uni")
}
func (u *UniPort) UpdateStream(stream bbsimTypes.Stream) {
for _, service := range u.Services {
service.UpdateStream(stream)
}
}
func (u *UniPort) Enable() error {
return u.OperState.Event(uniTxEnable)
}
func (u *UniPort) Disable() error {
if u.OperState.Is(UniStateDown) {
return nil
}
return u.OperState.Event(uniTxDisable)
}
// this method simply forwards the packet to the correct service
func (u *UniPort) HandlePackets() {
u.logger.Debug("listening-on-uni-packet-channel")
defer func() {
u.logger.Debug("done-listening-on-uni-packet-channel")
}()
for msg := range u.PacketCh {
u.logger.WithFields(log.Fields{
"messageType": msg.Type,
}).Trace("received-message-on-uni-packet-channel")
if msg.Type == packetHandlers.EAPOL || msg.Type == packetHandlers.DHCP {
service, err := u.findServiceByMacAddress(msg.MacAddress)
if err != nil {
u.logger.WithFields(log.Fields{"err": err}).Error("cannot-process-uni-pkt")
continue
}
service.PacketCh <- msg
} else if msg.Type == packetHandlers.IGMP {
//IGMP packets don't refer to any Mac Address, thus
//if it's an IGMP packet we assume we have a single IGMP service
for _, s := range u.Services {
service := s.(*Service)
if service.NeedsIgmp {
service.PacketCh <- msg
}
}
}
}
}
func (u *UniPort) HandleAuth() {
for _, s := range u.Services {
s.HandleAuth()
}
}
func (u *UniPort) HandleDhcp(oPbit uint8, oVid int) {
for _, s := range u.Services {
s.HandleDhcp(oPbit, oVid)
}
}
func (u *UniPort) addGemPortToService(gemport uint32, ethType uint32, oVlan uint32, iVlan uint32) {
for _, s := range u.Services {
if service, ok := s.(*Service); ok {
// EAPOL is a strange case, as packets are untagged
// but we assume we will have a single service requiring EAPOL
if ethType == uint32(layers.EthernetTypeEAPOL) && service.NeedsEapol {
service.GemPort = gemport
}
// For DHCP services we single tag the outgoing packets,
// thus the flow only contains the CTag and we can use that to match the service
if ethType == uint32(layers.EthernetTypeIPv4) && service.NeedsDhcp && service.CTag == int(oVlan) {
service.GemPort = gemport
}
// for dataplane services match both C and S tags
if service.CTag == int(iVlan) && service.STag == int(oVlan) {
service.GemPort = gemport
}
// for loggin purpose only
if service.GemPort == gemport {
// TODO move to Trace level
u.logger.WithFields(log.Fields{
"OnuId": service.UniPort.Onu.ID,
"IntfId": service.UniPort.Onu.PonPortID,
"OnuSn": service.UniPort.Onu.Sn(),
"Name": service.Name,
"PortNo": service.UniPort.PortNo,
"UniId": service.UniPort.ID,
}).Debug("gem-port-added-to-service")
}
}
}
}
func (u *UniPort) findServiceByMacAddress(macAddress net.HardwareAddr) (*Service, error) {
for _, s := range u.Services {
service := s.(*Service)
if service.HwAddress.String() == macAddress.String() {
return service, nil
}
}
return nil, fmt.Errorf("cannot-find-service-with-mac-address-%s", macAddress.String())
}