| /* |
| * 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 <unistd.h> |
| #include <stdio.h> |
| #include <sys/time.h> |
| #include <sys/socket.h> |
| #include <linux/netlink.h> |
| #include <netlink/genl/ctrl.h> |
| #include <netlink/genl/family.h> |
| #include "netlink-types.h" |
| |
| /* Get head of attribute data. */ |
| struct nlattr *genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen) |
| { |
| return (struct nlattr *) \ |
| ((char *) gnlh + GENL_HDRLEN + NLMSG_ALIGN(hdrlen)); |
| |
| } |
| |
| /* Get length of attribute data. */ |
| int genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen) |
| { |
| struct nlattr *nla; |
| struct nlmsghdr *nlh; |
| |
| nla = genlmsg_attrdata(gnlh, hdrlen); |
| nlh = (struct nlmsghdr *) ((char *) gnlh - NLMSG_HDRLEN); |
| return (char *) nlmsg_tail(nlh) - (char *) nla; |
| } |
| |
| /* Add generic netlink header to netlink message. */ |
| void *genlmsg_put(struct nl_msg *msg, uint32_t pid, uint32_t seq, int family, |
| int hdrlen, int flags, uint8_t cmd, uint8_t version) |
| { |
| int new_size; |
| struct nlmsghdr *nlh; |
| struct timeval tv; |
| struct genlmsghdr *gmh; |
| |
| /* Make sure nl_msg has enough space */ |
| new_size = NLMSG_HDRLEN + GENL_HDRLEN + hdrlen; |
| if ((sizeof(struct nl_msg) + new_size) > msg->nm_size) |
| goto fail; |
| |
| /* Fill in netlink header */ |
| nlh = msg->nm_nlh; |
| nlh->nlmsg_len = new_size; |
| nlh->nlmsg_type = family; |
| nlh->nlmsg_pid = getpid(); |
| nlh->nlmsg_flags = flags | NLM_F_REQUEST | NLM_F_ACK; |
| |
| /* Get current time for sequence number */ |
| if (gettimeofday(&tv, NULL)) |
| nlh->nlmsg_seq = 1; |
| else |
| nlh->nlmsg_seq = (int) tv.tv_sec; |
| |
| /* Setup genlmsghdr in new message */ |
| gmh = (struct genlmsghdr *) ((char *)nlh + NLMSG_HDRLEN); |
| gmh->cmd = (__u8) cmd; |
| gmh->version = version; |
| |
| return gmh; |
| fail: |
| return NULL; |
| |
| } |
| |
| /* Socket has already been alloced to connect it to kernel? */ |
| int genl_connect(struct nl_sock *sk) |
| { |
| return nl_connect(sk, NETLINK_GENERIC); |
| |
| } |
| |
| int genl_ctrl_alloc_cache(struct nl_sock *sock, struct nl_cache **result) |
| { |
| int rc = -1; |
| int nl80211_genl_id = -1; |
| char sendbuf[sizeof(struct nlmsghdr)+sizeof(struct genlmsghdr)]; |
| struct nlmsghdr nlmhdr; |
| struct genlmsghdr gmhhdr; |
| struct iovec sendmsg_iov; |
| struct msghdr msg; |
| int num_char; |
| const int RECV_BUF_SIZE = getpagesize(); |
| char *recvbuf; |
| struct iovec recvmsg_iov; |
| int nl80211_flag = 0, nlm_f_multi = 0, nlmsg_done = 0; |
| struct nlmsghdr *nlh; |
| |
| /* REQUEST GENERIC NETLINK FAMILY ID */ |
| /* Message buffer */ |
| nlmhdr.nlmsg_len = sizeof(sendbuf); |
| nlmhdr.nlmsg_type = NETLINK_GENERIC; |
| nlmhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP; |
| nlmhdr.nlmsg_seq = sock->s_seq_next; |
| nlmhdr.nlmsg_pid = sock->s_local.nl_pid; |
| |
| /* Generic netlink header */ |
| memset(&gmhhdr, 0, sizeof(gmhhdr)); |
| gmhhdr.cmd = CTRL_CMD_GETFAMILY; |
| gmhhdr.version = CTRL_ATTR_FAMILY_ID; |
| |
| /* Combine netlink and generic netlink headers */ |
| memcpy(&sendbuf[0], &nlmhdr, sizeof(nlmhdr)); |
| memcpy(&sendbuf[0]+sizeof(nlmhdr), &gmhhdr, sizeof(gmhhdr)); |
| |
| /* Create IO vector with Netlink message */ |
| sendmsg_iov.iov_base = &sendbuf; |
| sendmsg_iov.iov_len = sizeof(sendbuf); |
| |
| /* Socket message */ |
| msg.msg_name = (void *) &sock->s_peer; |
| msg.msg_namelen = sizeof(sock->s_peer); |
| msg.msg_iov = &sendmsg_iov; |
| msg.msg_iovlen = 1; /* Only sending one iov */ |
| msg.msg_control = NULL; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| /* Send message and verify sent */ |
| num_char = sendmsg(sock->s_fd, &msg, 0); |
| if (num_char == -1) |
| return -errno; |
| |
| /* RECEIVE GENL CMD RESPONSE */ |
| |
| /* Create receive iov buffer */ |
| recvbuf = (char *) malloc(RECV_BUF_SIZE); |
| |
| /* Attach to iov */ |
| recvmsg_iov.iov_base = recvbuf; |
| recvmsg_iov.iov_len = RECV_BUF_SIZE; |
| |
| msg.msg_iov = &recvmsg_iov; |
| msg.msg_iovlen = 1; |
| |
| /***************************************************************/ |
| /* Receive message. If multipart message, keep receiving until */ |
| /* message type is NLMSG_DONE */ |
| /***************************************************************/ |
| |
| do { |
| |
| int recvmsg_len, nlmsg_rem; |
| |
| /* Receive message */ |
| memset(recvbuf, 0, RECV_BUF_SIZE); |
| recvmsg_len = recvmsg(sock->s_fd, &msg, 0); |
| |
| /* Make sure receive successful */ |
| if (recvmsg_len < 0) { |
| rc = -errno; |
| goto error_recvbuf; |
| } |
| |
| /* Parse nlmsghdr */ |
| nlmsg_for_each_msg(nlh, (struct nlmsghdr *) recvbuf, \ |
| recvmsg_len, nlmsg_rem) { |
| struct nlattr *nla; |
| int nla_rem; |
| |
| /* Check type */ |
| switch (nlh->nlmsg_type) { |
| case NLMSG_DONE: |
| goto return_genl_id; |
| break; |
| case NLMSG_ERROR: |
| |
| /* Should check nlmsgerr struct received */ |
| fprintf(stderr, "Receive message error\n"); |
| goto error_recvbuf; |
| case NLMSG_OVERRUN: |
| fprintf(stderr, "Receive data partly lost\n"); |
| goto error_recvbuf; |
| case NLMSG_MIN_TYPE: |
| case NLMSG_NOOP: |
| break; |
| default: |
| break; |
| } |
| |
| |
| |
| /* Check flags */ |
| if (nlh->nlmsg_flags & NLM_F_MULTI) |
| nlm_f_multi = 1; |
| else |
| nlm_f_multi = 0; |
| |
| if (nlh->nlmsg_type & NLMSG_DONE) |
| nlmsg_done = 1; |
| else |
| nlmsg_done = 0; |
| |
| /* Iteratve over attributes */ |
| nla_for_each_attr(nla, |
| nlmsg_attrdata(nlh, GENL_HDRLEN), |
| nlmsg_attrlen(nlh, GENL_HDRLEN), |
| nla_rem){ |
| |
| /* If this family is nl80211 */ |
| if (nla->nla_type == CTRL_ATTR_FAMILY_NAME && |
| !strcmp((char *)nla_data(nla), |
| "nl80211")) |
| nl80211_flag = 1; |
| |
| /* Save the family id */ |
| else if (nl80211_flag && |
| nla->nla_type == CTRL_ATTR_FAMILY_ID) { |
| nl80211_genl_id = |
| *((int *)nla_data(nla)); |
| nl80211_flag = 0; |
| } |
| |
| } |
| |
| } |
| |
| } while (nlm_f_multi && !nlmsg_done); |
| |
| return_genl_id: |
| /* Return family id as cache pointer */ |
| *result = (struct nl_cache *) nl80211_genl_id; |
| rc = 0; |
| error_recvbuf: |
| free(recvbuf); |
| error: |
| return rc; |
| } |
| |
| /* Checks the netlink cache to find family reference by name string */ |
| /* NOTE: Caller needs to call genl_family_put() when done with * |
| * returned object */ |
| struct genl_family *genl_ctrl_search_by_name(struct nl_cache *cache, \ |
| const char *name) |
| { |
| struct genl_family *gf = (struct genl_family *) \ |
| malloc(sizeof(struct genl_family)); |
| if (!gf) |
| goto fail; |
| memset(gf, 0, sizeof(*gf)); |
| |
| /* Add ref */ |
| gf->ce_refcnt++; |
| |
| /* Overriding cache pointer as family id for now */ |
| gf->gf_id = (uint16_t) ((uint32_t) cache); |
| strncpy(gf->gf_name, name, GENL_NAMSIZ); |
| |
| return gf; |
| fail: |
| return NULL; |
| |
| } |
| |
| int genl_ctrl_resolve(struct nl_sock *sk, const char *name) |
| { |
| struct nl_cache *cache = NULL; |
| struct genl_family *gf = NULL; |
| int id = -1; |
| |
| /* Hack to support wpa_supplicant */ |
| if (strcmp(name, "nlctrl") == 0) |
| return NETLINK_GENERIC; |
| |
| if (strcmp(name, "nl80211") != 0) { |
| fprintf(stderr, "%s is not supported\n", name); |
| return id; |
| } |
| |
| if (!genl_ctrl_alloc_cache(sk, &cache)) { |
| gf = genl_ctrl_search_by_name(cache, name); |
| if (gf) |
| id = genl_family_get_id(gf); |
| } |
| |
| if (gf) |
| genl_family_put(gf); |
| if (cache) |
| nl_cache_free(cache); |
| |
| return id; |
| } |