1<?php
2/**
3* Class to provide IPv4 calculations
4*
5* PHP versions 4 and 5
6*
7* LICENSE: This source file is subject to version 3.01 of the PHP license
8* that is available through the world-wide-web at the following URI:
9* http://www.php.net/license/3_01.txt.  If you did not receive a copy of
10* the PHP License and are unable to obtain it through the web, please
11* send a note to license@php.net so we can mail you a copy immediately.
12*
13* @category   Net
14* @package    Net_IPv4
15* @author     Eric Kilfoil <edk@ypass.net>
16* @author     Marco Kaiser <bate@php.net>
17* @author     Florian Anderiasch <fa@php.net>
18* @copyright  1997-2005 The PHP Group
19* @license    http://www.php.net/license/3_01.txt  PHP License 3.01
20* @version    CVS: $Id: IPv4.php 302879 2010-08-30 06:52:41Z bate $
21* @link       http://pear.php.net/package/Net_IPv4
22*/
23
24require_once 'PEAR.php';
25
26// {{{ GLOBALS
27/**
28 * Map of bitmasks to subnets
29 *
30 * This array contains every valid netmask.  The index of the dot quad
31 * netmask value is the corresponding CIDR notation (bitmask).
32 *
33 * @global array $GLOBALS['Net_IPv4_Netmask_Map']
34 */
35$GLOBALS['Net_IPv4_Netmask_Map'] = array(
36            0 => "0.0.0.0",
37            1 => "128.0.0.0",
38            2 => "192.0.0.0",
39            3 => "224.0.0.0",
40            4 => "240.0.0.0",
41            5 => "248.0.0.0",
42            6 => "252.0.0.0",
43            7 => "254.0.0.0",
44            8 => "255.0.0.0",
45            9 => "255.128.0.0",
46            10 => "255.192.0.0",
47            11 => "255.224.0.0",
48            12 => "255.240.0.0",
49            13 => "255.248.0.0",
50            14 => "255.252.0.0",
51            15 => "255.254.0.0",
52            16 => "255.255.0.0",
53            17 => "255.255.128.0",
54            18 => "255.255.192.0",
55            19 => "255.255.224.0",
56            20 => "255.255.240.0",
57            21 => "255.255.248.0",
58            22 => "255.255.252.0",
59            23 => "255.255.254.0",
60            24 => "255.255.255.0",
61            25 => "255.255.255.128",
62            26 => "255.255.255.192",
63            27 => "255.255.255.224",
64            28 => "255.255.255.240",
65            29 => "255.255.255.248",
66            30 => "255.255.255.252",
67            31 => "255.255.255.254",
68            32 => "255.255.255.255"
69        );
70// }}}
71// {{{ Net_IPv4
72
73/**
74* Class to provide IPv4 calculations
75*
76* Provides methods for validating IP addresses, calculating netmasks,
77* broadcast addresses, network addresses, conversion routines, etc.
78*
79* @category   Net
80* @package    Net_IPv4
81* @author     Eric Kilfoil <edk@ypass.net>
82* @author     Marco Kaiser <bate@php.net>
83* @author     Florian Anderiasch <fa@php.net>
84* @copyright  1997-2005 The PHP Group
85* @license    http://www.php.net/license/3_01.txt  PHP License 3.01
86* @version    CVS: @package_version@
87* @link       http://pear.php.net/package/Net_IPv4
88* @access  public
89*/
90class Net_IPv4
91{
92    // {{{ properties
93    var $ip = "";
94    var $bitmask = false;
95    var $netmask = "";
96    var $network = "";
97    var $broadcast = "";
98    var $long = 0;
99
100    //pear
101    public $pear;
102
103
104	//initialize PEAR object on init
105    public function __construct () {
106	    $this->pear = new PEAR ();
107    }
108
109    // }}}
110    // {{{ validateIP()
111
112    /**
113     * Validate the syntax of the given IP adress
114     *
115     * Using the PHP long2ip() and ip2long() functions, convert the IP
116     * address from a string to a long and back.  If the original still
117     * matches the converted IP address, it's a valid address.  This
118     * function does not allow for IP addresses to be formatted as long
119     * integers.
120     *
121     * @param  string $ip IP address in the format x.x.x.x
122     * @return bool       true if syntax is valid, otherwise false
123     */
124    function validateIP($ip)
125    {
126        if ($ip == long2ip(ip2long($ip))) {
127            return true;
128        } else {
129            return false;
130        }
131    }
132
133    // }}}
134    // {{{ check_ip()
135
136    /**
137     * Validate the syntax of the given IP address (compatibility)
138     *
139     * This function is identical to Net_IPv4::validateIP().  It is included
140     * merely for compatibility reasons.
141     *
142     * @param  string $ip IP address
143     * @return bool       true if syntax is valid, otherwise false
144     */
145    function check_ip($ip)
146    {
147        return $this->validateIP($ip);
148    }
149
150    // }}}
151    // {{{ validateNetmask()
152
153    /**
154     * Validate the syntax of a four octet netmask
155     *
156     * There are 33 valid netmask values.  This function will compare the
157     * string passed as $netmask to the predefined 33 values and return
158     * true or false.  This is most likely much faster than performing the
159     * calculation to determine the validity of the netmask.
160     *
161     * @param  string $netmask Netmask
162     * @return bool       true if syntax is valid, otherwise false
163     */
164    function validateNetmask($netmask)
165    {
166        if (! in_array($netmask, $GLOBALS['Net_IPv4_Netmask_Map'])) {
167            return false;
168        }
169        return true;
170    }
171
172    // }}}
173    // {{{ parseAddress()
174
175    /**
176     * Parse a formatted IP address
177     *
178     * Given a network qualified IP address, attempt to parse out the parts
179     * and calculate qualities of the address.
180     *
181     * The following formats are possible:
182     *
183     * [dot quad ip]/[ bitmask ]
184     * [dot quad ip]/[ dot quad netmask ]
185     * [dot quad ip]/[ hex string netmask ]
186     *
187     * The first would be [IP Address]/[BitMask]:
188     * 192.168.0.0/16
189     *
190     * The second would be [IP Address] [Subnet Mask in dot quad notation]:
191     * 192.168.0.0/255.255.0.0
192     *
193     * The third would be [IP Address] [Subnet Mask as Hex string]
194     * 192.168.0.0/ffff0000
195     *
196     * Usage:
197     *
198     * $cidr = '192.168.0.50/16';
199     * $net = Net_IPv4::parseAddress($cidr);
200     * echo $net->network; // 192.168.0.0
201     * echo $net->ip; // 192.168.0.50
202     * echo $net->broadcast; // 192.168.255.255
203     * echo $net->bitmask; // 16
204     * echo $net->long; // 3232235520 (long/double version of 192.168.0.50)
205     * echo $net->netmask; // 255.255.0.0
206     *
207     * @param  string $ip IP address netmask combination
208     * @return object     true if syntax is valid, otherwise false
209     */
210    function parseAddress($address)
211    {
212        $myself = new Net_IPv4;
213
214        // ctype fix
215        if(!function_exists('ctype_digit')) {
216            function ctype_digit ($int) {
217                return is_numeric($int);
218            }
219        }
220
221        if (strchr($address, "/")) {
222            $parts = explode("/", $address);
223            if (! $myself->validateIP($parts[0])) {
224                return $this->pear->raiseError("invalid IP address");
225            }
226            $myself->ip = $parts[0];
227
228            // Check the style of netmask that was entered
229            /*
230             *  a hexadecimal string was entered
231             */
232            if (preg_match("/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i", $parts[1], $regs)) {
233                // hexadecimal string
234                $myself->netmask = hexdec($regs[1]) . "." .  hexdec($regs[2]) . "." .
235                    hexdec($regs[3]) . "." .  hexdec($regs[4]);
236
237            /*
238             *  a standard dot quad netmask was entered.
239             */
240            } else if (strchr($parts[1], ".")) {
241                if (! $myself->validateNetmask($parts[1])) {
242                    return $this->pear->raiseError("invalid netmask value");
243                }
244                $myself->netmask = $parts[1];
245
246            /*
247             *  a CIDR bitmask type was entered
248             */
249            } else if (ctype_digit($parts[1]) && $parts[1] >= 0 && $parts[1] <= 32) {
250                // bitmask was entered
251                $myself->bitmask = $parts[1];
252
253            /*
254             *  Some unknown format of netmask was entered
255             */
256            } else {
257                return $this->pear->raiseError("invalid netmask value");
258            }
259            $myself->calculate();
260            return $myself;
261        } else if ($myself->validateIP($address)) {
262            $myself->ip = $address;
263            return $myself;
264        } else {
265            return $this->pear->raiseError("invalid IP address");
266        }
267    }
268
269    // }}}
270    // {{{ calculate()
271
272    /**
273     * Calculates network information based on an IP address and netmask.
274     *
275     * Fully populates the object properties based on the IP address and
276     * netmask/bitmask properties.  Once these two fields are populated,
277     * calculate() will perform calculations to determine the network and
278     * broadcast address of the network.
279     *
280     * @return mixed     true if no errors occured, otherwise PEAR_Error object
281     */
282    function calculate()
283    {
284        $validNM = $GLOBALS['Net_IPv4_Netmask_Map'];
285
286        if (! is_a($this, "net_ipv4")) {
287            $myself = new Net_IPv4;
288            return $this->pear->raiseError("cannot calculate on uninstantiated Net_IPv4 class");
289        }
290
291        /* Find out if we were given an ip address in dot quad notation or
292         * a network long ip address.  Whichever was given, populate the
293         * other field
294         */
295        if (strlen($this->ip)) {
296            if (! $this->validateIP($this->ip)) {
297                return $this->pear->raiseError("invalid IP address");
298            }
299            $this->long = $this->ip2double($this->ip);
300        } else if (is_numeric($this->long)) {
301            $this->ip = long2ip($this->long);
302        } else {
303           return $this->pear->raiseError("ip address not specified");
304        }
305
306        /*
307         * Check to see if we were supplied with a bitmask or a netmask.
308         * Populate the other field as needed.
309         */
310        if (strlen($this->bitmask)) {
311            $this->netmask = $validNM[$this->bitmask];
312        } else if (strlen($this->netmask)) {
313            $validNM_rev = array_flip($validNM);
314            $this->bitmask = $validNM_rev[$this->netmask];
315        } else {
316            return $this->pear->raiseError("netmask or bitmask are required for calculation");
317        }
318        $this->network = long2ip(ip2long($this->ip) & ip2long($this->netmask));
319        $this->broadcast = long2ip(ip2long($this->ip) |
320                (ip2long($this->netmask) ^ ip2long("255.255.255.255")));
321        return true;
322    }
323
324    // }}}
325    // {{{ getNetmask()
326
327	function getNetmask($length)
328	{
329		if (! PEAR::isError($ipobj = Net_IPv4::parseAddress("0.0.0.0/" . $length))) {
330			$mask = $ipobj->netmask;
331			unset($ipobj);
332			return $mask;
333		}
334		return false;
335	}
336
337    // }}}
338    // {{{ getNetLength()
339
340	function getNetLength($netmask)
341	{
342		if (! PEAR::isError($ipobj = Net_IPv4::parseAddress("0.0.0.0/" . $netmask))) {
343			$bitmask = $ipobj->bitmask;
344			unset($ipobj);
345			return $bitmask;
346		}
347		return false;
348	}
349
350    // }}}
351    // {{{ getSubnet()
352
353	function getSubnet($ip, $netmask)
354	{
355		if (! PEAR::isError($ipobj = Net_IPv4::parseAddress($ip . "/" . $netmask))) {
356			$net = $ipobj->network;
357			unset($ipobj);
358			return $net;
359		}
360		return false;
361	}
362
363    // }}}
364    // {{{ inSameSubnet()
365
366	function inSameSubnet($ip1, $ip2)
367	{
368		if (! is_object($ip1) || strcasecmp(get_class($ip1), 'net_ipv4') <> 0) {
369			$ipobj1 = Net_IPv4::parseAddress($ip1);
370			if (PEAR::isError($ipobj)) {
371                return $this->pear->raiseError("IP addresses must be an understood format or a Net_IPv4 object");
372			}
373		}
374		if (! is_object($ip2) || strcasecmp(get_class($ip2), 'net_ipv4') <> 0) {
375			$ipobj2 = Net_IPv4::parseAddress($ip2);
376			if (PEAR::isError($ipobj)) {
377                return $this->pear->raiseError("IP addresses must be an understood format or a Net_IPv4 object");
378			}
379		}
380		if ($ipobj1->network == $ipobj2->network &&
381				$ipobj1->bitmask == $ipobj2->bitmask) {
382				return true;
383		}
384		return false;
385	}
386
387    // }}}
388    // {{{ atoh()
389
390    /**
391     * Converts a dot-quad formatted IP address into a hexadecimal string
392     * @param  string $addr IP-adress in dot-quad format
393     * @return mixed        false if invalid IP and hexadecimal representation as string if valid
394     */
395    function atoh($addr)
396    {
397        if (! Net_IPv4::validateIP($addr)) {
398            return false;
399        }
400        $ap = explode(".", $addr);
401        return sprintf("%02x%02x%02x%02x", $ap[0], $ap[1], $ap[2], $ap[3]);
402    }
403
404    // }}}
405    // {{{ htoa()
406
407    /**
408     * Converts a hexadecimal string into a dot-quad formatted IP address
409     * @param  string $addr IP-adress in hexadecimal format
410     * @return mixed        false if invalid IP and dot-quad formatted IP as string if valid
411     */
412    function htoa($addr)
413    {
414        if (preg_match("/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i",
415                    $addr, $regs)) {
416            return hexdec($regs[1]) . "." .  hexdec($regs[2]) . "." .
417                   hexdec($regs[3]) . "." .  hexdec($regs[4]);
418        }
419        return false;
420    }
421
422    // }}}
423    // {{{ ip2double()
424
425    /**
426     * Converts an IP address to a PHP double.  Better than ip2long because
427     * a long in PHP is a signed integer.
428     * @param  string $ip  dot-quad formatted IP adress
429     * @return float       IP adress as double - positive value unlike ip2long
430     */
431    function ip2double($ip)
432    {
433        return (double)(sprintf("%u", ip2long($ip)));
434    }
435
436    // }}}
437    // {{{ ipInNetwork()
438
439    /**
440     * Determines whether or not the supplied IP is within the supplied network.
441     *
442     * This function determines whether an IP address is within a network.
443     * The IP address ($ip) must be supplied in dot-quad format, and the
444     * network ($network) may be either a string containing a CIDR
445     * formatted network definition, or a Net_IPv4 object.
446     *
447     * @param  string  $ip      A dot quad representation of an IP address
448     * @param  string  $network A string representing the network in CIDR format or a Net_IPv4 object.
449     * @return bool             true if the IP address exists within the network
450     */
451    function ipInNetwork($ip, $network)
452    {
453        if (! is_object($network) || strcasecmp(get_class($network), 'net_ipv4') <> 0) {
454            $network = Net_IPv4::parseAddress($network);
455        }
456        if (strcasecmp(get_class($network), 'pear_error') === 0) {
457            return false;
458        }
459        $net = Net_IPv4::ip2double($network->network);
460        $bcast = Net_IPv4::ip2double($network->broadcast);
461        $ip = Net_IPv4::ip2double($ip);
462        unset($network);
463        if ($ip >= $net && $ip <= $bcast) {
464            return true;
465        }
466        return false;
467    }
468
469    // }}}
470}
471
472// }}}
473
474/*
475 * vim: sts=4 ts=4 sw=4 cindent fdm=marker
476 */
477?>
478