NetworkManager/src/autoip.c
Dan Williams 6cfaab9c0c 2008-03-13 Dan Williams <dcbw@redhat.com>
* NetworkManagerUtils.c
	  NetworkManagerUtils.h
		- Remove NMSock stuff
		- Remove the completion stuff

	* nm-device.c
	  nm-device.h
	  NetworkManager.c
	  NetworkManagerSystem.c
	  autoip.c
	  nm-device-802-11-wireless.c
	  nm-device-802-3-ethernet.c
		- Remove NMSock and completion stuff
		- Remove nm_ioctl_info()



git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@3448 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2008-03-13 19:31:08 +00:00

351 lines
10 KiB
C

// Based upon http://www.zeroconf.org/AVH-IPv4LL.c
// Merged into NetworkManager by Tom Parker <palfrey@tevp.net>
// Original copyright continues below
//
// ----------------------------------
// Simple IPv4 Link-Local addressing (see <http://www.zeroconf.org/>)
// @(#)llip.c, 1.5, Copyright 2003 by Arthur van Hoff (avh@strangeberry.com)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// See <http://www.gnu.org/copyleft/lesser.html>
//
// This library 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
// Lesser General Public License for more details.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <netinet/ether.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <syslog.h>
#include <glib.h>
#include <unistd.h>
#include "NetworkManager.h"
#include "nm-device.h"
#include "nm-device-802-3-ethernet.h"
#include "nm-device-802-11-wireless.h"
#include "NetworkManagerUtils.h"
#include "nm-utils.h"
#include "autoip.h"
// Times here are in seconds
#define LINKLOCAL_ADDR 0xa9fe0000
#define LINKLOCAL_BCAST 0xa9feffff
#define PROBE_NUM 3
#define PROBE_MIN 1
#define PROBE_MAX 2
#define ANNOUNCE_NUM 3
#define ANNOUNCE_INTERVAL 2
#define ANNOUNCE_WAIT 2
#define FAILURE_TIMEOUT 14
typedef struct EtherHeader
{
u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
u_int16_t ether_type; /* packet type ID field */
} __attribute__((packed)) EtherHeader;
typedef struct ARPMessage
{
EtherHeader ethhdr;
u_short htype; /* hardware type (must be ARPHRD_ETHER) */
u_short ptype; /* protocol type (must be ETHERTYPE_IP) */
u_char hlen; /* hardware address length (must be 6) */
u_char plen; /* protocol address length (must be 4) */
u_short operation; /* ARP opcode */
u_char sHaddr[ETH_ALEN]; /* sender's hardware address */
u_char sInaddr[4]; /* sender's IP address */
u_char tHaddr[ETH_ALEN]; /* target's hardware address */
u_char tInaddr[4]; /* target's IP address */
u_char pad[18]; /* pad for min. Ethernet payload (60 bytes) */
} __attribute__((packed)) ARPMessage;
// Times here are in seconds
#define ARP_DEFAULT_LEASETIME 100
static struct in_addr null_ip = {0};
static struct ether_addr null_addr = {{0, 0, 0, 0, 0, 0}};
static struct ether_addr broadcast_addr = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
/**
* Pick a random link local IP address.
*/
static void pick (struct in_addr *ip)
{
ip->s_addr = htonl (LINKLOCAL_ADDR | ((abs(random()) % 0xFD00) + (abs(random()) % 0x0100)));
/* Make sure we don't use 0xFF or 0x00 anywhere */
while (((ip->s_addr & 0x0000FF00) == 0xFF00) || ((ip->s_addr & 0x0000FF00) == 0x0000))
ip->s_addr = (ip->s_addr & 0xFFFF00FF) + (abs(random()) && 0xFFFF);
while (((ip->s_addr & 0x000000FF) == 0xFF) || ((ip->s_addr & 0x000000FF) == 0x00))
ip->s_addr = (ip->s_addr & 0xFFFFFF00) + (abs(random()) && 0xFF);
}
/**
* Send out an ARP packet.
*/
static gboolean arp(int fd, struct sockaddr *saddr, int op,
struct ether_addr *source_addr, struct in_addr source_ip,
struct ether_addr *target_addr, struct in_addr target_ip)
{
struct ARPMessage p;
gboolean success = FALSE;
memset (&p, 0, sizeof (p));
/* ether header */
p.ethhdr.ether_type = htons (ETHERTYPE_ARP);
memcpy (p.ethhdr.ether_shost, source_addr, ETH_ALEN);
memcpy (p.ethhdr.ether_dhost, &broadcast_addr, ETH_ALEN);
/* arp request */
p.htype = htons (ARPHRD_ETHER);
p.ptype = htons (ETHERTYPE_IP);
p.hlen = ETH_ALEN;
p.plen = 4;
p.operation = htons (op);
memcpy (&p.sHaddr, source_addr, ETH_ALEN);
memcpy (&p.sInaddr, &source_ip, sizeof (p.sInaddr));
memcpy (&p.tHaddr, target_addr, ETH_ALEN);
memcpy (&p.tInaddr, &target_ip, sizeof (p.tInaddr));
/* send it */
if (sendto (fd, &p, sizeof (p), 0, saddr, sizeof (*saddr)) < 0)
nm_warning ("autoip ARP sendto() failed.");
else
success = TRUE;
return success;
}
/*****************************************************************************/
/* Subtract the `struct timeval' values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0. */
static int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
{
/* Perform the carry for the later subtraction by updating Y. */
if (x->tv_usec < y->tv_usec)
{
int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
y->tv_usec -= 1000000 * nsec;
y->tv_sec += nsec;
}
if (x->tv_usec - y->tv_usec > 1000000)
{
int nsec = (x->tv_usec - y->tv_usec) / 1000000;
y->tv_usec += 1000000 * nsec;
y->tv_sec -= nsec;
}
/* Compute the time remaining to wait.
`tv_usec' is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - y->tv_usec;
/* Return 1 if result is negative. */
return x->tv_sec < y->tv_sec;
}
enum return_vals
{
RET_ERROR = 0,
RET_TIMEOUT,
RET_CEASED,
RET_SUCCESS
};
/*****************************************************************************/
/* "timeout" should be the future point in time when we wish to stop
* checking for data on the socket.
*/
static int peekfd (NMDevice *dev, int sk, struct timeval *timeout)
{
struct timeval diff;
struct timeval now;
/* Wake up each second to check whether or not we've been told
* to stop with iface->cease and check our timeout.
*/
gettimeofday (&now, NULL);
// nm_info ("autoip waiting for data, overall timeout = {%ds, %dus}\n", (int)timeout->tv_sec, (int)timeout->tv_usec);
while (timeval_subtract (&diff, timeout, &now) == 0)
{
fd_set fs;
struct timeval wait = {1, 0};
// nm_info ("autoip waiting for data, remaining timeout = {%ds, %dus}\n", (int)diff.tv_sec, (int)diff.tv_usec);
FD_ZERO (&fs);
FD_SET (sk, &fs);
if (select (sk+1, &fs, NULL, NULL, &wait) == -1)
return RET_ERROR;
if (FD_ISSET(sk, &fs))
return RET_SUCCESS;
gettimeofday (&now, NULL);
};
return RET_TIMEOUT;
}
gboolean get_autoip (NMDevice *dev, struct in_addr *out_ip)
{
struct sockaddr saddr;
ARPMessage p;
struct ether_addr addr;
struct in_addr ip = {0};
int fd = -1;
int nprobes = 0;
int nannounce = 0;
gboolean success = FALSE;
const char *iface;
g_return_val_if_fail (dev != NULL, FALSE);
g_return_val_if_fail (out_ip != NULL, FALSE);
out_ip->s_addr = 0;
iface = nm_device_get_iface (dev);
/* initialize saddr */
memset (&saddr, 0, sizeof (saddr));
strncpy (saddr.sa_data, iface, sizeof (saddr.sa_data));
if (NM_IS_DEVICE_802_3_ETHERNET (dev))
nm_device_802_3_ethernet_get_address (NM_DEVICE_802_3_ETHERNET (dev), &addr);
else if (NM_IS_DEVICE_802_11_WIRELESS (dev))
nm_device_802_11_wireless_get_address (NM_DEVICE_802_11_WIRELESS (dev), &addr);
else
goto out;
/* open an ARP socket */
fd = socket (AF_PACKET, SOCK_PACKET, htons (ETH_P_ALL));
if (fd < 0) {
nm_warning ("%s: Couldn't open network control socket.", iface);
goto out;
}
/* bind to the ARP socket */
if (bind (fd, &saddr, sizeof (saddr)) < 0) {
nm_warning ("%s: Couldn't bind to the device.", iface);
goto out;
}
/* initialize pseudo random selection of IP addresses */
srandom ( (addr.ether_addr_octet[ETHER_ADDR_LEN-4] << 24) |
(addr.ether_addr_octet[ETHER_ADDR_LEN-3] << 16) |
(addr.ether_addr_octet[ETHER_ADDR_LEN-2] << 8) |
(addr.ether_addr_octet[ETHER_ADDR_LEN-1] << 0));
/* pick an ip address */
if (ip.s_addr == 0)
pick (&ip);
while (1)
{
struct timeval timeout;
int err;
if (nprobes < PROBE_NUM)
{
nm_info ("autoip: Sending probe #%d for IP address %s.", nprobes, inet_ntoa (ip));
arp (fd, &saddr, ARPOP_REQUEST, &addr, null_ip, &null_addr, ip);
nprobes++;
gettimeofday (&timeout, NULL);
if (nprobes == PROBE_NUM)
{
/* Link local specifies a different interval between
* the end of probe requests and announce packets.
*/
timeout.tv_sec += ANNOUNCE_WAIT;
}
else
{
unsigned int usecs_to_sleep = ((PROBE_MAX - PROBE_MIN) * 1000000) - 1;
/* We want to sleep between PROBE_MIN and PROBE_MAX seconds, exclusive */
timeout.tv_sec += PROBE_MIN;
timeout.tv_usec += 1 + (random () % usecs_to_sleep);
}
}
else if (nannounce < ANNOUNCE_NUM)
{
nm_info ("autoip: Sending announce #%d for IP address %s.", nannounce, inet_ntoa (ip));
arp (fd, &saddr, ARPOP_REQUEST, &addr, ip, &addr, ip);
nannounce++;
gettimeofday (&timeout, NULL);
timeout.tv_sec += ANNOUNCE_INTERVAL;
timeout.tv_usec += (random () % 200000);
}
else
{
/* Use our address! */
memcpy (out_ip, &ip, sizeof (ip));
success = TRUE;
goto out;
}
nm_info ("autoip: Waiting for reply...");
err = peekfd (dev, fd, &timeout);
if ((err == RET_ERROR) || (err == RET_CEASED))
goto out;
/* There's some data waiting for us */
if (err == RET_SUCCESS) {
nm_info ("autoip: Got some data to check for reply packet.");
/* read ARP packet */
if (recv (fd, &p, sizeof (p), 0) < 0) {
nm_warning ("autoip: packet receive failure, ignoring it.");
continue;
}
#ifdef ARP_DEBUG
nm_warning ("autoip: (%s) recv arp type=%d, op=%d, ", iface, ntohs(p.ethhdr.ether_type), ntohs(p.operation));
{
struct in_addr a;
memcpy (&(a.s_addr), &(p.sInaddr), sizeof (a.s_addr));
nm_warning (" source = %s %02X:%02X:%02X:%02X:%02X:%02X, ", inet_ntoa (a),
p.sHaddr[0], p.sHaddr[1], p.sHaddr[2], p.sHaddr[3], p.sHaddr[4], p.sHaddr[5]);
memcpy (&(a.s_addr), &(p.tInaddr), sizeof (a.s_addr));
nm_warning (" target = %s %02X:%02X:%02X:%02X:%02X:%02X\n", inet_ntoa (a),
p.tHaddr[0], p.tHaddr[1], p.tHaddr[2], p.tHaddr[3], p.tHaddr[4], p.tHaddr[5]);
}
#endif
if ( (ntohs (p.ethhdr.ether_type) == ETHERTYPE_ARP)
&& (ntohs (p.operation) == ARPOP_REPLY)
&& ((uint32_t)(*p.tInaddr) == ip.s_addr)
&& (memcmp (&addr, &p.tHaddr, ETH_ALEN) != 0))
{
#ifdef ARP_DEBUG
nm_warning ("autoip: (%s) ARP conflict for IP address %s.\n", iface, inet_ntoa(ip));
#endif
/* Ok, start all over again */
pick (&ip);
nprobes = 0;
nannounce = 0;
}
}
}
out:
if (fd >= 0)
close (fd);
return success;
}