| /* |
| * 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") |
| } |