code: 9ferno

ref: b548687a8ed1d0a159c9d3f3f921d93bbb56908e
dir: /appl/lib/ipval.b/

View raw version
implement Ipval;

include "sys.m";
	sys: Sys;
	sprint: import sys;
include "bufio.m";
include "ip.m";
	ip: IP;
	IPaddr: import ip;
include "attrdb.m";
	attrdb: Attrdb;
	Db, Dbptr, Dbentry: import attrdb;
include "ipval.m";


init()
{
	if(sys != nil)
		return;

	sys = load Sys Sys->PATH;
	ip = load IP IP->PATH;
	ip->init();
	attrdb = load Attrdb Attrdb->PATH;
	attrdb->init();
	Db.open("/dev/null");  # force bufio to load...
}

findval(db: ref Db, ipaddr: string, rattr: string): (string, string)
{
	init();

	(l, err) := findvals(db, ipaddr, rattr::nil);
	v: string;
	if(err == nil && len l == 1)
		v = (hd l).t1;
	return (v, err);
}

has(l: list of (string, string), k: string): int
{
	for(; l != nil; l = tl l)
		if((hd l).t0 == k)
			return 1;
	return 0;
}

dbipval(db: ref Db, ipaddr: string, attr: string): string
{
	(e, nil) := db.findbyattr(nil, "ip", ipaddr, attr);
	if(e != nil)
		v := e.findfirst(attr);
	return v;
}

dbipnetmatch(e: ref Dbentry, ipa: IPaddr): array of byte
{
	eipaddr := e.findfirst("ip");
	eipmask := e.findfirst("ipmask");
	(ok0, eipa) := IPaddr.parse(eipaddr);
	(ok1, eipm) := IPaddr.parsemask(eipmask);
	if(ok0 < 0 || ok1 < 0)
		return nil;
	if(!ipa.mask(eipm).eq(eipa))
		return nil;
	return eipm.v6();
}

# compare the mask.  a >= b when a's mask is less specific than b's:  when the first different byte of the mask has a lower value.
# if the masks are the same, we use the position in the ndb file.
v6maskge(a, b: ref (int, array of byte, ref Dbentry)): int
{
	ma := a.t1;
	mb := b.t1;

	for(i := 0; i < len ma; i++)
		if(ma[i] != mb[i])
			return ma[i] <= mb[i];
	return a.t0 >= b.t0;
}

dbipnets(db: ref Db, ipaddr: string): array of ref Dbentry
{
	l: list of ref (int, array of byte, ref Dbentry);  # seq, v6 mask, db entry

	(ok, ipa) := IPaddr.parse(ipaddr);
	if(ok < 0)
		return nil;

	next: ref Dbptr;
	seq := 0;
	for(;;) {
		e: ref Dbentry;
		(e, next) = db.find(next, "ipnet");
		if(e != nil) {
			v6m := dbipnetmatch(e, ipa);
			if(v6m != nil)
				l = ref (seq++, v6m, e)::l;
		}
		if(e == nil || next == nil)
			break;
	}

	# use seq & v6 mask to sort, then extract db entries
	a := l2a(l);
	sort(a, v6maskge);
	r := array[len a] of ref Dbentry;
	for(i := 0; i < len a; i++)
		r[i] = a[i].t2;
	return r;
}

findvals(db: ref Db, ipaddr: string, rattrs: list of string): (list of (string, string), string)
{
	init();

	r: list of (string, string);

	# first look for explict matches to ip address
	for(l := rattrs; l != nil; l = tl l) {
		v := dbipval(db, ipaddr, hd l);
		if(v != nil)
			r = (hd l, v)::r;
	}

	# take remaining attributes from matching ipnet entries
	ipn := dbipnets(db, ipaddr);
	for(i := 0; len r < len rattrs && i < len ipn; i++) {
		dbe := ipn[i];
		for(l = rattrs; l != nil; l = tl l) {
			v := dbe.findfirst(hd l);
			if(v != nil && !has(r, hd l))
				r = (hd l, v)::r;
		}
	}

	return (r, nil);
}

l2a[T](l: list of T): array of T
{
	a := array[len l] of T;
	i := 0;
	for(; l != nil; l = tl l)
		a[i++] = hd l;
	return a;
}

sort[T](a: array of T, ge: ref fn(a, b: T): int)
{
	for(i := 1; i < len a; i++) {
		tmp := a[i];
		for(j := i; j > 0 && ge(a[j-1], tmp); j--)
			a[j] = a[j-1];
		a[j] = tmp;
	}
}