blob: cbd7e1d01aba8dcea16fff8167104a2aa36d243f [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2009 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <glib.h>
#include "connman.h"
#define _DBG_RTNL(fmt, arg...) DBG(DBG_RTNL, fmt, ## arg)
#define print(arg...) do { } while (0)
//#define print(arg...) connman_info(arg)
static inline int index_match(const struct connman_rtnl *rtnl, int index)
{
return (rtnl->index == CONNMAN_RTNL_DEVICE_ANY || rtnl->index == index);
}
static void trigger_rtnl(int index, void *user_data)
{
struct connman_rtnl *rtnl = user_data;
if (!index_match(rtnl, index))
return;
if (rtnl->newlink) {
unsigned short type = __connman_ipconfig_get_type(index);
unsigned int flags = __connman_ipconfig_get_flags(index);
const char *ifname = __connman_ipconfig_get_ifname(index);
rtnl->newlink(rtnl->private, index, type, ifname, flags, 0);
}
if (rtnl->newroute) {
const char *gateway = __connman_ipconfig_get_gateway(index);
if (gateway != NULL)
rtnl->newroute(rtnl->private, index,
RT_SCOPE_UNIVERSE, "0.0.0.0", gateway);
}
/* TODO(sleffler) push other state; e.g. addresses */
}
static GSList *rtnl_list = NULL;
static gint compare_priority(gconstpointer a, gconstpointer b)
{
const struct connman_rtnl *rtnl1 = a;
const struct connman_rtnl *rtnl2 = b;
return rtnl2->priority - rtnl1->priority;
}
/**
* connman_rtnl_register:
* @rtnl: RTNL module
*
* Register a new RTNL module
*
* Returns: %0 on success
*/
int connman_rtnl_register(struct connman_rtnl *rtnl)
{
_DBG_RTNL("rtnl %p name %s", rtnl, rtnl->name);
rtnl_list = g_slist_insert_sorted(rtnl_list, rtnl,
compare_priority);
/* TODO(sleffler) not clear this is needed */
__connman_ipdevice_foreach(trigger_rtnl, rtnl);
return 0;
}
/**
* connman_rtnl_unregister:
* @rtnl: RTNL module
*
* Remove a previously registered RTNL module
*/
void connman_rtnl_unregister(struct connman_rtnl *rtnl)
{
_DBG_RTNL("rtnl %p name %s", rtnl, rtnl->name);
rtnl_list = g_slist_remove(rtnl_list, rtnl);
}
static void extract_link(const struct ifinfomsg *msg, int bytes,
const char **ifname)
{
struct rtattr *attr;
for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFLA_IFNAME:
if (ifname != NULL)
*ifname = RTA_DATA(attr);
break;
}
}
}
static void process_newlink(const struct ifinfomsg *msg, int bytes)
{
unsigned short type = msg->ifi_type;
int index = msg->ifi_index;
unsigned flags = msg->ifi_flags;
unsigned change = msg->ifi_change;
const char *ifname = NULL;
GSList *list;
extract_link(msg, bytes, &ifname);
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->newlink)
rtnl->newlink(rtnl->private, index, type, ifname,
flags, change);
}
}
static void process_dellink(const struct ifinfomsg *msg, int bytes)
{
unsigned short type = msg->ifi_type;
int index = msg->ifi_index;
unsigned flags = msg->ifi_flags;
unsigned change = msg->ifi_change;
const char *ifname = NULL;
GSList *list;
extract_link(msg, bytes, &ifname);
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->dellink)
rtnl->dellink(rtnl->private, index, type, ifname,
flags, change);
}
}
static void extract_addr(struct ifaddrmsg *msg, int bytes,
const char **label,
struct in_addr *local,
struct in_addr *address,
struct in_addr *broadcast)
{
struct rtattr *attr;
for (attr = IFA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFA_ADDRESS:
if (address != NULL)
*address = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_LOCAL:
if (local != NULL)
*local = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_BROADCAST:
if (broadcast != NULL)
*broadcast = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_LABEL:
if (label != NULL)
*label = RTA_DATA(attr);
break;
}
}
}
static void process_newaddr(unsigned char family, unsigned char prefixlen,
int index, struct ifaddrmsg *msg, int bytes)
{
const char *label, *addrstr;
struct in_addr address = { INADDR_ANY };
GSList *list;
if (family != AF_INET)
return;
extract_addr(msg, bytes, &label, &address, NULL, NULL);
addrstr = inet_ntoa(address);
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->newaddr)
rtnl->newaddr(rtnl->private, index, label, family,
prefixlen, addrstr);
}
}
static void process_deladdr(unsigned char family, unsigned char prefixlen,
int index, struct ifaddrmsg *msg, int bytes)
{
const char *label, *addrstr;
struct in_addr address = { INADDR_ANY };
GSList *list;
if (family != AF_INET)
return;
extract_addr(msg, bytes, &label, &address, NULL, NULL);
addrstr = inet_ntoa(address);
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->deladdr)
rtnl->deladdr(rtnl->private, index, label, family,
prefixlen, addrstr);
}
}
static void extract_route(struct rtmsg *msg, int bytes, int *index,
struct in_addr *dst,
struct in_addr *gateway)
{
struct rtattr *attr;
for (attr = RTM_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case RTA_DST:
if (dst != NULL)
*dst = *((struct in_addr *) RTA_DATA(attr));
break;
case RTA_GATEWAY:
if (gateway != NULL)
*gateway = *((struct in_addr *) RTA_DATA(attr));
break;
case RTA_OIF:
if (index != NULL)
*index = *((int *) RTA_DATA(attr));
break;
}
}
}
static void process_newroute(unsigned char family, unsigned char scope,
struct rtmsg *msg, int bytes)
{
GSList *list;
struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
char dststr[16], gatewaystr[16];
int index = -1;
if (family != AF_INET)
return;
extract_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->newroute)
rtnl->newroute(rtnl->private, index, scope, dststr,
gatewaystr);
}
}
static void process_delroute(unsigned char family, unsigned char scope,
struct rtmsg *msg, int bytes)
{
GSList *list;
struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
char dststr[16], gatewaystr[16];
int index = -1;
if (family != AF_INET)
return;
extract_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (!index_match(rtnl, index))
continue;
if (rtnl->delroute)
rtnl->delroute(rtnl->private, index, scope, dststr,
gatewaystr);
}
}
static inline void print_ether(struct rtattr *attr, const char *name)
{
int len = (int) RTA_PAYLOAD(attr);
if (len == ETH_ALEN) {
struct ether_addr eth;
memcpy(&eth, RTA_DATA(attr), ETH_ALEN);
print(" attr %s (len %d) %s\n", name, len, ether_ntoa(&eth));
} else
print(" attr %s (len %d)\n", name, len);
}
static inline void print_inet(struct rtattr *attr, const char *name,
unsigned char family)
{
int len = (int) RTA_PAYLOAD(attr);
if (family == AF_INET && len == sizeof(struct in_addr)) {
print(" attr %s (len %d) %s\n", name, len,
inet_ntoa(*((struct in_addr *) RTA_DATA(attr))));
} else
print(" attr %s (len %d)\n", name, len);
}
static inline void print_string(struct rtattr *attr, const char *name)
{
print(" attr %s (len %d) %s\n", name, (int) RTA_PAYLOAD(attr),
(char *) RTA_DATA(attr));
}
static inline void print_byte(struct rtattr *attr, const char *name)
{
print(" attr %s (len %d) 0x%02x\n", name, (int) RTA_PAYLOAD(attr),
*((unsigned char *) RTA_DATA(attr)));
}
static inline void print_integer(struct rtattr *attr, const char *name)
{
print(" attr %s (len %d) %d\n", name, (int) RTA_PAYLOAD(attr),
*((int *) RTA_DATA(attr)));
}
static inline void print_attr(struct rtattr *attr, const char *name)
{
int len = (int) RTA_PAYLOAD(attr);
if (name && len > 0)
print(" attr %s (len %d)\n", name, len);
else
print(" attr %d (len %d)\n", attr->rta_type, len);
}
static void rtnl_link(struct nlmsghdr *hdr)
{
struct ifinfomsg *msg;
struct rtattr *attr;
int bytes;
msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
bytes = IFLA_PAYLOAD(hdr);
print("ifi_index %d ifi_flags 0x%04x", msg->ifi_index, msg->ifi_flags);
for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFLA_ADDRESS:
print_ether(attr, "address");
break;
case IFLA_BROADCAST:
print_ether(attr, "broadcast");
break;
case IFLA_IFNAME:
print_string(attr, "ifname");
break;
case IFLA_MTU:
print_integer(attr, "mtu");
break;
case IFLA_LINK:
print_attr(attr, "link");
break;
case IFLA_QDISC:
print_attr(attr, "qdisc");
break;
case IFLA_STATS:
print_attr(attr, "stats");
break;
case IFLA_COST:
print_attr(attr, "cost");
break;
case IFLA_PRIORITY:
print_attr(attr, "priority");
break;
case IFLA_MASTER:
print_attr(attr, "master");
break;
case IFLA_WIRELESS:
print_attr(attr, "wireless");
break;
case IFLA_PROTINFO:
print_attr(attr, "protinfo");
break;
case IFLA_TXQLEN:
print_integer(attr, "txqlen");
break;
case IFLA_MAP:
print_attr(attr, "map");
break;
case IFLA_WEIGHT:
print_attr(attr, "weight");
break;
case IFLA_OPERSTATE:
print_byte(attr, "operstate");
break;
case IFLA_LINKMODE:
print_byte(attr, "linkmode");
break;
default:
print_attr(attr, NULL);
break;
}
}
}
static void rtnl_newlink(struct nlmsghdr *hdr)
{
struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
rtnl_link(hdr);
process_newlink(msg, IFLA_PAYLOAD(hdr));
}
static void rtnl_dellink(struct nlmsghdr *hdr)
{
struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
rtnl_link(hdr);
process_dellink(msg, IFLA_PAYLOAD(hdr));
}
static void rtnl_addr(struct nlmsghdr *hdr)
{
struct ifaddrmsg *msg;
struct rtattr *attr;
int bytes;
msg = (struct ifaddrmsg *) NLMSG_DATA(hdr);
bytes = IFA_PAYLOAD(hdr);
print("ifa_family %d ifa_index %d", msg->ifa_family, msg->ifa_index);
for (attr = IFA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFA_ADDRESS:
print_inet(attr, "address", msg->ifa_family);
break;
case IFA_LOCAL:
print_inet(attr, "local", msg->ifa_family);
break;
case IFA_LABEL:
print_string(attr, "label");
break;
case IFA_BROADCAST:
print_inet(attr, "broadcast", msg->ifa_family);
break;
case IFA_ANYCAST:
print_attr(attr, "anycast");
break;
case IFA_CACHEINFO:
print_attr(attr, "cacheinfo");
break;
case IFA_MULTICAST:
print_attr(attr, "multicast");
break;
default:
print_attr(attr, NULL);
break;
}
}
}
static void rtnl_newaddr(struct nlmsghdr *hdr)
{
struct ifaddrmsg *msg = (struct ifaddrmsg *) NLMSG_DATA(hdr);
rtnl_addr(hdr);
process_newaddr(msg->ifa_family, msg->ifa_prefixlen, msg->ifa_index,
msg, IFA_PAYLOAD(hdr));
}
static void rtnl_deladdr(struct nlmsghdr *hdr)
{
struct ifaddrmsg *msg = (struct ifaddrmsg *) NLMSG_DATA(hdr);
rtnl_addr(hdr);
process_deladdr(msg->ifa_family, msg->ifa_prefixlen, msg->ifa_index,
msg, IFA_PAYLOAD(hdr));
}
static void rtnl_route(struct nlmsghdr *hdr)
{
struct rtmsg *msg;
struct rtattr *attr;
int bytes;
msg = (struct rtmsg *) NLMSG_DATA(hdr);
bytes = RTM_PAYLOAD(hdr);
print("rtm_family %d rtm_table %d rtm_protocol %d",
msg->rtm_family, msg->rtm_table, msg->rtm_protocol);
print("rtm_scope %d rtm_type %d rtm_flags 0x%04x",
msg->rtm_scope, msg->rtm_type, msg->rtm_flags);
for (attr = RTM_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case RTA_DST:
print_inet(attr, "dst", msg->rtm_family);
break;
case RTA_SRC:
print_inet(attr, "src", msg->rtm_family);
break;
case RTA_IIF:
print_string(attr, "iif");
break;
case RTA_OIF:
print_integer(attr, "oif");
break;
case RTA_GATEWAY:
print_inet(attr, "gateway", msg->rtm_family);
break;
case RTA_PRIORITY:
print_attr(attr, "priority");
break;
case RTA_PREFSRC:
print_inet(attr, "prefsrc", msg->rtm_family);
break;
case RTA_METRICS:
print_attr(attr, "metrics");
break;
case RTA_TABLE:
print_integer(attr, "table");
break;
default:
print_attr(attr, NULL);
break;
}
}
}
static void rtnl_newroute(struct nlmsghdr *hdr)
{
struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr);
rtnl_route(hdr);
if (msg->rtm_table == RT_TABLE_MAIN &&
msg->rtm_protocol == RTPROT_BOOT &&
msg->rtm_type == RTN_UNICAST)
process_newroute(msg->rtm_family, msg->rtm_scope,
msg, RTM_PAYLOAD(hdr));
}
static void rtnl_delroute(struct nlmsghdr *hdr)
{
struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr);
rtnl_route(hdr);
if (msg->rtm_table == RT_TABLE_MAIN &&
msg->rtm_protocol == RTPROT_BOOT &&
msg->rtm_type == RTN_UNICAST)
process_delroute(msg->rtm_family, msg->rtm_scope,
msg, RTM_PAYLOAD(hdr));
}
static const char *type2string(uint16_t type)
{
switch (type) {
case NLMSG_NOOP:
return "NOOP";
case NLMSG_ERROR:
return "ERROR";
case NLMSG_DONE:
return "DONE";
case NLMSG_OVERRUN:
return "OVERRUN";
case RTM_GETLINK:
return "GETLINK";
case RTM_NEWLINK:
return "NEWLINK";
case RTM_DELLINK:
return "DELLINK";
case RTM_NEWADDR:
return "NEWADDR";
case RTM_DELADDR:
return "DELADDR";
case RTM_GETROUTE:
return "GETROUTE";
case RTM_NEWROUTE:
return "NEWROUTE";
case RTM_DELROUTE:
return "DELROUTE";
default:
return "UNKNOWN";
}
}
static GIOChannel *channel = NULL;
struct rtnl_request {
struct nlmsghdr hdr;
struct rtgenmsg msg;
};
#define RTNL_REQUEST_SIZE (sizeof(struct nlmsghdr) + sizeof(struct rtgenmsg))
static GSList *request_list = NULL;
static guint32 request_seq = 0;
static struct rtnl_request *find_request(guint32 seq)
{
GSList *list;
for (list = request_list; list; list = list->next) {
struct rtnl_request *req = list->data;
if (req->hdr.nlmsg_seq == seq)
return req;
}
return NULL;
}
static int send_request(struct rtnl_request *req)
{
struct sockaddr_nl addr;
int sk;
_DBG_RTNL("%s len %d type %d flags 0x%04x seq %d",
type2string(req->hdr.nlmsg_type),
req->hdr.nlmsg_len, req->hdr.nlmsg_type,
req->hdr.nlmsg_flags, req->hdr.nlmsg_seq);
sk = g_io_channel_unix_get_fd(channel);
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
return sendto(sk, req, req->hdr.nlmsg_len, 0,
(struct sockaddr *) &addr, sizeof(addr));
}
static int queue_request(struct rtnl_request *req)
{
request_list = g_slist_append(request_list, req);
if (g_slist_length(request_list) > 1)
return 0;
return send_request(req);
}
static int process_response(guint32 seq)
{
struct rtnl_request *req;
_DBG_RTNL("seq %d", seq);
req = find_request(seq);
if (req != NULL) {
request_list = g_slist_remove(request_list, req);
g_free(req);
}
req = g_slist_nth_data(request_list, 0);
if (req == NULL)
return 0;
return send_request(req);
}
static void rtnl_message(void *buf, size_t len)
{
_DBG_RTNL("buf %p len %zd", buf, len);
while (len > 0) {
struct nlmsghdr *hdr = buf;
struct nlmsgerr *err;
if (!NLMSG_OK(hdr, len))
break;
_DBG_RTNL("%s len %d type %d flags 0x%04x seq %d",
type2string(hdr->nlmsg_type),
hdr->nlmsg_len, hdr->nlmsg_type,
hdr->nlmsg_flags, hdr->nlmsg_seq);
switch (hdr->nlmsg_type) {
case NLMSG_NOOP:
case NLMSG_OVERRUN:
return;
case NLMSG_DONE:
process_response(hdr->nlmsg_seq);
return;
case NLMSG_ERROR:
err = NLMSG_DATA(hdr);
_DBG_RTNL("error %d (%s)", -err->error,
strerror(-err->error));
return;
case RTM_NEWLINK:
rtnl_newlink(hdr);
break;
case RTM_DELLINK:
rtnl_dellink(hdr);
break;
case RTM_NEWADDR:
rtnl_newaddr(hdr);
break;
case RTM_DELADDR:
rtnl_deladdr(hdr);
break;
case RTM_NEWROUTE:
rtnl_newroute(hdr);
break;
case RTM_DELROUTE:
rtnl_delroute(hdr);
break;
}
len -= hdr->nlmsg_len;
buf += hdr->nlmsg_len;
}
}
static gboolean netlink_event(GIOChannel *chan,
GIOCondition cond, gpointer data)
{
unsigned char buf[4096];
gsize len = 0;
GIOStatus status;
if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
return FALSE;
memset(buf, 0, sizeof(buf));
status = g_io_channel_read_chars(chan, (gchar *) buf,
sizeof(buf), &len, NULL);
if (status != G_IO_STATUS_NORMAL) {
if (status == G_IO_STATUS_AGAIN)
return TRUE;
return FALSE;
}
rtnl_message(buf, len);
return TRUE;
}
static int send_getlink(void)
{
struct rtnl_request *req;
_DBG_RTNL("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (req == NULL)
return -ENOMEM;
req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
req->hdr.nlmsg_type = RTM_GETLINK;
req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req->hdr.nlmsg_pid = 0;
req->hdr.nlmsg_seq = request_seq++;
req->msg.rtgen_family = AF_INET;
return queue_request(req);
}
static int send_getaddr(void)
{
struct rtnl_request *req;
_DBG_RTNL("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (req == NULL)
return -ENOMEM;
req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
req->hdr.nlmsg_type = RTM_GETADDR;
req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req->hdr.nlmsg_pid = 0;
req->hdr.nlmsg_seq = request_seq++;
req->msg.rtgen_family = AF_INET;
return queue_request(req);
}
static int send_getroute(void)
{
struct rtnl_request *req;
_DBG_RTNL("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (req == NULL)
return -ENOMEM;
req->hdr.nlmsg_len = RTNL_REQUEST_SIZE;
req->hdr.nlmsg_type = RTM_GETROUTE;
req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req->hdr.nlmsg_pid = 0;
req->hdr.nlmsg_seq = request_seq++;
req->msg.rtgen_family = AF_INET;
return queue_request(req);
}
/*
* TODO(pstew): Harmonize this code with the rest of RTNL, which is
* currently bound to IPv4-only.
*/
struct connman_rtnl_reader *connman_rtnl_open_route(unsigned char family,
unsigned int flags,
void *dst_addr,
uint32_t index)
{
int sk = -1;
struct connman_rtnl_reader *reader = NULL;
struct rtmsg *msg;
struct rtattr *rta;
struct {
struct nlmsghdr hdr;
struct rtmsg msg; /* NB: The following fields only reserve */
struct rtattr rta; /* space -- alignment is enforced below */
in_addr_t dest_addr;
unsigned char attr_data[1024];
} req;
struct sockaddr_nl addr;
unsigned char *ptr = (unsigned char *) &req;
int address_size = 0;
int rta_len = sizeof(req.attr_data);
if (family == AF_INET)
address_size = sizeof(in_addr_t);
else if (family == AF_INET6)
address_size = sizeof(struct in6_addr);
else {
connman_error("%s: bad address family %d", __func__, family);
goto done;
}
sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (sk < 0) {
connman_error("%s: unable to open netlink socket %s",
__func__, strerror(errno));
goto done;
}
fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK);
memset(&req, 0, sizeof(req));
rta = (struct rtattr *) &ptr[NLMSG_ALIGN(sizeof(req.hdr)) +
NLMSG_ALIGN(sizeof(req.msg))];
if (dst_addr != NULL) {
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(address_size);
memcpy(RTA_DATA(rta), dst_addr, address_size);
rta = RTA_NEXT(rta, rta_len);
}
if (index != -1) {
rta->rta_type = RTA_OIF;
rta->rta_len = RTA_LENGTH(sizeof(index));
memcpy(RTA_DATA(rta), &index, sizeof(index));
rta = RTA_NEXT(rta, rta_len);
}
req.hdr.nlmsg_len = NLMSG_ALIGN(sizeof(req.hdr)) +
NLMSG_ALIGN(sizeof(req.msg)) +
NLMSG_ALIGN(sizeof(req.rta)) +
(sizeof(req.attr_data) - rta_len);
req.hdr.nlmsg_type = RTM_GETROUTE;
req.hdr.nlmsg_flags = NLM_F_REQUEST | flags;
msg = (struct rtmsg *) &ptr[NLMSG_ALIGN(sizeof(req.hdr))];
msg->rtm_family = family;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
if (sendto(sk, &req, req.hdr.nlmsg_len, 0,
(struct sockaddr *) &addr, sizeof(addr)) < 0) {
connman_error("%s: unable to send netlink packet %s",
__func__, strerror(errno));
goto done;
}
reader = g_try_new0(struct connman_rtnl_reader, 1);
if (reader == NULL) {
connman_error("%s: unable to allocate connman_rtnl_reader",
__func__);
goto done;
}
reader->fd = sk;
reader->address_size = address_size;
done:
if (reader == NULL && sk != -1)
close(sk);
return reader;
}
connman_bool_t connman_rtnl_read_route(struct connman_rtnl_reader *reader,
uint32_t *index,
unsigned char *dst_len,
unsigned char *src_len,
void *dst, void *src, void *gateway,
unsigned int *flags)
{
struct nlmsghdr *hdr;
struct rtmsg *msg;
struct rtattr *rta;
unsigned char *ptr = reader->ptr;
int len = reader->len;
int read_len, rta_len, rta_size;
connman_bool_t found = FALSE;
if (len != 0) {
memcpy(reader->buf, ptr, len);
}
if (reader->fd >= 0) {
read_len = recv(reader->fd, reader->buf + len,
sizeof(reader->buf) - len, 0);
if (read_len > 0) {
len += read_len;
} else {
close(reader->fd);
reader->fd = -1;
}
}
ptr = reader->buf;
while (len > 0 && found == FALSE) {
hdr = (struct nlmsghdr *) ptr;
if (!NLMSG_OK(hdr, len) ||
hdr->nlmsg_type != RTM_NEWROUTE) {
close(reader->fd);
reader->fd = -1;
len = 0;
break;
}
msg = NLMSG_DATA(hdr);
if (msg->rtm_table == RT_TABLE_MAIN) {
found = TRUE;
if (dst != NULL)
memset(dst, 0, reader->address_size);
if (src != NULL)
memset(src, 0, reader->address_size);
if (gateway != NULL)
memset(gateway, 0, reader->address_size);
if (dst_len != NULL)
*dst_len = msg->rtm_dst_len;
if (src_len != NULL)
*src_len = msg->rtm_src_len;
if (index != NULL)
*index = -1;
if (flags != NULL)
*flags = msg->rtm_flags;
rta = RTM_RTA(msg);
rta_len = hdr->nlmsg_len - NLMSG_ALIGN(sizeof(*hdr)) -
NLMSG_ALIGN(sizeof(*msg));
while (RTA_OK(rta, rta_len)) {
rta_size = RTA_PAYLOAD(rta);
switch (rta->rta_type) {
case RTA_SRC:
if (src != NULL &&
reader->address_size == rta_size)
memcpy(src, RTA_DATA(rta),
rta_size);
break;
case RTA_DST:
if (dst != NULL &&
reader->address_size == rta_size)
memcpy(dst, RTA_DATA(rta),
rta_size);
break;
case RTA_GATEWAY:
if (gateway != NULL &&
reader->address_size == rta_size)
memcpy(gateway, RTA_DATA(rta),
rta_size);
break;
case RTA_OIF:
if (index != NULL &&
sizeof(*index) == rta_size)
memcpy(index, RTA_DATA(rta),
rta_size);
break;
}
rta = RTA_NEXT(rta, rta_len);
}
}
ptr += hdr->nlmsg_len;
len -= hdr->nlmsg_len;
}
reader->ptr = ptr;
reader->len = len;
return found;
}
void connman_rtnl_close(struct connman_rtnl_reader *reader)
{
if (reader->fd != -1)
close(reader->fd);
g_free(reader);
}
int __connman_rtnl_init(void)
{
struct sockaddr_nl addr;
int sk;
_DBG_RTNL("");
sk = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (sk < 0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE;
if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sk);
return -1;
}
channel = g_io_channel_unix_new(sk);
g_io_channel_set_close_on_unref(channel, TRUE);
if (g_io_channel_set_encoding(channel, NULL, NULL)
!= G_IO_STATUS_NORMAL) {
g_io_channel_unref(channel);
return -1;
}
g_io_channel_set_buffered(channel, FALSE);
g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
netlink_event, NULL);
return 0;
}
void __connman_rtnl_start(void)
{
_DBG_RTNL("");
send_getlink();
send_getaddr();
send_getroute();
}
void __connman_rtnl_cleanup(void)
{
GSList *list;
_DBG_RTNL("");
for (list = request_list; list; list = list->next) {
struct rtnl_request *req = list->data;
_DBG_RTNL("%s len %d type %d flags 0x%04x seq %d",
type2string(req->hdr.nlmsg_type),
req->hdr.nlmsg_len, req->hdr.nlmsg_type,
req->hdr.nlmsg_flags, req->hdr.nlmsg_seq);
g_free(req);
list->data = NULL;
}
g_slist_free(request_list);
request_list = NULL;
g_io_channel_shutdown(channel, TRUE, NULL);
g_io_channel_unref(channel);
channel = NULL;
}