Plan 9 from Bell Labs’s /usr/web/sources/contrib/rog/socksnet.b

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


implement Socksnet;
include "sys.m";
	sys: Sys;
	Qid: import Sys;
include "draw.m";
include "tables.m";
	tables: Tables;
	Table: import tables;
include "string.m";
	str: String;
include "styx.m";
	styx: Styx;
	Rmsg, Tmsg: import styx;
include "styxservers.m";
	styxservers: Styxservers;
	Fid, Styxserver, Navigator, Navop, Eperm: import styxservers;
	readstr: import styxservers;
	nametree: Nametree;
	Tree: import nametree;

include "ip.m";
	ip: IP;
	IPaddr: import ip;

Socksnet: module {
	init: fn(nil: ref Draw->Context, argv: list of string);
};
Cvshift: con 4;

Qroot, Qtopdir, Qclone, Qconv,
Qdata, Qremote, Qlocal, Qstatus, Qctl: con iota;

server: string;

nomod(p: string)
{
	sys->fprint(sys->fildes(2), "socksnet: cannot load %s: %r\n", p);
	raise "fail:bad module";
}

init(nil: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	tables = load Tables Tables->PATH;
	str = load String String->PATH;
	styx = load Styx Styx->PATH;
	if(styx == nil)
		nomod(Styx->PATH);
	styx->init();
	styxservers = load Styxservers Styxservers->PATH;
	if(styxservers == nil)
		nomod(Styxservers->PATH);
	styxservers->init(styx);
	nametree = load Nametree Nametree->PATH;
	nametree->init();
	ip = load IP IP->PATH;
	ip->init();

	if(len argv != 2){
		sys->fprint(sys->fildes(2), "usage: socksnet socks_server\n");
		raise "fail:usage";
	}
	sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
	server = hd tl argv;

	(tree, treeop) := nametree->start();
	p := qmk(Qroot, 0);
	tree.create(p, dir(".", 8r555|Sys->DMDIR, qmk(Qroot, 0)));
	tree.create(p, dir("socks", 8r555|Sys->DMDIR, qmk(Qtopdir, 0)));
	tree.create(qmk(Qtopdir, 0), dir("clone", 8r666, qmk(Qclone, 0)));
	(tchan, srv) := Styxserver.new(sys->fildes(0), Navigator.new(treeop), p);
	serveloop(tchan, tree, srv);
	tree.quit();
}

Req: adt {
	pid: int;
};

Conv: adt {
	id: int;
	fd: ref Sys->FD;
	local: string;
	remote: string;
	refs: int;
};

serveloop(tchan: chan of ref Tmsg, tree: ref Tree, srv: ref Styxserver)
{
	blocking := Table[ref Req].new(11, nil);
	convs := Table[ref Conv].new(11, nil);
	freeconvs: list of ref Conv;
	maxcid := 0;
	replies := chan of ref Rmsg;
	for(;;)alt{
	r := <-replies =>
		blocking.del(r.tag);
		srv.reply(r);
	gm := <-tchan =>
		if(gm == nil)
			return;
		pick m := gm {
		Flush =>
			req := blocking.find(m.oldtag);
			if(req != nil)
				kill(req.pid);
			srv.reply(ref Rmsg.Flush(m.tag));
		Open =>
			(fid, mode, nil, err) := srv.canopen(m);
			if(fid == nil){
				srv.reply(ref Rmsg.Error(m.tag, err));
				break;
			}
			case qtype(fid.path){
			Qclone =>
				nc: ref Conv;
				if(freeconvs == nil){
					nc = ref Conv(maxcid++, nil, nil, nil, 0);
					convs.add(nc.id, nc);
					p := qmk(Qconv, nc.id);
					tree.create(qmk(Qtopdir, 0), dir(string nc.id, 8r555|Sys->DMDIR, p));
					tree.create(p, dir("ctl", 8r666, qmk(Qctl, nc.id)));
					tree.create(p, dir("data", 8r666, qmk(Qdata, nc.id)));
					tree.create(p, dir("remote", 8r444, qmk(Qremote, nc.id)));
					tree.create(p, dir("local", 8r444, qmk(Qlocal, nc.id)));
					tree.create(p, dir("status", 8r444, qmk(Qstatus, nc.id)));
				}else
					(nc, freeconvs) = (hd freeconvs, tl freeconvs);
				fid.open(mode, Qid(qmk(Qctl, nc.id), 0, 0));
				srv.reply(ref Rmsg.Open(m.tag, Qid(fid.path, 0, fid.qtype), 0));
				nc.refs++;
			Qdata =>
				conv := convs.find(qconv(fid.path));
				if(conv.fd == nil)
					srv.reply(ref Rmsg.Error(m.tag, "no convection"));
				else{
					srv.default(gm);
					conv.refs++;
				}
			* =>
				srv.default(gm);
			}
		Write =>
			(fid, err) := srv.canwrite(m);
			if(fid == nil){
				srv.reply(ref Rmsg.Error(m.tag, err));
				break;
			}
			case qtype(fid.path) {
			Qctl =>
				t := str->unquoted(string m.data);
				if(t == nil){
					srv.reply(ref Rmsg.Error(m.tag, "no request"));
					break;
				}
				case hd t {
				"connect" =>
					spawn connectproc(sync := chan of int, m, hd tl t, convs.find(qconv(fid.path)), replies);
					blocking.add(m.tag, ref Req(<-sync));
				* =>
					srv.reply(ref Rmsg.Error(m.tag, "unknown request"));
				}
			Qdata =>
				spawn writeproc(sync := chan of int, m, convs.find(qconv(fid.path)), replies);
				blocking.add(m.tag, ref Req(<-sync));
			* =>
				srv.reply(ref Rmsg.Error(m.tag, Eperm));
			}
		Read =>
			(fid, err) := srv.canread(m);
			if(fid == nil){
				srv.reply(ref Rmsg.Error(m.tag, err));
				break;
			}
			case qtype(fid.path) {
			Qctl =>
				srv.reply(readstr(m, string convs.find(qconv(fid.path)).id));
			Qdata =>
				spawn readproc(sync := chan of int, m, convs.find(qconv(fid.path)), replies);
				blocking.add(m.tag, ref Req(<-sync));
			Qstatus =>
				s := "Open";
				if(convs.find(qconv(fid.path)).fd == nil)
					s = "Closed";
				
				srv.reply(readstr(m, s));
			* =>
				srv.default(m);
			}
		Clunk =>
			fid := srv.getfid(m.fid);
			if(fid == nil){
				srv.reply(ref Rmsg.Error(m.tag, "bad fid"));
				break;
			}
			case qtype(fid.path) {
			Qdata or
			Qctl =>
				conv := convs.find(qconv(fid.path));
				if(--conv.refs == 0){
					conv.fd = nil;
					freeconvs = conv :: freeconvs;
				}
			}
			srv.default(gm);
		* =>
			srv.default(gm);
		}
	}
}

connectproc(sync: chan of int, m: ref Tmsg.Write, addr: string, conv: ref Conv, r: chan of ref Rmsg)
{
	sync <-= sys->pctl(0, nil);
	err := connect(conv, "tcp!"+addr);
	if(err != nil)
		r <-= ref Rmsg.Error(m.tag, err);
	else
		r <-= ref Rmsg.Write(m.tag, len m.data);
}

connect(conv: ref Conv, addr: string): string
{
	ipaddr, ipport: array of byte;

	(nil, ipa, err) := csquery(addr);
	if(ipa == nil)
		return err;
	(ipaddr, ipport, err) = mkipaddr(ipa);
	if(err != nil)
		return err;
	(rc, c) := dial(netmkaddr(server, "tcp", "socks"));
	if(rc == -1)
		return sys->sprint("cannot dial socks server: %r");
	d := array[1+1+2+4+1] of byte;
	d[0] = byte 4;		# version
	d[1] = byte 1;		# tcp
	d[2:] = ipport;
	d[4:] = ipaddr;
	d[8] = byte 0;		# username
	if(sys->write(c.dfd, d, len d) < 0)
		return (sys->sprint("write: %r"));

	d = array[8] of byte;
	n := sys->read(c.dfd, d, len d);
	if(n != 8)
		return "too few bytes in socks reply";

	if(d[0] != byte 0 || d[1] != byte 16r5a)
		return "request rejected";
	conv.fd = c.dfd;
	return nil;
}

writeproc(sync: chan of int, m: ref Tmsg.Write, conv: ref Conv, r: chan of ref Rmsg)
{
	sync <-= sys->pctl(0, nil);
	n := sys->write(conv.fd, m.data, len m.data);
	if(n < 0)
		r <-= ref Rmsg.Error(m.tag, sys->sprint("%r"));
	else
		r <-= ref Rmsg.Write(m.tag, n);
}

readproc(sync: chan of int, m: ref Tmsg.Read, conv: ref Conv, r: chan of ref Rmsg)
{
	sync <-= sys->pctl(0, nil);
	data := array[m.count] of byte;
	n := sys->read(conv.fd, data, m.count);
	if(n < 0)
		r <-= ref Rmsg.Error(m.tag, sys->sprint("%r"));
	else
		r <-= ref Rmsg.Read(m.tag, data[0:n]);
}

mkipaddr(addr: string): (array of byte, array of byte, string)
{
	(nt, toks) := sys->tokenize(addr, "!");
	if(nt != 2)
		return (nil, nil, "bad address");
	(ok, ipaddr) := IPaddr.parse(hd toks);
	if(ok < 0)
		return (nil, nil, "bad ip address");

	pn := int hd tl toks;
	port := array[2] of byte;
	port[0] = byte (pn >> 8);
	port[1] = byte pn;
	return (ipaddr.v4(), port, nil);
}

csquery(addr: string): (string, string, string)
{
	fd := sys->open("/net/cs", Sys->ORDWR);
	if(fd == nil){
		(nt, toks) := sys->tokenize(addr, "!");
		if(nt != 3)
			return (nil, nil, "bad address");
		return ("/net/"+hd toks, hd tl toks + "!" + hd tl tl toks, nil);
	}

	if(sys->fprint(fd, "%s", addr) == -1)
		return (nil, nil, sys->sprint("%r"));
	sys->seek(fd, big 0, Sys->SEEKSTART);
	buf := array[100] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0)
		return (nil, nil, sys->sprint("%r"));
	buf = buf[0:n];
	(nt, toks) := sys->tokenize(string buf[0:n], " ");
	if(nt != 2)
		return (nil, nil, "bad tokens from cs");
	return (hd toks, hd tl toks, nil);
}

reads(fd: ref Sys->FD): (int, string)
{
	buf := array[100] of byte;
	n := sys->read(fd, buf, len buf);
	if(n == -1)
		return (-1, nil);
	return (0, string buf[0:n]);
}

dropsuffix(s: string, suf: string): (int, string)
{
	if(len suf > len s)
		return (0, s);
	if(s[len s - len suf:] != suf)
		return (0, s);
	return (1, s[0:len s - len suf]);
}
	

# same as sys->dial, except ignore the network part of the csquery response
# and always make a tcp connection.
dial(addr: string): (int, Sys->Connection)
{
	c: Sys->Connection;
	(dir, ipa, err) := csquery(addr);
	if(err != nil){
		sys->werrstr(err);
		return (-1, c);
	}
	# XXX should honour non /net, non-socks net directories
	dir = "/net/tcp";
	c.cfd = sys->open(dir+"/clone", Sys->ORDWR);
	if(c.cfd == nil)
		return (-1, c);
	if(sys->fprint(c.cfd, "connect %s", ipa) == -1)
		return (-1, c);
	sys->seek(c.cfd, big 0, Sys->SEEKSTART);
	(r, s) := reads(c.cfd);
	if(r == -1)
		return (-1, c);
	c.dfd = sys->open(dir+"/"+string int s+"/data", Sys->ORDWR);
	if(c.dfd == nil)
		return (-1, c);
	return (0, c);
}

kill(pid: int)
{
	sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
}

qtype(qid: big): int
{
	return int qid & ((1 << Cvshift) - 1);
}

qconv(qid: big): int
{
	return int qid >> Cvshift;
}

qmk(qtype, qconv: int): big
{
	return big ((qconv << Cvshift) | qtype);
}

dir(name: string, perm: int, qid: big): Sys->Dir
{
	d := sys->zerodir;
	d.name = name;
	d.uid = "me";
	d.gid = "me";
	d.qid.path = qid;
	if (perm & Sys->DMDIR)
		d.qid.qtype = Sys->QTDIR;
	else
		d.qid.qtype = Sys->QTFILE;
	d.mode = perm;
	return d;
}

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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.