1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
package ztpv4
import (
"bytes"
"errors"
"strings"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/iana"
)
// VendorData is optional data a particular vendor may or may not include
// in the Vendor Class options.
type VendorData struct {
VendorName, Model, Serial string
}
var errVendorOptionMalformed = errors.New("malformed vendor option")
func parseClassIdentifier(packet *dhcpv4.DHCPv4) (*VendorData, error) {
vd := &VendorData{}
switch vc := packet.ClassIdentifier(); {
// Arista;DCS-7050S-64;01.23;JPE12221671
case strings.HasPrefix(vc, "Arista;"):
p := strings.Split(vc, ";")
if len(p) < 4 {
return nil, errVendorOptionMalformed
}
vd.VendorName = p[0]
vd.Model = p[1]
vd.Serial = p[3]
return vd, nil
// ZPESystems:NSC:002251623
case strings.HasPrefix(vc, "ZPESystems:"):
p := strings.Split(vc, ":")
if len(p) < 3 {
return nil, errVendorOptionMalformed
}
vd.VendorName = p[0]
vd.Model = p[1]
vd.Serial = p[2]
return vd, nil
// Juniper option 60 parsing is a bit more nuanced. The following are all
// "valid" identifying stings for Juniper:
// Juniper-ptx1000-DD576 <vendor>-<model>-<serial
// Juniper-qfx10008 <vendor>-<model> (serial in hostname option)
// Juniper-qfx10002-361-DN817 <vendor>-<model>-<serial> (model has a dash in it!)
case strings.HasPrefix(vc, "Juniper-"):
p := strings.Split(vc, "-")
if len(p) < 3 {
vd.Model = p[1]
vd.Serial = packet.HostName()
if len(vd.Serial) == 0 {
return nil, errors.New("host name option is missing")
}
} else {
vd.Model = strings.Join(p[1:len(p)-1], "-")
vd.Serial = p[len(p)-1]
}
vd.VendorName = p[0]
return vd, nil
// Cisco Firepower FPR4100/9300 models use Opt 60 for model info
// and Opt 61 contains the serial number
case vc == "FPR4100" || vc == "FPR9300":
vd.VendorName = iana.EntIDCiscoSystems.String()
vd.Model = vc
vd.Serial = dhcpv4.GetString(dhcpv4.OptionClientIdentifier, packet.Options)
return vd, nil
}
return nil, nil
}
func parseVIVC(packet *dhcpv4.DHCPv4) (*VendorData, error) {
vd := &VendorData{}
for _, id := range packet.VIVC() {
if id.EntID == uint32(iana.EntIDCiscoSystems) {
vd.VendorName = iana.EntIDCiscoSystems.String()
//SN:0;PID:R-IOSXRV9000-CC
for _, f := range bytes.Split(id.Data, []byte(";")) {
p := bytes.Split(f, []byte(":"))
if len(p) != 2 {
return nil, errVendorOptionMalformed
}
switch string(p[0]) {
case "SN":
vd.Serial = string(p[1])
case "PID":
vd.Model = string(p[1])
}
}
return vd, nil
}
}
return nil, nil
}
// ParseVendorData will try to parse dhcp4 options looking for more
// specific vendor data (like model, serial number, etc).
func ParseVendorData(packet *dhcpv4.DHCPv4) (*VendorData, error) {
vd, err := parseClassIdentifier(packet)
if err != nil {
return nil, err
}
// If VendorData is set, return early
if vd != nil {
return vd, nil
}
vd, err = parseVIVC(packet)
if err != nil {
return nil, err
}
if vd != nil {
return vd, nil
}
return nil, errors.New("no known ZTP vendor found")
}
|