IP Addresses in PHP/MySQL

I’ve been working on a web-based tool that stores, among other network-related things, IP addresses. When I first started I stored each IP address as four TINYINTS (0-255 for each octet):

mysql> DESC ipaddresses;
+----------+---------------------+------+-----+---------+----------------+
| FIELD    | TYPE                | NULL | KEY | DEFAULT | Extra          |
+----------+---------------------+------+-----+---------+----------------+
| id       | INT(10) UNSIGNED    | NO   | PRI | NULL    | AUTO_INCREMENT | 
| A        | tinyint(3) UNSIGNED | NO   |     | NULL    |                | 
| B        | tinyint(3) UNSIGNED | NO   |     | NULL    |                | 
| C        | tinyint(3) UNSIGNED | NO   |     | NULL    |                | 
| D        | tinyint(3) UNSIGNED | NO   |     | NULL    |                | 
+----------+---------------------+------+-----+---------+----------------+
5 ROWS IN SET (0.00 sec)
 
mysql> SELECT * FROM ipaddresses WHERE id=1
+----+----+----+----+-----+
| id | A  | B  | C  | D   |
+----+----+----+----+-----+
|  1 | 10 | 20 | 30 | 131 |
+----+----+----+----+-----+
1 ROW IN SET (0.02 sec)

As I started manipulating these addresses I found it awkward to do common binary math (like bitwise ANDs). I decided instead to store these 32-bit values as unsigned integers (of length 32). To make my life easier yet, MySQL and PHP both have native functions to convert IP addresses between my old and new formats to make this migration extremely easy.

In MySQL:

mysql> SELECT INET_ATON('10.20.30.131');
+---------------------------+
| INET_ATON('10.20.30.131') |
+---------------------------+
|                 169090691 | 
+---------------------------+
1 ROW IN SET (0.00 sec)
 
mysql> SELECT INET_NTOA(169090691);
+----------------------+
| INET_NTOA(169090691) |
+----------------------+
| 10.20.30.131         | 
+----------------------+
1 ROW IN SET (0.00 sec)

In PHP:

$ip_string="10.20.30.131";
$ip_integer = ip2long($ipstring);
echo $ip_integer; ## 169090691
echo long2ip($ip_integer); ## 10.20.30.131

In another example of inefficiency, I used to gather all sorts of info from the user and use PHP to make multiple SQL queries before modifying my data. I’ve since learned about stored procedures – the database does all the work and I only need to make one SQL call from PHP. This presented me with an unexpected problem, however:

mysql> CALL getAddress();
+----+----+----+----+----+
| id | A  | B  | C  | D  |
+----+----+----+----+----+
|  1 | 10 | 20 | 30 | 131|
+----+----+----+----+----+
1 ROW IN SET (0.00 sec)
 
Query OK, 0 ROWS affected (0.00 sec)

The string “Query OK, 0 rows affected (0.00 sec)” is a second result set, which the PHP mysql library can’t handle. The fix here is to use the mysqli library instead and access only the result sets you need. In my case I’m only concerned with the first result set so I added a bunch of wasteful while loops after each of my CALL queries.

while (mysqli_next_result()){}

Lastly, PHP’s ip2long function treats 32-bit numbers as signed ints, so I had to specifically treat them as unsigned ints:

echo $ip_integer; ## BAD
echo sprintf(%u, $ip_integer); ## GOOD