master
go 188 lines 3.48 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "encoding/hex"
7 "fmt"
8 "net/netip"
9 "strconv"
10 "strings"
11 )
12
13 func canonicalBridgeAddr(value, fallback string) string {
14 if mac := normalizeMAC(value); mac != "" && mac != "00:00:00:00:00:00" {
15 return mac
16 }
17 if mac := normalizeMAC(fallback); mac != "" && mac != "00:00:00:00:00:00" {
18 return mac
19 }
20 return ""
21 }
22
23 func primaryL2MACIdentity(chassisID, baseBridgeAddress string) string {
24 for _, candidate := range []string{chassisID, baseBridgeAddress} {
25 if mac := normalizeMAC(candidate); mac != "" && mac != "00:00:00:00:00:00" {
26 return mac
27 }
28 }
29 return ""
30 }
31
32 func canonicalIP(v string) string {
33 if ip := parseAddr(v); ip.IsValid() {
34 return ip.String()
35 }
36 if ip := parseAddr(decodeHexIP(v)); ip.IsValid() {
37 return ip.String()
38 }
39 return ""
40 }
41
42 func parseAddr(v string) netip.Addr {
43 addr, err := netip.ParseAddr(strings.TrimSpace(v))
44 if err != nil {
45 return netip.Addr{}
46 }
47 return addr.Unmap()
48 }
49
50 func decodeHexIP(v string) string {
51 bs := decodeHexBytes(v)
52 if len(bs) == 4 {
53 addr, ok := netip.AddrFromSlice(bs)
54 if ok {
55 return addr.Unmap().String()
56 }
57 }
58 if len(bs) == 16 {
59 addr, ok := netip.AddrFromSlice(bs)
60 if ok {
61 return addr.String()
62 }
63 }
64 return ""
65 }
66
67 func decodeHexBytes(v string) []byte {
68 clean := strings.ToLower(strings.TrimSpace(v))
69 clean = strings.TrimPrefix(clean, "0x")
70 if clean == "" {
71 return nil
72 }
73
74 if strings.ContainsAny(clean, ":-. \t") {
75 parts := strings.FieldsFunc(clean, func(r rune) bool {
76 return r == ':' || r == '-' || r == '.' || r == ' ' || r == '\t'
77 })
78 if len(parts) == 0 {
79 return nil
80 }
81 if bs := decodeGroupedHexParts(parts); len(bs) != 0 {
82 return bs
83 }
84
85 out := make([]byte, 0, len(parts))
86 for _, part := range parts {
87 part = strings.TrimSpace(part)
88 if part == "" {
89 continue
90 }
91 if len(part) > 2 {
92 return nil
93 }
94 if len(part) == 1 {
95 part = "0" + part
96 }
97 b, err := hex.DecodeString(part)
98 if err != nil || len(b) != 1 {
99 return nil
100 }
101 out = append(out, b[0])
102 }
103 if len(out) == 0 {
104 return nil
105 }
106 return out
107 }
108
109 if len(clean)%2 == 1 {
110 clean = "0" + clean
111 }
112 bs, err := hex.DecodeString(clean)
113 if err != nil {
114 return nil
115 }
116 return bs
117 }
118
119 func decodeGroupedHexParts(parts []string) []byte {
120 var joined strings.Builder
121 anyWidePart := false
122
123 for _, part := range parts {
124 part = strings.TrimSpace(part)
125 if part == "" {
126 continue
127 }
128 if len(part)%2 != 0 {
129 return nil
130 }
131 if len(part) > 2 {
132 anyWidePart = true
133 }
134 joined.WriteString(part)
135 }
136
137 if !anyWidePart || joined.Len() == 0 {
138 return nil
139 }
140
141 bs, err := hex.DecodeString(joined.String())
142 if err != nil || len(bs) == 0 {
143 return nil
144 }
145 return bs
146 }
147
148 func normalizeMAC(v string) string {
149 v = strings.TrimSpace(v)
150 if v == "" {
151 return ""
152 }
153
154 if bs := parseDottedDecimalBytes(v); len(bs) == 6 {
155 return formatMAC(bs)
156 }
157 if bs := decodeHexBytes(v); len(bs) == 6 {
158 return formatMAC(bs)
159 }
160 return ""
161 }
162
163 func parseDottedDecimalBytes(v string) []byte {
164 parts := strings.Split(strings.TrimSpace(v), ".")
165 if len(parts) != 6 {
166 return nil
167 }
168 out := make([]byte, 0, 6)
169 for _, part := range parts {
170 n, err := strconv.Atoi(strings.TrimSpace(part))
171 if err != nil || n < 0 || n > 255 {
172 return nil
173 }
174 out = append(out, byte(n))
175 }
176 return out
177 }
178
179 func formatMAC(bs []byte) string {
180 if len(bs) != 6 {
181 return ""
182 }
183 parts := make([]string, 0, 6)
184 for _, b := range bs {
185 parts = append(parts, fmt.Sprintf("%02x", b))
186 }
187 return strings.Join(parts, ":")
188 }