-# PostgreSQL type definitions for IP and MAC addresses.
+#
+# PostgreSQL types for IP and MAC addresses
+#
+# $Id: Makefile,v 1.2 1998/02/14 17:58:02 scrappy Exp $
all: ip.so mac.so
install: ip.so mac.so
install -c ip.so mac.so /usr/local/pgsql/modules
+#
# eof
+#
PostgreSQL type extensions for IP and MAC addresses.
---------------------------------------------------
+$Id: README,v 1.2 1998/02/14 17:58:03 scrappy Exp $
+
I needed to record IP and MAC level ethernet addresses in a data
base, and I really didn't want to store them as plain strings, with
no enforced error checking, so I put together the accompanying code
as a very simple example of how to do this sort of thing, so here
it is, in the hope that it will be useful.
-IP addresses are implemented as an 8 byte struct (this may well be
+IP addresses are implemented as a 6 byte struct (this may be 1 byte
more than is useful, but I figured that since it has to be at least 5,
-it might as well round well) that contains the four bytes of address
-and a mask width. Thus, a node address looks like '158.37.96.15/32'
-(or just '158.37.96.15', which is understood to mean the same thing).
-This address happens to be part of a subnet where I work;
-'158.37.96.0/24', which itself is a part of the larger subnet
-allocated to our institution, which is '158.37.96.0/21', which again,
-if you go by the book, is part of the class "B" net '158.37.0.0/16'.
+it might as well be an even number of bytes) that contains the four
+byte address and a mask width. The external representation of an IP
+address looks like '158.37.96.15/32' (or just '158.37.96.15', which is
+understood to mean the same thing). This address happens to be part
+of a subnet where I work; '158.37.96.0/24', which itself is a part of
+the larger subnet allocated to our site, which is '158.37.96.0/21',
+which again, if you go by the old book, is part of the class "B" net
+called '158.37.0.0/16'.
Input and output functions are supplied, along with the "normal" <,
-<=, =, >=, > and <> operators, which all do what you expect, and the
-similarity operator ~~, which checks whether two given addresses are
-either the same, or, failing that, whether one is a subnet
-specification and the other an address (or a smaller subnet) within
-that. Good for picking out records with addresses in a given subnet:
-note that '158.37.96.0/21' spans '158.37.96.0' to '158.37.103.255',
-which is not all that easily handled in its external representation.
-
-MAC level ethernet addresses are also implemented as an 8 byte struct
-(I wish I knew what alignment needs are actually present -- I'm just
-not taking any chances here) that contains the address as unsigned
-chars. Several input forms are accepted: the following are all the
-same address: '08002b:010203', '08002b-010203', '0800.2b01.0203',
-'08-00-2b-01-02-03' and '08:00:2b:01:02:03'. Upper and lower case is
-accepted for the digits 'a' through 'f'. Output is always in the
-latter of the given forms.
-
-Input and output functions are supplied, along with the = and <>
-operators, which do what you expect, and the similarity operator ~~,
-which checks whether two given addresses belong to hardware from the
-same manufacturer (first three bytes the same, that is). As an extra
+<=, =, >=, > and <> operators, which all do what you expect. In
+addition, there is a function to check whether a given address is a
+member of a given subnet: ipaddr_in_net(addr, net), and functions to
+return the netmask and the broadcast address of a given network:
+ipaddr_mask(net) and ipaddr_bcast(net).
+
+MAC level ethernet addresses are implemented as a 6 byte struct that
+contains the address as unsigned chars. Several input forms are
+accepted; the following are all the same address: '08002b:010203',
+'08002b-010203', '0800.2b01.0203', '08-00-2b-01-02-03' and
+'08:00:2b:01:02:03'. Upper and lower case is accepted for the digits
+'a' through 'f'. Output is always in the latter of the given forms.
+
+As with IP addresses, input and output functions are supplied as well
+as the "normal" operators, which do what you expect. As an extra
feature, a function macaddr_manuf() is defined, which returns the name
-of the manufacturer as a string.
+of the manufacturer as a string. This is currently held in a
+hard-coded struct internal to the C module -- it might be smarter to
+put this information into an actual data base table, and look up the
+manufacturer there. (Another TODO, for both new data types, is to
+interface them to indices. If anyone can explain this to me in a way
+that is easier to understand than the current documentation, I would
+be most grateful!)
-To install: fix the path names in the SQL files and the Makefile if
-you need to, then make, make install, slurp the SQL files into psql or
+I don't know what changes are needed to the Makefile for other systems
+than the one I'm running (NetBSD 1.3), but anyway: to install on a BSD
+system: fix the path names in the SQL files and the Makefile if you
+need to, then make, make install, slurp the SQL files into psql or
whatever, and you're off. Enjoy!
-Bergen, Norway, 1998-01-11, Tom Ivar Helbekkmo (tih@Hamartun.Priv.NO).
+Bergen, Norway, 1998-01-31, Tom Ivar Helbekkmo (tih@Hamartun.Priv.NO).
/*
* PostgreSQL type definitions for IP addresses.
+ *
+ * $Id: ip.c,v 1.2 1998/02/14 17:58:03 scrappy Exp $
*/
#include <stdio.h>
*/
typedef struct ipaddr {
- unsigned char a;
- unsigned char b;
- unsigned char c;
- unsigned char d;
- unsigned char w;
- unsigned char pad1;
- short pad2;
+ uint32 address;
+ int16 width;
} ipaddr;
/*
bool ipaddr_gt(ipaddr *a1, ipaddr *a2);
bool ipaddr_ne(ipaddr *a1, ipaddr *a2);
+
int4 ipaddr_cmp(ipaddr *a1, ipaddr *a2);
-bool ipaddr_like(ipaddr *a1, ipaddr *a2);
+
+bool ipaddr_in_net(ipaddr *a1, ipaddr *a2);
+ipaddr *ipaddr_mask(ipaddr *a);
+ipaddr *ipaddr_bcast(ipaddr *a);
/*
- * A utility macro used for sorting addresses numerically:
+ * Build a mask of a given width:
*/
-#define Mag(addr) \
- ((unsigned long)((addr->a<<24)|(addr->b<<16)|(addr->c<<8)|(addr->d)))
+unsigned long build_mask(unsigned char bits) {
+ unsigned long mask = 0;
+ int i;
+ for (i = 0; i < bits; i++)
+ mask = (mask >> 1) | 0x80000000;
+ return mask;
+}
/*
* IP address reader. Note how the count returned by sscanf()
result = (ipaddr *)palloc(sizeof(ipaddr));
- result->a = a;
- result->b = b;
- result->c = c;
- result->d = d;
- result->w = w;
+ result->address = (uint32) ((a<<24)|(b<<16)|(c<<8)|d);
+ result->address &= build_mask(w);
+ result->width = w;
return(result);
}
result = (char *)palloc(32);
- if (Mag(addr) > 0) {
- if (addr->w == 32)
+ if (addr->address > 0) {
+ if (addr->width == 32)
sprintf(result, "%d.%d.%d.%d",
- addr->a, addr->b, addr->c, addr->d);
+ (addr->address >> 24) & 0xff,
+ (addr->address >> 16) & 0xff,
+ (addr->address >> 8) & 0xff,
+ addr->address & 0xff);
else
sprintf(result, "%d.%d.%d.%d/%d",
- addr->a, addr->b, addr->c, addr->d, addr->w);
+ (addr->address >> 24) & 0xff,
+ (addr->address >> 16) & 0xff,
+ (addr->address >> 8) & 0xff,
+ addr->address & 0xff,
+ addr->width);
} else {
result[0] = 0; /* special case for missing address */
}
}
/*
- * Boolean tests. The Mag() macro was defined above.
+ * Boolean tests for magnitude.
*/
bool ipaddr_lt(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return (a1mag < a2mag);
+ return (a1->address < a2->address);
};
bool ipaddr_le(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return (a1mag <= a2mag);
+ return (a1->address <= a2->address);
};
bool ipaddr_eq(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return ((a1mag == a2mag) && (a1->w == a2->w));
+ return (a1->address == a2->address);
};
bool ipaddr_ge(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return (a1mag >= a2mag);
+ return (a1->address >= a2->address);
};
bool ipaddr_gt(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return (a1mag > a2mag);
+ return (a1->address > a2->address);
};
bool ipaddr_ne(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag, a2mag;
- a1mag = Mag(a1);
- a2mag = Mag(a2);
- return ((a1mag != a2mag) || (a1->w != a2->w));
+ return (a1->address != a2->address);
};
/*
*/
int4 ipaddr_cmp(ipaddr *a1, ipaddr *a2) {
- unsigned long a1mag = Mag(a1), a2mag = Mag(a2);
- if (a1mag < a2mag)
+ if (a1->address < a2->address)
return -1;
- else if (a1mag > a2mag)
+ else if (a1->address > a2->address)
return 1;
else
return 0;
}
/*
- * Our "similarity" operator checks whether two addresses are
- * either the same node address, or, failing that, whether one
- * of them contains the other. This will be true if they have
- * the same high bits down as far as the shortest mask reaches.
+ * Test whether an address is within a given subnet:
*/
-unsigned long build_mask(unsigned char bits) {
- unsigned long mask = 0;
- int i;
- for (i = 0; i < bits; i++)
- mask = (mask >> 1) | 0x80000000;
- return mask;
-}
-
-bool ipaddr_like(ipaddr *a1, ipaddr *a2) {
- unsigned long a1bits, a2bits, maskbits;
- if ((a1->w == 0) || (a2->w == 0))
+bool ipaddr_in_net(ipaddr *a1, ipaddr *a2) {
+ uint32 maskbits;
+ if (a1->width < a2->width)
return FALSE;
- if ((a1->w == 32) && (a2->w == 32))
+ if ((a1->width == 32) && (a2->width == 32))
return ipaddr_eq(a1, a2);
- a1bits = Mag(a1);
- a2bits = Mag(a2);
- if (a1->w > a2->w) {
- maskbits = build_mask(a2->w);
- return ((a1bits & maskbits) == (a2bits & maskbits));
- } else {
- maskbits = build_mask(a1->w);
- return ((a2bits & maskbits) == (a1bits & maskbits));
- }
+ maskbits = build_mask(a2->width);
+ if ((a1->address & maskbits) == (a2->address & maskbits))
+ return TRUE;
return FALSE;
}
+/*
+ * Pick out just the mask of a network:
+ */
+
+ipaddr *ipaddr_mask(ipaddr *a) {
+ ipaddr *result;
+
+ result = (ipaddr *)palloc(sizeof(ipaddr));
+ result->address = build_mask(a->width);
+ result->width = 32;
+
+ return result;
+}
+
+/*
+ * Return the broadcast address of a network:
+ */
+
+ipaddr *ipaddr_bcast(ipaddr *a) {
+ ipaddr *result;
+
+ result = (ipaddr *)palloc(sizeof(ipaddr));
+ result->address = a->address;
+ result->address |= (build_mask(32 - a->width) >> a->width);
+ result->width = 32;
+
+ return result;
+}
+
/*
* eof
*/
--
-- PostgreSQL code for IP addresses.
--
+-- $Id: ip.sql,v 1.2 1998/02/14 17:58:04 scrappy Exp $
+--
load '/usr/local/pgsql/modules/ip.so';
language 'c';
create type ipaddr (
- internallength = 8,
+ internallength = 6,
externallength = variable,
input = ipaddr_in,
output = ipaddr_out
as '/usr/local/pgsql/modules/ip.so'
language 'c';
-create function ipaddr_like(ipaddr, ipaddr)
+create function ipaddr_in_net(ipaddr, ipaddr)
returns bool
as '/usr/local/pgsql/modules/ip.so'
language 'c';
+create function ipaddr_mask(ipaddr)
+ returns ipaddr
+ as '/usr/local/pgsql/modules/ip.so'
+ language 'c';
+
+create function ipaddr_bcast(ipaddr)
+ returns ipaddr
+ as '/usr/local/pgsql/modules/ip.so'
+ language 'c';
+
--
-- Now the operators. Note how some of the parameters to some
-- of the 'create operator' commands are commented out. This
-- will be implicitly defined when those are, further down.
--
-create operator <= (
+create operator < (
leftarg = ipaddr,
rightarg = ipaddr,
--- commutator = >,
--- negator = >,
- procedure = ipaddr_le
+-- negator = >=,
+ procedure = ipaddr_lt
);
-create operator < (
+create operator <= (
leftarg = ipaddr,
rightarg = ipaddr,
--- commutator = >=,
--- negator = >=,
- procedure = ipaddr_lt
+-- negator = >,
+ procedure = ipaddr_le
);
create operator = (
create operator >= (
leftarg = ipaddr,
rightarg = ipaddr,
- commutator = <,
negator = <,
procedure = ipaddr_ge
);
create operator > (
leftarg = ipaddr,
rightarg = ipaddr,
- commutator = <=,
negator = <=,
procedure = ipaddr_gt
);
create operator <> (
leftarg = ipaddr,
rightarg = ipaddr,
- commutator = <>,
negator = =,
procedure = ipaddr_ne
);
-create operator ~~ (
- leftarg = ipaddr,
- rightarg = ipaddr,
- commutator = ~~,
- procedure = ipaddr_like
-);
-
--
-- eof
--
/*
* PostgreSQL type definitions for MAC addresses.
+ *
+ * $Id: mac.c,v 1.2 1998/02/14 17:58:05 scrappy Exp $
*/
#include <stdio.h>
unsigned char d;
unsigned char e;
unsigned char f;
- short pad;
} macaddr;
/*
macaddr *macaddr_in(char *str);
char *macaddr_out(macaddr *addr);
+bool macaddr_lt(macaddr *a1, macaddr *a2);
+bool macaddr_le(macaddr *a1, macaddr *a2);
bool macaddr_eq(macaddr *a1, macaddr *a2);
+bool macaddr_ge(macaddr *a1, macaddr *a2);
+bool macaddr_gt(macaddr *a1, macaddr *a2);
+
bool macaddr_ne(macaddr *a1, macaddr *a2);
int4 macaddr_cmp(macaddr *a1, macaddr *a2);
-bool macaddr_like(macaddr *a1, macaddr *a2);
+
text *macaddr_manuf(macaddr *addr);
/*
* Utility macros used for sorting and comparing:
*/
-#define MagM(addr) \
+#define hibits(addr) \
((unsigned long)((addr->a<<16)|(addr->b<<8)|(addr->c)))
-#define MagH(addr) \
+#define lobits(addr) \
((unsigned long)((addr->c<<16)|(addr->e<<8)|(addr->f)))
/*
result = (char *)palloc(32);
- if ((MagM(addr) > 0) || (MagH(addr) > 0)) {
+ if ((hibits(addr) > 0) || (lobits(addr) > 0)) {
sprintf(result, "%02x:%02x:%02x:%02x:%02x:%02x",
addr->a, addr->b, addr->c, addr->d, addr->e, addr->f);
} else {
* Boolean tests.
*/
+bool macaddr_lt(macaddr *a1, macaddr *a2) {
+ return((hibits(a1) < hibits(a2)) ||
+ ((hibits(a1) == hibits(a2)) && lobits(a1) < lobits(a2)));
+};
+
+bool macaddr_le(macaddr *a1, macaddr *a2) {
+ return((hibits(a1) < hibits(a2)) ||
+ ((hibits(a1) == hibits(a2)) && lobits(a1) <= lobits(a2)));
+};
+
bool macaddr_eq(macaddr *a1, macaddr *a2) {
- return((a1->a == a2->a) && (a1->b == a2->b) &&
- (a1->c == a2->c) && (a1->d == a2->d) &&
- (a1->e == a2->e) && (a1->f == a2->f));
+ return ((hibits(a1) == hibits(a2)) && (lobits(a1) == lobits(a2)));
+};
+
+bool macaddr_ge(macaddr *a1, macaddr *a2) {
+ return((hibits(a1) > hibits(a2)) ||
+ ((hibits(a1) == hibits(a2)) && lobits(a1) >= lobits(a2)));
+};
+
+bool macaddr_gt(macaddr *a1, macaddr *a2) {
+ return((hibits(a1) > hibits(a2)) ||
+ ((hibits(a1) == hibits(a2)) && lobits(a1) > lobits(a2)));
};
bool macaddr_ne(macaddr *a1, macaddr *a2) {
- return((a1->a != a2->a) || (a1->b != a2->b) ||
- (a1->c != a2->c) || (a1->d != a2->d) ||
- (a1->e != a2->e) || (a1->f != a2->f));
+ return ((hibits(a1) != hibits(a2)) || (lobits(a1) != lobits(a2)));
};
/*
*/
int4 macaddr_cmp(macaddr *a1, macaddr *a2) {
- unsigned long a1magm, a1magh, a2magm, a2magh;
- a1magm = MagM(a1);
- a1magh = MagH(a1);
- a2magm = MagM(a2);
- a2magh = MagH(a2);
- if (a1magm < a2magm)
+ if (hibits(a1) < hibits(a2))
return -1;
- else if (a1magm > a2magm)
+ else if (hibits(a1) > hibits(a2))
return 1;
- else if (a1magh < a2magh)
+ else if (lobits(a1) < lobits(a2))
return -1;
- else if (a1magh > a2magh)
+ else if (lobits(a1) > lobits(a2))
return 1;
else
return 0;
}
-/*
- * Similarity means having the same manufacurer, which means
- * having the same first three bytes of address:
- */
-
-bool macaddr_like(macaddr *a1, macaddr *a2) {
- unsigned long a1magm, a2magm;
- a1magm = MagM(a1);
- a2magm = MagM(a2);
- if ((a1magm == 0) || (a2magm == 0))
- return FALSE;
- return (a1magm == a2magm);
-}
-
/*
* The special manufacturer fetching function. See "mac.h".
*/
/*
* PostgreSQL type definitions for MAC addresses.
+ *
+ * $Id: mac.h,v 1.2 1998/02/14 17:58:07 scrappy Exp $
*/
typedef struct manufacturer {
--
-- PostgreSQL code for MAC addresses.
--
+-- $Id: mac.sql,v 1.2 1998/02/14 17:58:08 scrappy Exp $
+--
load '/usr/local/pgsql/modules/mac.so';
language 'c';
create type macaddr (
- internallength = 8,
+ internallength = 6,
externallength = variable,
input = macaddr_in,
output = macaddr_out
);
--
--- The various boolean tests:
+-- The boolean tests:
--
+create function macaddr_lt(macaddr, macaddr)
+ returns bool
+ as '/usr/local/pgsql/modules/mac.so'
+ language 'c';
+
+create function macaddr_le(macaddr, macaddr)
+ returns bool
+ as '/usr/local/pgsql/modules/mac.so'
+ language 'c';
+
create function macaddr_eq(macaddr, macaddr)
returns bool
as '/usr/local/pgsql/modules/mac.so'
language 'c';
-create function macaddr_ne(macaddr, macaddr)
+create function macaddr_ge(macaddr, macaddr)
+ returns bool
+ as '/usr/local/pgsql/modules/mac.so'
+ language 'c';
+
+create function macaddr_gt(macaddr, macaddr)
returns bool
as '/usr/local/pgsql/modules/mac.so'
language 'c';
-create function macaddr_like(macaddr, macaddr)
+create function macaddr_ne(macaddr, macaddr)
returns bool
as '/usr/local/pgsql/modules/mac.so'
language 'c';
--
--- Now the operators. Note how the "negator = <>" in the
--- definition of the equivalence operator is commented out.
--- It gets defined implicitly when "<>" is defined, with
--- "=" as its negator.
+-- Now the operators. Note how some of the parameters to some
+-- of the 'create operator' commands are commented out. This
+-- is because they reference as yet undefined operators, and
+-- will be implicitly defined when those are, further down.
--
+create operator < (
+ leftarg = macaddr,
+ rightarg = macaddr,
+-- negator = >=,
+ procedure = macaddr_lt
+);
+
+create operator <= (
+ leftarg = macaddr,
+ rightarg = macaddr,
+-- negator = >,
+ procedure = macaddr_le
+);
+
create operator = (
leftarg = macaddr,
rightarg = macaddr,
procedure = macaddr_eq
);
-create operator <> (
+create operator >= (
leftarg = macaddr,
rightarg = macaddr,
- commutator = <>,
- negator = =,
- procedure = macaddr_ne
+ negator = <,
+ procedure = macaddr_ge
+);
+
+create operator > (
+ leftarg = macaddr,
+ rightarg = macaddr,
+ negator = <=,
+ procedure = macaddr_gt
);
-create operator ~~ (
+create operator <> (
leftarg = macaddr,
rightarg = macaddr,
- commutator = ~~,
- procedure = macaddr_like
+ negator = =,
+ procedure = macaddr_ne
);
--
--- /dev/null
+--
+-- A quick test of the IP address code
+--
+-- $Id: test.sql,v 1.1 1998/02/14 17:58:09 scrappy Exp $
+--
+
+-- temporary table:
+create table addresses (address ipaddr);
+
+-- sample data from two subnets:
+insert into addresses values ('158.37.96.15');
+insert into addresses values ('158.37.96.16');
+insert into addresses values ('158.37.96.17');
+insert into addresses values ('158.37.97.15');
+insert into addresses values ('158.37.97.16');
+insert into addresses values ('158.37.97.17');
+insert into addresses values ('158.37.98.15');
+insert into addresses values ('158.37.98.16');
+insert into addresses values ('158.37.98.17');
+insert into addresses values ('158.37.96.150');
+insert into addresses values ('158.37.96.160');
+insert into addresses values ('158.37.96.170');
+insert into addresses values ('158.37.97.150');
+insert into addresses values ('158.37.97.160');
+insert into addresses values ('158.37.97.170');
+insert into addresses values ('158.37.98.150');
+insert into addresses values ('158.37.98.160');
+insert into addresses values ('158.37.98.170');
+
+-- show them all:
+select * from addresses;
+
+-- select the ones in subnet 96:
+select * from addresses where ipaddr_in_net(address, '158.37.96.0/24');
+
+-- select the ones not in subnet 96:
+select * from addresses where not ipaddr_in_net(address, '158.37.96.0/24');
+
+-- select the ones in subnet 97:
+select * from addresses where ipaddr_in_net(address, '158.37.97.0/24');
+
+-- select the ones not in subnet 97:
+select * from addresses where not ipaddr_in_net(address, '158.37.97.0/24');
+
+-- select the ones in subnet 96 or 97, sorted:
+select * from addresses where ipaddr_in_net(address, '158.37.96.0/23')
+ order by address;
+
+-- now some networks:
+create table networks (network ipaddr);
+
+-- now the subnets mentioned above:
+insert into networks values ('158.37.96.0/24');
+insert into networks values ('158.37.97.0/24');
+insert into networks values ('158.37.98.0/24');
+
+-- select the netmasks of the net containing each:
+select address, ipaddr_mask(network) from addresses, networks
+ where ipaddr_in_net(address, network);
+
+-- select the broadcast address of the net containing each:
+select address, ipaddr_bcast(network) from addresses, networks
+ where ipaddr_in_net(address, network);
+
+-- tidy up:
+drop table addresses;
+drop table networks;
+
+--
+-- eof
+--