|
|
|
@ -0,0 +1,309 @@
|
|
|
|
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
|
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
|
|
|
|
|
|
# https://kernel.org/doc/html/next/userspace-api/netlink/intro.html
|
|
|
|
|
|
|
|
|
|
import std/[nativesockets, net, os, posix]
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
|
AF_NETLINK* {.importc, header: "sys/socket.h", nodecl.}: uint16
|
|
|
|
|
SOL_NETLINK {.importc, header: "<sys/socket.h>", nodecl.}: cint
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{.pragma: netlinkNodecl, importc, header: "linux/netlink.h", nodecl.}
|
|
|
|
|
|
|
|
|
|
type Family* = distinct cint
|
|
|
|
|
var
|
|
|
|
|
NETLINK_ROUTE* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_W1* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_USERSOCK* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_FIREWALL* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_SOCK_DIAG* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_INET_DIAG* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_NFLOG* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_XFRM* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_SELINUX* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_ISCSI* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_AUDIT* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_FIB_LOOKUP* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_CONNECTOR* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_NETFILTER* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_SCSITRANSPORT* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_RDMA* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_IP6_FW* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_DNRTMSG* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_KOBJECT_UEVENT* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_GENERIC* {.netlinkNodecl.}: Family
|
|
|
|
|
NETLINK_CRYPTO* {.netlinkNodecl.}: Family
|
|
|
|
|
|
|
|
|
|
NETLINK_ADD_MEMBERSHIP* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_DROP_MEMBERSHIP* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_PKTINFO* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_BROADCAST_ERROR* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_NO_ENOBUFS* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_RX_RING* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_TX_RING* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_LISTEN_ALL_NSID* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_LIST_MEMBERSHIPS* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_CAP_ACK* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_EXT_ACK* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
NETLINK_GET_STRICT_CHK* {.importc, header: "linux/netlink.h", nodecl.}: int
|
|
|
|
|
|
|
|
|
|
type MessageType* = distinct uint16
|
|
|
|
|
var
|
|
|
|
|
NLMSG_NOOP* {.netlinkNodecl.}: MessageType
|
|
|
|
|
NLMSG_ERROR* {.netlinkNodecl.}: MessageType
|
|
|
|
|
NLMSG_DONE* {.netlinkNodecl.}: MessageType
|
|
|
|
|
|
|
|
|
|
func `==`(a, b: MessageType): bool {.borrow.}
|
|
|
|
|
|
|
|
|
|
type Flags* = distinct uint16
|
|
|
|
|
var
|
|
|
|
|
NLM_F_REQUEST* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_MULTI* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_ACK* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_ECHO* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_ROOT* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_MATCH* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_ATOMIC* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_DUMP* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_REPLACE* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_EXCL* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_CREATE* {.netlinkNodecl.}: Flags
|
|
|
|
|
NLM_F_APPEND* {.netlinkNodecl.}: Flags
|
|
|
|
|
|
|
|
|
|
proc `or`*(a, b: Flags): Flags {.borrow.}
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
Sockaddr_nl {.importc: "struct sockaddr_nl", header: "linux/netlink.h".} = object
|
|
|
|
|
nl_family: uint16
|
|
|
|
|
nl_pad: uint16
|
|
|
|
|
nl_pid: uint32
|
|
|
|
|
nl_groups: uint32
|
|
|
|
|
|
|
|
|
|
proc initSockaddr: Sockaddr_nl =
|
|
|
|
|
Sockaddr_nl(nl_family: AF_NETLINK)
|
|
|
|
|
|
|
|
|
|
proc saddr(sa_nl: var Sockaddr_nl): ptr Sockaddr =
|
|
|
|
|
cast[ptr Sockaddr](addr sa_nl)
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
Nlmsghdr {.importc: "struct nlmsghdr", header: "linux/netlink.h", completeStruct.} = object
|
|
|
|
|
nlmsg_len*: uint32
|
|
|
|
|
nlmsg_type*: uint16
|
|
|
|
|
nlmsg_flags*: Flags
|
|
|
|
|
nlmsg_seq*: uint32
|
|
|
|
|
nlmsg_pid*: uint32
|
|
|
|
|
|
|
|
|
|
Nlmsgerr* {.importc: "struct nlmsgerr", header: "linux/netlink.h".} = object
|
|
|
|
|
error*: cint
|
|
|
|
|
msg*: Nlmsghdr
|
|
|
|
|
|
|
|
|
|
Nlattr* {.importc: "struct nlattr", header: "linux/netlink.h", completeStruct.} = object
|
|
|
|
|
nla_len*, nla_type*: uint16
|
|
|
|
|
|
|
|
|
|
Genlmsghdr* {.importc: "struct genlmsghdr", header: "linux/genetlink.h", completeStruct.} = object
|
|
|
|
|
cmd*, version*: uint8
|
|
|
|
|
reserved: uint16
|
|
|
|
|
|
|
|
|
|
{.pragma: genetlink, importc, header: "linux/genetlink.h", nodecl.}
|
|
|
|
|
var
|
|
|
|
|
GENL_ID_CTRL* {.genetlink.}: MessageType
|
|
|
|
|
|
|
|
|
|
CTRL_CMD_UNSPEC* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_NEWFAMILY* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_DELFAMILY* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_GETFAMILY* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_NEWOPS* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_DELOPS* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_GETOPS* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_NEWMCAST_GRP* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_DELMCAST_GRP* {.genetlink.}: uint8
|
|
|
|
|
CTRL_CMD_GETMCAST_GRP* {.genetlink.}: uint8 ## unused
|
|
|
|
|
CTRL_CMD_GETPOLICY* {.genetlink.}: uint8
|
|
|
|
|
|
|
|
|
|
CTRL_ATTR_UNSPEC* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_FAMILY_ID* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_FAMILY_NAME* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_VERSION* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_HDRSIZE* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_MAXATTR* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_OPS* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_MCAST_GROUPS* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_POLICY* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_OP_POLICY* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_OP* {.genetlink.}: uint16
|
|
|
|
|
|
|
|
|
|
CTRL_ATTR_MCAST_GRP_UNSPEC* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_MCAST_GRP_NAME* {.genetlink.}: uint16
|
|
|
|
|
CTRL_ATTR_MCAST_GRP_ID* {.genetlink.}: uint16
|
|
|
|
|
|
|
|
|
|
type NetlinkSocket* = ref object
|
|
|
|
|
saLocal, saPeer: Sockaddr_nl
|
|
|
|
|
sock: Socket
|
|
|
|
|
seqNum: uint32
|
|
|
|
|
|
|
|
|
|
proc openSocket*(multicastGroups: varargs[int]): NetlinkSocket =
|
|
|
|
|
result = NetlinkSocket(
|
|
|
|
|
saLocal: initSockaddr(),
|
|
|
|
|
saPeer: initSockaddr(),
|
|
|
|
|
sock: newSocket(cint AF_NETLINK, posix.SOCK_RAW, cint NETLINK_GENERIC)
|
|
|
|
|
)
|
|
|
|
|
let fd = getFd(result.sock)
|
|
|
|
|
if bindAddr(fd, saddr result.saLocal, SockLen sizeof(result.saLocal)) != 0:
|
|
|
|
|
close(result.sock)
|
|
|
|
|
raiseOSError(osLastError(), "failed to bind Netlink socket")
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
|
check: Sockaddr_nl
|
|
|
|
|
checkSize = SockLen sizeof(check)
|
|
|
|
|
if getsockname(fd, saddr check, addr checkSize) != 0:
|
|
|
|
|
# check what kind of socket the kernel has bound
|
|
|
|
|
close(result.sock)
|
|
|
|
|
raiseOSError(osLastError(), "failed to examine Netlink socket")
|
|
|
|
|
if checkSize.int != sizeof(check):
|
|
|
|
|
close(result.sock)
|
|
|
|
|
raise newException(IOError, "bad socket len")
|
|
|
|
|
if result.saLocal.nl_family != AF_NETLINK:
|
|
|
|
|
close(result.sock)
|
|
|
|
|
raise newException(IOError, "Netlink not supported")
|
|
|
|
|
result.saLocal = check
|
|
|
|
|
for group in multicastGroups:
|
|
|
|
|
setSockOptInt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, group)
|
|
|
|
|
|
|
|
|
|
proc close*(nls: NetlinkSocket) =
|
|
|
|
|
close(nls.sock)
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
GenericHeader {.packed.} = object
|
|
|
|
|
n*: Nlmsghdr
|
|
|
|
|
g*: Genlmsghdr
|
|
|
|
|
|
|
|
|
|
GenericMessage* {.union.} = object
|
|
|
|
|
hdr*: GenericHeader
|
|
|
|
|
buf: array[4096, byte]
|
|
|
|
|
|
|
|
|
|
proc initGenericMessage: GenericMessage =
|
|
|
|
|
result.hdr.n.nlmsg_len = uint32 sizeOf(GenericHeader)
|
|
|
|
|
|
|
|
|
|
func len(msg: GenericMessage): int {.inline.} = int msg.hdr.n.nlmsg_len
|
|
|
|
|
|
|
|
|
|
proc incLen(msg: var GenericMessage; n: Natural) =
|
|
|
|
|
msg.hdr.n.nlmsg_len = (msg.hdr.n.nlmsg_len + n.uint32 + 3) and (not 3'u32)
|
|
|
|
|
doAssert(len(msg) < sizeOf(GenericMessage))
|
|
|
|
|
|
|
|
|
|
proc addAttr(msg: var GenericMessage; ctrl: uint16; data: openarray[char]) =
|
|
|
|
|
let
|
|
|
|
|
off = len(msg)
|
|
|
|
|
len = sizeOf(Nlattr) + data.len
|
|
|
|
|
if off + len > sizeOf(msg):
|
|
|
|
|
raise newException(ResourceExhaustedError, "netlink message overflow")
|
|
|
|
|
let attr = cast[ptr Nlattr](addr msg.buf[off])
|
|
|
|
|
attr.nla_len = uint16 len
|
|
|
|
|
attr.nla_type = ctrl
|
|
|
|
|
copyMem(addr msg.buf[off+sizeOf(Nlattr)], unsafeAddr data[0], data.len)
|
|
|
|
|
incLen(msg, attr.nla_len)
|
|
|
|
|
|
|
|
|
|
type NlattrParser* = object
|
|
|
|
|
off: int
|
|
|
|
|
attr*: Nlattr
|
|
|
|
|
data: pointer
|
|
|
|
|
|
|
|
|
|
proc parse*(parser: var NlattrParser; msg: GenericMessage): bool =
|
|
|
|
|
if parser.off == 0: parser.off = sizeOf(GenericHeader)
|
|
|
|
|
if parser.off+sizeOf(Nlattr) <= msg.len:
|
|
|
|
|
copyMem(addr parser.attr, unsafeAddr msg.buf[parser.off], sizeOf(parser.attr))
|
|
|
|
|
if parser.off+parser.attr.nla_len.int <= msg.len:
|
|
|
|
|
parser.data = msg.buf[parser.off+sizeof(Nlattr)].unsafeAddr.pointer
|
|
|
|
|
result = true
|
|
|
|
|
|
|
|
|
|
proc next*(parser: var NlattrParser) =
|
|
|
|
|
inc(parser.off, (parser.attr.nla_len.int + 3) and (not 3))
|
|
|
|
|
|
|
|
|
|
func len*(attr: Nlattr|ptr Nlattr): int {.inline.} = attr.nla_len.int - sizeOf(Nlattr)
|
|
|
|
|
|
|
|
|
|
proc copyString*(s: var string; parser: NlattrParser): bool =
|
|
|
|
|
let n = parser.attr.len
|
|
|
|
|
result = n > 0
|
|
|
|
|
if result:
|
|
|
|
|
setLen(s, parser.attr.len)
|
|
|
|
|
copyMem(addr s[0], parser.data, s.len)
|
|
|
|
|
|
|
|
|
|
proc copyNum*(i: var SomeInteger; parser: NlattrParser): bool =
|
|
|
|
|
let n = parser.attr.len
|
|
|
|
|
result = n <= sizeOf(i)
|
|
|
|
|
if result:
|
|
|
|
|
reset(i)
|
|
|
|
|
copyMem(addr i, parser.data, n)
|
|
|
|
|
|
|
|
|
|
proc copyObj*[T](x: var T; parser: NlattrParser): bool =
|
|
|
|
|
let n = parser.attr.len
|
|
|
|
|
result = sizeOf(T) <= n
|
|
|
|
|
if result: copyMem(addr x, parser.data, sizeOf(T))
|
|
|
|
|
|
|
|
|
|
proc send(nls: NetlinkSocket; buf: pointer; len: int): int =
|
|
|
|
|
result = sendTo(
|
|
|
|
|
nls.sock.getFd,
|
|
|
|
|
buf, len, 0,
|
|
|
|
|
saddr nls.saPeer, SockLen sizeOf(nls.saPeer))
|
|
|
|
|
if result < 0:
|
|
|
|
|
raiseOSError(osLastError(), "sendTo Netlink socket failed")
|
|
|
|
|
|
|
|
|
|
proc sendMsg*(nls: NetlinkSocket; msg: var GenericMessage) =
|
|
|
|
|
inc(nls.seqNum)
|
|
|
|
|
msg.hdr.n.nlmsg_seq = nls.seqNum
|
|
|
|
|
if send(nls, msg.addr, msg.len) != msg.len:
|
|
|
|
|
raise newException(IOError, "Netlink short send")
|
|
|
|
|
|
|
|
|
|
proc recvMsg*(nls: NetlinkSocket): GenericMessage =
|
|
|
|
|
var
|
|
|
|
|
sa = initSockaddr()
|
|
|
|
|
sl = SockLen sizeOf(sa)
|
|
|
|
|
let n = recvFrom(
|
|
|
|
|
nls.sock.getFd,
|
|
|
|
|
addr(result), sizeof(result), cint 0,
|
|
|
|
|
saddr sa, addr sl)
|
|
|
|
|
if n < 0 or result.hdr.n.nlmsg_len.int != n:
|
|
|
|
|
raiseOSError(osLastError(), "recvFrom Netlink socket failed")
|
|
|
|
|
|
|
|
|
|
type MulticastInfo* = tuple
|
|
|
|
|
mcastGrpName: string
|
|
|
|
|
mcastGrpId: uint64
|
|
|
|
|
familyId: uint16
|
|
|
|
|
|
|
|
|
|
proc resolveMulticastInfo*(nls: NetlinkSocket; family: string): MulticastInfo =
|
|
|
|
|
block:
|
|
|
|
|
var msg = initGenericMessage()
|
|
|
|
|
msg.hdr.n.nlmsg_type = uint16 GENL_ID_CTRL
|
|
|
|
|
msg.hdr.n.nlmsg_flags = NLM_F_REQUEST or NLM_F_ACK
|
|
|
|
|
msg.hdr.g.cmd = uint8 CTRL_CMD_GETFAMILY
|
|
|
|
|
addAttr(msg, CTRL_ATTR_FAMILY_NAME, family)
|
|
|
|
|
sendMsg(nls, msg)
|
|
|
|
|
|
|
|
|
|
var reply = recvMsg(nls)
|
|
|
|
|
if reply.hdr.n.nlmsg_type != GENL_ID_CTRL.uint16:
|
|
|
|
|
raise newException(IOError, "Expected GENL_ID_CTRL")
|
|
|
|
|
if reply.hdr.g.cmd != CTRL_CMD_NEWFAMILY:
|
|
|
|
|
raise newException(IOError, "Expected CTRL_CMD_NEWFAMILY")
|
|
|
|
|
|
|
|
|
|
var parser: NlattrParser
|
|
|
|
|
while parse(parser, reply):
|
|
|
|
|
if parser.attr.nla_type == CTRL_ATTR_FAMILY_NAME:
|
|
|
|
|
var name: string
|
|
|
|
|
if copyString(name, parser) and name != family:
|
|
|
|
|
raise newException(IOError, "Expected " & family & " but got " & name)
|
|
|
|
|
elif parser.attr.nla_type == CTRL_ATTR_FAMILY_ID:
|
|
|
|
|
if not copyNum(result.familyId, parser):
|
|
|
|
|
raise newException(IOError, "Failed to collect family id")
|
|
|
|
|
elif parser.attr.nla_type == CTRL_ATTR_MCAST_GROUPS.uint16:
|
|
|
|
|
var parser = parser
|
|
|
|
|
inc(parser.off, sizeof(Nlattr)*2) # why two nlattrs headers?
|
|
|
|
|
while parse(parser, reply):
|
|
|
|
|
if parser.attr.nla_type == CTRL_ATTR_MCAST_GRP_NAME:
|
|
|
|
|
if not copyString(result.mcastGrpName, parser):
|
|
|
|
|
raise newException(IOError, "Failed to collect multicast group name")
|
|
|
|
|
elif parser.attr.nla_type == CTRL_ATTR_MCAST_GRP_ID:
|
|
|
|
|
if not copyNum(result.mcastGrpId, parser):
|
|
|
|
|
raise newException(IOError, "Failed to collect multicast group id")
|
|
|
|
|
next(parser)
|
|
|
|
|
next(parser)
|