hush/networking/ifconfig.c
Rob Landley ef8f423e90 Erik pointed out that in the last try at the #ifdef cleanup
infrastructure, the compiler isn't smart enough to replace const static 
int with the constant, and allocates space for each set of them, 
bloating the executable something fierce.  Oops.

So now, we #define ENABLE_XXX to 0 or 1 for each CONFIG_XXX (which
is still there so the 1000+ #ifdef/#ifndef tests don't have to be
replaced wholesale).  Changed the test instance in networking/ifconfig.c 
to use this.
2005-07-28 19:36:33 +00:00

608 lines
17 KiB
C

/* ifconfig
*
* Similar to the standard Unix ifconfig, but with only the necessary
* parts for AF_INET, and without any printing of if info (for now).
*
* Bjorn Wesen, Axis Communications AB
*
*
* Authors of the original ifconfig was:
* Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
*
* 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.
*
* $Id: ifconfig.c,v 1.30 2004/03/31 11:30:08 andersen Exp $
*
*/
/*
* Heavily modified by Manuel Novoa III Mar 6, 2001
*
* From initial port to busybox, removed most of the redundancy by
* converting to a table-driven approach. Added several (optional)
* args missing from initial port.
*
* Still missing: media, tunnel.
*
* 2002-04-20
* IPV6 support added by Bart Visscher <magick@linux-fan.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strcmp and friends */
#include <ctype.h> /* isdigit and friends */
#include <stddef.h> /* offsetof */
#include <unistd.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h>
#else
#include <sys/types.h>
#include <netinet/if_ether.h>
#endif
#include "inet_common.h"
#include "busybox.h"
#ifdef CONFIG_FEATURE_IFCONFIG_SLIP
# include <net/if_slip.h>
#endif
/* I don't know if this is needed for busybox or not. Anyone? */
#define QUESTIONABLE_ALIAS_CASE
/* Defines for glibc2.0 users. */
#ifndef SIOCSIFTXQLEN
# define SIOCSIFTXQLEN 0x8943
# define SIOCGIFTXQLEN 0x8942
#endif
/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */
#ifndef ifr_qlen
# define ifr_qlen ifr_ifru.ifru_mtu
#endif
#ifndef IFF_DYNAMIC
# define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */
#endif
#ifdef CONFIG_FEATURE_IPV6
struct in6_ifreq {
struct in6_addr ifr6_addr;
uint32_t ifr6_prefixlen;
int ifr6_ifindex;
};
#endif
/*
* Here are the bit masks for the "flags" member of struct options below.
* N_ signifies no arg prefix; M_ signifies arg prefixed by '-'.
* CLR clears the flag; SET sets the flag; ARG signifies (optional) arg.
*/
#define N_CLR 0x01
#define M_CLR 0x02
#define N_SET 0x04
#define M_SET 0x08
#define N_ARG 0x10
#define M_ARG 0x20
#define M_MASK (M_CLR | M_SET | M_ARG)
#define N_MASK (N_CLR | N_SET | N_ARG)
#define SET_MASK (N_SET | M_SET)
#define CLR_MASK (N_CLR | M_CLR)
#define SET_CLR_MASK (SET_MASK | CLR_MASK)
#define ARG_MASK (M_ARG | N_ARG)
/*
* Here are the bit masks for the "arg_flags" member of struct options below.
*/
/*
* cast type:
* 00 int
* 01 char *
* 02 HOST_COPY in_ether
* 03 HOST_COPY INET_resolve
*/
#define A_CAST_TYPE 0x03
/*
* map type:
* 00 not a map type (mem_start, io_addr, irq)
* 04 memstart (unsigned long)
* 08 io_addr (unsigned short)
* 0C irq (unsigned char)
*/
#define A_MAP_TYPE 0x0C
#define A_ARG_REQ 0x10 /* Set if an arg is required. */
#define A_NETMASK 0x20 /* Set if netmask (check for multiple sets). */
#define A_SET_AFTER 0x40 /* Set a flag at the end. */
#define A_COLON_CHK 0x80 /* Is this needed? See below. */
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
#define A_HOSTNAME 0x100 /* Set if it is ip addr. */
#define A_BROADCAST 0x200 /* Set if it is broadcast addr. */
#else
#define A_HOSTNAME 0
#define A_BROADCAST 0
#endif
/*
* These defines are for dealing with the A_CAST_TYPE field.
*/
#define A_CAST_CHAR_PTR 0x01
#define A_CAST_RESOLVE 0x01
#define A_CAST_HOST_COPY 0x02
#define A_CAST_HOST_COPY_IN_ETHER A_CAST_HOST_COPY
#define A_CAST_HOST_COPY_RESOLVE (A_CAST_HOST_COPY | A_CAST_RESOLVE)
/*
* These defines are for dealing with the A_MAP_TYPE field.
*/
#define A_MAP_ULONG 0x04 /* memstart */
#define A_MAP_USHORT 0x08 /* io_addr */
#define A_MAP_UCHAR 0x0C /* irq */
/*
* Define the bit masks signifying which operations to perform for each arg.
*/
#define ARG_METRIC (A_ARG_REQ /*| A_CAST_INT*/)
#define ARG_MTU (A_ARG_REQ /*| A_CAST_INT*/)
#define ARG_TXQUEUELEN (A_ARG_REQ /*| A_CAST_INT*/)
#define ARG_MEM_START (A_ARG_REQ | A_MAP_ULONG)
#define ARG_IO_ADDR (A_ARG_REQ | A_MAP_ULONG)
#define ARG_IRQ (A_ARG_REQ | A_MAP_UCHAR)
#define ARG_DSTADDR (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE)
#define ARG_NETMASK (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_NETMASK)
#define ARG_BROADCAST (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_BROADCAST)
#define ARG_HW (A_ARG_REQ | A_CAST_HOST_COPY_IN_ETHER)
#define ARG_POINTOPOINT (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER)
#define ARG_KEEPALIVE (A_ARG_REQ | A_CAST_CHAR_PTR)
#define ARG_OUTFILL (A_ARG_REQ | A_CAST_CHAR_PTR)
#define ARG_HOSTNAME (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_COLON_CHK | A_HOSTNAME)
#define ARG_ADD_DEL (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER)
/*
* Set up the tables. Warning! They must have corresponding order!
*/
struct arg1opt {
const char *name;
int selector;
unsigned short ifr_offset;
};
struct options {
const char *name;
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
const unsigned int flags:6;
const unsigned int arg_flags:10;
#else
const unsigned char flags;
const unsigned char arg_flags;
#endif
const unsigned short selector;
};
#define ifreq_offsetof(x) offsetof(struct ifreq, x)
static const struct arg1opt Arg1Opt[] = {
{"SIOCSIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric)},
{"SIOCSIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu)},
{"SIOCSIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen)},
{"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)},
{"SIOCSIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask)},
{"SIOCSIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr)},
#ifdef CONFIG_FEATURE_IFCONFIG_HW
{"SIOCSIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr)},
#endif
{"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)},
#ifdef SIOCSKEEPALIVE
{"SIOCSKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data)},
#endif
#ifdef SIOCSOUTFILL
{"SIOCSOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data)},
#endif
#ifdef CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
{"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start)},
{"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr)},
{"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq)},
#endif
/* Last entry if for unmatched (possibly hostname) arg. */
#ifdef CONFIG_FEATURE_IPV6
{"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */
{"SIOCDIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */
#endif
{"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)},
};
static const struct options OptArray[] = {
{"metric", N_ARG, ARG_METRIC, 0},
{"mtu", N_ARG, ARG_MTU, 0},
{"txqueuelen", N_ARG, ARG_TXQUEUELEN, 0},
{"dstaddr", N_ARG, ARG_DSTADDR, 0},
{"netmask", N_ARG, ARG_NETMASK, 0},
{"broadcast", N_ARG | M_CLR, ARG_BROADCAST, IFF_BROADCAST},
#ifdef CONFIG_FEATURE_IFCONFIG_HW
{"hw", N_ARG, ARG_HW, 0},
#endif
{"pointopoint", N_ARG | M_CLR, ARG_POINTOPOINT, IFF_POINTOPOINT},
#ifdef SIOCSKEEPALIVE
{"keepalive", N_ARG, ARG_KEEPALIVE, 0},
#endif
#ifdef SIOCSOUTFILL
{"outfill", N_ARG, ARG_OUTFILL, 0},
#endif
#ifdef CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
{"mem_start", N_ARG, ARG_MEM_START, 0},
{"io_addr", N_ARG, ARG_IO_ADDR, 0},
{"irq", N_ARG, ARG_IRQ, 0},
#endif
#ifdef CONFIG_FEATURE_IPV6
{"add", N_ARG, ARG_ADD_DEL, 0},
{"del", N_ARG, ARG_ADD_DEL, 0},
#endif
{"arp", N_CLR | M_SET, 0, IFF_NOARP},
{"trailers", N_CLR | M_SET, 0, IFF_NOTRAILERS},
{"promisc", N_SET | M_CLR, 0, IFF_PROMISC},
{"multicast", N_SET | M_CLR, 0, IFF_MULTICAST},
{"allmulti", N_SET | M_CLR, 0, IFF_ALLMULTI},
{"dynamic", N_SET | M_CLR, 0, IFF_DYNAMIC},
{"up", N_SET, 0, (IFF_UP | IFF_RUNNING)},
{"down", N_CLR, 0, IFF_UP},
{NULL, 0, ARG_HOSTNAME, (IFF_UP | IFF_RUNNING)}
};
/*
* A couple of prototypes.
*/
#ifdef CONFIG_FEATURE_IFCONFIG_HW
static int in_ether(char *bufp, struct sockaddr *sap);
#endif
#ifdef CONFIG_FEATURE_IFCONFIG_STATUS
extern int interface_opt_a;
extern int display_interfaces(char *ifname);
#endif
/*
* Our main function.
*/
int ifconfig_main(int argc, char **argv)
{
struct ifreq ifr;
struct sockaddr_in sai;
#ifdef CONFIG_FEATURE_IPV6
struct sockaddr_in6 sai6;
#endif
#ifdef CONFIG_FEATURE_IFCONFIG_HW
struct sockaddr sa;
#endif
const struct arg1opt *a1op;
const struct options *op;
int sockfd; /* socket fd we use to manipulate stuff with */
int goterr;
int selector;
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
unsigned int mask;
unsigned int did_flags;
unsigned int sai_hostname, sai_netmask;
#else
unsigned char mask;
unsigned char did_flags;
#endif
char *p;
char host[128];
goterr = 0;
did_flags = 0;
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
sai_hostname = 0;
sai_netmask = 0;
#endif
/* skip argv[0] */
++argv;
--argc;
#ifdef CONFIG_FEATURE_IFCONFIG_STATUS
if ((argc > 0) && (((*argv)[0] == '-') && ((*argv)[1] == 'a') && !(*argv)[2])) {
interface_opt_a = 1;
--argc;
++argv;
}
#endif
if (argc <= 1) {
#ifdef CONFIG_FEATURE_IFCONFIG_STATUS
return display_interfaces(argc ? *argv : NULL);
#else
bb_error_msg_and_die
("ifconfig was not compiled with interface status display support.");
#endif
}
/* Create a channel to the NET kernel. */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
bb_perror_msg_and_die("socket");
}
/* get interface name */
safe_strncpy(ifr.ifr_name, *argv, IFNAMSIZ);
/* Process the remaining arguments. */
while (*++argv != (char *) NULL) {
p = *argv;
mask = N_MASK;
if (*p == '-') { /* If the arg starts with '-'... */
++p; /* advance past it and */
mask = M_MASK; /* set the appropriate mask. */
}
for (op = OptArray; op->name; op++) { /* Find table entry. */
if (strcmp(p, op->name) == 0) { /* If name matches... */
if ((mask &= op->flags)) { /* set the mask and go. */
goto FOUND_ARG;;
}
/* If we get here, there was a valid arg with an */
/* invalid '-' prefix. */
++goterr;
goto LOOP;
}
}
/* We fell through, so treat as possible hostname. */
a1op = Arg1Opt + (sizeof(Arg1Opt) / sizeof(Arg1Opt[0])) - 1;
mask = op->arg_flags;
goto HOSTNAME;
FOUND_ARG:
if (mask & ARG_MASK) {
mask = op->arg_flags;
a1op = Arg1Opt + (op - OptArray);
if (mask & A_NETMASK & did_flags) {
bb_show_usage();
}
if (*++argv == NULL) {
if (mask & A_ARG_REQ) {
bb_show_usage();
} else {
--argv;
mask &= A_SET_AFTER; /* just for broadcast */
}
} else { /* got an arg so process it */
HOSTNAME:
did_flags |= (mask & (A_NETMASK|A_HOSTNAME));
if (mask & A_CAST_HOST_COPY) {
#ifdef CONFIG_FEATURE_IFCONFIG_HW
if (mask & A_CAST_RESOLVE) {
#endif
#ifdef CONFIG_FEATURE_IPV6
char *prefix;
int prefix_len = 0;
#endif
safe_strncpy(host, *argv, (sizeof host));
#ifdef CONFIG_FEATURE_IPV6
if ((prefix = strchr(host, '/'))) {
if (safe_strtoi(prefix + 1, &prefix_len) ||
(prefix_len < 0) || (prefix_len > 128))
{
++goterr;
goto LOOP;
}
*prefix = 0;
}
#endif
sai.sin_family = AF_INET;
sai.sin_port = 0;
if (!strcmp(host, bb_INET_default)) {
/* Default is special, meaning 0.0.0.0. */
sai.sin_addr.s_addr = INADDR_ANY;
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
} else if (((host[0] == '+') && !host[1]) && (mask & A_BROADCAST) &&
(did_flags & (A_NETMASK|A_HOSTNAME)) == (A_NETMASK|A_HOSTNAME)) {
/* + is special, meaning broadcast is derived. */
sai.sin_addr.s_addr = (~sai_netmask) | (sai_hostname & sai_netmask);
#endif
#ifdef CONFIG_FEATURE_IPV6
} else if (inet_pton(AF_INET6, host, &sai6.sin6_addr) > 0) {
int sockfd6;
struct in6_ifreq ifr6;
memcpy((char *) &ifr6.ifr6_addr,
(char *) &sai6.sin6_addr,
sizeof(struct in6_addr));
/* Create a channel to the NET kernel. */
if ((sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
bb_perror_msg_and_die("socket6");
}
if (ioctl(sockfd6, SIOGIFINDEX, &ifr) < 0) {
perror("SIOGIFINDEX");
++goterr;
continue;
}
ifr6.ifr6_ifindex = ifr.ifr_ifindex;
ifr6.ifr6_prefixlen = prefix_len;
if (ioctl(sockfd6, a1op->selector, &ifr6) < 0) {
perror(a1op->name);
++goterr;
}
continue;
#endif
} else if (inet_aton(host, &sai.sin_addr) == 0) {
/* It's not a dotted quad. */
struct hostent *hp;
if ((hp = gethostbyname(host)) == (struct hostent *)NULL) {
++goterr;
continue;
}
memcpy((char *) &sai.sin_addr, (char *) hp->h_addr_list[0],
sizeof(struct in_addr));
}
#ifdef CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS
if (mask & A_HOSTNAME) {
sai_hostname = sai.sin_addr.s_addr;
}
if (mask & A_NETMASK) {
sai_netmask = sai.sin_addr.s_addr;
}
#endif
p = (char *) &sai;
#ifdef CONFIG_FEATURE_IFCONFIG_HW
} else { /* A_CAST_HOST_COPY_IN_ETHER */
/* This is the "hw" arg case. */
if (strcmp("ether", *argv) || (*++argv == NULL)) {
bb_show_usage();
}
safe_strncpy(host, *argv, (sizeof host));
if (in_ether(host, &sa)) {
bb_error_msg("invalid hw-addr %s", host);
++goterr;
continue;
}
p = (char *) &sa;
}
#endif
memcpy((((char *) (&ifr)) + a1op->ifr_offset),
p, sizeof(struct sockaddr));
} else {
unsigned int i = strtoul(*argv, NULL, 0);
p = ((char *) (&ifr)) + a1op->ifr_offset;
#ifdef CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
if (mask & A_MAP_TYPE) {
if (ioctl(sockfd, SIOCGIFMAP, &ifr) < 0) {
++goterr;
continue;
}
if ((mask & A_MAP_UCHAR) == A_MAP_UCHAR) {
*((unsigned char *) p) = i;
} else if (mask & A_MAP_USHORT) {
*((unsigned short *) p) = i;
} else {
*((unsigned long *) p) = i;
}
} else
#endif
if (mask & A_CAST_CHAR_PTR) {
*((caddr_t *) p) = (caddr_t) i;
} else { /* A_CAST_INT */
*((int *) p) = i;
}
}
if (ioctl(sockfd, a1op->selector, &ifr) < 0) {
perror(a1op->name);
++goterr;
continue;
}
#ifdef QUESTIONABLE_ALIAS_CASE
if (mask & A_COLON_CHK) {
/*
* Don't do the set_flag() if the address is an alias with
* a - at the end, since it's deleted already! - Roman
*
* Should really use regex.h here, not sure though how well
* it'll go with the cross-platform support etc.
*/
char *ptr;
short int found_colon = 0;
for (ptr = ifr.ifr_name; *ptr; ptr++) {
if (*ptr == ':') {
found_colon++;
}
}
if (found_colon && *(ptr - 1) == '-') {
continue;
}
}
#endif
}
if (!(mask & A_SET_AFTER)) {
continue;
}
mask = N_SET;
}
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
perror("SIOCGIFFLAGS");
++goterr;
} else {
selector = op->selector;
if (mask & SET_MASK) {
ifr.ifr_flags |= selector;
} else {
ifr.ifr_flags &= ~selector;
}
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
perror("SIOCSIFFLAGS");
++goterr;
}
}
LOOP:
continue;
} /* end of while-loop */
if (ENABLE_FEATURE_CLEAN_UP) close(sockfd);
return goterr;
}
#ifdef CONFIG_FEATURE_IFCONFIG_HW
/* Input an Ethernet address and convert to binary. */
static int in_ether(char *bufp, struct sockaddr *sap)
{
unsigned char *ptr;
int i, j;
unsigned char val;
unsigned char c;
sap->sa_family = ARPHRD_ETHER;
ptr = sap->sa_data;
i = 0;
do {
j = val = 0;
/* We might get a semicolon here - not required. */
if (i && (*bufp == ':')) {
bufp++;
}
do {
c = *bufp;
if (((unsigned char)(c - '0')) <= 9) {
c -= '0';
} else if (((unsigned char)((c|0x20) - 'a')) <= 5) {
c = (c|0x20) - ('a'-10);
} else if (j && (c == ':' || c == 0)) {
break;
} else {
return -1;
}
++bufp;
val <<= 4;
val += c;
} while (++j < 2);
*ptr++ = val;
} while (++i < ETH_ALEN);
return (int) (*bufp); /* Error if we don't end at end of string. */
}
#endif