chiark / gitweb /
sd-rtnl: message_addr - allow dumping of messages
[elogind.git] / src / libsystemd / sd-rtnl / rtnl-message.c
index c9f348704d8686628de1b89184c066f5c7f5ed8f..1afba8f0bb1ed19d66e467c77e41032b72d4055c 100644 (file)
@@ -259,6 +259,66 @@ int sd_rtnl_message_addr_set_scope(sd_rtnl_message *m, unsigned char scope) {
         return 0;
 }
 
+int sd_rtnl_message_addr_get_family(sd_rtnl_message *m, unsigned char *family) {
+        struct ifaddrmsg *ifa;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL);
+        assert_return(family, -EINVAL);
+
+        ifa = NLMSG_DATA(m->hdr);
+
+        *family = ifa->ifa_family;
+
+        return 0;
+}
+
+int sd_rtnl_message_addr_get_scope(sd_rtnl_message *m, unsigned char *scope) {
+        struct ifaddrmsg *ifa;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL);
+        assert_return(scope, -EINVAL);
+
+        ifa = NLMSG_DATA(m->hdr);
+
+        *scope = ifa->ifa_scope;
+
+        return 0;
+}
+
+int sd_rtnl_message_addr_get_flags(sd_rtnl_message *m, unsigned char *flags) {
+        struct ifaddrmsg *ifa;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL);
+        assert_return(flags, -EINVAL);
+
+        ifa = NLMSG_DATA(m->hdr);
+
+        *flags = ifa->ifa_flags;
+
+        return 0;
+}
+
+int sd_rtnl_message_addr_get_ifindex(sd_rtnl_message *m, int *ifindex) {
+        struct ifaddrmsg *ifa;
+
+        assert_return(m, -EINVAL);
+        assert_return(m->hdr, -EINVAL);
+        assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL);
+        assert_return(ifindex, -EINVAL);
+
+        ifa = NLMSG_DATA(m->hdr);
+
+        *ifindex = ifa->ifa_index;
+
+        return 0;
+}
+
 int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret,
                              uint16_t nlmsg_type, int index,
                              unsigned char family) {
@@ -266,15 +326,17 @@ int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret,
         int r;
 
         assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL);
-        assert_return(index > 0, -EINVAL);
-        assert_return(family == AF_INET || family == AF_INET6, -EINVAL);
+        assert_return((nlmsg_type == RTM_GETADDR && index == 0) ||
+                      index > 0, -EINVAL);
+        assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) ||
+                      family == AF_INET || family == AF_INET6, -EINVAL);
         assert_return(ret, -EINVAL);
 
         r = message_new(rtnl, ret, nlmsg_type);
         if (r < 0)
                 return r;
 
-        if (nlmsg_type == RTM_GETADDR && family == AF_INET)
+        if (nlmsg_type == RTM_GETADDR)
                 (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP;
 
         ifa = NLMSG_DATA((*ret)->hdr);
@@ -318,6 +380,8 @@ sd_rtnl_message *sd_rtnl_message_unref(sd_rtnl_message *m) {
                 for (i = 0; i <= m->n_containers; i++)
                         free(m->rta_offset_tb[i]);
 
+                sd_rtnl_message_unref(m->next);
+
                 free(m);
         }
 
@@ -935,26 +999,6 @@ int sd_rtnl_message_get_errno(sd_rtnl_message *m) {
         return err->error;
 }
 
-static int message_receive_need(sd_rtnl *rtnl, size_t *need) {
-        assert(rtnl);
-        assert(need);
-
-        /* ioctl(rtnl->fd, FIONREAD, &need)
-           Does not appear to work on netlink sockets. libnl uses
-           MSG_PEEK instead. I don't know if that is worth the
-           extra roundtrip.
-
-           For now we simply use the maximum message size the kernel
-           may use (NLMSG_GOODSIZE), and then realloc to the actual
-           size after reading the message (hence avoiding huge memory
-           usage in case many small messages are kept around) */
-        *need = page_size();
-        if (*need > 8192UL)
-                *need = 8192UL;
-
-        return 0;
-}
-
 int rtnl_message_parse(sd_rtnl_message *m,
                        size_t **rta_offset_tb,
                        unsigned short *rta_tb_size,
@@ -1018,7 +1062,8 @@ int socket_write_message(sd_rtnl *nl, sd_rtnl_message *m) {
  * On failure, a negative error code is returned.
  */
 int socket_read_message(sd_rtnl *rtnl) {
-        _cleanup_free_ void *buffer = NULL;
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *first = NULL;
+        sd_rtnl_message *previous = NULL;
         uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred))];
         struct iovec iov = {};
         struct msghdr msg = {
@@ -1030,23 +1075,37 @@ int socket_read_message(sd_rtnl *rtnl) {
         struct cmsghdr *cmsg;
         bool auth = false;
         struct nlmsghdr *new_msg;
-        size_t need, len;
+        size_t len;
         int r, ret = 0;
 
         assert(rtnl);
+        assert(rtnl->rbuffer);
+
+        iov.iov_base = rtnl->rbuffer;
+        iov.iov_len = rtnl->rbuffer_allocated;
 
-        r = message_receive_need(rtnl, &need);
+        /* peek at the pending message header to get the message size */
+        r = recvmsg(rtnl->fd, &msg, MSG_PEEK);
         if (r < 0)
-                return r;
+                /* no data */
+                return (errno == EAGAIN) ? 0 : -errno;
+        else if (r == 0)
+                /* connection was closed by the kernel */
+                return -ECONNRESET;
+        else if ((size_t)r < sizeof(struct nlmsghdr))
+                return -EIO;
 
-        buffer = malloc0(need);
-        if (!buffer)
+        /* make room for the pending message */
+        if (!greedy_realloc((void **)&rtnl->rbuffer,
+                            &rtnl->rbuffer_allocated,
+                            rtnl->rbuffer->nlmsg_len,
+                            sizeof(uint8_t)))
                 return -ENOMEM;
 
-        iov.iov_base = buffer;
-        iov.iov_len = need;
+        iov.iov_base = rtnl->rbuffer;
+        iov.iov_len = rtnl->rbuffer_allocated;
 
-        r = recvmsg(rtnl->fd, &msg, 0);
+        r = recvmsg(rtnl->fd, &msg, MSG_TRUNC);
         if (r < 0)
                 /* no data */
                 return (errno == EAGAIN) ? 0 : -errno;
@@ -1056,6 +1115,10 @@ int socket_read_message(sd_rtnl *rtnl) {
         else
                 len = (size_t)r;
 
+        if (len > rtnl->rbuffer_allocated)
+                /* message did not fit in read buffer */
+                return -EIO;
+
         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                 if (cmsg->cmsg_level == SOL_SOCKET &&
                     cmsg->cmsg_type == SCM_CREDENTIALS &&
@@ -1074,7 +1137,7 @@ int socket_read_message(sd_rtnl *rtnl) {
                 /* not from the kernel, ignore */
                 return 0;
 
-        for (new_msg = buffer; NLMSG_OK(new_msg, len); new_msg = NLMSG_NEXT(new_msg, len)) {
+        for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len); new_msg = NLMSG_NEXT(new_msg, len)) {
                 _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL;
                 const NLType *nl_type;
 
@@ -1086,6 +1149,10 @@ int socket_read_message(sd_rtnl *rtnl) {
                 if (new_msg->nlmsg_type == NLMSG_NOOP)
                         continue;
 
+                /* finished reading multi-part message */
+                if (new_msg->nlmsg_type == NLMSG_DONE)
+                        break;
+
                 /* check that we support this message type */
                 r = type_system_get_type(NULL, &nl_type, new_msg->nlmsg_type);
                 if (r < 0) {
@@ -1113,21 +1180,30 @@ int socket_read_message(sd_rtnl *rtnl) {
                 if (r < 0)
                         return r;
 
-                r = rtnl_rqueue_make_room(rtnl);
-                if (r < 0)
-                        return r;
+                if (!first)
+                        first = m;
+                else {
+                        assert(previous);
 
-                rtnl->rqueue[rtnl->rqueue_size ++] = m;
+                        previous->next = m;
+                }
+                previous = m;
                 m = NULL;
+
                 ret += new_msg->nlmsg_len;
 
-                /* reached end of multi-part message, or not a multi-part
-                   message at all */
-                if (new_msg->nlmsg_type == NLMSG_DONE ||
-                    !(new_msg->nlmsg_flags & NLM_F_MULTI))
+                /* not a multi-part message, so stop reading*/
+                if (!(new_msg->nlmsg_flags & NLM_F_MULTI))
                         break;
         }
 
+        r = rtnl_rqueue_make_room(rtnl);
+        if (r < 0)
+                return r;
+
+        rtnl->rqueue[rtnl->rqueue_size ++] = first;
+        first = NULL;
+
         return ret;
 }
 
@@ -1189,3 +1265,9 @@ void rtnl_message_seal(sd_rtnl_message *m) {
 
         m->sealed = true;
 }
+
+sd_rtnl_message *sd_rtnl_message_next(sd_rtnl_message *m) {
+        assert_return(m, NULL);
+
+        return m->next;
+}