github.com/haraldrudell/parl@v0.4.176/pnet/nexthop.go (about) 1 /* 2 © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package pnet 7 8 import ( 9 "fmt" 10 "net" 11 "net/netip" 12 13 "github.com/haraldrudell/parl" 14 "github.com/haraldrudell/parl/perrors" 15 ) 16 17 const ( 18 UseInterfaceNameCache = true 19 ) 20 21 // NextHop describes a route target 22 type NextHop struct { 23 /* 24 if NextHop is an address on the local host or on a local subnet, 25 Gateway is nil 26 LinkAddr describes the local interface 27 Src is the address on that local interface 28 29 If Nexthop is remote, beyond any local subnet, 30 Gateway is an IP on a local subnet 31 LinkAddr describes the local interface for that subnet 32 Src is the address on that local interface 33 */ 34 Gateway netip.Addr 35 LinkAddr // LinkAddr is the hosts’s network interface where to send packets 36 Src netip.Addr // the source ip to use for originating packets on LinkAddr 37 nIPv6 int // number of assigned IPv6 addresses, 0 if unknown 38 nIPv4 int // number of assigned IPv6 addresses, 0 if unknown 39 } 40 41 // NewNextHop assembles a route destination 42 func NewNextHop(gateway netip.Addr, linkAddr *LinkAddr, src netip.Addr) (nextHop *NextHop) { 43 next0 := NextHop{} 44 if gateway.IsValid() { 45 next0.Gateway = gateway 46 } 47 if linkAddr != nil { 48 next0.LinkAddr = *linkAddr 49 } 50 if src.IsValid() { 51 next0.Src = src 52 } 53 nextHop = &next0 54 return 55 } 56 57 // NewNextHopCounts returns NextHop with current IP address counts 58 // - if input LinkAddr does not have interface name, interface name is added to output nextHop 59 // - 6in4 are converted to IPv4 60 func NewNextHopCounts(gateway netip.Addr, linkAddr *LinkAddr, src netip.Addr, 61 useNameCache ...NameCacher, 62 ) (nextHop *NextHop, err error) { 63 var doCache = NoCache 64 if len(useNameCache) > 0 { 65 doCache = useNameCache[0] 66 } 67 68 // tentative NextHop value based on input arguments 69 var nh = NewNextHop(gateway, linkAddr, src) 70 71 // interface from LinkAddr is required to get IP addresses assigned to the network interface 72 var netInterface *net.Interface 73 var interfaceName string 74 var unknownInterface bool 75 // obtain interface using index, name or mac 76 if netInterface, unknownInterface, err = nh.LinkAddr.Interface(); err != nil { 77 // for netlink packets of deleted interface, the index is already invalid 78 // - use the cache to obtain the interface name 79 if unknownInterface && doCache != NoCache && linkAddr.IfIndex.IsValid() { 80 if interfaceName, err = networkInterfaceNameCache.CachedName(linkAddr.IfIndex, doCache); err == nil { 81 if nh.LinkAddr.Name == "" && interfaceName != "" { 82 nh.LinkAddr.Name = interfaceName // update interface name if not already set 83 } 84 } 85 } 86 if err != nil { 87 return // error in LinkAddr.Interface or CachedName 88 } 89 } 90 if netInterface == nil { 91 nextHop = nh 92 return // Linkaddr interface did not exist 93 } 94 95 // update nh.Linkaddr from netInterface 96 if !nh.LinkAddr.IfIndex.IsValid() { 97 if nh.LinkAddr.IfIndex, err = NewIfIndexInt(netInterface.Index); err != nil { 98 return 99 } 100 } 101 if nh.LinkAddr.Name == "" { 102 nh.LinkAddr.Name = netInterface.Name // update interface name if not already set 103 } 104 if len(nh.LinkAddr.HardwareAddr) == 0 { 105 nh.LinkAddr.HardwareAddr = netInterface.HardwareAddr 106 } 107 108 // get IP address counts from interface 109 var i4, i6 []netip.Prefix 110 if i4, i6, err = InterfaceAddrs(netInterface); err != nil { 111 return 112 } 113 nh.nIPv4 = len(i4) 114 nh.nIPv6 = len(i6) 115 // macOS lo0 has address: 116 // for i := 0; i < len(i6); { 117 // addr := i6[i].Addr() 118 // if addr.Is4In6() { 119 120 // i4 = append(i4, netip.PrefixFrom(addr.As4(), ) 121 // i6 = slices.Delete[](i6, i, i+1) 122 // continue 123 // } 124 // i++ 125 // } 126 nextHop = nh 127 return 128 } 129 130 // NewNextHop2 assembles a route destination based on IfIndex 131 func NewNextHop2(index IfIndex, gateway netip.Addr, src netip.Addr) (next *NextHop, err error) { 132 var linkAddr *LinkAddr 133 if index.IsValid() { 134 linkAddr = NewLinkAddr(index, "") 135 if linkAddr, err = linkAddr.UpdateName(); err != nil { 136 return 137 } 138 } 139 return NewNextHop(gateway, linkAddr, src), err 140 } 141 142 func NewNextHop3(gateway netip.Addr, linkAddr *LinkAddr, src netip.Addr, nIPv4, nIPv6 int) (nextHop *NextHop) { 143 return &NextHop{Gateway: gateway, 144 LinkAddr: *linkAddr, 145 Src: src, 146 nIPv4: nIPv4, 147 nIPv6: nIPv6, 148 } 149 } 150 151 // HasGateway determines if next hop uses a remote gateway 152 func (n *NextHop) HasGateway() bool { 153 return n.Gateway.IsValid() && !n.Gateway.IsUnspecified() 154 } 155 156 // HasSrc determines if next hop has src specified 157 func (n *NextHop) HasSrc() bool { 158 return n.Src.IsValid() && !n.Src.IsUnspecified() 159 } 160 161 func (n *NextHop) IsZeroValue() (isZeroValue bool) { 162 return !n.Gateway.IsValid() && 163 n.LinkAddr.IsZeroValue() && 164 !n.Src.IsValid() && 165 n.nIPv4 == 0 && 166 n.nIPv6 == 0 167 } 168 169 // Name returns nextHop interface name 170 // - name can be returned empty 171 // - name of Linkaddr, then interface from index, name, mac 172 func (n *NextHop) Name(useNameCache ...NameCacher) (name string, err error) { 173 var doCache = NoCache 174 if len(useNameCache) > 0 { 175 doCache = useNameCache[0] 176 } 177 178 // is name already present? 179 if name = n.LinkAddr.Name; name != "" { 180 return // nexthop had interface name available return 181 } 182 183 // interface from LinkAddr 184 var netInterface *net.Interface 185 var noSuchInterface bool 186 if netInterface, noSuchInterface, err = n.LinkAddr.Interface(); err != nil { 187 if noSuchInterface { 188 err = nil 189 } else { 190 return // interface retrieval error return 191 } 192 } 193 if netInterface != nil { 194 name = netInterface.Name 195 return // name from interface return 196 } 197 198 // interface from IP address 199 var a = n.Gateway 200 if !a.IsValid() { 201 a = n.Src 202 } 203 if !a.IsValid() { 204 return // no IP available return 205 } 206 if netInterface, _, _, err = InterfaceFromAddr(a); err != nil { 207 return 208 } else if netInterface != nil { 209 name = netInterface.Name 210 return 211 } 212 213 zone, znum, hasZone, isNumeric := Zone(a) 214 if hasZone && !isNumeric { 215 name = zone 216 return // interface name from zone 217 } 218 219 // should cache be used? 220 if doCache == NoCache { 221 return 222 } 223 224 // get indexes that can be used with cache 225 var ixs []IfIndex 226 if n.LinkAddr.IfIndex.IsValid() { 227 ixs = append(ixs, n.LinkAddr.IfIndex) 228 } 229 if isNumeric { 230 var ifIndex IfIndex 231 if ifIndex, err = NewIfIndexInt(znum); err != nil { 232 return 233 } 234 if ifIndex.IsValid() && ifIndex != n.LinkAddr.IfIndex { 235 ixs = append(ixs, ifIndex) 236 } 237 } 238 239 // search cache 240 for _, ifi := range ixs { 241 if name, err = networkInterfaceNameCache.CachedName(ifi, doCache); err != nil { 242 return 243 } else if name != "" { 244 return 245 } 246 } 247 return 248 } 249 250 // Target describes the destination for this next hop 251 // - gateway is invalid for local network targets or gateway unspecified address "0.0.0.0" 252 // - s is: 253 // - empty string for nil NextHop 254 // - network interface name mac index or 0 for gateway missing, invalid or unspecified 255 // - otherwise gateway ip, interface description and source IP or source cidr 256 func (n *NextHop) Target() (gateway netip.Addr, s string) { 257 258 // ensure NextHop present 259 if n == nil { 260 return // no NextHop available: invalid and empty string 261 } 262 263 // network interface description: 264 // “en5” or “aa:bb…” or “#3” or “0” but never empty string 265 s = n.LinkAddr.OneString() 266 if !n.HasGateway() { 267 return // target is on local network, only the network interface describes it, gateway invalid 268 } 269 // gateway is valid gateway IP from NextHop field 270 gateway = n.Gateway 271 // is4 indicates that an IPv4 address is sought 272 var is4 = Addr46(gateway).Is4() 273 274 // try to obtain source IP and prefix 275 276 // srcIP is possible source IP specified in NextHop 277 var srcIP netip.Addr 278 // hasSrcIP indicates that srcIP is usable 279 var hasSrcIP = n.Src.IsValid() && !n.Src.IsUnspecified() 280 if hasSrcIP { 281 srcIP = Addr46(n.Src) 282 } 283 var cidr netip.Prefix 284 var e error 285 cidr, e = n.ifCidr(srcIP, is4) 286 _ = e 287 288 // sourceIPString is source IP “1.2.3.4” or cidr “1.2.3.4/24” 289 var sourceIPString string 290 if cidr.IsValid() { 291 sourceIPString = "\x20" + cidr.String() 292 } else if hasSrcIP { 293 sourceIPString = "\x20" + srcIP.String() 294 } 295 296 // “192.168.1.12 en5 192.168.1.21/24” 297 s = fmt.Sprintf("%s %s%s", gateway, s, sourceIPString) 298 299 return 300 } 301 302 // targets tries to obtain cidr from network interface IP assignment 303 func (n *NextHop) ifCidr(srcIP netip.Addr, is4 bool) (cidr netip.Prefix, err error) { 304 305 // network interface index 306 var index = n.LinkAddr.IfIndex 307 if index == 0 { 308 return // failed to obtain network interface index 309 } 310 var iface *net.Interface 311 if iface, err = net.InterfaceByIndex(int(index)); perrors.IsPF(&err, "InterfaceByIndex %w", err) { 312 return // failed to obtain interface 313 } 314 // addrs is a list of IP addresses assigned to the network interface 315 var addrs []net.Addr 316 if addrs, err = iface.Addrs(); perrors.IsPF(&err, "iface.Addrs %w", err) { 317 return // failed to obtain interface address 318 } 319 if len(addrs) == 0 { 320 return // failed to obtain any assigned addresses 321 } 322 // prefixes are list of cidrs assigned top network interface 323 var prefixes []netip.Prefix 324 if prefixes, err = AddrSlicetoPrefix(addrs, Do46); err != nil { 325 return // addr slice failed to convert 326 } 327 if !srcIP.IsValid() { 328 // if no srcIP, pick the first address of the correct family 329 for _, prefix := range prefixes { 330 var addr = Addr46(prefix.Addr()) 331 if addr.Is4() == is4 { 332 cidr = prefix 333 return // found a cidr 334 } 335 } 336 var family string 337 if is4 { 338 family = "IPv4" 339 } else { 340 family = "IPv6" 341 } 342 err = perrors.ErrorfPF("interface %s has no %d addresses", iface.Name, family) 343 return 344 } 345 // find the prefix that contains scrIP, it should exist 346 for _, prefix := range prefixes { 347 if prefix.Contains(srcIP) { 348 cidr = prefix 349 return 350 } 351 } 352 err = perrors.ErrorfPF("interface %s is not assign a cidr for %s", iface.Name, srcIP) 353 return 354 } 355 356 func (n *NextHop) Dump() (s string) { 357 return parl.Sprintf("nextHop_gwIP_%s_%s_src_%s_4:%d_6:%d", 358 n.Gateway.String(), 359 n.LinkAddr.Dump(), 360 n.Src, 361 n.nIPv4, n.nIPv6, 362 ) 363 } 364 365 func (n *NextHop) String() (s string) { 366 367 // addr and hasNameZone 368 var hasNameZone bool 369 if n.HasGateway() { 370 s = n.Gateway.String() 371 gatewayAddr := n.Gateway 372 _, _, hasZone, isNumeric := Zone(gatewayAddr) 373 hasNameZone = hasZone && !isNumeric 374 } 375 376 // interface name 377 if !hasNameZone && !n.LinkAddr.IsZeroValue() { 378 if s != "" { 379 s += "\x20" 380 } 381 s += n.LinkAddr.OneString() // name or mac or if-index 382 } 383 384 // src 1.2.3.4 385 if n.Src.IsValid() && 386 ((n.Src.Is4() && n.nIPv4 > 1) || 387 (n.Src.Is6() && n.nIPv6 > 1)) { 388 s += " src " + n.Src.String() 389 } 390 391 return 392 }