diff options
author | Andrea Barberio <insomniac@slackware.it> | 2021-08-09 17:48:57 +0200 |
---|---|---|
committer | insomniac <insomniacslk@users.noreply.github.com> | 2021-08-13 12:35:03 +0200 |
commit | c143d771146ef619fc01b272a1ce49b35c3d9c7c (patch) | |
tree | dad5ebb8aa8c8dfc55140c7d18bba1d80d2382b2 | |
parent | decc701b36654be7f26b9c91537d509b8db85425 (diff) |
Added OptNTPServer
Signed-off-by: Andrea Barberio <insomniac@slackware.it>
-rw-r--r-- | dhcpv6/dhcpv6message.go | 26 | ||||
-rw-r--r-- | dhcpv6/option_ntp_server.go | 155 | ||||
-rw-r--r-- | dhcpv6/option_ntp_server_test.go | 82 | ||||
-rw-r--r-- | dhcpv6/options.go | 2 |
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: |