blob: e5496615900d59776427a280945c92d6214f207a [file] [log] [blame]
/* ST-Ericsson U300 RIL
**
** Copyright (C) ST-Ericsson AB 2008-2010
** Copyright 2006, 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.
**
** Author: Sjur Brendeland <sjur.brandeland@stericsson.com>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if_arp.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <stdint.h>
#include <stdbool.h>
#include <linux/caif/if_caif.h>
#include <assert.h>
#include <errno.h>
#include "u300-ril.h"
#include "u300-ril-netif.h"
#define LOG_TAG "RILV"
#include <utils/Log.h>
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((uint8_t *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
#define MAX_PAD_SIZE 1024
#define MAX_BUF_SIZE 4096
struct iplink_req {
struct nlmsghdr n;
struct ifinfomsg i;
char pad[MAX_PAD_SIZE];
int ifindex;
char ifname[MAX_IFNAME_LEN];
};
static __u32 ipconfig_seqnr = 1;
static bool get_ifname(struct ifinfomsg *msg, int bytes,
const char **ifname)
{
struct rtattr *attr;
if (ifname == NULL)
return false;
for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
if (attr->rta_type == IFLA_IFNAME) {
*ifname = RTA_DATA(attr);
return true;
}
}
return false;
}
static void handle_rtnl_response(struct iplink_req *req, unsigned short type,
int index, unsigned flags, unsigned change,
struct ifinfomsg *msg, int bytes)
{
const char *ifname = NULL;
get_ifname(msg, bytes, &ifname);
req->ifindex = index;
strncpy(req->ifname, ifname, sizeof(req->ifname));
req->ifname[sizeof(req->ifname)-1] = '\0';
}
/**
* Returns -1 and sets errno on errors.
*/
static int send_iplink_req(int sk, struct iplink_req *req)
{
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
return sendto(sk, req, req->n.nlmsg_len, 0,
(struct sockaddr *) &addr, sizeof(addr));
}
/**
* Returns 0 on receiving an ACK message;
* Negative on error;
* Positive otherwise.
*/
static int parse_rtnl_message(uint8_t *buf, size_t len, struct iplink_req *req)
{
struct ifinfomsg *msg;
while (len > 0) {
struct nlmsghdr *hdr = (struct nlmsghdr *)buf;
struct nlmsgerr *err;
if (!NLMSG_OK(hdr, len))
return -EBADMSG;
if (hdr->nlmsg_type == NLMSG_ERROR) {
err = NLMSG_DATA(hdr);
if (err->error)
LOGE("%s(): RTNL failed: seq:%d, error %d(%s)\n", __func__,
hdr->nlmsg_seq, err->error, strerror(-err->error));
return err->error;
} else if (hdr->nlmsg_type == RTM_NEWLINK ||
hdr->nlmsg_type == RTM_DELLINK) {
msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
handle_rtnl_response(req, msg->ifi_type,
msg->ifi_index, msg->ifi_flags,
msg->ifi_change, msg,
IFA_PAYLOAD(hdr));
}
len -= hdr->nlmsg_len;
buf += hdr->nlmsg_len;
}
return 1;
}
/**
* Returns 0 on success; On failure, errno is set and a negative value returned.
*/
static int netlink_get_response(int sk, struct iplink_req *req)
{
unsigned char *buf;
int ret;
buf = malloc(MAX_BUF_SIZE);
assert(buf != NULL);
/*
* Loops until an ACK message is received (i.e. parse_rtnl_message
* returns 0) or an error occurs.
*/
do {
ret = read(sk, buf, MAX_BUF_SIZE);
if (ret < 0) {
if (errno == EINTR) {
ret = 1;
continue;
}
else
break;
}
/*
* EOF is treated as error. This may happen when no process
* has the pipe open for writing or the other end closed
* the socket orderly.
*/
if (ret == 0) {
LOGW("EOF received.\n");
errno = EIO;
ret = -1;
break;
}
ret = parse_rtnl_message(buf, ret, req);
if (ret < 0)
errno = -ret;
} while (ret > 0);
free(buf);
return ret;
}
static void add_attribute(struct nlmsghdr *n, int maxlen, int type,
const void *data, int datalen)
{
int len = RTA_LENGTH(datalen);
struct rtattr *rta;
if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) {
LOGE("%s(): attribute too large for message. nlmsg_len:%d, len:%d, \
maxlen:%d\n", __func__, n->nlmsg_len, len, maxlen);
assert(false && "attribute too large for message.");
}
rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = len;
memcpy(RTA_DATA(rta), data, datalen);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
return;
}
/**
* Sets errno and returns -1 on error.
*/
static int create_caif_interface(int sk, struct iplink_req *req,
int connection_type, char *ifname,
int nsapi, char loop_enabled)
{
const char *type = "caif";
struct rtattr *linkinfo;
struct rtattr *data;
req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
req->n.nlmsg_type = RTM_NEWLINK;
req->n.nlmsg_seq = ipconfig_seqnr++;
req->i.ifi_family = AF_UNSPEC;
add_attribute(&req->n, sizeof(*req), IFLA_IFNAME,
ifname, strlen(ifname));
linkinfo = NLMSG_TAIL(&req->n);
add_attribute(&req->n, sizeof(*req), IFLA_LINKINFO,
NULL, 0);
add_attribute(&req->n, sizeof(*req), IFLA_INFO_KIND,
type, strlen(type));
data = NLMSG_TAIL(&req->n);
add_attribute(&req->n, sizeof(*req), IFLA_INFO_DATA,
NULL, 0);
if (connection_type == IFLA_CAIF_IPV4_CONNID ||
connection_type == IFLA_CAIF_IPV6_CONNID) {
add_attribute(&req->n, sizeof(*req),
connection_type, &nsapi, sizeof(nsapi));
} else {
LOGE("%s(): Unsupported linktype.\n", __func__);
errno = EINVAL;
return -1;
}
if (loop_enabled)
add_attribute(&req->n, sizeof(*req),
IFLA_CAIF_LOOPBACK, &loop_enabled, sizeof(loop_enabled));
data->rta_len = (uint8_t *)NLMSG_TAIL(&req->n) - (uint8_t *)data;
linkinfo->rta_len = (uint8_t *)NLMSG_TAIL(&req->n) - (uint8_t *)linkinfo;
return send_iplink_req(sk, req);
}
static int destroy_caif_interface(int sk, struct iplink_req *req,
int ifindex, char *ifname)
{
req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
req->n.nlmsg_type = RTM_DELLINK;
req->n.nlmsg_seq = ipconfig_seqnr++;
req->i.ifi_family = AF_UNSPEC;
req->i.ifi_index = ifindex;
if (ifname != NULL)
add_attribute(&req->n, sizeof(*req), IFLA_IFNAME,
ifname, strlen(ifname));
return send_iplink_req(sk, req);
}
/**
* Returns netlink socket on success; On failure, errno is set and -1 returned.
*/
static int rtnl_init(void)
{
struct sockaddr_nl addr;
int sk, ret;
sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
/* -1 is returned if failed to create socket. */
if (sk < 0)
goto error;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE;
ret = bind(sk, (struct sockaddr *) &addr, sizeof(addr));
/* -1 is returned if failed to bind a name to a socket. */
if (ret < 0) {
/* errno may be clobbered by a successful close. */
int old_errno = errno;
close(sk);
errno = old_errno;
goto error;
}
goto exit;
error:
sk = -1;
exit:
return sk;
}
/* Note ifname is in/out and must be minimum size 16 */
int rtnl_create_caif_interface(int type, int conn_id,
char ifname[MAX_IFNAME_LEN],
int *ifindex, char loop)
{
int sk, ret;
struct iplink_req *req;
req = malloc(sizeof(*req));
assert(req != NULL);
memset(req, 0, sizeof(*req));
sk = rtnl_init();
ret = sk;
if (sk < 0)
goto exit;
ret = create_caif_interface(sk, req, type, ifname, conn_id, loop);
if (ret < 0)
goto exit;
ret = netlink_get_response(sk, req);
if (ret < 0)
goto exit;
strncpy(ifname, req->ifname, MAX_IFNAME_LEN);
ifname[MAX_IFNAME_LEN - 1] = '\0';
*ifindex = req->ifindex;
exit:
close(sk);
free(req);
return ret;
}
int rtnl_delete_caif_interface(int ifid, char *name)
{
struct iplink_req req;
int sk, ret;
memset(&req, 0, sizeof(req));
sk = rtnl_init();
ret = sk;
if (sk < 0)
return ret;
ret = destroy_caif_interface(sk, &req, ifid, name);
if (ret < 0)
goto exit;
ret = netlink_get_response(sk, &req);
if (ret < 0)
goto exit;
ret = 0;
exit:
close(sk);
return ret;
}