| /* | |
| * Copyright (C) 2011 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* NOTICE: This is a clean room re-implementation of libnl */ | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <sys/socket.h> | |
| #include "netlink-types.h" | |
| #define NL_BUFFER_SZ (32768U) | |
| /* Checks message for completeness and sends it out */ | |
| int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg) | |
| { | |
| struct nlmsghdr *nlh = msg->nm_nlh; | |
| struct timeval tv; | |
| if (!nlh) { | |
| int errsv = errno; | |
| fprintf(stderr, "Netlink message header is NULL!\n"); | |
| return -errsv; | |
| } | |
| /* Complete the nl_msg header */ | |
| if (gettimeofday(&tv, NULL)) | |
| nlh->nlmsg_seq = 1; | |
| else | |
| nlh->nlmsg_seq = (int) tv.tv_sec; | |
| nlh->nlmsg_pid = sk->s_local.nl_pid; | |
| nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_ACK; | |
| return nl_send(sk, msg); | |
| } | |
| /* Receives a netlink message, allocates a buffer in *buf and stores | |
| * the message content. The peer's netlink address is stored in | |
| * *nla. The caller is responsible for freeing the buffer allocated in | |
| * *buf if a positive value is returned. Interrupted system calls are | |
| * handled by repeating the read. The input buffer size is determined | |
| * by peeking before the actual read is done */ | |
| int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla, \ | |
| unsigned char **buf, struct ucred **creds) | |
| { | |
| int rc = -1; | |
| int sk_flags; | |
| int RECV_BUF_SIZE = getpagesize(); | |
| int errsv; | |
| struct iovec recvmsg_iov; | |
| struct msghdr msg; | |
| /* Allocate buffer */ | |
| *buf = (unsigned char *) malloc(RECV_BUF_SIZE); | |
| if (!(*buf)) { | |
| rc = -ENOMEM; | |
| goto fail; | |
| } | |
| /* Prepare to receive message */ | |
| recvmsg_iov.iov_base = *buf; | |
| recvmsg_iov.iov_len = RECV_BUF_SIZE; | |
| msg.msg_name = (void *) &sk->s_peer; | |
| msg.msg_namelen = sizeof(sk->s_peer); | |
| msg.msg_iov = &recvmsg_iov; | |
| msg.msg_iovlen = 1; | |
| msg.msg_control = NULL; | |
| msg.msg_controllen = 0; | |
| msg.msg_flags = 0; | |
| /* Make non blocking and then restore previous setting */ | |
| sk_flags = fcntl(sk->s_fd, F_GETFL, 0); | |
| fcntl(sk->s_fd, F_SETFL, O_NONBLOCK); | |
| rc = recvmsg(sk->s_fd, &msg, 0); | |
| errsv = errno; | |
| fcntl(sk->s_fd, F_SETFL, sk_flags); | |
| if (rc < 0) { | |
| rc = -errsv; | |
| free(*buf); | |
| *buf = NULL; | |
| } | |
| fail: | |
| return rc; | |
| } | |
| /* Receive a set of messages from a netlink socket */ | |
| /* NOTE: Does not currently support callback replacements!!! */ | |
| int nl_recvmsgs(struct nl_sock *sk, struct nl_cb *cb) | |
| { | |
| struct sockaddr_nl nla; | |
| struct ucred *creds; | |
| int rc, cb_rc = NL_OK, done = 0; | |
| do { | |
| unsigned char *buf; | |
| int i, rem, flags; | |
| struct nlmsghdr *nlh; | |
| struct nlmsgerr *nlme; | |
| struct nl_msg *msg; | |
| done = 0; | |
| rc = nl_recv(sk, &nla, &buf, &creds); | |
| if (rc < 0) | |
| break; | |
| nlmsg_for_each_msg(nlh, (struct nlmsghdr *) buf, rc, rem) { | |
| if (rc <= 0 || cb_rc == NL_STOP) | |
| break; | |
| /* Check for callbacks */ | |
| msg = (struct nl_msg *) malloc(sizeof(struct nl_msg)); | |
| memset(msg, 0, sizeof(*msg)); | |
| msg->nm_nlh = nlh; | |
| /* Check netlink message type */ | |
| switch (msg->nm_nlh->nlmsg_type) { | |
| case NLMSG_ERROR: /* Used for ACK too */ | |
| /* Certainly we should be doing some | |
| * checking here to make sure this | |
| * message is intended for us */ | |
| nlme = nlmsg_data(msg->nm_nlh); | |
| if (nlme->error == 0) | |
| msg->nm_nlh->nlmsg_flags |= NLM_F_ACK; | |
| rc = nlme->error; | |
| cb_rc = cb->cb_err(&nla, nlme, cb->cb_err_arg); | |
| nlme = NULL; | |
| break; | |
| case NLMSG_DONE: | |
| done = 1; | |
| case NLMSG_OVERRUN: | |
| case NLMSG_NOOP: | |
| default: | |
| break; | |
| }; | |
| for (i = 0; i <= NL_CB_TYPE_MAX; i++) { | |
| if (cb->cb_set[i]) { | |
| switch (i) { | |
| case NL_CB_VALID: | |
| if (rc > 0) | |
| cb_rc = cb->cb_set[i](msg, cb->cb_args[i]); | |
| break; | |
| case NL_CB_FINISH: | |
| if ((msg->nm_nlh->nlmsg_flags & NLM_F_MULTI) && | |
| (msg->nm_nlh->nlmsg_type & NLMSG_DONE)) | |
| cb_rc = cb->cb_set[i](msg, cb->cb_args[i]); | |
| break; | |
| case NL_CB_ACK: | |
| if (msg->nm_nlh->nlmsg_flags & NLM_F_ACK) | |
| cb_rc = cb->cb_set[i](msg, cb->cb_args[i]); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| free(msg); | |
| if (done) | |
| break; | |
| } | |
| free(buf); | |
| buf = NULL; | |
| if (done) | |
| break; | |
| } while (rc > 0 && cb_rc != NL_STOP); | |
| success: | |
| fail: | |
| return rc; | |
| } | |
| /* Send raw data over netlink socket */ | |
| int nl_send(struct nl_sock *sk, struct nl_msg *msg) | |
| { | |
| struct nlmsghdr *nlh = nlmsg_hdr(msg); | |
| struct iovec msg_iov; | |
| /* Create IO vector with Netlink message */ | |
| msg_iov.iov_base = nlh; | |
| msg_iov.iov_len = nlh->nlmsg_len; | |
| return nl_send_iovec(sk, msg, &msg_iov, 1); | |
| } | |
| /* Send netlink message */ | |
| int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, | |
| struct iovec *iov, unsigned iovlen) | |
| { | |
| int rc; | |
| /* Socket message */ | |
| struct msghdr mh = { | |
| .msg_name = (void *) &sk->s_peer, | |
| .msg_namelen = sizeof(sk->s_peer), | |
| .msg_iov = iov, | |
| .msg_iovlen = iovlen, | |
| .msg_control = NULL, | |
| .msg_controllen = 0, | |
| .msg_flags = 0 | |
| }; | |
| /* Send message and verify sent */ | |
| rc = nl_sendmsg(sk, (struct nl_msg *) &mh, 0); | |
| if (rc < 0) | |
| fprintf(stderr, "Error sending netlink message: %d\n", errno); | |
| return rc; | |
| } | |
| /* Send netlink message with control over sendmsg() message header */ | |
| int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr) | |
| { | |
| return sendmsg(sk->s_fd, (struct msghdr *) msg, (int) hdr); | |
| } | |
| /* Create and connect netlink socket */ | |
| int nl_connect(struct nl_sock *sk, int protocol) | |
| { | |
| struct sockaddr addr; | |
| socklen_t addrlen; | |
| int rc; | |
| /* Create RX socket */ | |
| sk->s_fd = socket(PF_NETLINK, SOCK_RAW, protocol); | |
| if (sk->s_fd < 0) | |
| return -errno; | |
| /* Set size of RX and TX buffers */ | |
| if (nl_socket_set_buffer_size(sk, NL_BUFFER_SZ, NL_BUFFER_SZ) < 0) | |
| return -errno; | |
| /* Bind RX socket */ | |
| rc = bind(sk->s_fd, (struct sockaddr *)&sk->s_local, \ | |
| sizeof(sk->s_local)); | |
| if (rc < 0) | |
| return -errno; | |
| addrlen = sizeof(addr); | |
| getsockname(sk->s_fd, &addr, &addrlen); | |
| return 0; | |
| } |