blob: 7e7f361bd5a55172e0007ac4f86589aea9e990ed [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 (
"encoding/hex"
"errors"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
log "github.com/sirupsen/logrus"
"net"
)
type DHCPServerIf interface {
HandleServerPacket(pkt gopacket.Packet) (gopacket.Packet, error)
}
type DHCPServer struct {
DHCPServerMacAddress net.HardwareAddr
}
func NewDHCPServer() *DHCPServer {
return &DHCPServer{
// NOTE we may need to make this configurable in case we'll need multiple servers
DHCPServerMacAddress: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
}
}
func (s *DHCPServer) getClientMacAddress(pkt gopacket.Packet) (net.HardwareAddr, error) {
dhcpLayer, err := GetDhcpLayer(pkt)
if err != nil {
return nil, err
}
return dhcpLayer.ClientHWAddr, nil
}
func (s *DHCPServer) getTxId(pkt gopacket.Packet) (uint32, error) {
dhcpLayer, err := GetDhcpLayer(pkt)
if err != nil {
return 0, err
}
return dhcpLayer.Xid, nil
}
func (s *DHCPServer) getPacketHostName(pkt gopacket.Packet) ([]byte, error) {
dhcpLayer, err := GetDhcpLayer(pkt)
if err != nil {
return nil, err
}
for _, option := range dhcpLayer.Options {
if option.Type == layers.DHCPOptHostname {
return option.Data, nil
}
}
return nil, errors.New("hostname-not-found")
}
func (s *DHCPServer) getOption82(pkt gopacket.Packet) ([]byte, error) {
dhcpLayer, err := GetDhcpLayer(pkt)
if err != nil {
return nil, err
}
for _, option := range dhcpLayer.Options {
if option.Type == 82 {
return option.Data, nil
}
}
log.WithFields(log.Fields{
"pkt": hex.EncodeToString(pkt.Data()),
}).Debug("option82-not-found")
return []byte{}, nil
}
func (s *DHCPServer) createDefaultDhcpReply(xid uint32, mac net.HardwareAddr) layers.DHCPv4 {
clientIp := s.createIpFromMacAddress(mac)
return layers.DHCPv4{
Operation: layers.DHCPOpReply,
HardwareType: layers.LinkTypeEthernet,
HardwareLen: 6,
HardwareOpts: 0,
Xid: xid,
ClientHWAddr: mac,
ClientIP: clientIp,
YourClientIP: clientIp,
}
}
func (s *DHCPServer) createIpFromMacAddress(mac net.HardwareAddr) net.IP {
ip := []byte{}
for i := 2; i < 6; i++ {
ip = append(ip, mac[i])
}
return net.IPv4(10+ip[0], ip[1], ip[2], ip[3])
}
func (s *DHCPServer) serializeServerDHCPPacket(clientMac net.HardwareAddr, dhcpLayer *layers.DHCPv4) (gopacket.Packet, error) {
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
ethernetLayer := &layers.Ethernet{
SrcMAC: s.DHCPServerMacAddress,
DstMAC: clientMac,
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: 67,
DstPort: 68,
}
_ = udpLayer.SetNetworkLayerForChecksum(ipLayer)
if err := gopacket.SerializeLayers(buffer, options, ethernetLayer, ipLayer, udpLayer, dhcpLayer); err != nil {
dhcpLogger.Error("SerializeLayers")
return nil, err
}
return gopacket.NewPacket(buffer.Bytes(), layers.LayerTypeEthernet, gopacket.Default), nil
}
func (s *DHCPServer) getDefaultDhcpServerOptions(hostname []byte, option82 []byte) []layers.DHCPOption {
defaultOpts := []layers.DHCPOption{}
defaultOpts = append(defaultOpts, layers.DHCPOption{
Type: layers.DHCPOptHostname,
Data: hostname,
Length: uint8(len(hostname)),
})
defaultOpts = append(defaultOpts, layers.DHCPOption{
Type: 82,
Data: option82,
Length: uint8(len(option82)),
})
return defaultOpts
}
// get a Discover packet an return a valid Offer
func (s *DHCPServer) handleDiscover(pkt gopacket.Packet) (gopacket.Packet, error) {
sTag, cTag, err := packetHandlers.GetTagsFromPacket(pkt)
if err != nil {
return nil, err
}
clientMac, err := s.getClientMacAddress(pkt)
if err != nil {
return nil, err
}
txId, err := s.getTxId(pkt)
if err != nil {
return nil, err
}
hostname, err := s.getPacketHostName(pkt)
if err != nil {
return nil, err
}
option82, err := s.getOption82(pkt)
if err != nil {
return nil, err
}
dhcpLogger.WithFields(log.Fields{
"sTag": sTag,
"cTag": cTag,
"clientMac": clientMac,
"txId": txId,
"hostname": string(hostname),
"option82": string(option82),
}).Debug("Handling DHCP Discovery packet")
dhcpLayer := s.createDefaultDhcpReply(txId, clientMac)
defaultOpts := s.getDefaultDhcpServerOptions(hostname, option82)
dhcpLayer.Options = append([]layers.DHCPOption{{
Type: layers.DHCPOptMessageType,
Data: []byte{byte(layers.DHCPMsgTypeOffer)},
Length: 1,
}}, defaultOpts...)
data := []byte{01}
data = append(data, clientMac...)
dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
Type: layers.DHCPOptClientID,
Data: data,
Length: uint8(len(data)),
})
// serialize the packet
responsePkt, err := s.serializeServerDHCPPacket(clientMac, &dhcpLayer)
if err != nil {
return nil, err
}
taggedResponsePkt, err := packetHandlers.PushDoubleTag(int(sTag), int(cTag), responsePkt, 0)
if err != nil {
return nil, err
}
return taggedResponsePkt, nil
}
func (s *DHCPServer) handleRequest(pkt gopacket.Packet) (gopacket.Packet, error) {
sTag, cTag, err := packetHandlers.GetTagsFromPacket(pkt)
if err != nil {
return nil, err
}
clientMac, err := s.getClientMacAddress(pkt)
if err != nil {
return nil, err
}
txId, err := s.getTxId(pkt)
if err != nil {
return nil, err
}
hostname, err := s.getPacketHostName(pkt)
if err != nil {
return nil, err
}
option82, err := s.getOption82(pkt)
if err != nil {
return nil, err
}
dhcpLogger.WithFields(log.Fields{
"sTag": sTag,
"cTag": cTag,
"clientMac": clientMac,
"txId": txId,
"hostname": string(hostname),
"option82": string(option82),
}).Debug("Handling DHCP Request packet")
dhcpLayer := s.createDefaultDhcpReply(txId, clientMac)
defaultOpts := s.getDefaultDhcpServerOptions(hostname, option82)
dhcpLayer.Options = append([]layers.DHCPOption{{
Type: layers.DHCPOptMessageType,
Data: []byte{byte(layers.DHCPMsgTypeAck)},
Length: 1,
}}, defaultOpts...)
// TODO can we move this in getDefaultDhcpServerOptions?
data := []byte{01}
data = append(data, clientMac...)
dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
Type: layers.DHCPOptClientID,
Data: data,
Length: uint8(len(data)),
})
// serialize the packet
responsePkt, err := s.serializeServerDHCPPacket(clientMac, &dhcpLayer)
if err != nil {
return nil, err
}
taggedResponsePkt, err := packetHandlers.PushDoubleTag(int(sTag), int(cTag), responsePkt, 0)
if err != nil {
return nil, err
}
return taggedResponsePkt, nil
}
// HandleServerPacket is a very simple implementation of a DHCP server
// that only replies to DHCPDiscover and DHCPRequest packets
func (s DHCPServer) HandleServerPacket(pkt gopacket.Packet) (gopacket.Packet, error) {
dhcpLayer, _ := GetDhcpLayer(pkt)
if dhcpLayer.Operation == layers.DHCPOpReply {
dhcpLogger.WithFields(log.Fields{
"pkt": hex.EncodeToString(pkt.Data()),
}).Error("Received DHCP Reply on the server. Ignoring the packet but this is a serious error.")
}
dhcpMessageType, _ := GetDhcpMessageType(dhcpLayer)
switch dhcpMessageType {
case layers.DHCPMsgTypeDiscover:
dhcpLogger.Info("Received DHCP Discover")
return s.handleDiscover(pkt)
case layers.DHCPMsgTypeRequest:
dhcpLogger.Info("Received DHCP Request")
return s.handleRequest(pkt)
}
return nil, fmt.Errorf("cannot-handle-dhcp-packet-of-type-%s", dhcpMessageType.String())
}