/*
* iface.c - Network interface configuration API
*
* Copyright (C) 2006, 2007, 2008 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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, see .
*
* Author(s): David Cantrell
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "iface.h"
#include "str.h"
/* Internal-only function prototypes. */
static struct nl_handle *_iface_get_handle(void);
static struct nl_cache *_iface_get_link_cache(struct nl_handle **);
static int _iface_have_valid_addr(void *addr, int family, int length);
static int _iface_redirect_io(char *device, int fd, int mode);
/*
* Return a libnl handle for NETLINK_ROUTE.
*/
static struct nl_handle *_iface_get_handle(void) {
struct nl_handle *handle = NULL;
if ((handle = nl_handle_alloc()) == NULL) {
return NULL;
}
if (nl_connect(handle, NETLINK_ROUTE)) {
nl_handle_destroy(handle);
return NULL;
}
return handle;
}
/*
* Return an NETLINK_ROUTE cache.
*/
static struct nl_cache *_iface_get_link_cache(struct nl_handle **handle) {
struct nl_cache *cache = NULL;
if ((*handle = _iface_get_handle()) == NULL) {
return NULL;
}
if ((cache = rtnl_link_alloc_cache(*handle)) == NULL) {
nl_close(*handle);
nl_handle_destroy(*handle);
return NULL;
}
return cache;
}
/*
* Determine if a struct in_addr or struct in6_addr contains a valid address.
*/
static int _iface_have_valid_addr(void *addr, int family, int length) {
char buf[length+1];
if ((addr == NULL) || (family != AF_INET && family != AF_INET6)) {
return 0;
}
memset(buf, '\0', sizeof(buf));
if (inet_ntop(family, addr, buf, length) == NULL) {
return 0;
} else {
/* check for unknown addresses */
if (family == AF_INET) {
if (!strncmp(buf, "0.0.0.0", 7)) {
return 0;
}
} else if (family == AF_INET6) {
if (!strncmp(buf, "::", 2)) {
return 0;
}
}
}
return 1;
}
/*
* Redirect I/O to another device (e.g., stdout to /dev/tty5)
*/
int _iface_redirect_io(char *device, int fd, int mode) {
int io = -1;
if ((io = open(device, mode)) == -1) {
return 1;
}
if (close(fd) == -1) {
return 2;
}
if (dup2(io, fd) == -1) {
return 3;
}
if (close(io) == -1) {
return 4;
}
return 0;
}
/*
* Given an interface name (e.g., eth0) and address family (e.g., AF_INET),
* return the IP address in human readable format (i.e., the output from
* inet_ntop()). Return NULL for no match or error.
*/
char *iface_ip2str(char *ifname, int family) {
char *ipaddr = NULL;
char *nm_iface = NM_DBUS_INTERFACE;
char *property = NULL;
char *device_path = NULL;
char *interface = NULL;
struct in_addr addr;
DBusConnection *connection = NULL;
DBusError error;
DBusMessage *message = NULL, *reply = NULL, *devreply = NULL;
DBusMessageIter iter, a_iter, d_iter, v_iter;
if (ifname == NULL) {
return NULL;
}
/* DCFIXME: add IPv6 once NM gains support */
if (family != AF_INET) {
return NULL;
}
dbus_error_init(&error);
connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
if (connection == NULL) {
dbus_error_free(&error);
return NULL;
}
message = dbus_message_new_method_call(NM_DBUS_SERVICE,
NM_DBUS_PATH,
NM_DBUS_SERVICE,
"GetDevices");
if (!message) {
return NULL;
}
reply = dbus_connection_send_with_reply_and_block(connection,
message,
-1, &error);
dbus_message_unref(message);
if (!reply) {
return NULL;
}
dbus_message_iter_init(reply, &iter);
dbus_message_iter_recurse(&iter, &a_iter);
while (dbus_message_iter_get_arg_type(&a_iter) != DBUS_TYPE_INVALID) {
dbus_message_iter_get_basic(&a_iter, &device_path);
message = dbus_message_new_method_call(NM_DBUS_SERVICE,
device_path,
DBUS_INTERFACE_PROPERTIES,
"Get");
if (!message) {
return NULL;
}
property = "Interface";
if (!dbus_message_append_args(message,
DBUS_TYPE_STRING, &nm_iface,
DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) {
dbus_message_unref(message);
return NULL;
}
devreply = dbus_connection_send_with_reply_and_block(connection,
message,
-1, &error);
dbus_message_unref(message);
if (!devreply) {
continue;
}
dbus_message_iter_init(devreply, &d_iter);
while (dbus_message_iter_get_arg_type(&d_iter) != DBUS_TYPE_INVALID) {
dbus_message_iter_recurse(&d_iter, &v_iter);
dbus_message_iter_get_basic(&v_iter, &interface);
if (!strcmp(ifname, interface)) {
message = dbus_message_new_method_call(NM_DBUS_SERVICE,
device_path, DBUS_INTERFACE_PROPERTIES, "Get");
if (!message) {
return NULL;
}
if (family == AF_INET) {
property = "Ip4Address";
}
if (!dbus_message_append_args(message,
DBUS_TYPE_STRING, &nm_iface,
DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) {
dbus_message_unref(message);
return NULL;
}
devreply = dbus_connection_send_with_reply_and_block(connection,
message, -1, &error);
dbus_message_unref(message);
if (!devreply) {
return NULL;
}
dbus_message_iter_init(devreply, &d_iter);
dbus_message_iter_recurse(&d_iter, &v_iter);
if (dbus_message_iter_get_arg_type(&v_iter)==DBUS_TYPE_UINT32) {
memset(&addr, 0, sizeof(addr));
dbus_message_iter_get_basic(&v_iter, &addr.s_addr);
if ((ipaddr = malloc(INET_ADDRSTRLEN+1)) == NULL) {
abort();
}
if (inet_ntop(family, &addr, ipaddr,
INET_ADDRSTRLEN) == NULL) {
abort();
}
dbus_connection_unref(connection);
return ipaddr;
}
}
dbus_message_iter_next(&d_iter);
}
dbus_message_iter_next(&a_iter);
}
dbus_connection_unref(connection);
return NULL;
}
/*
* Given an interface name (e.g., eth0), return the MAC address in human
* readable format (e.g., 00:11:52:12:D9:A0). Return NULL for no match.
*/
char *iface_mac2str(char *ifname) {
int buflen = 20;
char *buf = NULL;
struct nl_handle *handle = NULL;
struct nl_cache *cache = NULL;
struct rtnl_link *link = NULL;
struct nl_addr *addr = NULL;
if (ifname == NULL) {
return NULL;
}
if ((cache = _iface_get_link_cache(&handle)) == NULL) {
return NULL;
}
if ((link = rtnl_link_get_by_name(cache, ifname)) == NULL) {
goto mac2str_error2;
}
if ((addr = rtnl_link_get_addr(link)) == NULL) {
goto mac2str_error3;
}
if ((buf = calloc(sizeof(char *), buflen)) == NULL) {
goto mac2str_error4;
}
if ((buf = nl_addr2str(addr, buf, buflen)) != NULL) {
buf = str2upper(buf);
}
mac2str_error4:
nl_addr_destroy(addr);
mac2str_error3:
rtnl_link_put(link);
mac2str_error2:
nl_close(handle);
nl_handle_destroy(handle);
return buf;
}
/*
* Convert an IPv4 CIDR prefix to a dotted-quad netmask. Return NULL on
* failure.
*/
struct in_addr *iface_prefix2netmask(int prefix) {
int mask = 0;
char *buf = NULL;
struct in_addr *ret;
if ((buf = calloc(sizeof(char *), INET_ADDRSTRLEN + 1)) == NULL) {
return NULL;
}
mask = htonl(~((1 << (32 - prefix)) - 1));
if (inet_ntop(AF_INET, (struct in_addr *) &mask, buf,
INET_ADDRSTRLEN) == NULL) {
return NULL;
}
if ((ret = calloc(sizeof(struct in_addr), 1)) == NULL) {
return NULL;
}
memcpy(ret, (struct in_addr *) &mask, sizeof(struct in_addr));
return ret;
}
/*
* Convert an IPv4 netmask to an IPv4 CIDR prefix. Return -1 on failure.
*/
int iface_netmask2prefix(struct in_addr *netmask) {
int ret = -1;
struct in_addr mask;
if (netmask == NULL) {
return -1;
}
memcpy(&mask, netmask, sizeof(struct in_addr));
while (mask.s_addr != 0) {
mask.s_addr = mask.s_addr >> 1;
ret++;
}
return ret;
}
/*
* Initialize a new iface_t structure to default values.
*/
void iface_init_iface_t(iface_t *iface) {
int i;
memset(&iface->device, '\0', sizeof(iface->device));
memset(&iface->ipaddr, 0, sizeof(iface->ipaddr));
memset(&iface->netmask, 0, sizeof(iface->netmask));
memset(&iface->broadcast, 0, sizeof(iface->broadcast));
memset(&iface->ip6addr, 0, sizeof(iface->ip6addr));
memset(&iface->gateway, 0, sizeof(iface->gateway));
memset(&iface->gateway6, 0, sizeof(iface->gateway6));
for (i = 0; i < MAXNS; i++) {
iface->dns[i] = NULL;
}
iface->macaddr = NULL;
iface->ip6prefix = 0;
iface->nextserver = NULL;
iface->bootfile = NULL;
iface->numdns = 0;
iface->hostname = NULL;
iface->domain = NULL;
iface->search = NULL;
iface->dhcptimeout = 0;
iface->vendorclass = NULL;
iface->ssid = NULL;
iface->wepkey = NULL;
iface->mtu = 0;
iface->subchannels = NULL;
iface->portname = NULL;
iface->peerid = NULL;
iface->nettype = NULL;
iface->ctcprot = NULL;
iface->flags = 0;
iface->ipv4method = IPV4_UNUSED_METHOD;
iface->ipv6method = IPV6_UNUSED_METHOD;
return;
}
/*
* Given a pointer to a struct in_addr, return 1 if it contains a valid
* address, 0 otherwise.
*/
int iface_have_in_addr(struct in_addr *addr) {
return _iface_have_valid_addr(addr, AF_INET, INET_ADDRSTRLEN);
}
/*
* Given a pointer to a struct in6_addr, return 1 if it contains a valid
* address, 0 otherwise.
*/
int iface_have_in6_addr(struct in6_addr *addr6) {
return _iface_have_valid_addr(addr6, AF_INET6, INET6_ADDRSTRLEN);
}
/* Check if NM is already running */
int is_nm_running(DBusConnection *connection, int *running, char **error_str)
{
DBusError error;
DBusMessage *message, *reply;
const char *nm_service = NM_DBUS_SERVICE;
dbus_bool_t alive = FALSE;
message = dbus_message_new_method_call("org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"NameHasOwner");
if (!message)
return 33;
if (!dbus_message_append_args(message,
DBUS_TYPE_STRING, &nm_service,
DBUS_TYPE_INVALID)) {
dbus_message_unref(message);
return 34;
}
dbus_error_init(&error);
reply = dbus_connection_send_with_reply_and_block(connection,
message, 2000,
&error);
if (!reply) {
if (dbus_error_is_set(&error)) {
*error_str = strdup(error.message);
dbus_error_free(&error);
}
dbus_message_unref(message);
return 35;
}
dbus_error_init(&error);
if (!dbus_message_get_args(reply, &error,
DBUS_TYPE_BOOLEAN, &alive,
DBUS_TYPE_INVALID)) {
if (dbus_error_is_set(&error)) {
*error_str = strdup(error.message);
dbus_error_free(&error);
}
dbus_message_unref(message);
dbus_message_unref(reply);
return 36;
}
*running = alive;
dbus_message_unref(message);
dbus_message_unref(reply);
return 0;
}
/*
* Wait for NetworkManager to appear on the system bus
*/
int wait_for_nm(DBusConnection *connection, char **error_str) {
int count = 0;
/* send message and block until a reply or error comes back */
while (count < 45) {
int running = 0, ret;
ret = is_nm_running(connection, &running, error_str);
if (ret != 0)
return ret; /* error */
if (running)
return 0; /* nm is alive */
sleep(1);
count++;
}
return 37;
}
/*
* Start NetworkManager -- requires that you have already written out the
* control files in /etc/sysconfig for the interface.
*/
int iface_start_NetworkManager(DBusConnection *connection, char **error) {
pid_t pid;
int ret, running = 0;
char *ignore = NULL;
ret = is_nm_running(connection, &running, &ignore);
if (ignore)
free(ignore);
if (ret == 0 && running)
return 0; /* already running */
/* Start NetworkManager */
pid = fork();
if (pid == 0) {
if (setpgrp() == -1) {
exit(1);
}
if (_iface_redirect_io("/dev/null", STDIN_FILENO, O_RDONLY) ||
_iface_redirect_io(OUTPUT_TERMINAL, STDOUT_FILENO, O_WRONLY) ||
_iface_redirect_io(OUTPUT_TERMINAL, STDERR_FILENO, O_WRONLY)) {
exit(2);
}
if (execl(NETWORKMANAGER, NETWORKMANAGER,
"--pid-file=/var/run/NetworkManager/NetworkManager.pid",
NULL) == -1) {
exit(3);
}
} else if (pid == -1) {
return 1;
} else {
return wait_for_nm(connection, error);
}
return 0;
}
/*
* Set the MTU on the specified device.
*/
int iface_set_interface_mtu(char *ifname, int mtu) {
int ret = 0;
struct nl_handle *handle = NULL;
struct nl_cache *cache = NULL;
struct rtnl_link *link = NULL;
struct rtnl_link *request = NULL;
if (ifname == NULL) {
return -1;
}
if (mtu <= 0) {
return -2;
}
if ((cache = _iface_get_link_cache(&handle)) == NULL) {
return -3;
}
if ((link = rtnl_link_get_by_name(cache, ifname)) == NULL) {
ret = -4;
goto ifacemtu_error1;
}
request = rtnl_link_alloc();
rtnl_link_set_mtu(request, mtu);
if (rtnl_link_change(handle, link, request, 0)) {
ret = -5;
goto ifacemtu_error2;
}
ifacemtu_error2:
rtnl_link_put(link);
ifacemtu_error1:
nl_close(handle);
nl_handle_destroy(handle);
return ret;
}