X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Flibsystemd%2Fsd-rtnl%2Frtnl-message.c;h=c0a38e148b1ee6c8a1cb20349cee82f33f1f7c7f;hp=a93cb0c8e46459e3d2654ae128811a141f37ed71;hb=ca4e095ab9e970cb8fa472ae69ea1f0648041722;hpb=a88f77c406ea56160c1e5a4b39eab1de134abe40 diff --git a/src/libsystemd/sd-rtnl/rtnl-message.c b/src/libsystemd/sd-rtnl/rtnl-message.c index a93cb0c8e..c0a38e148 100644 --- a/src/libsystemd/sd-rtnl/rtnl-message.c +++ b/src/libsystemd/sd-rtnl/rtnl-message.c @@ -146,7 +146,7 @@ int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret, return r; if (nlmsg_type == RTM_NEWROUTE) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; rtm = NLMSG_DATA((*ret)->hdr); @@ -213,6 +213,22 @@ int sd_rtnl_message_new_link(sd_rtnl *rtnl, sd_rtnl_message **ret, return 0; } +int sd_rtnl_message_request_dump(sd_rtnl_message *m, int dump) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(m->hdr->nlmsg_type == RTM_GETLINK || + m->hdr->nlmsg_type == RTM_GETADDR || + m->hdr->nlmsg_type == RTM_GETROUTE, + -EINVAL); + + if (dump) + m->hdr->nlmsg_flags |= NLM_F_DUMP; + else + m->hdr->nlmsg_flags &= ~NLM_F_DUMP; + + return 0; +} + int sd_rtnl_message_addr_set_prefixlen(sd_rtnl_message *m, unsigned char prefixlen) { struct ifaddrmsg *ifa; @@ -274,6 +290,21 @@ int sd_rtnl_message_addr_get_family(sd_rtnl_message *m, unsigned char *family) { return 0; } +int sd_rtnl_message_addr_get_prefixlen(sd_rtnl_message *m, unsigned char *prefixlen) { + 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(prefixlen, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *prefixlen = ifa->ifa_prefixlen; + + return 0; +} + int sd_rtnl_message_addr_get_scope(sd_rtnl_message *m, unsigned char *scope) { struct ifaddrmsg *ifa; @@ -326,15 +357,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); @@ -434,24 +467,28 @@ int sd_rtnl_message_link_get_flags(sd_rtnl_message *m, unsigned *flags) { /* If successful the updated message will be correctly aligned, if unsuccessful the old message is untouched. */ static int add_rtattr(sd_rtnl_message *m, unsigned short type, const void *data, size_t data_length) { - uint32_t rta_length, message_length; + uint32_t rta_length; + size_t message_length, padding_length; struct nlmsghdr *new_hdr; struct rtattr *rta; char *padding; unsigned i; + int offset; assert(m); assert(m->hdr); assert(!m->sealed); assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); - assert(!data || data_length > 0); - assert(data || m->n_containers < RTNL_CONTAINER_DEPTH); + assert(!data || data_length); + + /* get offset of the new attribute */ + offset = m->hdr->nlmsg_len; /* get the size of the new rta attribute (with padding at the end) */ rta_length = RTA_LENGTH(data_length); /* get the new message size (with padding at the end) */ - message_length = m->hdr->nlmsg_len + RTA_ALIGN(rta_length); + message_length = offset + RTA_ALIGN(rta_length); /* realloc to fit the new attribute */ new_hdr = realloc(m->hdr, message_length); @@ -460,33 +497,35 @@ static int add_rtattr(sd_rtnl_message *m, unsigned short type, const void *data, m->hdr = new_hdr; /* get pointer to the attribute we are about to add */ - rta = (struct rtattr *) ((uint8_t *) m->hdr + m->hdr->nlmsg_len); + rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); /* if we are inside containers, extend them */ for (i = 0; i < m->n_containers; i++) - GET_CONTAINER(m, i)->rta_len += message_length - m->hdr->nlmsg_len; + GET_CONTAINER(m, i)->rta_len += message_length - offset; /* fill in the attribute */ rta->rta_type = type; rta->rta_len = rta_length; - if (!data) { - //TODO: simply return this value rather than check for !data - /* this is the start of a new container */ - m->container_offsets[m->n_containers ++] = m->hdr->nlmsg_len; - } else { + if (data) /* we don't deal with the case where the user lies about the type * and gives us too little data (so don't do that) - */ + */ padding = mempcpy(RTA_DATA(rta), data, data_length); - /* make sure also the padding at the end of the message is initialized */ - memzero(padding, - (uint8_t *) m->hdr + message_length - (uint8_t *) padding); + else { + /* if no data was passed, make sure we still initialize the padding + note that we can have data_length > 0 (used by some containers) */ + padding = RTA_DATA(rta); + data_length = 0; } + /* make sure also the padding at the end of the message is initialized */ + padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; + memzero(padding, padding_length); + /* update message size */ m->hdr->nlmsg_len = message_length; - return 0; + return offset; } static int message_attribute_has_type(sd_rtnl_message *m, uint16_t attribute_type, uint16_t data_type) { @@ -661,6 +700,7 @@ int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) { assert_return(m, -EINVAL); assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); r = message_attribute_has_type(m, type, NLA_NESTED); if (r < 0) @@ -678,6 +718,8 @@ int sd_rtnl_message_open_container(sd_rtnl_message *m, unsigned short type) { if (r < 0) return r; + m->container_offsets[m->n_containers ++] = r; + return 0; } @@ -707,6 +749,8 @@ int sd_rtnl_message_open_container_union(sd_rtnl_message *m, unsigned short type if (r < 0) return r; + m->container_offsets[m->n_containers ++] = r; + return 0; } @@ -742,7 +786,7 @@ int rtnl_message_read_internal(sd_rtnl_message *m, unsigned short type, void **d return RTA_PAYLOAD(rta); } -int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, char **data) { +int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, const char **data) { int r; void *attr_data; @@ -756,7 +800,7 @@ int sd_rtnl_message_read_string(sd_rtnl_message *m, unsigned short type, char ** else if (strnlen(attr_data, r) >= (size_t) r) return -EIO; - *data = (char *) attr_data; + *data = (const char *) attr_data; return 0; } @@ -918,7 +962,7 @@ int sd_rtnl_message_enter_container(sd_rtnl_message *m, unsigned short type) { return r; } else if (nl_type->type == NLA_UNION) { const NLTypeSystemUnion *type_system_union; - char *key; + const char *key; r = type_system_get_type_system_union(m->container_type_system[m->n_containers], &type_system_union, @@ -1054,62 +1098,99 @@ int socket_write_message(sd_rtnl *nl, sd_rtnl_message *m) { return k; } -/* On success, the number of bytes received is returned and *ret points to the received message - * which has a valid header and the correct size. - * If nothing useful was received 0 is returned. - * On failure, a negative error code is returned. - */ -int socket_read_message(sd_rtnl *rtnl) { - _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 = {}; +static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { + uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred)) + + CMSG_SPACE(sizeof(struct nl_pktinfo))]; struct msghdr msg = { - .msg_iov = &iov, + .msg_iov = iov, .msg_iovlen = 1, .msg_control = cred_buffer, .msg_controllen = sizeof(cred_buffer), }; struct cmsghdr *cmsg; + uint32_t group = 0; bool auth = false; - struct nlmsghdr *new_msg; - size_t len; - int r, ret = 0; + int r; - assert(rtnl); - assert(rtnl->rbuffer); + assert(fd >= 0); + assert(iov); - iov.iov_base = rtnl->rbuffer; - iov.iov_len = rtnl->rbuffer_allocated; - - /* peek at the pending message header to get the message size */ - r = recvmsg(rtnl->fd, &msg, MSG_PEEK); + r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); if (r < 0) /* 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; + + 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; + } else if (cmsg->cmsg_level == SOL_NETLINK && + cmsg->cmsg_type == NETLINK_PKTINFO && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { + struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); + + /* multi-cast group */ + group = pktinfo->group; + } + } + + if (!auth) + /* not from the kernel, ignore */ + return 0; + + if (group) + *_group = group; + + return r; +} + +/* On success, the number of bytes received is returned and *ret points to the received message + * which has a valid header and the correct size. + * If nothing useful was received 0 is returned. + * On failure, a negative error code is returned. + */ +int socket_read_message(sd_rtnl *rtnl) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *first = NULL; + struct iovec iov = {}; + uint32_t group = 0; + bool multi_part = false, done = false; + struct nlmsghdr *new_msg; + size_t len; + int r; + unsigned i = 0; + + assert(rtnl); + assert(rtnl->rbuffer); + assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr)); + + /* read nothing, just get the pending message size */ + r = socket_recv_message(rtnl->fd, &iov, &group, true); + if (r <= 0) + return r; + else + len = (size_t)r; /* make room for the pending message */ if (!greedy_realloc((void **)&rtnl->rbuffer, &rtnl->rbuffer_allocated, - rtnl->rbuffer->nlmsg_len, - sizeof(uint8_t))) + len, sizeof(uint8_t))) return -ENOMEM; iov.iov_base = rtnl->rbuffer; iov.iov_len = rtnl->rbuffer_allocated; - r = recvmsg(rtnl->fd, &msg, MSG_TRUNC); - if (r < 0) - /* no data */ - return (errno == EAGAIN) ? 0 : -errno; - else if (r == 0) - /* connection was closed by the kernel */ - return -ECONNRESET; + /* read the pending message */ + r = socket_recv_message(rtnl->fd, &iov, &group, false); + if (r <= 0) + return r; else len = (size_t)r; @@ -1117,39 +1198,35 @@ int socket_read_message(sd_rtnl *rtnl) { /* 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 && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { - struct ucred *ucred = (void *)CMSG_DATA(cmsg); + if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = true; - /* from the kernel */ - if (ucred->uid == 0 && ucred->pid == 0) { - auth = true; + for (i = 0; i < rtnl->rqueue_partial_size; i++) { + if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == + rtnl->rbuffer->nlmsg_seq) { + first = rtnl->rqueue_partial[i]; break; } } } - if (!auth) - /* not from the kernel, ignore */ - return 0; - 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; - if (new_msg->nlmsg_pid && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) + if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) /* not broadcast and not for us */ continue; - /* silently drop noop messages */ if (new_msg->nlmsg_type == NLMSG_NOOP) + /* silently drop noop messages */ continue; - /* finished reading multi-part message */ - if (new_msg->nlmsg_type == NLMSG_DONE) + if (new_msg->nlmsg_type == NLMSG_DONE) { + /* finished reading multi-part message */ + done = true; break; + } /* check that we support this message type */ r = type_system_get_type(NULL, &nl_type, new_msg->nlmsg_type); @@ -1178,31 +1255,52 @@ int socket_read_message(sd_rtnl *rtnl) { if (r < 0) return r; - if (!first) - first = m; - else { - assert(previous); - - previous->next = m; - } - previous = m; + /* push the message onto the multi-part message stack */ + if (first) + m->next = first; + first = m; m = NULL; + } - ret += new_msg->nlmsg_len; + if (len) + log_debug("sd-rtnl: discarding %zu bytes of incoming message", len); - /* not a multi-part message, so stop reading*/ - if (!(new_msg->nlmsg_flags & NLM_F_MULTI)) - break; - } + if (!first) + return 0; - r = rtnl_rqueue_make_room(rtnl); - if (r < 0) - return r; + if (!multi_part || done) { + /* we got a complete message, push it on the read queue */ + r = rtnl_rqueue_make_room(rtnl); + if (r < 0) + return r; - rtnl->rqueue[rtnl->rqueue_size ++] = first; - first = NULL; + rtnl->rqueue[rtnl->rqueue_size ++] = first; + first = NULL; - return ret; + if (multi_part && (i < rtnl->rqueue_partial_size)) { + /* remove the message form the partial read queue */ + memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, + sizeof(sd_rtnl_message*) * (rtnl->rqueue_partial_size - i - 1)); + rtnl->rqueue_partial_size --; + } + + return 1; + } else { + /* we only got a partial multi-part message, push it on the + partial read queue */ + if (i < rtnl->rqueue_partial_size) { + rtnl->rqueue_partial[i] = first; + } else { + r = rtnl_rqueue_partial_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue_partial[rtnl->rqueue_partial_size ++] = first; + } + first = NULL; + + return 0; + } } int sd_rtnl_message_rewind(sd_rtnl_message *m) {