Disallow bits beyond the mask length for CIDR values, per discussion

on pghackers.  Arrange for the sort ordering of general INET values
to be network part as major sort key, host part as minor sort key.
I did not force an initdb for this change, but anyone who's running
indexes on general INET values may need to recreate those indexes.
This commit is contained in:
Tom Lane 2000-10-27 01:55:23 +00:00
parent 2f35b4efdb
commit 063c0f6bea
3 changed files with 66 additions and 32 deletions

View File

@ -3,7 +3,7 @@
* is for IP V4 CIDR notation, but prepared for V6: just
* add the necessary bits where the comments indicate.
*
* $Header: /cvsroot/pgsql/src/backend/utils/adt/network.c,v 1.24 2000/08/03 23:07:46 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/network.c,v 1.25 2000/10/27 01:52:15 tgl Exp $
*
* Jon Postel RIP 16 Oct 1998
*/
@ -20,8 +20,9 @@
#include "utils/inet.h"
static int v4bitncmp(unsigned int a1, unsigned int a2, int bits);
static int32 network_cmp_internal(inet *a1, inet *a2);
static int v4bitncmp(unsigned long a1, unsigned long a2, int bits);
static bool v4addressOK(unsigned long a1, int bits);
/*
* Access macros. Add IPV6 support.
@ -50,14 +51,29 @@ network_in(char *src, int type)
inet *dst;
dst = (inet *) palloc(VARHDRSZ + sizeof(inet_struct));
/* make sure any unused bits in a CIDR value are zeroed */
MemSet(dst, 0, VARHDRSZ + sizeof(inet_struct));
/* First, try for an IP V4 address: */
ip_family(dst) = AF_INET;
bits = inet_net_pton(ip_family(dst), src, &ip_v4addr(dst),
type ? ip_addrsize(dst) : -1);
if ((bits < 0) || (bits > 32))
{
/* Go for an IPV6 address here, before faulting out: */
elog(ERROR, "could not parse \"%s\"", src);
elog(ERROR, "invalid %s value '%s'",
type ? "CIDR" : "INET", src);
}
/*
* Error check: CIDR values must not have any bits set beyond the masklen.
* XXX this code not IPV6 ready.
*/
if (type)
{
if (! v4addressOK(ip_v4addr(dst), bits))
elog(ERROR, "invalid CIDR value '%s': width too small", src);
}
VARATT_SIZEP(dst) = VARHDRSZ
+ ((char *) &ip_v4addr(dst) - (char *) VARDATA(dst))
@ -128,11 +144,12 @@ cidr_out(PG_FUNCTION_ARGS)
/*
* Basic comparison function for sorting and inet/cidr comparisons.
*
* XXX this ignores bits to the right of the mask. That's probably
* correct for CIDR, almost certainly wrong for INET. We need to have
* two sets of comparator routines, not just one. Note that suggests
* that CIDR and INET should not be considered binary-equivalent by
* the parser?
* Comparison is first on the common bits of the network part, then on
* the length of the network part, and then on the whole unmasked address.
* The effect is that the network part is the major sort key, and for
* equal network parts we sort on the host part. Note this is only sane
* for CIDR if address bits to the right of the mask are guaranteed zero;
* otherwise logically-equal CIDRs might compare different.
*/
static int32
@ -140,12 +157,16 @@ network_cmp_internal(inet *a1, inet *a2)
{
if (ip_family(a1) == AF_INET && ip_family(a2) == AF_INET)
{
int order = v4bitncmp(ip_v4addr(a1), ip_v4addr(a2),
Min(ip_bits(a1), ip_bits(a2)));
int order;
order = v4bitncmp(ip_v4addr(a1), ip_v4addr(a2),
Min(ip_bits(a1), ip_bits(a2)));
if (order != 0)
return order;
return ((int32) ip_bits(a1)) - ((int32) ip_bits(a2));
order = ((int) ip_bits(a1)) - ((int) ip_bits(a2));
if (order != 0)
return order;
return v4bitncmp(ip_v4addr(a1), ip_v4addr(a2), 32);
}
else
{
@ -455,13 +476,11 @@ network_netmask(PG_FUNCTION_ARGS)
*/
static int
v4bitncmp(unsigned int a1, unsigned int a2, int bits)
v4bitncmp(unsigned long a1, unsigned long a2, int bits)
{
unsigned long mask = 0;
int i;
unsigned long mask;
for (i = 0; i < bits; i++)
mask = (mask >> 1) | 0x80000000;
mask = (0xFFFFFFFFL << (32 - bits)) & 0xFFFFFFFFL;
a1 = ntohl(a1);
a2 = ntohl(a2);
if ((a1 & mask) < (a2 & mask))
@ -470,3 +489,18 @@ v4bitncmp(unsigned int a1, unsigned int a2, int bits)
return (1);
return (0);
}
/*
* Returns true if given address fits fully within the specified bit width.
*/
static bool
v4addressOK(unsigned long a1, int bits)
{
unsigned long mask;
mask = (0xFFFFFFFFL << (32 - bits)) & 0xFFFFFFFFL;
a1 = ntohl(a1);
if ((a1 & mask) == a1)
return true;
return false;
}

View File

@ -6,7 +6,7 @@ DROP TABLE INET_TBL;
ERROR: table "inet_tbl" does not exist
CREATE TABLE INET_TBL (c cidr, i inet);
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.226/24');
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.0/24', '192.168.1.226');
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10.0.0.0', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10.1.2.3', '10.1.2.3/32');
@ -15,6 +15,9 @@ INSERT INTO INET_TBL (c, i) VALUES ('10.1', '10.1.2.3/16');
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10', '11.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10', '9.1.2.3/8');
-- check that CIDR rejects invalid input:
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
ERROR: invalid CIDR value '192.168.1.2/24': width too small
SELECT '' AS ten, c AS cidr, i AS inet FROM INET_TBL;
ten | cidr | inet
-----+--------------+------------------
@ -107,15 +110,10 @@ SELECT '' AS four, c AS cidr, masklen(c) AS "masklen(cidr)",
SELECT '' AS six, c AS cidr, i AS inet FROM INET_TBL
WHERE c = i;
six | cidr | inet
-----+--------------+------------------
| 192.168.1/24 | 192.168.1.226/24
| 10/8 | 10.1.2.3/8
| 10.1.2.3/32 | 10.1.2.3
| 10.1.2/24 | 10.1.2.3/24
| 10.1/16 | 10.1.2.3/16
| 10/8 | 10.1.2.3/8
(6 rows)
six | cidr | inet
-----+-------------+----------
| 10.1.2.3/32 | 10.1.2.3
(1 row)
SELECT '' AS ten, i, c,
i < c AS lt, i <= c AS le, i = c AS eq,
@ -125,14 +123,14 @@ SELECT '' AS ten, i, c,
FROM INET_TBL;
ten | i | c | lt | le | eq | ge | gt | ne | sb | sbe | sup | spe
-----+------------------+--------------+----+----+----+----+----+----+----+-----+-----+-----
| 192.168.1.226/24 | 192.168.1/24 | f | t | t | t | f | f | f | t | f | t
| 192.168.1.226/24 | 192.168.1/24 | f | f | f | t | t | t | f | t | f | t
| 192.168.1.226 | 192.168.1/24 | f | f | f | t | t | t | t | t | f | f
| 10.1.2.3/8 | 10/8 | f | t | t | t | f | f | f | t | f | t
| 10.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | t | f | t
| 10.1.2.3/8 | 10.0.0.0/32 | t | t | f | f | f | t | f | f | t | t
| 10.1.2.3 | 10.1.2.3/32 | f | t | t | t | f | f | f | t | f | t
| 10.1.2.3/24 | 10.1.2/24 | f | t | t | t | f | f | f | t | f | t
| 10.1.2.3/16 | 10.1/16 | f | t | t | t | f | f | f | t | f | t
| 10.1.2.3/8 | 10/8 | f | t | t | t | f | f | f | t | f | t
| 10.1.2.3/24 | 10.1.2/24 | f | f | f | t | t | t | f | t | f | t
| 10.1.2.3/16 | 10.1/16 | f | f | f | t | t | t | f | t | f | t
| 10.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | t | f | t
| 11.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | f | f | f
| 9.1.2.3/8 | 10/8 | t | t | f | f | f | t | f | f | f | f
(10 rows)

View File

@ -7,7 +7,7 @@
DROP TABLE INET_TBL;
CREATE TABLE INET_TBL (c cidr, i inet);
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.226/24');
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.0/24', '192.168.1.226');
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10.0.0.0', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10.1.2.3', '10.1.2.3/32');
@ -16,6 +16,8 @@ INSERT INTO INET_TBL (c, i) VALUES ('10.1', '10.1.2.3/16');
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10', '11.1.2.3/8');
INSERT INTO INET_TBL (c, i) VALUES ('10', '9.1.2.3/8');
-- check that CIDR rejects invalid input:
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
SELECT '' AS ten, c AS cidr, i AS inet FROM INET_TBL;