blob: db4fe3d8347ea656c6caa646087d6285d8ac2663 [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 <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <net/ethernet.h>
#include <linux/if_arp.h>
#include <linux/if_addr.h>
#include <linux/wireless.h>
#include <linux/rtnetlink.h>
#include "connman.h"
#define _DBG_INET(fmt, arg...) DBG(DBG_INET, fmt, ## arg)
/*
* Create a socket suitable for network ioctls.
*
* Return <0 and log a msg on failure.
*
* TODO(sleffler) keep a socket open instead of closing after each use?
*/
static int getsocket(const char *func, unsigned char family)
{
int sk = socket(family, SOCK_DGRAM, 0);
if (sk < 0)
connman_error("%s: socket: %s", func, strerror(errno));
return sk;
}
/*
* Get an interface's name; it's returned in the struct ifreq passed
* in which is made usable for subsequent ioctls.
*
* Return -1 and log a msg on failure.
*/
static int getifname(int sk, int index, struct ifreq *ifr, const char *func)
{
memset(ifr, 0, sizeof(*ifr));
ifr->ifr_ifindex = index;
if (ioctl(sk, SIOCGIFNAME, ifr) < 0) {
connman_error("%s: SIOCGIFNAME(index %d): %s", func,
ifr->ifr_ifindex, strerror(errno));
return -1;
}
return 0;
}
static void iferror(const char *func, const char *op, const char *name)
{
connman_error("%s: %s(%s): %s", func, op, name, strerror(errno));
}
/*
* Return an interface's index given its name.
*
* Return -1 and log a msg on failure.
*/
int connman_inet_ifindex(const char *name)
{
struct ifreq ifr;
int sk, err;
if (name == NULL)
return -1; /* XXX log? */
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
err = ioctl(sk, SIOCGIFINDEX, &ifr);
if (err < 0)
iferror(__func__, "SIOCGIFINDEX", name);
close(sk);
return (err < 0 ? -1 : ifr.ifr_ifindex);
}
/*
* Return the interface's name given it's index. The caller is
* responsible for the storage used to hold the name.
*
* Return NULL and log a msg on failure.
*/
char *connman_inet_ifname(int index)
{
struct ifreq ifr;
int sk, err;
if (index < 0)
return NULL; /* XXX log? */
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return NULL;
err = getifname(sk, index, &ifr, __func__);
close(sk);
return (err < 0 ? NULL : g_strdup(ifr.ifr_name));
}
/*
* Adjust flags on an interface. If flags is >= 0 then
* they are "added", otherwise they are "subtracted".
* Return 0 on success. If we are adjusting IFF_UP and
* the interace is already up/down return -EALREADY.
*
* Return <0 (-errno) on failure and log a msg.
*/
static int inet_setflags(int index, int flags, const char *func)
{
struct ifreq ifr;
int sk, err;
sk = getsocket(func, PF_INET);
if (sk < 0)
return -errno;
err = 0;
if (getifname(sk, index, &ifr, func) < 0) {
err = -errno;
goto done;
}
if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
iferror(func, "SIOCGIFFLAGS", ifr.ifr_name);
err = -errno;
goto done;
}
if (flags < 0) {
if ((ifr.ifr_flags & IFF_UP) == 0) {
err = -EALREADY;
goto done;
}
ifr.ifr_flags &= ~(-flags);
} else {
if (ifr.ifr_flags & IFF_UP) {
err = -EALREADY;
goto done;
}
ifr.ifr_flags |= flags;
}
if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0) {
iferror(func, "SIOCSIFFLAGS", ifr.ifr_name);
err = -errno;
}
done:
close(sk);
return err;
}
/*
* Mark the specified interface UP. Return 0 on success.
* If the interace is already up return -EALREADY.
*
* Return <0 (-errno) on failure and log a msg.
*/
int connman_inet_ifup(int index)
{
return inet_setflags(index, IFF_UP, __func__);
}
/*
* Mark the specified interface !UP. Return 0 on success.
* If the interace is already down return -EALREADY.
*
* Return <0 (-errno) on failure and log a msg.
*/
int connman_inet_ifdown(int index)
{
return inet_setflags(index, -IFF_UP, __func__);
}
/*
* Get an interface's h/w address and leave it in the struct ifreq
* passed in. The interface is specified by its index.
*
* Return -1 and log a msg on failure.
*/
static int getifhwaddr(int index, struct ifreq *ifr, const char *func)
{
int sk, err;
if (index < 0)
return -1;
sk = getsocket(func, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, ifr, func);
if (err < 0)
goto done;
err = ioctl(sk, SIOCGIFHWADDR, ifr);
if (err < 0)
iferror(__func__, "SIOCGIFHWADDR", ifr->ifr_name);
done:
close(sk);
return err;
}
/*
* Return an interface's protocol type/family. In the event
* of an error we return ARPHRD_VOID and log a msg.
*/
static unsigned short index2type(int index)
{
struct ifreq ifr;
int err;
err = getifhwaddr(index, &ifr, __func__);
return (err < 0 ? ARPHRD_VOID : ifr.ifr_hwaddr.sa_family);
}
/*
* Construct an interface's identifier from the hardware address
* and any specified prefix string. The caller is responsible for
* reclaiming storage associated with the returned string.
*
* Return NULL and log a msg on failure.
*/
static char *index2ident(int index, const char *prefix)
{
struct ifreq ifr;
struct ether_addr eth;
char *str;
if (getifhwaddr(index, &ifr, __func__) < 0)
return NULL;
memcpy(&eth, &ifr.ifr_hwaddr.sa_data, sizeof(eth));
str = g_strdup_printf("%s%02x%02x%02x%02x%02x%02x",
prefix ? prefix : "",
eth.ether_addr_octet[0], eth.ether_addr_octet[1],
eth.ether_addr_octet[2], eth.ether_addr_octet[3],
eth.ether_addr_octet[4], eth.ether_addr_octet[5]);
if (str == NULL)
connman_error("%s: no memory", __func__);
return str;
}
/*
* Return an interface's hardware address. The caller is
* responsible for reclaiming storage associated with the
* returned string.
*
* Return NULL and log a msg on failure.
*
* NB: This should just be index2ident(index, NULL) except each
* nibble is formatted w/ %02X instead of %02x.
*/
static char *index2addr(int index)
{
struct ifreq ifr;
struct ether_addr eth;
char *str;
if (getifhwaddr(index, &ifr, __func__) < 0)
return NULL;
memcpy(&eth, &ifr.ifr_hwaddr.sa_data, sizeof(eth));
str = g_strdup_printf("%02X%02X%02X%02X%02X%02X",
eth.ether_addr_octet[0], eth.ether_addr_octet[1],
eth.ether_addr_octet[2], eth.ether_addr_octet[3],
eth.ether_addr_octet[4], eth.ether_addr_octet[5]);
if (str == NULL)
connman_error("%s: no memory", __func__);
return str;
}
static connman_bool_t _hasclassnetdir(const char *type, const char *devname)
{
char pathname[PATH_MAX];
struct stat st;
snprintf(pathname, PATH_MAX, "/sys/class/net/%s/%s", devname, type);
return (stat(pathname, &st) == 0 && (st.st_mode & S_IFDIR) ?
TRUE : FALSE);
}
static connman_bool_t isphy80211(const char *devname)
{
return _hasclassnetdir("phy80211", devname);
}
/*
* Return an indication of whether the interface is using
* the mac80211 support in the kernel.
*
* Return FALSE and log a msg if unable to find an answer.
*/
connman_bool_t connman_inet_is_mac80211(int index)
{
connman_bool_t result = FALSE;
struct ifreq ifr;
int sk;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return FALSE;
if (getifname(sk, index, &ifr, __func__) == 0)
result = isphy80211(ifr.ifr_name);
close(sk);
return result;
}
static connman_bool_t isbonding(const char *devname)
{
return _hasclassnetdir("bonding", devname);
}
static connman_bool_t isbridge(const char *devname)
{
return _hasclassnetdir("bridge", devname);
}
static connman_bool_t iswimax(const char *devname)
{
return _hasclassnetdir("wimax", devname);
}
static connman_bool_t hasiwname(const char *devname)
{
struct iwreq iwr;
int sk, err;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return FALSE;
memset(&iwr, 0, sizeof(iwr));
strncpy(iwr.ifr_ifrn.ifrn_name, devname, IFNAMSIZ);
err = ioctl(sk, SIOCGIWNAME, &iwr);
close(sk);
return (err == 0 ? TRUE : FALSE);
}
/*
* Return the internal device type using various heuristics.
*/
enum connman_device_type __connman_inet_get_device_type(int index)
{
enum connman_device_type devtype;
char *devname;
devname = connman_inet_ifname(index);
if (devname == NULL)
return CONNMAN_DEVICE_TYPE_UNKNOWN;
devtype = CONNMAN_DEVICE_TYPE_UNKNOWN;
switch (index2type(index)) {
case ARPHRD_ETHER:
if (isphy80211(devname) == TRUE || hasiwname(devname) == TRUE)
devtype = CONNMAN_DEVICE_TYPE_WIFI;
else if (g_str_has_prefix(devname, "wmx") == TRUE ||
iswimax(devname) == TRUE)
devtype = CONNMAN_DEVICE_TYPE_WIMAX;
else if (g_str_has_prefix(devname, "vmnet") == TRUE ||
g_str_has_prefix(devname, "vboxnet") == TRUE ||
g_str_has_prefix(devname, "bnep") == TRUE ||
isbridge(devname) == TRUE ||
isbonding(devname) == TRUE)
devtype = CONNMAN_DEVICE_TYPE_UNKNOWN;
else if (__connman_udev_has_associated_modem(devname))
devtype = CONNMAN_DEVICE_TYPE_CELLULAR;
else
devtype = CONNMAN_DEVICE_TYPE_ETHERNET;
break;
case ARPHRD_NONE:
break;
}
g_free(devname);
return devtype;
}
/*
* Create a connman_device given an interface's index.
*/
struct connman_device *connman_inet_create_device(int index)
{
enum connman_device_mode mode = CONNMAN_DEVICE_MODE_UNKNOWN;
enum connman_device_type type;
struct connman_device *device;
char *devname, *ident = NULL;
char *addr = NULL, *name = NULL, *node = NULL;
if (index < 0)
return NULL;
devname = connman_inet_ifname(index);
if (devname == NULL)
return NULL;
if (__connman_device_isfiltered(devname) == TRUE) {
connman_info("Ignoring network interface %s (filtered)",
devname);
g_free(devname);
return NULL;
}
__connman_udev_get_devtype(devname);
type = __connman_inet_get_device_type(index);
switch (type) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
connman_info("Ignoring network interface %s (type unknown)",
devname);
g_free(devname);
return NULL;
case CONNMAN_DEVICE_TYPE_ETHERNET:
case CONNMAN_DEVICE_TYPE_WIFI:
case CONNMAN_DEVICE_TYPE_WIMAX:
case CONNMAN_DEVICE_TYPE_CELLULAR:
name = index2ident(index, "");
addr = index2addr(index);
break;
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
case CONNMAN_DEVICE_TYPE_GPS:
case CONNMAN_DEVICE_TYPE_VENDOR:
name = g_strdup(devname);
break;
}
if (name == NULL) {
connman_error("%s: no memory for network name", __func__);
device = NULL;
goto done;
}
device = connman_device_create(name, type);
if (device == NULL)
goto done;
switch (type) {
case CONNMAN_DEVICE_TYPE_UNKNOWN:
case CONNMAN_DEVICE_TYPE_VENDOR:
case CONNMAN_DEVICE_TYPE_GPS:
mode = CONNMAN_DEVICE_MODE_UNKNOWN;
break;
case CONNMAN_DEVICE_TYPE_ETHERNET:
mode = CONNMAN_DEVICE_MODE_TRANSPORT_IP;
ident = index2ident(index, NULL);
break;
case CONNMAN_DEVICE_TYPE_WIFI:
case CONNMAN_DEVICE_TYPE_WIMAX:
case CONNMAN_DEVICE_TYPE_CELLULAR:
mode = CONNMAN_DEVICE_MODE_NETWORK_SINGLE;
ident = index2ident(index, NULL);
break;
case CONNMAN_DEVICE_TYPE_BLUETOOTH:
mode = CONNMAN_DEVICE_MODE_NETWORK_MULTIPLE;
break;
}
connman_device_set_mode(device, mode);
connman_device_set_index(device, index);
connman_device_set_interface(device, devname, node);
if (ident != NULL) {
connman_device_set_ident(device, ident);
g_free(ident);
}
connman_device_set_string(device, "Address", addr);
done:
g_free(devname);
g_free(node);
g_free(name);
g_free(addr);
return device;
}
/*
* Set a device's address, netmask, broadcast address, and mtu.
*
* NB: We handle partially-formed address record so multiple
* incomplete ipconfig records can be concatenated.
*/
int connman_inet_set_address(int index, const struct connman_ipaddress *ipaddr)
{
struct ifreq ifr;
struct sockaddr_in addr;
int sk, err;
if (ipaddr->af != AF_INET) {
connman_error("%s: no support for af %d", __func__, ipaddr->af);
return -1;
}
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Set inet for %s: ipaddr %s bcast %s prefixlen %d mtu %d",
ifr.ifr_name, ipaddr->local, ipaddr->broadcast, ipaddr->prefixlen,
ipaddr->mtu);
if (ipaddr->mask & CONNMAN_IPCONFIG_LOCAL) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ipaddr->local, &addr.sin_addr);
memcpy(&ifr.ifr_addr, &addr, sizeof(ifr.ifr_addr));
err = ioctl(sk, SIOCSIFADDR, &ifr);
if (err < 0) {
iferror(__func__, "SIOCSIFADDR", ifr.ifr_name);
goto done;
}
}
if (ipaddr->mask & CONNMAN_IPCONFIG_PREFIX) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
/*
* For the ">>" operator, the ANSI C standard says that
* the result is undefined if the right operand is greater
* than equal to the width of the left operand, so we must
* handle prefixlen == 32 as a special case.
*/
if (ipaddr->prefixlen == 32)
addr.sin_addr.s_addr = 0xffffffff;
else
addr.sin_addr.s_addr =
htonl(~(0xfffffffflu >> ipaddr->prefixlen));
memcpy(&ifr.ifr_netmask, &addr, sizeof(ifr.ifr_netmask));
err = ioctl(sk, SIOCSIFNETMASK, &ifr);
if (err < 0) {
iferror(__func__, "SIOCSIFNETMASK", ifr.ifr_name);
goto done;
}
}
if (ipaddr->mask & CONNMAN_IPCONFIG_BCAST) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ipaddr->broadcast, &addr.sin_addr);
memcpy(&ifr.ifr_broadaddr, &addr, sizeof(ifr.ifr_broadaddr));
err = ioctl(sk, SIOCSIFBRDADDR, &ifr);
if (err < 0) {
iferror(__func__, "SIOCSIFBRDADDR", ifr.ifr_name);
goto done;
}
}
if (ipaddr->mask & CONNMAN_IPCONFIG_PEER) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ipaddr->peer, &addr.sin_addr);
memcpy(&ifr.ifr_dstaddr, &addr, sizeof(ifr.ifr_dstaddr));
err = ioctl(sk, SIOCSIFDSTADDR, &ifr);
if (err < 0) {
iferror(__func__, "SIOCSIFDSTADDR", ifr.ifr_name);
goto done;
}
/*
* The kernel creates an implicit host route to peer addresses.
* Remove that route as it may conflict with with the tunnel's
* output IP address.
*/
if (connman_inet_del_hostroute(index,
addr.sin_addr.s_addr, 0) < 0) {
connman_error("%s: unable to remove implicit peer "
"host route", __func__);
goto done;
}
}
if (ipaddr->mask & CONNMAN_IPCONFIG_MTU) {
ifr.ifr_mtu = ipaddr->mtu;
err = ioctl(sk, SIOCSIFMTU, &ifr);
if (err < 0)
iferror(__func__, "SIOCSIFMTU", ifr.ifr_name);
}
done:
close(sk);
return err;
}
/*
* Clear all non-static globally scoped IPv6 addresses from the interface
*/
static int connman_inet_clear_ipv6_address(int index)
{
int sk6;
FILE *inet6_file;
char addr6p[8][5];
char addr6[40], devname[21];
int if_idx, prefix_len, scope, flags;
int ret;
int found = 0;
/*
* TODO(pstew): This is a local definition of in6_ifreq. This
* is because the only file that defines it, linux/ipv6.h,
* does not expect to be included by user code, and causes
* compile errors
*/
struct in6_ifreq {
struct in6_addr ifr6_addr;
uint32_t ifr6_prefixlen;
int ifr6_ifindex;
} ifr6;
sk6 = getsocket(__func__, PF_INET6);
if (sk6 < 0)
return -1;
ifr6.ifr6_ifindex = index;
/*
* This is how ifconfig.c queries IPv6 addresses.
* We could use netlink, but then this code will get much bigger.
*/
if ((inet6_file = fopen("/proc/net/if_inet6", "r")) == NULL) {
close(sk6);
return -1;
}
do {
ret = fscanf(inet6_file, "%4s%4s%4s%4s%4s%4s%4s%4s "
"%x %x %x %x %20s\n",
addr6p[0], addr6p[1], addr6p[2], addr6p[3],
addr6p[4], addr6p[5], addr6p[6], addr6p[7],
&if_idx, &prefix_len, &scope, &flags,
devname);
if (ret != 13 || if_idx != ifr6.ifr6_ifindex)
continue;
/*
* If this address is not globally scoped, or has any
* funny flags on it (for example "permanent address")
* skip it. We do, however accept addresses marked as
* "temporary".
*/
if (scope != RT_SCOPE_UNIVERSE ||
(flags & ~IFA_F_TEMPORARY) != 0)
continue;
snprintf(addr6, sizeof(addr6), "%s:%s:%s:%s:%s:%s:%s:%s",
addr6p[0], addr6p[1], addr6p[2], addr6p[3],
addr6p[4], addr6p[5], addr6p[6], addr6p[7]);
if (inet_pton(AF_INET6, addr6, &ifr6.ifr6_addr) < 0) {
connman_error("%s: inet_pton %s %s", __func__,
addr6, strerror(errno));
continue;
}
ifr6.ifr6_prefixlen = prefix_len;
if (ioctl(sk6, SIOCDIFADDR, &ifr6) < 0) {
connman_error("%s: SIOCDIFADDR %s/%d %s", __func__,
addr6, prefix_len, strerror(errno));
continue;
}
connman_info("Clear inet6 address %s/%d\n", addr6, prefix_len);
found++;
} while (ret != EOF);
fclose(inet6_file);
close(sk6);
return found;
}
/*
* Clear IPv4 addresss from the interface
*/
int connman_inet_clear_ipv4_address(int index)
{
struct ifreq ifr;
struct sockaddr_in addr;
int sk, err;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Clear inet for %s", ifr.ifr_name);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
memcpy(&ifr.ifr_addr, &addr, sizeof(ifr.ifr_addr));
//err = ioctl(sk, SIOCDIFADDR, &ifr);
err = ioctl(sk, SIOCSIFADDR, &ifr);
if (err < 0) {
if (errno != EADDRNOTAVAIL)
iferror(__func__, "SIOCSIFADDR", ifr.ifr_name);
else
err = 0; /* NB: ignore error */
}
done:
close(sk);
return err;
}
/*
* Clear all addrconf-created IPv6 routes from the interface
*/
static void connman_inet_clear_ipv6_route(int index)
{
struct connman_rtnl_reader *rt;
struct in6_addr dst, src, gateway;
unsigned char src_len, dst_len;
struct in6_rtmsg msg;
int sk6;
unsigned int flags;
sk6 = getsocket(__func__, PF_INET6);
if (sk6 < 0)
connman_error("%s: socket: %s", __func__, strerror(errno));
rt = connman_rtnl_open_route(AF_INET6, NLM_F_DUMP, NULL, index);
if (rt == NULL)
goto done;
while (connman_rtnl_read_route(rt, NULL, &dst_len, &src_len,
&dst, &src, &gateway, &flags)) {
if (IN6_IS_ADDR_LINKLOCAL(&dst) || IN6_IS_ADDR_MULTICAST(&dst))
continue;
memset(&msg, 0, sizeof(msg));
msg.rtmsg_type = RTMSG_DELROUTE;
msg.rtmsg_dst = dst;
msg.rtmsg_dst_len = dst_len;
msg.rtmsg_src = src;
msg.rtmsg_src_len = src_len;
msg.rtmsg_gateway = gateway;
msg.rtmsg_ifindex = index;
/* NB: Fail silently since this operation is racy */
ioctl(sk6, SIOCDELRT, &msg);
}
connman_rtnl_close(rt);
done:
close(sk6);
}
/*
* Clear any address associated with a device.
*/
void connman_inet_clear_address(int index)
{
connman_inet_clear_ipv4_address(index);
}
static void connman_inet_set_ipv6_disabled(int index, int value)
{
struct ifreq ifr;
int sk, err;
char *filename;
FILE *autoconf_file = NULL;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return;
err = getifname(sk, index, &ifr, __func__);
close(sk);
if (err < 0)
return;
filename = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/disable_ipv6",
ifr.ifr_name);
autoconf_file = fopen(filename, "w");
g_free(filename);
if (autoconf_file == NULL) {
connman_error("%s: Open autoconf for %s: %s",
__func__, ifr.ifr_name, strerror(errno));
return;
}
fprintf(autoconf_file, "%d", value);
fclose(autoconf_file);
}
void connman_inet_enable_ipv6(int index)
{
/*
* Kicking this file ensures that IPv6 address configuration starts
* immediately.
*/
connman_inet_set_ipv6_disabled(index, 0);
}
void connman_inet_disable_ipv6(int index)
{
/*
* Since we never "down" the interface, IPv6 addresses assigned
* to this interface will persist unless we remove them explicitly.
* Not doing so will confuse client applications that use the
* existence of a globally scoped IPv6 address to determine whether
* to connect to IPv6 vs IPv4 addresses.
*/
connman_inet_set_ipv6_disabled(index, 1);
connman_inet_clear_ipv6_address(index);
connman_inet_clear_ipv6_route(index);
}
/*
* Set IPv6 privacy on a network interface
*/
void connman_inet_set_ipv6_privacy(const char *ifname)
{
char *file = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/use_tempaddr",
ifname);
FILE *privacy_file = fopen(file, "w");
g_free(file);
if (privacy_file == NULL) {
connman_error("%s: Open use_tempaddr for %s: %s",
__func__, ifname, strerror(errno));
return;
}
if (fwrite("2", 1, 1, privacy_file) != 1) {
connman_error("%s: Write use_tempaddr for %s: %s",
__func__, ifname, strerror(errno));
}
fclose(privacy_file);
}
static void setup_rt(struct rtentry *rt, int flags, in_addr_t *dst,
in_addr_t *gw, in_addr_t *mask, short metric)
{
struct sockaddr_in addr;
memset(rt, 0, sizeof(*rt));
rt->rt_flags = flags;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = (dst != NULL ? *dst : INADDR_ANY);
memcpy(&rt->rt_dst, &addr, sizeof(rt->rt_dst));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = (gw != NULL ? *gw : INADDR_ANY);
memcpy(&rt->rt_gateway, &addr, sizeof(rt->rt_gateway));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = (mask != NULL ? *mask : INADDR_ANY);
memcpy(&rt->rt_genmask, &addr, sizeof(rt->rt_genmask));
rt->rt_metric = metric;
}
/*
* Route packets through the specified device.
* Both host and (default) gateway routes are installed.
*/
int connman_inet_set_gateway(int index, in_addr_t gateway, short metric)
{
struct ifreq ifr;
struct rtentry rt;
int sk, err;
char gwbuf[16];
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Set default route for %s: gw %s metric %d",
ifr.ifr_name,
inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)),
metric);
/*
* Linux requires an existing route for any gateway (and it
* must use the same interface). Satisfy this by always
* creating a host route.
*/
setup_rt(&rt, RTF_UP | RTF_HOST, &gateway, NULL, NULL, metric);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCADDRT, &rt);
if (err < 0 && errno != EEXIST) {
iferror(__func__, "SIOCADDRT (host)", ifr.ifr_name);
goto done;
}
setup_rt(&rt, RTF_UP | RTF_GATEWAY, NULL, &gateway, NULL, metric);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCADDRT, &rt);
if (err < 0) {
if (errno != EEXIST)
iferror(__func__, "SIOCADDRT (gw)", ifr.ifr_name);
else
err = 0;
}
/* Delete the unnecessary host route we created above. */
setup_rt(&rt, RTF_UP | RTF_HOST, &gateway, NULL, NULL, metric);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCDELRT, &rt);
if (err < 0)
iferror(__func__, "SIOCDELRT (host)", ifr.ifr_name);
done:
close(sk);
return err;
}
/*
* Clear routing state installed by connman_inet_set_gateway.
*/
int connman_inet_del_gateway(int index, in_addr_t gateway, short metric)
{
struct ifreq ifr;
struct rtentry rt;
int sk, err;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Delete default route through %s metric %d",
ifr.ifr_name, metric);
setup_rt(&rt, RTF_UP | RTF_GATEWAY, NULL, &gateway, NULL, metric);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCDELRT, &rt);
if (err < 0)
iferror(__func__, "SIOCDELRT (gw)", ifr.ifr_name);
done:
close(sk);
return err;
}
int connman_inet_add_route(int index, in_addr_t ipaddr, in_addr_t netmask,
in_addr_t gateway)
{
struct ifreq ifr;
struct rtentry rt;
int sk, err;
char addrbuf[16], maskbuf[16], gwbuf[16];
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Add network route through %s: ip %s mask %s gw %s",
ifr.ifr_name,
inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
inet_ntop(AF_INET, &netmask, maskbuf, sizeof(maskbuf)),
inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));
setup_rt(&rt, RTF_UP | RTF_GATEWAY, &ipaddr, &gateway, &netmask, 0);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCADDRT, &rt);
if (err < 0) {
if (errno != EEXIST)
iferror(__func__, "SIOCADDRT (network)", ifr.ifr_name);
else
err = 0;
}
done:
close(sk);
return err;
}
int connman_inet_add_hostroute(int index, in_addr_t ipaddr, in_addr_t gateway)
{
struct ifreq ifr;
struct rtentry rt;
int sk, err;
char addrbuf[16], gwbuf[16];
int flags;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Add host route through %s: ip %s gw %s", ifr.ifr_name,
inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));
flags = RTF_UP | RTF_HOST;
if (gateway != INADDR_ANY)
flags |= RTF_GATEWAY;
setup_rt(&rt, flags, &ipaddr, &gateway, NULL, 0);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCADDRT, &rt);
if (err < 0) {
if (errno != EEXIST)
iferror(__func__, "SIOCADDRT (host)", ifr.ifr_name);
else
err = 0;
}
done:
close(sk);
return err;
}
/* TODO(kmixter) support IPv6 */
connman_bool_t connman_inet_get_route(in_addr_t dest, uint32_t *index,
in_addr_t *gateway, in_addr_t *src)
{
connman_bool_t ret;
struct connman_rtnl_reader *rt =
connman_rtnl_open_route(AF_INET, 0, &dest, -1);
if (rt == NULL)
return FALSE;
ret = connman_rtnl_read_route(rt, index, NULL, NULL, NULL,
src, gateway, NULL);
connman_rtnl_close(rt);
return ret;
}
int connman_inet_del_hostroute(int index, in_addr_t ipaddr, in_addr_t gateway)
{
struct ifreq ifr;
struct rtentry rt;
int sk, err;
char addrbuf[16], gwbuf[16];
int flags;
sk = getsocket(__func__, PF_INET);
if (sk < 0)
return -1;
err = getifname(sk, index, &ifr, __func__);
if (err < 0)
goto done;
connman_info("Del host route through %s: ip %s gw %s", ifr.ifr_name,
inet_ntop(AF_INET, &ipaddr, addrbuf, sizeof(addrbuf)),
inet_ntop(AF_INET, &gateway, gwbuf, sizeof(gwbuf)));
flags = RTF_UP | RTF_HOST;
if (gateway != INADDR_ANY)
flags |= RTF_GATEWAY;
setup_rt(&rt, flags, &ipaddr, &gateway, NULL, 0);
rt.rt_dev = ifr.ifr_name;
err = ioctl(sk, SIOCDELRT, &rt);
if (err < 0) {
if (errno != EEXIST)
iferror(__func__, "SIOCDELRT (host)", ifr.ifr_name);
else
err = 0;
}
done:
close(sk);
return err;
}