+
+/*
+ * rtnetlink_msg - send rtnetlink message, receive response
+ * and return received error code:
+ * 0 - success
+ * positive value - error during sending / receiving message
+ * negative value - rtnetlink responce error code
+ */
+static int rtnetlink_msg(const char *desc, int *shared_fd, void *nlreq, size_t nlreq_len, void *nlresp_data, size_t *nlresp_size, unsigned nlresp_type)
+{
+ struct nlresp_hdr {
+ struct nlmsghdr nlh;
+ struct nlmsgerr nlerr;
+ } nlresp_hdr;
+ struct sockaddr_nl nladdr;
+ struct iovec iov[2];
+ struct msghdr msg;
+ ssize_t nlresp_len;
+ int one;
+ int fd;
+
+ if (shared_fd && *shared_fd >= 0) {
+ fd = *shared_fd;
+ } else {
+ fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd < 0) {
+ error("rtnetlink_msg: socket(NETLINK_ROUTE): %m (line %d)", __LINE__);
+ return 1;
+ }
+
+ /*
+ * Tell kernel to not send to us payload of acknowledgment error message.
+ * NETLINK_CAP_ACK option is supported since Linux kernel version 4.3 and
+ * older kernel versions always send full payload in acknowledgment netlink
+ * message. We ignore payload of this message as we need only error code,
+ * to check if our set remote peer address request succeeded or failed.
+ * So ignore return value from the following setsockopt() call as setting
+ * option NETLINK_CAP_ACK means for us just a kernel hint / optimization.
+ */
+ one = 1;
+ setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
+ error("rtnetlink_msg: bind(AF_NETLINK): %m (line %d)", __LINE__);
+ close(fd);
+ return 1;
+ }
+
+ if (shared_fd)
+ *shared_fd = fd;
+ }
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ memset(&iov[0], 0, sizeof(iov[0]));
+ iov[0].iov_base = nlreq;
+ iov[0].iov_len = nlreq_len;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov[0];
+ msg.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msg, 0) < 0) {
+ error("rtnetlink_msg: sendmsg(%s): %m (line %d)", desc, __LINE__);
+ if (!shared_fd)
+ close(fd);
+ return 1;
+ }
+
+ memset(iov, 0, sizeof(iov));
+ iov[0].iov_base = &nlresp_hdr;
+ if (nlresp_size && *nlresp_size > sizeof(nlresp_hdr)) {
+ iov[0].iov_len = offsetof(struct nlresp_hdr, nlerr);
+ iov[1].iov_base = nlresp_data;
+ iov[1].iov_len = *nlresp_size;
+ } else {
+ iov[0].iov_len = sizeof(nlresp_hdr);
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = (nlresp_size && *nlresp_size > sizeof(nlresp_hdr)) ? 2 : 1;
+
+ nlresp_len = recvmsg(fd, &msg, 0);
+
+ if (!shared_fd)
+ close(fd);
+
+ if (nlresp_len < 0) {
+ error("rtnetlink_msg: recvmsg(%s): %m (line %d)", desc, __LINE__);
+ return 1;
+ }
+
+ if (nladdr.nl_family != AF_NETLINK) {
+ error("rtnetlink_msg: recvmsg(%s): Not a netlink packet (line %d)", desc, __LINE__);
+ return 1;
+ }
+
+ if (!nlresp_size) {
+ if ((size_t)nlresp_len < sizeof(nlresp_hdr) || nlresp_hdr.nlh.nlmsg_len < sizeof(nlresp_hdr)) {
+ error("rtnetlink_msg: recvmsg(%s): Acknowledgment netlink packet too short (line %d)", desc, __LINE__);
+ return 1;
+ }
+
+ /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */
+ if (nlresp_hdr.nlh.nlmsg_type != NLMSG_ERROR) {
+ error("rtnetlink_msg: recvmsg(%s): Not an acknowledgment netlink packet (line %d)", desc, __LINE__);
+ return 1;
+ }
+ }
+
+ if (nlresp_size) {
+ if (*nlresp_size > sizeof(nlresp_hdr))
+ memcpy((unsigned char *)&nlresp_hdr + offsetof(struct nlresp_hdr, nlerr), nlresp_data, sizeof(nlresp_hdr.nlerr));
+ else
+ memcpy(nlresp_data, (unsigned char *)&nlresp_hdr + offsetof(struct nlresp_hdr, nlerr), *nlresp_size);
+ }
+
+ /* error == 0 indicates success, negative value is errno code */
+ if (nlresp_hdr.nlh.nlmsg_type == NLMSG_ERROR && nlresp_hdr.nlerr.error)
+ return nlresp_hdr.nlerr.error;
+
+ if (nlresp_size) {
+ if (nlresp_hdr.nlh.nlmsg_type != nlresp_type) {
+ error("rtnetlink_msg: recvmsg(%s): Not a netlink packet of type 0x%x (line %d)", desc, nlresp_type, __LINE__);
+ return 1;
+ }
+ *nlresp_size = nlresp_len - offsetof(struct nlresp_hdr, nlerr);
+ }
+
+ return 0;
+}
+