/*
* cec — simple ethernet console
* Copyright © Coraid, Inc. 2006-2009
* All Rights Reserved.
*/
#include <u.h>
#include <libc.h>
#include <ip.h> /* really! */
#include <bio.h>
#include <ndb.h>
#include "cec.h"
enum {
Tinita = 0,
Tinitb,
Tinitc,
Tdata,
Tack,
Tdiscover,
Toffer,
Treset,
Hdrsz = 18,
Timeout = 7500,
};
typedef struct {
uchar ea[Eaddrlen];
int major;
char name[28];
} Shelf;
int conn(int);
int pickone(void);
void probe(void);
uchar bcast[Eaddrlen] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
uchar ea[Eaddrlen];
uchar ea0[Eaddrlen];
uchar contag;
char esc = '';
char *host;
int ntab;
char pflag;
int shelf = -1;
char *srv;
char *svc;
Shelf *con;
Shelf tab[1000];
extern int fd; /* set in netopen */
void
post(char *srv, int fd)
{
char buf[32];
int f;
if((f = create(srv, OWRITE, 0666)) == -1)
sysfatal("create %s: %r", srv);
snprint(buf, sizeof buf, "%d", fd);
if(write(f, buf, strlen(buf)) != strlen(buf))
sysfatal("write %s: %r", srv);
close(f);
}
void
dosrv(char *s)
{
int p[2];
if(pipe(p) < 0)
sysfatal("pipe: %r");
if (srv[0] != '/')
svc = smprint("/srv/%s", s);
else
svc = smprint("%s", s);
post(svc, p[0]);
close(p[0]);
dup(p[1], 0);
dup(p[1], 1);
switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
case -1:
sysfatal("fork: %r");
case 0:
break;
default:
exits("");
}
close(2);
}
void
usage(void)
{
fprint(2, "usage: cec [-dp] [-c esc] [-e ea] [-s shelf] [-h host] [-S srv] interface\n");
exits0("usage");
}
void
catch(void*, char *note)
{
if(strcmp(note, "alarm") == 0)
noted(NCONT);
noted(NDFLT);
}
int
nilea(uchar *ea)
{
return memcmp(ea, ea0, sizeof ea0) == 0;
}
int
specific(void)
{
return shelf != -1 || host != 0 || !nilea(ea);
}
void
main(int argc, char **argv)
{
char *p, *v;
int r, n;
ARGBEGIN{
case 'S':
srv = EARGF(usage());
break;
case 'c':
esc = toupper(*(EARGF(usage()))) - 'A' + 1;
if(esc < 1 || esc > 0x19)
usage();
break;
case 'd':
debug++;
break;
case 'e':
p = EARGF(usage());
if(v = csgetvalue(nil, "sys", p, "ether", 0))
p = v;
if(parseether(ea, p) == -1)
usage();
free(v);
pflag = 1;
break;
case 'h':
host = EARGF(usage());
break;
case 'p':
pflag = 1;
break;
case 's':
shelf = atoi(EARGF(usage()));
break;
default:
usage();
}ARGEND
if(argc == 0)
*argv = "/net/ether0";
else if(argc != 1)
usage();
fmtinstall('E', eipfmt);
if(srv != nil)
dosrv(srv);
r = netopen(*argv);
if(r == -1){
fprint(2, "cec: can't netopen %s\n", *argv);
exits0("open");
}
notify(catch);
probe();
for(;;){
n = 0;
if(!specific())
n = pickone();
rawon();
conn(n);
rawoff();
if(pflag == 0){
if(shelf != -1)
exits0("shelf not found");
if(host)
exits0("host not found");
if(!nilea(ea))
exits0("ea not found");
}else if(specific())
exits0("");
}
}
void
timewait(int ms)
{
alarm(ms);
}
int
didtimeout(void)
{
char buf[ERRMAX];
rerrstr(buf, sizeof buf);
if(strcmp(buf, "interrupted") == 0){
werrstr(buf, 0);
return 1;
}
return 0;
}
ushort
htons(ushort h)
{
ushort n;
uchar *p;
p = (uchar*)&n;
p[0] = h >> 8;
p[1] = h;
return n;
}
ushort
ntohs(int h)
{
ushort n;
uchar *p;
n = h;
p = (uchar*)&n;
return p[0] << 8 | p[1];
}
int
tcmp(void *a, void *b)
{
int d;
Shelf *s, *t;
s = a;
t = b;
d = s->major - t->major;
if(d == 0)
d = strcmp(s->name, t->name);
if(d == 0)
d = memcmp(s->ea, t->ea, Eaddrlen);
return d;
}
void
probe(void)
{
char *sh, *other;
int n, i;
Pkt q;
Shelf *p;
top:
ntab = 0;
memset(q.dst, 0xff, Eaddrlen);
memset(q.src, 0, Eaddrlen);
putbe(q.etype, Etype, 2);
q.type = Tdiscover;
q.len = 0;
q.conn = 0;
q.seq = 1; /* protocol extension */
netsend(&q, 60);
timewait(Iowait);
while((n = netget(&q, sizeof q)) >= 0){
if((n <= 0 && didtimeout()) || ntab == nelem(tab))
break;
if(n < 60 || q.len == 0 || q.type != Toffer)
continue;
q.data[q.len] = 0;
sh = strtok((char *)q.data, " \t");
if(sh == nil)
continue;
if(!nilea(ea) && memcmp(ea, q.src, Eaddrlen))
continue;
if(shelf != -1 && atoi(sh) != shelf)
continue;
other = strtok(nil, "\x1");
if(other == 0)
other = "";
if(host && strcmp(host, other))
continue;
p = tab + ntab++;
memcpy(p->ea, q.src, Eaddrlen);
p->major = atoi(sh);
p->name[0] = 0;
if(p->name)
snprint(p->name, sizeof p->name, "%s", other);
if(specific())
break;
/* dedup; prefer hostnames */
for(i = 0; i < ntab-1; i++)
if(memcmp(p->ea, tab[i].ea, Eaddrlen) == 0){
if(strlen(p->name) > 0)
memcpy(tab+i, p, sizeof *p);
ntab--;
break;
}
}
alarm(0);
if(ntab == 0){
if(pflag)
goto top;
fprint(2, "none found.\n");
exits0("none found");
}
qsort(tab, ntab, sizeof tab[0], tcmp);
}
void
showtable(void)
{
int i;
for(i = 0; i < ntab; i++){
if(i > 0 && tcmp(tab - 1, tab) == 0)
continue;
print("%2d %5d %E %s\n", i, tab[i].major, tab[i].ea, tab[i].name);
}
}
int
pickone(void)
{
char buf[80];
int n, i;
for(;;){
showtable();
print("[#qp]: ");
switch(n = read(0, buf, sizeof buf)){
case 1:
if(buf[0] == '\n')
continue;
case 2:
if(buf[0] == 'p'){
probe();
break;
}
if(buf[0] == 'q')
case 0:
case -1:
exits0(0);
}
if(buf[0] >= '0' && buf[0] <= '9'){
buf[n] = 0;
i = atoi(buf);
if(i >= 0 && i < ntab)
break;
}
}
return i;
}
void
sethdr(Pkt *p, int type)
{
memmove(p->dst, con->ea, Eaddrlen);
memset(p->src, 0, Eaddrlen);
putbe(p->etype, Etype, 2);
p->type = type;
p->len = 0;
p->conn = contag;
}
void
ethclose(void)
{
static Pkt msg;
sethdr(&msg, Treset);
timewait(Iowait);
netsend(&msg, 60);
alarm(0);
con = 0;
}
int
ethopen(int force)
{
int t, w;
Pkt tpk, rpk;
contag = (getpid() >> 8) ^ (getpid() & 0xff);
sethdr(&tpk, Tinita);
sethdr(&rpk, 0);
t = time(0);
while(rpk.type != Tinitb){
netsend(&tpk, 60);
w = 30*Iowait;
if(t - time(0) > 6){
if(!force)
break;
w = Iowait;
}
timewait(w);
netget(&rpk, sizeof rpk);
alarm(0);
}
if(rpk.type != Tinitb)
return -1;
sethdr(&tpk, Tinitc);
netsend(&tpk, 60);
return 0;
}
char
escape(void)
{
char buf[64];
int r;
for(;;){
fprint(2, ">>> ");
buf[0] = '.';
rawoff();
r = read(0, buf, sizeof buf - 1);
rawon();
if(r == -1)
exits0("kbd: %r");
switch(buf[0]){
case 'i':
case 'q':
case '.':
return buf[0];
}
fprint(2, " (q)uit, (i)nterrupt, (.)continue\n");
}
}
/*
* this is a bit too agressive. it really needs to replace only \n\r with \n.
*/
static uchar crbuf[256];
void
nocrwrite(int fd, uchar *buf, int n)
{
int i, j, c;
j = 0;
for(i = 0; i < n; i++){
if((c = buf[i]) == '\r')
continue;
crbuf[j++] = c;
}
write(fd, crbuf, j);
}
int
doloop(void)
{
uchar c, tseq, rseq, ea[Eaddrlen];
int l, unacked, set[2];
Pkt tpk, spk, ppk;
Mux *m;
memmove(ea, con->ea, Eaddrlen);
tseq = 0;
rseq = -1;
set[0] = 0;
set[1] = fd;
top:
if((m = mux(set)) == 0)
exits0("mux: %r");
unacked = 0;
sethdr(&ppk, Tdata);
for(;;) switch(muxread(m, &spk)){
case -1:
break;
case Ftimedout:
if(ppk.len > 0){
tpk = ppk;
tpk.seq = ++tseq;
unacked = tpk.len;
netsend(&tpk, Hdrsz+tpk.len);
muxtimeout(m, Timeout);
ppk.len = 0;
}else{
unacked = 0;
muxtimeout(m, 0);
}
break;
case Ftimeout:
netsend(&tpk, Hdrsz+unacked);
break;
case Fkbd:
c = spk.data[0];
if(c == esc) {
muxfree(m);
switch(escape()) {
case 'q':
tpk.len = 0;
tpk.type = Treset;
netsend(&tpk, 60);
return 0;
case '.':
goto top;
case 'i':
if((m = mux(set)) == 0)
exits0("mux: %r");
break;
}
}
if(unacked>0 || ppk.len>0){
l = spk.len;
if(ppk.len + l > 255)
l = 255 - ppk.len;
memmove(ppk.data + ppk.len, spk.data, l);
ppk.len += l;
}
if(unacked == 0){
sethdr(&tpk, Tdata);
memmove(tpk.data, spk.data, spk.len);
tpk.len = spk.len;
tpk.seq = ++tseq;
unacked = spk.len;
netsend(&tpk, Hdrsz+spk.len);
muxtimeout(m, Timeout);
}
break;
case Fcec:
if(memcmp(spk.src, ea, 6) != 0 || getbe(spk.etype, 2) != Etype)
continue;
if(spk.type == Toffer && spk.seq == 0){
muxfree(m);
return 1;
}
if(spk.conn != contag)
continue;
switch(spk.type){
case Tdata:
if(spk.seq == rseq)
break;
nocrwrite(1, spk.data, spk.len);
memmove(spk.dst, spk.src, Eaddrlen);
memset(spk.src, 0, Eaddrlen);
spk.type = Tack;
spk.len = 0;
rseq = spk.seq;
netsend(&spk, 60);
break;
case Tack:
if(spk.seq == tseq){
muxtimeout(m, 0);
unacked = 0;
if(ppk.len > 0){
tpk = ppk;
tpk.seq = ++tseq;
unacked = spk.len;
netsend(&tpk, Hdrsz+spk.len);
muxtimeout(m, Timeout);
ppk.len = 0;
}
}
break;
case Treset:
muxfree(m);
return 1;
}
break;
case Ffatal:
muxfree(m);
fprint(2, "kbd read error\n");
exits0("fatal");
}
}
int
conn(int n)
{
int r;
for(;;){
if(con)
ethclose();
con = tab + n;
if(debug)
fprint(2, "ethopen->\n");
if(ethopen(!specific()) < 0){
fprint(2, "connection failed\n");
return 0;
}
if(debug)
fprint(2, "ethopen<-\n");
r = doloop();
if(r <= 0)
return r;
}
}
void
exits0(char *s)
{
if(con != nil)
ethclose();
rawoff();
if(svc != nil)
remove(svc);
exits(s);
}
|