Networking With PEAR

Trawl the archives for PHP versions of common networking tools.

Widget Works

You may not know this, but PEAR, the PHP Extension and Application Repository, has a whole category of classes devoted specifically to networking. This category contains widgets for a variety of different tasks, ranging from validating IP addresses and performing DNS queries to retrieving mail headers and pinging remote hosts to see whether they're operational.

This article will introduce you to some of these widgets, by taking you on a whistle-stop tour of PEAR's networking tools and demonstrating some of the things they let you do. If you're a developer of Web applications, and if you've ever wished you had a way of accessing basic networking tools like dig, ping, traceroute or whois through PHP, keep reading, because your wishes are about to come true.

A Ping In Time

Let's start with something basic: pinging a remote host to see if it's "up". PEAR lets you do this via the Net_Ping package, available at http://pear.php.net/package/Net_Ping. This package internally searches for the system's ping binary, and uses it to test the status of the remote host. Take a look at an example which demonstrates it in action:

<?php
// include class
include("Net/Ping.php");

// initialize object
$ping = Net_Ping::factory();

// ping host
$result = $ping->ping("example.com");
print_r($result);
?>

Pretty simple, this - initialize an object of the class, and then call its ping() method with the name or IP address of the remote host. You can also access individual elements of the response, by calling different methods of the class. Take a look at this variant, which uses built-in class methods to retrieve the total number of bytes sent, the minimum and maximum response time, and the packet TTL.

<?php
// include class
include("Net/Ping.php");

// initialize object
$ping = Net_Ping::factory();

// ping host
$result = $ping->ping("example.com");

// print ping results
echo "Target IP: " . $result->getTargetIp() . "\n";
echo "Total bytes sent: " . $result->getBytesTotal() . "\n";
echo "TTL: " . $result->getTTL() . "\n";
echo "Min: " . $result->getMin() . "\n";
echo "Max: " . $result->getMax() . "\n";
echo "Raw data: " . implode("\n", $result->getRawData());
?>

If you're looking to build a nice Web-based interface to the ping command, these methods can come in handy to format the response output. Pay special attention to the getRawData() method, as this returns the raw output of the system's ping command.

The Scenic Route

You've figured out that the remote host is operational...now how about figuring out how to get to it? Yup, PEAR comes with an interface to the traceroute command as well, and it goes by the name of Net_Traceroute. You can get it from http://pear.php.net/package/Net_Traceroute. Like Net_Ping, this package too uses the system's traceroute command to calculate the number of hops between the current and destination host. Take a look:

<?php
// include class
include("Net/Traceroute.php");

// initialize object
$tr = Net_Traceroute::factory();

// trace route to host
$result = $tr->traceroute("example.com");
print_r($result);
?>

As before, the usage procedure is fairly simple - initialize an object of the class, and then call the traceroute() method with the remote host name or IP address as argument to calculate the route to it.

As with Net_Ping, it's possible to access the individual elements of the response, such as the number of hops or the packet TTL, using special class methods. The following revision of the previous script demonstrates:

<?php
// include class
include("Net/Traceroute.php");

// initialize object
$tr = Net_Traceroute::factory();

// trace route to host
$result = $tr->traceroute("example.com");

// print results
echo "Target IP: " . $result->getTargetIp() . "\n";
echo "Number of hops: " . $result->getHops() . "\n";
echo "TTL: " . $result->getTTL() . "\n";
echo "Raw data: " . implode("\n", $result->getRawData());
?>

Doctor Who

Want to create a Web-based interface to look up domain records? It's easy to do with the Net_Whois package, which can look up information on any registered Internet domain. You can download and install this package from http://pear.php.net/package/Net_Whois; once you've got it installed, give it a spin with the following script:

<?php
// include class
include("Net/Whois.php");

// initialize object
$whois = new Net_Whois();

// perform WHOIS query
$result = $whois->query("example.com");
print_r($result);
?>

You can easily modify the previous script to create an application that allows the user to enter a domain name for online WHOIS lookup. Here's how:

<html>
<head></head>
<body>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    Doman name: <input type="text" name="q">
    <input type="submit" name="Look up domain">
</form>

<?php
if ($_POST['q']) {
    // include class
    include("Net/Whois.php");

    // initialize object
    $whois = new Net_Whois();

    // perform WHOIS query
    $result = $whois->query($_POST['q']);
    print_r($result);
}
?>

</body>
</html>

Here the domain name submitted by the user is passed to the object's method. This query() method then requests the domain record from the appropriate domain registrar, and displays the response as a Web page.

What's In A Name?

Need the IP address for a domain, but don't have access to PHP's built-in DNS functions? Use PEAR's Net_DNS package from http://pear.php.net/package/Net_DNS, a sophisticated toolkit which lets you indulge in all manner of DNS trickery. This package actually contains a series of different classes, each one performing a different task; however, the one you're most likely to use is the Net_DNS_Resolver class, which lets you resolve domain names to IP addresses, and vice-versa. Here's an example of it in action:

<?php
// include class
include("Net/DNS.php");

// initialize object
$dns = new Net_DNS_Resolver();

// perform DNS query
$result = $dns->search("example.com");
print_r($result);
?>

In this example, the search() method queries the name server to retrieve DNS records for the domain example.com.

You can force the search() method to query for a particular type of domain record - for example, a CNAME or MX record - by adding the record type to the search() method call. Here's an example:

<?php
// include class
include("Net/DNS.php");

// initialize object
$dns = new Net_DNS_Resolver();

// perform DNS query
$result = $dns->search("yahoo.com", "MX");
print_r($result);
?>

It's important to note that the Net_DNS class doesn't depend on system libraries to perform DNS lookups; instead, it directly communicates with the DNS server using a socket connection. In order to do this, it depends on information in the system's resolv.conf file. If this file is not present, the Net_DNS package will not function correctly.

You've Got Mail

PEAR is no slouch when it comes to sending or receiving mail, either. The Net_SMTP and Net_POP3 packages, available from http://pear.php.net/package/Net_SMTP and http://pear.php.net/package/Net_POP3 respectively, provide implementations of the SMTP and POP3 protocols. These packages provide method calls to directly communicate with an SMTP or POP3 server for email transactions.

To see how this works, consider the next example, which performs an SMTP transaction:

<?php
// include class
include("Net/SMTP.php");

// initialize object
$smtp = new Net_SMTP("example.com", 25);

// attempt connection to server
$cmd = $smtp->connect();
if ($smtp->isError($cmd)) {
    die("Error in connection: " . $cmd->message . "\n");
}

// send RSET command
$cmd = $smtp->rset();
if ($smtp->isError($cmd)) {
    die("Error in RSET: " . $cmd->getMessage() . "\n");
}

// send MAIL FROM: command
$cmd = $smtp->mailFrom("joe@example.com");
if ($smtp->isError($cmd)) {
    die("Error in MAIL FROM: " . $cmd->getMessage() . "\n");
}

// send RCPT TO: command
$cmd = $smtp->rcptTo("john@example.com");
if ($smtp->isError($cmd)) {
    die("Error in RCPT TO: " . $cmd->getMessage() . "\n");
}

// send DATA: command
$cmd = $smtp->data("Subject: Hi\n\nHello John,\n\nThis is a test\n\nJoe");
if ($smtp->isError($cmd)) {
    die("Error in DATA: " . $cmd->getMessage() . "\n");
}

// disconnect from server
$smtp->disconnect();
echo "Message sent\n";
?>

If you've ever manually performed an SMTP transaction, this should be familiar to you. First, an instance of the Net_SMTP class is created, and initialized with the host name and port number of the target SMTP server. A connection is attempted via the connect() method and, once connected, the SMTP RSET, MAIL FROM, RCPT TO and DATA commands are transmitted to the server via class methods of the same names. Once the message is transmitted, the server connection is closed with a call to the disconnect() method. Errors, if any, are intercepted as they occur.

A similar procedure is followed when using the Net_POP3 class to retrieve email from a POP3-compliant mailbox. Here's a script which demonstrates by retrieving the message headers for new mail:

<?php
// include class
include("Net/POP3.php");

// initialize object
$pop3 = new Net_POP3();

// attempt connection to server
if (!$pop3->connect("example.com", 110)) {
    die("Error in connection");
}

// attempt login
$ret = $pop3->login("john", "doe");
if (is_a($ret, 'PEAR_Error')) {
    die("Error in authentication: " . $ret->getMessage());
}

// print number of messages found
echo $pop3->numMsg() . " message(s) in mailbox\n";

// print message headers
if ($pop3->numMsg() > 0) {
    for ($x=1; $x<=$pop3->numMsg(); $x++) {
        $hdrs = $pop3->getParsedHeaders($x);
        echo $hdrs['From'] . "\n" . $hdrs['Subject'] . "\n\n";
    }
}

// disconnect from server
$pop3->disconnect();
?>

Here too, the method call sequence mimics the commands in a normal POP3 session. First, a connection is opened to the POP3 server with the connect() method, and the login() method internally issues the USER and PASS commands to gain access to the user's mailbox. The number of messages in the mailbox is retrieved with a call to the numMsg() method, while the getParsedHeaders() method retrieves the headers for each message as an associative array. The connection is then terminated with a call to the disconnect() method.

The Net_POP3 class also comes with built-in methods to retrieve the message size (in bytes) as well as the message body, and can also be used to mark messages for deletion.

A Tough Client

If you're creating a Web site with complicated client-side whiz-bangs, you might need to find out which browser the user has and add special code to handle any quirks it might have. This is best handled with a professional browser detection library, such as the Net_UserAgent_Detect package from http://pear.php.net/package/Net_UserAgent_Detect. This package identifies the client browser, version and platform through the User-Agent string, and comes in very handy when writing conditional tests to handle different browser versions. Here's an example:

<?php
// include class
include("Net/Detect.php");

// initialize object
$ua = new Net_UserAgent_Detect();

// get browser and OS
echo "Browser: " . $ua->getBrowserString() . "\n";
echo "OS: " . $ua->getOSString() . "\n";
?>

Here, the getBrowserString() and getOSString() methods return the browser and platform name and version respectively. While this is useful in itself, even more useful are the isIE(), ieNetscape() and isNavigator() methods, which can be wrapped around browser-specific code routines. Here's a simple example which illustrates these methods:

<?php
// include class
include("Net/Detect.php");

// initialize object
$ua = new Net_UserAgent_Detect();

// test browser
if ($ua->isIE()) {
    echo "Browser is Internet Explorer";
} elseif ($ua->isNavigator()) {
    echo "Browser is Netscape Navigator";
} elseif ($ua->isNetscape()) {
    echo "Browser is a Netscape/Mozilla browser";
}
?>

The Wrong Address

When it comes to working with IP addresses and URLs, PEAR comes with a number of options. First on the list are the Net_Ipv4 and Net_Ipv6 packages, available from http://pear.php.net/package/Net_IPv4 and http://pear.php.net/package/Net_IPv6 respectively. Both these packages come with methods to test the validity of an IP address, and the Net_Ipv4 package also includes methods to parse an IP address, calculate netmasks and subnet masks and deduce whether a particular IP address belongs to a particular network.

To begin with, here's a simple example that shows how the Net_IPv4 package can be used to validate addresses and netmasks:

<?php
// include class
include("Net/IPv4.php");

// initialize object
$ipv4 = new Net_IPv4();

// validate IP address
echo $ipv4->validateIp("192.168.0.10") ? "IP is valid" : "IP is invalid";

// validate netmask
echo $ipv4->validateNetmask("255.255.255.0") ? "Netmask is valid" : "Netmask is invalid";
?>

You can do the same thing with the Net_IPv6 package, which provides a checkIPv6() method to test the validity of IPv6 addresses.

<?php
// include class
include("Net/IPv6.php");

// initialize object
$ipv6 = new Net_IPv6();

// validate IP address
echo $ipv6->checkIPv6("3ffe:400:100:f101:2e0:18ff:fe90:9205") ? "IP is valid" : "IP is invalid";
?>
The ipInNetwork() method accepts two arguments, an IPv4 address and a network identifier, and tells you whether the former is a part of the latter. Here's an example:
<?php
// include class
include("Net/IPv4.php");

// initialize object
$ipv4 = new Net_IPv4();

// check IP address within network
echo $ipv4->ipInNetwork("192.168.0.10", "192.168.0.0/24") ? "In same network" : "In different network";
?>

Finally, a utility class that doesn't really have anything to do with networking, but still finds itself in the PEAR networking category. The Net_URL class, available from http://pear.php.net/package/Net_URL, makes it easy to parse a URL into its individual components. Here's an example of how it works:

<?php
// include class
include("Net/URL.php");

// initialize object
$url = new Net_URL("http://www.example.com/path/file.php?a=1&b=3");

// parse URL
echo "Domain: " . $url->host . "\n";
echo "Path: " . $url->path . "\n";
echo "Protocol: " . $url->protocol . "\n";
?>

Here, the URL provided to the class constructor is broken down into individual components, which can then be accessed as object properties. Use the print_r() function to see the complete list of available properties, as in the next example:

<?php
// include class
include("Net/URL.php");

// initialize object
$url = new Net_URL("http://www.example.com/path/file.php?a=1&b=3");

// parse URL
print_r($url);
?>

And that's about all I have time for. Hopefully, this article introduced you to some of the hidden gems in PEAR's networking collection, and gave you some ideas for the next time you need to add networking functions to your application. Happy coding!

This article was first published on03 Oct 2006.