Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 674b22a

Browse files
committed
From: Tom I Helbekkmo <tih@Hamartun.Priv.NO>
PostgreSQL type extensions for IP and MAC addresses. 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 my first experiment with adding a data type to PostgreSQL. I then thought that this might be useful to others, both directly and 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.
1 parent 9f8d3b6 commit 674b22a

File tree

7 files changed

+834
-0
lines changed

7 files changed

+834
-0
lines changed

contrib/ip_and_mac/Makefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# PostgreSQL type definitions for IP and MAC addresses.
2+
3+
all: ip.so mac.so
4+
5+
ip.so: ip.o
6+
ld -Bshareable -o ip.so ip.o
7+
8+
ip.o: ip.c
9+
cc -g -O -fPIC -I/usr/local/pgsql/include -c ip.c
10+
11+
mac.so: mac.o
12+
ld -Bshareable -o mac.so mac.o
13+
14+
mac.o: mac.c mac.h
15+
cc -g -O -fPIC -I/usr/local/pgsql/include -c mac.c
16+
17+
install: ip.so mac.so
18+
install -c ip.so mac.so /usr/local/pgsql/modules
19+
20+
# eof

contrib/ip_and_mac/README

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
PostgreSQL type extensions for IP and MAC addresses.
2+
---------------------------------------------------
3+
4+
I needed to record IP and MAC level ethernet addresses in a data
5+
base, and I really didn't want to store them as plain strings, with
6+
no enforced error checking, so I put together the accompanying code
7+
as my first experiment with adding a data type to PostgreSQL. I
8+
then thought that this might be useful to others, both directly and
9+
as a very simple example of how to do this sort of thing, so here
10+
it is, in the hope that it will be useful.
11+
12+
IP addresses are implemented as an 8 byte struct (this may well be
13+
more than is useful, but I figured that since it has to be at least 5,
14+
it might as well round well) that contains the four bytes of address
15+
and a mask width. Thus, a node address looks like '158.37.96.15/32'
16+
(or just '158.37.96.15', which is understood to mean the same thing).
17+
This address happens to be part of a subnet where I work;
18+
'158.37.96.0/24', which itself is a part of the larger subnet
19+
allocated to our institution, which is '158.37.96.0/21', which again,
20+
if you go by the book, is part of the class "B" net '158.37.0.0/16'.
21+
22+
Input and output functions are supplied, along with the "normal" <,
23+
<=, =, >=, > and <> operators, which all do what you expect, and the
24+
similarity operator ~~, which checks whether two given addresses are
25+
either the same, or, failing that, whether one is a subnet
26+
specification and the other an address (or a smaller subnet) within
27+
that. Good for picking out records with addresses in a given subnet:
28+
note that '158.37.96.0/21' spans '158.37.96.0' to '158.37.103.255',
29+
which is not all that easily handled in its external representation.
30+
31+
MAC level ethernet addresses are also implemented as an 8 byte struct
32+
(I wish I knew what alignment needs are actually present -- I'm just
33+
not taking any chances here) that contains the address as unsigned
34+
chars. Several input forms are accepted: the following are all the
35+
same address: '08002b:010203', '08002b-010203', '0800.2b01.0203',
36+
'08-00-2b-01-02-03' and '08:00:2b:01:02:03'. Upper and lower case is
37+
accepted for the digits 'a' through 'f'. Output is always in the
38+
latter of the given forms.
39+
40+
Input and output functions are supplied, along with the = and <>
41+
operators, which do what you expect, and the similarity operator ~~,
42+
which checks whether two given addresses belong to hardware from the
43+
same manufacturer (first three bytes the same, that is). As an extra
44+
feature, a function macaddr_manuf() is defined, which returns the name
45+
of the manufacturer as a string.
46+
47+
To install: fix the path names in the SQL files and the Makefile if
48+
you need to, then make, make install, slurp the SQL files into psql or
49+
whatever, and you're off. Enjoy!
50+
51+
Bergen, Norway, 1998-01-11, Tom Ivar Helbekkmo (tih@Hamartun.Priv.NO).

contrib/ip_and_mac/ip.c

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*
2+
* PostgreSQL type definitions for IP addresses.
3+
*/
4+
5+
#include <stdio.h>
6+
7+
#include <postgres.h>
8+
#include <utils/palloc.h>
9+
10+
/*
11+
* This is the internal storage format for IP addresses:
12+
*/
13+
14+
typedef struct ipaddr {
15+
unsigned char a;
16+
unsigned char b;
17+
unsigned char c;
18+
unsigned char d;
19+
unsigned char w;
20+
unsigned char pad1;
21+
short pad2;
22+
} ipaddr;
23+
24+
/*
25+
* Various forward declarations:
26+
*/
27+
28+
ipaddr *ipaddr_in(char *str);
29+
char *ipaddr_out(ipaddr *addr);
30+
31+
bool ipaddr_lt(ipaddr *a1, ipaddr *a2);
32+
bool ipaddr_le(ipaddr *a1, ipaddr *a2);
33+
bool ipaddr_eq(ipaddr *a1, ipaddr *a2);
34+
bool ipaddr_ge(ipaddr *a1, ipaddr *a2);
35+
bool ipaddr_gt(ipaddr *a1, ipaddr *a2);
36+
37+
bool ipaddr_ne(ipaddr *a1, ipaddr *a2);
38+
int4 ipaddr_cmp(ipaddr *a1, ipaddr *a2);
39+
bool ipaddr_like(ipaddr *a1, ipaddr *a2);
40+
41+
/*
42+
* A utility macro used for sorting addresses numerically:
43+
*/
44+
45+
#define Mag(addr) \
46+
((unsigned long)((addr->a<<24)|(addr->b<<16)|(addr->c<<8)|(addr->d)))
47+
48+
/*
49+
* IP address reader. Note how the count returned by sscanf()
50+
* is used to determine whether the mask size was specified.
51+
*/
52+
53+
ipaddr *ipaddr_in(char *str) {
54+
int a, b, c, d, w;
55+
ipaddr *result;
56+
int count;
57+
58+
if (strlen(str) > 0) {
59+
60+
count = sscanf(str, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &w);
61+
62+
if (count < 4) {
63+
elog(ERROR, "ipaddr_in: error in parsing \"%s\"", str);
64+
return(NULL);
65+
}
66+
67+
if (count == 4)
68+
w = 32;
69+
70+
if ((a < 0) || (a > 255) || (b < 0) || (b > 255) ||
71+
(c < 0) || (c > 255) || (d < 0) || (d > 255) ||
72+
(w < 0) || (w > 32)) {
73+
elog(ERROR, "ipaddr_in: illegal address \"%s\"", str);
74+
return(NULL);
75+
}
76+
} else {
77+
a = b = c = d = w = 0; /* special case for missing address */
78+
}
79+
80+
result = (ipaddr *)palloc(sizeof(ipaddr));
81+
82+
result->a = a;
83+
result->b = b;
84+
result->c = c;
85+
result->d = d;
86+
result->w = w;
87+
88+
return(result);
89+
}
90+
91+
/*
92+
* IP address output function. Note mask size specification
93+
* generated only for subnets, not for plain host addresses.
94+
*/
95+
96+
char *ipaddr_out(ipaddr *addr) {
97+
char *result;
98+
99+
if (addr == NULL)
100+
return(NULL);
101+
102+
result = (char *)palloc(32);
103+
104+
if (Mag(addr) > 0) {
105+
if (addr->w == 32)
106+
sprintf(result, "%d.%d.%d.%d",
107+
addr->a, addr->b, addr->c, addr->d);
108+
else
109+
sprintf(result, "%d.%d.%d.%d/%d",
110+
addr->a, addr->b, addr->c, addr->d, addr->w);
111+
} else {
112+
result[0] = 0; /* special case for missing address */
113+
}
114+
return(result);
115+
}
116+
117+
/*
118+
* Boolean tests. The Mag() macro was defined above.
119+
*/
120+
121+
bool ipaddr_lt(ipaddr *a1, ipaddr *a2) {
122+
unsigned long a1mag, a2mag;
123+
a1mag = Mag(a1);
124+
a2mag = Mag(a2);
125+
return (a1mag < a2mag);
126+
};
127+
128+
bool ipaddr_le(ipaddr *a1, ipaddr *a2) {
129+
unsigned long a1mag, a2mag;
130+
a1mag = Mag(a1);
131+
a2mag = Mag(a2);
132+
return (a1mag <= a2mag);
133+
};
134+
135+
bool ipaddr_eq(ipaddr *a1, ipaddr *a2) {
136+
unsigned long a1mag, a2mag;
137+
a1mag = Mag(a1);
138+
a2mag = Mag(a2);
139+
return ((a1mag == a2mag) && (a1->w == a2->w));
140+
};
141+
142+
bool ipaddr_ge(ipaddr *a1, ipaddr *a2) {
143+
unsigned long a1mag, a2mag;
144+
a1mag = Mag(a1);
145+
a2mag = Mag(a2);
146+
return (a1mag >= a2mag);
147+
};
148+
149+
bool ipaddr_gt(ipaddr *a1, ipaddr *a2) {
150+
unsigned long a1mag, a2mag;
151+
a1mag = Mag(a1);
152+
a2mag = Mag(a2);
153+
return (a1mag > a2mag);
154+
};
155+
156+
bool ipaddr_ne(ipaddr *a1, ipaddr *a2) {
157+
unsigned long a1mag, a2mag;
158+
a1mag = Mag(a1);
159+
a2mag = Mag(a2);
160+
return ((a1mag != a2mag) || (a1->w != a2->w));
161+
};
162+
163+
/*
164+
* Comparison function for sorting:
165+
*/
166+
167+
int4 ipaddr_cmp(ipaddr *a1, ipaddr *a2) {
168+
unsigned long a1mag = Mag(a1), a2mag = Mag(a2);
169+
if (a1mag < a2mag)
170+
return -1;
171+
else if (a1mag > a2mag)
172+
return 1;
173+
else
174+
return 0;
175+
}
176+
177+
/*
178+
* Our "similarity" operator checks whether two addresses are
179+
* either the same node address, or, failing that, whether one
180+
* of them contains the other. This will be true if they have
181+
* the same high bits down as far as the shortest mask reaches.
182+
*/
183+
184+
unsigned long build_mask(unsigned char bits) {
185+
unsigned long mask = 0;
186+
int i;
187+
for (i = 0; i < bits; i++)
188+
mask = (mask >> 1) | 0x80000000;
189+
return mask;
190+
}
191+
192+
bool ipaddr_like(ipaddr *a1, ipaddr *a2) {
193+
unsigned long a1bits, a2bits, maskbits;
194+
if ((a1->w == 0) || (a2->w == 0))
195+
return FALSE;
196+
if ((a1->w == 32) && (a2->w == 32))
197+
return ipaddr_eq(a1, a2);
198+
a1bits = Mag(a1);
199+
a2bits = Mag(a2);
200+
if (a1->w > a2->w) {
201+
maskbits = build_mask(a2->w);
202+
return ((a1bits & maskbits) == (a2bits & maskbits));
203+
} else {
204+
maskbits = build_mask(a1->w);
205+
return ((a2bits & maskbits) == (a1bits & maskbits));
206+
}
207+
return FALSE;
208+
}
209+
210+
/*
211+
* eof
212+
*/

0 commit comments

Comments
 (0)