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=30e33584c4ac649cac0a833707b62dad00d6825e;hp=3a24cb885f36a5d8b51d118f4ae6e5448fb806ac;hb=cedc611207b6a14cdbaf1d73e12b62ee55cc53de;hpb=1403f45ab61d6f2026a3a7a06b52a536c1b7a3b3 diff --git a/src/libsystemd/sd-rtnl/rtnl-message.c b/src/libsystemd/sd-rtnl/rtnl-message.c index 3a24cb885..30e33584c 100644 --- a/src/libsystemd/sd-rtnl/rtnl-message.c +++ b/src/libsystemd/sd-rtnl/rtnl-message.c @@ -133,7 +133,8 @@ int sd_rtnl_message_route_set_scope(sd_rtnl_message *m, unsigned char scope) { } int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret, - uint16_t nlmsg_type, unsigned char rtm_family) { + uint16_t nlmsg_type, int rtm_family, + unsigned char rtm_protocol) { struct rtmsg *rtm; int r; @@ -146,7 +147,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); @@ -154,7 +155,7 @@ int sd_rtnl_message_new_route(sd_rtnl *rtnl, sd_rtnl_message **ret, rtm->rtm_scope = RT_SCOPE_UNIVERSE; rtm->rtm_type = RTN_UNICAST; rtm->rtm_table = RT_TABLE_MAIN; - rtm->rtm_protocol = RTPROT_BOOT; + rtm->rtm_protocol = rtm_protocol; return 0; } @@ -213,6 +214,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; @@ -259,7 +276,7 @@ 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) { +int sd_rtnl_message_addr_get_family(sd_rtnl_message *m, int *family) { struct ifaddrmsg *ifa; assert_return(m, -EINVAL); @@ -274,6 +291,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; @@ -321,20 +353,22 @@ int sd_rtnl_message_addr_get_ifindex(sd_rtnl_message *m, int *ifindex) { int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret, uint16_t nlmsg_type, int index, - unsigned char family) { + int family) { struct ifaddrmsg *ifa; 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); @@ -350,7 +384,7 @@ int sd_rtnl_message_new_addr(sd_rtnl *rtnl, sd_rtnl_message **ret, } int sd_rtnl_message_new_addr_update(sd_rtnl *rtnl, sd_rtnl_message **ret, - int index, unsigned char family) { + int index, int family) { int r; r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); @@ -431,27 +465,46 @@ int sd_rtnl_message_link_get_flags(sd_rtnl_message *m, unsigned *flags) { return 0; } +int sd_rtnl_message_link_get_type(sd_rtnl_message *m, unsigned *type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(type, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *type = ifi->ifi_type; + + return 0; +} + /* 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 +513,34 @@ 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); } + /* 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 +715,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 +733,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 +764,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 +801,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 +815,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 +977,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, @@ -997,26 +1056,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, @@ -1074,51 +1113,33 @@ 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; - _cleanup_free_ void *buffer = 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 need, len; - int r, ret = 0; - - assert(rtnl); - - r = message_receive_need(rtnl, &need); - if (r < 0) - return r; - - buffer = malloc0(need); - if (!buffer) - return -ENOMEM; + int r; - iov.iov_base = buffer; - iov.iov_len = need; + assert(fd >= 0); + assert(iov); - r = recvmsg(rtnl->fd, &msg, 0); - if (r < 0) + r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); + if (r < 0) { /* no data */ + if (errno == ENOBUFS) + log_debug("rtnl: kernel receive buffer overrun"); + return (errno == EAGAIN) ? 0 : -errno; - else if (r == 0) + } else if (r == 0) /* connection was closed by the kernel */ return -ECONNRESET; - else - len = (size_t)r; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && @@ -1127,10 +1148,15 @@ int socket_read_message(sd_rtnl *rtnl) { struct ucred *ucred = (void *)CMSG_DATA(cmsg); /* from the kernel */ - if (ucred->uid == 0 && ucred->pid == 0) { + if (ucred->uid == 0 && ucred->pid == 0) auth = true; - break; - } + } 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; } } @@ -1138,21 +1164,87 @@ 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)) { + 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, + len, sizeof(uint8_t))) + return -ENOMEM; + + iov.iov_base = rtnl->rbuffer; + iov.iov_len = rtnl->rbuffer_allocated; + + /* read the pending message */ + r = socket_recv_message(rtnl->fd, &iov, &group, false); + if (r <= 0) + return r; + else + len = (size_t)r; + + if (len > rtnl->rbuffer_allocated) + /* message did not fit in read buffer */ + return -EIO; + + if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = 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; + } + } + } + + 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); @@ -1181,31 +1273,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) {