Files
cloudpods/pkg/util/dhcp/helpers.go
T
2025-07-12 12:10:31 +08:00

237 lines
6.6 KiB
Go

// 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 dhcp
import (
"encoding/binary"
"fmt"
"net"
"strings"
"time"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/util/netutils2"
)
const (
PXECLIENT = "PXEClient"
OptClasslessRouteLin OptionCode = OptionClasslessRouteFormat //Classless Static Route Option
OptClasslessRouteWin OptionCode = 249
)
// http://www.networksorcery.com/enp/rfc/rfc2132.txt
type ResponseConfig struct {
InterfaceMac net.HardwareAddr
VlanId uint16
OsName string
ServerIP net.IP // OptServerIdentifier 54
ClientIP net.IP
Gateway net.IP // OptRouters 3
Domain string // OptDomainName 15
LeaseTime time.Duration // OptLeaseTime 51
RenewalTime time.Duration // OptRenewalTime 58
BroadcastAddr net.IP // OptBroadcastAddr 28
Hostname string // OptHostname 12
SubnetMask net.IP // OptSubnetMask 1
DNSServers []net.IP // OptDNSServers
Routes []netutils2.SRouteInfo // TODO: 249 for windows, 121 for linux
NTPServers []net.IP // OptNTPServers 42
MTU uint16 // OptMTU 26
ClientIP6 net.IP
Gateway6 net.IP
PrefixLen6 uint8
DNSServers6 []net.IP
NTPServers6 []net.IP
Routes6 []netutils2.SRouteInfo
IsDefaultGW bool
// Relay Info https://datatracker.ietf.org/doc/html/rfc3046
RelayInfo []byte
// TFTP config
BootServer string
BootFile string
BootBlock uint16
}
func (conf ResponseConfig) GetHostname() string {
return conf.Hostname
}
func GetOptUint16(val uint16) []byte {
opts := []byte{0, 0}
binary.BigEndian.PutUint16(opts, val)
return opts
}
func GetOptUint32(val uint32) []byte {
opts := []byte{0, 0, 0, 0}
binary.BigEndian.PutUint32(opts, val)
return opts
}
func GetOptIP(ip net.IP) []byte {
return []byte(ip.To4())
}
func GetOptIPs(ips []net.IP) []byte {
buf := make([]byte, 0)
for _, ip := range ips {
buf = append(buf, []byte(ip.To4())...)
}
return buf
}
func GetOptTime(d time.Duration) []byte {
timeBytes := make([]byte, 4)
binary.BigEndian.PutUint32(timeBytes, uint32(d/time.Second))
return timeBytes
}
func getClasslessRoutePack(route netutils2.SRouteInfo) []byte {
// var snet, gw = route[0], route[1]
// tmp := strings.Split(snet, "/")
netaddr := route.Prefix
if netaddr != nil {
netaddr = netaddr.To4()
}
masklen := route.PrefixLen
netlen := masklen / 8
if masklen%8 > 0 {
netlen += 1
}
if netlen < 4 {
netaddr = netaddr[0:netlen]
}
gwaddr := route.Gateway
if gwaddr != nil {
gwaddr = gwaddr.To4()
}
res := []byte{byte(masklen)}
res = append(res, []byte(netaddr)...)
return append(res, []byte(gwaddr)...)
}
func MakeReplyPacket(pkt Packet, conf *ResponseConfig) (Packet, error) {
msgType := Offer
if pkt.Type() == Request {
reqAddr, _ := pkt.ParseOptions().IP(OptionRequestedIPAddress)
if reqAddr != nil && !conf.ClientIP.Equal(reqAddr) {
msgType = NAK
} else {
msgType = ACK
}
}
return makeDHCPReplyPacket(pkt, conf, msgType), nil
}
func getPacketVendorClassId(pkt Packet) string {
bs := pkt.ParseOptions()[OptionVendorClassIdentifier]
vendorClsId := string(bs)
return vendorClsId
}
func makeDHCPReplyPacket(req Packet, conf *ResponseConfig, msgType MessageType) Packet {
if conf.OsName == "" {
if vendorClsId := getPacketVendorClassId(req); vendorClsId != "" && strings.HasPrefix(vendorClsId, "MSFT ") {
conf.OsName = "win"
}
}
opts := make([]Option, 0)
if conf.SubnetMask != nil {
opts = append(opts, Option{Code: OptionSubnetMask, Value: GetOptIP(conf.SubnetMask)})
}
if conf.Gateway != nil {
opts = append(opts, Option{Code: OptionRouter, Value: GetOptIP(conf.Gateway)})
}
if conf.Domain != "" {
opts = append(opts, Option{Code: OptionDomainName, Value: []byte(conf.Domain)})
}
if conf.BroadcastAddr != nil {
opts = append(opts, Option{Code: OptionBroadcastAddress, Value: GetOptIP(conf.BroadcastAddr)})
}
if conf.Hostname != "" {
opts = append(opts, Option{Code: OptionHostName, Value: []byte(conf.GetHostname())})
}
if len(conf.DNSServers) > 0 {
opts = append(opts, Option{Code: OptionDomainNameServer, Value: GetOptIPs(conf.DNSServers)})
}
if len(conf.NTPServers) > 0 {
opts = append(opts, Option{Code: OptionNetworkTimeProtocolServers, Value: GetOptIPs(conf.NTPServers)})
}
if conf.MTU > 0 {
opts = append(opts, Option{Code: OptionInterfaceMTU, Value: GetOptUint16(conf.MTU)})
}
if conf.RelayInfo != nil {
opts = append(opts, Option{Code: OptionRelayAgentInformation, Value: conf.RelayInfo})
}
var clientIP net.IP
if conf.ClientIP != nil {
clientIP = conf.ClientIP
} else {
clientIP = net.ParseIP("0.0.0.0")
opts = append(opts, Option{Code: OptionIPv6Only, Value: GetOptUint32(60)})
}
resp := ReplyPacket(req, msgType, conf.ServerIP, clientIP, conf.LeaseTime, opts)
if conf.BootServer != "" {
//resp.Options[OptOverload] = []byte{3}
resp.SetSIAddr(net.ParseIP(conf.BootServer))
resp.AddOption(OptionTFTPServerName, []byte(fmt.Sprintf("%s\x00", conf.BootServer)))
}
if conf.BootFile != "" {
resp.AddOption(OptionBootFileName, []byte(fmt.Sprintf("%s\x00", conf.BootFile)))
sz := make([]byte, 2)
binary.BigEndian.PutUint16(sz, conf.BootBlock)
resp.AddOption(OptionBootFileSize, sz)
}
//if bs, _ := req.ParseOptions().Bytes(OptionClientMachineIdentifier); bs != nil {
//resp.AddOption(OptionClientMachineIdentifier, bs)
//}
if conf.RenewalTime > 0 {
resp.AddOption(OptionRenewalTimeValue, GetOptTime(conf.RenewalTime))
}
if conf.Routes != nil {
var optCode = OptClasslessRouteLin
if strings.HasPrefix(strings.ToLower(conf.OsName), "win") {
optCode = OptClasslessRouteWin
}
for _, route := range conf.Routes {
routeBytes := getClasslessRoutePack(route)
resp.AddOption(optCode, routeBytes)
}
}
return resp
}
func IsPXERequest(pkt Packet) bool {
//if pkt.Type != MsgDiscover {
//log.Warningf("packet is %s, not %s", pkt.Type, MsgDiscover)
//return false
//}
if pkt.GetOptionValue(OptionClientArchitecture) == nil {
log.Debugf("%s not a PXE boot request (missing option 93)", pkt.CHAddr().String())
return false
}
return true
}