github.com/haraldrudell/parl@v0.4.176/pnet/linkaddr.go (about)

     1  /*
     2  © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pnet
     7  
     8  import (
     9  	"errors"
    10  	"net"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/haraldrudell/parl"
    15  	"github.com/haraldrudell/parl/perrors"
    16  	"golang.org/x/exp/slices"
    17  )
    18  
    19  const (
    20  	HardwareAddrMac48  = 6
    21  	HardwareAddrEui64  = 8
    22  	HardwareAddrInfini = 20
    23  )
    24  
    25  // ErrNoSuchInterface is the value net.errNoSuchInterface
    26  //
    27  // Usage:
    28  //
    29  //	if errors.Is(err, pnet.ErrNoSuchInterface) { …
    30  var ErrNoSuchInterface = func() (err error) {
    31  	for _, e := net.InterfaceByName("a b"); e != nil; e = errors.Unwrap(e) {
    32  		err = e
    33  	}
    34  	if err == nil {
    35  		panic(perrors.NewPF("failed to obtain NoSuchInterface from InterfaceByName"))
    36  	}
    37  	return
    38  }()
    39  
    40  var HardwareAddrLengths = []int{HardwareAddrMac48, HardwareAddrEui64, HardwareAddrInfini}
    41  var HardwareAddrLengthsWithZero = append([]int{0}, HardwareAddrLengths...)
    42  
    43  func IsHardwareAddrLength(byts []byte) (isHardwareAddrLength bool) {
    44  	return slices.Contains(HardwareAddrLengths, len(byts))
    45  }
    46  
    47  // LinkAddr contains an Ethernet mac address, its interface name and interface index
    48  type LinkAddr struct {
    49  	IfIndex                 // 0 is none
    50  	Name             string // "" none
    51  	net.HardwareAddr        // []byte
    52  }
    53  
    54  // NewLinkAddr instantiates LinkAddr
    55  func NewLinkAddr(index IfIndex, name string) (linkAddr *LinkAddr) {
    56  	return &LinkAddr{
    57  		IfIndex: index,
    58  		Name:    name,
    59  	}
    60  }
    61  
    62  func (a *LinkAddr) UpdateFrom(b *LinkAddr) (isComplete bool) {
    63  	if !a.IfIndex.IsValid() && b.IfIndex.IsValid() {
    64  		a.IfIndex = b.IfIndex
    65  	}
    66  	if a.Name == "" && b.Name != "" {
    67  		a.Name = b.Name
    68  	}
    69  	if len(a.HardwareAddr) == 0 && len(b.HardwareAddr) > 0 {
    70  		a.HardwareAddr = b.HardwareAddr
    71  	}
    72  	return a.IsComplete()
    73  }
    74  
    75  func (a *LinkAddr) SetHw(hw net.HardwareAddr) (err error) {
    76  	if !slices.Contains(HardwareAddrLengthsWithZero, len(hw)) {
    77  		err = perrors.ErrorfPF("hardware address bad length: %d allowed: [%v]", hw)
    78  		return
    79  	} else if len(hw) > 0 {
    80  		a.HardwareAddr = hw
    81  	}
    82  	return
    83  }
    84  
    85  func (a *LinkAddr) SetName(name string) {
    86  	a.Name = name
    87  }
    88  
    89  // UpdateName attempts to populate interface name if not already present
    90  func (a *LinkAddr) UpdateName() (linkAddr *LinkAddr, err error) {
    91  	linkAddr = a
    92  	if a.Name != "" {
    93  		return // name already present return
    94  	}
    95  	var name string
    96  	if name, _, err = a.IfIndex.Zone(); err != nil {
    97  		return // error while getting interface data return
    98  	}
    99  	if name == "" {
   100  		return // no new name obtained return
   101  	}
   102  	linkAddr = &LinkAddr{
   103  		IfIndex:      a.IfIndex,
   104  		Name:         name,
   105  		HardwareAddr: a.HardwareAddr,
   106  	}
   107  	return // name updated return
   108  }
   109  
   110  // Interface returns net.Interface associated with LinkAddr
   111  //   - order is index, name, mac
   112  //   - if LinkAddr is zero-value, nil is returned
   113  func (a *LinkAddr) Interface() (netInterface *net.Interface, isNoSuchInterface bool, err error) {
   114  	if a.IfIndex.IsValid() {
   115  		return a.IfIndex.Interface()
   116  	} else if a.Name != "" {
   117  		if netInterface, err = net.InterfaceByName(a.Name); err != nil {
   118  			isNoSuchInterface = errors.Is(err, ErrNoSuchInterface)
   119  			err = perrors.Errorf("net.InterfaceByName %w", err)
   120  		}
   121  		return
   122  	} else if len(a.HardwareAddr) > 0 {
   123  		return HardwareAddrInterface(a.HardwareAddr)
   124  	}
   125  	return // zero-value: netInterface nil return
   126  }
   127  
   128  // ZoneID is the IPv6 ZoneID for this interface
   129  func (a *LinkAddr) ZoneID() string {
   130  	if a != nil {
   131  		if a.Name != "" {
   132  			return a.Name
   133  		} else if a.IfIndex > 0 {
   134  			return strconv.Itoa(int(a.IfIndex))
   135  		}
   136  	}
   137  	return "0"
   138  }
   139  
   140  // OneString picks the most meaningful value
   141  //   - interface name or hardware address or #interface index or "0"
   142  func (a *LinkAddr) OneString() string {
   143  	if a != nil {
   144  		if a.Name != "" {
   145  			return a.Name
   146  		} else if len(a.HardwareAddr) > 0 {
   147  			return a.HardwareAddr.String()
   148  		} else if a.IfIndex > 0 {
   149  			return "#" + strconv.Itoa(int(a.IfIndex))
   150  		}
   151  	}
   152  	return "0"
   153  }
   154  
   155  func (a *LinkAddr) IsValid() (isValid bool) {
   156  	return a.IfIndex != 0 ||
   157  		a.Name != "" ||
   158  		len(a.HardwareAddr) > 0
   159  }
   160  
   161  func (a *LinkAddr) IsZeroValue() (isZeroValue bool) {
   162  	return !a.IfIndex.IsValid() &&
   163  		a.Name == "" &&
   164  		a.HardwareAddr == nil
   165  }
   166  
   167  func (a *LinkAddr) IsComplete() (isComplete bool) {
   168  	return a.IfIndex.IsValid() &&
   169  		a.Name != "" &&
   170  		len(a.HardwareAddr) > 0
   171  }
   172  
   173  // "#13_en5_00:00:5e:00:53:01"
   174  //   - zero-values are skipped
   175  //   - zero-value: "zero-value"
   176  func (a *LinkAddr) NonZero() (s string) {
   177  	var sL []string
   178  	if a.IfIndex != 0 {
   179  		sL = append(sL, a.IfIndex.String()) // "#13"
   180  	}
   181  	if a.Name != "" {
   182  		sL = append(sL, a.Name)
   183  	}
   184  	if len(a.HardwareAddr) > 0 {
   185  		sL = append(sL, a.HardwareAddr.String()) // "00:00:5e:00:53:01"
   186  	}
   187  	if len(sL) > 0 {
   188  		s = strings.Join(sL, "_")
   189  	} else {
   190  		s = "zero-value"
   191  	}
   192  	return
   193  }
   194  
   195  func (a *LinkAddr) Dump() (s string) {
   196  	return parl.Sprintf("linkAddr#%d%q_hw%s",
   197  		a.IfIndex,
   198  		a.Name,
   199  		a.HardwareAddr.String(),
   200  	)
   201  }
   202  
   203  // "en8(28)00:11:22:33:44:55:66"
   204  func (a *LinkAddr) String() (s string) {
   205  	if len(a.Name) > 0 {
   206  		s += a.Name
   207  	}
   208  	if a.IfIndex > 0 {
   209  		s += "(" + strconv.Itoa(int(a.IfIndex)) + ")"
   210  	}
   211  	s += a.HardwareAddr.String()
   212  	return
   213  }