summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv6/dhcpv6message.go26
-rw-r--r--dhcpv6/option_ntp_server.go155
-rw-r--r--dhcpv6/option_ntp_server_test.go82
-rw-r--r--dhcpv6/options.go2
4 files changed, 265 insertions, 0 deletions
diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go
index e18fd59..5b76d3a 100644
--- a/dhcpv6/dhcpv6message.go
+++ b/dhcpv6/dhcpv6message.go
@@ -288,6 +288,32 @@ func (mo MessageOptions) DHCP4oDHCP6Server() *OptDHCP4oDHCP6Server {
return nil
}
+// NTPServers returns the NTP server addresses contained in the
+// NTP_SUBOPTION_SRV_ADDR of an OPTION_NTP_SERVER.
+// If multiple NTP server options exist, the function will return all the NTP
+// server addresses it finds, as defined by RFC 5908.
+func (mo MessageOptions) NTPServers() []net.IP {
+ opts := mo.Options.Get(OptionNTPServer)
+ if opts == nil {
+ return nil
+ }
+ addrs := make([]net.IP, 0)
+ for _, opt := range opts {
+ ntp, ok := opt.(*OptNTPServer)
+ if ok {
+ continue
+ }
+ for _, subopt := range ntp.Suboptions {
+ so, ok := subopt.(*NTPSuboptionSrvAddr)
+ if !ok {
+ continue
+ }
+ addrs = append(addrs, net.IP(*so))
+ }
+ }
+ return addrs
+}
+
// Message represents a DHCPv6 Message as defined by RFC 3315 Section 6.
type Message struct {
MessageType MessageType
diff --git a/dhcpv6/option_ntp_server.go b/dhcpv6/option_ntp_server.go
new file mode 100644
index 0000000..a7aafb7
--- /dev/null
+++ b/dhcpv6/option_ntp_server.go
@@ -0,0 +1,155 @@
+package dhcpv6
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/insomniacslk/dhcp/rfc1035label"
+ "github.com/u-root/uio/uio"
+)
+
+// NTPSuboptionSrvAddr is NTP_SUBOPTION_SRV_ADDR according to RFC 5908.
+type NTPSuboptionSrvAddr net.IP
+
+// Code returns the suboption code.
+func (n *NTPSuboptionSrvAddr) Code() OptionCode {
+ return NTPSuboptionSrvAddrCode
+}
+
+// ToBytes returns the byte serialization of the suboption.
+func (n *NTPSuboptionSrvAddr) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ buf.Write16(uint16(NTPSuboptionSrvAddrCode))
+ buf.Write16(uint16(net.IPv6len))
+ buf.WriteBytes(net.IP(*n).To16())
+ return buf.Data()
+}
+
+func (n *NTPSuboptionSrvAddr) String() string {
+ return fmt.Sprintf("Server Address: %s", net.IP(*n).String())
+}
+
+// NTPSuboptionMCAddr is NTP_SUBOPTION_MC_ADDR according to RFC 5908.
+type NTPSuboptionMCAddr net.IP
+
+// Code returns the suboption code.
+func (n *NTPSuboptionMCAddr) Code() OptionCode {
+ return NTPSuboptionMCAddrCode
+}
+
+// ToBytes returns the byte serialization of the suboption.
+func (n *NTPSuboptionMCAddr) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ buf.Write16(uint16(NTPSuboptionMCAddrCode))
+ buf.Write16(uint16(net.IPv6len))
+ buf.WriteBytes(net.IP(*n).To16())
+ return buf.Data()
+}
+
+func (n *NTPSuboptionMCAddr) String() string {
+ return fmt.Sprintf("Multicast Address: %s", net.IP(*n).String())
+}
+
+// NTPSuboptionSrvFQDN is NTP_SUBOPTION_SRV_FQDN according to RFC 5908.
+type NTPSuboptionSrvFQDN rfc1035label.Labels
+
+// Code returns the suboption code.
+func (n *NTPSuboptionSrvFQDN) Code() OptionCode {
+ return NTPSuboptionSrvFQDNCode
+}
+
+// ToBytes returns the byte serialization of the suboption.
+func (n *NTPSuboptionSrvFQDN) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ buf.Write16(uint16(NTPSuboptionSrvFQDNCode))
+ l := rfc1035label.Labels(*n)
+ buf.Write16(uint16(l.Length()))
+ buf.WriteBytes(l.ToBytes())
+ return buf.Data()
+}
+
+func (n *NTPSuboptionSrvFQDN) String() string {
+ l := rfc1035label.Labels(*n)
+ return fmt.Sprintf("Server FQDN: %s", l.String())
+}
+
+// NTPSuboptionSrvAddr is the value of NTP_SUBOPTION_SRV_ADDR according to RFC 5908.
+const (
+ NTPSuboptionSrvAddrCode = OptionCode(1)
+ NTPSuboptionMCAddrCode = OptionCode(2)
+ NTPSuboptionSrvFQDNCode = OptionCode(3)
+)
+
+// parseNTPSuboption implements the OptionParser interface.
+func parseNTPSuboption(code OptionCode, data []byte) (Option, error) {
+ //var o Options
+ buf := uio.NewBigEndianBuffer(data)
+ length := len(data)
+ data, err := buf.ReadN(length)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read %d bytes for suboption: %w", length, err)
+ }
+ switch code {
+ case NTPSuboptionSrvAddrCode, NTPSuboptionMCAddrCode:
+ if length != net.IPv6len {
+ return nil, fmt.Errorf("invalid suboption length, want %d, got %d", net.IPv6len, length)
+ }
+ var so Option
+ switch code {
+ case NTPSuboptionSrvAddrCode:
+ sos := NTPSuboptionSrvAddr(data)
+ so = &sos
+ case NTPSuboptionMCAddrCode:
+ som := NTPSuboptionMCAddr(data)
+ so = &som
+ }
+ return so, nil
+ case NTPSuboptionSrvFQDNCode:
+ l, err := rfc1035label.FromBytes(data)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse rfc1035 labels: %w", err)
+ }
+ // TODO according to rfc3315, this label must not be compressed.
+ // Need to add support for compression detection to the
+ // `rfc1035label` package in order to do that.
+ so := NTPSuboptionSrvFQDN(*l)
+ return &so, nil
+ default:
+ gopt := OptionGeneric{OptionCode: code, OptionData: data}
+ return &gopt, nil
+ }
+}
+
+// ParseOptNTPServer parses a sequence of bytes into an OptNTPServer object.
+func ParseOptNTPServer(data []byte) (*OptNTPServer, error) {
+ var so Options
+ if err := so.FromBytesWithParser(data, parseNTPSuboption); err != nil {
+ return nil, err
+ }
+ return &OptNTPServer{
+ Suboptions: so,
+ }, nil
+}
+
+// OptNTPServer is an option NTP server as defined by RFC 5908.
+type OptNTPServer struct {
+ Suboptions Options
+}
+
+// Code returns the option code
+func (op *OptNTPServer) Code() OptionCode {
+ return OptionNTPServer
+}
+
+// ToBytes returns the option serialized to bytes.
+func (op *OptNTPServer) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ for _, so := range op.Suboptions {
+ buf.WriteBytes(so.ToBytes())
+ }
+ return buf.Data()
+}
+
+func (op *OptNTPServer) String() string {
+ return fmt.Sprintf("NTP: %v", op.Suboptions)
+}
diff --git a/dhcpv6/option_ntp_server_test.go b/dhcpv6/option_ntp_server_test.go
new file mode 100644
index 0000000..6cc5dd3
--- /dev/null
+++ b/dhcpv6/option_ntp_server_test.go
@@ -0,0 +1,82 @@
+package dhcpv6
+
+import (
+ "net"
+ "testing"
+
+ "github.com/insomniacslk/dhcp/rfc1035label"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSuboptionSrvAddr(t *testing.T) {
+ ip := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35")
+ so := NTPSuboptionSrvAddr(ip)
+ assert.Equal(t, NTPSuboptionSrvAddrCode, so.Code())
+ expected := append([]byte{0x00, 0x01, 0x00, 0x10}, ip...)
+ assert.Equal(t, expected, so.ToBytes())
+}
+
+func TestSuboptionMCAddr(t *testing.T) {
+ ip := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35")
+ so := NTPSuboptionMCAddr(ip)
+ assert.Equal(t, NTPSuboptionMCAddrCode, so.Code())
+ expected := append([]byte{0x00, 0x02, 0x00, 0x10}, ip...)
+ assert.Equal(t, expected, so.ToBytes())
+}
+
+func TestSuboptionSrvFQDN(t *testing.T) {
+ fqdn, err := rfc1035label.FromBytes([]byte("\x03ntp\x07example\x03com"))
+ require.NoError(t, err)
+ so := NTPSuboptionSrvFQDN(*fqdn)
+ assert.Equal(t, NTPSuboptionSrvFQDNCode, so.Code())
+ expected := append([]byte{0x00, 0x03, 0x00, 0x10}, fqdn.ToBytes()...)
+ assert.Equal(t, expected, so.ToBytes())
+}
+
+func TestSuboptionGeneric(t *testing.T) {
+ data := []byte{
+ 0xff, 0xff, // unknown sub-option type
+ 0x00, 0x04, // length, 4 bytes
+ 0x74, 0x65, 0x73, 0x74, // the ASCII bytes for the string "test"
+ }
+ o, err := ParseOptNTPServer(data)
+ require.NoError(t, err)
+ require.Equal(t, 1, len(o.Suboptions))
+ assert.IsType(t, &OptionGeneric{}, o.Suboptions[0])
+ og := o.Suboptions[0].(*OptionGeneric)
+ assert.Equal(t, []byte("test"), og.ToBytes())
+}
+
+func TestParseOptNTPServer(t *testing.T) {
+ ip := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35")
+ fqdn, err := rfc1035label.FromBytes([]byte("\x03ntp\x07example\x03com"))
+ require.NoError(t, err)
+
+ // add server address sub-option
+ data := []byte{
+ 0x00, 0x01, // sub-option type
+ 0x00, 0x10, // length (16, IPv6 address)
+ }
+ data = append(data, []byte(ip)...)
+
+ // add server FQDN sub-option
+ data = append(data, []byte{
+ 0x00, 0x03, // sub-option type
+ 0x00, 0x10, // length (16, the FQDN "ntp.example.com." as rfc1035 label)
+ }...)
+ data = append(data, fqdn.ToBytes()...)
+
+ o, err := ParseOptNTPServer(data)
+ require.NoError(t, err)
+ require.NotNil(t, o)
+ assert.Equal(t, 2, len(o.Suboptions))
+
+ optAddr, ok := o.Suboptions[0].(*NTPSuboptionSrvAddr)
+ require.True(t, ok)
+ assert.Equal(t, ip, net.IP(*optAddr))
+
+ optFQDN, ok := o.Suboptions[1].(*NTPSuboptionSrvFQDN)
+ require.True(t, ok)
+ assert.Equal(t, *fqdn, rfc1035label.Labels(*optFQDN))
+}
diff --git a/dhcpv6/options.go b/dhcpv6/options.go
index 16e2d8c..06a1028 100644
--- a/dhcpv6/options.go
+++ b/dhcpv6/options.go
@@ -81,6 +81,8 @@ func ParseOption(code OptionCode, optData []byte) (Option, error) {
opt, err = ParseOptRemoteID(optData)
case OptionFQDN:
opt, err = ParseOptFQDN(optData)
+ case OptionNTPServer:
+ opt, err = ParseOptNTPServer(optData)
case OptionBootfileURL:
opt, err = parseOptBootFileURL(optData)
case OptionBootfileParam: