Search on partial IP addresses stored as integers

北城余情 提交于 2019-12-11 07:47:15

问题


I currently have a MySQL database that includes IP addresses. On a search form, the client wants to search on a partial IP address and have (perhaps) many results pop up. I am currently storing the IP addresses in mysql as unsigned int. I am using PHP 5.2, so do not have access to PHP 5.7 and its INET6_NTOA function.

The current database has over 50,000 records and continues to grow, so I don't want to have to convert all the IP's to dotted notation, then do a match - that seems a bit unwieldy.

Is there a better way for me to search on a partial IP address?


回答1:


Assuming you are dealing with IPv4 addresses, each address is nothing but 32 bits.

There is MySQL INET_NTOA function which is responsible to return string by your IP.

So, you might want use smth like:

SELECT ... FROM ... WHERE INET_NTOA(...) LIKE (...)

Hope it helps.

UPD: to increase productivity I would suggest you to update the table adding new CHAR(16) field for string representation of IP and a trigger ON UPDATE which is to fill that field with INET_NTOA(...) value. Selecting against this field will work like a charm.




回答2:


Actually, the unsigned integer column is already the most efficient way to search for matches on partial ip addresses! Please don't waste your energy nor CPU time on converting back to dotted notation or going for a LIKE search on some kind of string column.

There exist several way of writing down partial IP address, but in the end, they all come down to a base ip with a net mask. Also, assuming that by partial, you mean all IPs with a common prefix, Then this is also equivalent to specifying a range of IPs.

Either way, the partial IP address specification ends up being described as two 32 bits, unsigned integers, encoded in the same format as your database column. Either you have a starting ip and end ip, or you have a base ip and a mask. These integers can be used directly inside your SQL query to obtain matches efficiently. Even better, if you use the ip range approach, then the engine will be able to take advantage of an ordered index on your ip column. You can't expect any better.

So how to build the IP range? We'll that depends how your partial addresses were specified on the first place, but assuming that you do know the net mask, then the start address is equal to (base ip & net mask), and the end address is ((base ip & net mask) | (~netmask)), where &, | and ~ respectively means bitwise-and, bitwise-or and bitwise-not.

Update

Here is a sample code to apply the strategy I described.

Now, it's been a very long time since I last write PHP code, and the following has never been executed, so please excuse any error I might have introduced. I also chose deliberately to "expand" each notation scenario in order to make them easier to understand, rather than to squeeze all of them in a single, very complex regex.

if (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [/] (\d{1,2}) $/x', $input, $r)) {
    // Four-dotted IP with number of significant bits: 123.45.67.89/24

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = intval($r[5]);

} elseif (preg_match(' /^ (\d{1,3}) (?: [.] [*0] [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with three-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 8 bits)

    $a = intval($r[1]);
    $b = 0;
    $c = 0;
    $d = 0;
    $mask = 8;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with two-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 16 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = 0;
    $d = 0;
    $mask = 16;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with last number missing, or equals to 0 or *:
    // 123.45.67, 123.45.67.0, 123.45.67.*  (assume netmask of 24 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = 0;
    $mask = 24;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) $/x', $input, $r)) {
    // Four-dotted IP: 123.45.67.89 (assume netmask of 32 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = 32;

} else {
    throw new Exception('...');
}

if ($a < 0 || $a > 255) {  throw new Exception('...') };
if ($b < 0 || $b > 255) {  throw new Exception('...') };
if ($c < 0 || $c > 255) {  throw new Exception('...') };
if ($d < 0 || $d > 255) {  throw new Exception('...') };
if ($mask < 1 || $mask > 32) {  throw new Exception('...') };

$baseip = ($a << 24) + ($b << 16) + ($c << 8) + ($d);
$netmask = (1 << (32 - $mask)) - 1;

$startip = $baseip & netmask;
$endip = ($baseip & netmask) | (~netmask);

// ...

doSql( "SELECT ... FROM ... WHERE ipaddress >= ? && ipaddress <= ?", $startip, $endip);

// or

doSql( "SELECT ... FROM ... WHERE ((ipaddress & ?) = ?)", $netmask, $startip);



回答3:


Here is it.

$ip = '127.5.3';

if (preg_match('/^([0-9]*)?\.?([0-9]*)?\.?([0-9]*)?\.?([0-9]*)$/',$ip, $m)) {
  $from = (int)$m[1]*256*256*256 +(int)$m[2]*256*256 + (int)$m[3]*256 + (int)$m[4];
  // or $from = ip2long($m[1].'.'.$m[2].'.'.$m[3].'.'.$m[4]);
  $to = ($m[1]>0?$m[1]:255)*256*256*256 + ($m[2]>0?$m[2]:255)*256*256 + ($m[3]>0?$m[3]:255)*256+($m[4]>0?$m[4]:255);
  // select * from sometable where ip between $from and $to
} else
  echo "Incorrect IP";



回答4:


Since you want a partial search and return a list with matching ips, I would suggest using LIKE and then a % at the end

SELECT ip FROM ip_table
WHERE ip LIKE '$ip%'


来源:https://stackoverflow.com/questions/26508008/search-on-partial-ip-addresses-stored-as-integers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!