blob: 1a39c6ab3dcf8c24a5801a503640fc84e4353f7a [file] [log] [blame]
/*
* 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;
}