blob: ce71069412a2a714579356b8d177139fcf9d0f16 [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 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"
log "github.com/sirupsen/logrus"
"net"
"reflect"
"strconv"
)
var GetGemPortId = omci.GetGemPortId
var dhcpLogger = log.WithFields(log.Fields{
"module": "DHCP",
})
var defaultParamsRequestList = []layers.DHCPOpt{
layers.DHCPOptSubnetMask,
layers.DHCPOptBroadcastAddr,
layers.DHCPOptTimeOffset,
layers.DHCPOptRouter,
layers.DHCPOptDomainName,
layers.DHCPOptDNS,
layers.DHCPOptDomainSearch,
layers.DHCPOptHostname,
layers.DHCPOptNetBIOSTCPNS,
layers.DHCPOptNetBIOSTCPScope,
layers.DHCPOptInterfaceMTU,
layers.DHCPOptClasslessStaticRoute,
layers.DHCPOptNTPServers,
}
func createDefaultDHCPReq(intfId uint32, onuId uint32, mac net.HardwareAddr) layers.DHCPv4 {
// NOTE we want to generate a unique XID, the easiest way is to concat the PON ID and the ONU ID
// we increment them by one otherwise:
// - PON: 0 ONU: 62 = 062 -> 62
// - PON: 6 ONU: 2 = 62 -> 62
xid, err := strconv.Atoi(fmt.Sprintf("%d%d", intfId+1, onuId+1))
if err != nil {
log.Fatal("Can't generate unique XID for ONU")
}
return layers.DHCPv4{
Operation: layers.DHCPOpRequest,
HardwareType: layers.LinkTypeEthernet,
HardwareLen: 6,
HardwareOpts: 0,
Xid: uint32(xid),
ClientHWAddr: mac,
}
}
func createDefaultOpts(intfId uint32, onuId uint32) []layers.DHCPOption {
hostname := []byte(fmt.Sprintf("%d.%d.bbsim.onf.org", intfId, onuId))
opts := []layers.DHCPOption{}
opts = append(opts, layers.DHCPOption{
Type: layers.DHCPOptHostname,
Data: hostname,
Length: uint8(len(hostname)),
})
bytes := []byte{}
for _, option := range defaultParamsRequestList {
bytes = append(bytes, byte(option))
}
opts = append(opts, layers.DHCPOption{
Type: layers.DHCPOptParamsRequest,
Data: bytes,
Length: uint8(len(bytes)),
})
return opts
}
func createDHCPDisc(intfId uint32, onuId uint32, macAddress net.HardwareAddr) *layers.DHCPv4 {
dhcpLayer := createDefaultDHCPReq(intfId, onuId, macAddress)
defaultOpts := createDefaultOpts(intfId, onuId)
dhcpLayer.Options = append([]layers.DHCPOption{layers.DHCPOption{
Type: layers.DHCPOptMessageType,
Data: []byte{byte(layers.DHCPMsgTypeDiscover)},
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, macAddress net.HardwareAddr, offeredIp net.IP) *layers.DHCPv4 {
dhcpLayer := createDefaultDHCPReq(intfId, onuId, macAddress)
defaultOpts := createDefaultOpts(intfId, onuId)
dhcpLayer.Options = append(defaultOpts, layers.DHCPOption{
Type: layers.DHCPOptMessageType,
Data: []byte{byte(layers.DHCPMsgTypeRequest)},
Length: 1,
})
data := []byte{182, 21, 0, 128}
dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
Type: layers.DHCPOptServerID,
Data: data,
Length: uint8(len(data)),
})
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)),
})
// 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: offeredIp,
Length: uint8(len(offeredIp)),
})
return &dhcpLayer
}
func serializeDHCPPacket(intfId uint32, onuId uint32, srcMac net.HardwareAddr, dhcp *layers.DHCPv4) ([]byte, error) {
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
ethernetLayer := &layers.Ethernet{
SrcMAC: srcMac,
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
EthernetType: layers.EthernetTypeIPv4,
}
ipLayer := &layers.IPv4{
Version: 4,
TOS: 0x10,
TTL: 128,
SrcIP: []byte{0, 0, 0, 0},
DstIP: []byte{255, 255, 255, 255},
Protocol: layers.IPProtocolUDP,
}
udpLayer := &layers.UDP{
SrcPort: 68,
DstPort: 67,
}
udpLayer.SetNetworkLayerForChecksum(ipLayer)
if err := gopacket.SerializeLayers(buffer, options, ethernetLayer, ipLayer, udpLayer, dhcp); err != nil {
return nil, err
}
bytes := buffer.Bytes()
return bytes, nil
}
func GetDhcpLayer(pkt gopacket.Packet) (*layers.DHCPv4, error) {
layerDHCP := pkt.Layer(layers.LayerTypeDHCPv4)
dhcp, _ := layerDHCP.(*layers.DHCPv4)
if dhcp == nil {
return nil, errors.New("Failed-to-extract-DHCP-layer")
}
return dhcp, nil
}
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.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)}) {
return layers.DHCPMsgTypeRelease, nil
} else {
msg := fmt.Sprintf("This type %x is not supported", option.Data)
return 0, errors.New(msg)
}
}
}
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)
if err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": msg.OnuId,
"IntfId": msg.IntfId,
}).Errorf("Can't retrieve GemPortId: %s", err)
return err
}
data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
IntfType: "pon",
IntfId: msg.IntfId,
GemportId: uint32(gemid),
Pkt: msg.Bytes,
PortNo: portNo,
}}
if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
dhcpLogger.Errorf("Fail to send DHCP PktInd indication. %v", err)
return err
}
return nil
}
func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, 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 {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
"OfferedIp": offeredIp.String(),
}).Errorf("Cannot serializeDHCPPacket: %s", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
// NOTE I don't think we need to tag the packet
//taggedPkt, err := packetHandlers.PushSingleTag(cTag, pkt)
msg := bbsim.ByteMsg{
IntfId: ponPortId,
OnuId: onuId,
Bytes: pkt,
}
if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Cannot sendDHCPPktIn: %s", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
"OfferedIp": offeredIp.String(),
}).Infof("DHCPRequest Sent")
return nil
}
func updateDhcpFailed(onuId uint32, ponPortId uint32, serialNumber string, onuStateMachine *fsm.FSM) error {
if err := onuStateMachine.Event("dhcp_failed"); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Error while transitioning ONU State %v", err)
return err
}
return nil
}
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, onuHwAddress)
pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
if err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Cannot serializeDHCPPacket: %s", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
// NOTE I don't think we need to tag the packet
//taggedPkt, err := packetHandlers.PushSingleTag(cTag, pkt)
msg := bbsim.ByteMsg{
IntfId: ponPortId,
OnuId: onuId,
Bytes: pkt,
}
if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Cannot sendDHCPPktIn: %s", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Infof("DHCPDiscovery Sent")
if err := onuStateMachine.Event("dhcp_discovery_sent"); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Error while transitioning ONU State %v", err)
}
return nil
}
// FIXME cTag is not used here
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)
if err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Can't get DHCP Layer from Packet: %v", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
dhcpMessageType, err := GetDhcpMessageType(dhcpLayer)
if err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Can't get DHCP Message Type from DHCP Layer: %v", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
if dhcpLayer.Operation == layers.DHCPOpReply {
if dhcpMessageType == layers.DHCPMsgTypeOffer {
offeredIp := dhcpLayer.YourClientIP
if err := sendDHCPRequest(ponPortId, onuId, serialNumber, portNo, onuStateMachine, onuHwAddress, offeredIp, stream); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Can't send DHCP Request: %s", err)
if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
return err
}
return err
}
if err := onuStateMachine.Event("dhcp_request_sent"); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Error while transitioning ONU State %v", err)
}
} else if dhcpMessageType == layers.DHCPMsgTypeAck {
// NOTE once the ack is received we don't need to do anything but change the state
if err := onuStateMachine.Event("dhcp_ack_received"); err != nil {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Errorf("Error while transitioning ONU State %v", err)
}
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Infof("DHCP State machine completed")
}
// NOTE do we need to care about DHCPMsgTypeRelease??
} else {
dhcpLogger.WithFields(log.Fields{
"OnuId": onuId,
"IntfId": ponPortId,
"OnuSn": serialNumber,
}).Warnf("Unsupported DHCP Operation: %s", dhcpLayer.Operation.String())
}
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
}