2017-04-17 10:33:09 +05:30

158 lines
3.8 KiB
Go
Executable File

// +build linux
package nlgo
import (
"log"
"sync"
"syscall"
)
type NetlinkListener interface {
NetlinkListen(syscall.NetlinkMessage)
}
// RtHub is a high layer thread-safe API, which is not present in libnl.
// RtHub is useful for listening to kernel event notification.
type RtHub struct {
sock *NlSock
lock *sync.Mutex
unilock *sync.Mutex
uniseq uint32
unicast NetlinkListener
multicast map[uint32][]NetlinkListener
}
func NewRtHub() (*RtHub, error) {
self := &RtHub{
sock: NlSocketAlloc(),
lock: &sync.Mutex{},
unilock: &sync.Mutex{},
multicast: make(map[uint32][]NetlinkListener),
}
if err := NlConnect(self.sock, syscall.NETLINK_ROUTE); err != nil {
NlSocketFree(self.sock)
return nil, err
}
go func() {
for {
buf := make([]byte, syscall.Getpagesize())
if n, _, err := syscall.Recvfrom(self.sock.Fd, buf, syscall.MSG_TRUNC); err != nil {
if e, ok := err.(syscall.Errno); ok && e.Temporary() {
continue
}
break
} else if msgs, err := syscall.ParseNetlinkMessage(buf[:n]); err != nil {
break
} else {
for _, msg := range msgs {
multi := func() []NetlinkListener {
self.lock.Lock()
defer self.lock.Unlock()
var ret []NetlinkListener
for _, s := range self.multicast {
ret = append(ret, s...)
}
return ret
}()
if msg.Header.Seq == self.uniseq {
if self.unicast != nil {
self.unicast.NetlinkListen(msg)
}
switch msg.Header.Type {
case syscall.NLMSG_DONE, syscall.NLMSG_ERROR:
self.unilock.Unlock()
}
}
if msg.Header.Seq == 0 {
for _, proc := range multi {
proc.NetlinkListen(msg)
}
}
}
}
}
log.Print("rt hub loop exit")
}()
return self, nil
}
func (self RtHub) Close() {
NlSocketFree(self.sock)
}
// Async() submits request with callback. Note that this locks sending request.
// Calling Async() in GenlListen() may create dead lock.
// netlink message header will be reparsed.
func (self *RtHub) Async(msg syscall.NetlinkMessage, listener NetlinkListener) error {
self.unilock.Lock()
self.unicast = listener
self.uniseq = self.sock.SeqNext
hdr := msg.Header
if err := NlSendSimple(self.sock, hdr.Type, hdr.Flags, msg.Data); err != nil {
self.unilock.Unlock()
return err
}
return nil
}
type hubCapture struct {
Msgs []syscall.NetlinkMessage
}
func (self *hubCapture) NetlinkListen(msg syscall.NetlinkMessage) {
self.Msgs = append(self.Msgs, msg)
}
// Sync() is synchronous version of Async().
// Calling Sync() in GenlListen() may create dead lock.
func (self *RtHub) Sync(msg syscall.NetlinkMessage) ([]syscall.NetlinkMessage, error) {
cap := &hubCapture{}
if err := self.Async(msg, cap); err != nil {
return nil, err
} else {
self.unilock.Lock() // waits for unlock, which means response arrival.
defer self.unilock.Unlock()
return cap.Msgs, nil
}
}
// Add adds a listener to the hub.
// listener will recieve all of the rtnetlink events, regardless of their group registration.
// If you want to split it, then use separate RtHub.
func (self RtHub) Add(group uint32, listener NetlinkListener) error {
self.lock.Lock()
defer self.lock.Unlock()
if len(self.multicast[group]) == 0 {
if err := NlSocketAddMembership(self.sock, int(group)); err != nil {
return err
}
}
self.multicast[group] = append(self.multicast[group], listener)
return nil
}
func (self RtHub) Remove(group uint32, listener NetlinkListener) error {
self.lock.Lock()
defer self.lock.Unlock()
var active []NetlinkListener
for _, li := range self.multicast[group] {
if li != listener {
active = append(active, li)
}
}
self.multicast[group] = active
if len(active) == 0 {
if err := NlSocketDropMembership(self.sock, int(group)); err != nil {
return err
}
}
return nil
}