summaryrefslogtreecommitdiff
path: root/appl/cmd/ip
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/ip
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/ip')
-rw-r--r--appl/cmd/ip/bootpd.b662
-rw-r--r--appl/cmd/ip/dhcp.b162
-rw-r--r--appl/cmd/ip/mkfile30
-rw-r--r--appl/cmd/ip/nppp/mkfile24
-rw-r--r--appl/cmd/ip/nppp/modem.b469
-rw-r--r--appl/cmd/ip/nppp/modem.m47
-rw-r--r--appl/cmd/ip/nppp/pppchat.b322
-rw-r--r--appl/cmd/ip/nppp/ppplink.b782
-rw-r--r--appl/cmd/ip/nppp/ppptest.b90
-rw-r--r--appl/cmd/ip/nppp/script.b171
-rw-r--r--appl/cmd/ip/nppp/script.m15
-rw-r--r--appl/cmd/ip/obootpd.b777
-rw-r--r--appl/cmd/ip/ping.b369
-rw-r--r--appl/cmd/ip/ppp/mkfile27
-rw-r--r--appl/cmd/ip/ppp/modem.b468
-rw-r--r--appl/cmd/ip/ppp/modem.m41
-rw-r--r--appl/cmd/ip/ppp/pppclient.b216
-rw-r--r--appl/cmd/ip/ppp/pppclient.m31
-rw-r--r--appl/cmd/ip/ppp/pppdial.b283
-rw-r--r--appl/cmd/ip/ppp/pppgui.b373
-rw-r--r--appl/cmd/ip/ppp/pppgui.m21
-rw-r--r--appl/cmd/ip/ppp/ppptest.b86
-rw-r--r--appl/cmd/ip/ppp/script.b168
-rw-r--r--appl/cmd/ip/ppp/script.m14
-rw-r--r--appl/cmd/ip/rip.b620
-rw-r--r--appl/cmd/ip/sntp.b313
-rw-r--r--appl/cmd/ip/tftpd.b514
-rw-r--r--appl/cmd/ip/virgild.b127
28 files changed, 7222 insertions, 0 deletions
diff --git a/appl/cmd/ip/bootpd.b b/appl/cmd/ip/bootpd.b
new file mode 100644
index 00000000..bf3313b2
--- /dev/null
+++ b/appl/cmd/ip/bootpd.b
@@ -0,0 +1,662 @@
+implement Bootpd;
+
+#
+# to do:
+# DHCP
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Attr, Db, Dbentry, Tuples: import attrdb;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+include "ipattr.m";
+ ipattr: IPattr;
+
+include "ether.m";
+ ether: Ether;
+
+include "arg.m";
+
+Bootpd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+debug: int;
+sniff: int;
+verbose: int;
+
+siaddr: IPaddr;
+netmask: IPaddr;
+myname: string;
+progname := "bootpd";
+net := "/net";
+ndb: ref Db;
+ndbfile := "/lib/ndb/local";
+mtime := 0;
+testing := 0;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ loadfail(Bufio->PATH);
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loadfail(Attrdb->PATH);
+ attrdb->init();
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ loadfail(IP->PATH);
+ ip->init();
+ ipattr = load IPattr IPattr->PATH;
+ if(ipattr == nil)
+ loadfail(IPattr->PATH);
+ ipattr->init(attrdb, ip);
+ ether = load Ether Ether->PATH;
+ if(ether == nil)
+ loadfail(Ether->PATH);
+ ether->init();
+
+ verbose = 1;
+ sniff = 0;
+ debug = 0;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ raise "fail: load Arg";
+ arg->init(args);
+ arg->setusage("bootpd [-dsqv] [-f file] [-x network]");
+ progname = arg->progname();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 's' => sniff = 1; debug = 255;
+ 'q' => verbose = 0;
+ 'v' => verbose = 1;
+ 'x' => net = arg->earg();
+ 'f' => ndbfile = arg->earg();
+ 't' => testing = 1; debug = 1; verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
+
+ if(!sniff && (err := dbread()) != nil)
+ error(err);
+
+ myname = sysname();
+ if(myname == nil)
+ error("system name not set");
+ (siaddr, err) = csquery(myname);
+ if(err != nil)
+ error(sys->sprint("can't find IP address for %s: %s", myname, err));
+ if(debug)
+ sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text());
+
+ addr := net+"/udp!*!67";
+ if(testing)
+ addr = net+"/udp!*!499";
+ if(debug)
+ sys->fprint(stderr, "bootpd: announcing %s\n", addr);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ error(sys->sprint("can't announce %s: %r", addr));
+ if(sys->fprint(c.cfd, "headers") < 0)
+ error(sys->sprint("can't set headers mode: %r"));
+ sys->fprint(c.cfd, "oldheaders");
+
+ if(debug)
+ sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir);
+ c.dfd = sys->open(c.dir+"/data", sys->ORDWR);
+ if(c.dfd == nil)
+ error(sys->sprint("can't open %s/data: %r", c.dir));
+
+ spawn server(c);
+}
+
+loadfail(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "bootpd: %s\n", s);
+ raise "fail:error";
+}
+
+server(c: Sys->Connection)
+{
+ buf := array[2048] of byte;
+ badread := 0;
+ for(;;) {
+ n := sys->read(c.dfd, buf, len buf);
+ if(n <0) {
+ if (badread++ > 10)
+ break;
+ continue;
+ }
+ badread = 0;
+ if(n < Udphdrsize) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n);
+ continue;
+ }
+ hdr := Udphdr.unpack(buf, Udphdrsize);
+ if(debug)
+ sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport);
+ if(n < Udphdrsize+300) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize);
+ continue;
+ }
+
+ (bootp, err) := Bootp.unpack(buf[Udphdrsize:]);
+ if(err != nil) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err);
+ continue;
+ }
+ if(debug >= 2)
+ sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text());
+ if(sniff)
+ continue;
+ if(bootp.htype != 1 || bootp.hlen != 6) {
+ # if it isn't ether, we don't do it
+ if(debug)
+ sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n");
+ continue;
+ }
+ if((err = dbread()) != nil) {
+ sys->fprint(stderr, "bootpd: getreply: dbread failed: %s\n", err);
+ continue;
+ }
+ rec := lookup(bootp);
+ if(rec == nil) {
+ # we can't answer this request
+ if(debug)
+ sys->fprint(stderr, "bootpd: cannot answer request.\n");
+ continue;
+ }
+ if(debug)
+ sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text());
+ mkreply(bootp, rec);
+ if(verbose)
+ sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text());
+ if(debug)
+ sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text());
+ repl := bootp.pack();
+ if(!testing)
+ arpenter(rec.ip.text(), ether->text(rec.ha));
+ send(hdr, repl);
+ }
+ sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread);
+}
+
+arpenter(ip, ha: string)
+{
+ if(debug)
+ sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha);
+ fd := sys->open(net+"/arp", Sys->OWRITE);
+ if(fd == nil) {
+ if(debug)
+ sys->fprint(stderr, "bootpd: arp open failed: %r\n");
+ return;
+ }
+ if(sys->fprint(fd, "add %s %s", ip, ha) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootpd: error writing arp: %r\n");
+ }
+}
+
+sysname(): string
+{
+ t := rf("/dev/sysname");
+ if(t != nil)
+ return t;
+ return rf("#e/sysname");
+}
+
+rf(name: string): string
+{
+ fd := sys->open(name, Sys->OREAD);
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+csquery(name: string): (IPaddr, string)
+{
+ siaddr = ip->noaddr;
+ # get a local IP address by translating our sysname with cs(8)
+ csfile := net+"/cs";
+ fd := sys->open(net+"/cs", Sys->ORDWR);
+ if(fd == nil)
+ return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile));
+ if(sys->fprint(fd, "net!%s!0", name) < 0)
+ return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name));
+ sys->seek(fd, big 0, 0);
+ a := array[1024] of byte;
+ n := sys->read(fd, a, len a);
+ if(n <= 0)
+ return (ip->noaddr, "no result from "+csfile);
+ reply := string a[0:n];
+ (l, addr):= sys->tokenize(reply, " ");
+ if(l != 2)
+ return (ip->noaddr, "bad cs reply format");
+ (l, addr) = sys->tokenize(hd tl addr, "!");
+ if(l < 2)
+ return (ip->noaddr, "bad cs reply format");
+ (ok, ipa) := IPaddr.parse(hd addr);
+ if(ok < 0 || !ipok(siaddr))
+ return (ip->noaddr, "can't parse address: "+hd addr);
+ return (ipa, nil);
+}
+
+Hostinfo: adt {
+ hostname: string;
+
+ ha: array of byte; # hardware addr
+ ip: IPaddr; # client IP addr
+ bootf: string; # boot file path
+ netmask: IPaddr; # subnet mask
+ ipgw: IPaddr; # gateway IP addr
+ fs: IPaddr; # file server IP addr
+ auth: IPaddr; # authentication server IP addr
+
+ text: fn(inf: self ref Hostinfo): string;
+};
+
+send(hdr: ref Udphdr, msg: array of byte)
+{
+ replyaddr := net+"/udp!255.255.255.255!68"; # TO DO: gateway
+ if(testing)
+ replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport);
+ lport := "67";
+ if(testing)
+ lport = "499";
+ (n, c) := sys->dial(replyaddr, lport);
+ if(n < 0) {
+ sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr);
+ return;
+ }
+ n = sys->write(c.dfd, msg, len msg);
+ if(n != len msg)
+ sys->fprint(stderr, "bootpd: udp write error: %r\n");
+}
+
+mkreply(bootp: ref Bootp, rec: ref Hostinfo)
+{
+ bootp.op = 2; # boot reply
+ bootp.yiaddr = rec.ip;
+ bootp.siaddr = siaddr;
+ bootp.giaddr = ip->noaddr;
+ bootp.sname = myname;
+ bootp.file = string rec.bootf;
+ bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text());
+}
+
+dbread(): string
+{
+ if(ndb == nil){
+ ndb = Db.open(ndbfile);
+ if(ndb == nil)
+ return sys->sprint("cannot open %s: %r", ndbfile);
+ }else if(ndb.changed())
+ ndb.reopen();
+ return nil;
+}
+
+ipok(a: IPaddr): int
+{
+ return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast());
+}
+
+lookup(bootp: ref Bootp): ref Hostinfo
+{
+ if(ndb == nil)
+ return nil;
+ inf: ref Hostinfo;
+ hwaddr := ether->text(bootp.chaddr);
+ if(ipok(bootp.ciaddr)){
+ # client thinks it knows address; check match with MAC address
+ ipaddr := bootp.ciaddr.text();
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether");
+ if(e == nil)
+ break;
+ # TO DO: check result
+ inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr);
+ if(inf != nil)
+ return inf;
+ }
+ }
+ # look up an ip address associated with given MAC address
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip");
+ if(e == nil)
+ break;
+ # TO DO: check right net etc.
+ inf = matchandfill(e, "ether", hwaddr, "ip", nil);
+ if(inf != nil)
+ return inf;
+ }
+ return nil;
+}
+
+matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo
+{
+ matches := e.findbyattr(attr, val, rattr);
+ for(; matches != nil; matches = tl matches){
+ (line, attrs) := hd matches;
+ for(; attrs != nil; attrs = tl attrs){
+ if(rval == nil || (hd attrs).val == rval){
+ inf := fillup(line, e);
+ if(inf != nil)
+ return inf;
+ break;
+ }
+ }
+ }
+ return nil;
+}
+
+fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo
+{
+ ok: int;
+ inf := ref Hostinfo;
+ inf.netmask = ip->noaddr;
+ inf.ipgw = ip->noaddr;
+ inf.fs = ip->v4noaddr;
+ inf.auth = ip->v4noaddr;
+ inf.hostname = find(line, e, "sys");
+ s := find(line, e, "ether");
+ if(s != nil)
+ inf.ha = ether->parse(s);
+ s = find(line, e, "ip");
+ if(s == nil)
+ return nil;
+ (ok, inf.ip) = IPaddr.parse(s);
+ if(ok < 0)
+ return nil;
+ (results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"});
+ if(err != nil)
+ return nil;
+ for(; results != nil; results = tl results){
+ (a, nattrs) := hd results;
+ if(!a.eq(inf.ip))
+ continue; # different network
+ for(; nattrs != nil; nattrs = tl nattrs){
+ na := hd nattrs;
+ case na.name {
+ "ipmask" =>
+ inf.netmask = takeipmask(na.pairs, inf.netmask);
+ "ipgw" =>
+ inf.ipgw = takeipattr(na.pairs, inf.ipgw);
+ "fs" or "FILESERVER" =>
+ inf.fs = takeipattr(na.pairs, inf.fs);
+ "auth" or "SIGNER" =>
+ inf.auth = takeipattr(na.pairs, inf.auth);
+ "bootf" =>
+ inf.bootf = takeattr(na.pairs, inf.bootf);
+ }
+ }
+ }
+ return inf;
+}
+
+takeattr(pairs: list of ref Attr, s: string): string
+{
+ if(s != nil || pairs == nil)
+ return s;
+ return (hd pairs).val;
+}
+
+takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr
+{
+ if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
+ return a;
+ (ok, na) := parseip((hd pairs).val);
+ if(ok < 0)
+ return a;
+ return na;
+}
+
+takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr
+{
+ if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr)))
+ return a;
+ (ok, na) := IPaddr.parsemask((hd pairs).val);
+ if(ok < 0)
+ return a;
+ return na;
+}
+
+findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr)
+{
+ s := find(line, e, attr);
+ if(s == nil)
+ return (-1, ip->noaddr);
+ return parseip(s);
+}
+
+parseip(s: string): (int, IPaddr)
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0){
+ # look it up if it's a system name
+ s = findbyattr("sys", s, "ip");
+ (ok, a) = IPaddr.parse(s);
+ }
+ return (ok, a);
+}
+
+find(line: ref Tuples, e: ref Dbentry, attr: string): string
+{
+ if(line != nil){
+ a := line.find(attr);
+ if(a != nil)
+ return (hd a).val;
+ }
+ if(e != nil){
+ for(matches := e.find(attr); matches != nil; matches = tl matches){
+ (nil, a) := hd matches;
+ if(a != nil)
+ return (hd a).val;
+ }
+ }
+ return nil;
+}
+
+findbyattr(attr: string, val: string, rattr: string): string
+{
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr);
+ if(e == nil)
+ break;
+ rvl := e.find(rattr);
+ if(rvl != nil){
+ (nil, al) := hd rvl;
+ return (hd al).val;
+ }
+ }
+ return nil;
+}
+
+missing(rec: ref Hostinfo): string
+{
+ s := "";
+ if(rec.ha == nil)
+ s += " hardware address";
+ if(rec.ip.eq(ip->noaddr))
+ s += " IP address";
+ if(rec.bootf == nil)
+ s += " bootfile";
+ if(rec.netmask.eq(ip->noaddr))
+ s += " subnet mask";
+ if(rec.ipgw.eq(ip->noaddr))
+ s += " gateway";
+ if(rec.fs.eq(ip->noaddr))
+ s += " file server";
+ if(rec.auth.eq(ip->noaddr))
+ s += " authentication server";
+ if(s != "")
+ return s[1:];
+ return nil;
+}
+
+dtoa(data: array of byte): string
+{
+ if(data == nil)
+ return nil;
+ result: string;
+ for(i:=0; i < len data; i++)
+ result += sys->sprint(".%d", int data[i]);
+ return result[1:];
+}
+
+magic(cookie: array of byte): string
+{
+ if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' }))
+ return "plan9";
+ if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 }))
+ return "rfc1048";
+ if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 }))
+ return "cmu";
+ return dtoa(cookie);
+}
+
+eqa(a1: array of byte, a2: array of byte): int
+{
+ if(len a1 != len a2)
+ return 0;
+ for(i := 0; i < len a1; i++)
+ if(a1[i] != a2[i])
+ return 0;
+ return 1;
+}
+
+Hostinfo.text(rec: self ref Hostinfo): string
+{
+ return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s",
+ ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text());
+}
+
+Bootp: adt
+{
+ op: int; # opcode [1]
+ htype: int; # hardware type[1]
+ hlen: int; # hardware address length [1]
+ hops: int; # gateway hops [1]
+ xid: int; # random number [4]
+ secs: int; # seconds elapsed since client started booting [2]
+ flags: int; # flags[2]
+ ciaddr: IPaddr; # client ip address (client->server)[4]
+ yiaddr: IPaddr; # your ip address (server->client)[4]
+ siaddr: IPaddr; # server's ip address [4]
+ giaddr: IPaddr; # gateway ip address [4]
+ chaddr: array of byte; # client hardware (mac) address [16]
+ sname: string; # server host name [64]
+ file: string; # boot file name [128]
+ vend: array of byte; # vendor-specific [128]
+
+ unpack: fn(a: array of byte): (ref Bootp, string);
+ pack: fn(bp: self ref Bootp): array of byte;
+ text: fn(bp: self ref Bootp): string;
+};
+
+Bootp.unpack(data: array of byte): (ref Bootp, string)
+{
+ if(len data < 300)
+ return (nil, "too short");
+
+ bp := ref Bootp;
+ bp.op = int data[0];
+ bp.htype = int data[1];
+ bp.hlen = int data[2];
+ if(bp.hlen > 16)
+ return (nil, "length error");
+ bp.hops = int data[3];
+ bp.xid = ip->get4(data, 4);
+ bp.secs = ip->get2(data, 8);
+ bp.flags = ip->get2(data, 10);
+ bp.ciaddr = IPaddr.newv4(data[12:16]);
+ bp.yiaddr = IPaddr.newv4(data[16:20]);
+ bp.siaddr = IPaddr.newv4(data[20:24]);
+ bp.giaddr = IPaddr.newv4(data[24:28]);
+ bp.chaddr = data[28:28+bp.hlen];
+ bp.sname = ctostr(data[44:108]);
+ bp.file = ctostr(data[108:236]);
+ bp.vend = data[236:300];
+ return (bp, nil);
+}
+
+Bootp.pack(bp: self ref Bootp): array of byte
+{
+ data := array[364] of { * => byte 0 };
+ data[0] = byte bp.op;
+ data[1] = byte bp.htype;
+ data[2] = byte bp.hlen;
+ data[3] = byte bp.hops;
+ ip->put4(data, 4, bp.xid);
+ ip->put2(data, 8, bp.secs);
+ ip->put2(data, 10, bp.flags);
+ data[12:] = bp.ciaddr.v4();
+ data[16:] = bp.yiaddr.v4();
+ data[20:] = bp.siaddr.v4();
+ data[24:] = bp.giaddr.v4();
+ data[28:] = bp.chaddr;
+ data[44:] = array of byte bp.sname;
+ data[108:] = array of byte bp.file;
+ data[236:] = bp.vend;
+ return data;
+}
+
+ctostr(cstr: array of byte): string
+{
+ for(i:=0; i<len cstr; i++)
+ if(cstr[i] == byte 0)
+ break;
+ return string cstr[0:i];
+}
+
+Bootp.text(bp: self ref Bootp): string
+{
+ s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s",
+ int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text());
+ s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q",
+ bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4]));
+ if(magic(bp.vend[0:4]) == "plan9")
+ s += "("+ctostr(bp.vend)+")";
+ return s;
+}
diff --git a/appl/cmd/ip/dhcp.b b/appl/cmd/ip/dhcp.b
new file mode 100644
index 00000000..63ac2255
--- /dev/null
+++ b/appl/cmd/ip/dhcp.b
@@ -0,0 +1,162 @@
+implement Dhcp;
+
+#
+# configure an interface using DHCP
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPv4off, IPaddrlen, OUdphdrlen: import IP;
+ IPaddr: import ip;
+ get2, get4, put2, put4: import ip;
+
+include "dhcp.m";
+ dhcpclient: Dhcpclient;
+ Bootconf, Lease: import dhcpclient;
+
+include "arg.m";
+
+Dhcp: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+RetryTime: con 10*1000; # msec
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ ip = load IP IP->PATH;
+ dhcpclient = load Dhcpclient Dhcpclient->PATH;
+
+ sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: nil);
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("dhcp [-bdmnpr] [-g ipgw] [-h hostname] [-x /net] ifcdir [ip [ipmask]]");
+ trace := 0;
+ pcfg := 0;
+ bootp := 0;
+ monitor := 0;
+ retry := 0;
+ noctl := 0;
+ netdir := "/net";
+ cfg := Bootconf.new();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'b' => bootp = 1;
+ 'd' => trace++;
+ 'g' => cfg.ipgw = arg->earg();
+ 'h' => cfg.puts(Dhcpclient->Ohostname, arg->earg());
+ 'm' => monitor = 1;
+ 'n' => noctl = 1;
+ 'p' => pcfg = 1;
+ 'r' => retry = 1;
+ 'x' => netdir = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args == 0)
+ arg->usage();
+
+ ifcdir := hd args;
+ args = tl args;
+ if(args != nil){
+ cfg.ip = hd args;
+ args = tl args;
+ if(args != nil){
+ cfg.ipmask = hd args;
+ args = tl args;
+ if(args != nil)
+ arg->usage();
+ }
+ }
+ arg = nil;
+
+ ifcctl: ref Sys->FD;
+ if(noctl == 0){
+ ifcctl = sys->open(ifcdir+"/ctl", Sys->OWRITE);
+ if(ifcctl == nil)
+ err(sys->sprint("cannot open %s/ctl: %r", ifcdir));
+ }
+ etherdir := finddev(ifcdir);
+ if(etherdir == nil)
+ err(sys->sprint("cannot find network device in %s/status: %r", ifcdir));
+ if(etherdir[0] != '/' && etherdir[0] != '#')
+ etherdir = netdir+"/"+etherdir;
+
+ ip->init();
+ dhcpclient->init();
+ dhcpclient->tracing(trace);
+ e: string;
+ lease: ref Lease;
+ for(;;){
+ if(bootp){
+ (cfg, e) = dhcpclient->bootp(netdir, ifcctl, etherdir+"/addr", cfg);
+ if(e == nil){
+ if(cfg != nil)
+ dhcpclient->applycfg(netdir, ifcctl, cfg);
+ if(pcfg)
+ printcfg(cfg);
+ break;
+ }
+ }else{
+ (cfg, lease, e) = dhcpclient->dhcp(netdir, ifcctl, etherdir+"/addr", cfg, nil); # last is array of int options
+ if(e == nil){
+ if(pcfg)
+ printcfg(cfg);
+ if(cfg.lease > 0 && monitor)
+ leasemon(lease.configs, pcfg);
+ break;
+ }
+ }
+ if(!retry)
+ err("failed to configure network: "+e);
+ sys->fprint(sys->fildes(2), "dhcp: failed to configure network: %s; retrying", e);
+ sys->sleep(RetryTime);
+ }
+}
+
+leasemon(configs: chan of (ref Bootconf, string), pcfg: int)
+{
+ for(;;){
+ (cfg, e) := <-configs;
+ if(e != nil)
+ sys->fprint(sys->fildes(2), "dhcp: %s", e);
+ if(pcfg)
+ printcfg(cfg);
+ }
+}
+
+printcfg(cfg: ref Bootconf)
+{
+ sys->print("ip=%s ipmask=%s ipgw=%s iplease=%d\n", cfg.ip, cfg.ipmask, cfg.ipgw, cfg.lease);
+}
+
+finddev(ifcdir: string): string
+{
+ fd := sys->open(ifcdir+"/status", Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+ (nf, l) := sys->tokenize(string buf[0:n], " \n");
+ if(nf < 2){
+ sys->werrstr("unexpected format for status file");
+ return nil;
+ }
+ return hd tl l;
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "dhcp: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/ip/mkfile b/appl/cmd/ip/mkfile
new file mode 100644
index 00000000..a2238154
--- /dev/null
+++ b/appl/cmd/ip/mkfile
@@ -0,0 +1,30 @@
+<../../../mkconfig
+
+DIRS=\
+ ppp\
+# nppp\
+
+TARG=\
+ bootpd.dis\
+ dhcp.dis\
+ ping.dis\
+ rip.dis\
+ tftpd.dis\
+ virgild.dis\
+ obootpd.dis\
+ sntp.dis\
+
+SYSMODULES=\
+ attrdb.m\
+ bufio.m\
+ dhcp.m\
+ draw.m\
+ ether.m\
+ ip.m\
+ ipattr.m\
+ sys.m\
+
+DISBIN=$ROOT/dis/ip
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/ip/nppp/mkfile b/appl/cmd/ip/nppp/mkfile
new file mode 100644
index 00000000..0f803acd
--- /dev/null
+++ b/appl/cmd/ip/nppp/mkfile
@@ -0,0 +1,24 @@
+<../../../../mkconfig
+
+TARG=\
+ ppplink.dis\
+ pppchat.dis\
+ modem.dis\
+ script.dis\
+# ppptest.dis\
+
+MODULES=\
+ modem.m\
+ script.m\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ tk.m\
+ dict.m\
+ string.m\
+ lock.m\
+
+DISBIN=$ROOT/dis/ip/nppp
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ip/nppp/modem.b b/appl/cmd/ip/nppp/modem.b
new file mode 100644
index 00000000..f8c81396
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.b
@@ -0,0 +1,469 @@
+implement Modem;
+
+#
+# Copyright © 1998-2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+
+include "draw.m";
+
+include "modem.m";
+
+hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ
+
+# modem return codes
+Ok, Success, Failure, Abort, Noise, Found: con iota;
+
+maxspeed: con 115200;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", Ok),
+ ("NO CARRIER", Failure),
+ ("ERROR", Failure),
+ ("NO DIALTONE", Failure),
+ ("BUSY", Failure),
+ ("NO ANSWER", Failure),
+ ("CONNECT", Success),
+};
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0)
+ sys->print("modem: can't kill %d: %r\n", pid);
+}
+
+#
+# prepare a modem port
+#
+openserial(d: ref Device): string
+{
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil)
+ return sys->sprint("can't open %s: %r", d.local);
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil)
+ return sys->sprint("can't open %s: %r", d.local+"ctl");
+
+ d.speed = maxspeed;
+ d.avail = nil;
+ return nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+Device.close(m: self ref Device): ref Sys->Connection
+{
+ if(m.pid != 0){
+ kill(m.pid);
+ m.pid = 0;
+ }
+ if(m.data == nil)
+ return nil;
+ mc := ref sys->Connection(m.data, m.ctl, nil);
+ m.ctl = nil;
+ m.data = nil;
+ return mc;
+}
+
+#
+# Send a string to the modem
+#
+
+Device.send(d: self ref Device, x: string): string
+{
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if(f != len a) {
+ # let's attempt to close & reopen the modem
+ d.close();
+ err := openserial(d);
+ if(err != nil)
+ return err;
+ f = sys->write(d.data,a, len a);
+ if(f < 0)
+ return sys->sprint("%r");
+ if(f != len a)
+ return "short write";
+ }
+ if(d.trace)
+ sys->print("->%s\n",x);
+ return nil;
+}
+
+#
+# apply a string of commands to modem & look for a response
+#
+
+apply(d: ref Device, s: string, substr: string, secs: int): int
+{
+ m := Ok;
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(d.send(buf) != nil)
+ return Abort;
+ (m, nil) = readmsg(d, secs, substr);
+ buf = "";
+ }
+ }
+ return m;
+}
+
+#
+# get modem into command mode if it isn't already
+#
+GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz
+
+attention(d: ref Device): int
+{
+ for(i := 0; i < 3; i++){
+ if(apply(d, hangupcmd, nil, 2) == Ok)
+ return Ok;
+ sys->sleep(GUARDTIME);
+ if(d.send("+++") != nil)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil && d.trace)
+ sys->print("status: %s\n", msg);
+ }
+ return Failure;
+}
+
+#
+# apply a command type
+#
+
+applyspecial(d: ref Device, cmd: string): int
+{
+ if(cmd == nil)
+ return Failure;
+ return apply(d, cmd, nil, 2);
+}
+
+#
+# hang up any connections in progress and close the device
+#
+Device.onhook(d: self ref Device)
+{
+ # hang up the modem
+ monitoring(d);
+ if(attention(d) != Ok)
+ sys->print("modem: no attention\n");
+
+ # hangup the stream (eg, for ppp) and toggle the lines to the modem
+ if(d.ctl != nil) {
+ sys->fprint(d.ctl,"d0\n");
+ sys->fprint(d.ctl,"r0\n");
+ sys->fprint(d.ctl, "h\n"); # hangup on native serial
+ sys->sleep(250);
+ sys->fprint(d.ctl,"r1\n");
+ sys->fprint(d.ctl,"d1\n");
+ }
+
+ d.close();
+}
+
+#
+# does string s contain t anywhere?
+#
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+#
+# read till we see a message or we time out
+#
+readmsg(d: ref Device, secs: int, substr: string): (int, string)
+{
+ found := 0;
+ msecs := secs*1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+msecs;){
+ a := d.getinput(1);
+ if(len a == 0){
+ if(limit){
+ sys->sleep(1);
+ continue;
+ }
+ break;
+ }
+ if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){
+ if (len s) {
+ if (s[(len s)-1] == '\r')
+ s[(len s)-1] = '\n';
+ sys->print("<-%s\n",s);
+ }
+ if(substr != nil && contains(s, substr))
+ found = 1;
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text &&
+ s[0:len msgs[k].text] == msgs[k].text){
+ if(found)
+ return (Found, s);
+ return (msgs[k].code, s);
+ }
+ start = sys->millisec();
+ s = "";
+ continue;
+ }
+ s[len s] = int a[0];
+ limit--;
+ }
+ s = "no response from modem";
+ if(found)
+ return (Found, s);
+
+ return (Noise, s);
+}
+
+#
+# get baud rate from a connect message
+#
+
+getspeed(msg: string, speed: int): int
+{
+ p := msg[7:]; # skip "CONNECT"
+ while(p[0] == ' ' || p[0] == '\t')
+ p = p[1:];
+ s := int p;
+ if(s <= 0)
+ return speed;
+ else
+ return s;
+}
+
+#
+# set speed and RTS/CTS modem flow control
+#
+
+setspeed(d: ref Device, baud: int)
+{
+ if(d != nil && d.ctl != nil){
+ sys->fprint(d.ctl, "b%d", baud);
+ sys->fprint(d.ctl, "m1");
+ }
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc, nil);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int, errc: chan of string)
+{
+ err := openserial(d);
+ pidc <-= sys->pctl(0, nil);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ a := array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ d.lock.obtain();
+ d.status = "Idle";
+ d.remote = "";
+ setspeed(d, d.speed);
+ d.lock.release();
+ # shuttle bytes
+ while((n := sys->read(d.data, a, len a)) > 0){
+ d.lock.obtain();
+ if (len d.avail < Sys->ATOMICIO) {
+ na := array[len d.avail + n] of byte;
+ na[0:] = d.avail[0:];
+ na[len d.avail:] = a[0:n];
+ d.avail = na;
+ }
+ d.lock.release();
+ }
+ # on an error, try reopening the device
+ d.data = nil;
+ d.ctl = nil;
+ err = openserial(d);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+Device.getinput(d: self ref Device, n: int): array of byte
+{
+ if(d==nil || n <= 0)
+ return nil;
+ a: array of byte;
+ d.lock.obtain();
+ if(len d.avail != 0){
+ if(n > len d.avail)
+ n = len d.avail;
+ a = d.avail[0:n];
+ d.avail = d.avail[n:];
+ }
+ d.lock.release();
+ return a;
+}
+
+Device.getc(d: self ref Device, msec: int): int
+{
+ start := sys->millisec();
+ while((b := d.getinput(1)) == nil) {
+ if (msec && sys->millisec() > start+msec)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if(lock == nil)
+ return sys->sprint("can't load %s: %r", Lock->PATH);
+ lock->init();
+ return nil;
+}
+
+Device.new(modeminfo: ref ModemInfo, trace: int): ref Device
+{
+ d := ref Device;
+ d.lock = Semaphore.new();
+ d.local = modeminfo.path;
+ d.pid = 0;
+ d.speed = 0;
+ d.t = *modeminfo;
+ if(d.t.hangup == nil)
+ d.t.hangup = hangupcmd;
+ d.trace = trace | 1; # always trace for now
+ return d;
+}
+
+#
+# dial a number
+#
+Device.dial(d: self ref Device, number: string): string
+{
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ if(d.trace)
+ sys->print("modem: attention\n");
+ x := attention(d);
+ if (x != Ok && d.trace)
+ return "bad response from modem";
+ #
+ # extended Hayes commands, meaning depends on modem
+ #
+ sys->print("modem: init\n");
+ if(d.t.country != nil)
+ applyspecial(d, d.t.country);
+
+ if(d.t.init != nil)
+ applyspecial(d, d.t.init);
+
+ if(d.t.other != nil)
+ applyspecial(d, d.t.other);
+
+ applyspecial(d, d.t.errorcorrection);
+
+ compress := Abort;
+ if(d.t.mnponly != nil)
+ compress = applyspecial(d, d.t.mnponly);
+ if(d.t.compression != nil)
+ compress = applyspecial(d, d.t.compression);
+
+ rateadjust := Abort;
+ if(compress != Ok)
+ rateadjust = applyspecial(d, d.t.rateadjust);
+ applyspecial(d, d.t.flowctl);
+
+ # finally, dialout
+ if(d.trace)
+ sys->print("modem: dial\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ err := d.send(sys->sprint("%s%s\r", dt, number));
+ if(err != nil){
+ if(d.trace)
+ sys->print("modem: can't dial %s: %s\n", number, err);
+ return err;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success){
+ if(d.trace)
+ sys->print("modem: modem error reply: %s\n", msg);
+ return msg;
+ }
+
+ connectspeed := getspeed(msg, d.speed);
+
+ # change line rate if not compressing
+ if(rateadjust == Ok)
+ setspeed(d, connectspeed);
+
+ if(d.ctl != nil){
+ if(d != nil)
+ sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it)
+ sys->fprint(d.ctl, "c1"); # enable CD monitoring
+ }
+
+ return nil;
+}
+
+dumpa(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++){
+ b := int a[i];
+ if(b >= ' ' && b < 16r7f)
+ s[len s] = b;
+ else
+ s += sys->sprint("\\%.2x", b);
+ }
+ return s;
+}
diff --git a/appl/cmd/ip/nppp/modem.m b/appl/cmd/ip/nppp/modem.m
new file mode 100644
index 00000000..6e84b0e3
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.m
@@ -0,0 +1,47 @@
+Modem: module
+{
+ PATH: con "/dis/ip/nppp/modem.dis";
+
+ ModemInfo: adt {
+ path: string;
+ init: string;
+ country: string;
+ other: string;
+ errorcorrection:string;
+ compression: string;
+ flowctl: string;
+ rateadjust: string;
+ mnponly: string;
+ dialtype: string;
+ hangup: string;
+ };
+
+ Device: adt {
+ lock: ref Lock->Semaphore;
+ # modem stuff
+ ctl: ref Sys->FD;
+ data: ref Sys->FD;
+
+ local: string;
+ remote: string;
+ status: string;
+ speed: int;
+ t: ModemInfo;
+ trace: int;
+
+ # input reader
+ avail: array of byte;
+ pid: int;
+
+ new: fn(i: ref ModemInfo, trace: int): ref Device;
+ dial: fn(m: self ref Device, number: string): string;
+ getc: fn(m: self ref Device, msec: int): int;
+ getinput: fn(m: self ref Device, n: int): array of byte;
+ send: fn(m: self ref Device, x: string): string;
+ close: fn(m: self ref Device): ref Sys->Connection;
+ onhook: fn(m: self ref Device);
+ };
+
+ init: fn(): string;
+
+};
diff --git a/appl/cmd/ip/nppp/pppchat.b b/appl/cmd/ip/nppp/pppchat.b
new file mode 100644
index 00000000..77202b18
--- /dev/null
+++ b/appl/cmd/ip/nppp/pppchat.b
@@ -0,0 +1,322 @@
+implement Dialupchat;
+
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "wmlib.m";
+ wmlib: Wmlib;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+Dialupchat: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# Dimension constant for ISP Connect window
+WIDTH: con 300;
+HEIGHT: con 58;
+
+LightGreen: con "#00FF80"; # colour for successful blob
+Blobx: con 8;
+Gapx: con 4;
+BARW: con (Blobx+Gapx)*10; # Progress bar width
+BARH: con 18; # Progress bar height
+DIALQUANTA : con 1000;
+ICONQUANTA : con 5000;
+
+pppquanta := DIALQUANTA;
+
+Maxstep: con 9;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ wmlib = load Wmlib Wmlib->PATH;
+ wmlib->init();
+
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppchat");
+ dicterr: string;
+ (dict, dicterr) = translate->opendict(dictname);
+ if(dicterr != nil)
+ sys->fprint(sys->fildes(2), "pppchat: can't open %s: %s\n", dictname, dicterr);
+ }else
+ sys->fprint(sys->fildes(2), "pppchat: can't load %s: %r\n", Translate->PATH);
+
+ tkargs: string;
+ if(args != nil) {
+ tkargs = hd args;
+ args = tl args;
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ pppfd := sys->open("/chan/pppctl", Sys->ORDWR);
+ if(pppfd == nil)
+ error(sys->sprint("can't open /chan/pppctl: %r"));
+
+ (t, wmctl) := wmlib->titlebar(ctxt.screen, tkargs, X("Dialup Connection"), Wmlib->Hide);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ pb := Progressbar.mk(t, ".f.prog.c", (BARW, BARH));
+
+ config_win := array[] of {
+ "frame .f",
+ "frame .f.prog",
+ "frame .f.b",
+
+ pb.tkcreate(),
+ "pack .f.prog.c -pady 6 -side top",
+
+ "label .f.stat -fg blue -text {"+X("Initialising connection...")+"}",
+ "pack .f.stat -side top -fill x -expand 1 -anchor n",
+
+ "pack .f -side top -expand 1 -padx 5 -pady 3 -fill both -anchor w",
+ "pack .f.prog -side top -expand 1 -fill x",
+ "button .f.b.done -text {"+X("Cancel")+"} -command {send cmd cancel}",
+ "pack .f.b.done -side right -padx 1 -pady 1 -anchor s",
+ "button .f.b.retry -text {"+X("Retry")+"} -command {send cmd retry} -state disabled",
+ "pack .f.b.retry -side left -padx 1 -pady 1 -anchor s",
+ "pack .f.b -side top -expand 1 -fill x",
+
+ "pack propagate . 0",
+ "update",
+ };
+
+ for(i := 0; i < len config_win; i++)
+ tkcmd(t, config_win[i]);
+
+ connected := 0;
+ winmapped := 1;
+ timecount := 0;
+ xmin := 0;
+ x := 0;
+ turn := 0;
+
+ pppquanta = DIALQUANTA;
+ ticks := chan of int;
+ spawn ppptimer(ticks);
+
+ statuslines := chan of (string, string);
+ pids := chan of int;
+ spawn ctlreader(pppfd, pids, statuslines);
+ ctlpid := <-pids;
+
+Work:
+ for(;;) alt {
+
+ s := <-wmctl =>
+ if(s == "exit")
+ s = "task";
+ if(s == "task"){
+ spawn wmlib->titlectl(t, s);
+ continue;
+ }
+ wmlib->titlectl(t, s);
+
+ press := <-cmd =>
+ case press {
+ "cancel" or "disconnect" =>
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s", X("Disconnecting")));
+ tkcmd(t, "update");
+ if(sys->fprint(pppfd, "hangup") < 0){
+ err := sys->sprint("%r");
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s: %s", X("Error disconnecting"), X(err)));
+ sys->fprint(sys->fildes(2), "pppchat: can't disconnect: %s\n", err);
+ }
+ break Work;
+ "retry" =>
+ if(sys->fprint(pppfd, "connect") < 0){
+ err := sys->sprint("%r");
+ }
+ }
+
+ <-ticks =>
+ ticks <-= 1;
+ if(!connected){
+ if(pb != nil){
+ if((turn ^= 1) == 0)
+ pb.setcolour("white");
+ else
+ pb.setcolour(LightGreen);
+ }
+ tkcmd(t, "raise .; update");
+ }
+
+ (status, err) := <-statuslines =>
+ if(status == nil){
+ status = "0 1 empty status";
+ if(err != nil)
+ sys->print("pppchat: !%s\n", err);
+ } else
+ sys->print("pppchat: %s\n", status);
+ (nf, flds) := sys->tokenize(status, " \t\n");
+# for(i = 0; i < len status; i++)
+# if(status[i] == ' ' || status[i] == '\t') {
+# status = status[i+1:];
+# break;
+# }
+ if(nf < 3)
+ break;
+ step := int hd flds; flds = tl flds;
+ nstep := int hd flds; flds = tl flds;
+ if(step < 0)
+ raise "pppchat: bad step";
+ case hd flds {
+ "error:" =>
+ tkcmd(t, ".f.stat configure -fg red -text '"+X(status));
+ tkcmd(t, ".f.b.retry configure -state normal");
+ tkcmd(t, "update");
+ wmlib->unhide();
+ winmapped = 1;
+ pb.stepto(step, "red");
+ #break Work;
+ * =>
+ pb.setcolour(LightGreen);
+ pb.stepto(step, LightGreen);
+ }
+ turn = 0;
+ statusmsg := X(status);
+ tkcmd(t, ".f.stat configure -text '"+statusmsg);
+ tkcmd(t, "raise .; update");
+
+ case hd flds {
+ "up" or "done" =>
+ if(!connected){
+ connected = 1;
+ }
+ pppquanta = ICONQUANTA;
+
+ # display connection speed
+ if(tl flds != nil)
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+" "+"SPEED"+" hd tl flds}");
+ else
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+"}");
+ tkcmd(t, ".f.b.done configure -text Disconnect -command 'send cmd disconnect");
+ tkcmd(t, "update");
+ sys->sleep(2000);
+ tkcmd(t, "pack forget .f.prog; update");
+ spawn wmlib->titlectl(t, "task");
+ winmapped = 0;
+ }
+ tkcmd(t, "update");
+ }
+ <-ticks;
+ ticks <-= 0; # stop ppptimer
+ kill(ctlpid);
+}
+
+ppptimer(ticks: chan of int)
+{
+ do{
+ sys->sleep(pppquanta);
+ ticks <-= 1;
+ }while(<-ticks);
+}
+
+ctlreader(fd: ref Sys->FD, pidc: chan of int, lines: chan of (string, string))
+{
+ pidc <-= sys->pctl(0, nil);
+ buf := array[128] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ lines <-= (string buf[0:n], nil);
+ if(n < 0)
+ lines <-= (nil, sys->sprint("%r"));
+ else
+ lines <-= (nil, nil);
+}
+
+Progressbar: adt {
+ t: ref Tk->Toplevel;
+ canvas: string;
+ csize: Point;
+ blobs: list of string;
+
+ mk: fn(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar;
+ tkcreate: fn(pb: self ref Progressbar): string;
+ setcolour: fn(pb: self ref Progressbar, c: string);
+ stepto: fn(pb: self ref Progressbar, step: int, col: string);
+ destroy: fn(pb: self ref Progressbar);
+};
+
+Progressbar.mk(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar
+{
+ return ref Progressbar(t, canvas, csize, nil);
+}
+
+Progressbar.tkcreate(pb: self ref Progressbar): string
+{
+ return sys->sprint("canvas %s -width %d -height %d", pb.canvas, pb.csize.x, pb.csize.y);
+}
+
+Progressbar.setcolour(pb: self ref Progressbar, colour: string)
+{
+ if(pb.blobs != nil)
+ tkcmd(pb.t, sys->sprint("%s itemconfigure %s -fill %s; update", pb.canvas, hd pb.blobs, colour));
+}
+
+Progressbar.stepto(pb: self ref Progressbar, step: int, col: string)
+{
+ for(nblob := len pb.blobs; nblob > step+1; nblob--){
+ tkcmd(pb.t, sys->sprint("%s delete %s", pb.canvas, hd pb.blobs));
+ pb.blobs = tl pb.blobs;
+ }
+ if(nblob == step+1)
+ return;
+ p := Point(step*(Blobx+Gapx), 0);
+ r := Rect(p, p.add((Blobx, pb.csize.y-2)));
+ pb.blobs = tkcmd(pb.t, sys->sprint("%s create rectangle %d %d %d %d -fill %s", pb.canvas, r.min.x,r.min.y, r.max.x,r.max.y, col)) :: pb.blobs;
+}
+
+Progressbar.destroy(pb: self ref Progressbar)
+{
+ tk->cmd(pb.t, "destroy "+pb.canvas); # ignore errors
+}
+
+tkcmd(t: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(t, s);
+ if(e != nil && e[0] == '!')
+ sys->print("pppchat: tk error: %s [%s]\n", e, s);
+ return e;
+}
+
+kill(pid: int)
+{
+ if(pid > 0 && (fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
+ sys->fprint(fd, "kill");
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "pppchat: %s\n", s);
+ raise "fail:error";
+}
+
+X(s: string): string
+{
+ if(dict != nil)
+ return dict.xlate(s);
+ return s;
+}
diff --git a/appl/cmd/ip/nppp/ppplink.b b/appl/cmd/ip/nppp/ppplink.b
new file mode 100644
index 00000000..5f0e9686
--- /dev/null
+++ b/appl/cmd/ip/nppp/ppplink.b
@@ -0,0 +1,782 @@
+implement PPPlink;
+
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "cfgfile.m";
+ cfg: CfgFile;
+ ConfigFile: import cfg;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+
+include "sh.m";
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+PPPlink: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+PPPInfo: adt {
+ ipaddr: string;
+ ipmask: string;
+ peeraddr: string;
+ maxmtu: string;
+ username: string;
+ password: string;
+};
+
+modeminfo: ref Modem->ModemInfo;
+context: ref Draw->Context;
+pppinfo: ref PPPInfo;
+scriptinfo: ref Script->ScriptInfo;
+isp_number: string;
+lastCdir: ref Sys->Dir; # state of file when last read
+netdir := "/net";
+
+Packet: adt {
+ src: array of byte;
+ dst: array of byte;
+ data: array of byte;
+};
+
+DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo
+DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo
+MODEM_DB_PATH: con "modem.cfg"; # contains modeminfo
+ISP_DB_PATH: con "isp.cfg"; # contains pppinfo & scriptinfo
+
+primary := 0;
+framing := 1;
+
+Disconnected, Modeminit, Dialling, Modemup, Scriptstart, Scriptdone, Startingppp, Startedppp, Login, Linkup: con iota;
+Error: con -1;
+
+Ignorems: con 10*1000; # time to ignore outgoing packets between dial attempts
+
+statustext := array[] of {
+Disconnected => "Disconnected",
+Modeminit => "Initializing Modem",
+Dialling => "Dialling Service Provider",
+Modemup => "Logging Into Network",
+Scriptstart => "Executing Login Script",
+Scriptdone => "Script Execution Complete",
+Startingppp => "Logging Into Network",
+Startedppp => "Logging Into Network",
+Login => "Verifying Password",
+Linkup => "Connected",
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: ppplink [-P] [-f] [-m mtu] [local [remote]]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppclient");
+ (dict, nil) = translate->opendict(dictname);
+ }
+ mtu := 1450;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(0, sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'm' =>
+ if((s := arg->arg()) == nil || !(s[0]>='0' && s[0]<='9'))
+ usage();
+ mtu = int s;
+ 'P' =>
+ primary = 1;
+ 'f' =>
+ framing = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ localip := "10.9.8.7"; # should be something locally unique
+ fake := 1;
+ if(args != nil){
+ fake = 0;
+ localip = hd args;
+ args = tl args;
+ }
+
+ cerr := configinit();
+ if(cerr != nil)
+ error(0, sys->sprint("can't configure: %s", cerr));
+ context = ctxt;
+
+ # make default (for now)
+ # if packet appears, start ppp and reset routing until it stops
+
+ (cfd, dir, err) := getifc();
+ if(err != nil)
+ error(0, err);
+
+ if(sys->fprint(cfd, "bind pkt") < 0)
+ error(0, sys->sprint("can't bind pkt: %r"));
+ if(sys->fprint(cfd, "add %s 255.255.255.0 10.9.8.0 %d", localip, mtu) < 0)
+ error(0, sys->sprint("can't add ppp addresses: %r"));
+ if(primary && addroute("0", "0", localip) < 0)
+ error(0, sys->sprint("can't add default route: %r"));
+ dfd := sys->open(dir+"/data", Sys->ORDWR);
+ if(dfd == nil)
+ error(0, sys->sprint("can't open %s: %r", dir));
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ packets := chan of ref Packet;
+ spawn netreader(dfd, dir, localip, fake, packets);
+
+ logger := chan of (int, string);
+ iocmd := sys->file2chan("/chan", "pppctl");
+ if(iocmd == nil)
+ error(0, sys->sprint("can't create /chan/pppctl: %r"));
+ spawn servestatus(iocmd.read, logger);
+
+ starteduser := 0;
+ lasttime := 0;
+
+ for(;;) alt{
+ (nil, data, nil, wc) := <-iocmd.write => # remote io control
+ if(wc == nil)
+ break;
+ (nil, flds) := sys->tokenize(string data, " \t");
+ if(len flds > 1){
+ case hd flds {
+ "cancel" or "disconnect" or "hangup" =>
+ ; # ignore it
+ "connect" =>
+ # start connection ...
+ ;
+ * =>
+ wreply(wc, (0, "illegal request"));
+ continue;
+ }
+ }
+ wreply(wc, (len data, nil));
+
+ pkt := <-packets =>
+ sys->print("ppplink: received packet %s->%s: %d bytes\n", ipa(pkt.src), ipa(pkt.dst), len pkt.data);
+ if(abs(sys->millisec()-lasttime) < Ignorems){
+ sys->print("ppplink: ignored, not enough time elapsed yet between dial attempts\n");
+ break;
+ }
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if(ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat)){
+ cerr = configinit();
+ if(cerr != nil){
+ sys->print("ppplink: can't reconfigure: %s\n", cerr);
+ # use existing configuration
+ }
+ }
+ if(!starteduser){
+ sync := chan of int;
+ spawn userinterface(sync);
+ starteduser = <-sync;
+ }
+ (ppperr, pppdir) := makeconnection(packets, logger, iocmd.write);
+ lasttime = sys->millisec();
+ if(ppperr == nil){
+ sys->print("ppplink: connected on %s\n", pppdir);
+ # converse ...
+sys->sleep(120*1000);
+ }else{
+ sys->print("ppplink: ppp connect error: %s\n", ppperr);
+ hangup(pppdir);
+ }
+ }
+}
+
+servestatus(reader: chan of (int, int, int, Sys->Rread), updates: chan of (int, string))
+{
+ statuspending := 0;
+ statusreq: (int, int, Sys->Rread);
+ step := Disconnected;
+ statuslist := statusline(step, step, nil) :: nil;
+
+ for(;;) alt{
+ (off, nbytes, fid, rc) := <-reader=>
+ if(rc == nil){
+ statuspending = 0;
+ if(step == Disconnected)
+ statuslist = nil;
+ break;
+ }
+ if(statuslist == nil){
+ if(statuspending){
+ alt{
+ rc <-= (nil, "pppctl file already in use") => ;
+ * => ;
+ }
+ break;
+ }
+ statusreq = (nbytes, fid, rc);
+ statuspending = 1;
+ break;
+ }
+ alt{
+ rc <-= reads(hd statuslist, 0, nbytes) =>
+ statuslist = tl statuslist;
+ * => ;
+ }
+
+ (code, arg) := <-updates =>
+ # convert to string
+ if(code != Error)
+ step = code;
+ status := statusline(step, code, arg);
+ if(code == Error)
+ step = Disconnected;
+ statuslist = appends(statuslist, status);
+ sys->print("status: %d %d %s\n", step, code, status);
+ if(statuspending){
+ (nbytes, nil, rc) := statusreq;
+ statuspending = 0;
+ alt{
+ rc <-= reads(hd statuslist, 0, nbytes) =>
+ statuslist = tl statuslist;
+ * =>
+ ;
+ }
+ }
+ }
+}
+
+makeconnection(packets: chan of ref Packet, logger: chan of (int, string), writer: chan of (int, array of byte, int, Sys->Rwrite)): (string, string)
+{
+ result := chan of (string, string);
+ sync := chan of int;
+ spawn pppconnect(result, sync, logger);
+ pid := <-sync;
+ for(;;) alt{
+ (err, pppdir) := <-result =>
+ # pppconnect finished
+ return (err, pppdir);
+
+ pkt := <-packets =>
+ # ignore packets whilst connecting
+ sys->print("ppplink: ignored packet %s->%s: %d byten", ipa(pkt.src), ipa(pkt.dst), len pkt.data);
+
+ (nil, data, nil, wc) := <-writer => # user control
+ if(wc == nil)
+ break;
+ (nil, flds) := sys->tokenize(string data, " \t");
+ if(len flds > 1){
+ case hd flds {
+ "connect" =>
+ ; # ignore it
+ "cancel" or "disconnect" or "hangup"=>
+ kill(pid, "killgrp");
+ wreply(wc, (len data, nil));
+ return ("cancelled", nil);
+ * =>
+ wreply(wc, (0, "illegal request"));
+ continue;
+ }
+ }
+ wreply(wc, (len data, nil));
+ }
+}
+
+wreply(wc: chan of (int, string), v: (int, string))
+{
+ alt{
+ wc <-= v => ;
+ * => ;
+ }
+}
+
+appends(l: list of string, s: string): list of string
+{
+ if(l == nil)
+ return s :: nil;
+ return hd l :: appends(tl l, s);
+}
+
+statusline(step: int, code: int, arg: string): string
+{
+ s: string;
+ if(code >= 0 && code < len statustext){
+ n := "step";
+ if(code == Linkup)
+ n = "connect";
+ s = sys->sprint("%d %d %s %s", step, len statustext, n, X(statustext[code]));
+ }else
+ s = sys->sprint("%d %d error", step, len statustext);
+ if(arg != nil)
+ s += sys->sprint(": %s", arg);
+ return s;
+}
+
+getifc(): (ref Sys->FD, string, string)
+{
+ clonefile := netdir+"/ipifc/clone";
+ cfd := sys->open(clonefile, Sys->ORDWR);
+ if(cfd == nil)
+ return (nil, nil, sys->sprint("can't open %s: %r", clonefile));
+ buf := array[32] of byte;
+ n := sys->read(cfd, buf, len buf);
+ if(n <= 0)
+ return (nil, nil, sys->sprint("can't read %s: %r", clonefile));
+ return (cfd, netdir+"/ipifc/" + string buf[0:n], nil);
+}
+
+addroute(addr, mask, gate: string): int
+{
+ fd := sys->open(netdir+"/iproute", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ return sys->fprint(fd, "add %s %s %s", addr, mask, gate);
+}
+
+# uchar vihl; /* Version and header length */
+# uchar tos; /* Type of service */
+# uchar length[2]; /* packet length */
+# uchar id[2]; /* ip->identification */
+# uchar frag[2]; /* Fragment information */
+# uchar ttl; /* Time to live */
+# uchar proto; /* Protocol */
+# uchar cksum[2]; /* Header checksum */
+# uchar src[4]; /* IP source */
+# uchar dst[4]; /* IP destination */
+IPhdrlen: con 20;
+
+netreader(dfd: ref Sys->FD, dir: string, localip: string, fake: int, outc: chan of ref Packet)
+{
+ buf := array [32*1024] of byte;
+ while((n := sys->read(dfd, buf, len buf)) > 0){
+ if(n < IPhdrlen){
+ sys->print("ppplink: received short packet: %d bytes\n", n);
+ continue;
+ }
+ pkt := ref Packet;
+ if(n < 9*1024){
+ pkt.data = array[n] of byte;
+ pkt.data[0:] = buf[0:n];
+ }else{
+ pkt.data = buf[0:n];
+ buf = array[32*1024] of byte;
+ }
+ pkt.src = pkt.data[12:];
+ pkt.dst = pkt.data[16:];
+ outc <-= pkt;
+ }
+ if(n < 0)
+ error(1, sys->sprint("packet interface read error: %r"));
+ else if(n == 0)
+ error(1, "packet interface: end of file");
+}
+
+ipa(a: array of byte): string
+{
+ if(len a < 4)
+ return "???";
+ return sys->sprint("%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]);
+}
+
+reads(str: string, off, nbytes: int): (array of byte, string)
+{
+ bstr := array of byte str;
+ slen := len bstr;
+ if(off < 0 || off >= slen)
+ return (nil, nil);
+ if(off + nbytes > slen)
+ nbytes = slen - off;
+ if(nbytes <= 0)
+ return (nil, nil);
+ return (bstr[off:off+nbytes], nil);
+}
+
+readppplog(log: chan of (int, string), errfile: string, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ src := sys->open(errfile, Sys->OREAD);
+ if(src == nil)
+ log <-= (Error, sys->sprint("can't open %s: %r", errfile));
+
+ buf := array[1024] of byte;
+ connected := 0;
+ lasterror := "";
+
+ while((count := sys->read(src, buf, len buf)) > 0) {
+ (nil, tokens) := sys->tokenize(string buf[:count],"\n");
+ for(; tokens != nil; tokens = tl tokens) {
+ case hd tokens {
+ "no error" =>
+ log <-= (Linkup, nil);
+ lasterror = nil;
+ connected = 1;
+ "permission denied" =>
+ lasterror = X("Username or Password Incorrect");
+ log <-= (Error, lasterror);
+ "write to hungup channel" =>
+ lasterror = X("Remote Host Hung Up");
+ log <-= (Error, lasterror);
+ * =>
+ lasterror = X(hd tokens);
+ log <-= (Error, lasterror);
+ }
+ }
+ }
+ if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead?
+ #hangup(nil);
+ log <-= (Error, X("Lost Connection"));
+ }
+}
+
+dialup(mi: ref Modem->ModemInfo, number: string, scriptinfo: ref Script->ScriptInfo, logchan: chan of (int, string)): (string, ref Sys->Connection)
+{
+ logchan <-= (Modeminit, nil);
+
+ # open & init the modem
+
+ modeminfo = mi;
+ modem := load Modem Modem->PATH;
+ if(modem == nil)
+ return (sys->sprint("can't load %s: %r", Modem->PATH), nil);
+ err := modem->init();
+ if(err != nil)
+ return (sys->sprint("couldn't init modem: %s", err), nil);
+ Device: import modem;
+ d := Device.new(modeminfo, 1);
+ logchan <-= (Dialling, number);
+ err = d.dial(number);
+ if(err != nil){
+ d.close();
+ return (err, nil);
+ }
+ logchan <-= (Modemup, nil);
+
+ # login script
+
+ if(scriptinfo != nil) {
+ logchan <-= (Scriptstart, nil);
+ err = runscript(modem, d, scriptinfo);
+ if(err != nil){
+ d.close();
+ return (err, nil);
+ }
+ logchan <-= (Scriptdone, nil);
+ }
+
+ mc := d.close();
+ return (nil, mc);
+
+}
+
+startppp(logchan: chan of (int, string), pppinfo: ref PPPInfo): (string, string)
+{
+ (ifd, dir, err) := getifc();
+ if(ifd == nil)
+ return (err, nil);
+
+ sync := chan of int;
+ spawn readppplog(logchan, dir + "/err", sync); # unbind gives eof on err
+ <-sync;
+
+ if(pppinfo.ipaddr == nil)
+ pppinfo.ipaddr = "-";
+# if(pppinfo.ipmask == nil)
+# pppinfo.ipmask = "255.255.255.255";
+ if(pppinfo.peeraddr == nil)
+ pppinfo.peeraddr = "-";
+ if(pppinfo.maxmtu == nil)
+ pppinfo.maxmtu = "-";
+# if(pppinfo.maxmtu <= 0)
+# pppinfo.maxmtu = mtu;
+# if(pppinfo.maxmtu < 576)
+# pppinfo.maxmtu = 576;
+ if(pppinfo.username == nil)
+ pppinfo.username = "-";
+ if(pppinfo.password == nil)
+ pppinfo.password = "-";
+
+ ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu
+ +" "+string framing+" "+pppinfo.username+" "+pppinfo.password;
+
+ if(sys->fprint(ifd, "%s", ifc) < 0)
+ return (sys->sprint("can't bind ppp to %s: %r", dir), nil);
+
+ sys->print("ppplink: %s\n", ifc);
+
+ return (nil, dir);
+}
+
+runscript(modem: Modem, dev: ref Modem->Device, scriptinfo: ref Script->ScriptInfo): string
+{
+ script := load Script Script->PATH;
+ if(script == nil)
+ return sys->sprint("can't load %s: %r", Script->PATH);
+ err := script->init(modem);
+ if(err != nil)
+ return err;
+ return script->execute(dev, scriptinfo);
+}
+
+hangup(pppdir: string)
+{
+ sys->print("ppplink: hangup...\n");
+ if(pppdir != nil){ # shut down the PPP link
+ fd := sys->open(pppdir + "/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "unbind") < 0)
+ sys->print("ppplink: hangup: can't unbind ppp on %s: %r\n", pppdir);
+ fd = nil;
+ }
+ modem := load Modem Modem->PATH;
+ if(modem == nil) {
+ sys->print("ppplink: hangup: can't load %s: %r", Modem->PATH);
+ return;
+ }
+ err := modem->init();
+ if(err != nil){
+ sys->print("ppplink: hangup: couldn't init modem: %s", err);
+ return;
+ }
+ Device: import modem;
+ d := Device.new(modeminfo, 1);
+ if(d != nil){
+ d.onhook();
+ d.close();
+ }
+}
+
+kill(pid: int, msg: string)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", msg) < 0)
+ sys->print("pppclient: can't %s %d: %r\n", msg, pid);
+}
+
+error(dokill: int, s: string)
+{
+ sys->fprint(sys->fildes(2), "ppplink: %s\n", s);
+ if(dokill)
+ kill(sys->pctl(0, nil), "killgrp");
+ raise "fail:error";
+}
+
+X(s : string) : string
+{
+ if(dict != nil)
+ return dict.xlate(s);
+ return s;
+}
+
+cfile(file: string): string
+{
+ if(len file > 0 && file[0] == '/')
+ return file;
+ return "/usr/"+user()+"/config/"+file;
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ buf := array[64] of byte;
+ if(fd != nil && (n := sys->read(fd, buf, len buf)) > 0)
+ return string buf[0:n];
+ return "inferno"; # hmmm.
+}
+
+cfvalue(c: ref ConfigFile, key: string) :string
+{
+ s := "";
+ for(values := c.getcfg(key); values != nil; values = tl values){
+ if(s != "")
+ s[len s] = ' ';
+ s += hd values;
+ }
+ return s;
+}
+
+configinit(): string
+{
+ cfg = load CfgFile CfgFile->PATH;
+ if(cfg == nil)
+ return sys->sprint("can't load %s: %r", CfgFile->PATH);
+
+ # Modem Configuration
+
+ modemdb := cfile(MODEM_DB_PATH);
+ cfg->verify(DEFAULT_MODEM_DB_PATH, modemdb);
+ modemcfg := cfg->init(modemdb);
+ if(modemcfg == nil)
+ return sys->sprint("can't open %s: %r", modemdb);
+ modeminfo = ref Modem->ModemInfo;
+ modeminfo.path = cfvalue(modemcfg, "PATH");
+ modeminfo.init = cfvalue(modemcfg, "INIT");
+ modeminfo.country = cfvalue(modemcfg, "COUNTRY");
+ modeminfo.other = cfvalue(modemcfg, "OTHER");
+ modeminfo.errorcorrection = cfvalue(modemcfg,"CORRECT");
+ modeminfo.compression = cfvalue(modemcfg,"COMPRESS");
+ modeminfo.flowctl = cfvalue(modemcfg,"FLOWCTL");
+ modeminfo.rateadjust = cfvalue(modemcfg,"RATEADJ");
+ modeminfo.mnponly = cfvalue(modemcfg,"MNPONLY");
+ modeminfo.dialtype = cfvalue(modemcfg,"DIALING");
+ if(modeminfo.dialtype!="ATDP")
+ modeminfo.dialtype="ATDT";
+
+ ispdb := cfile(ISP_DB_PATH);
+ cfg->verify(DEFAULT_ISP_DB_PATH, ispdb);
+ sys->print("cfg->init(%s)\n", ispdb);
+
+ # ISP Configuration
+ pppcfg := cfg->init(ispdb);
+ if(pppcfg == nil)
+ return sys->sprint("can't read or create ISP configuration file %s: %r", ispdb);
+ (ok, stat) := sys->stat(ispdb);
+ if(ok >= 0)
+ lastCdir = ref stat;
+
+ pppinfo = ref PPPInfo;
+ isp_number = cfvalue(pppcfg, "NUMBER");
+ pppinfo.ipaddr = cfvalue(pppcfg,"IPADDR");
+ pppinfo.ipmask = cfvalue(pppcfg,"IPMASK");
+ pppinfo.peeraddr = cfvalue(pppcfg,"PEERADDR");
+ pppinfo.maxmtu = cfvalue(pppcfg,"MAXMTU");
+ pppinfo.username = cfvalue(pppcfg,"USERNAME");
+ pppinfo.password = cfvalue(pppcfg,"PASSWORD");
+
+ info := pppcfg.getcfg("SCRIPT");
+ if(info != nil) {
+ scriptinfo = ref Script->ScriptInfo;
+ scriptinfo.path = hd info;
+ scriptinfo.username = pppinfo.username;
+ scriptinfo.password = pppinfo.password;
+ } else
+ scriptinfo = nil;
+
+ info = pppcfg.getcfg("TIMEOUT");
+ if(info != nil)
+ scriptinfo.timeout = int (hd info);
+ cfg = nil; # unload it
+
+ if(modeminfo.path == nil)
+ return "no modem device configured";
+ if(isp_number == nil)
+ return "no telephone number configured for ISP";
+
+ return nil;
+}
+
+isipaddr(a: string): int
+{
+ i, c, ac, np : int = 0;
+
+ for(i = 0; i < len a; i++) {
+ c = a[i];
+ if(c >= '0' && c <= '9') {
+ np = 10*np + c - '0';
+ continue;
+ }
+ if(c == '.' && np) {
+ ac++;
+ if(np > 255)
+ return 0;
+ np = 0;
+ continue;
+ }
+ return 0;
+ }
+ return np && np < 256 && ac == 3;
+}
+
+userinterface(sync: chan of int)
+{
+ pppgui := load Command "pppchat.dis";
+ if(pppgui == nil){
+ sys->fprint(sys->fildes(2), "ppplink: can't load %s: %r\n", "/dis/svc/nppp/pppchat.dis");
+ # TO DO: should be optional
+ sync <-= 0;
+ }
+
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+ sync <-= sys->pctl(0, nil);
+ pppgui->init(context, "pppchat" :: nil);
+}
+
+pppconnect(result: chan of (string, string), sync: chan of int, status: chan of (int, string))
+{
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+ sync <-= sys->pctl(0, nil);
+ pppdir: string;
+ (err, mc) := dialup(modeminfo, isp_number, scriptinfo, status); # mc keeps connection open until startppp binds it to ppp
+ if(err == nil){
+ if(0 && (cfd := mc.cfd) != nil){
+ sys->fprint(cfd, "m1"); # cts/rts flow control/fifo's on
+ sys->fprint(cfd, "q64000"); # increase queue size to 64k
+ sys->fprint(cfd, "n1"); # nonblocking writes on
+ sys->fprint(cfd, "r1"); # rts on
+ sys->fprint(cfd, "d1"); # dtr on
+ }
+ status <-= (Startingppp, nil);
+ (err, pppdir) = startppp(status, pppinfo);
+ if(err == nil){
+ status <-= (Startedppp, nil);
+ result <-= (nil, pppdir);
+ return;
+ }
+ }
+ status <-= (Error, err);
+ result <-= (err, nil);
+}
+
+getspeed(file: string): string
+{
+ return findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil);
+}
+
+findrate(file: string, opt: list of string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array [1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 1)
+ return nil;
+ (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n");
+ for(; flds != nil; flds = tl flds)
+ for(l := opt; l != nil; l = tl l)
+ if(hd flds == hd l)
+ return hd tl flds;
+ return nil;
+}
+
+samefile(d1, d2: Sys->Dir): int
+{
+ return d1.dev==d2.dev && d1.dtype==d2.dtype &&
+ d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
+ d1.mtime==d2.mtime;
+}
+
+abs(n: int): int
+{
+ if(n < 0)
+ return -n;
+ return n;
+}
diff --git a/appl/cmd/ip/nppp/ppptest.b b/appl/cmd/ip/nppp/ppptest.b
new file mode 100644
index 00000000..af8e16e0
--- /dev/null
+++ b/appl/cmd/ip/nppp/ppptest.b
@@ -0,0 +1,90 @@
+# Last change: R 24 May 2001 11:05 am
+implement PPPTest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+include "pppgui.m";
+
+PPPTest: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+usage()
+{
+ sys->print("ppptest device modem_init tel user password \n");
+ sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n");
+ exit;
+
+}
+init( ctxt: ref Draw->Context, argv: list of string )
+{
+ sys = load Sys Sys->PATH;
+
+ mi: Modem->ModemInfo;
+ pi: PPPClient->PPPInfo;
+ tel : string;
+# si: Script->ScriptInfo;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.path = hd argv;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.init = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ tel = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ pi.username = hd argv;
+ argv = tl argv;
+ if(argv==nil)
+ usage();
+ else
+ pi.password = hd argv;
+
+
+ #si.path = "rdid.script";
+ #si.username = "ericvh";
+ #si.password = "foobar";
+ #si.timeout = 60;
+
+
+ ppp := load PPPClient PPPClient->PATH;
+
+ logger := chan of int;
+
+ spawn ppp->connect( ref mi, tel, nil, ref pi, logger );
+
+ pppgui := load PPPGUI PPPGUI->PATH;
+ (respchan, err) := pppgui->init(ctxt, logger, ppp, nil);
+ if(err != nil){
+ sys->print("ppptest: can't %s: %s\n", PPPGUI->PATH, err);
+ exit;
+ }
+
+ event := 0;
+ while(1) {
+ event =<- respchan;
+ sys->print("GUI event received: %d\n",event);
+ if(event) {
+ sys->print("success");
+ exit;
+ } else {
+ raise "fail: Couldn't connect to ISP";
+ }
+ }
+}
diff --git a/appl/cmd/ip/nppp/script.b b/appl/cmd/ip/nppp/script.b
new file mode 100644
index 00000000..d929ff7a
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.b
@@ -0,0 +1,171 @@
+implement Script;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "lock.m";
+include "modem.m";
+ modem: Modem;
+ Device: import modem;
+
+include "script.m";
+
+Scriptlim: con 32*1024; # should be enough for all
+
+init(mm: Modem): string
+{
+ sys = load Sys Sys->PATH;
+ modem = mm;
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load %s: %r", String->PATH);
+ return nil;
+}
+
+execute(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string
+{
+ if(scriptinfo.path != nil) {
+ if(m.trace)
+ sys->print("script: using %s\n",scriptinfo.path);
+ # load the script
+ err: string;
+ (scriptinfo.content, err) = scriptload(scriptinfo.path);
+ if(err != nil)
+ return err;
+ }else{
+ if(m.trace)
+ sys->print("script: using inline script\n");
+ }
+
+ if(scriptinfo.timeout == 0)
+ scriptinfo.timeout = 20;
+
+ tend := sys->millisec() + 1000*scriptinfo.timeout;
+
+ for(conv := scriptinfo.content; conv != nil; conv = tl conv){
+ e, s: string = nil;
+ p := hd conv;
+ if(len p == 0)
+ continue;
+ if(m.trace)
+ sys->print("script: %s\n",p);
+ if(p[0] == '-') { # just send
+ if(len p == 1)
+ continue;
+ s = p[1:];
+ } else {
+ (n, esl) := sys->tokenize(p, "-");
+ if(n > 0) {
+ e = hd esl;
+ esl = tl esl;
+ if(n > 1)
+ s = hd esl;
+ }
+ }
+ if(e != nil) {
+ if(match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) {
+ if(m.trace)
+ sys->print("script: match failed\n");
+ return "script failed";
+ }
+ }
+ if(s != nil)
+ m.send(special(s, scriptinfo));
+ }
+ if(m.trace)
+ sys->print("script: done\n");
+ return nil;
+}
+
+match(m: ref Modem->Device, s: string, msec: int): int
+{
+ for(;;) {
+ c := m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ head:
+ while(c == s[0]) {
+ i := 1;
+ while(i < len s) {
+ c = m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ if(c != s[i])
+ continue head;
+ i++;
+ }
+ return 1;
+ }
+ if(c == '~')
+ return 1; # assume PPP for now
+ }
+}
+
+#
+# Expand special script sequences
+#
+special(s: string, scriptinfo: ref ScriptInfo): string
+{
+ if(s == "$username") # special variable
+ s = scriptinfo.username;
+ else if(s == "$password")
+ s = scriptinfo.password;
+ return deparse(s);
+}
+
+deparse(s: string): string
+{
+ r: string = "";
+ for(i:=0; i < len s; i++) {
+ c := s[i];
+ if(c == '\\' && i+1 < len s) {
+ c = s[++i];
+ case c {
+ 't' => c = '\t';
+ 'n' => c = '\n';
+ 'r' => c = '\r';
+ 'b' => c = '\b';
+ 'a' => c = '\a';
+ 'v' => c = '\v';
+ '0' => c = '\0';
+ '$' => c = '$';
+ 'u' =>
+ if(i+4 < len s) {
+ i++;
+ (c, nil) = str->toint(s[i:i+4], 16);
+ i+=3;
+ }
+ }
+ }
+ r[len r] = c;
+ }
+ return r;
+}
+
+scriptload(path: string): (list of string, string)
+{
+ dfd := sys->open(path, Sys->OREAD);
+ if(dfd == nil)
+ return (nil, sys->sprint("can't open script %s: %r", path));
+
+ b := array[Scriptlim] of byte;
+ n := sys->read(dfd, b, len b);
+ if(n < 0)
+ return (nil, sys->sprint("can't read script %s: %r", path));
+
+ (nil, script) := sys->tokenize(string b[0:n], "\n");
+ return (script, nil);
+}
diff --git a/appl/cmd/ip/nppp/script.m b/appl/cmd/ip/nppp/script.m
new file mode 100644
index 00000000..a1f66e06
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.m
@@ -0,0 +1,15 @@
+Script: module
+{
+ PATH: con "/dis/ip/nppp/script.dis";
+
+ ScriptInfo: adt {
+ path: string;
+ content: list of string;
+ timeout: int;
+ username: string;
+ password: string;
+ };
+
+ init: fn(m: Modem): string;
+ execute: fn(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string;
+};
diff --git a/appl/cmd/ip/obootpd.b b/appl/cmd/ip/obootpd.b
new file mode 100644
index 00000000..8795d672
--- /dev/null
+++ b/appl/cmd/ip/obootpd.b
@@ -0,0 +1,777 @@
+implement Bootpd;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbentry: import attrdb;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+include "ether.m";
+ ether: Ether;
+
+include "arg.m";
+
+Bootpd: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+debug: int;
+sniff: int;
+verbose: int;
+
+siaddr: array of byte;
+sysname: string;
+progname := "bootpd";
+net := "/net";
+
+Udphdrsize: con IP->OUdphdrlen;
+
+NEED_HA: con 1;
+NEED_IP: con 0;
+NEED_BF: con 0;
+NEED_SM: con 0;
+NEED_GW: con 0;
+NEED_FS: con 0;
+NEED_AU: con 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ loadfail(Bufio->PATH);
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loadfail(Attrdb->PATH);
+ attrdb->init();
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ loadfail(IP->PATH);
+ ip->init();
+ ether = load Ether Ether->PATH;
+ if(ether == nil)
+ loadfail(Ether->PATH);
+ ether->init();
+
+ fname := "/services/bootp/db";
+ verbose = 1;
+ sniff = 0;
+ debug = 0;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ raise "fail: load Arg";
+ arg->init(args);
+ arg->setusage("bootpd [-dsqv] [-f file] [-x network]");
+ progname = arg->progname();
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 's' => sniff = 1; debug = 255;
+ 'q' => verbose = 0;
+ 'v' => verbose = 1;
+ 'x' => net = arg->earg();
+ 'f' => fname = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKFD|Sys->FORKNS, nil);
+ if(tabopen(fname))
+ raise "fail: open database";
+
+ if(!sniff && (err := dbread()) != nil)
+ error(sys->sprint("error in %s: %s", fname, err));
+
+ addr := net+"/udp!*!67";
+ if(debug)
+ sys->fprint(stderr, "bootp: announcing %s\n", addr);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ error(sys->sprint("can't announce %s: %r", addr));
+ get_sysname();
+ get_ip();
+
+ if(sys->fprint(c.cfd, "headers") < 0)
+ error(sys->sprint("can't set headers mode: %r"));
+ sys->fprint(c.cfd, "oldheaders");
+
+ if(debug)
+ sys->fprint(stderr, "bootp: opening %s/data\n", c.dir);
+ c.dfd = sys->open(c.dir+"/data", sys->ORDWR);
+ if(c.dfd == nil)
+ error(sys->sprint("can't open %s/data: %r", c.dir));
+
+ spawn server(c);
+}
+
+loadfail(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "bootp: %s\n", s);
+ raise "fail:error";
+}
+
+server(c: Sys->Connection)
+{
+ buf := array[2048] of byte;
+ badread := 0;
+ for(;;) {
+ if(debug)
+ sys->fprint(stderr, "bootp: listening for bootp requests...\n");
+ n := sys->read(c.dfd, buf, len buf);
+ if(n <0) {
+ if (badread++ > 10)
+ break;
+ continue;
+ }
+ badread = 0;
+ if(n < Udphdrsize) {
+ if(debug)
+ sys->fprint(stderr, "bootp: short Udphdr: %d bytes\n", n);
+ continue;
+ }
+ hdr := Udphdr.unpack(buf, Udphdrsize);
+ if(debug)
+ sys->fprint(stderr, "bootp: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport);
+ if(n < Udphdrsize+300) {
+ if(debug)
+ sys->fprint(stderr, "bootp: short request of %d bytes\n", n - Udphdrsize);
+ continue;
+ }
+
+ (err, bootp) := M2S(buf[Udphdrsize:]);
+ if(err != nil) {
+ if(debug)
+ sys->fprint(stderr, "bootp: M2S failed: %s\n", err);
+ continue;
+ }
+ if(debug >= 2)
+ ppkt(bootp);
+ if(sniff)
+ continue;
+ if(bootp.htype != byte 1 || bootp.hlen != byte 6) {
+ # if it isn't ether, we don't do it
+ if(debug)
+ sys->fprint(stderr, "bootp: hardware type not ether; ignoring.\n");
+ continue;
+ }
+ if((err = dbread()) != nil) {
+ sys->fprint(stderr, "bootp: getreply: dbread failed: %s\n", err);
+ continue;
+ }
+ rec := lookup(bootp);
+ if(rec == nil) {
+ # we can't answer this request
+ if(debug)
+ sys->fprint(stderr, "bootp: cannot answer request.\n");
+ continue;
+ }
+ if(debug){
+ sys->fprint(stderr, "bootp: found a matching entry:\n");
+ pinfbp(rec);
+ }
+ mkreply(bootp, rec);
+ if(verbose) sys->print("bootp: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, iptoa(rec.ip));
+ if(debug >= 2) {
+ sys->fprint(stderr, "bootp: reply message:\n");
+ ppkt(bootp);
+ }
+ repl:= S2M(bootp);
+
+ if(debug)
+ sys->fprint(stderr, "bootp: sending reply.\n");
+ arpenter(iptoa(rec.ip), ether->text(rec.ha));
+ send(repl);
+ }
+ sys->fprint(stderr, "bootp: %d read errors: %r\n", badread);
+}
+
+arpenter(ip, ha: string)
+{
+ if(debug) sys->fprint(stderr, "bootp: arp: %s -> %s\n", ip, ha);
+ fd := sys->open(net+"/arp", Sys->OWRITE);
+ if(fd == nil) {
+ if(debug)
+ sys->fprint(stderr, "bootp: arp open failed: %r\n");
+ return;
+ }
+ if(sys->fprint(fd, "add %s %s", ip, ha) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootp: error writing arp: %r\n");
+ }
+}
+
+get_sysname()
+{
+ fd := sys->open("/dev/sysname", sys->OREAD);
+ if(fd == nil) {
+ sysname = "anon";
+ return;
+ }
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0) {
+ sysname = "anon";
+ return;
+ }
+ sysname = string buf[0:n];
+}
+
+get_ip()
+{
+ siaddr = array[4] of { * => byte 0 };
+ # get a local IP address by translating our sysname with cs(8)
+ fd := sys->open(net+"/cs", Sys->ORDWR);
+ if(fd == nil){
+ if(debug)
+ sys->fprint(stderr, "bootp: cannot open %s/cs for reading: %r.\n", net);
+ return;
+ }
+ if(sys->fprint(fd, "net!%s!0", sysname) < 0){
+ if(debug)
+ sys->fprint(stderr, "bootp: can't translate net!%s!0 via %s/cs: %r\n", sysname, net);
+ return;
+ }
+ sys->seek(fd, big 0, 0);
+ a := array[1024] of byte;
+ n := sys->read(fd, a, len a);
+ if(n < 0) {
+ if(debug) sys->fprint(stderr, "bootp: read from /net/cs: %r.\n");
+ return;
+ }
+ reply := string a[0:n];
+ if(debug) sys->fprint(stderr, "bootp: read %s from /net/cs\n", reply);
+
+ (l, addr):= sys->tokenize(reply, " ");
+ if(l != 2) {
+ if(debug) sys->fprint(stderr, "bootp: bad format from cs\n");
+ return;
+ }
+ (l, addr) = sys->tokenize(hd tl addr, "!");
+ if(l < 2) {
+ if(debug) sys->fprint(stderr, "bootp: short addr from cs\n");
+ return;
+ }
+ err:= "";
+ (err, siaddr) = get_ipaddr(hd addr);
+ if(err != nil || siaddr == nil) {
+ if(debug) sys->fprint(stderr, "bootp: invalid local IP addr %s.\n", hd tl addr);
+ siaddr = array[4] of { * => byte 0 };
+ };
+ if(debug) sys->fprint(stderr, "bootp: local IP address is %s.\n", iptoa(siaddr));
+}
+
+# byte op; /* opcode */
+# byte htype; /* hardware type */
+# byte hlen; /* hardware address len */
+# byte hops; /* hops */
+# byte xid[4]; /* a random number */
+# byte secs[2]; /* elapsed snce client started booting */
+# byte pad[2];
+# byte ciaddr[4]; /* client IP address (client tells server) */
+# byte yiaddr[4]; /* client IP address (server tells client) */
+# byte siaddr[4]; /* server IP address */
+# byte giaddr[4]; /* gateway IP address */
+# byte chaddr[16]; /* client hardware address */
+# byte sname[64]; /* server host name (optional) */
+# byte file[128]; /* boot file name */
+# byte vend[128]; /* vendor-specific goo */
+
+BootpPKT: adt
+{
+ op: byte; # Start of udp datagram
+ htype: byte;
+ hlen: byte;
+ hops: byte;
+ xid: int;
+ secs: int;
+ ciaddr: array of byte;
+ yiaddr: array of byte;
+ siaddr: array of byte;
+ giaddr: array of byte;
+ chaddr: array of byte;
+ sname: string;
+ file: string;
+ vend: array of byte;
+};
+
+InfBP: adt {
+ hostname: string;
+
+ ha: array of byte; # hardware addr
+ ip: array of byte; # client IP addr
+ bf: array of byte; # boot file path
+ sm: array of byte; # subnet mask
+ gw: array of byte; # gateway IP addr
+ fs: array of byte; # file server IP addr
+ au: array of byte; # authentication server IP addr
+};
+
+records: array of ref InfBP;
+
+tabbio: ref Bufio->Iobuf;
+tabname: string;
+mtime: int;
+
+tabopen(fname: string): int
+{
+ if(sniff) return 0;
+ tabname = fname;
+ if((tabbio = bufio->open(tabname, bufio->OREAD)) == nil) {
+ sys->fprint(stderr, "bootp: cannot open %s: %r\n", tabname);
+ return 1;
+ }
+ return 0;
+}
+
+send(msg: array of byte)
+{
+ if(debug) sys->fprint(stderr, "bootp: dialing udp!broadcast!68\n");
+ (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "67");
+# (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "192.168.129.1!67");
+ if(n < 0) {
+ sys->fprint(stderr, "bootp: send: error calling dial: %r\n");
+ return;
+ }
+ if(debug) sys->fprint(stderr, "bootp: writing to %s/data\n", c.dir);
+ n = sys->write(c.dfd, msg, len msg);
+ if(n <=0) {
+ sys->fprint(stderr, "bootp: send: error writing to %s/data: %r\n", c.dir);
+ return;
+ }
+ if(debug) sys->fprint(stderr, "bootp: successfully wrote %d bytes to %s/data\n", n, c.dir);
+}
+
+mkreply(bootp: ref BootpPKT, rec: ref InfBP)
+{
+ bootp.op = byte 2; # boot reply
+ bootp.yiaddr = rec.ip;
+ bootp.siaddr = siaddr;
+ bootp.giaddr = array[4] of { * => byte 0 };
+ bootp.sname = sysname;
+ bootp.file = string rec.bf;
+ bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", iptoa(rec.sm), iptoa(rec.fs), iptoa(rec.au), iptoa(rec.gw));
+}
+
+lookup(bootp: ref BootpPKT): ref InfBP
+{
+ for(i := 0; i < len records; i++)
+ if(eqa(bootp.chaddr[0:6], records[i].ha) || eqa(bootp.ciaddr, records[i].ip))
+ return records[i];
+ return nil;
+}
+
+dbread(): string
+{
+ (n, dir) := sys->fstat(tabbio.fd);
+ if(n < 0)
+ return sys->sprint("cannot fstat %s: %r", tabname);
+ if(mtime == 0 || mtime != dir.mtime) {
+ if(bufio->tabbio.seek(big 0, Sys->SEEKSTART) < big 0)
+ return sys->sprint("error seeking to start of %s.", tabname);
+ mtime = dir.mtime;
+ lnum: int = 0;
+ trecs: list of ref InfBP;
+LINES: while((line := bufio->tabbio.gets('\n')) != nil) {
+ lnum++;
+ if(line[0] == '#') # comment
+ continue LINES;
+ fields: list of string;
+ (n, fields) = sys->tokenize(line, ":\r\n");
+ if(n <= 0) { # blank line or colons
+ if(len line > 0) {
+ sys->fprint(stderr, "bootp: %s: %d empty entry.\n", tabname, lnum);
+ }
+ continue LINES;
+ }
+ rec := ref InfBP;
+ rec.hostname = hd fields;
+ fields = tl fields;
+ err: string;
+FIELDS: for(; fields != nil; fields = tl fields) {
+ field := hd fields;
+ if(len field <= len "xx=") {
+ sys->fprint(stderr, "bootp: %s:%d invalid field \"%s\" in entry for %s",
+ tabname, lnum, field, rec.hostname);
+ continue FIELDS;
+ }
+ err = nil;
+ case field[0:3] {
+ "ha=" =>
+ if(rec.ha != nil) {
+ sys->fprint(stderr,
+ "bootp: warning: %s:%d hardware address redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.ha) = get_haddr(field[3:]);
+ "ip=" =>
+ if(rec.ip != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d IP address redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.ip) = get_ipaddr(field[3:]);
+ "bf=" =>
+ if(rec.bf != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d bootfile redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.bf) = get_path(field[3:]);
+ "sm=" =>
+ if(rec.sm != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d subnet mask redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.sm) = get_ipaddr(field[3:]);
+ "gw=" =>
+ if(rec.gw != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d gateway redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.gw) = get_ipaddr(field[3:]);
+ "fs=" =>
+ if(rec.fs != nil) {
+ sys->fprint(stderr, "bootp: warning: %s:%d file server redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.fs) = get_ipaddr(field[3:]);
+ "au=" =>
+ if(rec.au != nil) {
+ sys->fprint(stderr,
+ "bootp: warning: %s:%d authentication server redefined for %s.\n",
+ tabname, lnum, rec.hostname);
+ }
+ (err, rec.au) = get_ipaddr(field[3:]);
+ * =>
+ sys->fprint(stderr,
+ "bootp: %s:%d invalid or unsupported tag \"%s\" in entry for %s.\n",
+ tabname, lnum, field[0:2], rec.hostname);
+ continue FIELDS;
+ }
+ if(err != nil) {
+ sys->fprint(stderr,
+ "bootp: %s:%d %s for %s.\nbootp: skipping entry for %s.\n",
+ tabname, lnum, err, rec.hostname,
+ rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.ha == nil) {
+ if(NEED_HA) {
+ sys->fprint(stderr, "bootp: %s:%d no hardware address defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.ip == nil) {
+ if(NEED_IP) {
+ sys->fprint(stderr, "bootp: %s:%d no IP address defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.bf == nil) {
+ if(NEED_BF) {
+ sys->fprint(stderr, "bootp: %s:%d no bootfile defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.sm == nil) {
+ if(NEED_SM) {
+ sys->fprint(stderr, "bootp: %s:%d no subnet mask defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.gw == nil) {
+ if(NEED_GW) {
+ sys->fprint(stderr, "bootp: %s:%d no gateway defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.fs == nil) {
+ if(NEED_FS) {
+ sys->fprint(stderr, "bootp: %s:%d no file server defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(rec.au == nil) {
+ if(NEED_AU) {
+ sys->fprint(stderr,
+ "bootp: %s:%d no authentication server defined for %s.\n",
+ tabname, lnum, rec.hostname);
+ sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname);
+ continue LINES;
+ }
+ }
+ if(debug) pinfbp(rec);
+ trecs = rec :: trecs;
+ }
+ if(trecs == nil) {
+ sys->fprint(stderr, "bootp: no valid entries in %s.\n", tabname);
+ if(records != nil) {
+ sys->fprint(stderr, "bootp: reverting to previous state.\n");
+ return nil;
+ }
+ return "no entries.";
+ }
+ records = array[len trecs] of ref InfBP;
+ for(n = len records; n > 0; trecs = tl trecs)
+ records[--n] = hd trecs;
+ }
+ return nil;
+}
+
+get_haddr(str: string): (string, array of byte)
+{
+ addr := ether->parse(str);
+ if(addr == nil)
+ return (sys->sprint("invalid hardware address \"%s\"", str), nil);
+ return (nil, addr);
+}
+
+get_ipaddr(str: string): (string, array of byte)
+{
+ (ok, a) := IPaddr.parse(str);
+ if(ok < 0)
+ return (sys->sprint("invalid address: %s", str), nil);
+ return (nil, a.v4());
+}
+
+get_path(str: string): (string, array of byte)
+{
+ if(str == nil) {
+ return ("nil path", nil);
+ }
+ path := array of byte str;
+ if(len path > 128)
+ return (sys->sprint("path too long (>128 bytes) \"%s...\"", string path[0:16]), nil);
+ return (nil, path);
+}
+
+iptoa(addr: array of byte): string
+{
+ if(len addr != 4)
+ return "0.0.0.0";
+ return sys->sprint("%d.%d.%d.%d",
+ int addr[0],
+ int addr[1],
+ int addr[2],
+ int addr[3]);
+}
+
+dtoa(data: array of byte): string
+{
+ if(data == nil)
+ return nil;
+ result: string;
+ for(i:=0; i < len data; i++)
+ result += sys->sprint(".%d", int data[i]);
+ return result[1:];
+}
+
+bptohw(bp: ref BootpPKT): string
+{
+ l := int bp.hlen;
+ if(l > 0 && l < len bp.chaddr)
+ return ether->text(bp.chaddr[0:l]);
+ return "";
+}
+
+ctostr(cstr: array of byte): string
+{
+ for(i:=0; i<len cstr; i++)
+ if(cstr[i] == byte 0)
+ break;
+ return string cstr[0:i];
+}
+
+strtoc(s: string): array of byte
+{
+ as := array of byte s;
+ cs := array[1 + len as] of byte;
+ cs[0:] = as;
+ cs[len cs - 1] = byte 0;
+ return cs;
+}
+
+ppkt(bootp: ref BootpPKT)
+{
+ sys->fprint(stderr, "BootpPKT {\n");
+ sys->fprint(stderr, "\top == %d\n", int bootp.op);
+ sys->fprint(stderr, "\thtype == %d\n", int bootp.htype);
+ sys->fprint(stderr, "\thlen == %d\n", int bootp.hlen);
+ sys->fprint(stderr, "\thops == %d\n", int bootp.hops);
+ sys->fprint(stderr, "\txid == %d\n", bootp.xid);
+ sys->fprint(stderr, "\tsecs == %d\n", bootp.secs);
+ sys->fprint(stderr, "\tC client == %s\n", dtoa(bootp.ciaddr));
+ sys->fprint(stderr, "\tY client == %s\n", dtoa(bootp.yiaddr));
+ sys->fprint(stderr, "\tserver == %s\n", dtoa(bootp.siaddr));
+ sys->fprint(stderr, "\tgateway == %s\n", dtoa(bootp.giaddr));
+ sys->fprint(stderr, "\thwaddr == %s\n", bptohw(bootp));
+ sys->fprint(stderr, "\thost == %s\n", bootp.sname);
+ sys->fprint(stderr, "\tfile == %s\n", bootp.file);
+ sys->fprint(stderr, "\tmagic == %s\n", magic(bootp.vend[0:4]));
+ if(magic(bootp.vend[0:4]) == "plan9") {
+ (n, strs) := sys->tokenize(string bootp.vend[4:], " \r\n");
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tsm == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tfs == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tau == %s\n", hd strs);
+ strs = tl strs;
+ }
+ if(strs != nil) {
+ sys->fprint(stderr, "\t\tgw == %s\n", hd strs);
+ strs = tl strs;
+ }
+ }
+ sys->fprint(stderr, "}\n\n");
+}
+
+eqa(a1: array of byte, a2: array of byte): int
+{
+ if(len a1 != len a2)
+ return 0;
+ for(i := 0; i < len a1; i++)
+ if(a1[i] != a2[i])
+ return 0;
+ return 1;
+}
+
+magic(cookie: array of byte): string
+{
+ if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' }))
+ return "plan9";
+ if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 }))
+ return "rfc1048";
+ if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 }))
+ return "cmu";
+ return dtoa(cookie);
+}
+
+pinfbp(rec: ref InfBP)
+{
+ sys->fprint(stderr, "Bootp entry {\n");
+ sys->fprint(stderr, "\tha == %s\n", ether->text(rec.ha));
+ sys->fprint(stderr, "\tip == %s\n", dtoa(rec.ip));
+ sys->fprint(stderr, "\tbf == %s\n", string rec.bf);
+ sys->fprint(stderr, "\tsm == %s\n", dtoa(rec.sm));
+ sys->fprint(stderr, "\tgw == %s\n", dtoa(rec.gw));
+ sys->fprint(stderr, "\tfs == %s\n", dtoa(rec.fs));
+ sys->fprint(stderr, "\tau == %s\n", dtoa(rec.au));
+ sys->fprint(stderr, "}\n");
+}
+
+M2S(data: array of byte): (string, ref BootpPKT)
+{
+ if(len data < 300)
+ return ("too short", nil);
+
+ bootp := ref BootpPKT;
+
+ bootp.op = data[0];
+ bootp.htype = data[1];
+ bootp.hlen = data[2];
+ bootp.hops = data[3];
+ bootp.xid = nhgetl(data[4:8]);
+ bootp.secs = nhgets(data[8:10]);
+ # data[10:12] unused
+ bootp.ciaddr = data[12:16];
+ bootp.yiaddr = data[16:20];
+ bootp.siaddr = data[20:24];
+ bootp.giaddr = data[24:28];
+ bootp.chaddr = data[28:44];
+ bootp.sname = ctostr(data[44:108]);
+ bootp.file = ctostr(data[108:236]);
+ bootp.vend = data[236:300];
+
+ return (nil, bootp);
+}
+
+S2M(bootp: ref BootpPKT): array of byte
+{
+ data := array[364] of { * => byte 0 };
+
+ data[0] = bootp.op;
+ data[1] = bootp.htype;
+ data[2] = bootp.hlen;
+ data[3] = bootp.hops;
+ data[4:] = nhputl(bootp.xid);
+ data[8:] = nhputs(bootp.secs);
+ # data[10:12] unused
+ data[12:] = bootp.ciaddr;
+ data[16:] = bootp.yiaddr;
+ data[20:] = bootp.siaddr;
+ data[24:] = bootp.giaddr;
+ data[28:] = bootp.chaddr;
+ data[44:] = array of byte bootp.sname;
+ data[108:] = array of byte bootp.file;
+ data[236:] = bootp.vend;
+
+ return data;
+}
+
+nhgetl(data: array of byte): int
+{
+ return (int data[0]<<24) | (int data[1]<<16) |
+ (int data[2]<<8) | int data[3];
+}
+
+nhgets(data: array of byte): int
+{
+ return (int data[0]<<8) | int data[1];
+}
+
+nhputl(value: int): array of byte
+{
+ return array[] of {
+ byte (value >> 24),
+ byte (value >> 16),
+ byte (value >> 8),
+ byte (value >> 0),
+ };
+}
+
+nhputs(value: int): array of byte
+{
+ return array[] of {
+ byte (value >> 8),
+ byte (value >> 0),
+ };
+}
+
diff --git a/appl/cmd/ip/ping.b b/appl/cmd/ip/ping.b
new file mode 100644
index 00000000..a148c1e6
--- /dev/null
+++ b/appl/cmd/ip/ping.b
@@ -0,0 +1,369 @@
+implement Ping;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "rand.m";
+ rand: Rand;
+
+include "arg.m";
+
+Ping: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Icmp: adt
+{
+ ttl: int; # time to live
+ src: IPaddr;
+ dst: IPaddr;
+ ptype: int;
+ code: int;
+ seq: int;
+ munged: int;
+ time: big;
+
+ unpack: fn(b: array of byte): ref Icmp;
+};
+
+# packet types
+EchoReply: con 0;
+Unreachable: con 3;
+SrcQuench: con 4;
+EchoRequest: con 8;
+TimeExceed: con 11;
+Timestamp: con 13;
+TimestampReply: con 14;
+InfoRequest: con 15;
+InfoReply: con 16;
+
+Nmsg: con 32;
+Interval: con 1000; # ms
+
+Req: adt
+{
+ seq: int; # sequence number
+ time: big; # time sent
+ rtt: big;
+ ttl: int;
+ replied: int;
+};
+
+debug := 0;
+quiet := 0;
+lostonly := 0;
+lostmsgs := 0;
+rcvdmsgs := 0;
+sum := big 0;
+firstseq := 0;
+addresses := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ rand = load Rand Rand->PATH;
+ timers = load Timers Timers->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+
+ msglen := interval := 0;
+ nmsg := Nmsg;
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("ip/ping [-alq] [-s msgsize] [-i millisecs] [-n #pings] destination");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'l' =>
+ lostonly++;
+ 'd' =>
+ debug++;
+ 's' =>
+ msglen = int arg->earg();
+ 'i' =>
+ interval = int arg->earg();
+ 'n' =>
+ nmsg = int arg->earg();
+ 'a' =>
+ addresses = 1;
+ 'q' =>
+ quiet = 1;
+ }
+ if(msglen < 32)
+ msglen = 64;
+ if(msglen >= 65*1024)
+ msglen = 65*1024-1;
+ if(interval <= 0)
+ interval = Interval;
+
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ opentime();
+ rand->init(int(nsec()/big 1000));
+
+ addr := netmkaddr(hd args, "icmp", "1");
+ (ok, c) := sys->dial(addr, nil);
+ if(ok < 0){
+ sys->fprint(sys->fildes(2), "ip/ping: can't dial %s: %r\n", addr);
+ raise "fail:dial";
+ }
+
+ sys->print("sending %d %d byte messages %d ms apart\n", nmsg, msglen, interval);
+
+ done := chan of int;
+ reqs := chan of ref Req;
+
+ spawn sender(c.dfd, msglen, interval, nmsg, done, reqs);
+ spid := <-done;
+
+ pids := chan of int;
+ replies := chan [8] of ref Icmp;
+ spawn reader(c.dfd, msglen, replies, pids);
+ rpid := <-pids;
+
+ tpid := 0;
+ timeout := chan of int;
+ requests: list of ref Req;
+Work:
+ for(;;) alt{
+ r := <-reqs =>
+ requests = r :: requests;
+ ic := <-replies =>
+ if(ic == nil){
+ rpid = 0;
+ break Work;
+ }
+ if(ic.munged)
+ sys->print("corrupted reply\n");
+ if(ic.ptype != EchoReply || ic.code != 0){
+ sys->print("bad type/code %d/%d seq %d\n",
+ ic.ptype, ic.code, ic.seq);
+ continue;
+ }
+ requests = clean(requests, ic);
+ if(lostmsgs+rcvdmsgs == nmsg)
+ break Work;
+ <-done =>
+ spid = 0;
+ # must be at least one message outstanding; wait for it
+ tpid = timers->init(Timers->Sec);
+ timeout = Timer.start((nmsg-lostmsgs-rcvdmsgs)*interval+5*Timers->Sec).timeout;
+ <-timeout =>
+ break Work;
+ }
+ kill(rpid);
+ kill(spid);
+ kill(tpid);
+
+ for(; requests != nil; requests = tl requests)
+ if((hd requests).replied == 0)
+ lostmsgs++;
+
+ if(lostmsgs){
+ sys->print("%d out of %d message(s) lost\n", lostmsgs, lostmsgs+rcvdmsgs);
+ raise "fail:lost messages";
+ }
+}
+
+kill(pid: int)
+{
+ if(pid)
+ sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
+
+SECOND: con big 1000000000; # nanoseconds
+MINUTE: con big 60*SECOND;
+
+clean(l: list of ref Req, ip: ref Icmp): list of ref Req
+{
+ left: list of ref Req;
+ for(; l != nil; l = tl l){
+ r := hd l;
+ if(ip.seq == r.seq){
+ r.rtt = ip.time-r.time;
+ r.ttl = ip.ttl;
+ reply(r, ip);
+ }
+ if(ip.time-r.time > MINUTE){
+ r.rtt = ip.time-r.time;
+ r.ttl = ip.ttl;
+ if(!r.replied)
+ lost(r, ip);
+ }else
+ left = r :: left;
+ }
+ return left;
+}
+
+sender(fd: ref Sys->FD, msglen: int, interval: int, n: int, done: chan of int, reqs: chan of ref Req)
+{
+
+ done <-= sys->pctl(0, nil);
+
+ firstseq = rand->rand(65536) - n; # -n to ensure we don't exceed 16 bits
+ if(firstseq < 0)
+ firstseq = 0;
+
+ buf := array[64*1024+512] of {* => byte 0};
+ for(i := Odata; i < msglen; i++)
+ buf[i] = byte i;
+ buf[Otype] = byte EchoRequest;
+ buf[Ocode] = byte 0;
+
+ seq := firstseq;
+ for(i = 0; i < n; i++){
+ if(i != 0)
+ sys->sleep(interval);
+ ip->put2(buf, Oseq, seq); # order?
+ r := ref Req;
+ r.seq = seq;
+ r.replied = 0;
+ r.time = nsec();
+ reqs <-= r;
+ if(sys->write(fd, buf, msglen) < msglen){
+ sys->fprint(sys->fildes(2), "ping: write failed: %r\n");
+ break;
+ }
+ seq++;
+ }
+ done <-= 1;
+}
+
+reader(fd: ref Sys->FD, msglen: int, out: chan of ref Icmp, pid: chan of int)
+{
+ pid <-= sys->pctl(0, nil);
+ buf := array[64*1024+512] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ now := nsec();
+ if(n < msglen){
+ sys->print("bad len %d/%d\n", n, msglen);
+ continue;
+ }
+ ic := Icmp.unpack(buf[0:n]);
+ ic.munged = 0;
+ for(i := Odata; i < msglen; i++)
+ if(buf[i] != byte i)
+ ic.munged++;
+ ic.time = now;
+ out <-= ic;
+ }
+ sys->print("read: %r\n");
+ out <-= nil;
+}
+
+reply(r: ref Req, ic: ref Icmp)
+{
+ rcvdmsgs++;
+ r.rtt /= big 1000;
+ sum += r.rtt;
+ if(!quiet && !lostonly){
+ if(addresses)
+ sys->print("%ud: %s->%s rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
+ r.seq-firstseq,
+ ic.src.text(), ic.dst.text(),
+ r.rtt, sum/big rcvdmsgs, r.ttl);
+ else
+ sys->print("%ud: rtt %bd µs, avg rtt %bd µs, ttl = %d\n",
+ r.seq-firstseq,
+ r.rtt, sum/big rcvdmsgs, r.ttl);
+ }
+ r.replied = 1; # TO DO: duplicates might be interesting
+}
+
+lost(r: ref Req, ic: ref Icmp)
+{
+ if(!quiet){
+ if(addresses)
+ sys->print("lost %ud: %s->%s avg rtt %bd µs\n",
+ r.seq-firstseq,
+ ic.src.text(), ic.dst.text(),
+ sum/big rcvdmsgs);
+ else
+ sys->print("lost %ud: avg rtt %bd µs\n",
+ r.seq-firstseq,
+ sum/big rcvdmsgs);
+ }
+ lostmsgs++;
+}
+
+Ovihl: con 0;
+Otos: con 1;
+Olength: con 2;
+Oid: con Olength+2;
+Ofrag: con Oid+2;
+Ottl: con Ofrag+2;
+Oproto: con Ottl+1;
+Oipcksum: con Oproto+1;
+Osrc: con Oipcksum+2;
+Odst: con Osrc+4;
+Otype: con Odst+4;
+Ocode: con Otype+1;
+Ocksum: con Ocode+1;
+Oicmpid: con Ocksum+2;
+Oseq: con Oicmpid+2;
+Odata: con Oseq+2;
+
+Icmp.unpack(b: array of byte): ref Icmp
+{
+ ic := ref Icmp;
+ ic.ttl = int b[Ottl];
+ ic.src = IPaddr.newv4(b[Osrc:]);
+ ic.dst = IPaddr.newv4(b[Odst:]);
+ ic.ptype = int b[Otype];
+ ic.code = int b[Ocode];
+ ic.seq = ip->get2(b, Oseq);
+ ic.munged = 0;
+ ic.time = big 0;
+ return ic;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
+
+timefd: ref Sys->FD;
+
+opentime()
+{
+ timefd = sys->open("/dev/time", Sys->OREAD);
+ if(timefd == nil){
+ sys->fprint(sys->fildes(2), "ping: can't open /dev/time: %r\n");
+ raise "fail:no time";
+ }
+}
+
+nsec(): big
+{
+ buf := array[64] of byte;
+ n := sys->pread(timefd, buf, len buf, big 0);
+ if(n <= 0)
+ return big 0;
+ return big string buf[0:n] * big 1000;
+}
diff --git a/appl/cmd/ip/ppp/mkfile b/appl/cmd/ip/ppp/mkfile
new file mode 100644
index 00000000..193b8faf
--- /dev/null
+++ b/appl/cmd/ip/ppp/mkfile
@@ -0,0 +1,27 @@
+<../../../../mkconfig
+
+TARG=\
+ pppclient.dis\
+ pppdial.dis\
+ pppgui.dis\
+ ppptest.dis\
+ modem.dis\
+ script.dis\
+
+MODULES=\
+ modem.m\
+ pppclient.m\
+ pppgui.m\
+ script.m\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ tk.m\
+ dict.m\
+ string.m\
+ lock.m\
+
+DISBIN=$ROOT/dis/ip/ppp
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ip/ppp/modem.b b/appl/cmd/ip/ppp/modem.b
new file mode 100644
index 00000000..6085524a
--- /dev/null
+++ b/appl/cmd/ip/ppp/modem.b
@@ -0,0 +1,468 @@
+implement Modem;
+
+include "sys.m";
+ sys: Sys;
+
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+
+include "draw.m";
+
+include "modem.m";
+
+hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ (BUG: should be in modeminfo)
+
+# modem return codes
+Ok, Success, Failure, Abort, Noise, Found: con iota;
+
+maxspeed: con 115200;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", Ok),
+ ("NO CARRIER", Failure),
+ ("ERROR", Failure),
+ ("NO DIALTONE", Failure),
+ ("BUSY", Failure),
+ ("NO ANSWER", Failure),
+ ("CONNECT", Success),
+};
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0)
+ sys->print("modem: can't kill %d: %r\n", pid);
+}
+
+#
+# prepare a modem port
+#
+openserial(d: ref Device)
+{
+ if (d==nil) {
+ raise "fail: device not initialized";
+ return;
+ }
+
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil) {
+ raise "fail: can't open "+d.local;
+ return;
+ }
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil) {
+ raise "can't open "+d.local+"ctl";
+ return;
+ }
+
+ d.speed = maxspeed;
+ d.avail = nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+close(m: ref Device): ref Sys->Connection
+{
+ if(m == nil)
+ return nil;
+ if(m.pid != 0){
+ kill(m.pid);
+ m.pid = 0;
+ }
+ if(m.data == nil)
+ return nil;
+ mc := ref sys->Connection(m.data, m.ctl, nil);
+ m.ctl = nil;
+ m.data = nil;
+ return mc;
+}
+
+#
+# Send a string to the modem
+#
+
+send(d: ref Device, x: string): int
+{
+ if (d == nil)
+ return -1;
+
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if (f < 0) {
+ # let's attempt to close & reopen the modem
+ close(d);
+ openserial(d);
+ f = sys->write(d.data,a, len a);
+ }
+ sys->print("->%s\n",x);
+ return f;
+}
+
+#
+# apply a string of commands to modem & look for a response
+#
+
+apply(d: ref Device, s: string, substr: string, secs: int): int
+{
+ m := Ok;
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(send(d, buf) < 0)
+ return Abort;
+ (m, nil) = readmsg(d, secs, substr);
+ buf = "";
+ }
+ }
+ return m;
+}
+
+#
+# get modem into command mode if it isn't already
+#
+GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz
+
+attention(d: ref Device): int
+{
+ for(i := 0; i < 3; i++){
+ if(apply(d, hangupcmd, nil, 2) == Ok)
+ return Ok;
+ sys->sleep(GUARDTIME);
+ if(send(d, "+++") < 0)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil)
+ sys->print("status: %s\n", msg);
+ }
+ return Failure;
+}
+
+#
+# apply a command type
+#
+
+applyspecial(d: ref Device, cmd: string): int
+{
+ if(cmd == nil)
+ return Failure;
+ return apply(d, cmd, nil, 2);
+}
+
+#
+# hang up any connections in progress and close the device
+#
+onhook(d: ref Device)
+{
+ if(d == nil)
+ return;
+
+ # hang up the modem
+ monitoring(d);
+ if(attention(d) != Ok)
+ sys->print("modem: no attention\n");
+
+ # hangup the stream (eg, for ppp) and toggle the lines to the modem
+ if(d.ctl != nil) {
+ sys->fprint(d.ctl,"d0\n");
+ sys->fprint(d.ctl,"r0\n");
+ sys->fprint(d.ctl, "h\n"); # hangup on native serial
+ sys->sleep(250);
+ sys->fprint(d.ctl,"r1\n");
+ sys->fprint(d.ctl,"d1\n");
+ }
+
+ close(d);
+}
+
+#
+# does string s contain t anywhere?
+#
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+#
+# read till we see a message or we time out
+#
+readmsg(d: ref Device, secs: int, substr: string): (int, string)
+{
+ if (d == nil)
+ return (Abort, "device not initialized");
+ found := 0;
+ secs *= 1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+secs;){
+ a := getinput(d,1);
+ if(len a == 0){
+ if(limit){
+ sys->sleep(1);
+ continue;
+ }
+ break;
+ }
+ if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){
+ if (len s) {
+ if (s[(len s)-1] == '\r')
+ s[(len s)-1] = '\n';
+ sys->print("<-%s\n",s);
+ }
+ if(substr != nil && contains(s, substr))
+ found = 1;
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text &&
+ s[0:len msgs[k].text] == msgs[k].text){
+ if(found)
+ return (Found, s);
+ return (msgs[k].code, s);
+ }
+ start = sys->millisec();
+ s = "";
+ continue;
+ }
+ s[len s] = int a[0];
+ limit--;
+ }
+ s = "No response from modem";
+ if(found)
+ return (Found, s);
+
+ return (Noise, s);
+}
+
+#
+# get baud rate from a connect message
+#
+
+getspeed(msg: string, speed: int): int
+{
+ p := msg[7:]; # skip "CONNECT"
+ while(p[0] == ' ' || p[0] == '\t')
+ p = p[1:];
+ s := int p;
+ if(s <= 0)
+ return speed;
+ else
+ return s;
+}
+
+#
+# set speed and RTS/CTS modem flow control
+#
+
+setspeed(d: ref Device, baud: int)
+{
+ if(d != nil && d.ctl != nil){
+ sys->fprint(d.ctl, "b%d", baud);
+ sys->fprint(d.ctl, "m1");
+ }
+}
+
+dumpa(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++){
+ b := int a[i];
+ if(b >= ' ' && b < 16r7f)
+ s[len s] = b;
+ else
+ s += sys->sprint("\\%.2x", b);
+ }
+ return s;
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int)
+{
+ openserial(d);
+ pidc <-= sys->pctl(0, nil); # pidc can be written once only.
+ a := array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ d.lock.obtain();
+ d.status = "Idle";
+ d.remote = "";
+ setspeed(d, d.speed);
+ d.lock.release();
+ # shuttle bytes
+ while((n := sys->read(d.data, a, len a)) > 0){
+ d.lock.obtain();
+ if (len d.avail < Sys->ATOMICIO) {
+ na := array[len d.avail + n] of byte;
+ na[0:] = d.avail[0:];
+ na[len d.avail:] = a[0:n];
+ d.avail = na;
+ }
+ d.lock.release();
+ }
+ # on an error, try reopening the device
+ d.data = nil;
+ d.ctl = nil;
+ openserial(d);
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+getinput(d: ref Device, n: int): array of byte
+{
+ if(d==nil || n <= 0)
+ return nil;
+ a: array of byte;
+ d.lock.obtain();
+ if(len d.avail != 0){
+ if(n > len d.avail)
+ n = len d.avail;
+ a = d.avail[0:n];
+ d.avail = d.avail[n:];
+ }
+ d.lock.release();
+ return a;
+}
+
+getc(m: ref Device, timo: int): int
+{
+ start := sys->millisec();
+ while((b := getinput(m, 1)) == nil) {
+ if (timo && sys->millisec() > start+timo)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(modeminfo: ref ModemInfo): ref Device
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if (lock == nil) {
+ raise "fail: Couldn't load lock module";
+ return nil;
+ }
+ lock->init();
+ }
+
+ newdev := ref Device;
+ newdev.lock = Semaphore.new();
+ newdev.local = modeminfo.path;
+ newdev.pid = 0;
+ newdev.t = modeminfo;
+
+ return newdev;
+}
+
+
+#
+# dial a number
+#
+dial(d: ref Device, number: string)
+{
+ if (d==nil) {
+ raise "fail: Device not initialized";
+ return;
+ }
+
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ sys->print("Attention\n");
+ x := attention(d);
+ if (x != Ok)
+ sys->print("Attention failed\n");
+ #
+ # extended Hayes commands, meaning depends on modem (VGA all over again)
+ #
+ sys->print("Init\n");
+ if(d.t.country != nil)
+ applyspecial(d, d.t.country);
+
+ if(d.t.init != nil)
+ applyspecial(d, d.t.init);
+
+ if(d.t.other != nil)
+ applyspecial(d, d.t.other);
+
+ applyspecial(d, d.t.errorcorrection);
+
+ compress := Abort;
+ if(d.t.mnponly != nil)
+ compress = applyspecial(d, d.t.mnponly);
+ if(d.t.compression != nil)
+ compress = applyspecial(d, d.t.compression);
+
+ rateadjust := Abort;
+ if(compress != Ok)
+ rateadjust = applyspecial(d, d.t.rateadjust);
+ applyspecial(d, d.t.flowctl);
+
+ # finally, dialout
+ sys->print("Dialing\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ if(send(d, sys->sprint("%s%s\r", dt, number)) < 0) {
+ raise "can't dial "+number;
+ return;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success) {
+ raise "fail: "+msg;
+ return;
+ }
+
+ connectspeed := getspeed(msg, d.speed);
+
+ # change line rate if not compressing
+ if(rateadjust == Ok)
+ setspeed(d, connectspeed);
+
+ if(d.ctl != nil){
+ if(d != nil)
+ sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it)
+ sys->fprint(d.ctl, "c1"); # enable CD monitoring
+ }
+}
diff --git a/appl/cmd/ip/ppp/modem.m b/appl/cmd/ip/ppp/modem.m
new file mode 100644
index 00000000..9a99acf8
--- /dev/null
+++ b/appl/cmd/ip/ppp/modem.m
@@ -0,0 +1,41 @@
+Modem: module
+{
+ PATH: con "/dis/ip/ppp/modem.dis";
+
+ ModemInfo: adt {
+ path: string;
+ init: string;
+ country: string;
+ other: string;
+ errorcorrection:string;
+ compression: string;
+ flowctl: string;
+ rateadjust: string;
+ mnponly: string;
+ dialtype: string;
+ };
+
+ Device: adt {
+ lock: ref Lock->Semaphore;
+ # modem stuff
+ ctl: ref Sys->FD;
+ data: ref Sys->FD;
+
+ local: string;
+ remote: string;
+ status: string;
+ speed: int;
+ t: ref ModemInfo;
+ # input reader
+ avail: array of byte;
+ pid: int;
+ };
+
+ init: fn(i: ref ModemInfo): ref Device;
+ dial: fn( m: ref Device, number: string);
+ getc: fn(m: ref Device, timout: int): int;
+ getinput: fn(m: ref Device, n: int ): array of byte;
+ send: fn(m: ref Device, x: string): int;
+ close: fn(m: ref Device): ref Sys->Connection;
+ onhook: fn(m: ref Device);
+};
diff --git a/appl/cmd/ip/ppp/pppclient.b b/appl/cmd/ip/ppp/pppclient.b
new file mode 100644
index 00000000..be321b59
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppclient.b
@@ -0,0 +1,216 @@
+implement PPPClient;
+
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+
+include "pppclient.m";
+
+include "translate.m";
+ translate : Translate;
+ Dict : import translate;
+ dict : ref Dict;
+
+#
+# Globals (these will have to be removed if we are going multithreaded)
+#
+
+pid := 0;
+modeminfo: ref Modem->ModemInfo;
+pppdir: string;
+
+ppplog(log: chan of int, errfile: string, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil); # set reset pid to our pid
+ src := sys->open(errfile, Sys->OREAD);
+ if (src == nil)
+ raise sys->sprint("fail: Couldn't open %s: %r", errfile);
+
+ LOGBUFMAX: con 1024;
+ buf := array[LOGBUFMAX] of byte;
+ connected := 0;
+
+ while ((count := sys->read(src, buf, LOGBUFMAX)) > 0) {
+ (n, toklist) := sys->tokenize(string buf[:count],"\n");
+ for (;toklist != nil;toklist = tl toklist) {
+ case hd toklist {
+ "no error" =>
+ log <-= s_SuccessPPP;
+ lasterror = nil;
+ connected = 1;
+ "permission denied" =>
+ lasterror = X("Username or Password Incorrect");
+ log <-= s_Error;
+ "write to hungup channel" =>
+ lasterror = X("Remote Host Hung Up");
+ log <-= s_Error;
+ * =>
+ lasterror = X(hd toklist);
+ log <-= s_Error;
+ }
+ }
+ }
+ if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead?
+ lasterror = X("Lost Connection");
+ log <-= s_Error;
+ }
+}
+
+startppp(logchan: chan of int, pppinfo: ref PPPInfo)
+{
+ ifd := sys->open("/net/ipifc/clone", Sys->ORDWR);
+ if (ifd == nil)
+ raise "fail: Couldn't open /net/ipifc/clone";
+
+ buf := array[32] of byte;
+ n := sys->read(ifd, buf, len buf);
+ if(n <= 0)
+ raise "fail: can't read from /net/ipifc/clone";
+
+ pppdir = "/net/ipifc/" + string buf[0:n];
+ pidc := chan of int;
+ spawn ppplog(logchan, pppdir + "/err", pidc);
+ pid = <-pidc;
+ logchan <-= s_StartPPP;
+
+ if (pppinfo.ipaddr == nil)
+ pppinfo.ipaddr = "-";
+# if (pppinfo.ipmask == nil)
+# pppinfo.ipmask = "255.255.255.255";
+ if (pppinfo.peeraddr == nil)
+ pppinfo.peeraddr = "-";
+ if (pppinfo.maxmtu == nil)
+ pppinfo.maxmtu = "512";
+ if (pppinfo.username == nil)
+ pppinfo.username = "-";
+ if (pppinfo.password == nil)
+ pppinfo.password = "-";
+ framing := "1";
+
+ ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu
+ +" "+framing+" "+pppinfo.username+" "+pppinfo.password;
+
+ # send the add command
+ if (sys->fprint(ifd, "%s", ifc) < 0) {
+ sys->print("pppclient: couldn't write %s/ctl: %r\n", pppdir);
+ raise "fail: Couldn't write /net/ipifc";
+ return;
+ }
+}
+
+connect(mi: ref Modem->ModemInfo, number: string,
+ scriptinfo: ref Script->ScriptInfo, pppinfo: ref PPPInfo, logchan: chan of int)
+{
+ sys = load Sys Sys->PATH;
+
+ translate = load Translate Translate->PATH;
+ if (translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppclient");
+ (dict, nil) = translate->opendict(dictname);
+ }
+ if (pid != 0) # yikes we are already running
+ reset();
+
+ # create a new process group
+ pid = sys->pctl( Sys->NEWPGRP, nil);
+
+ {
+ logchan <-= s_Initialized;
+
+ # open & init the modem
+ modeminfo = mi;
+ modem := load Modem Modem->PATH;
+ if (modem == nil) {
+ raise "fail: Couldn't load modem module";
+ return;
+ }
+
+ modemdev := modem->init(modeminfo);
+ logchan <-= s_StartModem;
+ modem->dial(modemdev, number);
+ logchan <-= s_SuccessModem;
+
+ # if script
+ if (scriptinfo != nil) {
+ script := load Script Script->PATH;
+ if (script == nil) {
+ raise "fail: Couldn't load script module";
+ return;
+ }
+ logchan <-= s_StartScript;
+ script->execute(modem, modemdev, scriptinfo);
+ logchan <-= s_SuccessScript;
+ }
+
+ mc := modem->close(modemdev); # keep connection open for ppp mode
+ modemdev = nil;
+ modem = nil; # unload modem module
+
+ # if ppp
+ if (pppinfo != nil)
+ startppp(logchan, pppinfo);
+ else
+ logchan <-= s_Done;
+ }
+ exception e{
+ "fail*" =>
+ lasterror = e;
+ sys->print("PPPclient: fatal exception: %s\n", e);
+ logchan <-= s_Error;
+ kill(pid, "killgrp");
+ exit;
+ }
+}
+
+reset()
+{
+ sys->print("reset...");
+ if(pid != 0){
+ kill(pid, "killgrp");
+ pid = 0;
+ }
+
+ if(pppdir != nil){ # shut down the PPP link
+ fd := sys->open(pppdir + "/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "unbind") < 0)
+ sys->print("pppclient: can't unbind: %r\n");
+ fd = nil;
+ pppdir = nil;
+ }
+
+ modem := load Modem Modem->PATH;
+ if (modem == nil) {
+ raise "fail: Couldn't load modem module";
+ return;
+ }
+ modemdev := modem->init(modeminfo);
+ if(modemdev != nil)
+ modem->onhook(modemdev);
+ modem = nil;
+
+ # clear error buffer
+ lasterror = nil;
+}
+
+kill(pid: int, msg: string)
+{
+ a := array of byte msg;
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->write(fd, a, len a) < 0)
+ sys->print("pppclient: can't %s %d: %r\n", msg, pid);
+}
+
+# Translate a string
+
+X(s : string) : string
+{
+ if (dict== nil) return s;
+ return dict.xlate(s);
+}
+
diff --git a/appl/cmd/ip/ppp/pppclient.m b/appl/cmd/ip/ppp/pppclient.m
new file mode 100644
index 00000000..23396af4
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppclient.m
@@ -0,0 +1,31 @@
+
+PPPClient: module {
+ PATH: con "/dis/ip/ppp/pppclient.dis";
+
+ PPPInfo: adt {
+ ipaddr: string;
+ ipmask: string;
+ peeraddr: string;
+ maxmtu: string;
+ username: string;
+ password: string;
+ };
+
+ connect: fn( mi: ref Modem->ModemInfo, number: string,
+ scriptinfo: ref Script->ScriptInfo,
+ pppinfo: ref PPPInfo, logchan: chan of int);
+ reset: fn();
+
+ lasterror :string;
+
+ s_Error: con -666;
+ s_Initialized, # Module Initialized
+ s_StartModem, # Modem Initialized
+ s_SuccessModem, # Modem Connected
+ s_StartScript, # Script Executing
+ s_SuccessScript, # Script Executed Sucessfully
+ s_StartPPP, # PPP Started
+ s_LoginPPP, # CHAP/PAP Authentication
+ s_SuccessPPP, # PPP Session Established
+ s_Done: con iota; # PPPClient Cleaningup & Exiting
+};
diff --git a/appl/cmd/ip/ppp/pppdial.b b/appl/cmd/ip/ppp/pppdial.b
new file mode 100644
index 00000000..ec689dc1
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppdial.b
@@ -0,0 +1,283 @@
+implement PPPdial;
+
+#
+# Module: ispservice
+# Purpose: Simple PPP Dial-on-Demand
+# Author: Eric Van Hensbergen (ericvh@lucent.com)
+#
+# Copyright © 1998-1999 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000-2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+
+include "cfgfile.m";
+ cfg: CfgFile;
+ ConfigFile: import cfg;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+ ppp: PPPClient;
+include "pppgui.m";
+
+PPPdial: module
+{
+ init: fn(nil: ref Draw->Context): string;
+ connect: fn(): string;
+};
+
+context: ref Draw->Context;
+modeminfo: ref Modem->ModemInfo;
+pppinfo: ref PPPClient->PPPInfo;
+scriptinfo: ref Script->ScriptInfo;
+isp_number: string; # should be part of pppinfo
+lastCdir: ref Sys->Dir; # state of file when last read
+
+DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo
+DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo
+MODEM_DB_PATH: con "/usr/inferno/config/modem.cfg"; # contains modeminfo
+ISP_DB_PATH: con "/usr/inferno/config/isp.cfg"; # contains pppinfo & scriptinfo
+ISP_RETRIES: con 5;
+
+getcfgstring(c: ref ConfigFile, key: string) :string
+{
+ l := c.getcfg(key);
+ if (l == nil)
+ return nil;
+ for(ret := ""; l != nil; l = tl l)
+ ret+= " " + hd l;
+
+ return ret[1:]; # trim the first space
+}
+
+configinit()
+{
+ mi: Modem->ModemInfo;
+ pppi: PPPClient->PPPInfo;
+ info: list of string;
+
+ cfg = load CfgFile CfgFile->PATH;
+ if (cfg == nil)
+ raise "fail: load CfgFile";
+
+ # Modem Configuration
+
+ cfg->verify(DEFAULT_MODEM_DB_PATH, MODEM_DB_PATH);
+ modemcfg := cfg->init(MODEM_DB_PATH);
+ if (modemcfg == nil)
+ raise "fail: read: "+MODEM_DB_PATH;
+ modeminfo = ref mi;
+
+ modeminfo.path = getcfgstring(modemcfg, "PATH");
+ modeminfo.init = getcfgstring(modemcfg, "INIT");
+ modeminfo.country = getcfgstring(modemcfg, "COUNTRY");
+ modeminfo.other = getcfgstring(modemcfg, "OTHER");
+ modeminfo.errorcorrection = getcfgstring(modemcfg,"CORRECT");
+ modeminfo.compression = getcfgstring(modemcfg,"COMPRESS");
+ modeminfo.flowctl = getcfgstring(modemcfg,"FLOWCTL");
+ modeminfo.rateadjust = getcfgstring(modemcfg,"RATEADJ");
+ modeminfo.mnponly = getcfgstring(modemcfg,"MNPONLY");
+ modeminfo.dialtype = getcfgstring(modemcfg,"DIALING");
+ if(modeminfo.dialtype!="ATDP")
+ modeminfo.dialtype="ATDT";
+
+ cfg->verify(DEFAULT_ISP_DB_PATH, ISP_DB_PATH);
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if(ok >= 0)
+ lastCdir = ref stat;
+ sys->print("cfg->init(%s)\n", ISP_DB_PATH);
+
+ # ISP Configuration
+ pppcfg := cfg->init(ISP_DB_PATH);
+ if (pppcfg == nil)
+ raise "fail: Couldn't load ISP configuration file: "+ISP_DB_PATH;
+ pppinfo = ref pppi;
+ isp_number = getcfgstring(pppcfg, "NUMBER");
+ pppinfo.ipaddr = getcfgstring(pppcfg,"IPADDR");
+ pppinfo.ipmask = getcfgstring(pppcfg,"IPMASK");
+ pppinfo.peeraddr = getcfgstring(pppcfg,"PEERADDR");
+ pppinfo.maxmtu = getcfgstring(pppcfg,"MAXMTU");
+ pppinfo.username = getcfgstring(pppcfg,"USERNAME");
+ pppinfo.password = getcfgstring(pppcfg,"PASSWORD");
+
+ info = pppcfg.getcfg("SCRIPT");
+ if (info != nil) {
+ scriptinfo = ref Script->ScriptInfo;
+ scriptinfo.path = hd info;
+ scriptinfo.username = pppinfo.username;
+ scriptinfo.password = pppinfo.password;
+ } else
+ scriptinfo = nil;
+
+ info = pppcfg.getcfg("TIMEOUT");
+ if (info != nil)
+ scriptinfo.timeout = int (hd info);
+
+ cfg = nil; # might as well unload it
+}
+
+#
+# Parts of the following two functions could be generalized
+#
+
+isipaddr(a: string): int
+{
+ i, c, ac, np: int = 0;
+
+ for(i = 0; i < len a; i++) {
+ c = a[i];
+ if(c >= '0' && c <= '9') {
+ np = 10*np + c - '0';
+ continue;
+ }
+ if (c == '.' && np) {
+ ac++;
+ if (np > 255)
+ return 0;
+ np = 0;
+ continue;
+ }
+ return 0;
+ }
+ return np && np < 256 && ac == 3;
+}
+
+# check if there is an existing PPP connection
+connected(): int
+{
+ ifd := sys->open("/net/ipifc", Sys->OREAD);
+ if(ifd == nil)
+ return 0;
+
+ buf := array[1024] of byte;
+
+ for(;;) {
+ (n, d) := sys->dirread(ifd);
+ if (n <= 0)
+ return 0;
+ for(i := 0; i < n; i++)
+ if(d[i].name[0] <= '9') {
+ sfd := sys->open("/net/ipifc/"+d[i].name+"/status", Sys->OREAD);
+ if (sfd == nil)
+ continue;
+ ns := sys->read(sfd, buf, len buf);
+ if (ns <= 0)
+ continue;
+ (nflds, flds) := sys->tokenize(string buf[0:ns], " \t\r\n");
+ if(nflds < 4)
+ continue;
+ if (isipaddr(hd tl tl flds))
+ return 1;
+ }
+ }
+}
+
+#
+# called once when loaded
+#
+init(ctxt: ref Draw->Context): string
+{
+ sys = load Sys Sys->PATH;
+ {
+ ppp = load PPPClient PPPClient->PATH;
+ if (ppp == nil)
+ raise "fail: Couldn't load ppp module";
+
+ # Contruct Config Tables During Init - may want to change later
+ # for multiple configs (Software Download Server versus ISP)
+ configinit();
+ context = ctxt;
+ }exception e {
+ "fail:*" =>
+ return e;
+ }
+ return nil;
+}
+
+dialup_cancelled := 0;
+connecting := 0;
+
+#
+# called each time a translation is needed, to check that we're on line(!)
+# eventually this will be replaced by a packet interface that does dial-on-demand
+#
+connect(): string
+{
+ {
+ dialup_cancelled = 0;
+ (ok, stat) := sys->stat(ISP_DB_PATH);
+ if (ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat))
+ configinit();
+ errc := chan of string;
+ while(!connected()){
+ if(!connecting) {
+ connecting = 1;
+ sync := chan of int;
+ spawn pppconnect(errc, sync);
+ <- sync;
+ return <-errc;
+ }else{
+ sys->sleep(2500);
+ if (dialup_cancelled)
+ return "fail: dialup cancelled";
+ }
+ }
+ }exception e{
+ "fail:*" =>
+ return e;
+ "*" =>
+ sys->print("pppdial: caught exception: %s\n", e);
+ return "fail: internal error: "+e;
+ }
+ return nil;
+}
+
+pppconnect(errc: chan of string, sync: chan of int)
+{
+ connecting = 1;
+ sys->pctl(Sys->NEWPGRP, nil);
+ sync <-= 0;
+ resp_chan: chan of int;
+ logger := chan of int;
+ pppgui := load PPPGUI PPPGUI->PATH;
+ for (count :=0; count < ISP_RETRIES; count++) {
+ resp_chan = pppgui->init(context, logger, ppp, nil);
+ spawn ppp->connect(modeminfo, isp_number, scriptinfo, pppinfo, logger);
+ x := <-resp_chan;
+ if (x > 0) {
+ if (x == 1) {
+ # alt needed in case calling process has been killed
+ alt {
+ errc <-= nil => ;
+ * => ;
+ }
+ } else { # user cancelled dial-in
+ dialup_cancelled = 1;
+ alt {
+ errc <-= "fail: dialup cancelled" => ;
+ * => ;
+ }
+ }
+ connecting = 0;
+ return;
+ }
+ # else connect failed, go around loop to try again
+ }
+ alt {
+ errc <-= "fail: dialup failed" => ;
+ * => ;
+ }
+ connecting = 0;
+}
+
+samefile(d1, d2: Sys->Dir): int
+{
+ return d1.dev==d2.dev && d1.dtype==d2.dtype &&
+ d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
+ d1.mtime==d2.mtime;
+}
diff --git a/appl/cmd/ip/ppp/pppgui.b b/appl/cmd/ip/ppp/pppgui.b
new file mode 100644
index 00000000..40e7e3b4
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppgui.b
@@ -0,0 +1,373 @@
+#
+# Copyright © 1998 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Originally Written by N. W. Knauft
+# Adapted by E. V. Hensbergen (ericvh@lucent.com)
+# Further adapted by Vita Nuova
+#
+
+implement PPPGUI;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+ ppp: PPPClient;
+
+include "pppgui.m";
+
+#Screen constants
+BBG: con "#C0C0C0"; # Background color for button
+PBG: con "#808080"; # Background color for progress bar
+LTGRN: con "#00FF80"; # Color for progress bar
+BARW: con 216; # Progress bar width
+BARH: con " 9"; # Progress bar height
+INCR: con 30; # Progress bar increment size
+N_INCR: con 7; # Number of increments in progress bar width
+BSIZE: con 25; # Icon button size
+ISIZE: con BSIZE + 4; # Icon window size
+DIALQUANTA : con 1000;
+ICONQUANTA : con 5000;
+
+#Globals
+pppquanta := DIALQUANTA;
+
+#Font
+FONT: con "/fonts/lucidasans/unicode.6.font";
+
+#Messages
+stat_msgs := array[] of {
+ "Initializing Modem",
+ "Dialling Service Provider",
+ "Logging Into Network",
+ "Executing Login Script",
+ "Script Execution Complete",
+ "Logging Into Network",
+ "Verifying Password",
+ "Connected",
+ "",
+};
+
+config_icon := array[] of {
+ "button .btn -text X -width "+string BSIZE+" -height "+string BSIZE+" -command {send tsk open} -bg "+BBG,
+ "pack .btn",
+
+ "pack propagate . no",
+ ". configure -bd 0",
+ ". unmap",
+ "update",
+};
+
+
+# Create internet connect window, spawn event handler
+init(ctxt: ref Draw->Context, stat: chan of int, pppmod: PPPClient, args: list of string): chan of int
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ if (draw == nil || tk == nil || tkclient == nil) {
+ sys->fprint(sys->fildes(2), "pppgui: can't load Draw or Tk: %r\n");
+ return nil;
+ }
+
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppgui");
+ dicterr: string;
+ (dict, dicterr) = translate->opendict(dictname);
+ if(dicterr != nil)
+ sys->fprint(sys->fildes(2), "pppgui: can't open %s: %s\n", dictname, dicterr);
+ }else
+ sys->fprint(sys->fildes(2), "pppgui: can't load %s: %r\n", Translate->PATH);
+ ppp = pppmod; # set the global
+
+ tkargs := "";
+
+ if (args != nil) {
+ tkargs = hd args;
+ args = tl args;
+ } else
+ tkargs="-x 340 -y 4";
+
+ tkclient->init();
+
+ (t, wmctl) := tkclient->toplevel(ctxt, tkargs, "PPP", Tkclient->Plain);
+
+ config_win := array[] of {
+ "frame .f",
+ "frame .fprog",
+
+ "canvas .cprog -bg "+PBG+" -bd 2 -width "+string BARW+" -height "+BARH+" -relief ridge",
+ "pack .cprog -in .fprog -pady 6",
+
+ "label .stat -text {"+X("Initializing connection...")+"} -width 164 -font "+FONT,
+ "pack .stat -in .f -side left -fill y -anchor w",
+
+ "button .done -text {"+X("Cancel")+"} -width 60 -command {send cmd cancel} -bg "+BBG+" -font "+FONT,
+ "pack .fprog -side bottom -expand 1 -fill x",
+ "pack .done -side right -padx 1 -pady 1 -fill y -anchor e",
+ "pack .f -side left -expand 1 -padx 5 -pady 3 -fill both -anchor w",
+
+ "pack propagate . no",
+ ". configure -bd 2 -relief raised -width "+string WIDTH,
+ "update",
+ };
+
+ for(i := 0; i < len config_win; i++)
+ tk->cmd(t, config_win[i]);
+
+ itkargs := "";
+ if (args != nil) {
+ itkargs = hd args;
+ args = tl args;
+ }
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: nil);
+
+ if (itkargs == "") {
+ x := int tk->cmd(t, ". cget x");
+ y := int tk->cmd(t, ". cget y");
+ x += WIDTH - ISIZE;
+ itkargs = "-x "+string x+" -y "+string y;
+ }
+
+ (ticon, iconctl) := tkclient->toplevel(ctxt, itkargs, "PPP", Tkclient->Plain);
+
+ for( i = 0; i < len config_icon; i++)
+ tk->cmd(ticon, config_icon[i]);
+
+ tk->cmd(ticon, "image create bitmap Network -file network.bit -maskfile network.bit");
+ tk->cmd(ticon, ".btn configure -image Network");
+ tkclient->startinput(ticon, "ptr"::nil);
+
+ chn := chan of int;
+ spawn handle_events(t, wmctl, ticon, iconctl, stat, chn);
+ return chn;
+}
+
+ppp_timer(sync: chan of int, stat: chan of int)
+{
+ for(;;) {
+ sys->sleep(pppquanta);
+ alt {
+ <-sync =>
+ return;
+ stat <-= -1 =>
+ ;
+ }
+ }
+}
+
+send(cmd: chan of string, msg: string)
+{
+ cmd <-= msg;
+}
+
+# Process events and pass disconnect cmd to calling app
+handle_events(t: ref Tk->Toplevel, wmctl: chan of string, ticon: ref Tk->Toplevel, iconctl: chan of string, stat, chn: chan of int)
+{
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ tsk := chan of string;
+ tk->namechan(ticon, tsk, "tsk");
+
+ connected := 0;
+ winmapped := 1;
+ timecount := 0;
+ xmin := 0;
+ x := 0;
+
+ iocmd := sys->file2chan("/chan", "pppgui");
+ if (iocmd == nil) {
+ sys->print("fail: pppgui: file2chan: /chan/pppgui: %r\n");
+ return;
+ }
+
+ pppquanta = DIALQUANTA;
+ sync_chan := chan of int;
+ spawn ppp_timer(sync_chan, stat);
+
+Work:
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ tkclient->wmctl(t, s);
+
+ s := <-ticon.ctxt.kbd =>
+ tk->keyboard(ticon, s);
+ s := <-ticon.ctxt.ptr =>
+ tk->pointer(ticon, *s);
+ s := <-ticon.ctxt.ctl or
+ s = <-ticon.wreq or
+ s = <-iconctl =>
+ tkclient->wmctl(ticon, s);
+
+ (off, data, fid, wc) := <-iocmd.write => # remote io control
+ if (wc == nil)
+ break;
+ spawn send(cmd, string data[0:len data]);
+ wc <-= (len data, nil);
+
+ (nil, nbytes, fid, rc) := <-iocmd.read =>
+ if (rc != nil)
+ rc <-= (nil, "not readable");
+
+ press := <-cmd =>
+ case press {
+ "cancel" or "disconnect" =>
+ tk->cmd(t, ".stat configure -text 'Disconnecting...");
+ tk->cmd(t, "update");
+ ppp->reset();
+ if (!connected) {
+ # other end may have gone away
+ alt {
+ chn <-= 666 => ;
+ * => ;
+ }
+ }
+ break Work;
+ * => ;
+ }
+
+ prs := <-tsk =>
+ case prs {
+ "open" =>
+ tk->cmd(ticon, ". unmap; update");
+ tk->cmd(t, ". map; raise .; update");
+ winmapped = 1;
+ timecount = 0;
+ * => ;
+ }
+
+ s := <-stat =>
+ if (s == -1) { # just an update event
+ if(winmapped){
+ if(!connected) { # increment status bar
+ if (x < xmin+INCR) {
+ x++;
+ tk->cmd(t, ".cprog create rectangle 0 0 "+string x + BARH+" -fill "+LTGRN);
+ }
+ }else{
+ timecount++;
+ if(timecount > 1){
+ winmapped = 0;
+ timecount = 0;
+ tk->cmd(t, ". unmap; update");
+ tk->cmd(ticon, ". map; raise .; update");
+ continue;
+ }
+ }
+ tk->cmd(t, "raise .; update");
+ } else {
+ tk->cmd(ticon, "raise .; update");
+ timecount = 0;
+ }
+ continue;
+ }
+ if (s == ppp->s_Error) {
+ tk->cmd(t, ".stat configure -text '"+ppp->lasterror);
+ if (!winmapped) {
+ tk->cmd(ticon, ". unmap; update");
+ tk->cmd(t, ". map; raise .");
+ }
+ tk->cmd(t, "update");
+ sys->sleep(3000);
+ ppp->reset();
+ if (!connected)
+ chn <-= 0; # Failure
+ break Work;
+ }
+
+ if (s == ppp->s_Initialized)
+ tk->cmd(t,".cprog create rectangle 0 0 "+string BARW + BARH+" -fill "+PBG);
+
+ x = xmin = s * INCR;
+ if (xmin > BARW)
+ xmin = BARW;
+ tk->cmd(t, ".cprog create rectangle 0 0 "+string xmin + BARH+" -fill "+LTGRN);
+ tk->cmd(t, "raise .; update");
+ tk->cmd(t, ".stat configure -text '"+X(stat_msgs[s]));
+
+ if (s == ppp->s_SuccessPPP || s == ppp->s_Done) {
+ if(!connected){
+ chn <-= 1;
+ connected = 1;
+ }
+ pppquanta = ICONQUANTA;
+
+ # find and display connection speed
+ speed := findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil);
+ if(speed != nil)
+ tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+" "+speed+" bps}");
+ else
+ tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+"}");
+ tk->cmd(t, ".done configure -text Disconnect -command 'send cmd disconnect");
+ tk->cmd(t, "update");
+ sys->sleep(2000);
+ tk->cmd(t, ". unmap; pack forget .fprog; update");
+ winmapped = 0;
+ tk->cmd(ticon, ". map; raise .; update");
+ }
+
+ tk->cmd(t, "update");
+ }
+ sync_chan <-= 1; # stop ppp_timer
+}
+
+findrate(file: string, opt: list of string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array [1024] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 1)
+ return nil;
+ (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n");
+ for(; flds != nil; flds = tl flds)
+ for(l := opt; l != nil; l = tl l)
+ if (hd flds == hd l)
+ return hd tl flds;
+ return nil;
+}
+
+
+
+# Translate a string
+
+X(s : string) : string
+{
+ if (dict== nil) return s;
+ return dict.xlate(s);
+}
+
diff --git a/appl/cmd/ip/ppp/pppgui.m b/appl/cmd/ip/ppp/pppgui.m
new file mode 100644
index 00000000..af9ec574
--- /dev/null
+++ b/appl/cmd/ip/ppp/pppgui.m
@@ -0,0 +1,21 @@
+#
+# Copyright © 1998 Lucent Technologies Inc. All rights reserved.
+# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Originally Written by N. W. Knauft
+# Adapted by E. V. Hensbergen (ericvh@lucent.com)
+# Further adapted by Vita Nuova
+#
+
+PPPGUI: module
+{
+ PATH: con "/dis/ip/ppp/pppgui.dis";
+
+ # Dimension constant for ISP Connect window
+ WIDTH: con 300;
+ HEIGHT: con 58;
+
+ init: fn(ctxt: ref Draw->Context, stat: chan of int,
+ ppp: PPPClient, args: list of string): chan of int;
+};
+
diff --git a/appl/cmd/ip/ppp/ppptest.b b/appl/cmd/ip/ppp/ppptest.b
new file mode 100644
index 00000000..e5dfced0
--- /dev/null
+++ b/appl/cmd/ip/ppp/ppptest.b
@@ -0,0 +1,86 @@
+# Last change: R 24 May 2001 11:05 am
+implement PPPTest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+include "pppgui.m";
+
+PPPTest: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+usage()
+{
+ sys->print("ppptest device modem_init tel user password \n");
+ sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n");
+ exit;
+
+}
+init( ctxt: ref Draw->Context, argv: list of string )
+{
+ sys = load Sys Sys->PATH;
+
+ mi: Modem->ModemInfo;
+ pi: PPPClient->PPPInfo;
+ tel : string;
+# si: Script->ScriptInfo;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.path = hd argv;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.init = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ tel = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ pi.username = hd argv;
+ argv = tl argv;
+ if(argv==nil)
+ usage();
+ else
+ pi.password = hd argv;
+
+
+ #si.path = "rdid.script";
+ #si.username = "ericvh";
+ #si.password = "foobar";
+ #si.timeout = 60;
+
+
+ ppp := load PPPClient PPPClient->PATH;
+
+ logger := chan of int;
+
+ spawn ppp->connect( ref mi, tel, nil, ref pi, logger );
+
+ pppgui := load PPPGUI PPPGUI->PATH;
+ respchan := pppgui->init( ctxt, logger,ppp, nil);
+
+ event := 0;
+ while (1) {
+ event =<- respchan;
+ sys->print("GUI event received: %d\n",event);
+ if (event) {
+ sys->print("success");
+ exit;
+ } else {
+ raise "fail: Couldn't connect to ISP";
+ }
+ }
+}
diff --git a/appl/cmd/ip/ppp/script.b b/appl/cmd/ip/ppp/script.b
new file mode 100644
index 00000000..8be184a4
--- /dev/null
+++ b/appl/cmd/ip/ppp/script.b
@@ -0,0 +1,168 @@
+implement Script;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "lock.m";
+include "modem.m";
+ modem: Modem;
+
+include "script.m";
+
+delim: con "-"; # expect-send delimiter
+BUFSIZE: con (1024 * 32);
+
+execute( modmod: Modem, m: ref Modem->Device, scriptinfo: ref ScriptInfo )
+{
+ sys= load Sys Sys->PATH;
+ str= load String String->PATH;
+ if (str == nil) {
+ raise "fail: couldn't load string module";
+ return;
+ }
+ modem = modmod;
+
+ if (scriptinfo.path != nil) {
+ sys->print("Executing Script %s\n",scriptinfo.path);
+ # load the script
+ scriptinfo.content = scriptload(scriptinfo.path);
+ } else {
+ sys->print("Executing Inline Script\n");
+ }
+
+ # Check for timeout variable
+
+ if (scriptinfo.timeout == 0)
+ scriptinfo.timeout = 20;
+
+ tend := sys->millisec() + 1000*scriptinfo.timeout;
+
+ conv := scriptinfo.content;
+
+ while (conv != nil) {
+ e, s: string = nil;
+ p := hd conv;
+ conv = tl conv;
+ if (len p == 0)
+ continue;
+ sys->print("script: %s\n",p);
+ if (p[0] == '-') { # just send
+ if (len p == 1)
+ continue;
+ s = p[1:];
+ } else {
+ (n, esl) := sys->tokenize(p, delim);
+ if (n > 0) {
+ e = hd esl;
+ esl = tl esl;
+ if (n > 1)
+ s = hd esl;
+ }
+ }
+ if (e != nil) {
+ if (match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) {
+ sys->print("script: match failed\n");
+ raise "fail: Script Failed";
+ return;
+ }
+ }
+ if (s != nil)
+ modem->send(m, special(s, scriptinfo));
+ }
+
+ sys->print("script: done!\n");
+}
+
+match(m: ref Modem->Device, s: string, timo: int): int
+{
+ for(;;) {
+ c := modem->getc(m, timo);
+ if (c == '\r')
+ c = '\n';
+ sys->print("%c",c);
+ if (c == 0)
+ return 0;
+ head:
+ while(c == s[0]) {
+ i := 1;
+ while(i < len s) {
+ c = modem->getc(m, timo);
+ if (c == '\r')
+ c = '\n';
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ if(c != s[i])
+ continue head;
+ i++;
+ }
+ return 1;
+ }
+ if(c == '~')
+ return 1; # assume PPP for now
+ }
+}
+
+#
+# Expand special script sequences
+#
+special(s: string, scriptinfo: ref ScriptInfo ): string
+{
+ if (s == "$username") # special variable
+ s = scriptinfo.username;
+ else if (s == "$password")
+ s = scriptinfo.password;
+
+ return deparse(s);
+}
+
+deparse(s : string) : string
+{
+ r: string = "";
+ for(i:=0; i < len s; i++) {
+ c := s[i];
+ if (c == '\\' && i+1 < len s) {
+ c = s[++i];
+ case c {
+ 't' => c = '\t';
+ 'n' => c = '\n';
+ 'r' => c = '\r';
+ 'b' => c = '\b';
+ 'a' => c = '\a';
+ 'v' => c = '\v';
+ '0' => c = '\0';
+ '$' => c = '$';
+ 'u' =>
+ if (i+4 < len s) {
+ i++;
+ (c, nil) = str->toint(s[i:i+4], 16);
+ i+=3;
+ }
+ }
+ }
+ r[len r] = c;
+ }
+ return r;
+}
+
+scriptload( path: string) :list of string
+{
+ dfd := sys->open(path, Sys->OREAD);
+ if (dfd == nil) {
+ raise "fail: Script file ("+path+") not found";
+ return nil;
+ }
+
+ scriptbuf := array[BUFSIZE] of byte;
+ scriptlen := sys->read(dfd, scriptbuf, len scriptbuf);
+ if(scriptlen < 0)
+ raise "fail: can't read script: "+sys->sprint("%r");
+
+ (nil, scriptlist) := sys->tokenize(string scriptbuf[0:scriptlen], "\n");
+ return scriptlist;
+}
diff --git a/appl/cmd/ip/ppp/script.m b/appl/cmd/ip/ppp/script.m
new file mode 100644
index 00000000..342d4d79
--- /dev/null
+++ b/appl/cmd/ip/ppp/script.m
@@ -0,0 +1,14 @@
+Script: module {
+ PATH: con "/dis/ip/ppp/script.dis";
+
+ ScriptInfo: adt {
+ path: string;
+ content: list of string;
+ timeout: int;
+ username: string;
+ password: string;
+ };
+
+ execute: fn( modem: Modem, m: ref Modem->Device,
+ scriptinfo: ref ScriptInfo );
+};
diff --git a/appl/cmd/ip/rip.b b/appl/cmd/ip/rip.b
new file mode 100644
index 00000000..90c1b6ce
--- /dev/null
+++ b/appl/cmd/ip/rip.b
@@ -0,0 +1,620 @@
+implement Rip;
+
+# basic RIP implementation
+# understands v2, sends v1
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Ifcaddr, Udphdr: import ip;
+
+include "attrdb.m";
+ attrdb: Attrdb;
+
+include "arg.m";
+
+Rip: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# rip header:
+# op[1] version[1] pad[2]
+
+Oop: con 0; # op: byte
+Oversion: con 1; # version: byte
+Opad: con 2; # 2 byte pad
+Riphdrlen: con Opad+2; # op[1] version[1] mbz[2]
+
+# rip route entry:
+# type[2] tag[2] addr[4] mask[4] nexthop[4] metric[4]
+
+Otype: con 0; # type[2]
+Otag: con Otype+2; # tag[2] v2 or mbz v1
+Oaddr: con Otag+2; # addr[4]
+Omask: con Oaddr+4; # mask[4] v2 or mbz v1
+Onexthop: con Omask+4;
+Ometric: con Onexthop+4; # metric[4]
+Ipdestlen: con Ometric+4;
+
+Maxripmsg: con 512;
+
+# operations
+OpRequest: con 1; # want route
+OpReply: con 2; # all or part of route table
+
+HopLimit: con 16; # defined by protocol as `infinity'
+RoutesInPkt: con 25; # limit defined by protocol
+RIPport: con 520;
+
+Expired: con 180;
+Discard: con 240;
+
+OutputRate: con 60; # seconds between routing table transmissions
+
+NetworkCost: con 1; # assume the simple case
+
+Gateway: adt {
+ dest: IPaddr;
+ mask: IPaddr;
+ gateway: IPaddr;
+ metric: int;
+ valid: int;
+ changed: int;
+ local: int;
+ time: int;
+
+ contains: fn(g: self ref Gateway, a: IPaddr): int;
+};
+
+netfd: ref Sys->FD;
+routefd: ref Sys->FD;
+AF_INET: con 2;
+
+routes: array of ref Gateway;
+Routeinc: con 50;
+defroute: ref Gateway;
+debug := 0;
+nochange := 0;
+quiet := 1;
+myversion := 1; # default protocol version
+logfile := "iproute";
+netdir := "/net";
+now: int;
+nets: list of ref Ifcaddr;
+addrs: list of IPaddr;
+
+syslog(nil: int, nil: string, s: string)
+{
+ sys->print("rip: %s\n", s);
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ ip = load IP IP->PATH;
+ ip->init();
+
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("ip/rip [-d] [-r]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 'b' => quiet = 0;
+ '2' => myversion = 2;
+ 'n' => nochange = 1;
+ 'x' => netdir = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ quiet = 0;
+ for(; args != nil; args = tl args){
+ (ok, a) := IPaddr.parse(hd args);
+ if(ok < 0)
+ fatal(sys->sprint("invalid address: %s", hd args));
+ addrs = a :: addrs;
+ }
+ arg = nil;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
+
+ whereami();
+ addlocal();
+
+ routefd = sys->open(sys->sprint("%s/iproute", netdir), Sys->ORDWR);
+ if(routefd == nil)
+ fatal(sys->sprint("can't open %s/iproute: %r", netdir));
+ readroutes();
+
+ syslog(0, logfile, "started");
+
+ netfd = riplisten();
+
+ # broadcast request for all routes
+
+ if(!quiet){
+ sendall(OpRequest, 0);
+ spawn sender();
+ }
+
+ # read routing requests
+
+ buf := array[8192] of byte;
+ while((nb := sys->read(netfd, buf, len buf)) > 0){
+ nb -= Riphdrlen + IP->Udphdrlen;
+ if(nb < 0)
+ continue;
+ uh := Udphdr.unpack(buf, IP->Udphdrlen);
+ hdr := buf[IP->Udphdrlen:];
+ version := int hdr[Oversion];
+ if(version < 1)
+ continue;
+ bp := buf[IP->Udphdrlen + Riphdrlen:];
+ case int hdr[Oop] {
+ OpRequest =>
+ # TO DO: transmit in response to request? only if something interesting to say...
+ ;
+
+ OpReply =>
+ # wrong source port?
+ if(uh.rport != RIPport)
+ continue;
+ # my own broadcast?
+ if(ismyaddr(uh.raddr))
+ continue;
+ now = daytime->now();
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "from %s:\n", uh.raddr.text());
+ for(; (nb -= Ipdestlen) >= 0; bp = bp[Ipdestlen:])
+ unpackroute(bp, version, uh.raddr);
+ * =>
+ if(debug)
+ sys->print("rip: unexpected op: %d\n", int hdr[Oop]);
+ }
+ }
+}
+
+whereami()
+{
+ for(ifcs := ip->readipifc(netdir, -1).t0; ifcs != nil; ifcs = tl ifcs)
+ for(al := (hd ifcs).addrs; al != nil; al = tl al){
+ ifa := hd al;
+ if(!ifa.ip.isv4())
+ continue;
+ # how to tell broadcast? must be told? actually, it's in /net/iproute
+ nets = ifa :: nets;
+ }
+}
+
+ismyaddr(a: IPaddr): int
+{
+ for(l := nets; l != nil; l = tl l)
+ if((hd l).ip.eq(a))
+ return 1;
+ return 0;
+}
+
+addlocal()
+{
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ g := lookup(ifc.net);
+ g.valid = 1;
+ g.local = 1;
+ g.gateway = ifc.ip;
+ g.mask = ifc.mask;
+ g.metric = NetworkCost;
+ g.time = 0;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ }
+}
+
+#
+# record any existing routes
+#
+readroutes()
+{
+ now = daytime->now();
+ b := bufio->fopen(routefd, Sys->OREAD);
+ while((l := b.gets('\n')) != nil){
+ (nf, flds) := sys->tokenize(l, " \t");
+ if(nf >= 5){
+ flags := hd tl tl tl flds;
+ if(flags == nil || flags[0] != '4' || contains(flags, "ibum"))
+ continue;
+ g := lookup(parseip(hd flds));
+ g.mask = parsemask(hd tl flds);
+ g.gateway = parseip(hd tl tl flds);
+ g.metric = HopLimit;
+ g.time = now;
+ g.changed = 1;
+ if(debug)
+ syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text()));
+ if(iszero(g.dest) && iszero(g.mask)){
+ defroute = g;
+ g.local = 1;
+ }else if(defroute != nil && g.dest.eq(defroute.gateway))
+ continue;
+ else
+ g.local = !ismyaddr(g.gateway);
+ }
+ }
+}
+
+unpackroute(b: array of byte, version: int, gwa: IPaddr)
+{
+ # check that it's an IP route, valid metric, MBZ fields zero
+
+ if(b[0] != byte 0 || b[1] != byte AF_INET){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- unknown address type %x,%x\n", int b[0], int b[1]);
+ return;
+ }
+ dest := IPaddr.newv4(b[Oaddr:]);
+ mask: IPaddr;
+ if(version == 1){
+ # check MBZ fields
+ if(ip->get2(b, 2) | ip->get4(b, Omask) | ip->get4(b, Onexthop)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t-- non-zero MBZ\n");
+ return;
+ }
+ mask = maskgen(dest);
+ }else if(version == 2){
+ if(ip->get4(b, Omask))
+ mask = IPaddr.newv4(b[Omask:]);
+ else
+ mask = maskgen(dest);
+ if(ip->get4(b, Onexthop))
+ gwa = IPaddr.newv4(b[Onexthop:]);
+ }
+ metric := ip->get4(b, Ometric);
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d\n", dest.text(), metric);
+ if(metric <= 0 || metric > HopLimit)
+ return;
+
+ # 1058/3.4.2: response processing
+ # ignore route if IP address is:
+ # class D or E
+ # net 0 (except perhaps 0.0.0.0)
+ # net 127
+ # broadcast address (all 1s host part)
+ # we allow host routes
+
+ if(dest.ismulticast() || dest.a[0] == byte 0 || dest.a[0] == byte 16r7F){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s %d invalid addr\n", dest.text(), metric);
+ return;
+ }
+ if(isbroadcast(dest, mask)){
+ if(debug > 1)
+ sys->fprint(sys->fildes(2), "\t%s & %s -> broadcast\n", dest.text(), mask.masktext());
+ return;
+ }
+
+ # update the metric min(metric+NetworkCost, HopLimit)
+
+ metric += NetworkCost;
+ if(metric > HopLimit)
+ metric = HopLimit;
+
+ updateroute(dest, mask, gwa, metric);
+}
+
+updateroute(dest, mask, gwa: IPaddr, metric: int)
+{
+ # RFC1058 rules page 27-28, with optional replacement of expiring routes
+ r := lookup(dest);
+ if(r.valid){
+ if(r.local)
+ return; # local, don't touch
+ if(r.gateway.eq(gwa)){
+ if(metric != HopLimit){
+ r.metric = metric;
+ r.time = now;
+ }else{
+ # metric == HopLimit
+ if(r.metric != HopLimit){
+ r.metric = metric;
+ r.changed = 1;
+ r.time = now - (Discard-120);
+ delroute(r); # don't use it for routing
+ # route remains valid but advertised with metric HopLimit
+ } else if(now >= r.time+Discard){
+ delroute(r); # finally dead
+ r.valid = 0;
+ r.changed = 1;
+ }
+ }
+ }else if(metric < r.metric ||
+ metric != HopLimit && metric == r.metric && now > r.time+Expired/2){
+ delroute(r);
+ r.metric = metric;
+ r.gateway = gwa;
+ r.time = now;
+ addroute(r);
+ }
+ } else if(metric < HopLimit){ # new entry
+
+ # 1058/3.4.2: don't add route-to-host if host is on net/subnet
+ # for which we have at least as good a route
+
+ if(!mask.eq(ip->allbits) ||
+ ((pr := findroute(dest)) == nil || metric <= pr.metric)){
+ r.valid = 1;
+ r.changed = 1;
+ r.time = now;
+ r.metric = metric;
+ r.dest = dest;
+ r.mask = mask;
+ r.gateway = gwa;
+ addroute(r);
+ }
+ }
+}
+
+sender()
+{
+ for(;;){
+ sys->sleep(OutputRate*1000); # could add some random fizz
+ sendall(OpReply, 1);
+ }
+}
+
+onlist(a: IPaddr, l: list of IPaddr): int
+{
+ for(; l != nil; l = tl l)
+ if(a.eq(hd l))
+ return 1;
+ return 0;
+}
+
+sendall(op: int, changes: int)
+{
+ for(l := nets; l != nil; l = tl l){
+ if(addrs != nil && !onlist((hd l).net, addrs))
+ continue;
+ a := (hd l).net.copy();
+ b := (ip->allbits).maskn((hd l).mask);
+ for(i := 0; i < len a.a; i++)
+ a.a[i] |= b.a[i];
+ sendroutes(hd l, a, op, changes);
+ }
+ for(i := 0; i < len routes; i++)
+ if((r := routes[i]) != nil)
+ r.changed = 0;
+}
+
+zeroentry := array[Ipdestlen] of {* => byte 0};
+
+sendroutes(ifc: ref Ifcaddr, dst: IPaddr, op: int, changes: int)
+{
+ if(debug > 1)
+ sys->print("rip: send %s\n", dst.text());
+ buf := array[Maxripmsg+IP->Udphdrlen] of byte;
+ hdr := Udphdr.new();
+ hdr.lport = hdr.rport = RIPport;
+ hdr.raddr = dst; # needn't copy
+ hdr.pack(buf, IP->Udphdrlen);
+ o := IP->Udphdrlen;
+ buf[o] = byte op;
+ buf[o+1] = byte myversion;
+ buf[o+2] = byte 0;
+ buf[o+3] = byte 0;
+ o += Riphdrlen;
+ rips := buf[IP->Udphdrlen+Riphdrlen:];
+ if(op == OpRequest){
+ buf[o:] = zeroentry;
+ ip->put4(buf, o+Ometric, HopLimit);
+ o += Ipdestlen;
+ } else {
+ # send routes
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid || changes && !r.changed)
+ continue;
+ if(r == defroute)
+ continue;
+ if(r.dest.eq(ifc.net) || isonnet(r.dest, ifc))
+ continue;
+ netmask := r.dest.classmask();
+ subnet := !r.mask.eq(netmask);
+ if(myversion < 2 && !r.mask.eq(ip->allbits)){
+ # if not a host route, don't let a subnet route leave its net
+ if(subnet && !netmask.eq(ifc.ip.classmask()))
+ continue;
+ }
+ if(o+Ipdestlen > IP->Udphdrlen+Maxripmsg){
+ if(sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "RIP write failed: %r\n");
+ o = IP->Udphdrlen + Riphdrlen;
+ }
+ buf[o:] = zeroentry;
+ ip->put2(buf, o+Otype, AF_INET);
+ buf[o+Oaddr:] = r.dest.v4();
+ ip->put4(buf, o+Ometric, r.metric);
+ if(myversion == 2 && subnet)
+ buf[o+Omask:] = r.mask.v4();
+ o += Ipdestlen;
+ }
+ }
+ if(o > IP->Udphdrlen+Riphdrlen && sys->write(netfd, buf, o) < 0)
+ sys->fprint(sys->fildes(2), "rip: network write to %s failed: %r\n", dst.text());
+}
+
+lookup(addr: IPaddr): ref Gateway
+{
+ avail := -1;
+ for(i:=0; i<len routes; i++){
+ g := routes[i];
+ if(g == nil || !g.valid){
+ if(avail < 0)
+ avail = i;
+ continue;
+ }
+ if(g.dest.eq(addr))
+ return g;
+ }
+ if(avail < 0){
+ avail = len routes;
+ a := array[len routes+Routeinc] of ref Gateway;
+ a[0:] = routes;
+ routes = a;
+ }
+ if((g := routes[avail]) == nil){
+ g = ref Gateway;
+ routes[avail] = g;
+ g.valid = 0;
+ }
+ g.dest = addr;
+ return g;
+}
+
+findroute(a: IPaddr): ref Gateway
+{
+ pr: ref Gateway;
+ for(i:=0; i<len routes; i++){
+ r := routes[i];
+ if(r == nil || !r.valid)
+ continue;
+ if(r.contains(a) && (pr == nil || !maskle(r.mask, pr.mask)))
+ pr = r; # more specific mask
+ }
+ return pr;
+}
+
+maskgen(addr: IPaddr): IPaddr
+{
+ net: ref Ifcaddr;
+ for(l := nets; l != nil; l = tl l){
+ ifc := hd l;
+ if(isonnet(addr, ifc) &&
+ (net == nil || maskle(ifc.mask, net.mask))) # less specific mask?
+ net = ifc;
+ }
+ if(net != nil)
+ return net.mask;
+ return addr.classmask();
+}
+
+isonnet(a: IPaddr, n: ref Ifcaddr): int
+{
+ return a.mask(n.mask).eq(n.net);
+}
+
+isbroadcast(a: IPaddr, mask: IPaddr): int
+{
+ h := a.maskn(mask); # host part
+ hm := (ip->allbits).maskn(mask); # host part of mask
+ return h.eq(hm);
+}
+
+iszero(a: IPaddr): int
+{
+ return a.eq(ip->v4noaddr) || a.eq(ip->noaddr);
+}
+
+maskle(a, b: IPaddr): int
+{
+ return a.mask(b).eq(a);
+}
+
+#
+# add ipdest mask gateway
+# add 0.0.0.0 0.0.0.0 gateway (default)
+# delete ipdest mask
+#
+addroute(g: ref Gateway)
+{
+ if(iszero(g.mask) && iszero(g.dest))
+ g.valid = 0; # don't change default route
+ else if(defroute != nil && defroute.gateway.eq(g.gateway)){
+ if(debug)
+ syslog(0, logfile, sys->sprint("default %s %s", g.dest.text(), g.mask.text())); # don't need a new entry
+ g.valid = 1;
+ g.changed = 1;
+ } else {
+ if(debug)
+ syslog(0, logfile, sys->sprint("add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()));
+ if(nochange || sys->fprint(routefd, "add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()) > 0){
+ g.valid = 1;
+ g.changed = 1;
+ }
+ }
+}
+
+delroute(g: ref Gateway)
+{
+ if(debug)
+ syslog(0, logfile, sys->sprint("delete %s %s", g.dest.text(), g.mask.text()));
+ if(!nochange)
+ sys->fprint(routefd, "delete %s %s", g.dest.text(), g.mask.text());
+}
+
+parseip(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parse(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+parsemask(s: string): IPaddr
+{
+ (ok, a) := IPaddr.parsemask(s);
+ if(ok < 0)
+ raise "bad route";
+ return a;
+}
+
+contains(s: string, t: string): int
+{
+ for(i := 0; i < len s; i++)
+ for(j := 0; j < len t; j++)
+ if(s[i] == t[j])
+ return 1;
+ return 0;
+}
+
+Gateway.contains(g: self ref Gateway, a: IPaddr): int
+{
+ return g.dest.eq(a.mask(g.mask));
+}
+
+riplisten(): ref Sys->FD
+{
+ addr := sys->sprint("%s/udp!*!rip", netdir);
+ (ok, c) := sys->announce(addr);
+ if(ok < 0)
+ fatal(sys->sprint("can't announce %s: %r", addr));
+ if(sys->fprint(c.cfd, "headers") < 0)
+ fatal(sys->sprint("can't set udp headers: %r"));
+ fd := sys->open(c.dir+"/data", Sys->ORDWR);
+ if(fd == nil)
+ fatal(sys->sprint("can't open %s: %r", c.dir+"/data"));
+ return fd;
+}
+
+fatal(s: string)
+{
+ syslog(0, logfile, s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/ip/sntp.b b/appl/cmd/ip/sntp.b
new file mode 100644
index 00000000..067d857d
--- /dev/null
+++ b/appl/cmd/ip/sntp.b
@@ -0,0 +1,313 @@
+implement Sntp;
+
+#
+# rfc1361 (simple network time protocol)
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr: import ip;
+
+include "timers.m";
+ timers: Timers;
+ Timer: import timers;
+
+include "arg.m";
+
+Sntp: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+Retries: con 4;
+Delay: con 3*1000; # milliseconds
+
+SNTP: adt {
+ li: int;
+ vn: int;
+ mode: int;
+ stratum: int; # level of local clock
+ poll: int; # log2(maximum interval in seconds between successive messages)
+ precision: int; # log2(seconds precision of local clock) [eg, -6 for mains, -18 for microsec]
+ rootdelay: int; # round trip delay in seconds to reference (16:16 fraction)
+ dispersion: int; # maximum error relative to primary reference
+ clockid: string; # reference clock identifier
+ reftime: big; # local time at which clock last set/corrected
+ orgtime: big; # local time at which client transmitted request
+ rcvtime: big; # time at which request arrived at server
+ xmttime: big; # time server transmitted reply
+ auth: array of byte; # auth field (ignored by this implementation)
+
+ new: fn(vn, mode: int): ref SNTP;
+ pack: fn(s: self ref SNTP): array of byte;
+ unpack: fn(a: array of byte): ref SNTP;
+};
+SNTPlen: con 4+3*4+4*8;
+
+Version: con 1; # accepted by version 2 and version 3 servers
+Stratum: con 0;
+Poll: con 0;
+LI: con 0;
+Symmetric: con 2;
+ClientMode: con 3;
+ServerMode: con 4;
+Epoch: con big 86400*big (365*70 + 17); # seconds between 1 Jan 1900 and 1 Jan 1970
+
+Microsec: con big 100000;
+
+server := "$ntp";
+stderr: ref Sys->FD;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ ip = load IP IP->PATH;
+ timers = load Timers Timers->PATH;
+
+ ip->init();
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("sntp [-d] [server]");
+
+ doset := 1;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => debug++;
+ 'i' => doset = 0;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args > 1)
+ arg->usage();
+ arg = nil;
+
+ if(args != nil)
+ server = hd args;
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ timers->init(100);
+
+ (ok, conn) := sys->dial(netmkaddr(server, "udp", "ntp"), nil);
+ if(ok < 0){
+ sys->fprint(stderr, "sntp: can't dial %s: %r\n", server);
+ raise "fail:dial";
+ }
+
+ replies := chan of ref SNTP;
+ spawn reader(conn.dfd, replies);
+
+ for(i:=0; i<Retries; i++){
+ request := SNTP.new(Version, ClientMode);
+ request.poll = 6;
+ request.orgtime = (big time() + Epoch)<<32;
+ b := request.pack();
+ if(sys->write(conn.dfd, b, len b) != len b){
+ sys->fprint(stderr, "sntp: UDP write failed: %r\n");
+ continue;
+ }
+ t := Timer.start(Delay);
+ alt{
+ reply := <-replies =>
+ t.stop();
+ if(reply == nil)
+ quit("read error");
+ if(debug){
+ sys->fprint(stderr, "LI = %d, version = %d, mode = %d\n", reply.li, reply.vn, reply.mode);
+ if(reply.stratum == 1)
+ sys->fprint(stderr, "stratum = 1 (%s), ", reply.clockid);
+ else
+ sys->fprint(stderr, "stratum = %d, ", reply.stratum);
+ sys->fprint(stderr, "poll = %d, prec = %d\n", reply.poll, reply.precision);
+ sys->fprint(stderr, "rootdelay = %d, dispersion = %d\n", reply.rootdelay, reply.dispersion);
+ }
+ if(reply.vn == 0 || reply.vn > 3)
+ continue; # unsupported version, ignored
+ if(reply.mode >= 6 || reply.mode == ClientMode)
+ continue;
+ now := ((reply.xmttime>>32)&16rFFFFFFFF) - Epoch;
+ if(now <= big 1120000000)
+ continue;
+ if(reply.li == 3 || reply.stratum == 0) # unsynchronised
+ sys->fprint(stderr, "sntp: time server not synchronised to reference time\n");
+ if(debug)
+ sys->print("%bd\n", now);
+ if(doset){
+ settime("#r/rtc", now);
+ settime("/dev/time", now * Microsec);
+ }
+ quit(nil);
+ <-t.timeout =>
+ continue;
+ }
+ }
+ sys->fprint(sys->fildes(2), "sntp: no response from server %s\n", server);
+ quit("timeout");
+}
+
+reader(fd: ref Sys->FD, replies: chan of ref SNTP)
+{
+ for(;;){
+ buf := array[512] of byte;
+ nb := sys->read(fd, buf, len buf);
+ if(nb <= 0)
+ break;
+ reply := SNTP.unpack(buf[0:nb]);
+ if(reply == nil){
+ # ignore bad replies
+ if(debug)
+ sys->fprint(stderr, "sntp: invalid reply (len %d)\n", nb);
+ continue;
+ }
+ replies <-= reply;
+ }
+ if(debug)
+ sys->fprint(stderr, "sntp: UDP read failed: %r\n");
+ replies <-= nil;
+}
+
+quit(s: string)
+{
+ pid := sys->pctl(0, nil);
+ timers->shutdown();
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ if(s != nil)
+ raise "fail:"+s;
+ exit;
+}
+
+time(): int
+{
+ fd := sys->open("#r/rtctime", Sys->OREAD);
+ if(fd == nil){
+ fd = sys->open("/dev/time", Sys->OREAD);
+ if(fd == nil)
+ return 0;
+ }
+ b := array[128] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return 0;
+ return int (big string b[0:n] / big 1000000);
+}
+
+settime(f: string, t: big)
+{
+ fd := sys->open(f, Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "%bd", t);
+}
+
+get8(a: array of byte, i: int): big
+{
+ b := big ip->get4(a, i+4) & 16rFFFFFFFF;
+ return (big ip->get4(a, i) << 32) | b;
+}
+
+put8(a: array of byte, o: int, v: big)
+{
+ ip->put4(a, o, int (v>>32));
+ ip->put4(a, o+4, int v);
+}
+
+SNTP.unpack(a: array of byte): ref SNTP
+{
+ if(len a < SNTPlen)
+ return nil;
+ s := ref SNTP;
+ mode := int a[0];
+ s.li = mode>>6;
+ s.vn = (mode>>3);
+ s.mode = mode & 3;
+ s.stratum = int a[1];
+ s.poll = int a[2];
+ if(s.poll & 16r80)
+ s.poll |= ~0 << 8;
+ s.precision = int a[3];
+ if(s.precision & 16r80)
+ s.precision |= ~0 << 8;
+ s.rootdelay = ip->get4(a, 4);
+ s.dispersion = ip->get4(a, 8);
+ if(s.stratum <= 1){
+ for(i := 12; i < 16; i++)
+ if(a[i] == byte 0)
+ break;
+ s.clockid = string a[12:i];
+ }else
+ s.clockid = sys->sprint("%d.%d.%d.%d", int a[12], int a[13], int a[14], int a[15]);
+ s.reftime = get8(a, 16);
+ s.orgtime = get8(a, 24);
+ s.rcvtime = get8(a, 32);
+ s.xmttime = get8(a, 40);
+ if(len a > SNTPlen)
+ s.auth = a[48:];
+ return s;
+}
+
+SNTP.pack(s: self ref SNTP): array of byte
+{
+ a := array[SNTPlen + len s.auth] of byte;
+ a[0] = byte ((s.li<<6) | (s.vn<<3) | s.mode);
+ a[1] = byte s.stratum;
+ a[2] = byte s.poll;
+ a[3] = byte s.precision;
+ ip->put4(a, 4, s.rootdelay);
+ ip->put4(a, 8, s.dispersion);
+ ip->put4(a, 12, 0); # clockid field
+ if(s.clockid != nil){
+ if(s.stratum <= 1){
+ b := array of byte s.clockid;
+ for(i := 0; i < len b && i < 4; i++)
+ a[12+i] = b[i];
+ }else
+ a[12:] = IPaddr.parse(s.clockid).t1.v4();
+ }
+ put8(a, 16, s.reftime);
+ put8(a, 24, s.orgtime);
+ put8(a, 32, s.rcvtime);
+ put8(a, 40, s.xmttime);
+ if(s.auth != nil)
+ a[48:] = s.auth;
+ return a;
+}
+
+SNTP.new(vn, mode: int): ref SNTP
+{
+ s := ref SNTP;
+ s.vn = vn;
+ s.mode = mode;
+ s.li = 0;
+ s.stratum = 0;
+ s.poll = 0;
+ s.precision = 0;
+ s.clockid = nil;
+ s.reftime = big 0;
+ s.orgtime = big 0;
+ s.rcvtime = big 0;
+ s.xmttime = big 0;
+ return s;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, nil) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/cmd/ip/tftpd.b b/appl/cmd/ip/tftpd.b
new file mode 100644
index 00000000..12411078
--- /dev/null
+++ b/appl/cmd/ip/tftpd.b
@@ -0,0 +1,514 @@
+implement Tftpd;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "draw.m";
+
+include "arg.m";
+
+include "ip.m";
+ ip: IP;
+ IPaddr, Udphdr: import ip;
+
+Tftpd: module
+{
+ init: fn (nil: ref Draw->Context, argv: list of string);
+};
+
+dir:= "/services/tftpd";
+net:= "/net";
+
+Tftp_READ: con 1;
+Tftp_WRITE: con 2;
+Tftp_DATA: con 3;
+Tftp_ACK: con 4;
+Tftp_ERROR: con 5;
+
+Segsize: con 512;
+
+dbg := 0;
+restricted := 0;
+port := 69;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+tftpcon: Sys->Connection;
+tftpreq: ref Sys->FD;
+
+dokill(pid: int, scope: string)
+{
+ fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE);
+ if(fd == nil)
+ fd = sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill%s", scope);
+}
+
+kill(pid: int) { dokill(pid, ""); }
+killgrp(pid: int) { dokill(pid, "grp"); }
+killme() { kill(sys->pctl(0,nil)); }
+killus() { killgrp(sys->pctl(0,nil)); }
+
+DBG(s: string)
+{
+ if(dbg)
+ sys->fprint(stderr, "tfptd: %d: %s\n", sys->pctl(0,nil), s);
+}
+
+false, true: con iota;
+
+Timer: adt {
+ KILL: con -1;
+ ALARM: con -2;
+ RETRY: con -3;
+ sig: chan of int;
+ create: fn(): ref Timer;
+ destroy: fn(t: self ref Timer);
+ set: fn(t: self ref Timer, msec, nretry: int);
+
+ ticker: fn(t: self ref Timer);
+ ticking: int;
+ wakeup: int;
+ timeout: int;
+ nretry: int;
+};
+
+Timer.create(): ref Timer
+{
+ t := ref Timer;
+ t.wakeup = 0;
+ t.ticking = false;
+ t.sig = chan of int;
+ return t;
+}
+
+Timer.destroy(t: self ref Timer)
+{
+ DBG("Timer.destroy");
+ alt {
+ t.sig <-= t.KILL =>
+ DBG("sent final msg");
+ * =>
+ DBG("couldn't send final msg");
+ }
+ DBG("Timer.destroy done");
+}
+
+Timer.ticker(t: self ref Timer)
+{
+ DBG("spawn: ticker");
+ t.ticking = true;
+ while(t.wakeup > sys->millisec()) {
+ DBG("Timer.ticker sleeping for "
+ +string (t.wakeup-sys->millisec()));
+ sys->sleep(t.wakeup-sys->millisec());
+ }
+ if(t.wakeup) {
+ DBG("Timer.ticker wakeup");
+ if(t.nretry) {
+ alt { t.sig <-= t.RETRY => ; }
+ t.ticking = false;
+ t.set(t.timeout, t.nretry-1);
+ } else
+ alt { t.sig <-= t.ALARM => ; }
+ }
+ t.ticking = false;
+ DBG("unspawn: ticker");
+}
+
+Timer.set(t: self ref Timer, msec, nretry: int)
+{
+ DBG(sys->sprint("Timer.set(%d, %d)", msec, nretry));
+ if(msec == 0) {
+ t.wakeup = 0;
+ t.timeout = 0;
+ t.nretry = 0;
+ } else {
+ t.wakeup = sys->millisec()+msec;
+ t.timeout = msec;
+ t.nretry = nretry;
+ if(!t.ticking)
+ spawn t.ticker();
+ }
+}
+
+killer(c: chan of int, pgid: int)
+{
+ DBG("spawn: killer");
+ cmd := <- c;
+ DBG(sys->sprint("killer has awakened (flag=%d)", cmd));
+ if(cmd == Timer.ALARM) {
+ killgrp(pgid);
+ DBG(sys->sprint("group %d has been killed", pgid));
+ }
+ DBG("unspawn killer");
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil);
+ stderr = sys->fildes(2);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ fatal("can't load Arg");
+
+ arg->init(args);
+ arg->setusage("tftpd [-dr] [-p port] [-h homedir] [-x network-dir]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dbg++;
+ 'h' => dir = arg->earg();
+ 'r' => restricted = 1;
+ 'p' => port = int arg->earg();
+ 'x' => net = arg->earg();
+ * => arg->usage();
+ }
+ args =arg->argv();
+ if(args != nil){
+ net = hd args;
+ args = tl args;
+ }
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ ip = load IP IP->PATH;
+ if(ip == nil)
+ fatal(sys->sprint("can't load %s: %r", IP->PATH));
+ ip->init();
+
+ if(sys->chdir(dir) < 0)
+ fatal("can't chdir to " + dir);
+
+ spawn mainthing();
+}
+
+mainthing()
+{
+ DBG("spawn: mainthing");
+ bigbuf := array[32768] of byte;
+
+ openlisten();
+ setuser();
+ for(;;) {
+ dlen := sys->read(tftpreq, bigbuf, len bigbuf);
+ if(dlen < 0)
+ fatal("listen");
+ if(dlen < Udphdrsize)
+ continue;
+
+ hdr := Udphdr.unpack(bigbuf, Udphdrsize);
+
+ raddr := sys->sprint("%s/udp!%s!%d", net, hdr.raddr.text(), hdr.rport);
+
+ DBG(sys->sprint("raddr=%s", raddr));
+ (err, cx) := sys->dial(raddr, nil);
+ if(err < 0)
+ fatal("dialing "+raddr);
+
+# showbuf("bigbuf", bigbuf[0:dlen]);
+
+ op := ip->get2(bigbuf, Udphdrsize);
+ mbuf := bigbuf[Udphdrsize+2:dlen]; # get past Udphdr and op
+ dlen -= 14;
+
+ case op {
+ Tftp_READ or Tftp_WRITE =>
+ ;
+ Tftp_ERROR =>
+ DBG("tftp error");
+ continue;
+ * =>
+ nak(cx.dfd, 4, "Illegal TFTP operation");
+ continue;
+ }
+
+# showbuf("mbuf", mbuf[0:dlen]);
+
+ i := 0;
+ while(dlen > 0 && mbuf[i] != byte 0) {
+ dlen--;
+ i++;
+ }
+
+ p := i++;
+ dlen--;
+ while(dlen > 0 && mbuf[i] != byte 0) {
+ dlen--;
+ i++;
+ }
+
+ path := string mbuf[0:p];
+ mode := string mbuf[p+1:i];
+ DBG(sys->sprint("path = %s, mode = %s", path, mode));
+
+ if(dlen == 0) {
+ nak(cx.dfd, 0, "bad tftpmode");
+ continue;
+ }
+
+ if(restricted && dodgy(path)){
+ nak(cx.dfd, 4, "Permission denied");
+ continue;
+ }
+
+ if(op == Tftp_READ)
+ spawn sendfile(cx.dfd, path, mode);
+ else
+ spawn recvfile(cx.dfd, path, mode);
+ }
+}
+
+dodgy(path: string): int
+{
+ n := len path;
+ nd := len dir;
+ if(n == 0 ||
+ path[0] == '#' ||
+ path[0] == '/' && (n < nd+1 || path[0:nd] != dir || path[nd] != '/'))
+ return 1;
+ (nil, flds) := sys->tokenize(path, "/");
+ for(; flds != nil; flds = tl flds)
+ if(hd flds == "..")
+ return 1;
+ return 0;
+}
+
+showbuf(msg: string, b: array of byte)
+{
+ sys->fprint(stderr, "%s: size %d: ", msg, len b);
+ for(i:=0; i<len b; i++)
+ sys->fprint(stderr, "%.2ux ", int b[i]);
+ sys->fprint(stderr, "\n");
+ for(i=0; i<len b; i++)
+ if(int b[i] >= 32 && int b[i] <= 126)
+ sys->fprint(stderr, " %c", int b[i]);
+ else
+ sys->fprint(stderr, " .");
+ sys->fprint(stderr, "\n");
+}
+
+sendblock(sig: chan of int, buf: array of byte, net: ref sys->FD, ksig: chan of int)
+{
+ DBG("spawn: sendblocks");
+ nbytes := 0;
+ loop: for(;;) {
+ DBG("sendblock: waiting for cmd");
+ cmd := <- sig;
+ DBG(sys->sprint("sendblock: cmd=%d", cmd));
+ case cmd {
+ Timer.KILL =>
+ DBG("sendblock: killed");
+ return;
+ Timer.RETRY =>
+ ;
+ Timer.ALARM =>
+ DBG("too many retries");
+ break loop;
+ * =>
+ nbytes = cmd;
+ }
+# showbuf("sendblock", buf[0:nbytes]);
+ ret := sys->write(net, buf, 4+nbytes);
+ DBG(sys->sprint("ret=%d", ret));
+
+ if(ret < 0) {
+ ksig <-= Timer.ALARM;
+ fatal("tftp: network write error");
+ }
+ if(ret != 4+nbytes)
+ return;
+ }
+ DBG("sendblock: exiting");
+ alt { ksig <-= Timer.ALARM => ; }
+ DBG("unspawn: sendblocks");
+}
+
+sendfile(net: ref sys->FD, name: string, mode: string)
+{
+
+ DBG(sys->sprint("spawn: sendfile: name=%s mode=%s", name, mode));
+
+ pgrp := sys->pctl(Sys->NEWPGRP, nil);
+ ack := array[1024] of byte;
+ if(name == "") {
+ nak(net, 0, "not in our database");
+ return;
+ }
+
+ file := sys->open(name, Sys->OREAD);
+ if(file == nil) {
+ DBG(sys->sprint("open failed: %s", name));
+ errbuf := sys->sprint("%r");
+ nak(net, 0, errbuf);
+ return;
+ }
+ DBG(sys->sprint("opened %s", name));
+
+ block := 0;
+ timer := Timer.create();
+ ksig := chan of int;
+ buf := array[4+Segsize] of byte;
+
+ spawn killer(ksig, pgrp);
+ spawn sendblock(timer.sig, buf, net, ksig);
+
+ mainloop: for(;;) {
+ block++;
+ buf[0:] = array[] of {byte 0, byte Tftp_DATA,
+ byte (block>>8), byte block};
+ n := sys->read(file, buf[4:], len buf-4);
+ DBG(sys->sprint("n=%d", n));
+ if(n < 0) {
+ errbuf := sys->sprint("%r");
+ nak(net, 0, errbuf);
+ break;
+ }
+ DBG(sys->sprint("signalling write of %d to block %d", n, block));
+ timer.sig <-= n;
+ for(rxl := 0; rxl < 10; rxl++) {
+
+ timer.set(1000, 15);
+ al := sys->read(net, ack, len ack);
+ timer.set(0, 0);
+ if(al < 0) {
+ timer.sig <-= Timer.ALARM;
+ break;
+ }
+ op := (int ack[0]<<8) | int ack[1];
+ if(op == Tftp_ERROR)
+ break mainloop;
+ ackblock := (int ack[2]<<8) | int ack[3];
+ DBG(sys->sprint("got ack: block=%d ackblock=%d",
+ block, ackblock));
+ if(ackblock == block)
+ break;
+ if(ackblock == 16rffff) {
+ block--;
+ break;
+ }
+ }
+ if(n < len buf-4)
+ break;
+ }
+ timer.destroy();
+ ksig <-= Timer.KILL;
+}
+
+recvfile(fd: ref sys->FD, name: string, mode: string)
+{
+ DBG(sys->sprint("spawn: recvfile: name=%s mode=%s", name, mode));
+
+ pgrp := sys->pctl(Sys->NEWPGRP, nil);
+
+ file := sys->create(name, sys->OWRITE, 8r666);
+ if(file == nil) {
+ errbuf := sys->sprint("%r");
+ nak(fd, 0, errbuf);
+ return;
+ }
+
+ block := 0;
+ ack(fd, block);
+ block++;
+
+ buf := array[8+Segsize] of byte;
+ timer := Timer.create();
+ spawn killer(timer.sig, pgrp);
+
+ for(;;) {
+ timer.set(15000, 0);
+ DBG(sys->sprint("reading block %d", block));
+ n := sys->read(fd, buf, len buf);
+ DBG(sys->sprint("read %d bytes", n));
+ timer.set(0, 0);
+
+ if(n < 0)
+ break;
+ op := int buf[0]<<8 | int buf[1];
+ if(op == Tftp_ERROR)
+ break;
+
+# showbuf("got", buf[0:n]);
+ n -= 4;
+ inblock := int buf[2]<<8 | int buf[3];
+# showbuf("hdr", buf[0:4]);
+ if(op == Tftp_DATA) {
+ if(inblock == block) {
+ ret := sys->write(file, buf[4:], n);
+ if(ret < 0) {
+ errbuf := sys->sprint("%r");
+ nak(fd, 0, errbuf);
+ break;
+ }
+ block++;
+ }
+ if(inblock < block) {
+ ack(fd, inblock);
+ DBG(sys->sprint("ok: inblock=%d block=%d",
+ inblock, block));
+ } else
+ DBG(sys->sprint("FAIL: inblock=%d block=%d",
+ inblock, block));
+ ack(fd, 16rffff);
+ if(n < 512)
+ break;
+ }
+ }
+ timer.destroy();
+}
+
+ack(fd: ref Sys->FD, block: int)
+{
+ buf := array[] of {byte 0, byte Tftp_ACK, byte (block>>8), byte block};
+# showbuf("ack", buf);
+ if(sys->write(fd, buf, 4) < 0)
+ fatal("write ack");
+}
+
+
+nak(fd: ref Sys->FD, code: int, msg: string)
+{
+sys->print("nak: %s\n", msg);
+ buf := array[128] of {byte 0, byte Tftp_ERROR, byte 0, byte code};
+ bmsg := array of byte msg;
+ buf[4:] = bmsg;
+ buf[4+len bmsg] = byte 0;
+ if(sys->write(fd, buf, 4+len bmsg+1) < 0)
+ fatal("write nak");
+}
+
+fatal(msg: string)
+{
+ sys->fprint(stderr, "tftpd: %s: %r\n", msg);
+ killus();
+ raise "fail:error";
+}
+
+openlisten()
+{
+ name := net+"/udp!*!" + string port;
+ err := 0;
+ (err, tftpcon) = sys->announce(name);
+ if(err < 0)
+ fatal("can't announce "+name);
+ if(sys->fprint(tftpcon.cfd, "headers") < 0)
+ fatal("can't set header mode");
+ sys->fprint(tftpcon.cfd, "oldheaders");
+
+ tftpreq = sys->open(tftpcon.dir+"/data", sys->ORDWR);
+ if(tftpreq == nil)
+ fatal("open udp data");
+}
+
+setuser()
+{
+ f := sys->open("/dev/user", sys->OWRITE);
+ if(f != nil)
+ sys->fprint(f, "none");
+}
+
diff --git a/appl/cmd/ip/virgild.b b/appl/cmd/ip/virgild.b
new file mode 100644
index 00000000..29ebba67
--- /dev/null
+++ b/appl/cmd/ip/virgild.b
@@ -0,0 +1,127 @@
+implement Virgild;
+
+include "sys.m";
+sys: Sys;
+
+include "draw.m";
+
+include "ip.m";
+
+Virgild: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+Udphdrsize: con IP->OUdphdrlen;
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
+ if(sys->chdir("/lib/ndb") < 0){
+ sys->fprint(stderr, "virgild: no database\n");
+ return;
+ }
+
+ for(;;sys->sleep(10*1000)){
+ fd := openlisten();
+ if(fd == nil)
+ return;
+
+ buf := array[512] of byte;
+ for(;;){
+ n := sys->read(fd, buf, len buf);
+ if(n <= Udphdrsize){
+ break;
+ }
+ if(n <= Udphdrsize+1)
+ continue;
+
+ # dump any cruft after the question
+ for(i := Udphdrsize; i < n; i++){
+ c := int buf[i];
+ if(c == ' ' || c == 0 || c == '\n')
+ break;
+ }
+
+ answer := query(string buf[Udphdrsize:i]);
+ if(answer == nil)
+ continue;
+
+ # reply
+ r := array of byte answer;
+ if(len r > len buf - Udphdrsize)
+ continue;
+ buf[Udphdrsize:] = r;
+ sys->write(fd, buf, Udphdrsize+len r);
+ }
+ fd = nil;
+ }
+}
+
+openlisten(): ref Sys->FD
+{
+ (ok, c) := sys->announce("udp!*!virgil");
+ if(ok < 0){
+ sys->fprint(stderr, "virgild: can't open port: %r\n");
+ return nil;
+ }
+
+ if(sys->fprint(c.cfd, "headers") <= 0){
+ sys->fprint(stderr, "virgild: can't set headers: %r\n");
+ return nil;
+ }
+ sys->fprint(c.cfd, "oldheaders");
+
+ c.dfd = sys->open(c.dir+"/data", Sys->ORDWR);
+ if(c.dfd == nil) {
+ sys->fprint(stderr, "virgild: can't open data file\n");
+ return nil;
+ }
+ return c.dfd;
+}
+
+#
+# query is userid?question
+#
+# for now, we're ignoring userid
+#
+query(request: string): string
+{
+ (n, l) := sys->tokenize(request, "?");
+ if(n < 2){
+ sys->fprint(stderr, "virgild: bad request %s %d\n", request, n);
+ return nil;
+ }
+
+ #
+ # until we have something better, ask cs
+ # to translate, make the request look cs-like
+ #
+ fd := sys->open("/net/cs", Sys->ORDWR);
+ if(fd == nil){
+ sys->fprint(stderr, "virgild: can't open /net/cs - %r\n");
+ return nil;
+ }
+ q := array of byte ("tcp!" + hd(tl l) + "!1000");
+ if(sys->write(fd, q, len q) < 0){
+ sys->fprint(stderr, "virgild: can't write /net/cs - %r: %s\n", string q);
+ return nil;
+ }
+ sys->seek(fd, big 0, 0);
+ buf := array[512-Udphdrsize-len request-1] of byte;
+ n = sys->read(fd, buf, len buf);
+ if(n <= 0){
+ sys->fprint(stderr, "virgild: can't read /net/cs - %r\n");
+ return nil;
+ }
+
+ (nil, l) = sys->tokenize(string buf[0:n], " \t");
+ (nil, l) = sys->tokenize(hd(tl l), "!");
+ return request + "=" + hd l;
+}