chiark / gitweb /
sd-rtnl: multi-part message - store as linked-list rather than independent messages
[elogind.git] / src / libsystemd / sd-rtnl / rtnl-message.c
index 4c746f9d8afe9294233af1777f572b3145d62413..3a24cb885f36a5d8b51d118f4ae6e5448fb806ac 100644 (file)
 #define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->container_offsets[i]) : NULL)
 #define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers ++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr;
 
-static int message_new_empty(sd_rtnl *rtnl, sd_rtnl_message **ret, size_t initial_size) {
+static int message_new_empty(sd_rtnl *rtnl, sd_rtnl_message **ret) {
         sd_rtnl_message *m;
 
         assert_return(ret, -EINVAL);
-        assert_return(initial_size >= sizeof(struct nlmsghdr), -EINVAL);
 
         /* Note that 'rtnl' is curretly unused, if we start using it internally
            we must take care to avoid problems due to mutual references between
@@ -57,15 +56,8 @@ static int message_new_empty(sd_rtnl *rtnl, sd_rtnl_message **ret, size_t initia
         if (!m)
                 return -ENOMEM;
 
-        m->hdr = malloc0(initial_size);
-        if (!m->hdr) {
-                free(m);
-                return -ENOMEM;
-        }
-
         m->n_ref = REFCNT_INIT;
 
-        m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
         m->sealed = false;
 
         *ret = m;
@@ -74,6 +66,7 @@ static int message_new_empty(sd_rtnl *rtnl, sd_rtnl_message **ret, size_t initia
 }
 
 int message_new(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t type) {
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL;
         const NLType *nl_type;
         size_t size;
         int r;
@@ -84,15 +77,25 @@ int message_new(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t type) {
 
         assert(nl_type->type == NLA_NESTED);
 
-        size = NLMSG_SPACE(nl_type->size);
-
-        r = message_new_empty(rtnl, ret, size);
+        r = message_new_empty(rtnl, &m);
         if (r < 0)
                 return r;
 
-        (*ret)->container_type_system[0] = nl_type->type_system;
-        (*ret)->hdr->nlmsg_len = size;
-        (*ret)->hdr->nlmsg_type = type;
+        size = NLMSG_SPACE(nl_type->size);
+
+        assert(size >= sizeof(struct nlmsghdr));
+        m->hdr = malloc0(size);
+        if (!m->hdr)
+                return -ENOMEM;
+
+        m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+        m->container_type_system[0] = nl_type->type_system;
+        m->hdr->nlmsg_len = size;
+        m->hdr->nlmsg_type = type;
+
+        *ret = m;
+        m = NULL;
 
         return 0;
 }
@@ -256,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) {
@@ -286,6 +349,19 @@ int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret,
         return 0;
 }
 
+int sd_rtnl_message_new_addr_update(sd_rtnl *rtnl, sd_rtnl_message **ret,
+                             int index, unsigned char family) {
+        int r;
+
+        r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family);
+        if (r < 0)
+                return r;
+
+        (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE;
+
+        return 0;
+}
+
 sd_rtnl_message *sd_rtnl_message_ref(sd_rtnl_message *m) {
         if (m)
                 assert_se(REFCNT_INC(m->n_ref) >= 2);
@@ -302,6 +378,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);
         }
 
@@ -559,6 +637,24 @@ int sd_rtnl_message_append_ether_addr(sd_rtnl_message *m, unsigned short type, c
         return 0;
 }
 
+int sd_rtnl_message_append_cache_info(sd_rtnl_message *m, unsigned short type, const struct ifa_cacheinfo *info) {
+        int r;
+
+        assert_return(m, -EINVAL);
+        assert_return(!m->sealed, -EPERM);
+        assert_return(info, -EINVAL);
+
+        r = message_attribute_has_type(m, type, NLA_CACHE_INFO);
+        if (r < 0)
+                return r;
+
+        r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo));
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) {
         size_t size;
         int r;
@@ -741,6 +837,25 @@ int sd_rtnl_message_read_ether_addr(sd_rtnl_message *m, unsigned short type, str
         return 0;
 }
 
+int sd_rtnl_message_read_cache_info(sd_rtnl_message *m, unsigned short type, struct ifa_cacheinfo *info) {
+        int r;
+        void *attr_data;
+
+        r = message_attribute_has_type(m, type, NLA_CACHE_INFO);
+        if (r < 0)
+                return r;
+
+        r = rtnl_message_read_internal(m, type, &attr_data);
+        if (r < 0)
+                return r;
+        else if ((size_t)r < sizeof(struct ifa_cacheinfo))
+                return -EIO;
+
+        memcpy(info, attr_data, sizeof(struct ifa_cacheinfo));
+
+        return 0;
+}
+
 int sd_rtnl_message_read_in_addr(sd_rtnl_message *m, unsigned short type, struct in_addr *data) {
         int r;
         void *attr_data;
@@ -911,19 +1026,20 @@ int rtnl_message_parse(sd_rtnl_message *m,
         unsigned short type;
         size_t *tb;
 
-        tb = (size_t *) new0(size_t *, max);
+        tb = new0(size_t, max + 1);
         if(!tb)
                 return -ENOMEM;
 
-        *rta_tb_size = max;
+        *rta_tb_size = max + 1;
 
         for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) {
                 type = rta->rta_type;
 
-                if (type > max) {
-                        log_debug("rtnl: message parse - ignore out of range attribute type");
+                /* if the kernel is newer than the headers we used
+                   when building, we ignore out-of-range attributes
+                 */
+                if (type > max)
                         continue;
-                }
 
                 if (tb[type])
                         log_debug("rtnl: message parse - overwriting repeated attribute");
@@ -963,83 +1079,133 @@ int socket_write_message(sd_rtnl *nl, sd_rtnl_message *m) {
  * If nothing useful was received 0 is returned.
  * On failure, a negative error code is returned.
  */
-int socket_read_message(sd_rtnl *nl, sd_rtnl_message **ret) {
-        _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL;
-        const NLType *nl_type;
-        struct nlmsghdr *new_hdr;
-        union {
-                struct sockaddr sa;
-                struct sockaddr_nl nl;
-        } addr;
-        socklen_t addr_len;
+int socket_read_message(sd_rtnl *rtnl) {
+        _cleanup_rtnl_message_unref_ sd_rtnl_message *first = NULL;
+        sd_rtnl_message *previous = NULL;
+        _cleanup_free_ void *buffer = NULL;
+        uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred))];
+        struct iovec iov = {};
+        struct msghdr msg = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = cred_buffer,
+                .msg_controllen = sizeof(cred_buffer),
+        };
+        struct cmsghdr *cmsg;
+        bool auth = false;
+        struct nlmsghdr *new_msg;
         size_t need, len;
-        int r;
+        int r, ret = 0;
 
-        assert(nl);
-        assert(ret);
+        assert(rtnl);
 
-        r = message_receive_need(nl, &need);
+        r = message_receive_need(rtnl, &need);
         if (r < 0)
                 return r;
 
-        r = message_new_empty(nl, &m, need);
-        if (r < 0)
-                return r;
+        buffer = malloc0(need);
+        if (!buffer)
+                return -ENOMEM;
 
-        addr_len = sizeof(addr);
+        iov.iov_base = buffer;
+        iov.iov_len = need;
 
-        r = recvfrom(nl->fd, m->hdr, need,
-                        0, &addr.sa, &addr_len);
+        r = recvmsg(rtnl->fd, &msg, 0);
         if (r < 0)
-                return (errno == EAGAIN) ? 0 : -errno; /* no data */
+                /* no data */
+                return (errno == EAGAIN) ? 0 : -errno;
         else if (r == 0)
-                return -ECONNRESET; /* connection was closed by the kernel */
-        else if (addr_len != sizeof(addr.nl) ||
-                        addr.nl.nl_family != AF_NETLINK)
-                return -EIO; /* not a netlink message */
-        else if (addr.nl.nl_pid != 0)
-                return 0; /* not from the kernel */
-        else if ((size_t) r < sizeof(struct nlmsghdr) ||
-                        (size_t) r < m->hdr->nlmsg_len)
-                return -EIO; /* too small (we do accept too big though) */
-        else if (m->hdr->nlmsg_pid && m->hdr->nlmsg_pid != nl->sockaddr.nl.nl_pid)
-                return 0; /* not broadcast and not for us */
+                /* connection was closed by the kernel */
+                return -ECONNRESET;
         else
-                len = (size_t) r;
+                len = (size_t)r;
+
+        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+                if (cmsg->cmsg_level == SOL_SOCKET &&
+                    cmsg->cmsg_type == SCM_CREDENTIALS &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+                        struct ucred *ucred = (void *)CMSG_DATA(cmsg);
+
+                        /* from the kernel */
+                        if (ucred->uid == 0 && ucred->pid == 0) {
+                                auth = true;
+                                break;
+                        }
+                }
+        }
 
-        /* silently drop noop messages */
-        if (m->hdr->nlmsg_type == NLMSG_NOOP)
+        if (!auth)
+                /* not from the kernel, ignore */
                 return 0;
 
-        /* check that we support this message type */
-        r = type_system_get_type(NULL, &nl_type, m->hdr->nlmsg_type);
-        if (r < 0) {
-                if (r == -ENOTSUP)
-                        log_debug("sd-rtnl: ignored message with unknown type: %u",
-                                  m->hdr->nlmsg_type);
+        for (new_msg = buffer; 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;
 
-                return 0;
-        }
+                if (new_msg->nlmsg_pid && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid)
+                        /* not broadcast and not for us */
+                        continue;
 
-        /* check that the size matches the message type */
-        if (len < NLMSG_LENGTH(nl_type->size))
-                        return -EIO;
+                /* silently drop noop messages */
+                if (new_msg->nlmsg_type == NLMSG_NOOP)
+                        continue;
 
-        /* we probably allocated way too much memory, give it back */
-        new_hdr = realloc(m->hdr, len);
-        if (!new_hdr)
-                return -ENOMEM;
-        m->hdr = new_hdr;
+                /* 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) {
+                        if (r == -ENOTSUP)
+                                log_debug("sd-rtnl: ignored message with unknown type: %u",
+                                          new_msg->nlmsg_type);
+
+                        continue;
+                }
+
+                /* check that the size matches the message type */
+                if (new_msg->nlmsg_len < NLMSG_LENGTH(nl_type->size))
+                        continue;
+
+                r = message_new_empty(rtnl, &m);
+                if (r < 0)
+                        return r;
+
+                m->hdr = memdup(new_msg, new_msg->nlmsg_len);
+                if (!m->hdr)
+                        return -ENOMEM;
 
-        /* seal and parse the top-level message */
-        r = sd_rtnl_message_rewind(m);
+                /* seal and parse the top-level message */
+                r = sd_rtnl_message_rewind(m);
+                if (r < 0)
+                        return r;
+
+                if (!first)
+                        first = m;
+                else {
+                        assert(previous);
+
+                        previous->next = m;
+                }
+                previous = m;
+                m = NULL;
+
+                ret += new_msg->nlmsg_len;
+
+                /* 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;
 
-        *ret = m;
-        m = NULL;
+        rtnl->rqueue[rtnl->rqueue_size ++] = first;
+        first = NULL;
 
-        return len;
+        return ret;
 }
 
 int sd_rtnl_message_rewind(sd_rtnl_message *m) {
@@ -1084,7 +1250,8 @@ int sd_rtnl_message_rewind(sd_rtnl_message *m) {
                                        &m->rta_offset_tb[m->n_containers],
                                        &m->rta_tb_size[m->n_containers],
                                        type_system->max,
-                                       (char*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(type->size),
+                                       (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) +
+                                                        NLMSG_ALIGN(type->size)),
                                        NLMSG_PAYLOAD(m->hdr, type->size));
                 if (r < 0)
                         return r;
@@ -1099,3 +1266,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;
+}