mirror of
https://github.com/cloudnativelabs/kube-router.git
synced 2025-10-13 02:41:05 +02:00
555 lines
12 KiB
Go
Executable File
555 lines
12 KiB
Go
Executable File
// +build linux
|
|
|
|
// Package nlgo implements netlink library routines.
|
|
//
|
|
// This was golang port of libnl. For basic concept, please have a look at
|
|
// original libnl documentation http://www.infradead.org/~tgr/libnl/ .
|
|
//
|
|
package nlgo
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/pkg/errors"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
func align(size, tick int) int {
|
|
return (size + tick - 1) &^ (tick - 1)
|
|
}
|
|
|
|
func NLMSG_ALIGN(size int) int {
|
|
return align(size, syscall.NLMSG_ALIGNTO)
|
|
}
|
|
|
|
func NLA_ALIGN(size int) int {
|
|
return align(size, syscall.NLA_ALIGNTO)
|
|
}
|
|
|
|
const NLA_TYPE_MASK = ^uint16(syscall.NLA_F_NESTED | syscall.NLA_F_NET_BYTEORDER)
|
|
|
|
type NlaValue interface {
|
|
// returns the entire nlattr byte sequence including header and trailing alignment padding
|
|
Build(syscall.NlAttr) []byte
|
|
}
|
|
|
|
// Attr represents single netlink attribute package.
|
|
// Developer memo: syscall.ParseNetlinkRouteAttr does not parse nested attributes.
|
|
// I wanted to make deep parser.
|
|
type Attr struct {
|
|
Header syscall.NlAttr
|
|
Value NlaValue
|
|
}
|
|
|
|
func (self Attr) Field() uint16 {
|
|
return self.Header.Type & NLA_TYPE_MASK
|
|
}
|
|
|
|
func (self Attr) Bytes() []byte {
|
|
return self.Value.Build(self.Header)
|
|
}
|
|
|
|
func (self Attr) Build(hdr syscall.NlAttr) []byte {
|
|
return Binary(self.Bytes()).Build(hdr)
|
|
}
|
|
|
|
func (self Attr) Array() []Attr {
|
|
return []Attr{self}
|
|
}
|
|
|
|
type AttrList interface {
|
|
Slice() []Attr
|
|
Bytes() []byte
|
|
}
|
|
|
|
type AttrSlice []Attr
|
|
|
|
func (self AttrSlice) Bytes() []byte {
|
|
var ret []byte
|
|
for _, attr := range []Attr(self) {
|
|
ret = append(ret, attr.Value.Build(attr.Header)...)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (self AttrSlice) Build(hdr syscall.NlAttr) []byte {
|
|
return Binary(self.Bytes()).Build(hdr)
|
|
}
|
|
|
|
func (self AttrSlice) String() string {
|
|
var comps []string
|
|
for _, attr := range []Attr(self) {
|
|
comps = append(comps, fmt.Sprintf("%d: %v", attr.Field(), attr.Value))
|
|
}
|
|
return fmt.Sprintf("[%s]", strings.Join(comps, ", "))
|
|
}
|
|
|
|
func (self AttrSlice) Slice() []Attr {
|
|
return []Attr(self)
|
|
}
|
|
|
|
type Policy interface {
|
|
Parse([]byte) (NlaValue, error)
|
|
}
|
|
|
|
type SinglePolicy int
|
|
|
|
const (
|
|
U8Policy SinglePolicy = iota
|
|
U16Policy
|
|
U32Policy
|
|
U64Policy
|
|
StringPolicy
|
|
FlagPolicy
|
|
NulStringPolicy
|
|
BinaryPolicy
|
|
S8Policy
|
|
S16Policy
|
|
S32Policy
|
|
S64Policy
|
|
)
|
|
|
|
func (self SinglePolicy) Parse(nla []byte) (NlaValue, error) {
|
|
if len(nla) < syscall.SizeofNlAttr {
|
|
return nil, errors.Wrapf(NLE_RANGE, "len(nla)=%d < syscall.SizeofNlAttr", len(nla))
|
|
}
|
|
hdr := (*syscall.NlAttr)(unsafe.Pointer(&nla[0]))
|
|
if int(hdr.Len) < NLA_HDRLEN || int(hdr.Len) > len(nla) {
|
|
return nil, errors.Wrapf(NLE_RANGE, "hdr.Len=%d len(nla)=%d", hdr.Len, len(nla))
|
|
}
|
|
attr := Attr{
|
|
Header: *hdr,
|
|
Value: Binary(nla[NLA_HDRLEN:hdr.Len]),
|
|
}
|
|
var err error
|
|
switch self {
|
|
case U8Policy:
|
|
err = setU8(&attr)
|
|
case U16Policy:
|
|
err = setU16(&attr)
|
|
case U32Policy:
|
|
err = setU32(&attr)
|
|
case U64Policy:
|
|
err = setU64(&attr)
|
|
case BinaryPolicy:
|
|
// do nothing
|
|
case StringPolicy:
|
|
err = setString(&attr)
|
|
case NulStringPolicy:
|
|
err = setNulString(&attr)
|
|
case FlagPolicy:
|
|
err = setFlag(&attr)
|
|
case S8Policy:
|
|
err = setS8(&attr)
|
|
case S16Policy:
|
|
err = setS16(&attr)
|
|
case S32Policy:
|
|
err = setS32(&attr)
|
|
case S64Policy:
|
|
err = setS64(&attr)
|
|
default:
|
|
err = fmt.Errorf("unknown single policy")
|
|
}
|
|
return attr, err
|
|
}
|
|
|
|
func parseElement(p Policy, attr *Attr) error {
|
|
if p != nil {
|
|
switch policy := p.(type) {
|
|
case SinglePolicy:
|
|
if value, err := policy.Parse(attr.Bytes()); err != nil {
|
|
return err
|
|
} else {
|
|
*attr = value.(Attr)
|
|
}
|
|
default:
|
|
if value, err := policy.Parse([]byte(attr.Value.(Binary))); err != nil {
|
|
return err
|
|
} else {
|
|
attr.Value = value
|
|
}
|
|
}
|
|
} else if attr.Header.Type&syscall.NLA_F_NESTED != 0 {
|
|
if list, err := SimpleListPolicy.Parse([]byte(attr.Value.(Binary))); err != nil {
|
|
return err
|
|
} else {
|
|
attr.Value = list
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ListPolicy struct {
|
|
Nested Policy
|
|
}
|
|
|
|
var SimpleListPolicy ListPolicy
|
|
|
|
func (self ListPolicy) Parse(nla []byte) (NlaValue, error) {
|
|
var attrs []Attr
|
|
for len(nla) >= NLA_HDRLEN {
|
|
var attr Attr
|
|
if sattr, err := BinaryPolicy.Parse(nla); err != nil {
|
|
return nil, err
|
|
} else {
|
|
attr = sattr.(Attr)
|
|
}
|
|
if err := parseElement(self.Nested, &attr); err != nil {
|
|
return nil, err
|
|
}
|
|
attrs = append(attrs, attr)
|
|
nla = nla[NLA_ALIGN(int(attr.Header.Len)):]
|
|
}
|
|
return AttrSlice(attrs), nil
|
|
}
|
|
|
|
type AttrMap struct {
|
|
AttrSlice
|
|
Policy MapPolicy
|
|
}
|
|
|
|
func (self AttrMap) Get(field uint16) NlaValue {
|
|
for _, attr := range self.Slice() {
|
|
if attr.Field() == field {
|
|
return attr.Value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self AttrMap) String() string {
|
|
var comps []string
|
|
for _, attr := range self.AttrSlice {
|
|
field := attr.Field()
|
|
name := "?"
|
|
if n, ok := self.Policy.Names[field]; ok {
|
|
name = n
|
|
}
|
|
comps = append(comps, fmt.Sprintf("%s: %v", name, attr.Value))
|
|
}
|
|
return fmt.Sprintf("%s(%s)", self.Policy.Prefix, strings.Join(comps, ", "))
|
|
}
|
|
|
|
type MapPolicy struct {
|
|
Prefix string
|
|
Names map[uint16]string
|
|
Rule map[uint16]Policy
|
|
}
|
|
|
|
func (self MapPolicy) Parse(nla []byte) (NlaValue, error) {
|
|
var attrs []Attr
|
|
for len(nla) >= NLA_HDRLEN {
|
|
var attr Attr
|
|
if sattr, err := BinaryPolicy.Parse(nla); err != nil {
|
|
return nil, err
|
|
} else {
|
|
attr = sattr.(Attr)
|
|
}
|
|
if err := parseElement(self.Rule[attr.Field()], &attr); err != nil {
|
|
return nil, err
|
|
}
|
|
attrs = append(attrs, attr)
|
|
nla = nla[NLA_ALIGN(int(attr.Header.Len)):]
|
|
}
|
|
return AttrMap{
|
|
AttrSlice: attrs,
|
|
Policy: self,
|
|
}, nil
|
|
}
|
|
|
|
// error.h
|
|
|
|
type NlError int
|
|
|
|
const (
|
|
NLE_SUCCESS NlError = iota
|
|
NLE_FAILURE
|
|
NLE_INTR
|
|
NLE_BAD_SOCK
|
|
NLE_AGAIN
|
|
NLE_NOMEM
|
|
NLE_EXIST
|
|
NLE_INVAL
|
|
NLE_RANGE
|
|
NLE_MSGSIZE
|
|
NLE_OPNOTSUPP
|
|
NLE_AF_NOSUPPORT
|
|
NLE_OBJ_NOTFOUND
|
|
NLE_NOATTR
|
|
NLE_MISSING_ATTR
|
|
NLE_AF_MISMATCH
|
|
NLE_SEQ_MISMATCH
|
|
NLE_MSG_OVERFLOW
|
|
NLE_MSG_TRUNC
|
|
NLE_NOADDR
|
|
NLE_SRCRT_NOSUPPORT
|
|
NLE_MSG_TOOSHORT
|
|
NLE_MSGTYPE_NOSUPPORT
|
|
NLE_OBJ_MISMATCH
|
|
NLE_NOCACHE
|
|
NLE_BUSY
|
|
NLE_PROTO_MISMATCH
|
|
NLE_NOACCESS
|
|
NLE_PERM
|
|
NLE_PKTLOC_FILE
|
|
NLE_PARSE_ERR
|
|
NLE_NODEV
|
|
NLE_IMMUTABLE
|
|
NLE_DUMP_INTR
|
|
)
|
|
|
|
func (self NlError) Error() string {
|
|
switch self {
|
|
default:
|
|
return "Unspecific failure"
|
|
case NLE_SUCCESS:
|
|
return "Success"
|
|
case NLE_FAILURE:
|
|
return "Unspecific failure"
|
|
case NLE_INTR:
|
|
return "Interrupted system call"
|
|
case NLE_BAD_SOCK:
|
|
return "Bad socket"
|
|
case NLE_AGAIN:
|
|
return "Try again"
|
|
case NLE_NOMEM:
|
|
return "Out of memory"
|
|
case NLE_EXIST:
|
|
return "Object exists"
|
|
case NLE_INVAL:
|
|
return "Invalid input data or parameter"
|
|
case NLE_RANGE:
|
|
return "Input data out of range"
|
|
case NLE_MSGSIZE:
|
|
return "Message size not sufficient"
|
|
case NLE_OPNOTSUPP:
|
|
return "Operation not supported"
|
|
case NLE_AF_NOSUPPORT:
|
|
return "Address family not supported"
|
|
case NLE_OBJ_NOTFOUND:
|
|
return "Object not found"
|
|
case NLE_NOATTR:
|
|
return "Attribute not available"
|
|
case NLE_MISSING_ATTR:
|
|
return "Missing attribute"
|
|
case NLE_AF_MISMATCH:
|
|
return "Address family mismatch"
|
|
case NLE_SEQ_MISMATCH:
|
|
return "Message sequence number mismatch"
|
|
case NLE_MSG_OVERFLOW:
|
|
return "Kernel reported message overflow"
|
|
case NLE_MSG_TRUNC:
|
|
return "Kernel reported truncated message"
|
|
case NLE_NOADDR:
|
|
return "Invalid address for specified address family"
|
|
case NLE_SRCRT_NOSUPPORT:
|
|
return "Source based routing not supported"
|
|
case NLE_MSG_TOOSHORT:
|
|
return "Netlink message is too short"
|
|
case NLE_MSGTYPE_NOSUPPORT:
|
|
return "Netlink message type is not supported"
|
|
case NLE_OBJ_MISMATCH:
|
|
return "Object type does not match cache"
|
|
case NLE_NOCACHE:
|
|
return "Unknown or invalid cache type"
|
|
case NLE_BUSY:
|
|
return "Object busy"
|
|
case NLE_PROTO_MISMATCH:
|
|
return "Protocol mismatch"
|
|
case NLE_NOACCESS:
|
|
return "No Access"
|
|
case NLE_PERM:
|
|
return "Operation not permitted"
|
|
case NLE_PKTLOC_FILE:
|
|
return "Unable to open packet location file"
|
|
case NLE_PARSE_ERR:
|
|
return "Unable to parse object"
|
|
case NLE_NODEV:
|
|
return "No such device"
|
|
case NLE_IMMUTABLE:
|
|
return "Immutable attribute"
|
|
case NLE_DUMP_INTR:
|
|
return "Dump inconsistency detected, interrupted"
|
|
}
|
|
}
|
|
|
|
// socket.c
|
|
|
|
var pidLock = &sync.Mutex{}
|
|
var pidUsed = make(map[int]bool)
|
|
|
|
const (
|
|
NL_SOCK_BUFSIZE_SET = 1 << iota
|
|
NL_SOCK_PASSCRED
|
|
NL_OWN_PORT
|
|
NL_MSG_PEEK
|
|
NL_NO_AUTO_ACK
|
|
)
|
|
|
|
type NlSock struct {
|
|
Local syscall.SockaddrNetlink
|
|
Peer syscall.SockaddrNetlink
|
|
Fd int
|
|
SeqNext uint32
|
|
SeqExpect uint32
|
|
Flags int // NL_NO_AUTO_ACK etc.,
|
|
}
|
|
|
|
func NlSocketAlloc() *NlSock {
|
|
tick := uint32(time.Now().Unix())
|
|
return &NlSock{
|
|
Fd: -1,
|
|
Local: syscall.SockaddrNetlink{
|
|
Family: syscall.AF_NETLINK,
|
|
},
|
|
Peer: syscall.SockaddrNetlink{
|
|
Family: syscall.AF_NETLINK,
|
|
},
|
|
SeqNext: tick,
|
|
SeqExpect: tick,
|
|
Flags: NL_OWN_PORT,
|
|
}
|
|
}
|
|
|
|
func NlSocketFree(sk *NlSock) {
|
|
if sk == nil {
|
|
return
|
|
}
|
|
if sk.Fd >= 0 {
|
|
syscall.Close(sk.Fd)
|
|
}
|
|
pidLock.Lock()
|
|
defer func() {
|
|
pidLock.Unlock()
|
|
}()
|
|
high := sk.Local.Pid >> 22
|
|
delete(pidUsed, int(high))
|
|
}
|
|
|
|
func NlSocketSetBufferSize(sk *NlSock, rxbuf, txbuf int) error {
|
|
if rxbuf <= 0 {
|
|
rxbuf = 32768
|
|
}
|
|
if txbuf <= 0 {
|
|
txbuf = 32768
|
|
}
|
|
if sk == nil || sk.Fd == -1 {
|
|
return NLE_BAD_SOCK
|
|
}
|
|
if err := syscall.SetsockoptInt(sk.Fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, txbuf); err != nil {
|
|
return err
|
|
}
|
|
if err := syscall.SetsockoptInt(sk.Fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, rxbuf); err != nil {
|
|
return err
|
|
}
|
|
sk.Flags |= NL_SOCK_BUFSIZE_SET
|
|
return nil
|
|
}
|
|
|
|
// NlConnect is same with libnl nl_connect. nl_close is required for releaseing internal fd.
|
|
func NlConnect(sk *NlSock, protocol int) error {
|
|
if sk == nil || sk.Fd != -1 {
|
|
return NLE_BAD_SOCK
|
|
}
|
|
if fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, protocol); err != nil {
|
|
return err
|
|
} else {
|
|
sk.Fd = fd
|
|
}
|
|
if sk.Flags&NL_SOCK_BUFSIZE_SET != 0 {
|
|
if err := NlSocketSetBufferSize(sk, 0, 0); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if sk.Local.Pid == 0 { // _nl_socket_is_local_port_unspecified
|
|
// kernel will assign Pid
|
|
local := &syscall.SockaddrNetlink{
|
|
Family: syscall.AF_NETLINK,
|
|
}
|
|
if err := syscall.Bind(sk.Fd, local); err != nil {
|
|
return err
|
|
} else if sa, err := syscall.Getsockname(sk.Fd); err != nil {
|
|
return err
|
|
} else {
|
|
local = sa.(*syscall.SockaddrNetlink)
|
|
}
|
|
if local == nil {
|
|
return NLE_EXIST
|
|
}
|
|
sk.Local = *local
|
|
sk.Flags &^= NL_OWN_PORT
|
|
} else {
|
|
if err := syscall.Bind(sk.Fd, &(sk.Local)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NlSocketAddMembership is same with libnl nl_socket_add_membership.
|
|
func NlSocketAddMembership(sk *NlSock, group int) error {
|
|
if sk == nil {
|
|
return NLE_BAD_SOCK
|
|
}
|
|
return syscall.SetsockoptInt(sk.Fd, SOL_NETLINK, syscall.NETLINK_ADD_MEMBERSHIP, group)
|
|
}
|
|
|
|
// NlSocketAddMembership is same with libnl nl_socket_drop_membership.
|
|
func NlSocketDropMembership(sk *NlSock, group int) error {
|
|
if sk == nil {
|
|
return NLE_BAD_SOCK
|
|
}
|
|
return syscall.SetsockoptInt(sk.Fd, SOL_NETLINK, syscall.NETLINK_DROP_MEMBERSHIP, group)
|
|
}
|
|
|
|
// msg.c
|
|
|
|
const NL_AUTO_PORT = 0
|
|
const NL_AUTO_SEQ = 0
|
|
|
|
// NlSendSimple is same with libnl nl_send_simple.
|
|
func NlSendSimple(sk *NlSock, family uint16, flags uint16, buf []byte) error {
|
|
return sk.Request(syscall.NetlinkMessage{
|
|
Header: syscall.NlMsghdr{
|
|
Len: syscall.NLMSG_HDRLEN + uint32(len(buf)),
|
|
Type: family,
|
|
Flags: flags,
|
|
},
|
|
Data: buf,
|
|
})
|
|
}
|
|
|
|
// nl.c
|
|
|
|
func completeMsg(sk *NlSock, hdr *syscall.NlMsghdr) {
|
|
if hdr.Pid == NL_AUTO_PORT {
|
|
hdr.Pid = sk.Local.Pid
|
|
}
|
|
if hdr.Seq == NL_AUTO_SEQ {
|
|
hdr.Seq = sk.SeqNext
|
|
sk.SeqNext++
|
|
}
|
|
hdr.Flags |= syscall.NLM_F_REQUEST
|
|
if sk.Flags&NL_NO_AUTO_ACK == 0 {
|
|
hdr.Flags |= syscall.NLM_F_ACK
|
|
}
|
|
}
|
|
|
|
// NlCompleteMsg is same with libnl nl_complete_msg.
|
|
func NlCompleteMsg(sk *NlSock, msg []byte) {
|
|
hdr := (*syscall.NlMsghdr)(unsafe.Pointer(&msg[0]))
|
|
completeMsg(sk, hdr)
|
|
}
|
|
|
|
func (sk *NlSock) Request(msg syscall.NetlinkMessage) error {
|
|
completeMsg(sk, &msg.Header)
|
|
msg.Header.Len = syscall.NLMSG_HDRLEN + uint32(len(msg.Data))
|
|
buf := make([]byte, syscall.NLMSG_HDRLEN+NLMSG_ALIGN(len(msg.Data)))
|
|
copy(buf, (*[syscall.NLMSG_HDRLEN]byte)(unsafe.Pointer(&msg.Header))[:])
|
|
copy(buf[syscall.NLMSG_HDRLEN:], msg.Data)
|
|
return syscall.Sendto(sk.Fd, buf, 0, &sk.Peer)
|
|
}
|