mirror of
https://github.com/0intro/conterm
synced 2024-11-22 13:51:34 +03:00
23a48c7cfe
Addresses are now stored as uchar[16] instead of ulong, with enough room for IPv6. Generic IP functions have been removed from devip.c and replaced by libip, imported from Plan 9. Names and addresses are resolved using either gethostbyname() or getaddrinfo() functions. On Windows, IPv6 name resolution is not enabled, because mingw32 doesn't provide inet_ntop(). R=rsc http://codereview.appspot.com/6408044
794 lines
13 KiB
C
794 lines
13 KiB
C
#include "u.h"
|
|
#include "lib.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "error.h"
|
|
#include "ip.h"
|
|
|
|
#include "devip.h"
|
|
|
|
void csclose(Chan*);
|
|
long csread(Chan*, void*, long, vlong);
|
|
long cswrite(Chan*, void*, long, vlong);
|
|
|
|
void osipinit(void);
|
|
|
|
enum
|
|
{
|
|
Qtopdir = 1, /* top level directory */
|
|
Qcs,
|
|
Qprotodir, /* directory for a protocol */
|
|
Qclonus,
|
|
Qconvdir, /* directory for a conversation */
|
|
Qdata,
|
|
Qctl,
|
|
Qstatus,
|
|
Qremote,
|
|
Qlocal,
|
|
Qlisten,
|
|
|
|
MAXPROTO = 4
|
|
};
|
|
#define TYPE(x) ((int)((x).path & 0xf))
|
|
#define CONV(x) ((int)(((x).path >> 4)&0xfff))
|
|
#define PROTO(x) ((int)(((x).path >> 16)&0xff))
|
|
#define QID(p, c, y) (((p)<<16) | ((c)<<4) | (y))
|
|
#define ipzero(x) memset(x, 0, IPaddrlen)
|
|
|
|
typedef struct Proto Proto;
|
|
typedef struct Conv Conv;
|
|
struct Conv
|
|
{
|
|
int x;
|
|
Ref r;
|
|
int sfd;
|
|
int perm;
|
|
char owner[KNAMELEN];
|
|
char* state;
|
|
uchar laddr[IPaddrlen];
|
|
ushort lport;
|
|
uchar raddr[IPaddrlen];
|
|
ushort rport;
|
|
int restricted;
|
|
char cerr[KNAMELEN];
|
|
Proto* p;
|
|
};
|
|
|
|
struct Proto
|
|
{
|
|
Lock l;
|
|
int x;
|
|
int stype;
|
|
char name[KNAMELEN];
|
|
int nc;
|
|
int maxconv;
|
|
Conv** conv;
|
|
Qid qid;
|
|
};
|
|
|
|
static int np;
|
|
static Proto proto[MAXPROTO];
|
|
|
|
static Conv* protoclone(Proto*, char*, int);
|
|
static void setladdr(Conv*);
|
|
|
|
int
|
|
ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
|
|
{
|
|
Qid q;
|
|
Conv *cv;
|
|
char *p;
|
|
|
|
USED(nname);
|
|
q.vers = 0;
|
|
q.type = 0;
|
|
switch(TYPE(c->qid)) {
|
|
case Qtopdir:
|
|
if(s >= 1+np)
|
|
return -1;
|
|
|
|
if(s == 0){
|
|
q.path = QID(s, 0, Qcs);
|
|
devdir(c, q, "cs", 0, "network", 0666, dp);
|
|
}else{
|
|
s--;
|
|
q.path = QID(s, 0, Qprotodir);
|
|
q.type = QTDIR;
|
|
devdir(c, q, proto[s].name, 0, "network", DMDIR|0555, dp);
|
|
}
|
|
return 1;
|
|
case Qprotodir:
|
|
if(s < proto[PROTO(c->qid)].nc) {
|
|
cv = proto[PROTO(c->qid)].conv[s];
|
|
sprint(up->genbuf, "%d", s);
|
|
q.path = QID(PROTO(c->qid), s, Qconvdir);
|
|
q.type = QTDIR;
|
|
devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
|
|
return 1;
|
|
}
|
|
s -= proto[PROTO(c->qid)].nc;
|
|
switch(s) {
|
|
default:
|
|
return -1;
|
|
case 0:
|
|
p = "clone";
|
|
q.path = QID(PROTO(c->qid), 0, Qclonus);
|
|
break;
|
|
}
|
|
devdir(c, q, p, 0, "network", 0555, dp);
|
|
return 1;
|
|
case Qconvdir:
|
|
cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
|
|
switch(s) {
|
|
default:
|
|
return -1;
|
|
case 0:
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qdata);
|
|
devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
|
|
return 1;
|
|
case 1:
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qctl);
|
|
devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
|
|
return 1;
|
|
case 2:
|
|
p = "status";
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qstatus);
|
|
break;
|
|
case 3:
|
|
p = "remote";
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qremote);
|
|
break;
|
|
case 4:
|
|
p = "local";
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qlocal);
|
|
break;
|
|
case 5:
|
|
p = "listen";
|
|
q.path = QID(PROTO(c->qid), CONV(c->qid), Qlisten);
|
|
break;
|
|
}
|
|
devdir(c, q, p, 0, cv->owner, 0444, dp);
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
newproto(char *name, int type, int maxconv)
|
|
{
|
|
int l;
|
|
Proto *p;
|
|
|
|
if(np >= MAXPROTO) {
|
|
print("no %s: increase MAXPROTO", name);
|
|
return;
|
|
}
|
|
|
|
p = &proto[np];
|
|
strcpy(p->name, name);
|
|
p->stype = type;
|
|
p->qid.path = QID(np, 0, Qprotodir);
|
|
p->qid.type = QTDIR;
|
|
p->x = np++;
|
|
p->maxconv = maxconv;
|
|
l = sizeof(Conv*)*(p->maxconv+1);
|
|
p->conv = mallocz(l, 1);
|
|
if(p->conv == 0)
|
|
panic("no memory");
|
|
}
|
|
|
|
void
|
|
ipinit(void)
|
|
{
|
|
osipinit();
|
|
|
|
newproto("udp", S_UDP, 10);
|
|
newproto("tcp", S_TCP, 30);
|
|
|
|
fmtinstall('I', eipfmt);
|
|
fmtinstall('E', eipfmt);
|
|
|
|
}
|
|
|
|
Chan *
|
|
ipattach(char *spec)
|
|
{
|
|
Chan *c;
|
|
|
|
c = devattach('I', spec);
|
|
c->qid.path = QID(0, 0, Qtopdir);
|
|
c->qid.type = QTDIR;
|
|
c->qid.vers = 0;
|
|
return c;
|
|
}
|
|
|
|
static Walkqid*
|
|
ipwalk(Chan *c, Chan *nc, char **name, int nname)
|
|
{
|
|
return devwalk(c, nc, name, nname, 0, 0, ipgen);
|
|
}
|
|
|
|
int
|
|
ipstat(Chan *c, uchar *dp, int n)
|
|
{
|
|
return devstat(c, dp, n, 0, 0, ipgen);
|
|
}
|
|
|
|
Chan *
|
|
ipopen(Chan *c, int omode)
|
|
{
|
|
Proto *p;
|
|
uchar raddr[IPaddrlen];
|
|
ushort rport;
|
|
int perm, sfd;
|
|
Conv *cv, *lcv;
|
|
|
|
omode &= 3;
|
|
perm = 0;
|
|
switch(omode) {
|
|
case OREAD:
|
|
perm = 4;
|
|
break;
|
|
case OWRITE:
|
|
perm = 2;
|
|
break;
|
|
case ORDWR:
|
|
perm = 6;
|
|
break;
|
|
}
|
|
|
|
switch(TYPE(c->qid)) {
|
|
default:
|
|
break;
|
|
case Qtopdir:
|
|
case Qprotodir:
|
|
case Qconvdir:
|
|
case Qstatus:
|
|
case Qremote:
|
|
case Qlocal:
|
|
if(omode != OREAD)
|
|
error(Eperm);
|
|
break;
|
|
case Qclonus:
|
|
p = &proto[PROTO(c->qid)];
|
|
cv = protoclone(p, up->user, -1);
|
|
if(cv == 0)
|
|
error(Enodev);
|
|
c->qid.path = QID(p->x, cv->x, Qctl);
|
|
c->qid.vers = 0;
|
|
break;
|
|
case Qdata:
|
|
case Qctl:
|
|
p = &proto[PROTO(c->qid)];
|
|
lock(&p->l);
|
|
cv = p->conv[CONV(c->qid)];
|
|
lock(&cv->r.lk);
|
|
if((perm & (cv->perm>>6)) != perm) {
|
|
if(strcmp(up->user, cv->owner) != 0 ||
|
|
(perm & cv->perm) != perm) {
|
|
unlock(&cv->r.lk);
|
|
unlock(&p->l);
|
|
error(Eperm);
|
|
}
|
|
}
|
|
cv->r.ref++;
|
|
if(cv->r.ref == 1) {
|
|
memmove(cv->owner, up->user, KNAMELEN);
|
|
cv->perm = 0660;
|
|
}
|
|
unlock(&cv->r.lk);
|
|
unlock(&p->l);
|
|
break;
|
|
case Qlisten:
|
|
p = &proto[PROTO(c->qid)];
|
|
lcv = p->conv[CONV(c->qid)];
|
|
sfd = so_accept(lcv->sfd, raddr, &rport);
|
|
cv = protoclone(p, up->user, sfd);
|
|
if(cv == 0) {
|
|
close(sfd);
|
|
error(Enodev);
|
|
}
|
|
ipmove(cv->raddr, raddr);
|
|
cv->rport = rport;
|
|
setladdr(cv);
|
|
cv->state = "Established";
|
|
c->qid.path = QID(p->x, cv->x, Qctl);
|
|
break;
|
|
}
|
|
c->mode = openmode(omode);
|
|
c->flag |= COPEN;
|
|
c->offset = 0;
|
|
return c;
|
|
}
|
|
|
|
void
|
|
ipclose(Chan *c)
|
|
{
|
|
Conv *cc;
|
|
|
|
switch(TYPE(c->qid)) {
|
|
case Qcs:
|
|
csclose(c);
|
|
break;
|
|
case Qdata:
|
|
case Qctl:
|
|
if((c->flag & COPEN) == 0)
|
|
break;
|
|
cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
|
|
if(decref(&cc->r) != 0)
|
|
break;
|
|
strcpy(cc->owner, "network");
|
|
cc->perm = 0666;
|
|
cc->state = "Closed";
|
|
ipzero(cc->laddr);
|
|
ipzero(cc->raddr);
|
|
cc->lport = 0;
|
|
cc->rport = 0;
|
|
close(cc->sfd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
long
|
|
ipread(Chan *ch, void *a, long n, vlong offset)
|
|
{
|
|
int r;
|
|
Conv *c;
|
|
Proto *x;
|
|
uchar ip[IPaddrlen];
|
|
char buf[128], *p;
|
|
|
|
/*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
|
|
p = a;
|
|
switch(TYPE(ch->qid)) {
|
|
default:
|
|
error(Eperm);
|
|
case Qcs:
|
|
return csread(ch, a, n, offset);
|
|
case Qprotodir:
|
|
case Qtopdir:
|
|
case Qconvdir:
|
|
return devdirread(ch, a, n, 0, 0, ipgen);
|
|
case Qctl:
|
|
sprint(buf, "%d", CONV(ch->qid));
|
|
return readstr(offset, p, n, buf);
|
|
case Qremote:
|
|
c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
|
|
ipmove(ip, c->raddr);
|
|
sprint(buf, "%I!%d\n", ip, c->rport);
|
|
return readstr(offset, p, n, buf);
|
|
case Qlocal:
|
|
c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
|
|
ipmove(ip, c->laddr);
|
|
sprint(buf, "%I!%d\n", ip, c->lport);
|
|
return readstr(offset, p, n, buf);
|
|
case Qstatus:
|
|
x = &proto[PROTO(ch->qid)];
|
|
c = x->conv[CONV(ch->qid)];
|
|
sprint(buf, "%s/%d %d %s \n",
|
|
c->p->name, c->x, c->r.ref, c->state);
|
|
return readstr(offset, p, n, buf);
|
|
case Qdata:
|
|
c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
|
|
r = so_recv(c->sfd, a, n, 0);
|
|
if(r < 0){
|
|
oserrstr();
|
|
nexterror();
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
static void
|
|
setladdr(Conv *c)
|
|
{
|
|
so_getsockname(c->sfd, c->laddr, &c->lport);
|
|
}
|
|
|
|
static void
|
|
setlport(Conv *c)
|
|
{
|
|
if(c->restricted == 0 && c->lport == 0)
|
|
return;
|
|
|
|
if(c->sfd == -1)
|
|
c->sfd = so_socket(c->p->stype, c->laddr);
|
|
|
|
so_bind(c->sfd, c->restricted, c->lport, c->laddr);
|
|
}
|
|
|
|
static void
|
|
setladdrport(Conv *c, char *str)
|
|
{
|
|
char *p;
|
|
uchar addr[IPaddrlen];
|
|
|
|
p = strchr(str, '!');
|
|
if(p == 0) {
|
|
p = str;
|
|
ipzero(c->laddr);
|
|
}
|
|
else {
|
|
*p++ = 0;
|
|
parseip(addr, str);
|
|
ipmove(c->laddr, addr);
|
|
}
|
|
if(*p == '*')
|
|
c->lport = 0;
|
|
else
|
|
c->lport = atoi(p);
|
|
|
|
setlport(c);
|
|
}
|
|
|
|
static char*
|
|
setraddrport(Conv *c, char *str)
|
|
{
|
|
char *p;
|
|
uchar addr[IPaddrlen];
|
|
|
|
p = strchr(str, '!');
|
|
if(p == 0)
|
|
return "malformed address";
|
|
*p++ = 0;
|
|
parseip(addr, str);
|
|
ipmove(c->raddr, addr);
|
|
c->rport = atoi(p);
|
|
p = strchr(p, '!');
|
|
if(p) {
|
|
if(strcmp(p, "!r") == 0)
|
|
c->restricted = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
long
|
|
ipwrite(Chan *ch, void *a, long n, vlong offset)
|
|
{
|
|
Conv *c;
|
|
Proto *x;
|
|
int r, nf;
|
|
char *p, *fields[3], buf[128];
|
|
|
|
switch(TYPE(ch->qid)) {
|
|
default:
|
|
error(Eperm);
|
|
case Qcs:
|
|
return cswrite(ch, a, n, offset);
|
|
case Qctl:
|
|
x = &proto[PROTO(ch->qid)];
|
|
c = x->conv[CONV(ch->qid)];
|
|
if(n > sizeof(buf)-1)
|
|
n = sizeof(buf)-1;
|
|
memmove(buf, a, n);
|
|
buf[n] = '\0';
|
|
|
|
nf = tokenize(buf, fields, 3);
|
|
if(strcmp(fields[0], "connect") == 0){
|
|
switch(nf) {
|
|
default:
|
|
error("bad args to connect");
|
|
case 2:
|
|
p = setraddrport(c, fields[1]);
|
|
if(p != 0)
|
|
error(p);
|
|
break;
|
|
case 3:
|
|
p = setraddrport(c, fields[1]);
|
|
if(p != 0)
|
|
error(p);
|
|
c->lport = atoi(fields[2]);
|
|
setlport(c);
|
|
break;
|
|
}
|
|
if(c->sfd == -1)
|
|
c->sfd = so_socket(c->p->stype, c->raddr);
|
|
so_connect(c->sfd, c->raddr, c->rport);
|
|
setladdr(c);
|
|
c->state = "Established";
|
|
return n;
|
|
}
|
|
if(strcmp(fields[0], "announce") == 0) {
|
|
switch(nf){
|
|
default:
|
|
error("bad args to announce");
|
|
case 2:
|
|
setladdrport(c, fields[1]);
|
|
break;
|
|
}
|
|
so_listen(c->sfd);
|
|
c->state = "Announced";
|
|
return n;
|
|
}
|
|
if(strcmp(fields[0], "bind") == 0){
|
|
switch(nf){
|
|
default:
|
|
error("bad args to bind");
|
|
case 2:
|
|
c->lport = atoi(fields[1]);
|
|
break;
|
|
}
|
|
setlport(c);
|
|
return n;
|
|
}
|
|
error("bad control message");
|
|
case Qdata:
|
|
x = &proto[PROTO(ch->qid)];
|
|
c = x->conv[CONV(ch->qid)];
|
|
r = so_send(c->sfd, a, n, 0);
|
|
if(r < 0){
|
|
oserrstr();
|
|
nexterror();
|
|
}
|
|
return r;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static Conv*
|
|
protoclone(Proto *p, char *user, int nfd)
|
|
{
|
|
Conv *c, **pp, **ep;
|
|
|
|
c = 0;
|
|
lock(&p->l);
|
|
if(waserror()) {
|
|
unlock(&p->l);
|
|
nexterror();
|
|
}
|
|
ep = &p->conv[p->maxconv];
|
|
for(pp = p->conv; pp < ep; pp++) {
|
|
c = *pp;
|
|
if(c == 0) {
|
|
c = mallocz(sizeof(Conv), 1);
|
|
if(c == 0)
|
|
error(Enomem);
|
|
lock(&c->r.lk);
|
|
c->r.ref = 1;
|
|
c->p = p;
|
|
c->x = pp - p->conv;
|
|
p->nc++;
|
|
*pp = c;
|
|
break;
|
|
}
|
|
lock(&c->r.lk);
|
|
if(c->r.ref == 0) {
|
|
c->r.ref++;
|
|
break;
|
|
}
|
|
unlock(&c->r.lk);
|
|
}
|
|
if(pp >= ep) {
|
|
unlock(&p->l);
|
|
poperror();
|
|
return 0;
|
|
}
|
|
|
|
strcpy(c->owner, user);
|
|
c->perm = 0660;
|
|
c->state = "Closed";
|
|
c->restricted = 0;
|
|
ipzero(c->laddr);
|
|
ipzero(c->raddr);
|
|
c->lport = 0;
|
|
c->rport = 0;
|
|
c->sfd = nfd;
|
|
|
|
unlock(&c->r.lk);
|
|
unlock(&p->l);
|
|
poperror();
|
|
return c;
|
|
}
|
|
|
|
void
|
|
csclose(Chan *c)
|
|
{
|
|
free(c->aux);
|
|
}
|
|
|
|
long
|
|
csread(Chan *c, void *a, long n, vlong offset)
|
|
{
|
|
if(c->aux == nil)
|
|
return 0;
|
|
return readstr(offset, a, n, c->aux);
|
|
}
|
|
|
|
static struct
|
|
{
|
|
char *name;
|
|
uint num;
|
|
} tab[] = {
|
|
"cs", 1,
|
|
"echo", 7,
|
|
"discard", 9,
|
|
"systat", 11,
|
|
"daytime", 13,
|
|
"netstat", 15,
|
|
"chargen", 19,
|
|
"ftp-data", 20,
|
|
"ftp", 21,
|
|
"ssh", 22,
|
|
"telnet", 23,
|
|
"smtp", 25,
|
|
"time", 37,
|
|
"whois", 43,
|
|
"dns", 53,
|
|
"domain", 53,
|
|
"uucp", 64,
|
|
"gopher", 70,
|
|
"rje", 77,
|
|
"finger", 79,
|
|
"http", 80,
|
|
"link", 87,
|
|
"supdup", 95,
|
|
"hostnames", 101,
|
|
"iso-tsap", 102,
|
|
"x400", 103,
|
|
"x400-snd", 104,
|
|
"csnet-ns", 105,
|
|
"pop-2", 109,
|
|
"pop3", 110,
|
|
"portmap", 111,
|
|
"uucp-path", 117,
|
|
"nntp", 119,
|
|
"netbios", 139,
|
|
"imap4", 143,
|
|
"NeWS", 144,
|
|
"print-srv", 170,
|
|
"z39.50", 210,
|
|
"fsb", 400,
|
|
"sysmon", 401,
|
|
"proxy", 402,
|
|
"proxyd", 404,
|
|
"https", 443,
|
|
"cifs", 445,
|
|
"ssmtp", 465,
|
|
"rexec", 512,
|
|
"login", 513,
|
|
"shell", 514,
|
|
"printer", 515,
|
|
"courier", 530,
|
|
"cscan", 531,
|
|
"uucp", 540,
|
|
"snntp", 563,
|
|
"9fs", 564,
|
|
"whoami", 565,
|
|
"guard", 566,
|
|
"ticket", 567,
|
|
"dlsftp", 666,
|
|
"fmclient", 729,
|
|
"imaps", 993,
|
|
"pop3s", 995,
|
|
"ingreslock", 1524,
|
|
"pptp", 1723,
|
|
"nfs", 2049,
|
|
"webster", 2627,
|
|
"weather", 3000,
|
|
"secstore", 5356,
|
|
"Xdisplay", 6000,
|
|
"styx", 6666,
|
|
"mpeg", 6667,
|
|
"rstyx", 6668,
|
|
"infdb", 6669,
|
|
"infsigner", 6671,
|
|
"infcsigner", 6672,
|
|
"inflogin", 6673,
|
|
"bandt", 7330,
|
|
"face", 32000,
|
|
"dhashgate", 11978,
|
|
"exportfs", 17007,
|
|
"rexexec", 17009,
|
|
"ncpu", 17010,
|
|
"cpu", 17013,
|
|
"glenglenda1", 17020,
|
|
"glenglenda2", 17021,
|
|
"glenglenda3", 17022,
|
|
"glenglenda4", 17023,
|
|
"glenglenda5", 17024,
|
|
"glenglenda6", 17025,
|
|
"glenglenda7", 17026,
|
|
"glenglenda8", 17027,
|
|
"glenglenda9", 17028,
|
|
"glenglenda10", 17029,
|
|
"flyboy", 17032,
|
|
"dlsftp", 17033,
|
|
"venti", 17034,
|
|
"wiki", 17035,
|
|
"vica", 17036,
|
|
0
|
|
};
|
|
|
|
static int
|
|
lookupport(char *s)
|
|
{
|
|
int i;
|
|
char buf[10], *p;
|
|
|
|
i = strtol(s, &p, 0);
|
|
if(*s && *p == 0)
|
|
return i;
|
|
|
|
i = so_getservbyname(s, "tcp", buf);
|
|
if(i != -1)
|
|
return atoi(buf);
|
|
for(i=0; tab[i].name; i++)
|
|
if(strcmp(s, tab[i].name) == 0)
|
|
return tab[i].num;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lookuphost(char *s, uchar *to)
|
|
{
|
|
ipzero(to);
|
|
if(parseip(to, s) != -1)
|
|
return 0;
|
|
if((s = hostlookup(s)) == nil)
|
|
return -1;
|
|
parseip(to, s);
|
|
free(s);
|
|
return 0;
|
|
}
|
|
|
|
long
|
|
cswrite(Chan *c, void *a, long n, vlong offset)
|
|
{
|
|
char *f[4];
|
|
char *s, *ns;
|
|
uchar ip[IPaddrlen];
|
|
int nf, port;
|
|
|
|
s = malloc(n+1);
|
|
if(s == nil)
|
|
error(Enomem);
|
|
if(waserror()){
|
|
free(s);
|
|
nexterror();
|
|
}
|
|
memmove(s, a, n);
|
|
s[n] = 0;
|
|
nf = getfields(s, f, nelem(f), 0, "!");
|
|
if(nf != 3)
|
|
error("can't translate");
|
|
|
|
port = lookupport(f[2]);
|
|
if(port <= 0)
|
|
error("no translation for port found");
|
|
|
|
if(lookuphost(f[1], ip) < 0)
|
|
error("no translation for host found");
|
|
|
|
ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
|
|
if(ns == nil)
|
|
error(Enomem);
|
|
free(c->aux);
|
|
c->aux = ns;
|
|
poperror();
|
|
free(s);
|
|
return n;
|
|
}
|
|
|
|
Dev ipdevtab =
|
|
{
|
|
'I',
|
|
"ip",
|
|
|
|
devreset,
|
|
ipinit,
|
|
devshutdown,
|
|
ipattach,
|
|
ipwalk,
|
|
ipstat,
|
|
ipopen,
|
|
devcreate,
|
|
ipclose,
|
|
ipread,
|
|
devbread,
|
|
ipwrite,
|
|
devbwrite,
|
|
devremove,
|
|
devwstat,
|
|
};
|
|
|