From 7cb3453c368a7bda878f82a899717f35e84ec31d Mon Sep 17 00:00:00 2001 From: SilverBut Date: Mon, 6 Sep 2021 01:23:03 +0800 Subject: [PATCH 01/10] dhcp: remove implemented TODO Signed-off-by: SilverBut --- plugins/ipam/dhcp/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index 72680607e..436706c19 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -88,8 +88,6 @@ func cmdDel(args *skel.CmdArgs) error { } func cmdCheck(args *skel.CmdArgs) error { - // TODO: implement - //return fmt.Errorf("not implemented") // Plugin must return result in same version as specified in netconf versionDecoder := &version.ConfigDecoder{} //confVersion, err := versionDecoder.Decode(args.StdinData) From 24259e7d213d4e0cc705271dac914fec30071509 Mon Sep 17 00:00:00 2001 From: SilverBut Date: Fri, 1 Oct 2021 04:16:51 +0800 Subject: [PATCH 02/10] dhcp ipam: using full config to regular the code Signed-off-by: SilverBut --- plugins/ipam/dhcp/daemon.go | 5 ++--- plugins/ipam/dhcp/main.go | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index 67a49c9b7..962264d45 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -29,7 +29,6 @@ import ( "time" "github.com/containernetworking/cni/pkg/skel" - "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/100" "github.com/coreos/go-systemd/v22/activation" ) @@ -62,7 +61,7 @@ func generateClientID(containerID string, netName string, ifName string) string // Allocate acquires an IP from a DHCP server for a specified container. // The acquired lease will be maintained until Release() is called. func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { - conf := types.NetConf{} + conf := NetConf{} if err := json.Unmarshal(args.StdinData, &conf); err != nil { return fmt.Errorf("error parsing netconf: %v", err) } @@ -94,7 +93,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { // Release stops maintenance of the lease acquired in Allocate() // and sends a release msg to the DHCP server. func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error { - conf := types.NetConf{} + conf := NetConf{} if err := json.Unmarshal(args.StdinData, &conf); err != nil { return fmt.Errorf("error parsing netconf: %v", err) } diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index 436706c19..3a3c324c3 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -33,6 +33,18 @@ import ( const defaultSocketPath = "/run/cni/dhcp.sock" +// The top-level network config - IPAM plugins are passed the full configuration +// of the calling plugin, not just the IPAM section. +type NetConf struct { + types.NetConf + IPAM *IPAMConfig `json:"ipam"` +} + +type IPAMConfig struct { + types.IPAM + DaemonSocketPath string `json:"daemonSocketPath,omitempty"` +} + func main() { if len(os.Args) > 1 && os.Args[1] == "daemon" { var pidfilePath string @@ -104,16 +116,8 @@ func cmdCheck(args *skel.CmdArgs) error { return nil } -type SocketPathConf struct { - DaemonSocketPath string `json:"daemonSocketPath,omitempty"` -} - -type TempNetConf struct { - IPAM SocketPathConf `json:"ipam,omitempty"` -} - func getSocketPath(stdinData []byte) (string, error) { - conf := TempNetConf{} + conf := NetConf{} if err := json.Unmarshal(stdinData, &conf); err != nil { return "", fmt.Errorf("error parsing socket path conf: %v", err) } From 6d1f71e55a38579ac8f67a3b0d4abc0857d8587b Mon Sep 17 00:00:00 2001 From: SilverBut Date: Fri, 1 Oct 2021 04:17:29 +0800 Subject: [PATCH 03/10] dhcp ipam: print error correctly without format string Signed-off-by: SilverBut --- plugins/ipam/dhcp/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index 3a3c324c3..e0a63a8bc 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -67,7 +67,7 @@ func main() { } if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, broadcast); err != nil { - log.Printf(err.Error()) + log.Print(err.Error()) os.Exit(1) } } else { From be383cf30da3bed1997b7c76bad46847f1d411f2 Mon Sep 17 00:00:00 2001 From: SilverBut Date: Fri, 1 Oct 2021 19:44:26 +0800 Subject: [PATCH 04/10] dhcp ipam: truncate client id to 254 bytes Signed-off-by: SilverBut --- plugins/ipam/dhcp/daemon.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index 962264d45..f1bceeab6 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -33,8 +33,6 @@ import ( "github.com/coreos/go-systemd/v22/activation" ) -const listenFdsStart = 3 - var errNoMoreTries = errors.New("no more tries") type DHCP struct { @@ -54,8 +52,15 @@ func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP { } } +// TODO: current client ID is too long. At least the container ID should not be used directly. +// A seperate issue is necessary to ensure no breaking change is affecting other users. func generateClientID(containerID string, netName string, ifName string) string { - return containerID + "/" + netName + "/" + ifName + clientID := containerID + "/" + netName + "/" + ifName + // defined in RFC 2132, length size can not be larger than 1 octet. So we truncate 254 to make everyone happy. + if len(clientID) > 254 { + clientID = clientID[0:254] + } + return clientID } // Allocate acquires an IP from a DHCP server for a specified container. From 2bebd89aa2384f9410e3764e9798733265d04f0f Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sat, 2 Oct 2021 20:48:17 +0800 Subject: [PATCH 05/10] dhcp ipam: support customizing dhcp options Signed-off-by: SilverBut --- plugins/ipam/dhcp/daemon.go | 9 +++- plugins/ipam/dhcp/lease.go | 90 ++++++++++++++++++++++++++++--- plugins/ipam/dhcp/main.go | 27 +++++++++- plugins/ipam/dhcp/options.go | 19 +++++++ plugins/ipam/dhcp/options_test.go | 32 +++++++++++ 5 files changed, 169 insertions(+), 8 deletions(-) diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index f1bceeab6..cdf11a8f1 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -71,9 +71,16 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { return fmt.Errorf("error parsing netconf: %v", err) } + optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequireOptions) + if err != nil { + return err + } + clientID := generateClientID(args.ContainerID, conf.Name, args.IfName) hostNetns := d.hostNetnsPrefix + args.Netns - l, err := AcquireLease(clientID, hostNetns, args.IfName, d.clientTimeout, d.clientResendMax, d.broadcast) + l, err := AcquireLease(clientID, hostNetns, args.IfName, + optsRequesting, optsProviding, + d.clientTimeout, d.clientResendMax, d.broadcast) if err != nil { return err } diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index ef68931d4..9a7e5e48a 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -19,6 +19,7 @@ import ( "log" "math/rand" "net" + "strings" "sync" "sync/atomic" "time" @@ -62,6 +63,74 @@ type DHCPLease struct { stopping uint32 stop chan struct{} wg sync.WaitGroup + // list of requesting and providing options and if they are necessary / their value + optsRequesting map[dhcp4.OptionCode]bool + optsProviding map[dhcp4.OptionCode][]byte +} + +var acquireOptionsDefault = map[dhcp4.OptionCode]bool{ + dhcp4.OptionRouter: true, + dhcp4.OptionSubnetMask: true, +} + +func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequireOptions []RequireOption) ( + optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte, err error) { + + // parse CNI args + cniArgsParsed := map[string]string{} + for _, argPair := range strings.Split(cniArgs, ";") { + args := strings.SplitN(argPair, "=", 2) + if len(args) > 1 { + cniArgsParsed[args[0]] = args[1] + } + } + + // parse providing options map + var optParsed dhcp4.OptionCode + optsProviding = make(map[dhcp4.OptionCode][]byte) + for _, opt := range ProvideOptions { + optParsed, err = parseOptionName(string(opt.Option)) + if err != nil { + err = fmt.Errorf("Can not parse option %q: %w", opt.Option, err) + return + } + if len(opt.Value) > 0 { + if len(opt.Value) > 255 { + err = fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value) + return + } + optsProviding[optParsed] = []byte(opt.Value) + } + if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok { + if len(value) > 255 { + err = fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value) + return + } + optsProviding[optParsed] = []byte(value) + } + } + + // parse necessary options map + optsRequesting = make(map[dhcp4.OptionCode]bool) + skipRequireDefault := false + for _, opt := range RequireOptions { + if opt.SkipDefault { + skipRequireDefault = true + } + optParsed, err = parseOptionName(string(opt.Option)) + if err != nil { + err = fmt.Errorf("Can not parse option %q: %w", opt.Option, err) + return + } + optsRequesting[optParsed] = true + } + for k, v := range acquireOptionsDefault { + // only set if not skipping default and this value does not exists + if _, ok := optsRequesting[k]; !ok && !skipRequireDefault { + optsRequesting[k] = v + } + } + return } // AcquireLease gets an DHCP lease and then maintains it in the background @@ -69,15 +138,18 @@ type DHCPLease struct { // calling DHCPLease.Stop() func AcquireLease( clientID, netns, ifName string, + optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte, timeout, resendMax time.Duration, broadcast bool, ) (*DHCPLease, error) { errCh := make(chan error, 1) l := &DHCPLease{ - clientID: clientID, - stop: make(chan struct{}), - timeout: timeout, - resendMax: resendMax, - broadcast: broadcast, + clientID: clientID, + stop: make(chan struct{}), + timeout: timeout, + resendMax: resendMax, + broadcast: broadcast, + optsRequesting: optsRequesting, + optsProviding: optsProviding, } log.Printf("%v: acquiring lease", clientID) @@ -139,7 +211,13 @@ func (l *DHCPLease) acquire() error { opts := make(dhcp4.Options) opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) - opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter), byte(dhcp4.OptionSubnetMask)} + opts[dhcp4.OptionParameterRequestList] = []byte{} + for k := range l.optsRequesting { + opts[dhcp4.OptionParameterRequestList] = append(opts[dhcp4.OptionParameterRequestList], byte(k)) + } + for k, v := range l.optsProviding { + opts[k] = v + } pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) { ok, ack, err := DhcpRequest(c, opts) diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index e0a63a8bc..e8e2771f6 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -42,7 +42,32 @@ type NetConf struct { type IPAMConfig struct { types.IPAM - DaemonSocketPath string `json:"daemonSocketPath,omitempty"` + DaemonSocketPath string `json:"daemonSocketPath"` + // When requesting IP from DHCP server, carry these options for management purpose. + // Some fields have default values, and can be override by setting a new option with the same name at here. + ProvideOptions []ProvideOption `json:"provide"` + // When requesting IP from DHCP server, claiming these options are necessary. Options are necessary unless `optional` + // is set to `false`. + // To override default required fields, set `skipDefault` to `false`. + // If an field is not optional, but the server failed to provide it, error will be raised. + RequireOptions []RequireOption `json:"require"` +} + +// DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5). +// Note that not all DHCP options are supported at all time. Error will be raised if unsupported options are used. +type DHCPOption string + +type ProvideOption struct { + Option DHCPOption `json:"option"` + + Value string `json:"value"` + ValueFromCNIArg string `json:"fromArg"` +} + +type RequireOption struct { + SkipDefault bool `json:"skipDefault"` + + Option DHCPOption `json:"option"` } func main() { diff --git a/plugins/ipam/dhcp/options.go b/plugins/ipam/dhcp/options.go index 910e1cc61..6415a9ac8 100644 --- a/plugins/ipam/dhcp/options.go +++ b/plugins/ipam/dhcp/options.go @@ -18,12 +18,31 @@ import ( "encoding/binary" "fmt" "net" + "strconv" "time" "github.com/containernetworking/cni/pkg/types" "github.com/d2g/dhcp4" ) +var optionNameToID = map[string]dhcp4.OptionCode{ + "dhcp-client-identifier": dhcp4.OptionClientIdentifier, + "subnet-mask": dhcp4.OptionSubnetMask, + "routers": dhcp4.OptionRouter, + "host-name": dhcp4.OptionHostName, +} + +func parseOptionName(option string) (dhcp4.OptionCode, error) { + if val, ok := optionNameToID[option]; ok { + return val, nil + } + i, err := strconv.ParseUint(option, 10, 8) + if err != nil { + return 0, fmt.Errorf("Can not parse option: %w", err) + } + return dhcp4.OptionCode(i), nil +} + func parseRouter(opts dhcp4.Options) net.IP { if opts, ok := opts[dhcp4.OptionRouter]; ok { if len(opts) == 4 { diff --git a/plugins/ipam/dhcp/options_test.go b/plugins/ipam/dhcp/options_test.go index 961070c23..ee18b8816 100644 --- a/plugins/ipam/dhcp/options_test.go +++ b/plugins/ipam/dhcp/options_test.go @@ -16,6 +16,7 @@ package main import ( "net" + "reflect" "testing" "github.com/containernetworking/cni/pkg/types" @@ -73,3 +74,34 @@ func TestParseCIDRRoutes(t *testing.T) { validateRoutes(t, routes) } + +func TestParseOptionName(t *testing.T) { + tests := []struct { + name string + option string + want dhcp4.OptionCode + wantErr bool + }{ + { + "hostname", "host-name", dhcp4.OptionHostName, false, + }, + { + "hostname in number", "12", dhcp4.OptionHostName, false, + }, + { + "random string", "doNotparseMe", 0, true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseOptionName(tt.option) + if (err != nil) != tt.wantErr { + t.Errorf("parseOptionName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseOptionName() = %v, want %v", got, tt.want) + } + }) + } +} From 4b216e9d9b4ef6914c2b9f62a51c43139e6188ba Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sat, 2 Oct 2021 23:04:24 +0800 Subject: [PATCH 06/10] dhcp ipam: add fast retry Almost every first retry of DHCP will fail due to interface is not up. Add a fast retry to reduce unnecessary latency. Signed-off-by: SilverBut --- plugins/ipam/dhcp/lease.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index 9a7e5e48a..0e9e72d25 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -423,6 +423,7 @@ func jitter(span time.Duration) time.Duration { func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { var baseDelay time.Duration = resendDelay0 var sleepTime time.Duration + var fastRetryLimit = 3 // fast retry for 3 times to speed up for { pkt, err := f() @@ -432,7 +433,12 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh log.Print(err) - sleepTime = baseDelay + jitter(time.Second) + if fastRetryLimit == 0 { + sleepTime = baseDelay + jitter(time.Second) + } else { + sleepTime = jitter(time.Second) + fastRetryLimit-- + } log.Printf("retrying in %f seconds", sleepTime.Seconds()) From c627ea807c1268f5227ff0a42b099a00da85d5d5 Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sat, 2 Oct 2021 23:30:59 +0800 Subject: [PATCH 07/10] dhcp ipam: add more options capable for sending Signed-off-by: SilverBut --- plugins/ipam/dhcp/options.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/ipam/dhcp/options.go b/plugins/ipam/dhcp/options.go index 6415a9ac8..0adbc6550 100644 --- a/plugins/ipam/dhcp/options.go +++ b/plugins/ipam/dhcp/options.go @@ -26,10 +26,12 @@ import ( ) var optionNameToID = map[string]dhcp4.OptionCode{ - "dhcp-client-identifier": dhcp4.OptionClientIdentifier, - "subnet-mask": dhcp4.OptionSubnetMask, - "routers": dhcp4.OptionRouter, - "host-name": dhcp4.OptionHostName, + "dhcp-client-identifier": dhcp4.OptionClientIdentifier, + "subnet-mask": dhcp4.OptionSubnetMask, + "routers": dhcp4.OptionRouter, + "host-name": dhcp4.OptionHostName, + "user-class": dhcp4.OptionUserClass, + "vendor-class-identifier": dhcp4.OptionVendorClassIdentifier, } func parseOptionName(option string) (dhcp4.OptionCode, error) { From a1051f3bf1b1078610c21c8a18bf43bad78680c4 Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sat, 2 Oct 2021 23:49:59 +0800 Subject: [PATCH 08/10] dhcp ipam: rename inconsistent options among files Signed-off-by: SilverBut --- plugins/ipam/dhcp/daemon.go | 2 +- plugins/ipam/dhcp/lease.go | 8 ++++---- plugins/ipam/dhcp/main.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index cdf11a8f1..b17a64614 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -71,7 +71,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error { return fmt.Errorf("error parsing netconf: %v", err) } - optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequireOptions) + optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions) if err != nil { return err } diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index 0e9e72d25..6fc0ffa63 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -68,12 +68,12 @@ type DHCPLease struct { optsProviding map[dhcp4.OptionCode][]byte } -var acquireOptionsDefault = map[dhcp4.OptionCode]bool{ +var requestOptionsDefault = map[dhcp4.OptionCode]bool{ dhcp4.OptionRouter: true, dhcp4.OptionSubnetMask: true, } -func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequireOptions []RequireOption) ( +func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequestOptions []RequestOption) ( optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte, err error) { // parse CNI args @@ -113,7 +113,7 @@ func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequireOptio // parse necessary options map optsRequesting = make(map[dhcp4.OptionCode]bool) skipRequireDefault := false - for _, opt := range RequireOptions { + for _, opt := range RequestOptions { if opt.SkipDefault { skipRequireDefault = true } @@ -124,7 +124,7 @@ func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequireOptio } optsRequesting[optParsed] = true } - for k, v := range acquireOptionsDefault { + for k, v := range requestOptionsDefault { // only set if not skipping default and this value does not exists if _, ok := optsRequesting[k]; !ok && !skipRequireDefault { optsRequesting[k] = v diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index e8e2771f6..be18aff4c 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -48,9 +48,9 @@ type IPAMConfig struct { ProvideOptions []ProvideOption `json:"provide"` // When requesting IP from DHCP server, claiming these options are necessary. Options are necessary unless `optional` // is set to `false`. - // To override default required fields, set `skipDefault` to `false`. + // To override default requesting fields, set `skipDefault` to `false`. // If an field is not optional, but the server failed to provide it, error will be raised. - RequireOptions []RequireOption `json:"require"` + RequestOptions []RequestOption `json:"request"` } // DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5). @@ -64,7 +64,7 @@ type ProvideOption struct { ValueFromCNIArg string `json:"fromArg"` } -type RequireOption struct { +type RequestOption struct { SkipDefault bool `json:"skipDefault"` Option DHCPOption `json:"option"` From 27fdec5cb97346a06c30a886884505497825b479 Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sun, 3 Oct 2021 05:56:31 +0800 Subject: [PATCH 09/10] dhcp ipam: fix client id First byte of client ID is type, instead of value. See this from RFC2132: Code Len Type Client-Identifier +-----+-----+-----+-----+-----+--- | 61 | n | t1 | i1 | i2 | ... +-----+-----+-----+-----+-----+--- Signed-off-by: SilverBut --- plugins/ipam/dhcp/lease.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index 6fc0ffa63..a2989c6ba 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -218,6 +218,10 @@ func (l *DHCPLease) acquire() error { for k, v := range l.optsProviding { opts[k] = v } + // client identifier's first byte is "type" + newClientID := []byte{0} + newClientID = append(newClientID, opts[dhcp4.OptionClientIdentifier]...) + opts[dhcp4.OptionClientIdentifier] = newClientID pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) { ok, ack, err := DhcpRequest(c, opts) From c9d04230234f29a4011287adc4ea93ba57cfb65d Mon Sep 17 00:00:00 2001 From: SilverBut Date: Sun, 28 Nov 2021 06:41:53 +0800 Subject: [PATCH 10/10] dhcp ipam: adjust retry mechanism Signed-off-by: SilverBut --- plugins/ipam/dhcp/lease.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index a2989c6ba..943e6e2c6 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -37,6 +37,11 @@ import ( const resendDelay0 = 4 * time.Second const resendDelayMax = 62 * time.Second +// To speed up the retry for first few failures, we retry without +// backoff for a few times +const resendFastDelay = 2 * time.Second +const resendFastMax = 4 + const ( leaseStateBound = iota leaseStateRenewing @@ -427,8 +432,7 @@ func jitter(span time.Duration) time.Duration { func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { var baseDelay time.Duration = resendDelay0 var sleepTime time.Duration - var fastRetryLimit = 3 // fast retry for 3 times to speed up - + var fastRetryLimit = resendFastMax for { pkt, err := f() if err == nil { @@ -440,7 +444,7 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh if fastRetryLimit == 0 { sleepTime = baseDelay + jitter(time.Second) } else { - sleepTime = jitter(time.Second) + sleepTime = resendFastDelay + jitter(time.Second) fastRetryLimit-- } @@ -448,7 +452,8 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh time.Sleep(sleepTime) - if baseDelay < resendMax { + // only adjust delay time if we are in normal backoff stage + if baseDelay < resendMax && fastRetryLimit == 0 { baseDelay *= 2 } else { break