| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- // Copyright 2019 Yunion
- //
- // 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 pxe
- import (
- "context"
- "encoding/hex"
- "fmt"
- "net"
- "strings"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/printutils"
- "yunion.io/x/onecloud/pkg/apis"
- api "yunion.io/x/onecloud/pkg/apis/compute"
- o "yunion.io/x/onecloud/pkg/baremetal/options"
- "yunion.io/x/onecloud/pkg/cloudcommon/types"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/mcclient"
- modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
- "yunion.io/x/onecloud/pkg/util/ctx"
- "yunion.io/x/onecloud/pkg/util/dhcp"
- )
- func (s *Server) serveDHCP(srv *dhcp.DHCPServer, handler dhcp.DHCPHandler) error {
- return srv.ListenAndServe(handler)
- }
- type NetworkInterfaceIdent struct {
- Type uint16
- Major uint16
- Minior uint16
- }
- type DHCPHandler struct {
- // baremetal manager
- baremetalManager IBaremetalManager
- }
- type dhcpRequest struct {
- packet dhcp.Packet
- ClientMac net.HardwareAddr // client nic mac
- ClientAddr net.IP // IP address from DHCP client
- RelayAddr net.IP // IP address of DHCP relay agent
- Options dhcp.Options // dhcp packet options
- VendorClassId string
- ClientArch uint16
- NetworkInterfaceIdent NetworkInterfaceIdent
- ClientGuid string
- // baremetal manager
- baremetalManager IBaremetalManager
- // baremetal instance
- baremetalInstance IBaremetalInstance
- // cloud network config
- netConfig *types.SNetworkConfig
- }
- func (h *DHCPHandler) ServeDHCP(pkt dhcp.Packet, _ net.HardwareAddr, _ *net.UDPAddr) (dhcp.Packet, []string, error) {
- req, err := h.newRequest(pkt, h.baremetalManager)
- if err != nil {
- log.Errorf("[DHCP] new request by packet error: %v", err)
- return nil, nil, err
- }
- log.V(4).Debugf("[DHCP] request packet: %#v", req)
- if req.RelayAddr.String() == "0.0.0.0" {
- return nil, nil, fmt.Errorf("Request not from a DHCP relay, ignore mac: %s", req.ClientMac)
- }
- log.Infof("[DHCP] from relay %s packet, mac: %s", req.RelayAddr, req.ClientMac)
- conf, targets, err := req.fetchConfig(ctx.CtxWithTime(), h.baremetalManager.GetClientSession())
- if err != nil {
- return nil, nil, errors.Wrapf(err, "fetchConfig for %s", req.ClientMac.String())
- }
- if conf == nil {
- return nil, nil, fmt.Errorf("Empty packet config")
- }
- // handle Option 82 relay information
- // https://datatracker.ietf.org/doc/html/rfc3046
- if relayInfo := pkt.GetOptionValue(dhcp.OptionRelayAgentInformation); relayInfo != nil {
- // return relay information transparently
- conf.RelayInfo = relayInfo
- }
- pkg, err := dhcp.MakeReplyPacket(pkt, conf)
- if err != nil {
- return nil, nil, errors.Wrap(err, "dhcp.MakeReplyPacket")
- }
- return pkg, targets, nil
- }
- func (h *DHCPHandler) newRequest(pkt dhcp.Packet, man IBaremetalManager) (*dhcpRequest, error) {
- req := &dhcpRequest{
- baremetalManager: man,
- packet: pkt,
- ClientAddr: pkt.CIAddr(),
- ClientMac: pkt.CHAddr(),
- RelayAddr: pkt.RelayAddr(),
- Options: pkt.ParseOptions(),
- }
- var (
- vendorClsId string
- cliArch uint16
- err error
- netIfIdent NetworkInterfaceIdent
- cliGuid string
- )
- for optCode, data := range req.Options {
- switch optCode {
- case dhcp.OptionVendorClassIdentifier:
- vendorClsId, err = req.Options.String(optCode)
- case dhcp.OptionClientArchitecture:
- cliArch, err = req.Options.Uint16(optCode)
- case dhcp.OptionClientNetworkInterfaceIdentifier:
- netIfIdentBs, err := req.Options.Bytes(optCode)
- if err != nil {
- break
- }
- netIfIdent = NetworkInterfaceIdent{
- Type: uint16(netIfIdentBs[0]),
- Major: uint16(netIfIdentBs[1]),
- Minior: uint16(netIfIdentBs[2]),
- }
- log.Debugf("[DHCP] get network iface identifier: %#v", netIfIdent)
- case dhcp.OptionClientMachineIdentifier:
- switch len(data) {
- case 0:
- // A missing GUID is invalid according to the spec, however
- // there are PXE ROMs in the wild that omit the GUID and still
- // expect to boot.
- case 17:
- if data[0] != 0 {
- err = errors.Error("malformed client GUID (option 97), leading byte must be zero")
- }
- default:
- err = errors.Error("malformed client GUID (option 97), wrong size")
- }
- cliGuid, err = req.Options.String(optCode)
- }
- if err != nil {
- log.Errorf("[DHCP] parse vendor option %d error: %v", optCode, err)
- }
- }
- var cliUUIDStr string
- if len(cliGuid) == 17 {
- cliUUIDStr = formatUuidString([]byte(cliGuid)[1:])
- }
- req.VendorClassId = vendorClsId
- req.ClientArch = cliArch
- req.NetworkInterfaceIdent = netIfIdent
- req.ClientGuid = cliUUIDStr
- log.Debugf("Client GUID: %s", req.ClientGuid)
- return req, err
- }
- func swapBytes(input []byte) []byte {
- output := make([]byte, len(input))
- for i := range input {
- output[i] = input[len(input)-1-i]
- }
- return output
- }
- func formatUuidString(uuidBytes []byte) string {
- return strings.Join([]string{
- hex.EncodeToString(swapBytes(uuidBytes[0:4])),
- hex.EncodeToString(swapBytes(uuidBytes[4:6])),
- hex.EncodeToString(swapBytes(uuidBytes[6:8])),
- hex.EncodeToString(uuidBytes[8:10]),
- hex.EncodeToString(uuidBytes[10:16]),
- }, "-")
- }
- func (req *dhcpRequest) fetchConfig(ctx context.Context, session *mcclient.ClientSession) (*dhcp.ResponseConfig, []string, error) {
- // 1. find_network_conf
- netConf, err := req.findNetworkConf(session, false)
- if err != nil {
- return nil, nil, err
- }
- req.netConfig = netConf
- var dhcpAddrList []string
- if len(netConf.GuestDhcp) > 0 {
- dhcpAddrList = strings.Split(netConf.GuestDhcp, ",")
- }
- log.Debugf("find network config %#v, dhcpAddrList %s", netConf, dhcpAddrList)
- // TODO: set cache for netConf
- if req.isPXERequest() {
- if !o.Options.EnablePxeBoot {
- return nil, nil, errors.Error("PXE Boot disabled")
- }
- // handle PXE DHCP request
- log.Infof("[PXE DHCP] relay from %s(%s) for %s, find matched networks: %#v", req.RelayAddr, req.ClientAddr, req.ClientMac, netConf)
- bmDesc, err := req.createOrUpdateBaremetal(session)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "createOrUpdateBaremetal for %s", req.ClientMac.String())
- }
- err = req.doInitBaremetalAdminNetif(ctx, bmDesc)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "doInitBaremetalAdminNetif for %s", req.ClientMac.String())
- }
- // always response PXE request
- // let bootloader decide boot local or remote
- // if req.baremetalInstance.NeedPXEBoot() {
- conf, err := req.baremetalInstance.GetPXEDHCPConfig(req.ClientArch)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "req.baremetalInstance.GetPXEDHCPConfig for %s", req.ClientMac.String())
- }
- return conf, dhcpAddrList, nil
- // }
- // ignore
- // log.Warningf("No need to pxeboot, ignore the request ...(mac:%s guid:%s)", req.ClientMac, req.ClientGuid)
- // return nil, nil
- } else {
- // handle normal DHCP request
- bmInstance := req.baremetalManager.GetBaremetalByMac(req.ClientMac)
- if bmInstance == nil {
- // options.EnableGeneralGuestDhcp
- // cloud be an instance not served by a host-server
- // from guestdhcp import GuestDHCPHelperTask
- // task = GuestDHCPHelperTask(self)
- // task.start()
- log.Infof("[NORMAL DHCP] Not found baremetal from request with mac: %s", req.ClientMac)
- return nil, nil, nil
- }
- req.baremetalInstance = bmInstance
- ipmiNic := req.baremetalInstance.GetIPMINic(req.ClientMac)
- if ipmiNic != nil && ipmiNic.Mac == req.ClientMac.String() {
- err = req.baremetalInstance.InitAdminNetif(ctx,
- req.ClientMac, req.netConfig.WireId, api.NIC_TYPE_IPMI, api.NETWORK_TYPE_IPMI, false, "")
- if err != nil {
- return nil, nil, errors.Wrapf(err, "InitAdminNetif for %s", req.ClientMac.String())
- }
- } else {
- err = req.baremetalInstance.RegisterNetif(ctx, req.ClientMac, req.netConfig.WireId)
- if err != nil {
- log.Errorf("RegisterNetif %s error: %v", req.ClientMac.String(), err)
- return nil, nil, errors.Wrapf(err, "RegisterNetif for %s", req.ClientMac.String())
- }
- }
- conf, err := req.baremetalInstance.GetDHCPConfig(req.ClientMac)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "req.baremetalInstance.GetDHCPConfig for %s", req.ClientMac.String())
- }
- return conf, dhcpAddrList, nil
- }
- }
- func (req *dhcpRequest) findNetworkConf(session *mcclient.ClientSession, filterUseIp bool) (*types.SNetworkConfig, error) {
- params := jsonutils.NewDict()
- if filterUseIp {
- params.Add(jsonutils.NewString(req.RelayAddr.String()), "ip")
- } else {
- params.Add(jsonutils.NewString(
- fmt.Sprintf("guest_gateway.equals(%s)", req.RelayAddr)),
- "filter.0")
- params.Add(jsonutils.NewString(
- fmt.Sprintf("guest_dhcp.startswith('%s,')", req.RelayAddr)),
- "filter.1")
- params.Add(jsonutils.NewString(
- fmt.Sprintf("guest_dhcp.contains(',%s,')", req.RelayAddr)),
- "filter.2")
- params.Add(jsonutils.NewString(
- fmt.Sprintf("guest_dhcp.endswith(',%s')", req.RelayAddr)),
- "filter.3")
- params.Add(jsonutils.NewString(
- fmt.Sprintf("guest_dhcp.equals(%s)", req.RelayAddr)),
- "filter.4")
- params.Add(jsonutils.JSONTrue, "filter_any")
- }
- params.Add(jsonutils.JSONTrue, "is_classic")
- params.Add(jsonutils.NewString("system"), "scope")
- ret, err := modules.Networks.List(session, params)
- if err != nil {
- return nil, err
- }
- if len(ret.Data) == 0 {
- if !filterUseIp {
- // use ip filter try again
- return req.findNetworkConf(session, true)
- }
- return nil, fmt.Errorf("DHCP relay from %s(%s) for %s, find no match network", req.RelayAddr, req.ClientAddr, req.ClientMac)
- }
- idx := 0
- for i := range ret.Data {
- netType, _ := ret.Data[i].GetString("server_type")
- if netType == string(api.NETWORK_TYPE_PXE) {
- idx = i
- break
- }
- }
- network := types.SNetworkConfig{}
- err = ret.Data[idx].Unmarshal(&network)
- return &network, err
- }
- func (req *dhcpRequest) findBaremetalsByUuid(session *mcclient.ClientSession) (*printutils.ListResult, error) {
- params := jsonutils.NewDict()
- params.Add(jsonutils.NewString(req.ClientGuid), "uuid")
- params.Add(jsonutils.NewString("system"), "scope")
- ret, err := modules.Hosts.List(session, params)
- if err != nil {
- return nil, err
- }
- if len(ret.Data) > 1 {
- return nil, httperrors.NewDuplicateResourceError("duplicate uuid %s", req.ClientGuid)
- }
- return ret, nil
- }
- func (req *dhcpRequest) findBaremetalsOfAnyMac(session *mcclient.ClientSession, isBaremetal bool) (*printutils.ListResult, error) {
- params := jsonutils.NewDict()
- params.Add(jsonutils.NewString(req.ClientMac.String()), "any_mac")
- params.Add(jsonutils.NewString("system"), "scope")
- if isBaremetal {
- params.Add(jsonutils.JSONTrue, "is_baremetal")
- } else {
- params.Add(jsonutils.NewString(api.HOST_TYPE_BAREMETAL), "host_type")
- }
- return modules.Hosts.List(session, params)
- }
- // createOrUpdateBaremetal create or update baremetal by client MAC
- func (req *dhcpRequest) createOrUpdateBaremetal(session *mcclient.ClientSession) (jsonutils.JSONObject, error) {
- // first try mac and is_baremetal=true
- ret, err := req.findBaremetalsOfAnyMac(session, true)
- if err != nil {
- return nil, err
- }
- if len(ret.Data) == 0 {
- // try mac and host_type=baremetal
- ret, err = req.findBaremetalsOfAnyMac(session, false)
- if err != nil {
- return nil, err
- }
- }
- if len(ret.Data) == 0 && len(req.ClientGuid) > 0 {
- // try UUID
- ret, err = req.findBaremetalsByUuid(session)
- if err != nil {
- return nil, err
- }
- }
- switch len(ret.Data) {
- case 0:
- // found new baremetal, create it if auto register
- if o.Options.AutoRegisterBaremetal {
- return req.createBaremetal(session)
- }
- case 1:
- // already exists, do update
- bmId, err := ret.Data[0].GetString("id")
- if err != nil {
- return nil, err
- }
- return req.updateBaremetal(session, bmId)
- }
- return nil, fmt.Errorf("Found %d records match %s", len(ret.Data), req.ClientMac)
- }
- func (req *dhcpRequest) getArch() string {
- switch req.ClientArch {
- case dhcp.CLIENT_ARCH_EFI_BC, dhcp.CLIENT_ARCH_EFI_X86_64:
- return apis.OS_ARCH_X86_64
- case dhcp.CLIENT_ARCH_EFI_IA32:
- return apis.OS_ARCH_X86_32
- case dhcp.CLIENT_ARCH_EFI_ARM64:
- return apis.OS_ARCH_AARCH64
- case dhcp.CLIENT_ARCH_EFI_ARM32:
- return apis.OS_ARCH_AARCH32
- default:
- return ""
- }
- }
- func (req *dhcpRequest) createBaremetal(session *mcclient.ClientSession) (jsonutils.JSONObject, error) {
- params := jsonutils.NewDict()
- mac := req.ClientMac.String()
- zoneId := req.baremetalManager.GetZoneId()
- name := strings.ToLower(fmt.Sprintf("BM%s", strings.Replace(mac, ":", "", -1)))
- params.Add(jsonutils.NewString(name), "name")
- params.Add(jsonutils.NewString(mac), "access_mac")
- params.Add(jsonutils.NewString("baremetal"), "host_type")
- params.Add(jsonutils.JSONTrue, "is_baremetal")
- params.Add(jsonutils.NewString(zoneId), "zone_id")
- params.Add(jsonutils.NewString(req.getArch()), "cpu_architecture")
- desc, err := modules.Hosts.Create(session, params)
- if err != nil {
- return nil, err
- }
- return desc, nil
- }
- func (req *dhcpRequest) updateBaremetal(session *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) {
- params := jsonutils.NewDict()
- params.Add(jsonutils.NewString(req.ClientMac.String()), "access_mac")
- params.Add(jsonutils.NewString(req.baremetalManager.GetZoneId()), "zone_id")
- params.Add(jsonutils.NewString(req.getArch()), "cpu_architecture")
- // params.Add(jsonutils.NewString("baremetal"), "host_type")
- params.Add(jsonutils.JSONTrue, "is_baremetal")
- if !dhcp.IsUEFIPxeArch(req.ClientArch) {
- // clean uefi info
- params.Add(jsonutils.NewDict(), "uefi_info")
- }
- desc, err := modules.Hosts.Update(session, id, params)
- if err != nil {
- return nil, err
- }
- return desc, nil
- }
- func (req *dhcpRequest) doInitBaremetalAdminNetif(ctx context.Context, desc jsonutils.JSONObject) error {
- var err error
- req.baremetalInstance, err = req.baremetalManager.AddBaremetal(ctx, desc)
- if err != nil {
- return err
- }
- err = req.baremetalInstance.InitAdminNetif(ctx,
- req.ClientMac, req.netConfig.WireId, api.NIC_TYPE_ADMIN, api.NETWORK_TYPE_PXE, false, "")
- return err
- }
- func (req *dhcpRequest) isPXERequest() bool {
- pkt := req.packet
- return dhcp.IsPXERequest(pkt)
- }
- func (s *Server) validateDHCP(pkt dhcp.Packet) (Machine, Firmware, error) {
- var mach Machine
- var fwtype Firmware
- fwt, err := pkt.ParseOptions().Uint16(dhcp.OptionClientArchitecture)
- if err != nil {
- return mach, fwtype, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err)
- }
- // Basic architecture and firmware identification, based purely on
- // the PXE architecture option.
- switch fwt {
- // TODO: complete case 1, 2, 3, 4, 5, 8
- case 0:
- // Intel x86PC
- mach.Arch = ArchIA32
- fwtype = FirmwareX86PC
- case 1:
- // NEC/PC98
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 2:
- // EFI Itanium
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 3:
- // DEC Alpha
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 4:
- // Arc x86
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 5:
- // Intel Lean Client
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 6:
- // EFI IA32
- mach.Arch = ArchIA32
- fwtype = FirmwareEFI32
- case 7:
- // EFI BC
- mach.Arch = ArchX64
- fwtype = FirmwareEFI64
- case 8:
- // EFI Xscale
- mach.Arch = ArchUnknown
- fwtype = FirmwareUnknown
- case 9:
- // EFI x86-64
- mach.Arch = ArchX64
- fwtype = FirmwareEFIBC
- default:
- return mach, 0, fmt.Errorf("unsupported client firmware type '%d'", fwtype)
- }
- guid, _ := pkt.ParseOptions().Bytes(dhcp.OptionClientMachineIdentifier)
- switch len(guid) {
- case 0:
- // A missing GUID is invalid according to the spec, however
- // there are PXE ROMs in the wild that omit the GUID and still
- // expect to boot. The only thing we do with the GUID is
- // mirror it back to the client if it's there, so we might as
- // well accept these buggy ROMs.
- case 17:
- if guid[0] != 0 {
- return mach, 0, errors.Error("malformed client GUID (option 97), leading byte must be zero")
- }
- default:
- return mach, 0, errors.Error("malformed client GUID (option 97), wrong size")
- }
- mach.MAC = pkt.CHAddr()
- return mach, fwtype, nil
- }
|