blob: b885ec523477da7326b0983c6536705a3c099195 [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 core
import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/rand"
"net"
"reflect"
"sync"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/opencord/voltha-bbsim/common/logger"
)
// Constants for DHCP states
const (
DHCP_INIT clientState = iota + 1
DHCP_SELECTING
DHCP_REQUESTING
DHCP_BOUND
)
type dhcpResponder struct {
clients map[clientKey]*dhcpClientInstance
dhcpIn chan *byteMsg
}
type dhcpClientInstance struct {
key clientKey
srcaddr *net.HardwareAddr
srcIP *net.IPAddr
serverIP *net.IPAddr
hostname string
curId uint32
curState clientState
}
var dhcpresp *dhcpResponder
var dhcponce sync.Once
func getDHCPResponder() *dhcpResponder {
dhcponce.Do(func() {
dhcpresp = &dhcpResponder{clients: make(map[clientKey]*dhcpClientInstance), dhcpIn: nil}
})
return dhcpresp
}
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,
}
// RunDhcpResponder responds to the DHCP client messages
func RunDhcpResponder(ctx context.Context, dhcpOut chan *byteMsg, dhcpIn chan *byteMsg, errch chan error) {
responder := getDHCPResponder()
responder.dhcpIn = dhcpIn
clients := responder.clients
go func() {
logger.Debug("DHCP response process starts")
defer logger.Debug("DHCP response process was done")
for {
select {
case msg := <-dhcpOut:
logger.Debug("Received dhcp message from dhcpOut")
if c, ok := clients[clientKey{intfid: msg.IntfId, onuid: msg.OnuId}]; ok {
nextstate := respondMessage("DHCP", *c, msg, dhcpIn)
c.updateState(nextstate)
} else {
logger.Error("Failed to find dhcp client instance intfid:%d onuid:%d", msg.IntfId, msg.OnuId)
}
case <-ctx.Done():
return
}
}
}()
}
func startDHCPClient(intfid uint32, onuid uint32) error {
logger.Debug("startDHCPClient intfid:%d onuid:%d", intfid, onuid)
client := dhcpClientInstance{key: clientKey{intfid: intfid, onuid: onuid},
srcaddr: &net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, 0x07, byte(onuid)},
hostname: "voltha",
curId: rand.Uint32(),
curState: DHCP_INIT}
dhcp := client.createDHCPDisc()
bytes, err := client.createDHCP(dhcp)
if err != nil {
logger.Error("%s", err)
return err
}
resp := getDHCPResponder()
dhcpIn := resp.dhcpIn
if err := client.sendBytes(bytes, dhcpIn); err != nil {
logger.Error("Failed to send DHCP Discovery")
return errors.New("Failed to send DHCP Discovery")
}
client.curState = DHCP_SELECTING
logger.Debug("Sending DHCP Discovery intfid:%d onuid:%d", intfid, onuid)
resp.clients[clientKey{intfid: intfid, onuid: onuid}] = &client
return nil
}
func (c dhcpClientInstance) transitState(cur clientState, recvbytes []byte) (next clientState, sendbytes []byte, err error) {
recvpkt := gopacket.NewPacket(recvbytes, layers.LayerTypeEthernet, gopacket.Default)
dhcp, err := extractDHCP(recvpkt)
if err != nil {
return cur, nil, nil
}
msgType, err := getMsgType(dhcp)
if err != nil {
logger.Error("%s", err)
return cur, nil, nil
}
if dhcp.Operation == layers.DHCPOpReply && msgType == layers.DHCPMsgTypeOffer {
logger.Debug("Received DHCP Offer")
logger.Debug(recvpkt.Dump())
if cur == DHCP_SELECTING {
senddhcp := c.createDHCPReq()
sendbytes, err := c.createDHCP(senddhcp)
if err != nil {
logger.Debug("Failed to createDHCP")
return cur, nil, err
}
return DHCP_REQUESTING, sendbytes, nil
}
} else if dhcp.Operation == layers.DHCPOpReply && msgType == layers.DHCPMsgTypeAck {
logger.Debug("Received DHCP Ack")
logger.Debug(recvpkt.Dump())
if cur == DHCP_REQUESTING {
return DHCP_BOUND, nil, nil
}
} else if dhcp.Operation == layers.DHCPOpReply && msgType == layers.DHCPMsgTypeRelease {
if cur == DHCP_BOUND {
senddhcp := c.createDHCPDisc()
sendbytes, err := c.createDHCP(senddhcp)
if err != nil {
fmt.Println("Failed to createDHCP")
return DHCP_INIT, nil, err
}
return DHCP_SELECTING, sendbytes, nil
}
} else {
logger.Debug("Received unsupported DHCP message Operation:%d MsgType:%d", dhcp.Operation, msgType)
return cur, nil, nil
}
logger.Error("State transition does not support..current state:%d", cur)
logger.Debug(recvpkt.Dump())
return cur, nil, nil
}
func (c dhcpClientInstance) getState() clientState {
return c.curState
}
func (c *dhcpClientInstance) updateState(state clientState) {
msg := fmt.Sprintf("DHCP update state intfid:%d onuid:%d state:%d", c.key.intfid, c.key.onuid, state)
logger.Debug(msg)
c.curState = state
}
func (c dhcpClientInstance) getKey() clientKey {
return c.key
}
func (c *dhcpClientInstance) createDHCP(dhcp *layers.DHCPv4) ([]byte, error) {
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
ethernetLayer := &layers.Ethernet{
SrcMAC: *c.srcaddr,
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 (c *dhcpClientInstance) createDefaultDHCPReq() layers.DHCPv4 {
return layers.DHCPv4{
Operation: layers.DHCPOpRequest,
HardwareType: layers.LinkTypeEthernet,
HardwareLen: 6,
HardwareOpts: 0,
Xid: c.curId,
ClientHWAddr: *c.srcaddr,
}
}
func (c *dhcpClientInstance) createDefaultOpts() []layers.DHCPOption {
hostname := []byte(c.hostname)
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 (c *dhcpClientInstance) createDHCPDisc() *layers.DHCPv4 {
dhcpLayer := c.createDefaultDHCPReq()
defaultOpts := c.createDefaultOpts()
dhcpLayer.Options = append([]layers.DHCPOption{layers.DHCPOption{
Type: layers.DHCPOptMessageType,
Data: []byte{byte(layers.DHCPMsgTypeDiscover)},
Length: 1,
}}, defaultOpts...)
return &dhcpLayer
}
func (c *dhcpClientInstance) createDHCPReq() *layers.DHCPv4 {
dhcpLayer := c.createDefaultDHCPReq()
defaultOpts := c.createDefaultOpts()
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, 0xcb, 0xcc}
dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
Type: layers.DHCPOptClientID,
Data: data,
Length: uint8(len(data)),
})
data = []byte{182, 21, 0, byte(c.key.onuid)}
dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
Type: layers.DHCPOptRequestIP,
Data: data,
Length: uint8(len(data)),
})
return &dhcpLayer
}
func (c *dhcpClientInstance) sendBytes(bytes []byte, dhcpIn chan *byteMsg) error {
// Send our packet
msg := byteMsg{IntfId: c.key.intfid,
OnuId: c.key.onuid,
Byte: bytes}
dhcpIn <- &msg
logger.Debug("sendBytes intfid:%d onuid:%d", c.key.intfid, c.key.onuid)
logger.Debug(hex.Dump(msg.Byte))
return nil
}
func extractDHCP(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")
}
return dhcp, nil
}
func getMsgType(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)}) {
return layers.DHCPMsgTypeOffer, 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")
}