qubes-installer-qubes-os/isys/iface.c
2011-01-18 04:24:57 -05:00

543 lines
12 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Author(s): David Cantrell <dcantrell@redhat.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <netinet/in.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/route/rtnl.h>
#include <netlink/route/route.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <glib.h>
#include <NetworkManager.h>
#include <nm-client.h>
#include <nm-device.h>
#include <nm-ip4-config.h>
#include <nm-setting-ip4-config.h>
#include "isys.h"
#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) {
int i;
NMClient *client = NULL;
NMIP4Config *ip4config = NULL;
NMIP4Address *ipaddr = NULL;
NMDevice *candidate = NULL;
struct in_addr tmp_addr;
const GPtrArray *devices;
const char *iface;
char ipstr[INET_ADDRSTRLEN+1];
if (ifname == NULL) {
return NULL;
}
/* DCFIXME: add IPv6 once NM gains support */
if (family != AF_INET) {
return NULL;
}
g_type_init();
client = nm_client_new();
if (!client) {
return NULL;
}
if (nm_client_get_state(client) != NM_STATE_CONNECTED) {
g_object_unref(client);
return NULL;
}
devices = nm_client_get_devices(client);
for (i=0; i < devices->len; i++) {
candidate = g_ptr_array_index(devices, i);
iface = nm_device_get_iface(candidate);
if (nm_device_get_state(candidate) != NM_DEVICE_STATE_ACTIVATED)
continue;
if (!iface || strcmp(iface, ifname))
continue;
if (!(ip4config = nm_device_get_ip4_config(candidate)))
continue;
if (!(ipaddr = nm_ip4_config_get_addresses(ip4config)->data))
continue;
memset(&ipstr, '\0', sizeof(ipstr));
tmp_addr.s_addr = nm_ip4_address_get_address(ipaddr);
if (inet_ntop(AF_INET, &tmp_addr, ipstr, INET_ADDRSTRLEN) == NULL) {
g_object_unref(client);
return NULL;
}
g_object_unref(client);
return g_strdup(ipstr);
}
g_object_unref(client);
return NULL;
}
/* Given an interface's MAC address, return the name (e.g., eth0) in human
* readable format. Return NULL for no match
*/
char *iface_mac2device(char *mac) {
struct nl_handle *handle = NULL;
struct nl_cache *cache = NULL;
struct rtnl_link *link = NULL;
struct nl_addr *mac_as_nl_addr = NULL;
char *retval = NULL;
int i, n;
if (mac == NULL) {
return NULL;
}
if ((mac_as_nl_addr = nl_addr_parse(mac, AF_LLC)) == NULL) {
return NULL;
}
if ((cache = _iface_get_link_cache(&handle)) == NULL) {
return NULL;
}
n = nl_cache_nitems(cache);
for (i = 0; i <= n; i++) {
struct nl_addr *addr;
if ((link = rtnl_link_get(cache, i)) == NULL) {
continue;
}
addr = rtnl_link_get_addr(link);
if (!nl_addr_cmp(mac_as_nl_addr, addr)) {
retval = strdup(rtnl_link_get_name(link));
rtnl_link_put(link);
break;
}
rtnl_link_put(link);
}
nl_close(handle);
nl_handle_destroy(handle);
return retval;
}
/*
* 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;
}
/*
* 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->layer2 = NULL;
iface->portno = 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 has an active connection */
gboolean is_nm_connected(void) {
NMState state;
NMClient *client = NULL;
g_type_init();
client = nm_client_new();
if (!client)
return FALSE;
state = nm_client_get_state(client);
g_object_unref(client);
if (state == NM_STATE_CONNECTED)
return TRUE;
else
return FALSE;
}
/* Check if NM is already running */
gboolean is_nm_running(void) {
gboolean running;
NMClient *client = NULL;
g_type_init();
client = nm_client_new();
if (!client)
return FALSE;
running = nm_client_get_manager_running(client);
g_object_unref(client);
return running;
}
/*
* Wait for NetworkManager to appear on the system bus
*/
int wait_for_nm(void) {
int count = 0;
/* send message and block until a reply or error comes back */
while (count < 45) {
if (is_nm_running())
return 0;
sleep(1);
count++;
}
return 1;
}
/*
* Start NetworkManager -- requires that you have already written out the
* control files in /etc/sysconfig for the interface.
*/
int iface_start_NetworkManager(void) {
pid_t pid;
if (is_nm_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();
}
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;
}