diff options
-rw-r--r-- | dhcpv6/ztpv6/mellanox.go | 37 | ||||
-rw-r--r-- | dhcpv6/ztpv6/mellanox_test.go | 66 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_vendor_options.go | 12 | ||||
-rw-r--r-- | iana/entid.go | 10 |
4 files changed, 119 insertions, 6 deletions
diff --git a/dhcpv6/ztpv6/mellanox.go b/dhcpv6/ztpv6/mellanox.go new file mode 100644 index 0000000..3ed8b43 --- /dev/null +++ b/dhcpv6/ztpv6/mellanox.go @@ -0,0 +1,37 @@ +package ztpv6 + +import ( + "errors" + + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/iana" +) + +type MlnxSubOption uint16 + +const ( + MlnxSubOptionModel MlnxSubOption = 1 + MlnxSubOptionPartNum MlnxSubOption = 2 + MlnxSubOptionSerial MlnxSubOption = 3 + MlnxSubOptionMac MlnxSubOption = 4 + MlnxSubOptionProfile MlnxSubOption = 5 + MlnxSubOptionRelease MlnxSubOption = 6 +) + +func getMellanoxVendorData(vendorOptsOption *dhcpv6.OptVendorOpts) (*VendorData, error) { + vd := VendorData{} + vd.VendorName = iana.EnterpriseIDMellanoxTechnologiesLTD.String() + for _, opt := range vendorOptsOption.VendorOpts { + switch MlnxSubOption(opt.Code()) { + case MlnxSubOptionSerial: + vd.Serial = string(opt.ToBytes()) + case MlnxSubOptionModel: + vd.Model = string(opt.ToBytes()) + } + } + if (vd.Serial == "") || (vd.Model == "") { + return nil, errors.New("couldn't parse Mellanox sub-option for serial or model") + } + + return &vd, nil +} diff --git a/dhcpv6/ztpv6/mellanox_test.go b/dhcpv6/ztpv6/mellanox_test.go new file mode 100644 index 0000000..29293d1 --- /dev/null +++ b/dhcpv6/ztpv6/mellanox_test.go @@ -0,0 +1,66 @@ +package ztpv6 + +import ( + "testing" + + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/iana" + "github.com/stretchr/testify/require" +) + +func TestParseMellanoxVendorData(t *testing.T) { + tt := []struct { + name string + vendorOpts []dhcpv6.Option + want *VendorData + fail bool + }{ + {name: "empty", fail: true}, + { + name: "ok", + fail: false, + vendorOpts: []dhcpv6.Option{ + &dhcpv6.OptionGeneric{OptionData: []byte("SomeModel"), OptionCode: dhcpv6.OptionCode(MlnxSubOptionModel)}, + &dhcpv6.OptionGeneric{OptionData: []byte("SomeModel-1234"), OptionCode: dhcpv6.OptionCode(MlnxSubOptionPartNum)}, + &dhcpv6.OptionGeneric{OptionData: []byte("ABC1234"), OptionCode: dhcpv6.OptionCode(MlnxSubOptionSerial)}, + &dhcpv6.OptionGeneric{OptionData: []byte("1.2.3"), OptionCode: dhcpv6.OptionCode(MlnxSubOptionRelease)}, + }, + want: &VendorData{ + VendorName: iana.EnterpriseIDMellanoxTechnologiesLTD.String(), + Model: "SomeModel", + Serial: "ABC1234", + }, + }, + { + name: "no model", + fail: true, + vendorOpts: []dhcpv6.Option{ + &dhcpv6.OptionGeneric{OptionData: []byte("ABC1234"), OptionCode: dhcpv6.OptionCode(MlnxSubOptionSerial)}, + }, + want: nil, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + packet, err := dhcpv6.NewMessage() + if err != nil { + t.Fatalf("failed to creat dhcpv6 packet object: %v", err) + } + + packet.AddOption(&dhcpv6.OptVendorOpts{ + VendorOpts: tc.vendorOpts, EnterpriseNumber: uint32(iana.EnterpriseIDMellanoxTechnologiesLTD)}) + + vd, err := ParseVendorData(packet) + if err != nil && !tc.fail { + t.Errorf("unexpected failure: %v", err) + } + + if vd != nil { + require.Equal(t, *tc.want, *vd, "comparing vendor option data") + } else { + require.Equal(t, tc.want, vd, "comparing vendor option data") + } + }) + } +} diff --git a/dhcpv6/ztpv6/parse_vendor_options.go b/dhcpv6/ztpv6/parse_vendor_options.go index f1508fd..caa4e2f 100644 --- a/dhcpv6/ztpv6/parse_vendor_options.go +++ b/dhcpv6/ztpv6/parse_vendor_options.go @@ -33,8 +33,16 @@ func ParseVendorData(packet dhcpv6.DHCPv6) (*VendorData, error) { vData := []string{} if opt17 != nil { - vo := opt17.(*dhcpv6.OptVendorOpts).VendorOpts - for _, opt := range vo { + vendorOptsOption := opt17.(*dhcpv6.OptVendorOpts) + + // MLNX-OS has the relevant information spread over different sub-options + // of option 17 so the usual approach doesn't work + if vendorOptsOption.EnterpriseNumber == uint32(iana.EnterpriseIDMellanoxTechnologiesLTD) { + return getMellanoxVendorData(vendorOptsOption) + } + + // rest of vendors use a single sub-option so we stringify them and parse them below + for _, opt := range vendorOptsOption.VendorOpts { vData = append(vData, string(opt.(*dhcpv6.OptionGeneric).OptionData)) } } else { diff --git a/iana/entid.go b/iana/entid.go index 8703b79..6aa318c 100644 --- a/iana/entid.go +++ b/iana/entid.go @@ -5,13 +5,15 @@ type EnterpriseID int // See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers for values const ( - EnterpriseIDCiscoSystems EnterpriseID = 9 - EnterpriseIDCienaCorporation EnterpriseID = 1271 + EnterpriseIDCiscoSystems EnterpriseID = 9 + EnterpriseIDCienaCorporation EnterpriseID = 1271 + EnterpriseIDMellanoxTechnologiesLTD EnterpriseID = 33049 ) var enterpriseIDToStringMap = map[EnterpriseID]string{ - EnterpriseIDCiscoSystems: "Cisco Systems", - EnterpriseIDCienaCorporation: "Ciena Corporation", + EnterpriseIDCiscoSystems: "Cisco Systems", + EnterpriseIDCienaCorporation: "Ciena Corporation", + EnterpriseIDMellanoxTechnologiesLTD: "Mellanox Technologies LTD", } // String returns the vendor name for a given Enterprise ID |