Reputation: 116
I have a MySQL blacklist table, with either single IPv4, IPv6 or CIDR ranges of one of both types stored in it.
My table looks somewhat like this:
+-----------+-------------+
| Name | Type |
+-----------+-------------+
| IpAddress | VARCHAR(46) |
+-----------+-------------+
| Mask | INT(2) |
+-----------+-------------+
Now, I'd like to check whether a given IP address is in one of the saved CIDR ranges. I already accomplished this after a few days of researching a whole lot of pages, and finally just ported the following solution to MySQL: PHP5 calculate IPv6 range from cidr prefix?
So I transform a IP + CIDR mask to the first and last IP in that range, which I then convert to numbers using INET6_ATON and compare it with the BETWEEN operator.
My implementation:
Get last ip
FUNCTION (`Ip` VARCHAR(46), `Mask` INT(2) UNSIGNED) RETURNS varchar(39)
BEGIN
DECLARE IpNumber VARBINARY(16);
DECLARE Last VARCHAR(39) DEFAULT '';
DECLARE FlexBits, Counter, Deci, NewByte INT UNSIGNED;
DECLARE HexIp VARCHAR(32);
SET IpNumber = INET6_ATON(Ip);
SET HexIp = HEX(IpNumber);
SET FlexBits = 128 - Mask;
SET Counter = 32;
WHILE (FlexBits > 0) DO
SET Deci = CONV(SUBSTR(HexIp, Counter, 1), 16, 10);
SET NewByte = Deci | (POW(2, LEAST(4, FlexBits)) - 1);
SET Last = CONCAT(CONV(NewByte, 10, 16), Last);
IF FlexBits >= 4 THEN SET FlexBits = FlexBits - 4;
ELSE SET FlexBits = 0;
END IF;
SET Counter = Counter - 1;
END WHILE;
SET Last = CONCAT(SUBSTR(HexIp, 1, Counter), Last);
RETURN INET6_NTOA(UNHEX(Last));
END
Get first ip
FUNCTION (`Ip` VARCHAR(46), `Mask` INT(2) UNSIGNED, `WithMask` BOOLEAN) RETURNS varchar(39)
BEGIN
DECLARE First VARCHAR (42) DEFAULT '';
SET First = INET6_NTOA(UNHEX(RPAD(SUBSTR(HEX(INET6_ATON(Ip)), 1, Mask / 4), 32, 0)));
IF (WithMask = 1) THEN
SET First = CONCAT(First, '/', CAST(Mask AS CHAR));
END IF;
RETURN First;
END
This works fine! I just have the idea that it could be done way more efficient using some clever bit operations. I've read tons of questions about this topic, but I didn't really found a concrete solution Any help in the right direction would be really appreciated!
NOTE: Only for IPv6, I've got IPv4 implemented the correct way.
Upvotes: 3
Views: 2022
Reputation: 1694
I found a bug in your Get last ip function.
instead of this:
SET FlexBits = FlexBits - 4;
use this:
IF FlexBits >= 4 THEN SET FlexBits = FlexBits - 4;
ELSE SET FlexBits = 0;
END IF;
Otherwise you will get this kind of error message:
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(FlexBits@4 - 4)'
When you try to get the last IP of these IPv6 ranges (where the mask cannot be divided by 4):
SELECT getLastIp("2001:200:1::", 47);
SELECT getLastIp("2001:200:0:8000", 49);
SELECT getLastIp("2001:470:1f06:2000::", 51);
SELECT getLastIp("2001:470:0:284:2::", 79);
SELECT getLastIp("2001:470:1f08:415::8:0", 109);
SELECT getLastIp("2001:550:0:1000::8275:8000", 113);
SELECT getLastIp("2001:550:0:1000::9a19:300", 123);
SELECT getLastIp("2001:550:0:1000::9a19:320", 126);
Upvotes: 1
Reputation: 11
I've implemented this for MySQL the same way about a year ago. So the idea is fine. However there's a small but rather significant bug in your code.
The line:
SET Last = CONCAT(Last, CONV(NewByte, 10, 16));
should rather be:
SET Last = CONCAT(CONV(NewByte, 10, 16), Last);
Upvotes: 1