mirror of https://github.com/vishvananda/netlink
334 lines
7.4 KiB
Go
334 lines
7.4 KiB
Go
|
package netlink
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"sync/atomic"
|
||
|
"syscall"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
var nextSeqNr uint32
|
||
|
|
||
|
// Get native endianness for the system
|
||
|
func nativeEndian() binary.ByteOrder {
|
||
|
var x uint32 = 0x01020304
|
||
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||
|
return binary.BigEndian
|
||
|
}
|
||
|
return binary.LittleEndian
|
||
|
}
|
||
|
|
||
|
// Byte swap a 16 bit value in place
|
||
|
func swap16(i uint16) uint16 {
|
||
|
return (i&0xff00)>>8 | (i&0xff)<<8
|
||
|
}
|
||
|
|
||
|
// Byte swap a 32 bit value in place
|
||
|
func swap32(i uint32) uint32 {
|
||
|
return (i&0xff000000)>>24 | (i&0xff0000)>>8 | (i&0xff00)<<8 | (i&0xff)<<24
|
||
|
}
|
||
|
|
||
|
type NetlinkRequestData interface {
|
||
|
Len() int
|
||
|
Serialize() []byte
|
||
|
}
|
||
|
|
||
|
// IfInfomsg is related to links, but it is used for list requests as well
|
||
|
type IfInfomsg struct {
|
||
|
syscall.IfInfomsg
|
||
|
}
|
||
|
|
||
|
// Create an IfInfomsg with family specified
|
||
|
func newIfInfomsg(family int) *IfInfomsg {
|
||
|
return &IfInfomsg{
|
||
|
IfInfomsg: syscall.IfInfomsg{
|
||
|
Family: uint8(family),
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func DeserializeIfInfomsg(b []byte) *IfInfomsg {
|
||
|
return (*IfInfomsg)(unsafe.Pointer(&b[0:syscall.SizeofIfInfomsg][0]))
|
||
|
}
|
||
|
|
||
|
func (msg *IfInfomsg) Serialize() []byte {
|
||
|
return (*(*[syscall.SizeofIfInfomsg]byte)(unsafe.Pointer(msg)))[:]
|
||
|
}
|
||
|
|
||
|
func (msg *IfInfomsg) Len() int {
|
||
|
return syscall.SizeofIfInfomsg
|
||
|
}
|
||
|
|
||
|
func rtaAlignOf(attrlen int) int {
|
||
|
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
||
|
}
|
||
|
|
||
|
// Extend RtAttr to handle data and children
|
||
|
type RtAttr struct {
|
||
|
syscall.RtAttr
|
||
|
Data []byte
|
||
|
children []NetlinkRequestData
|
||
|
}
|
||
|
|
||
|
// Create a new Extended RtAttr object
|
||
|
func newRtAttr(attrType int, data []byte) *RtAttr {
|
||
|
return &RtAttr{
|
||
|
RtAttr: syscall.RtAttr{
|
||
|
Type: uint16(attrType),
|
||
|
},
|
||
|
children: []NetlinkRequestData{},
|
||
|
Data: data,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create a new RtAttr obj anc add it as a child of an existing object
|
||
|
func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
|
||
|
attr := newRtAttr(attrType, data)
|
||
|
parent.children = append(parent.children, attr)
|
||
|
return attr
|
||
|
}
|
||
|
|
||
|
func (a *RtAttr) Len() int {
|
||
|
if len(a.children) == 0 {
|
||
|
return (syscall.SizeofRtAttr + len(a.Data))
|
||
|
}
|
||
|
|
||
|
l := 0
|
||
|
for _, child := range a.children {
|
||
|
l += child.Len()
|
||
|
}
|
||
|
l += syscall.SizeofRtAttr
|
||
|
return rtaAlignOf(l + len(a.Data))
|
||
|
}
|
||
|
|
||
|
// Serialize the RtAttr into a byte array
|
||
|
// This can't ust unsafe.cast because it must iterate through children.
|
||
|
func (a *RtAttr) Serialize() []byte {
|
||
|
native := nativeEndian()
|
||
|
|
||
|
length := a.Len()
|
||
|
buf := make([]byte, rtaAlignOf(length))
|
||
|
|
||
|
if a.Data != nil {
|
||
|
copy(buf[4:], a.Data)
|
||
|
} else {
|
||
|
next := 4
|
||
|
for _, child := range a.children {
|
||
|
childBuf := child.Serialize()
|
||
|
copy(buf[next:], childBuf)
|
||
|
next += rtaAlignOf(len(childBuf))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if l := uint16(length); l != 0 {
|
||
|
native.PutUint16(buf[0:2], l)
|
||
|
}
|
||
|
native.PutUint16(buf[2:4], a.Type)
|
||
|
return buf
|
||
|
}
|
||
|
|
||
|
type NetlinkRequest struct {
|
||
|
syscall.NlMsghdr
|
||
|
Data []NetlinkRequestData
|
||
|
}
|
||
|
|
||
|
// Serialize the Netlink Request into a byte array
|
||
|
func (msg *NetlinkRequest) Serialize() []byte {
|
||
|
length := syscall.SizeofNlMsghdr
|
||
|
dataBytes := make([][]byte, len(msg.Data))
|
||
|
for i, data := range msg.Data {
|
||
|
dataBytes[i] = data.Serialize()
|
||
|
length = length + len(dataBytes[i])
|
||
|
}
|
||
|
msg.Len = uint32(length)
|
||
|
b := make([]byte, length)
|
||
|
hdr := (*(*[syscall.SizeofNlMsghdr]byte)(unsafe.Pointer(msg)))[:]
|
||
|
next := syscall.SizeofNlMsghdr
|
||
|
copy(b[0:next], hdr)
|
||
|
for _, data := range dataBytes {
|
||
|
for _, dataByte := range data {
|
||
|
b[next] = dataByte
|
||
|
next = next + 1
|
||
|
}
|
||
|
}
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
func (msg *NetlinkRequest) AddData(data NetlinkRequestData) {
|
||
|
if data != nil {
|
||
|
msg.Data = append(msg.Data, data)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Execute the request against a the given sockType.
|
||
|
// Returns a list of netlink messages in seriaized format, optionally filtered
|
||
|
// by resType.
|
||
|
func (req *NetlinkRequest) Execute(sockType int, resType uint16) ([][]byte, error) {
|
||
|
s, err := getNetlinkSocket(sockType)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer s.Close()
|
||
|
|
||
|
if err := s.Send(req); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
pid, err := s.GetPid()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
res := make([][]byte, 0)
|
||
|
|
||
|
done:
|
||
|
for {
|
||
|
msgs, err := s.Recieve()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
for _, m := range msgs {
|
||
|
if m.Header.Seq != req.Seq {
|
||
|
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
|
||
|
}
|
||
|
if m.Header.Pid != pid {
|
||
|
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||
|
}
|
||
|
if m.Header.Type == syscall.NLMSG_DONE {
|
||
|
break done
|
||
|
}
|
||
|
if m.Header.Type == syscall.NLMSG_ERROR {
|
||
|
native := nativeEndian()
|
||
|
error := int32(native.Uint32(m.Data[0:4]))
|
||
|
if error == 0 {
|
||
|
break done
|
||
|
}
|
||
|
return nil, syscall.Errno(-error)
|
||
|
}
|
||
|
if resType != 0 && m.Header.Type != resType {
|
||
|
continue
|
||
|
}
|
||
|
res = append(res, m.Data)
|
||
|
}
|
||
|
}
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
// Create a new netlink request from proto and flags
|
||
|
// Note the Len value will be inaccurate once data is added until
|
||
|
// the message is serialized
|
||
|
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
|
||
|
return &NetlinkRequest{
|
||
|
NlMsghdr: syscall.NlMsghdr{
|
||
|
Len: uint32(syscall.SizeofNlMsghdr),
|
||
|
Type: uint16(proto),
|
||
|
Flags: syscall.NLM_F_REQUEST | uint16(flags),
|
||
|
Seq: atomic.AddUint32(&nextSeqNr, 1),
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type NetlinkSocket struct {
|
||
|
fd int
|
||
|
lsa syscall.SockaddrNetlink
|
||
|
}
|
||
|
|
||
|
func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
|
||
|
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
s := &NetlinkSocket{
|
||
|
fd: fd,
|
||
|
}
|
||
|
s.lsa.Family = syscall.AF_NETLINK
|
||
|
if err := syscall.Bind(fd, &s.lsa); err != nil {
|
||
|
syscall.Close(fd)
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (s *NetlinkSocket) Close() {
|
||
|
syscall.Close(s.fd)
|
||
|
}
|
||
|
|
||
|
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
|
||
|
if err := syscall.Sendto(s.fd, request.Serialize(), 0, &s.lsa); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *NetlinkSocket) Recieve() ([]syscall.NetlinkMessage, error) {
|
||
|
rb := make([]byte, syscall.Getpagesize())
|
||
|
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if nr < syscall.NLMSG_HDRLEN {
|
||
|
return nil, fmt.Errorf("Got short response from netlink")
|
||
|
}
|
||
|
rb = rb[:nr]
|
||
|
return syscall.ParseNetlinkMessage(rb)
|
||
|
}
|
||
|
|
||
|
func (s *NetlinkSocket) GetPid() (uint32, error) {
|
||
|
lsa, err := syscall.Getsockname(s.fd)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
switch v := lsa.(type) {
|
||
|
case *syscall.SockaddrNetlink:
|
||
|
return v.Pid, nil
|
||
|
}
|
||
|
return 0, fmt.Errorf("Wrong socket type")
|
||
|
}
|
||
|
|
||
|
func zeroTerminated(s string) []byte {
|
||
|
bytes := make([]byte, len(s)+1)
|
||
|
for i := 0; i < len(s); i++ {
|
||
|
bytes[i] = s[i]
|
||
|
}
|
||
|
bytes[len(s)] = 0
|
||
|
return bytes
|
||
|
}
|
||
|
|
||
|
func nonZeroTerminated(s string) []byte {
|
||
|
bytes := make([]byte, len(s))
|
||
|
for i := 0; i < len(s); i++ {
|
||
|
bytes[i] = s[i]
|
||
|
}
|
||
|
return bytes
|
||
|
}
|
||
|
|
||
|
func bytesToString(b []byte) string {
|
||
|
n := bytes.Index(b, []byte{0})
|
||
|
return string(b[:n])
|
||
|
}
|
||
|
|
||
|
func parseRouteAttr(b []byte) ([]syscall.NetlinkRouteAttr, error) {
|
||
|
var attrs []syscall.NetlinkRouteAttr
|
||
|
for len(b) >= syscall.SizeofRtAttr {
|
||
|
a, vbuf, alen, err := netlinkRouteAttrAndValue(b)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ra := syscall.NetlinkRouteAttr{Attr: *a, Value: vbuf[:int(a.Len)-syscall.SizeofRtAttr]}
|
||
|
attrs = append(attrs, ra)
|
||
|
b = b[alen:]
|
||
|
}
|
||
|
return attrs, nil
|
||
|
}
|
||
|
|
||
|
func netlinkRouteAttrAndValue(b []byte) (*syscall.RtAttr, []byte, int, error) {
|
||
|
a := (*syscall.RtAttr)(unsafe.Pointer(&b[0]))
|
||
|
if int(a.Len) < syscall.SizeofRtAttr || int(a.Len) > len(b) {
|
||
|
return nil, nil, 0, syscall.EINVAL
|
||
|
}
|
||
|
return a, b[syscall.SizeofRtAttr:], rtaAlignOf(int(a.Len)), nil
|
||
|
}
|