package rtnetlink import ( "errors" "fmt" "net" "github.com/jsimonetti/rtnetlink/internal/unix" "github.com/mdlayher/netlink" ) var ( // errInvalidLinkMessage is returned when a LinkMessage is malformed. errInvalidLinkMessage = errors.New("rtnetlink LinkMessage is invalid or too short") // errInvalidLinkMessageAttr is returned when link attributes are malformed. errInvalidLinkMessageAttr = errors.New("rtnetlink LinkMessage has a wrong attribute data length") ) var _ Message = &LinkMessage{} // A LinkMessage is a route netlink link message. type LinkMessage struct { // Always set to AF_UNSPEC (0) Family uint16 // Device Type Type uint16 // Unique interface index, using a nonzero value with // NewLink will instruct the kernel to create a // device with the given index (kernel 3.7+ required) Index uint32 // Contains device flags, see netdevice(7) Flags uint32 // Change Flags, specifies which flags will be affected by the Flags field Change uint32 // Attributes List Attributes *LinkAttributes } // MarshalBinary marshals a LinkMessage into a byte slice. func (m *LinkMessage) MarshalBinary() ([]byte, error) { b := make([]byte, unix.SizeofIfInfomsg) b[0] = 0 // Family b[1] = 0 // reserved nativeEndian.PutUint16(b[2:4], m.Type) nativeEndian.PutUint32(b[4:8], m.Index) nativeEndian.PutUint32(b[8:12], m.Flags) nativeEndian.PutUint32(b[12:16], m.Change) if m.Attributes != nil { ae := netlink.NewAttributeEncoder() ae.ByteOrder = nativeEndian err := m.Attributes.encode(ae) if err != nil { return nil, err } a, err := ae.Encode() if err != nil { return nil, err } return append(b, a...), nil } return b, nil } // UnmarshalBinary unmarshals the contents of a byte slice into a LinkMessage. func (m *LinkMessage) UnmarshalBinary(b []byte) error { l := len(b) if l < unix.SizeofIfInfomsg { return errInvalidLinkMessage } m.Family = nativeEndian.Uint16(b[0:2]) m.Type = nativeEndian.Uint16(b[2:4]) m.Index = nativeEndian.Uint32(b[4:8]) m.Flags = nativeEndian.Uint32(b[8:12]) m.Change = nativeEndian.Uint32(b[12:16]) if l > unix.SizeofIfInfomsg { m.Attributes = &LinkAttributes{} ad, err := netlink.NewAttributeDecoder(b[16:]) if err != nil { return err } ad.ByteOrder = nativeEndian err = m.Attributes.decode(ad) if err != nil { return err } } return nil } // rtMessage is an empty method to sattisfy the Message interface. func (*LinkMessage) rtMessage() {} // LinkService is used to retrieve rtnetlink family information. type LinkService struct { c *Conn } // execute executes the request and returns the messages as a LinkMessage slice func (l *LinkService) execute(m Message, family uint16, flags netlink.HeaderFlags) ([]LinkMessage, error) { msgs, err := l.c.Execute(m, family, flags) links := make([]LinkMessage, len(msgs)) for i := range msgs { links[i] = *msgs[i].(*LinkMessage) } return links, err } // New creates a new interface using the LinkMessage information. func (l *LinkService) New(req *LinkMessage) error { flags := netlink.Request | netlink.Create | netlink.Acknowledge | netlink.Excl _, err := l.execute(req, unix.RTM_NEWLINK, flags) return err } // Delete removes an interface by index. func (l *LinkService) Delete(index uint32) error { req := &LinkMessage{ Index: index, } flags := netlink.Request | netlink.Acknowledge _, err := l.c.Execute(req, unix.RTM_DELLINK, flags) return err } // Get retrieves interface information by index. func (l *LinkService) Get(index uint32) (LinkMessage, error) { req := &LinkMessage{ Index: index, } flags := netlink.Request | netlink.DumpFiltered links, err := l.execute(req, unix.RTM_GETLINK, flags) if len(links) != 1 { return LinkMessage{}, fmt.Errorf("too many/little matches, expected 1, actual %d", len(links)) } return links[0], err } // Set sets interface attributes according to the LinkMessage information. // // ref: https://lwn.net/Articles/236919/ // We explicitly use RTM_NEWLINK to set link attributes instead of // RTM_SETLINK because: // - using RTM_SETLINK is actually an old rtnetlink API, not supporting most // attributes common today // - using RTM_NEWLINK is the prefered way to create AND update links // - RTM_NEWLINK is backward compatible to RTM_SETLINK func (l *LinkService) Set(req *LinkMessage) error { flags := netlink.Request | netlink.Acknowledge _, err := l.c.Execute(req, unix.RTM_NEWLINK, flags) return err } func (l *LinkService) list(kind string) ([]LinkMessage, error) { req := &LinkMessage{} if kind != "" { req.Attributes = &LinkAttributes{ Info: &LinkInfo{Kind: kind}, } } flags := netlink.Request | netlink.Dump return l.execute(req, unix.RTM_GETLINK, flags) } // ListByKind retrieves all interfaces of a specific kind. func (l *LinkService) ListByKind(kind string) ([]LinkMessage, error) { return l.list(kind) } // List retrieves all interfaces. func (l *LinkService) List() ([]LinkMessage, error) { return l.list("") } // LinkAttributes contains all attributes for an interface. type LinkAttributes struct { Address net.HardwareAddr // Interface L2 address Alias *string // Interface alias name Broadcast net.HardwareAddr // L2 broadcast address Carrier *uint8 // Current physical link state of the interface. CarrierChanges *uint32 // Number of times the link has seen a change from UP to DOWN and vice versa CarrierUpCount *uint32 // Number of times the link has been up CarrierDownCount *uint32 // Number of times the link has been down Index *uint32 // System-wide interface unique index identifier Info *LinkInfo // Detailed Interface Information LinkMode *uint8 // Interface link mode MTU uint32 // MTU of the device Name string // Device name NetDevGroup *uint32 // Interface network device group OperationalState OperationalState // Interface operation state PhysPortID *string // Interface unique physical port identifier within the NIC PhysPortName *string // Interface physical port name within the NIC PhysSwitchID *string // Unique physical switch identifier of a switch this port belongs to QueueDisc string // Queueing discipline Master *uint32 // Master device index (0 value un-enslaves) Stats *LinkStats // Interface Statistics Stats64 *LinkStats64 // Interface Statistics (64 bits version) TxQueueLen *uint32 // Interface transmit queue len in number of packets Type uint32 // Link type XDP *LinkXDP // Express Data Patch Information } // OperationalState represents an interface's operational state. type OperationalState uint8 // Constants that represent operational state of an interface // // Adapted from https://elixir.bootlin.com/linux/v4.19.2/source/include/uapi/linux/if.h#L166 const ( OperStateUnknown OperationalState = iota // status could not be determined OperStateNotPresent // down, due to some missing component (typically hardware) OperStateDown // down, either administratively or due to a fault OperStateLowerLayerDown // down, due to lower-layer interfaces OperStateTesting // operationally down, in some test mode OperStateDormant // down, waiting for some external event OperStateUp // interface is in a state to send and receive packets ) // unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage. func (a *LinkAttributes) decode(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.IFLA_UNSPEC: // unused attribute case unix.IFLA_ADDRESS: l := len(ad.Bytes()) if l < 4 || l > 32 { return errInvalidLinkMessageAttr } a.Address = ad.Bytes() case unix.IFLA_IFALIAS: v := ad.String() a.Alias = &v case unix.IFLA_BROADCAST: l := len(ad.Bytes()) if l < 4 || l > 32 { return errInvalidLinkMessageAttr } a.Broadcast = ad.Bytes() case unix.IFLA_CARRIER: v := ad.Uint8() a.Carrier = &v case unix.IFLA_CARRIER_CHANGES: v := ad.Uint32() a.CarrierChanges = &v case unix.IFLA_CARRIER_UP_COUNT: v := ad.Uint32() a.CarrierUpCount = &v case unix.IFLA_CARRIER_DOWN_COUNT: v := ad.Uint32() a.CarrierDownCount = &v case unix.IFLA_GROUP: v := ad.Uint32() a.NetDevGroup = &v case unix.IFLA_MTU: a.MTU = ad.Uint32() case unix.IFLA_IFNAME: a.Name = ad.String() case unix.IFLA_LINK: a.Type = ad.Uint32() case unix.IFLA_LINKINFO: a.Info = &LinkInfo{} ad.Nested(a.Info.decode) case unix.IFLA_LINKMODE: v := ad.Uint8() a.LinkMode = &v case unix.IFLA_MASTER: v := ad.Uint32() a.Master = &v case unix.IFLA_OPERSTATE: a.OperationalState = OperationalState(ad.Uint8()) case unix.IFLA_PHYS_PORT_ID: v := ad.String() a.PhysPortID = &v case unix.IFLA_PHYS_SWITCH_ID: v := ad.String() a.PhysSwitchID = &v case unix.IFLA_PHYS_PORT_NAME: v := ad.String() a.PhysPortName = &v case unix.IFLA_QDISC: a.QueueDisc = ad.String() case unix.IFLA_STATS: a.Stats = &LinkStats{} err := a.Stats.unmarshalBinary(ad.Bytes()) if err != nil { return err } case unix.IFLA_STATS64: a.Stats64 = &LinkStats64{} err := a.Stats64.unmarshalBinary(ad.Bytes()) if err != nil { return err } case unix.IFLA_TXQLEN: v := ad.Uint32() a.TxQueueLen = &v case unix.IFLA_XDP: a.XDP = &LinkXDP{} ad.Nested(a.XDP.decode) } } return nil } // MarshalBinary marshals a LinkAttributes into a byte slice. func (a *LinkAttributes) encode(ae *netlink.AttributeEncoder) error { ae.Uint16(unix.IFLA_UNSPEC, 0) ae.String(unix.IFLA_IFNAME, a.Name) ae.Uint32(unix.IFLA_LINK, a.Type) ae.String(unix.IFLA_QDISC, a.QueueDisc) if a.MTU != 0 { ae.Uint32(unix.IFLA_MTU, a.MTU) } if len(a.Address) != 0 { ae.Bytes(unix.IFLA_ADDRESS, a.Address) } if len(a.Broadcast) != 0 { ae.Bytes(unix.IFLA_BROADCAST, a.Broadcast) } if a.OperationalState != OperStateUnknown { ae.Uint8(unix.IFLA_OPERSTATE, uint8(a.OperationalState)) } if a.Info != nil { nae := netlink.NewAttributeEncoder() nae.ByteOrder = ae.ByteOrder err := a.Info.encode(nae) if err != nil { return err } b, err := nae.Encode() if err != nil { return err } ae.Bytes(unix.IFLA_LINKINFO, b) } if a.XDP != nil { nae := netlink.NewAttributeEncoder() nae.ByteOrder = ae.ByteOrder err := a.XDP.encode(nae) if err != nil { return err } b, err := nae.Encode() if err != nil { return err } ae.Bytes(unix.IFLA_XDP, b) } if a.Master != nil { ae.Uint32(unix.IFLA_MASTER, *a.Master) } return nil } // LinkStats contains packet statistics type LinkStats struct { RXPackets uint32 // total packets received TXPackets uint32 // total packets transmitted RXBytes uint32 // total bytes received TXBytes uint32 // total bytes transmitted RXErrors uint32 // bad packets received TXErrors uint32 // packet transmit problems RXDropped uint32 // no space in linux buffers TXDropped uint32 // no space available in linux Multicast uint32 // multicast packets received Collisions uint32 // detailed rx_errors: RXLengthErrors uint32 RXOverErrors uint32 // receiver ring buff overflow RXCRCErrors uint32 // recved pkt with crc error RXFrameErrors uint32 // recv'd frame alignment error RXFIFOErrors uint32 // recv'r fifo overrun RXMissedErrors uint32 // receiver missed packet // detailed tx_errors TXAbortedErrors uint32 TXCarrierErrors uint32 TXFIFOErrors uint32 TXHeartbeatErrors uint32 TXWindowErrors uint32 // for cslip etc RXCompressed uint32 TXCompressed uint32 RXNoHandler uint32 // dropped, no handler found } // unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage. func (a *LinkStats) unmarshalBinary(b []byte) error { l := len(b) if l != 92 && l != 96 { return fmt.Errorf("incorrect LinkMessage size, want: 92 or 96, got: %d", len(b)) } a.RXPackets = nativeEndian.Uint32(b[0:4]) a.TXPackets = nativeEndian.Uint32(b[4:8]) a.RXBytes = nativeEndian.Uint32(b[8:12]) a.TXBytes = nativeEndian.Uint32(b[12:16]) a.RXErrors = nativeEndian.Uint32(b[16:20]) a.TXErrors = nativeEndian.Uint32(b[20:24]) a.RXDropped = nativeEndian.Uint32(b[24:28]) a.TXDropped = nativeEndian.Uint32(b[28:32]) a.Multicast = nativeEndian.Uint32(b[32:36]) a.Collisions = nativeEndian.Uint32(b[36:40]) a.RXLengthErrors = nativeEndian.Uint32(b[40:44]) a.RXOverErrors = nativeEndian.Uint32(b[44:48]) a.RXCRCErrors = nativeEndian.Uint32(b[48:52]) a.RXFrameErrors = nativeEndian.Uint32(b[52:56]) a.RXFIFOErrors = nativeEndian.Uint32(b[56:60]) a.RXMissedErrors = nativeEndian.Uint32(b[60:64]) a.TXAbortedErrors = nativeEndian.Uint32(b[64:68]) a.TXCarrierErrors = nativeEndian.Uint32(b[68:72]) a.TXFIFOErrors = nativeEndian.Uint32(b[72:76]) a.TXHeartbeatErrors = nativeEndian.Uint32(b[76:80]) a.TXWindowErrors = nativeEndian.Uint32(b[80:84]) a.RXCompressed = nativeEndian.Uint32(b[84:88]) a.TXCompressed = nativeEndian.Uint32(b[88:92]) if l == 96 { // kernel 4.6+ a.RXNoHandler = nativeEndian.Uint32(b[92:96]) } return nil } // LinkStats64 contains packet statistics type LinkStats64 struct { RXPackets uint64 // total packets received TXPackets uint64 // total packets transmitted RXBytes uint64 // total bytes received TXBytes uint64 // total bytes transmitted RXErrors uint64 // bad packets received TXErrors uint64 // packet transmit problems RXDropped uint64 // no space in linux buffers TXDropped uint64 // no space available in linux Multicast uint64 // multicast packets received Collisions uint64 // detailed rx_errors: RXLengthErrors uint64 RXOverErrors uint64 // receiver ring buff overflow RXCRCErrors uint64 // recved pkt with crc error RXFrameErrors uint64 // recv'd frame alignment error RXFIFOErrors uint64 // recv'r fifo overrun RXMissedErrors uint64 // receiver missed packet // detailed tx_errors TXAbortedErrors uint64 TXCarrierErrors uint64 TXFIFOErrors uint64 TXHeartbeatErrors uint64 TXWindowErrors uint64 // for cslip etc RXCompressed uint64 TXCompressed uint64 RXNoHandler uint64 // dropped, no handler found RXOtherhostDropped uint64 // Number of packets dropped due to mismatch in destination MAC address. } // unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage. func (a *LinkStats64) unmarshalBinary(b []byte) error { l := len(b) if l != 184 && l != 192 && l != 200 { return fmt.Errorf("incorrect size, want: 184 or 192 or 200") } a.RXPackets = nativeEndian.Uint64(b[0:8]) a.TXPackets = nativeEndian.Uint64(b[8:16]) a.RXBytes = nativeEndian.Uint64(b[16:24]) a.TXBytes = nativeEndian.Uint64(b[24:32]) a.RXErrors = nativeEndian.Uint64(b[32:40]) a.TXErrors = nativeEndian.Uint64(b[40:48]) a.RXDropped = nativeEndian.Uint64(b[48:56]) a.TXDropped = nativeEndian.Uint64(b[56:64]) a.Multicast = nativeEndian.Uint64(b[64:72]) a.Collisions = nativeEndian.Uint64(b[72:80]) a.RXLengthErrors = nativeEndian.Uint64(b[80:88]) a.RXOverErrors = nativeEndian.Uint64(b[88:96]) a.RXCRCErrors = nativeEndian.Uint64(b[96:104]) a.RXFrameErrors = nativeEndian.Uint64(b[104:112]) a.RXFIFOErrors = nativeEndian.Uint64(b[112:120]) a.RXMissedErrors = nativeEndian.Uint64(b[120:128]) a.TXAbortedErrors = nativeEndian.Uint64(b[128:136]) a.TXCarrierErrors = nativeEndian.Uint64(b[136:144]) a.TXFIFOErrors = nativeEndian.Uint64(b[144:152]) a.TXHeartbeatErrors = nativeEndian.Uint64(b[152:160]) a.TXWindowErrors = nativeEndian.Uint64(b[160:168]) a.RXCompressed = nativeEndian.Uint64(b[168:176]) a.TXCompressed = nativeEndian.Uint64(b[176:184]) if l > 191 { // kernel 4.6+ a.RXNoHandler = nativeEndian.Uint64(b[184:192]) } if l > 199 { // kernel 5.19+ a.RXOtherhostDropped = nativeEndian.Uint64(b[192:200]) } return nil } // LinkInfo contains data for specific network types type LinkInfo struct { Kind string // Driver name Data []byte // Driver specific configuration stored as nested Netlink messages SlaveKind string // Slave driver name SlaveData []byte // Slave driver specific configuration } func (i *LinkInfo) decode(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.IFLA_INFO_KIND: i.Kind = ad.String() case unix.IFLA_INFO_SLAVE_KIND: i.SlaveKind = ad.String() case unix.IFLA_INFO_DATA: i.Data = ad.Bytes() case unix.IFLA_INFO_SLAVE_DATA: i.SlaveData = ad.Bytes() } } return nil } func (i *LinkInfo) encode(ae *netlink.AttributeEncoder) error { ae.String(unix.IFLA_INFO_KIND, i.Kind) ae.Bytes(unix.IFLA_INFO_DATA, i.Data) if len(i.SlaveData) > 0 { ae.String(unix.IFLA_INFO_SLAVE_KIND, i.SlaveKind) ae.Bytes(unix.IFLA_INFO_SLAVE_DATA, i.SlaveData) } return nil } // LinkXDP holds Express Data Path specific information type LinkXDP struct { FD int32 ExpectedFD int32 Attached uint8 Flags uint32 ProgID uint32 } func (xdp *LinkXDP) decode(ad *netlink.AttributeDecoder) error { for ad.Next() { switch ad.Type() { case unix.IFLA_XDP_FD: xdp.FD = ad.Int32() case unix.IFLA_XDP_EXPECTED_FD: xdp.ExpectedFD = ad.Int32() case unix.IFLA_XDP_ATTACHED: xdp.Attached = ad.Uint8() case unix.IFLA_XDP_FLAGS: xdp.Flags = ad.Uint32() case unix.IFLA_XDP_PROG_ID: xdp.ProgID = ad.Uint32() } } return nil } func (xdp *LinkXDP) encode(ae *netlink.AttributeEncoder) error { ae.Int32(unix.IFLA_XDP_FD, xdp.FD) ae.Int32(unix.IFLA_XDP_EXPECTED_FD, xdp.ExpectedFD) ae.Uint32(unix.IFLA_XDP_FLAGS, xdp.Flags) // XDP_ATtACHED and XDP_PROG_ID are things that only can return from the kernel, // not be send, so we don't encode them. // source: https://elixir.bootlin.com/linux/v5.10.15/source/net/core/rtnetlink.c#L2894 return nil }