master
go 144 lines 3.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 _ "embed"
7 "sort"
8 "strings"
9 "sync"
10 )
11
12 //go:embed mac_oui_vendors.tsv
13 var macOUIVendorsTSV string
14
15 type topologyOUIVendorIndex struct {
16 byPrefixLen map[int]map[string]string
17 prefixLens []int
18 }
19
20 var (
21 topologyOUIVendorsOnce sync.Once
22 topologyOUIVendorsIndex topologyOUIVendorIndex
23 )
24
25 func loadTopologyOUIVendorsIndex() topologyOUIVendorIndex {
26 topologyOUIVendorsOnce.Do(func() {
27 topologyOUIVendorsIndex = buildTopologyOUIVendorIndex(macOUIVendorsTSV)
28 })
29 return topologyOUIVendorsIndex
30 }
31
32 func buildTopologyOUIVendorIndex(tsv string) topologyOUIVendorIndex {
33 byPrefixLen := make(map[int]map[string]string)
34 for line := range strings.SplitSeq(tsv, "\n") {
35 line = strings.TrimSpace(line)
36 if line == "" || strings.HasPrefix(line, "#") {
37 continue
38 }
39 prefix, vendor, ok := strings.Cut(line, "\t")
40 if !ok {
41 continue
42 }
43 prefix = strings.ToUpper(strings.TrimSpace(prefix))
44 vendor = strings.TrimSpace(vendor)
45 if prefix == "" || vendor == "" {
46 continue
47 }
48 if len(prefix) < 6 || len(prefix) > 12 {
49 continue
50 }
51 if !isHexToken(prefix) {
52 continue
53 }
54 if byPrefixLen[len(prefix)] == nil {
55 byPrefixLen[len(prefix)] = make(map[string]string)
56 }
57 if _, exists := byPrefixLen[len(prefix)][prefix]; exists {
58 continue
59 }
60 byPrefixLen[len(prefix)][prefix] = vendor
61 }
62
63 prefixLens := make([]int, 0, len(byPrefixLen))
64 for prefixLen := range byPrefixLen {
65 prefixLens = append(prefixLens, prefixLen)
66 }
67 sort.Slice(prefixLens, func(i, j int) bool {
68 return prefixLens[i] > prefixLens[j]
69 })
70 return topologyOUIVendorIndex{
71 byPrefixLen: byPrefixLen,
72 prefixLens: prefixLens,
73 }
74 }
75
76 func isHexToken(value string) bool {
77 if value == "" {
78 return false
79 }
80 for _, r := range value {
81 if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') || (r >= 'a' && r <= 'f') {
82 continue
83 }
84 return false
85 }
86 return true
87 }
88
89 func lookupTopologyVendorByMAC(mac string) (vendor string, prefix string) {
90 return lookupTopologyVendorByMACInIndex(loadTopologyOUIVendorsIndex(), mac)
91 }
92
93 func lookupTopologyVendorByMACInIndex(index topologyOUIVendorIndex, mac string) (vendor string, prefix string) {
94 mac = normalizeMAC(mac)
95 if mac == "" {
96 return "", ""
97 }
98 hex := strings.ToUpper(strings.ReplaceAll(mac, ":", ""))
99 if hex == "" {
100 return "", ""
101 }
102
103 for _, prefixLen := range index.prefixLens {
104 if len(hex) < prefixLen {
105 continue
106 }
107 candidatePrefix := hex[:prefixLen]
108 candidateVendor, ok := index.byPrefixLen[prefixLen][candidatePrefix]
109 if !ok {
110 continue
111 }
112 return candidateVendor, candidatePrefix
113 }
114 return "", ""
115 }
116
117 func inferTopologyVendorFromMatch(match Match) (vendor string, prefix string) {
118 candidates := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
119 for _, value := range match.MacAddresses {
120 if mac := normalizeMAC(value); mac != "" {
121 candidates[mac] = struct{}{}
122 }
123 }
124 for _, value := range match.ChassisIDs {
125 if mac := normalizeMAC(value); mac != "" {
126 candidates[mac] = struct{}{}
127 }
128 }
129 if len(candidates) == 0 {
130 return "", ""
131 }
132
133 macs := make([]string, 0, len(candidates))
134 for mac := range candidates {
135 macs = append(macs, mac)
136 }
137 sort.Strings(macs)
138 for _, mac := range macs {
139 if vendor, prefix := lookupTopologyVendorByMAC(mac); vendor != "" {
140 return vendor, prefix
141 }
142 }
143 return "", ""
144 }