blob: 409729ca89ad32ee0f9926a5e8e3cd255d858ad1 [file] [log] [blame] [edit]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2013 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 <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <netinet/icmp6.h>
#include <net/if_arp.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/wireless.h>
#include <glib.h>
#include "connman.h"
#ifndef ARPHDR_PHONET_PIPE
#define ARPHDR_PHONET_PIPE (821)
#endif
#define print(arg...) do { if (0) connman_info(arg); } while (0)
//#define print(arg...) connman_info(arg)
struct watch_data {
unsigned int id;
int index;
connman_rtnl_link_cb_t newlink;
void *user_data;
};
static GSList *watch_list = NULL;
static unsigned int watch_id = 0;
static GSList *update_list = NULL;
static guint update_interval = G_MAXUINT;
static guint update_timeout = 0;
struct interface_data {
int index;
char *ident;
enum connman_service_type service_type;
enum connman_device_type device_type;
};
static GHashTable *interface_list = NULL;
static void free_interface(gpointer data)
{
struct interface_data *interface = data;
__connman_technology_remove_interface(interface->service_type,
interface->index, interface->ident);
g_free(interface->ident);
g_free(interface);
}
static bool ether_blacklisted(const char *name)
{
if (!name)
return true;
if (__connman_device_isfiltered(name))
return true;
return false;
}
static bool wext_interface(char *ifname)
{
struct iwreq wrq;
int fd, err;
fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd < 0)
return false;
memset(&wrq, 0, sizeof(wrq));
strncpy(wrq.ifr_name, ifname, sizeof(wrq.ifr_name) - 1);
err = ioctl(fd, SIOCGIWNAME, &wrq);
close(fd);
if (err < 0)
return false;
return true;
}
static void read_uevent(struct interface_data *interface)
{
char *filename, *name, line[128];
bool found_devtype;
FILE *f;
name = connman_inet_ifname(interface->index);
if (ether_blacklisted(name)) {
interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
} else {
interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
}
filename = g_strdup_printf("/sys/class/net/%s/uevent", name);
f = fopen(filename, "re");
g_free(filename);
if (!f) {
interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
goto out;
}
found_devtype = false;
while (fgets(line, sizeof(line), f)) {
char *pos;
pos = strchr(line, '\n');
if (!pos)
continue;
pos[0] = '\0';
if (strncmp(line, "DEVTYPE=", 8) != 0)
continue;
found_devtype = true;
if (strcmp(line + 8, "wlan") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_WIFI;
interface->device_type = CONNMAN_DEVICE_TYPE_WIFI;
} else if (strcmp(line + 8, "wwan") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_CELLULAR;
interface->device_type = CONNMAN_DEVICE_TYPE_CELLULAR;
} else if (strcmp(line + 8, "bluetooth") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
interface->device_type = CONNMAN_DEVICE_TYPE_BLUETOOTH;
} else if (strcmp(line + 8, "gadget") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_GADGET;
interface->device_type = CONNMAN_DEVICE_TYPE_GADGET;
} else if (strcmp(line + 8, "vlan") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
} else if (strcmp(line + 8, "lowpan") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_LOWPAN;
interface->device_type = CONNMAN_DEVICE_TYPE_LOWPAN;
} else if (strcmp(line + 8, "lowpan") == 0) {
interface->service_type = CONNMAN_SERVICE_TYPE_LOWPAN;
interface->device_type = CONNMAN_DEVICE_TYPE_LOWPAN;
} else {
interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
}
}
fclose(f);
if (found_devtype)
goto out;
/* We haven't got a DEVTYPE, let's check if it's a wireless device */
if (wext_interface(name)) {
interface->service_type = CONNMAN_SERVICE_TYPE_WIFI;
interface->device_type = CONNMAN_DEVICE_TYPE_WIFI;
connman_error("%s runs an unsupported 802.11 driver", name);
}
out:
g_free(name);
}
enum connman_device_type __connman_rtnl_get_device_type(int index)
{
struct interface_data *interface;
interface = g_hash_table_lookup(interface_list,
GINT_TO_POINTER(index));
if (!interface)
return CONNMAN_DEVICE_TYPE_UNKNOWN;
return interface->device_type;
}
/**
* connman_rtnl_add_newlink_watch:
* @index: network device index
* @callback: callback function
* @user_data: callback data;
*
* Add a new RTNL watch for newlink events
*
* Returns: %0 on failure and a unique id on success
*/
unsigned int connman_rtnl_add_newlink_watch(int index,
connman_rtnl_link_cb_t callback, void *user_data)
{
struct watch_data *watch;
watch = g_try_new0(struct watch_data, 1);
if (!watch)
return 0;
watch->id = ++watch_id;
watch->index = index;
watch->newlink = callback;
watch->user_data = user_data;
watch_list = g_slist_prepend(watch_list, watch);
DBG("id %d", watch->id);
if (callback) {
unsigned int flags = __connman_ipconfig_get_flags_from_index(index);
if (flags > 0)
callback(flags, 0, user_data);
}
return watch->id;
}
/**
* connman_rtnl_remove_watch:
* @id: watch identifier
*
* Remove the RTNL watch for the identifier
*/
void connman_rtnl_remove_watch(unsigned int id)
{
GSList *list;
DBG("id %d", id);
if (id == 0)
return;
for (list = watch_list; list; list = list->next) {
struct watch_data *watch = list->data;
if (watch->id == id) {
watch_list = g_slist_remove(watch_list, watch);
g_free(watch);
break;
}
}
}
static void trigger_rtnl(int index, void *user_data)
{
struct connman_rtnl *rtnl = user_data;
if (rtnl->newlink) {
unsigned short type = __connman_ipconfig_get_type_from_index(index);
unsigned int flags = __connman_ipconfig_get_flags_from_index(index);
rtnl->newlink(type, index, flags, 0);
}
if (rtnl->newgateway) {
const char *gateway =
__connman_ipconfig_get_gateway_from_index(index,
CONNMAN_IPCONFIG_TYPE_ALL);
if (gateway)
rtnl->newgateway(index, gateway);
}
}
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 %p name %s", rtnl, rtnl->name);
rtnl_list = g_slist_insert_sorted(rtnl_list, rtnl,
compare_priority);
__connman_ipconfig_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 %p name %s", rtnl, rtnl->name);
rtnl_list = g_slist_remove(rtnl_list, rtnl);
}
static const char *operstate2str(unsigned char operstate)
{
switch (operstate) {
case IF_OPER_UNKNOWN:
return "UNKNOWN";
case IF_OPER_NOTPRESENT:
return "NOT-PRESENT";
case IF_OPER_DOWN:
return "DOWN";
case IF_OPER_LOWERLAYERDOWN:
return "LOWER-LAYER-DOWN";
case IF_OPER_TESTING:
return "TESTING";
case IF_OPER_DORMANT:
return "DORMANT";
case IF_OPER_UP:
return "UP";
}
return "";
}
static bool extract_link(struct ifinfomsg *msg, int bytes,
struct ether_addr *address, const char **ifname,
unsigned int *mtu, unsigned char *operstate,
struct rtnl_link_stats *stats)
{
struct rtattr *attr;
for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFLA_ADDRESS:
if (address)
memcpy(address, RTA_DATA(attr), ETH_ALEN);
break;
case IFLA_IFNAME:
if (ifname)
*ifname = RTA_DATA(attr);
break;
case IFLA_MTU:
if (mtu)
*mtu = *((unsigned int *) RTA_DATA(attr));
break;
case IFLA_STATS:
if (stats)
memcpy(stats, RTA_DATA(attr),
sizeof(struct rtnl_link_stats));
break;
case IFLA_OPERSTATE:
if (operstate)
*operstate = *((unsigned char *) RTA_DATA(attr));
break;
case IFLA_LINKMODE:
break;
case IFLA_WIRELESS:
return false;
}
}
return true;
}
static void process_newlink(unsigned short type, int index, unsigned flags,
unsigned change, struct ifinfomsg *msg, int bytes)
{
struct ether_addr address = {{ 0, 0, 0, 0, 0, 0 }};
struct rtnl_link_stats stats;
unsigned char operstate = 0xff;
struct interface_data *interface;
const char *ifname = NULL;
unsigned int mtu = 0;
char ident[13], str[18];
GSList *list;
memset(&stats, 0, sizeof(stats));
if (!extract_link(msg, bytes, &address, &ifname, &mtu, &operstate, &stats))
return;
snprintf(ident, 13, "%02x%02x%02x%02x%02x%02x",
address.ether_addr_octet[0],
address.ether_addr_octet[1],
address.ether_addr_octet[2],
address.ether_addr_octet[3],
address.ether_addr_octet[4],
address.ether_addr_octet[5]);
snprintf(str, 18, "%02X:%02X:%02X:%02X:%02X:%02X",
address.ether_addr_octet[0],
address.ether_addr_octet[1],
address.ether_addr_octet[2],
address.ether_addr_octet[3],
address.ether_addr_octet[4],
address.ether_addr_octet[5]);
if (flags & IFF_SLAVE) {
connman_info("%s {newlink} ignoring slave, index %d address %s",
ifname, index, str);
return;
}
switch (type) {
case ARPHRD_ETHER:
case ARPHRD_LOOPBACK:
case ARPHDR_PHONET_PIPE:
case ARPHRD_PPP:
case ARPHRD_NONE:
__connman_ipconfig_newlink(index, type, flags,
str, mtu, &stats);
break;
}
connman_info("%s {newlink} index %d address %s mtu %u",
ifname, index, str, mtu);
if (operstate != 0xff)
connman_info("%s {newlink} index %d operstate %u <%s>",
ifname, index, operstate,
operstate2str(operstate));
interface = g_hash_table_lookup(interface_list, GINT_TO_POINTER(index));
if (!interface) {
interface = g_new0(struct interface_data, 1);
interface->index = index;
interface->ident = g_strdup(ident);
g_hash_table_insert(interface_list,
GINT_TO_POINTER(index), interface);
if (type == ARPHRD_ETHER)
read_uevent(interface);
} else
interface = NULL;
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (rtnl->newlink)
rtnl->newlink(type, index, flags, change);
}
/*
* The interface needs to be added after the newlink call.
* The newlink will create the technology when needed and
* __connman_technology_add_interface() expects the
* technology to be there already.
*/
if (interface)
__connman_technology_add_interface(interface->service_type,
interface->index, interface->ident);
for (list = watch_list; list; list = list->next) {
struct watch_data *watch = list->data;
if (watch->index != index)
continue;
if (watch->newlink)
watch->newlink(flags, change, watch->user_data);
}
}
static void process_dellink(unsigned short type, int index, unsigned flags,
unsigned change, struct ifinfomsg *msg, int bytes)
{
struct rtnl_link_stats stats;
unsigned char operstate = 0xff;
const char *ifname = NULL;
GSList *list;
memset(&stats, 0, sizeof(stats));
if (!extract_link(msg, bytes, NULL, &ifname, NULL, &operstate, &stats))
return;
if (operstate != 0xff)
connman_info("%s {dellink} index %d operstate %u <%s>",
ifname, index, operstate,
operstate2str(operstate));
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (rtnl->dellink)
rtnl->dellink(type, index, flags, change);
}
switch (type) {
case ARPHRD_ETHER:
case ARPHRD_LOOPBACK:
case ARPHDR_PHONET_PIPE:
case ARPHRD_PPP:
case ARPHRD_NONE:
__connman_ipconfig_dellink(index, &stats);
break;
}
g_hash_table_remove(interface_list, GINT_TO_POINTER(index));
}
static void extract_ipv4_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)
*address = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_LOCAL:
if (local)
*local = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_BROADCAST:
if (broadcast)
*broadcast = *((struct in_addr *) RTA_DATA(attr));
break;
case IFA_LABEL:
if (label)
*label = RTA_DATA(attr);
break;
}
}
}
static void extract_ipv6_addr(struct ifaddrmsg *msg, int bytes,
struct in6_addr *addr,
struct in6_addr *local)
{
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 (addr)
*addr = *((struct in6_addr *) RTA_DATA(attr));
break;
case IFA_LOCAL:
if (local)
*local = *((struct in6_addr *) RTA_DATA(attr));
break;
}
}
}
static void process_newaddr(unsigned char family, unsigned char prefixlen,
int index, struct ifaddrmsg *msg, int bytes)
{
struct in_addr ipv4_addr = { INADDR_ANY };
struct in6_addr ipv6_address, ipv6_local;
const char *label = NULL;
void *src;
char ip_string[INET6_ADDRSTRLEN];
if (family == AF_INET) {
extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL);
src = &ipv4_addr;
} else if (family == AF_INET6) {
extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local);
if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address))
return;
src = &ipv6_address;
} else {
return;
}
if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN))
return;
if (__connman_ipconfig_newaddr(index, family, label,
prefixlen, ip_string) >= 0) {
if (family == AF_INET6) {
/*
* Re-create RDNSS configured servers if there
* are any for this interface. This is done
* because we might have now properly
* configured interface with proper
* autoconfigured address.
*/
__connman_resolver_redo_servers(index);
}
}
}
static void process_deladdr(unsigned char family, unsigned char prefixlen,
int index, struct ifaddrmsg *msg, int bytes)
{
struct in_addr ipv4_addr = { INADDR_ANY };
struct in6_addr ipv6_address, ipv6_local;
const char *label = NULL;
void *src;
char ip_string[INET6_ADDRSTRLEN];
if (family == AF_INET) {
extract_ipv4_addr(msg, bytes, &label, &ipv4_addr, NULL, NULL);
src = &ipv4_addr;
} else if (family == AF_INET6) {
extract_ipv6_addr(msg, bytes, &ipv6_address, &ipv6_local);
if (IN6_IS_ADDR_LINKLOCAL(&ipv6_address))
return;
src = &ipv6_address;
} else {
return;
}
if (!inet_ntop(family, src, ip_string, INET6_ADDRSTRLEN))
return;
__connman_ipconfig_deladdr(index, family, label,
prefixlen, ip_string);
}
static void extract_ipv4_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)
*dst = *((struct in_addr *) RTA_DATA(attr));
break;
case RTA_GATEWAY:
if (gateway)
*gateway = *((struct in_addr *) RTA_DATA(attr));
break;
case RTA_OIF:
if (index)
*index = *((int *) RTA_DATA(attr));
break;
}
}
}
static void extract_ipv6_route(struct rtmsg *msg, int bytes, int *index,
struct in6_addr *dst,
struct in6_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)
*dst = *((struct in6_addr *) RTA_DATA(attr));
break;
case RTA_GATEWAY:
if (gateway)
*gateway =
*((struct in6_addr *) RTA_DATA(attr));
break;
case RTA_OIF:
if (index)
*index = *((int *) RTA_DATA(attr));
break;
}
}
}
static void process_newroute(unsigned char family, unsigned char scope,
struct rtmsg *msg, int bytes)
{
GSList *list;
char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN];
int index = -1;
if (family == AF_INET) {
struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
extract_ipv4_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
__connman_ipconfig_newroute(index, family, scope, dststr,
gatewaystr);
/* skip host specific routes */
if (scope != RT_SCOPE_UNIVERSE &&
!(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY))
return;
if (dst.s_addr != INADDR_ANY)
return;
} else if (family == AF_INET6) {
struct in6_addr dst = IN6ADDR_ANY_INIT,
gateway = IN6ADDR_ANY_INIT;
extract_ipv6_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
__connman_ipconfig_newroute(index, family, scope, dststr,
gatewaystr);
/* skip host specific routes */
if (scope != RT_SCOPE_UNIVERSE &&
!(scope == RT_SCOPE_LINK &&
IN6_IS_ADDR_UNSPECIFIED(&dst)))
return;
if (!IN6_IS_ADDR_UNSPECIFIED(&dst))
return;
} else
return;
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (rtnl->newgateway)
rtnl->newgateway(index, gatewaystr);
}
}
static void process_delroute(unsigned char family, unsigned char scope,
struct rtmsg *msg, int bytes)
{
GSList *list;
char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN];
int index = -1;
if (family == AF_INET) {
struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY };
extract_ipv4_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
__connman_ipconfig_delroute(index, family, scope, dststr,
gatewaystr);
/* skip host specific routes */
if (scope != RT_SCOPE_UNIVERSE &&
!(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY))
return;
if (dst.s_addr != INADDR_ANY)
return;
} else if (family == AF_INET6) {
struct in6_addr dst = IN6ADDR_ANY_INIT,
gateway = IN6ADDR_ANY_INIT;
extract_ipv6_route(msg, bytes, &index, &dst, &gateway);
inet_ntop(family, &dst, dststr, sizeof(dststr));
inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr));
__connman_ipconfig_delroute(index, family, scope, dststr,
gatewaystr);
/* skip host specific routes */
if (scope != RT_SCOPE_UNIVERSE &&
!(scope == RT_SCOPE_LINK &&
IN6_IS_ADDR_UNSPECIFIED(&dst)))
return;
if (!IN6_IS_ADDR_UNSPECIFIED(&dst))
return;
} else
return;
for (list = rtnl_list; list; list = list->next) {
struct connman_rtnl *rtnl = list->data;
if (rtnl->delgateway)
rtnl->delgateway(index, 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)) {
struct in_addr addr;
addr = *((struct in_addr *) RTA_DATA(attr));
print(" attr %s (len %d) %s\n", name, len, inet_ntoa(addr));
} 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);
if (hdr->nlmsg_type == IFLA_WIRELESS)
connman_warn_once("Obsolete WEXT WiFi driver detected");
process_newlink(msg->ifi_type, msg->ifi_index, msg->ifi_flags,
msg->ifi_change, msg, IFA_PAYLOAD(hdr));
}
static void rtnl_dellink(struct nlmsghdr *hdr)
{
struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr);
rtnl_link(hdr);
process_dellink(msg->ifi_type, msg->ifi_index, msg->ifi_flags,
msg->ifi_change, msg, IFA_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 bool is_route_rtmsg(struct rtmsg *msg)
{
if (msg->rtm_flags & RTM_F_CLONED)
return false;
if (msg->rtm_table != RT_TABLE_MAIN)
return false;
if (msg->rtm_protocol != RTPROT_BOOT &&
msg->rtm_protocol != RTPROT_KERNEL)
return false;
if (msg->rtm_type != RTN_UNICAST)
return false;
return true;
}
static void rtnl_newroute(struct nlmsghdr *hdr)
{
struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr);
rtnl_route(hdr);
if (is_route_rtmsg(msg))
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 (is_route_rtmsg(msg))
process_delroute(msg->rtm_family, msg->rtm_scope,
msg, RTM_PAYLOAD(hdr));
}
static void *rtnl_nd_opt_rdnss(struct nd_opt_hdr *opt, guint32 *lifetime,
int *nr_servers)
{
guint32 *optint = (void *)opt;
if (opt->nd_opt_len < 3)
return NULL;
if (*lifetime > ntohl(optint[1]))
*lifetime = ntohl(optint[1]);
/* nd_opt_len is in units of 8 bytes. The header is 1 unit (8 bytes)
and each address is another 2 units (16 bytes).
So the number of addresses (given rounding) is nd_opt_len/2 */
*nr_servers = opt->nd_opt_len / 2;
/* And they start 8 bytes into the packet, or two guint32s in. */
return optint + 2;
}
static const char **rtnl_nd_opt_dnssl(struct nd_opt_hdr *opt, guint32 *lifetime)
{
const char **domains = NULL;
guint32 *optint = (void *)opt;
unsigned char *optc = (void *)&optint[2];
int data_len = (opt->nd_opt_len * 8) - 8;
int nr_domains = 0;
int i, tmp;
if (*lifetime > ntohl(optint[1]))
*lifetime = ntohl(optint[1]);
/* Turn it into normal strings by converting the length bytes into '.',
and count how many search domains there are while we're at it. */
i = 0;
while (i < data_len) {
if (optc[i] > 0x3f) {
DBG("DNSSL contains compressed elements in violation of RFC6106");
return NULL;
}
if (optc[i] == 0) {
nr_domains++;
i++;
/* Check for double zero */
if (i < data_len && optc[i] == 0)
break;
continue;
}
tmp = i;
i += optc[i] + 1;
if (i >= data_len) {
DBG("DNSSL data overflows option length");
return NULL;
}
optc[tmp] = '.';
}
domains = g_try_new0(const char *, nr_domains + 1);
if (!domains)
return NULL;
/* Now point to the normal strings, missing out the leading '.' that
each of them will have now. */
for (i = 0; i < nr_domains; i++) {
domains[i] = (char *)optc + 1;
optc += strlen((char *)optc) + 1;
}
return domains;
}
static void rtnl_newnduseropt(struct nlmsghdr *hdr)
{
struct nduseroptmsg *msg = (struct nduseroptmsg *) NLMSG_DATA(hdr);
struct nd_opt_hdr *opt;
guint32 lifetime = -1;
const char **domains = NULL;
struct in6_addr *servers = NULL;
int i, nr_servers = 0;
int msglen = msg->nduseropt_opts_len;
int index;
DBG("family %d index %d len %d type %d code %d",
msg->nduseropt_family, msg->nduseropt_ifindex,
msg->nduseropt_opts_len, msg->nduseropt_icmp_type,
msg->nduseropt_icmp_code);
if (msg->nduseropt_family != AF_INET6 ||
msg->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
msg->nduseropt_icmp_code != 0)
return;
index = msg->nduseropt_ifindex;
if (index < 0)
return;
for (opt = (void *)&msg[1];
msglen > 0;
msglen -= opt->nd_opt_len * 8,
opt = ((void *)opt) + opt->nd_opt_len*8) {
DBG("remaining %d nd opt type %d len %d\n",
msglen, opt->nd_opt_type, opt->nd_opt_len);
if (opt->nd_opt_type == 25) { /* ND_OPT_RDNSS */
char buf[40];
servers = rtnl_nd_opt_rdnss(opt, &lifetime,
&nr_servers);
for (i = 0; i < nr_servers; i++) {
if (!inet_ntop(AF_INET6, servers + i, buf,
sizeof(buf)))
continue;
connman_resolver_append_lifetime(index,
NULL, buf, lifetime);
}
} else if (opt->nd_opt_type == 31) { /* ND_OPT_DNSSL */
g_free(domains);
domains = rtnl_nd_opt_dnssl(opt, &lifetime);
for (i = 0; domains && domains[i]; i++)
connman_resolver_append_lifetime(index,
domains[i], NULL, lifetime);
}
}
g_free(domains);
}
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_GETADDR:
return "GETADDR";
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";
case RTM_NEWNDUSEROPT:
return "NEWNDUSEROPT";
default:
return "UNKNOWN";
}
}
static GIOChannel *channel = NULL;
static guint channel_watch = 0;
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("%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("seq %d", seq);
req = find_request(seq);
if (req) {
request_list = g_slist_remove(request_list, req);
g_free(req);
}
req = g_slist_nth_data(request_list, 0);
if (!req)
return 0;
return send_request(req);
}
static void rtnl_message(void *buf, size_t len)
{
DBG("buf %p len %zd", buf, len);
while (len > 0) {
struct nlmsghdr *hdr = buf;
struct nlmsgerr *err;
if (!NLMSG_OK(hdr, len))
break;
DBG("%s len %d type %d flags 0x%04x seq %d pid %d",
type2string(hdr->nlmsg_type),
hdr->nlmsg_len, hdr->nlmsg_type,
hdr->nlmsg_flags, hdr->nlmsg_seq,
hdr->nlmsg_pid);
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("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;
case RTM_NEWNDUSEROPT:
rtnl_newnduseropt(hdr);
break;
}
len -= hdr->nlmsg_len;
buf += hdr->nlmsg_len;
}
}
static gboolean netlink_event(GIOChannel *chan, GIOCondition cond, gpointer data)
{
unsigned char buf[4096];
struct sockaddr_nl nladdr;
socklen_t addr_len = sizeof(nladdr);
ssize_t status;
int fd;
if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
return FALSE;
memset(buf, 0, sizeof(buf));
memset(&nladdr, 0, sizeof(nladdr));
fd = g_io_channel_unix_get_fd(chan);
status = recvfrom(fd, buf, sizeof(buf), 0,
(struct sockaddr *) &nladdr, &addr_len);
if (status < 0) {
if (errno == EINTR || errno == EAGAIN)
return TRUE;
return FALSE;
}
if (status == 0)
return FALSE;
if (nladdr.nl_pid != 0) { /* not sent by kernel, ignore */
DBG("Received msg from %u, ignoring it", nladdr.nl_pid);
return TRUE;
}
rtnl_message(buf, status);
return TRUE;
}
static int send_getlink(void)
{
struct rtnl_request *req;
DBG("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (!req)
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("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (!req)
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("");
req = g_try_malloc0(RTNL_REQUEST_SIZE);
if (!req)
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);
}
static gboolean update_timeout_cb(gpointer user_data)
{
__connman_rtnl_request_update();
return TRUE;
}
static void update_interval_callback(guint min)
{
if (update_timeout > 0)
g_source_remove(update_timeout);
if (min < G_MAXUINT) {
update_interval = min;
update_timeout = g_timeout_add_seconds(update_interval,
update_timeout_cb, NULL);
} else {
update_timeout = 0;
update_interval = G_MAXUINT;
}
}
static gint compare_interval(gconstpointer a, gconstpointer b)
{
guint val_a = GPOINTER_TO_UINT(a);
guint val_b = GPOINTER_TO_UINT(b);
return val_a - val_b;
}
unsigned int __connman_rtnl_update_interval_add(unsigned int interval)
{
guint min;
if (interval == 0)
return 0;
update_list = g_slist_insert_sorted(update_list,
GUINT_TO_POINTER(interval), compare_interval);
min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0));
if (min < update_interval) {
update_interval_callback(min);
__connman_rtnl_request_update();
}
return update_interval;
}
unsigned int __connman_rtnl_update_interval_remove(unsigned int interval)
{
guint min = G_MAXUINT;
if (interval == 0)
return 0;
update_list = g_slist_remove(update_list, GINT_TO_POINTER(interval));
if (update_list)
min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0));
if (min > update_interval)
update_interval_callback(min);
return min;
}
int __connman_rtnl_request_update(void)
{
return send_getlink();
}
int __connman_rtnl_init(void)
{
struct sockaddr_nl addr;
int sk;
DBG("");
interface_list = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, free_interface);
sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, 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 |
RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE |
(1<<(RTNLGRP_ND_USEROPT-1));
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);
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, FALSE);
channel_watch = 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("");
send_getlink();
send_getaddr();
send_getroute();
}
void __connman_rtnl_cleanup(void)
{
GSList *list;
DBG("");
for (list = watch_list; list; list = list->next) {
struct watch_data *watch = list->data;
DBG("removing watch %d", watch->id);
g_free(watch);
list->data = NULL;
}
g_slist_free(watch_list);
watch_list = NULL;
g_slist_free(update_list);
update_list = NULL;
for (list = request_list; list; list = list->next) {
struct rtnl_request *req = list->data;
DBG("%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;
if (channel_watch) {
g_source_remove(channel_watch);
channel_watch = 0;
}
g_io_channel_shutdown(channel, TRUE, NULL);
g_io_channel_unref(channel);
channel = NULL;
g_hash_table_destroy(interface_list);
}