fix(dhcp): can not renew an ip address
The dhcp server is systemd-networkd, and the dhcp plugin can request an ip but can not renew it. The systemd-networkd just ignore the renew request. ``` 2024/09/14 21:46:00 no DHCP packet received within 10s 2024/09/14 21:46:00 retrying in 31.529038 seconds 2024/09/14 21:46:42 no DHCP packet received within 10s 2024/09/14 21:46:42 retrying in 63.150490 seconds 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: no more tries 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: renewal time expired, rebinding 2024/09/14 21:47:45 Link "eth1" down. Attempting to set up 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: lease rebound, expiration is 2024-09-14 22:47:45.309270751 +0800 CST m=+11730.048516519 ``` Follow the https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.6, following options must not be sent in renew - Requested IP Address - Server Identifier Since the upstream code has been inactive for 6 years, we should switch to another dhcpv4 library. The new selected one is https://github.com/insomniacslk/dhcp. Signed-off-by: Songmin Li <lisongmin@protonmail.com>
This commit is contained in:

committed by
Casey Callendrello

parent
e4950728ce
commit
d61e7e5e1f
380
vendor/github.com/insomniacslk/dhcp/dhcpv4/options.go
generated
vendored
Normal file
380
vendor/github.com/insomniacslk/dhcp/dhcpv4/options.go
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
||||
package dhcpv4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
"github.com/insomniacslk/dhcp/rfc1035label"
|
||||
"github.com/u-root/uio/uio"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrShortByteStream is an error that is thrown any time a short byte stream is
|
||||
// detected during option parsing.
|
||||
ErrShortByteStream = errors.New("short byte stream")
|
||||
|
||||
// ErrZeroLengthByteStream is an error that is thrown any time a zero-length
|
||||
// byte stream is encountered.
|
||||
ErrZeroLengthByteStream = errors.New("zero-length byte stream")
|
||||
)
|
||||
|
||||
// OptionValue is an interface that all DHCP v4 options adhere to.
|
||||
type OptionValue interface {
|
||||
ToBytes() []byte
|
||||
String() string
|
||||
}
|
||||
|
||||
// Option is a DHCPv4 option and consists of a 1-byte option code and a value
|
||||
// stream of bytes.
|
||||
//
|
||||
// The value is to be interpreted based on the option code.
|
||||
type Option struct {
|
||||
Code OptionCode
|
||||
Value OptionValue
|
||||
}
|
||||
|
||||
// String returns a human-readable version of this option.
|
||||
func (o Option) String() string {
|
||||
v := o.Value.String()
|
||||
if strings.Contains(v, "\n") {
|
||||
return fmt.Sprintf("%s:\n%s", o.Code, v)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", o.Code, v)
|
||||
}
|
||||
|
||||
// Options is a collection of options.
|
||||
type Options map[uint8][]byte
|
||||
|
||||
// OptionsFromList adds all given options to an options map.
|
||||
func OptionsFromList(o ...Option) Options {
|
||||
opts := make(Options)
|
||||
for _, opt := range o {
|
||||
opts.Update(opt)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Get will attempt to get all options that match a DHCPv4 option
|
||||
// from its OptionCode. If the option was not found it will return an
|
||||
// empty list.
|
||||
//
|
||||
// According to RFC 3396, options that are specified more than once are
|
||||
// concatenated, and hence this should always just return one option. This
|
||||
// currently returns a list to be API compatible.
|
||||
func (o Options) Get(code OptionCode) []byte {
|
||||
return o[code.Code()]
|
||||
}
|
||||
|
||||
// Has checks whether o has the given opcode.
|
||||
func (o Options) Has(opcode OptionCode) bool {
|
||||
_, ok := o[opcode.Code()]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Del deletes the option matching the option code.
|
||||
func (o Options) Del(opcode OptionCode) {
|
||||
delete(o, opcode.Code())
|
||||
}
|
||||
|
||||
// Update updates the existing options with the passed option, adding it
|
||||
// at the end if not present already
|
||||
func (o Options) Update(option Option) {
|
||||
o[option.Code.Code()] = option.Value.ToBytes()
|
||||
}
|
||||
|
||||
// ToBytes makes Options usable as an OptionValue as well.
|
||||
//
|
||||
// Used in the case of vendor-specific and relay agent options.
|
||||
func (o Options) ToBytes() []byte {
|
||||
return uio.ToBigEndian(o)
|
||||
}
|
||||
|
||||
// FromBytes parses a sequence of bytes until the end and builds a list of
|
||||
// options from it.
|
||||
//
|
||||
// The sequence should not contain the DHCP magic cookie.
|
||||
//
|
||||
// Returns an error if any invalid option or length is found.
|
||||
func (o Options) FromBytes(data []byte) error {
|
||||
return o.fromBytesCheckEnd(data, false)
|
||||
}
|
||||
|
||||
const (
|
||||
optPad = 0
|
||||
optAgentInfo = 82
|
||||
optEnd = 255
|
||||
)
|
||||
|
||||
// FromBytesCheckEnd parses Options from byte sequences using the
|
||||
// parsing function that is passed in as a paremeter
|
||||
func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
buf := uio.NewBigEndianBuffer(data)
|
||||
|
||||
var end bool
|
||||
for buf.Len() >= 1 {
|
||||
// 1 byte: option code
|
||||
// 1 byte: option length n
|
||||
// n bytes: data
|
||||
code := buf.Read8()
|
||||
|
||||
if code == optPad {
|
||||
continue
|
||||
} else if code == optEnd {
|
||||
end = true
|
||||
break
|
||||
}
|
||||
length := int(buf.Read8())
|
||||
|
||||
// N bytes: option data
|
||||
data := buf.Consume(length)
|
||||
if data == nil {
|
||||
return fmt.Errorf("error collecting options: %v", buf.Error())
|
||||
}
|
||||
data = data[:length:length]
|
||||
|
||||
// RFC 2131, Section 4.1 "Options may appear only once, [...].
|
||||
// The client concatenates the values of multiple instances of
|
||||
// the same option into a single parameter list for
|
||||
// configuration."
|
||||
//
|
||||
// See also RFC 3396 for concatenation order and options longer
|
||||
// than 255 bytes.
|
||||
o[code] = append(o[code], data...)
|
||||
}
|
||||
|
||||
// If we never read the End option, the sender of this packet screwed
|
||||
// up.
|
||||
if !end && checkEndOption {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sortedKeys returns an ordered slice of option keys from the Options map, for
|
||||
// use in serializing options to binary.
|
||||
func (o Options) sortedKeys() []int {
|
||||
// Send all values for a given key
|
||||
var codes []int
|
||||
var hasOptAgentInfo, hasOptEnd bool
|
||||
for k := range o {
|
||||
// RFC 3046 section 2.1 states that option 82 SHALL come last (ignoring End).
|
||||
if k == optAgentInfo {
|
||||
hasOptAgentInfo = true
|
||||
continue
|
||||
}
|
||||
if k == optEnd {
|
||||
hasOptEnd = true
|
||||
continue
|
||||
}
|
||||
codes = append(codes, int(k))
|
||||
}
|
||||
|
||||
sort.Ints(codes)
|
||||
|
||||
if hasOptAgentInfo {
|
||||
codes = append(codes, optAgentInfo)
|
||||
}
|
||||
if hasOptEnd {
|
||||
codes = append(codes, optEnd)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
// Marshal writes options binary representations to b.
|
||||
func (o Options) Marshal(b *uio.Lexer) {
|
||||
for _, c := range o.sortedKeys() {
|
||||
code := uint8(c)
|
||||
// Even if the End option is in there, don't marshal it until
|
||||
// the end.
|
||||
// Don't write padding either, since the options are sorted
|
||||
// it would always be written first which isn't useful
|
||||
if code == optEnd || code == optPad {
|
||||
continue
|
||||
}
|
||||
|
||||
data := o[code]
|
||||
|
||||
// Ensure even 0-length options are written out
|
||||
if len(data) == 0 {
|
||||
b.Write8(code)
|
||||
b.Write8(0)
|
||||
continue
|
||||
}
|
||||
// RFC 3396: If more than 256 bytes of data are given, the
|
||||
// option is simply listed multiple times.
|
||||
for len(data) > 0 {
|
||||
// 1 byte: option code
|
||||
b.Write8(code)
|
||||
|
||||
n := len(data)
|
||||
if n > math.MaxUint8 {
|
||||
n = math.MaxUint8
|
||||
}
|
||||
|
||||
// 1 byte: option length
|
||||
b.Write8(uint8(n))
|
||||
|
||||
// N bytes: option data
|
||||
b.WriteBytes(data[:n])
|
||||
data = data[n:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String prints options using DHCP-specified option codes.
|
||||
func (o Options) String() string {
|
||||
return o.ToString(dhcpHumanizer)
|
||||
}
|
||||
|
||||
// Summary prints options in human-readable values.
|
||||
//
|
||||
// Summary uses vendorParser to interpret the OptionVendorSpecificInformation option.
|
||||
func (o Options) Summary(vendorDecoder OptionDecoder) string {
|
||||
return o.ToString(OptionHumanizer{
|
||||
ValueHumanizer: parserFor(vendorDecoder),
|
||||
CodeHumanizer: func(c uint8) OptionCode {
|
||||
return optionCode(c)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// OptionParser gives a human-legible interpretation of data for the given option code.
|
||||
type OptionParser func(code OptionCode, data []byte) fmt.Stringer
|
||||
|
||||
// OptionHumanizer is used to interpret a set of Options for their option code
|
||||
// name and values.
|
||||
//
|
||||
// There should be separate OptionHumanizers for each Option "space": DHCP,
|
||||
// BSDP, Relay Agent Info, and others.
|
||||
type OptionHumanizer struct {
|
||||
ValueHumanizer OptionParser
|
||||
CodeHumanizer func(code uint8) OptionCode
|
||||
}
|
||||
|
||||
// Stringify returns a human-readable interpretation of the option code and its
|
||||
// associated data.
|
||||
func (oh OptionHumanizer) Stringify(code uint8, data []byte) string {
|
||||
c := oh.CodeHumanizer(code)
|
||||
val := oh.ValueHumanizer(c, data)
|
||||
return fmt.Sprintf("%s: %s", c, val)
|
||||
}
|
||||
|
||||
// dhcpHumanizer humanizes the set of DHCP option codes.
|
||||
var dhcpHumanizer = OptionHumanizer{
|
||||
ValueHumanizer: parseOption,
|
||||
CodeHumanizer: func(c uint8) OptionCode {
|
||||
return optionCode(c)
|
||||
},
|
||||
}
|
||||
|
||||
// ToString uses parse to parse options into human-readable values.
|
||||
func (o Options) ToString(humanizer OptionHumanizer) string {
|
||||
var ret string
|
||||
for _, c := range o.sortedKeys() {
|
||||
code := uint8(c)
|
||||
v := o[code]
|
||||
optString := humanizer.Stringify(code, v)
|
||||
// If this option has sub structures, offset them accordingly.
|
||||
if strings.Contains(optString, "\n") {
|
||||
optString = strings.Replace(optString, "\n ", "\n ", -1)
|
||||
}
|
||||
ret += fmt.Sprintf(" %v\n", optString)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func parseOption(code OptionCode, data []byte) fmt.Stringer {
|
||||
return parserFor(nil)(code, data)
|
||||
}
|
||||
|
||||
func parserFor(vendorParser OptionDecoder) OptionParser {
|
||||
return func(code OptionCode, data []byte) fmt.Stringer {
|
||||
return getOption(code, data, vendorParser)
|
||||
}
|
||||
}
|
||||
|
||||
// OptionDecoder can decode a byte stream into a human-readable option.
|
||||
type OptionDecoder interface {
|
||||
fmt.Stringer
|
||||
FromBytes([]byte) error
|
||||
}
|
||||
|
||||
func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.Stringer {
|
||||
var d OptionDecoder
|
||||
switch code {
|
||||
case OptionRouter, OptionDomainNameServer, OptionNTPServers, OptionServerIdentifier:
|
||||
d = &IPs{}
|
||||
|
||||
case OptionBroadcastAddress, OptionRequestedIPAddress:
|
||||
d = &IP{}
|
||||
|
||||
case OptionClientSystemArchitectureType:
|
||||
d = &iana.Archs{}
|
||||
|
||||
case OptionSubnetMask:
|
||||
d = &IPMask{}
|
||||
|
||||
case OptionDHCPMessageType:
|
||||
var mt MessageType
|
||||
d = &mt
|
||||
|
||||
case OptionParameterRequestList:
|
||||
d = &OptionCodeList{}
|
||||
|
||||
case OptionHostName, OptionDomainName, OptionRootPath,
|
||||
OptionClassIdentifier, OptionTFTPServerName, OptionBootfileName,
|
||||
OptionMessage, OptionReferenceToTZDatabase:
|
||||
var s String
|
||||
d = &s
|
||||
|
||||
case OptionRelayAgentInformation:
|
||||
d = &RelayOptions{}
|
||||
|
||||
case OptionDNSDomainSearchList:
|
||||
d = &rfc1035label.Labels{}
|
||||
|
||||
case OptionIPAddressLeaseTime, OptionRenewTimeValue,
|
||||
OptionRebindingTimeValue, OptionIPv6OnlyPreferred, OptionArpCacheTimeout,
|
||||
OptionTimeOffset:
|
||||
var dur Duration
|
||||
d = &dur
|
||||
|
||||
case OptionMaximumDHCPMessageSize:
|
||||
var u Uint16
|
||||
d = &u
|
||||
|
||||
case OptionUserClassInformation:
|
||||
var s Strings
|
||||
d = &s
|
||||
if s.FromBytes(data) != nil {
|
||||
var s String
|
||||
d = &s
|
||||
}
|
||||
|
||||
case OptionAutoConfigure:
|
||||
var a AutoConfiguration
|
||||
d = &a
|
||||
|
||||
case OptionVendorIdentifyingVendorClass:
|
||||
d = &VIVCIdentifiers{}
|
||||
|
||||
case OptionVendorSpecificInformation:
|
||||
d = vendorDecoder
|
||||
|
||||
case OptionClasslessStaticRoute:
|
||||
d = &Routes{}
|
||||
}
|
||||
if d != nil && d.FromBytes(data) == nil {
|
||||
return d
|
||||
}
|
||||
return OptionGeneric{data}
|
||||
}
|
Reference in New Issue
Block a user