Skip to content

Commit

Permalink
Add support for IPv6-only preferred (RFC8925) and
Browse files Browse the repository at this point in the history
control of link-local autoconfigure (RFC2563)

Signed-off-by: Brian Candler <b.candler@pobox.com>
  • Loading branch information
candlerb authored and Natolumin committed Feb 27, 2024
1 parent 321f8a6 commit 12a2f40
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 471 deletions.
2 changes: 2 additions & 0 deletions cmds/coredhcp-generator/core-plugins.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/coredhcp/coredhcp/plugins/autoconfigure
github.com/coredhcp/coredhcp/plugins/dns
github.com/coredhcp/coredhcp/plugins/file
github.com/coredhcp/coredhcp/plugins/ipv6only
github.com/coredhcp/coredhcp/plugins/leasetime
github.com/coredhcp/coredhcp/plugins/mtu
github.com/coredhcp/coredhcp/plugins/netmask
Expand Down
4 changes: 4 additions & 0 deletions cmds/coredhcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
"github.com/coredhcp/coredhcp/server"

"github.com/coredhcp/coredhcp/plugins"
pl_autoconfigure "github.com/coredhcp/coredhcp/plugins/autoconfigure"
pl_dns "github.com/coredhcp/coredhcp/plugins/dns"
pl_file "github.com/coredhcp/coredhcp/plugins/file"
pl_ipv6only "github.com/coredhcp/coredhcp/plugins/ipv6only"
pl_leasetime "github.com/coredhcp/coredhcp/plugins/leasetime"
pl_mtu "github.com/coredhcp/coredhcp/plugins/mtu"
pl_nbp "github.com/coredhcp/coredhcp/plugins/nbp"
Expand Down Expand Up @@ -61,8 +63,10 @@ func getLogLevels() []string {
}

var desiredPlugins = []*plugins.Plugin{
&pl_autoconfigure.Plugin,
&pl_dns.Plugin,
&pl_file.Plugin,
&pl_ipv6only.Plugin,
&pl_leasetime.Plugin,
&pl_mtu.Plugin,
&pl_nbp.Plugin,
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb
github.com/fsnotify/fsnotify v1.7.0
github.com/google/gopacket v1.1.19
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8
github.com/mattn/go-sqlite3 v1.14.19
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.9.3
Expand Down Expand Up @@ -37,11 +37,9 @@ require (
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
Expand Down
471 changes: 3 additions & 468 deletions go.sum

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions plugins/autoconfigure/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

package autoconfigure

// This plugin implements RFC2563:
// 1. If the client has been allocated an IP address, do nothing
// 2. If the client has not been allocated an IP address
// (yiaddr=0.0.0.0), then:
// 2a. If the client has requested the "AutoConfigure" option,
// then add the defined value to the response
// 2b. Otherwise, terminate processing and send no reply
//
// This plugin should be used at the end of the plugin chain,
// after any IP address allocation has taken place.
//
// The optional argument is the string "DoNotAutoConfigure" or
// "AutoConfigure" (or "0" or "1" respectively). The default
// is DoNotAutoConfigure.

import (
"errors"
"fmt"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/sirupsen/logrus"
)

var log = logger.GetLogger("plugins/autoconfigure")

var autoconfigure dhcpv4.AutoConfiguration

var Plugin = plugins.Plugin{
Name: "autoconfigure",
Setup4: setup4,
}

var argMap = map[string]dhcpv4.AutoConfiguration{
"0": dhcpv4.AutoConfiguration(0),
"1": dhcpv4.AutoConfiguration(1),
"DoNotAutoConfigure": dhcpv4.DoNotAutoConfigure,
"AutoConfigure": dhcpv4.AutoConfigure,
}

func setup4(args ...string) (handler.Handler4, error) {
if len(args) > 0 {
var ok bool
autoconfigure, ok = argMap[args[0]]
if !ok {
return nil, fmt.Errorf("unexpected value '%v' for autoconfigure argument", args[0])
}
}
if len(args) > 1 {
return nil, errors.New("too many arguments")
}
return Handler4, nil
}

func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
if resp.MessageType() != dhcpv4.MessageTypeOffer || !resp.YourIPAddr.IsUnspecified() {
return resp, false
}

ac, ok := req.AutoConfigure()
if ok {
resp.UpdateOption(dhcpv4.OptAutoConfigure(autoconfigure))
log.WithFields(logrus.Fields{
"mac": req.ClientHWAddr.String(),
"autoconfigure": fmt.Sprintf("%v", ac),
}).Debugf("Responded with autoconfigure %v", autoconfigure)
return resp, false
}

log.WithFields(logrus.Fields{
"mac": req.ClientHWAddr.String(),
"autoconfigure": "nil",
}).Debugf("Client does not support autoconfigure")
// RFC2563 2.3: if no address is chosen for the host [...]
// If the DHCPDISCOVER does not contain the Auto-Configure option,
// it is not answered.
return nil, true
}
118 changes: 118 additions & 0 deletions plugins/autoconfigure/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

package autoconfigure

import (
"bytes"
"net"
"testing"

"github.com/insomniacslk/dhcp/dhcpv4"
)

func TestOptionRequested0(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
req.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionAutoConfigure, []byte{1}))
stub, err := dhcpv4.NewReplyFromRequest(req,
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
)
if err != nil {
t.Fatal(err)
}

resp, stop := Handler4(req, stub)
if resp == nil {
t.Fatal("plugin did not return a message")
}
if stop {
t.Error("plugin interrupted processing")
}
opt := resp.Options.Get(dhcpv4.OptionAutoConfigure)
if opt == nil {
t.Fatal("plugin did not return the Auto-Configure option")
}
if !bytes.Equal(opt, []byte{0}) {
t.Errorf("plugin gave wrong option response: %v", opt)
}
}

func TestOptionRequested1(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
req.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionAutoConfigure, []byte{1}))
stub, err := dhcpv4.NewReplyFromRequest(req,
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
)
if err != nil {
t.Fatal(err)
}

autoconfigure = 1
resp, stop := Handler4(req, stub)
if resp == nil {
t.Fatal("plugin did not return a message")
}
if stop {
t.Error("plugin interrupted processing")
}
opt := resp.Options.Get(dhcpv4.OptionAutoConfigure)
if opt == nil {
t.Fatal("plugin did not return the Auto-Configure option")
}
if !bytes.Equal(opt, []byte{1}) {
t.Errorf("plugin gave wrong option response: %v", opt)
}
}

func TestNotRequestedAssignedIP(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
stub, err := dhcpv4.NewReplyFromRequest(req,
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
)
if err != nil {
t.Fatal(err)
}
stub.YourIPAddr = net.ParseIP("192.0.2.100")

resp, stop := Handler4(req, stub)
if resp == nil {
t.Fatal("plugin did not return a message")
}
if stop {
t.Error("plugin interrupted processing")
}
if resp.Options.Get(dhcpv4.OptionAutoConfigure) != nil {
t.Error("plugin responsed with AutoConfigure option")
}
}

func TestNotRequestedNoIP(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
stub, err := dhcpv4.NewReplyFromRequest(req,
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
)
if err != nil {
t.Fatal(err)
}

resp, stop := Handler4(req, stub)
if resp != nil {
t.Error("plugin returned a message")
}
if !stop {
t.Error("plugin did not interrupt processing")
}
}
64 changes: 64 additions & 0 deletions plugins/ipv6only/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

package ipv6only

// This plugin implements RFC8925: if the client has requested the
// IPv6-Only Preferred option, then add the option response and then
// terminate processing immediately.
//
// This module should be invoked *before* any IP address
// allocation has been done, so that the yiaddr is 0.0.0.0 and
// no pool addresses are consumed for compatible clients.
//
// The optional argument is the V6ONLY_WAIT configuration variable,
// described in RFC8925 section 3.2.

import (
"errors"
"time"

"github.com/coredhcp/coredhcp/handler"
"github.com/coredhcp/coredhcp/logger"
"github.com/coredhcp/coredhcp/plugins"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/sirupsen/logrus"
)

var log = logger.GetLogger("plugins/ipv6only")

var v6only_wait time.Duration

var Plugin = plugins.Plugin{
Name: "ipv6only",
Setup4: setup4,
}

func setup4(args ...string) (handler.Handler4, error) {
if len(args) > 0 {
dur, err := time.ParseDuration(args[0])
if err != nil {
log.Errorf("invalid duration: %v", args[0])
return nil, errors.New("ipv6only failed to initialize")
}
v6only_wait = dur
}
if len(args) > 1 {
return nil, errors.New("too many arguments")
}
return Handler4, nil
}

func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
v6pref := req.IsOptionRequested(dhcpv4.OptionIPv6OnlyPreferred)
log.WithFields(logrus.Fields{
"mac": req.ClientHWAddr.String(),
"ipv6only": v6pref,
}).Debug("ipv6only status")
if v6pref {
resp.UpdateOption(dhcpv4.OptIPv6OnlyPreferred(v6only_wait))
return resp, true
}
return resp, false
}
65 changes: 65 additions & 0 deletions plugins/ipv6only/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

package ipv6only

import (
"bytes"
"net"
"testing"
"time"

"github.com/insomniacslk/dhcp/dhcpv4"
)

func TestOptionRequested(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
req.UpdateOption(dhcpv4.OptParameterRequestList(dhcpv4.OptionBroadcastAddress, dhcpv4.OptionIPv6OnlyPreferred))
stub, err := dhcpv4.NewReplyFromRequest(req)
if err != nil {
t.Fatal(err)
}

v6only_wait = 0x1234 * time.Second

resp, stop := Handler4(req, stub)
if resp == nil {
t.Fatal("plugin did not return a message")
}
if !stop {
t.Error("plugin did not interrupt processing")
}
opt := resp.Options.Get(dhcpv4.OptionIPv6OnlyPreferred)
if opt == nil {
t.Fatal("plugin did not return the IPv6-Only Preferred option")
}
if !bytes.Equal(opt, []byte{0x00, 0x00, 0x12, 0x34}) {
t.Errorf("plugin gave wrong option response: %v", opt)
}
}

func TestNotRequested(t *testing.T) {
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
if err != nil {
t.Fatal(err)
}
stub, err := dhcpv4.NewReplyFromRequest(req)
if err != nil {
t.Fatal(err)
}

resp, stop := Handler4(req, stub)
if resp == nil {
t.Fatal("plugin did not return a message")
}
if stop {
t.Error("plugin interrupted processing")
}
if resp.Options.Get(dhcpv4.OptionIPv6OnlyPreferred) != nil {
t.Error("Found IPv6-Only Preferred option when not requested")
}
}

0 comments on commit 12a2f40

Please sign in to comment.