VOL-1431, SEBA-436 Add DHCP responder

Change-Id: Ifdc614a8b5a212e23308d4fcc71987b75397fea2
diff --git a/core/dhcp.go b/core/dhcp.go
new file mode 100644
index 0000000..12b4652
--- /dev/null
+++ b/core/dhcp.go
@@ -0,0 +1,345 @@
+/*
+ * 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"
+	"gerrit.opencord.org/voltha-bbsim/common/logger"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"math/rand"
+	"net"
+	"reflect"
+	"sync"
+)
+
+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,
+}
+
+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")
+	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")
+	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")
+}