diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/ip | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/ip')
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; +} |
