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  }