| /* Netlink helpers for zbuf |
| * Copyright (c) 2014-2015 Timo Teräs |
| * |
| * This file is free software: you may copy, redistribute and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| |
| #include "znl.h" |
| |
| #define ZNL_ALIGN(len) (((len)+3) & ~3) |
| |
| void *znl_push(struct zbuf *zb, size_t n) |
| { |
| return zbuf_pushn(zb, ZNL_ALIGN(n)); |
| } |
| |
| void *znl_pull(struct zbuf *zb, size_t n) |
| { |
| return zbuf_pulln(zb, ZNL_ALIGN(n)); |
| } |
| |
| struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags) |
| { |
| struct nlmsghdr *n; |
| |
| n = znl_push(zb, sizeof(*n)); |
| if (!n) return NULL; |
| |
| *n = (struct nlmsghdr) { |
| .nlmsg_type = type, |
| .nlmsg_flags = flags, |
| }; |
| return n; |
| } |
| |
| void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n) |
| { |
| n->nlmsg_len = zb->tail - (uint8_t*)n; |
| } |
| |
| struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload) |
| { |
| struct nlmsghdr *n; |
| size_t plen; |
| |
| n = znl_pull(zb, sizeof(*n)); |
| if (!n) return NULL; |
| |
| plen = n->nlmsg_len - sizeof(*n); |
| zbuf_init(payload, znl_pull(zb, plen), plen, plen); |
| zbuf_may_pulln(zb, ZNL_ALIGN(plen) - plen); |
| |
| return n; |
| } |
| |
| struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, size_t len) |
| { |
| struct rtattr *rta; |
| uint8_t *dst; |
| |
| rta = znl_push(zb, ZNL_ALIGN(sizeof(*rta)) + ZNL_ALIGN(len)); |
| if (!rta) return NULL; |
| |
| *rta = (struct rtattr) { |
| .rta_type = type, |
| .rta_len = ZNL_ALIGN(sizeof(*rta)) + len, |
| }; |
| |
| dst = (uint8_t *)(rta+1); |
| memcpy(dst, val, len); |
| memset(dst+len, 0, ZNL_ALIGN(len) - len); |
| |
| return rta; |
| } |
| |
| struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val) |
| { |
| return znl_rta_push(zb, type, &val, sizeof(val)); |
| } |
| |
| struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type) |
| { |
| struct rtattr *rta; |
| |
| rta = znl_push(zb, sizeof(*rta)); |
| if (!rta) return NULL; |
| |
| *rta = (struct rtattr) { |
| .rta_type = type, |
| }; |
| return rta; |
| } |
| |
| void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta) |
| { |
| size_t len = zb->tail - (uint8_t*) rta; |
| size_t align = ZNL_ALIGN(len) - len; |
| |
| if (align) { |
| void *dst = zbuf_pushn(zb, align); |
| if (dst) memset(dst, 0, align); |
| } |
| rta->rta_len = len; |
| } |
| |
| struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload) |
| { |
| struct rtattr *rta; |
| size_t plen; |
| |
| rta = znl_pull(zb, sizeof(*rta)); |
| if (!rta) return NULL; |
| |
| if (rta->rta_len > sizeof(*rta)) { |
| plen = rta->rta_len - sizeof(*rta); |
| zbuf_init(payload, znl_pull(zb, plen), plen, plen); |
| } else { |
| zbuf_init(payload, NULL, 0, 0); |
| } |
| |
| return rta; |
| } |
| |
| int znl_open(int protocol, int groups) |
| { |
| struct sockaddr_nl addr; |
| int fd, buf = 128 * 1024; |
| |
| fd = socket(AF_NETLINK, SOCK_RAW, protocol); |
| if (fd < 0) |
| return -1; |
| |
| fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); |
| fcntl(fd, F_SETFD, FD_CLOEXEC); |
| if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) |
| goto error; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.nl_family = AF_NETLINK; |
| addr.nl_groups = groups; |
| if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) |
| goto error; |
| |
| return fd; |
| error: |
| close(fd); |
| return -1; |
| } |
| |