blob: 37ca5734e62077f7eb91d72e952a571641a100c7 [file] [log] [blame]
#include <poll.h>
#include <limits.h>
#include <setjmp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/time.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h> /* for getopts */
#include <net/if_arp.h>
#include "ipconfig.h"
#include "netdev.h"
#include "bootp_packet.h"
#include "bootp_proto.h"
#include "dhcp_proto.h"
#include "packet.h"
static const char sysfs_class_net[] = "/sys/class/net";
static const char *progname;
static jmp_buf abort_buf;
static char do_not_config;
static unsigned int default_caps = CAP_DHCP | CAP_BOOTP | CAP_RARP;
static int loop_timeout = -1;
static int configured;
static int bringup_first = 0;
/* DHCP vendor class identifier */
char vendor_class_identifier[260];
int vendor_class_identifier_len;
struct state {
int state;
int restart_state;
time_t expire;
int retry_period;
struct netdev *dev;
struct state *next;
};
static inline const char *my_inet_ntoa(uint32_t addr)
{
struct in_addr a;
a.s_addr = addr;
return inet_ntoa(a);
}
static void print_device_config(struct netdev *dev)
{
printf("IP-Config: %s complete (from %s):\n", dev->name,
my_inet_ntoa(dev->serverid ? dev->serverid : dev->ip_server));
printf(" address: %-16s ", my_inet_ntoa(dev->ip_addr));
printf("broadcast: %-16s ", my_inet_ntoa(dev->ip_broadcast));
printf("netmask: %-16s\n", my_inet_ntoa(dev->ip_netmask));
printf(" gateway: %-16s ", my_inet_ntoa(dev->ip_gateway));
printf("dns0 : %-16s ", my_inet_ntoa(dev->ip_nameserver[0]));
printf("dns1 : %-16s\n", my_inet_ntoa(dev->ip_nameserver[1]));
if (dev->hostname[0])
printf(" host : %-64s\n", dev->hostname);
if (dev->dnsdomainname[0])
printf(" domain : %-64s\n", dev->dnsdomainname);
if (dev->nisdomainname[0])
printf(" nisdomain: %-64s\n", dev->nisdomainname);
printf(" rootserver: %s ", my_inet_ntoa(dev->ip_server));
printf("rootpath: %s\n", dev->bootpath);
printf(" filename : %s\n", dev->filename);
}
static void configure_device(struct netdev *dev)
{
if (do_not_config)
return;
if (netdev_setmtu(dev))
printf("IP-Config: failed to set MTU on %s to %u\n",
dev->name, dev->mtu);
if (netdev_setaddress(dev))
printf("IP-Config: failed to set addresses on %s\n",
dev->name);
if (netdev_setdefaultroute(dev))
printf("IP-Config: failed to set default route on %s\n",
dev->name);
if (dev->hostname[0] &&
sethostname(dev->hostname, strlen(dev->hostname)))
printf("IP-Config: failed to set hostname '%s' from %s\n",
dev->hostname, dev->name);
}
/*
* Escape shell varialbes in git style:
* Always start with a single quote ('), then leave all characters
* except ' and ! unchanged.
*/
static void write_option(FILE *f, const char *name, const char *chr)
{
fprintf(f, "%s='", name);
while (*chr) {
switch (*chr) {
case '!':
case '\'':
fprintf(f, "'\\%c'", *chr);
break;
default:
fprintf(f, "%c", *chr);
break;
}
++chr;
}
fprintf(f, "'\n");
}
static void dump_device_config(struct netdev *dev)
{
char fn[40];
FILE *f;
snprintf(fn, sizeof(fn), "/tmp/net-%s.conf", dev->name);
f = fopen(fn, "w");
if (f) {
write_option(f, "DEVICE", dev->name);
write_option(f, "IPV4ADDR",
my_inet_ntoa(dev->ip_addr));
write_option(f, "IPV4BROADCAST",
my_inet_ntoa(dev->ip_broadcast));
write_option(f, "IPV4NETMASK",
my_inet_ntoa(dev->ip_netmask));
write_option(f, "IPV4GATEWAY",
my_inet_ntoa(dev->ip_gateway));
write_option(f, "IPV4DNS0",
my_inet_ntoa(dev->ip_nameserver[0]));
write_option(f, "IPV4DNS1",
my_inet_ntoa(dev->ip_nameserver[1]));
write_option(f, "HOSTNAME", dev->hostname);
write_option(f, "DNSDOMAIN", dev->dnsdomainname);
write_option(f, "NISDOMAIN", dev->nisdomainname);
write_option(f, "ROOTSERVER",
my_inet_ntoa(dev->ip_server));
write_option(f, "ROOTPATH", dev->bootpath);
write_option(f, "filename", dev->filename);
fclose(f);
}
}
static uint32_t inet_class_netmask(uint32_t ip)
{
ip = ntohl(ip);
if (IN_CLASSA(ip))
return htonl(IN_CLASSA_NET);
if (IN_CLASSB(ip))
return htonl(IN_CLASSB_NET);
if (IN_CLASSC(ip))
return htonl(IN_CLASSC_NET);
return INADDR_ANY;
}
static void postprocess_device(struct netdev *dev)
{
if (dev->ip_netmask == INADDR_ANY) {
dev->ip_netmask = inet_class_netmask(dev->ip_addr);
printf("IP-Config: %s guessed netmask %s\n",
dev->name, my_inet_ntoa(dev->ip_netmask));
}
if (dev->ip_broadcast == INADDR_ANY) {
dev->ip_broadcast =
(dev->ip_addr & dev->ip_netmask) | ~dev->ip_netmask;
printf("IP-Config: %s guessed broadcast address %s\n",
dev->name, my_inet_ntoa(dev->ip_broadcast));
}
}
static void complete_device(struct netdev *dev)
{
postprocess_device(dev);
configure_device(dev);
dump_device_config(dev);
print_device_config(dev);
++configured;
dev->next = ifaces;
ifaces = dev;
}
/*
* Returns:
* 0 = Not handled, try again later
* 1 = Handled
*/
static int process_receive_event(struct state *s, time_t now)
{
int handled = 1;
switch (s->state) {
case DEVST_ERROR:
return 0; /* Not handled */
case DEVST_COMPLETE:
return 0; /* Not handled as already configured */
case DEVST_BOOTP:
s->restart_state = DEVST_BOOTP;
switch (bootp_recv_reply(s->dev)) {
case -1:
s->state = DEVST_ERROR;
break;
case 0:
handled = 0;
break;
case 1:
s->state = DEVST_COMPLETE;
dprintf("\n bootp reply\n");
break;
}
break;
case DEVST_DHCPDISC:
s->restart_state = DEVST_DHCPDISC;
switch (dhcp_recv_offer(s->dev)) {
case -1:
s->state = DEVST_ERROR;
break;
case 0:
handled = 0;
break;
case DHCPOFFER: /* Offer received */
s->state = DEVST_DHCPREQ;
dhcp_send_request(s->dev);
break;
}
break;
case DEVST_DHCPREQ:
s->restart_state = DEVST_DHCPDISC;
switch (dhcp_recv_ack(s->dev)) {
case -1: /* error */
s->state = DEVST_ERROR;
break;
case 0:
handled = 0;
break;
case DHCPACK: /* ACK received */
s->state = DEVST_COMPLETE;
break;
case DHCPNAK: /* NAK received */
s->state = DEVST_DHCPDISC;
break;
}
break;
default:
dprintf("\n");
handled = 0;
break;
}
switch (s->state) {
case DEVST_COMPLETE:
complete_device(s->dev);
break;
case DEVST_ERROR:
/* error occurred, try again in 10 seconds */
s->expire = now + 10;
break;
}
return handled;
}
static void process_timeout_event(struct state *s, time_t now)
{
int ret = 0;
/*
* Is the link up? If not, try again in 1 second.
*/
if (!netdev_running(s->dev)) {
s->expire = now + 1;
s->state = s->restart_state;
return;
}
/*
* If we had an error, restore a sane state to
* restart from.
*/
if (s->state == DEVST_ERROR)
s->state = s->restart_state;
/*
* Now send a packet depending on our state.
*/
switch (s->state) {
case DEVST_BOOTP:
ret = bootp_send_request(s->dev);
s->restart_state = DEVST_BOOTP;
break;
case DEVST_DHCPDISC:
ret = dhcp_send_discover(s->dev);
s->restart_state = DEVST_DHCPDISC;
break;
case DEVST_DHCPREQ:
ret = dhcp_send_request(s->dev);
s->restart_state = DEVST_DHCPDISC;
break;
}
if (ret == -1) {
s->state = DEVST_ERROR;
s->expire = now + 10;
} else {
s->expire = now + s->retry_period;
s->retry_period *= 2;
if (s->retry_period > 60)
s->retry_period = 60;
}
}
static struct state *slist;
struct netdev *ifaces;
/*
* Returns:
* 0 = No dhcp/bootp packet was received
* 1 = A packet was received and handled
*/
static int do_pkt_recv(int pkt_fd, time_t now)
{
int ret = 0;
struct state *s;
for (s = slist; s; s = s->next)
ret |= process_receive_event(s, now);
return ret;
}
static int loop(void)
{
#define NR_FDS 1
struct pollfd fds[NR_FDS];
struct state *s;
int pkt_fd;
int nr = 0, rc = 0;
struct timeval now, prev;
time_t start;
pkt_fd = packet_open();
if (pkt_fd == -1) {
perror("packet_open");
return -1;
}
fds[0].fd = pkt_fd;
fds[0].events = POLLRDNORM;
gettimeofday(&now, NULL);
start = now.tv_sec;
while (1) {
int timeout = 60;
int pending = 0;
int done = 0;
int timeout_ms;
int x;
for (s = slist; s; s = s->next) {
dprintf("%s: state = %d\n", s->dev->name, s->state);
if (s->state == DEVST_COMPLETE) {
done++;
continue;
}
pending++;
if (s->expire - now.tv_sec <= 0) {
dprintf("timeout\n");
process_timeout_event(s, now.tv_sec);
}
if (timeout > s->expire - now.tv_sec)
timeout = s->expire - now.tv_sec;
}
if (pending == 0 || (bringup_first && done))
break;
timeout_ms = timeout * 1000;
for (x = 0; x < 2; x++) {
int delta_ms;
if (timeout_ms <= 0)
timeout_ms = 100;
nr = poll(fds, NR_FDS, timeout_ms);
prev = now;
gettimeofday(&now, NULL);
if ((nr > 0) && (fds[0].revents & POLLRDNORM)) {
if (do_pkt_recv(pkt_fd, now.tv_sec) == 1)
break;
}
if (loop_timeout >= 0 &&
now.tv_sec - start >= loop_timeout) {
printf("IP-Config: no response after %d "
"secs - giving up\n", loop_timeout);
rc = -1;
goto bail;
}
delta_ms = (now.tv_sec - prev.tv_sec) * 1000;
delta_ms += (now.tv_usec - prev.tv_usec) / 1000;
dprintf("Delta: %d ms\n", delta_ms);
timeout_ms -= delta_ms;
}
}
bail:
packet_close();
return rc;
}
static int add_one_dev(struct netdev *dev)
{
struct state *state;
state = malloc(sizeof(struct state));
if (!state)
return -1;
state->dev = dev;
state->expire = time(NULL);
state->retry_period = 1;
/*
* Select the state that we start from.
*/
if (dev->caps & CAP_DHCP && dev->ip_addr == INADDR_ANY)
state->restart_state = state->state = DEVST_DHCPDISC;
else if (dev->caps & CAP_DHCP)
state->restart_state = state->state = DEVST_DHCPREQ;
else if (dev->caps & CAP_BOOTP)
state->restart_state = state->state = DEVST_BOOTP;
state->next = slist;
slist = state;
return 0;
}
static void parse_addr(uint32_t *addr, const char *ip)
{
struct in_addr in;
if (inet_aton(ip, &in) == 0) {
fprintf(stderr, "%s: can't parse IP address '%s'\n",
progname, ip);
longjmp(abort_buf, 1);
}
*addr = in.s_addr;
}
static unsigned int parse_proto(const char *ip)
{
unsigned int caps = 0;
if (*ip == '\0' || strcmp(ip, "on") == 0 || strcmp(ip, "any") == 0)
caps = CAP_BOOTP | CAP_DHCP | CAP_RARP;
else if (strcmp(ip, "both") == 0)
caps = CAP_BOOTP | CAP_RARP;
else if (strcmp(ip, "dhcp") == 0)
caps = CAP_BOOTP | CAP_DHCP;
else if (strcmp(ip, "bootp") == 0)
caps = CAP_BOOTP;
else if (strcmp(ip, "rarp") == 0)
caps = CAP_RARP;
else if (strcmp(ip, "none") == 0 || strcmp(ip, "static") == 0
|| strcmp(ip, "off") == 0)
goto bail;
else {
fprintf(stderr, "%s: invalid protocol '%s'\n", progname, ip);
longjmp(abort_buf, 1);
}
bail:
return caps;
}
static int add_all_devices(struct netdev *template);
static int parse_device(struct netdev *dev, const char *ip)
{
char *cp;
int opt;
int is_ip = 0;
dprintf("IP-Config: parse_device: \"%s\"\n", ip);
if (strncmp(ip, "ip=", 3) == 0) {
ip += 3;
is_ip = 1;
} else if (strncmp(ip, "nfsaddrs=", 9) == 0) {
ip += 9;
is_ip = 1; /* Not sure about this...? */
}
if (!strchr(ip, ':')) {
/* Only one option, e.g. "ip=dhcp", or an interface name */
if (is_ip) {
dev->caps = parse_proto(ip);
bringup_first = 1;
} else {
dev->name = ip;
}
} else {
for (opt = 0; ip && *ip; ip = cp, opt++) {
if ((cp = strchr(ip, ':'))) {
*cp++ = '\0';
}
if (opt > 6) {
fprintf(stderr,
"%s: too many options for %s\n",
progname, dev->name);
longjmp(abort_buf, 1);
}
if (*ip == '\0')
continue;
dprintf("IP-Config: opt #%d: '%s'\n", opt, ip);
switch (opt) {
case 0:
parse_addr(&dev->ip_addr, ip);
dev->caps = 0;
break;
case 1:
parse_addr(&dev->ip_server, ip);
break;
case 2:
parse_addr(&dev->ip_gateway, ip);
break;
case 3:
parse_addr(&dev->ip_netmask, ip);
break;
case 4:
strncpy(dev->hostname, ip, SYS_NMLN - 1);
dev->hostname[SYS_NMLN - 1] = '\0';
memcpy(dev->reqhostname, dev->hostname,
SYS_NMLN);
break;
case 5:
dev->name = ip;
break;
case 6:
dev->caps = parse_proto(ip);
break;
}
}
}
if (dev->name == NULL ||
dev->name[0] == '\0' || strcmp(dev->name, "all") == 0) {
add_all_devices(dev);
bringup_first = 1;
return 0;
}
return 1;
}
static void bringup_device(struct netdev *dev)
{
if (netdev_up(dev) == 0) {
if (dev->caps)
add_one_dev(dev);
else
complete_device(dev);
}
}
static void bringup_one_dev(struct netdev *template, struct netdev *dev)
{
if (template->ip_addr != INADDR_NONE)
dev->ip_addr = template->ip_addr;
if (template->ip_server != INADDR_NONE)
dev->ip_server = template->ip_server;
if (template->ip_gateway != INADDR_NONE)
dev->ip_gateway = template->ip_gateway;
if (template->ip_netmask != INADDR_NONE)
dev->ip_netmask = template->ip_netmask;
if (template->ip_nameserver[0] != INADDR_NONE)
dev->ip_nameserver[0] = template->ip_nameserver[0];
if (template->ip_nameserver[1] != INADDR_NONE)
dev->ip_nameserver[1] = template->ip_nameserver[1];
if (template->hostname[0] != '\0')
strcpy(dev->hostname, template->hostname);
if (template->reqhostname[0] != '\0')
strcpy(dev->reqhostname, template->reqhostname);
dev->caps &= template->caps;
bringup_device(dev);
}
static struct netdev *add_device(const char *info)
{
struct netdev *dev;
int i;
dev = malloc(sizeof(struct netdev));
if (dev == NULL) {
fprintf(stderr, "%s: out of memory\n", progname);
longjmp(abort_buf, 1);
}
memset(dev, 0, sizeof(struct netdev));
dev->caps = default_caps;
if (parse_device(dev, info) == 0)
goto bail;
if (netdev_init_if(dev) == -1)
goto bail;
if (bootp_init_if(dev) == -1)
goto bail;
printf("IP-Config: %s hardware address", dev->name);
for (i = 0; i < dev->hwlen; i++)
printf("%c%02x", i == 0 ? ' ' : ':', dev->hwaddr[i]);
printf(" mtu %d%s%s\n", dev->mtu,
dev->caps & CAP_DHCP ? " DHCP" :
dev->caps & CAP_BOOTP ? " BOOTP" : "",
dev->caps & CAP_RARP ? " RARP" : "");
return dev;
bail:
free(dev);
return NULL;
}
static int add_all_devices(struct netdev *template)
{
DIR *d;
struct dirent *de;
struct netdev *dev;
char t[PATH_MAX], p[255];
int i, fd;
unsigned long flags;
d = opendir(sysfs_class_net);
if (!d)
return 0;
while ((de = readdir(d)) != NULL) {
/* This excludes devices beginning with dots or "dummy",
as well as . or .. */
if (de->d_name[0] == '.' || !strcmp(de->d_name, ".."))
continue;
i = snprintf(t, PATH_MAX - 1, "%s/%s/flags", sysfs_class_net,
de->d_name);
if (i < 0 || i >= PATH_MAX - 1)
continue;
t[i] = '\0';
fd = open(t, O_RDONLY);
if (fd < 0) {
perror(t);
continue;
}
i = read(fd, &p, sizeof(p) - 1);
close(fd);
if (i < 0) {
perror(t);
continue;
}
p[i] = '\0';
flags = strtoul(p, NULL, 0);
/* Heuristic for if this is a reasonable boot interface.
This is the same
logic the in-kernel ipconfig uses... */
if (!(flags & IFF_LOOPBACK) &&
(flags & (IFF_BROADCAST | IFF_POINTOPOINT))) {
dprintf("Trying to bring up %s\n", de->d_name);
dev = add_device(de->d_name);
if (!dev)
continue;
bringup_one_dev(template, dev);
}
}
closedir(d);
return 1;
}
static int check_autoconfig(void)
{
int ndev = 0, nauto = 0;
struct state *s;
for (s = slist; s; s = s->next) {
ndev++;
if (s->dev->caps)
nauto++;
}
if (ndev == 0) {
if (configured == 0) {
fprintf(stderr, "%s: no devices to configure\n",
progname);
longjmp(abort_buf, 1);
}
}
return nauto;
}
static void set_vendor_identifier(const char *id)
{
int len = strlen(id);
if (len >= 255) {
fprintf(stderr,
"%s: invalid vendor class identifier: "
"%s\n", progname, id);
longjmp(abort_buf, 1);
}
memcpy(vendor_class_identifier+2, id, len);
vendor_class_identifier[0] = 60;
vendor_class_identifier[1] = len;
vendor_class_identifier_len = len+2;
}
int main(int argc, char *argv[])
__attribute__ ((weak, alias("ipconfig_main")));
int ipconfig_main(int argc, char *argv[])
{
struct netdev *dev;
int c, port;
int err = 0;
/* If progname is set we're invoked from another program */
if (!progname) {
struct timeval now;
progname = argv[0];
gettimeofday(&now, NULL);
srand48(now.tv_usec ^ (now.tv_sec << 24));
}
if ((err = setjmp(abort_buf)))
return err;
/* Default vendor identifier */
set_vendor_identifier("Linux ipconfig");
do {
c = getopt(argc, argv, "c:d:i:onp:t:");
if (c == EOF)
break;
switch (c) {
case 'c':
default_caps = parse_proto(optarg);
break;
case 'p':
port = atoi(optarg);
if (port <= 0 || port > USHRT_MAX) {
fprintf(stderr,
"%s: invalid port number %d\n",
progname, port);
longjmp(abort_buf, 1);
}
cfg_local_port = port;
cfg_remote_port = cfg_local_port - 1;
break;
case 't':
loop_timeout = atoi(optarg);
if (loop_timeout < 0) {
fprintf(stderr,
"%s: invalid timeout %d\n",
progname, loop_timeout);
longjmp(abort_buf, 1);
}
break;
case 'i':
set_vendor_identifier(optarg);
break;
case 'o':
bringup_first = 1;
break;
case 'n':
do_not_config = 1;
break;
case 'd':
dev = add_device(optarg);
if (dev)
bringup_device(dev);
break;
case '?':
fprintf(stderr, "%s: invalid option -%c\n",
progname, optopt);
longjmp(abort_buf, 1);
}
} while (1);
for (c = optind; c < argc; c++) {
dev = add_device(argv[c]);
if (dev)
bringup_device(dev);
}
if (check_autoconfig()) {
if (cfg_local_port != LOCAL_PORT) {
printf("IP-Config: binding source port to %d, "
"dest to %d\n",
cfg_local_port, cfg_remote_port);
}
err = loop();
}
return err;
}