Commit 6cf05acb authored by Leigh B. Stoller's avatar Leigh B. Stoller

New event agent to control wireless links. At present, this agent is

very specific to wireless links in general, and to iwconfig on Redhat
9.0. It allows you to control the entire lan or an individual member
of a wireless lan via the event system. For example to change the
accesspoint of a wireless lan, you could do this:

	tevc -e foo/bar now lan0 modify accesspoint=00:09:5B:93:0B:A4

The agent deciphers the event arguments and calls iwconfig with the
appropriate as needed. Note that there are many ways to make the lan
unusable doing this, so you want to be careful. You can get the MAC
addresses from the experiment info page (tbreport).

New script called link_config, which might be badly named since it
implies generality) to front end tevc. Operates mostly like
delay_config in that it will change the physical table settings, and
optionally (-m) the virtual table entries. So,

	link_config testbed two-wireless lan0 accesspoint=00:09:5B:93:0B:A4

You can change individual members of a lan too:

	link_config -s nodew1 testbed two-wireless lan0 txpower=50

Currently no web interface; too much work. I will add an xmlrpc
interface though since that is easy to do.
parent 7c481b95
......@@ -2,7 +2,7 @@
# Guess values for system-dependent variables and create Makefiles.
# Generated automatically using autoconf version 2.13
# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
# Copyright (C) 1992, 93, 94, 95, 96, 04 Free Software Foundation, Inc.
#
# This configure script is free software; the Free Software Foundation
# gives unlimited permission to copy, distribute and modify it.
......@@ -1245,6 +1245,7 @@ else
event/nsetrafgen/nse-makepatch \
event/delay-agent/GNUmakefile \
event/program-agent/GNUmakefile \
event/link-agent/GNUmakefile \
event/stated/waitForState \
event/stated/GNUmakefile event/stated/stated \
event/linktest/GNUmakefile \
......@@ -1381,7 +1382,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
capture/GNUmakefile \
db/GNUmakefile db/nalloc db/nfree db/if2port db/backup \
db/webcontrol db/node_status db/genelists db/genelists.proxy \
db/setsitevar db/newwanode db/audit \
db/setsitevar db/newwanode db/audit db/changeuid \
db/libdb.pm db/inuse db/avail db/nodeip db/showgraph \
db/dhcpd_makeconf db/nodelog db/webnodelog db/unixgroups \
db/dbcheck db/interswitch db/dbboot db/schemacheck \
......@@ -1466,6 +1467,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
utils/firstuser utils/export_tables utils/eventping \
utils/cvsupd.pl utils/newnode utils/grantnodetype \
utils/nsgen/GNUmakefile utils/nsgen/webnsgen \
utils/link_config utils/import_commitlog \
www/GNUmakefile www/defs.php3 www/dbdefs.php3 \
www/swish.conf www/websearch \
vis/GNUmakefile vis/webvistopology vis/dbvistopology \
......@@ -1483,6 +1485,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
xmlrpc/webxmlrpc \
xmlrpc/emulab xmlrpc/node xmlrpc/experiment xmlrpc/fs xmlrpc/user \
xmlrpc/imageid xmlrpc/osid \
cdrom/GNUmakefile cdrom/tbbootconfig/GNUmakefile \
install/ops-install install/boss-install \
install/newnode_sshkeys/GNUmakefile "
......
......@@ -360,6 +360,7 @@ else
event/nsetrafgen/nse-makepatch \
event/delay-agent/GNUmakefile \
event/program-agent/GNUmakefile \
event/link-agent/GNUmakefile \
event/stated/waitForState \
event/stated/GNUmakefile event/stated/stated \
event/linktest/GNUmakefile \
......@@ -426,7 +427,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
capture/GNUmakefile \
db/GNUmakefile db/nalloc db/nfree db/if2port db/backup \
db/webcontrol db/node_status db/genelists db/genelists.proxy \
db/setsitevar db/newwanode db/audit \
db/setsitevar db/newwanode db/audit db/changeuid \
db/libdb.pm db/inuse db/avail db/nodeip db/showgraph \
db/dhcpd_makeconf db/nodelog db/webnodelog db/unixgroups \
db/dbcheck db/interswitch db/dbboot db/schemacheck \
......@@ -511,6 +512,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
utils/firstuser utils/export_tables utils/eventping \
utils/cvsupd.pl utils/newnode utils/grantnodetype \
utils/nsgen/GNUmakefile utils/nsgen/webnsgen \
utils/link_config utils/import_commitlog \
www/GNUmakefile www/defs.php3 www/dbdefs.php3 \
www/swish.conf www/websearch \
vis/GNUmakefile vis/webvistopology vis/dbvistopology \
......@@ -528,6 +530,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
xmlrpc/webxmlrpc \
xmlrpc/emulab xmlrpc/node xmlrpc/experiment xmlrpc/fs xmlrpc/user \
xmlrpc/imageid xmlrpc/osid \
cdrom/GNUmakefile cdrom/tbbootconfig/GNUmakefile \
install/ops-install install/boss-install \
install/newnode_sshkeys/GNUmakefile "
......
......@@ -13,7 +13,8 @@ include $(OBJDIR)/Makeconf
SYSTEM := $(shell uname -s)
SUBDIRS = lib tbgen trafgen program-agent proxy example linktest
SUBDIRS = lib tbgen trafgen program-agent proxy example linktest \
link-agent
ifeq ($(SYSTEM),FreeBSD)
SUBDIRS += sched delay-agent nsetrafgen stated
endif
......@@ -39,6 +40,7 @@ client-install: lib/all.MAKE
ifeq ($(SYSTEM),FreeBSD)
@$(MAKE) -C delay-agent client-install
endif
@$(MAKE) -C link-agent client-install
@$(MAKE) -C tbgen client-install
@$(MAKE) -C program-agent client-install
@$(MAKE) -C trafgen client-install
......
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
SRCDIR = @srcdir@
TESTBED_SRCDIR = @top_srcdir@
OBJDIR = ../..
SUBDIR = event/link-agent
SYSTEM := $(shell uname -s)
PROGRAMS = link-agent
include $(OBJDIR)/Makeconf
all: $(PROGRAMS)
include $(TESTBED_SRCDIR)/GNUmakerules
CFLAGS += -DDEBUG
CFLAGS += -O -g -static -Wall
CFLAGS += -I. -I${OBJDIR} -I$(SRCDIR)/../lib -I$(TESTBED_SRCDIR)/lib/libtb
CFLAGS += `elvin-config --cflags vin4c`
LDFLAGS += -static -L../lib -L${OBJDIR}/lib/libtb
LIBS += -levent -ltb -lcrypto
LIBS += `elvin-config --libs vin4c`
ifeq ($(SYSTEM),Linux)
NEEDKERB := $(shell nm /usr/lib/libssl.a | grep -q krb; echo $$?)
ifeq ($(NEEDKERB),0)
CFLAGS += `/usr/kerberos/bin/krb5-config --cflags`
LIBS += `/usr/kerberos/bin/krb5-config --libs krb5`
endif
LIBS += -ldl
endif
link-agent: link-agent.o
$(CC) $(LDFLAGS) -o $@ link-agent.o $(LIBS)
$(PROGRAMS): ../lib/libevent.a ../lib/event.h
install:
client-install:
$(INSTALL_PROGRAM) link-agent \
$(DESTDIR)$(CLIENT_BINDIR)/link-agent
clean:
/bin/rm -f *.o $(PROGRAMS)
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2000-2004 University of Utah and the Flux Group.
* All rights reserved.
*/
/*
* Link Agent; very linux/wireless specific at the moment!
*/
#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <unistd.h>
#include <paths.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include "event.h"
#include "log.h"
#include "tbdefs.h"
static char *progname;
static int debug = 0;
static int verbose = 0;
static void callback(event_handle_t handle,
event_notification_t notification, void *data);
static int runcommand(char *cmd);
static char *convertmac(char *mac);
/*
* Map from the object name to the interface we actually need to change.
*/
typedef struct ifmap {
char *objname;
char *iface;
char *mac;
struct ifmap *next;
} ifmap_t;
ifmap_t *mapping = (ifmap_t *) NULL;
/*
* List of allowed settings we can change. We do not worry about verifying
* the arguments; people are free to screw themselves.
*
* XXX Very iwconfig specific!
*/
char *settings[] = {
"sensitivity",
"txpower",
"channel",
"rate",
"bitrate",
"accesspoint"
};
void
usage()
{
fprintf(stderr,
"Usage: %s [-s server] [-p port] [-k keyfile] [-l logfile] "
"[-i pidfile] -e pid/eid [names ...]\n", progname);
exit(-1);
}
static inline void
upcase(char *str)
{
if (str) {
while (*str) {
*str = toupper(*str);
str++;
}
}
}
int
main(int argc, char **argv)
{
event_handle_t handle;
address_tuple_t tuple;
char buf[BUFSIZ];
char *server = "localhost";
char *port = NULL;
char *keyfile = NULL;
char *pideid = NULL;
char *logfile = NULL;
char *pidfile = NULL;
int c;
progname = argv[0];
while ((c = getopt(argc, argv, "dvs:p:e:k:i:l:")) != -1) {
switch (c) {
case 'd':
debug++;
break;
case 'l':
logfile = optarg;
break;
case 'i':
pidfile = optarg;
break;
case 's':
server = optarg;
break;
case 'p':
port = optarg;
break;
case 'e':
pideid = optarg;
break;
case 'k':
keyfile = optarg;
break;
case 'v':
verbose++;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (!pideid)
usage();
if (!argc)
usage();
if (debug)
loginit(0, logfile);
else {
/* Become a daemon */
daemon(0, 0);
if (logfile)
loginit(0, logfile);
else
loginit(1, "link-agent");
}
debug = verbose;
/*
* Write out a pidfile if root.
*/
if (!getuid()) {
FILE *fp;
if (pidfile)
strcpy(buf, pidfile);
else
sprintf(buf, "%s/linkagent.pid", _PATH_VARRUN);
fp = fopen(buf, "w");
if (fp != NULL) {
fprintf(fp, "%d\n", getpid());
(void) fclose(fp);
}
}
/*
* Convert server/port to elvin thing.
*
* XXX This elvin string stuff should be moved down a layer.
*/
snprintf(buf, sizeof(buf), "elvin://%s%s%s",
server,
(port ? ":" : ""),
(port ? port : ""));
server = buf;
/* Register with the event system: */
handle = event_register_withkeyfile(server, 0, keyfile);
if (handle == NULL) {
fatal("could not register with event system");
}
/*
* Construct an address tuple for subscribing to events for
* this node.
*/
tuple = address_tuple_alloc();
if (tuple == NULL) {
fatal("could not allocate an address tuple");
}
tuple->host = ADDRESSTUPLE_ANY;
tuple->site = ADDRESSTUPLE_ANY;
tuple->group = ADDRESSTUPLE_ANY;
tuple->expt = pideid;
tuple->objtype = ADDRESSTUPLE_ANY;
tuple->eventtype = ADDRESSTUPLE_ANY;
tuple->objname = ""; /* See below */
/*
* Process the rest of the arguments. They are of the form:
*
* linkname,vnode,iface,mac linkname,vnode,iface,mac ....
*/
while (argc) {
char *bp, *ap = *argv;
ifmap_t *mp;
char *link, *vnode, *iface, *mac;
if ((bp = index(ap, ',')) == NULL)
usage();
*bp++ = (char) NULL;
link = ap;
vnode = bp;
if ((bp = index(bp, ',')) == NULL)
usage();
*bp++ = (char) NULL;
iface = bp;
if ((bp = index(bp, ',')) == NULL)
usage();
*bp++ = (char) NULL;
mac = convertmac(bp);
if (! mac) {
fatal("Must have a proper MAC!");
}
mac = strdup(mac);
/*
* Need two mappings; one for the entire link/lan, and one
* for just this member of the link,lan.
*/
if ((mp = calloc(1, sizeof(*mp))) == NULL)
fatal("Out of memory!");
mp->iface = iface;
mp->mac = mac;
mp->objname = link;
mp->next = mapping;
mapping = mp;
if (debug)
info("%s %s %s\n", mp->objname, mp->iface, mp->mac);
if ((mp = calloc(1, sizeof(*mp))) == NULL)
fatal("Out of memory!");
strcpy(buf, link);
strcat(buf, "-");
strcat(buf, vnode);
mp->iface = iface;
mp->mac = mac;
mp->objname = strdup(buf);
mp->next = mapping;
mapping = mp;
if (debug)
info("%s %s %s\n", mp->objname, mp->iface, mp->mac);
/*
* Also build up a subscription string.
*/
if ((bp = malloc(strlen(tuple->objname) + strlen(link) +
strlen(buf) + 3)) == NULL)
fatal("Out of memory!");
if (*(tuple->objname)) {
strcpy(bp, tuple->objname);
strcat(bp, ",");
}
else
*bp = (char) NULL;
strcat(bp, link);
strcat(bp, ",");
strcat(bp, buf);
tuple->objname = bp;
argc--;
argv++;
}
if (debug)
info("Subscribing to %s\n", tuple->objname);
/*
* Subscribe to the event we specified above.
*/
if (! event_subscribe(handle, callback, tuple, NULL)) {
fatal("could not subscribe to event");
}
/*
* Begin the event loop, waiting to receive event notifications:
*/
event_main(handle);
/* Unregister with the event system: */
if (event_unregister(handle) == 0) {
fatal("could not unregister with event system");
}
return 0;
}
/* Output macro to check for string overflow */
#define OUTPUT(buf, size, format...) \
({ \
int __count__ = snprintf((buf), (size), ##format); \
\
if (__count__ >= (size)) { \
error("Not enough room in output buffer! line %d.\n", __LINE__);\
return; \
} \
__count__; \
})
static void
callback(event_handle_t handle, event_notification_t notification, void *data)
{
char objname[TBDB_FLEN_EVOBJNAME];
char eventtype[TBDB_FLEN_EVEVENTTYPE];
char args[2 * BUFSIZ];
char cmd[2 * BUFSIZ];
ifmap_t *mp;
event_notification_get_objname(handle, notification,
objname, sizeof(objname));
event_notification_get_eventtype(handle, notification,
eventtype, sizeof(eventtype));
event_notification_get_arguments(handle, notification,
args, sizeof(args));
if (debug)
info("%s %s %s\n", objname, eventtype, args);
/*
* Find the mapping for this event so we know what interface to
* mess with.
*/
mp = mapping;
while (mp) {
if (strcmp(mp->objname, objname) == 0)
break;
mp = mp->next;
}
if (!mp) {
error("Unknown object name: %s\n", objname);
return;
}
/*
* The idea is this; there are currently two (or more) link agents
* vying for the same LINK events. We just worry about the ones
* we care about and ignore the rest. For example, the delay agent
* is also listening to these events.
*/
if (strcmp(eventtype, TBDB_EVENTTYPE_UP) == 0) {
sprintf(cmd, "iwconfig %s txpower auto", mp->iface);
runcommand(cmd);
}
else if (strcmp(eventtype, TBDB_EVENTTYPE_DOWN) == 0) {
sprintf(cmd, "iwconfig %s txpower off", mp->iface);
runcommand(cmd);
}
else if (strcmp(eventtype, TBDB_EVENTTYPE_MODIFY) == 0) {
char *ap = args;
char *cp = cmd, *ecp = &cmd[sizeof(cmd)-1];
int cmdlen;
cp += OUTPUT(cp, sizeof(cmd), "iwconfig %s ", mp->iface);
cmdlen = strlen(cmd);
/*
* Perl string matching would be so much nicer!
*/
while (*ap) {
char *kp, *setting, *value;
int quoted = 0;
while (*ap == ' ')
ap++;
if (! *ap)
break;
if ((kp = index(ap, '=')) == NULL) {
error("Could(1) not parse arg at '%s'\n", ap);
return;
}
setting = ap;
*kp++ = '\0';
ap = kp;
/*
* Setting is either enclosed in quotes, or a single
* token with no spaces in it.
*/
if (*ap == '\'') {
quoted = 1;
ap++;
}
value = ap;
/*
* Scan for end of value; up until quote,
* space, or null.
*/
while (*ap) {
if ((quoted && *ap == '\'') || *ap == ' ')
break;
ap++;
}
if ((quoted && *ap != '\'') ||
(!quoted && (*ap != '\0' && *ap != ' '))) {
error("Could(2) not parse arg at '%s'\n", ap);
return;
}
*ap = '\0';
/* Skip past close quote. */
if (quoted)
ap++;
ap++;
/*
* Okay, now do something with setting and value.
*/
if (! strcasecmp("sensitivity", setting)) {
cp += OUTPUT(cp, ecp - cp, " sens %s", value);
}
else if (! strcasecmp("txpower", setting)) {
if (*value == '\0')
value = "auto";
cp += OUTPUT(cp, ecp - cp, " txpower %s",
value);
}
else if (! strcasecmp("rate", setting) ||
! strcasecmp("bitrate", setting)) {
if (*value == '\0')
value = "auto";
cp += OUTPUT(cp, ecp - cp, " rate %s", value);
}
else if (! strcasecmp("channel", setting)) {
if (*value == '\0')
value = "3";
cp += OUTPUT(cp, ecp - cp, " channel %s",
value);
}
else if (! strcasecmp("accesspoint", setting)) {
char *mac = value;
if (! index(mac, ':')) {
mac = convertmac(mac);
if (mac == NULL)
continue;
}
if (strcasecmp(mac, mp->mac)) {
cp += OUTPUT(cp, ecp - cp,
" mode Managed ap %s",
mac);
}
else {
cp += OUTPUT(cp, ecp - cp,
" mode Master");
}
}
else {
error("Ignoring setting: %s=%s\n",
setting, value);
continue;
}
info("%s\n", cmd);
}
if (strlen(cmd) > cmdlen)
runcommand(cmd);
}
else if (debug) {
info("Ignoring event: %s %s %s\n", objname, eventtype, args);
}
}
/*
* Run a command. We use popen so we capture the output and send it to the
* the log file.
*/
static int
runcommand(char *cmd)
{
FILE *fp;
char buf[BUFSIZ];
info("%s\n", cmd);