From 93dfd86d1f8b8f1250915a8b5527f9c2cbac08a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 6 Sep 2026 10:53:35 +0000 Subject: [PATCH] Add DNS Query Options section to usage docs Co-authored-by: purplepixie <1479871+purplepixie@users.noreply.github.com> --- docs/README.md | 78 +++++++++++++++ docs/_config.yml | 16 +++ docs/examples.md | 87 ++++++++++++++++ docs/technical.md | 7 ++ docs/usage.md | 245 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 433 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/_config.yml create mode 100644 docs/examples.md create mode 100644 docs/technical.md create mode 100644 docs/usage.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9fc02de --- /dev/null +++ b/docs/README.md @@ -0,0 +1,78 @@ +# phpdns: PHP DNS Client Library + +## Overview + +The PHP DNS client is a GPL set of PHP classes originally developed by [David Cutting](https://davecutting.uk) providing a direct socket-level domain name service client API. Originally developed to be a testing module for the [FreeNATS network monitor](https://www.purplepixie.org/freenats/) it was decided to package it up as a standalone library as well. + +> Although there are plenty of other DNS classes/clients out there I found them to either be too overblown or actually non-functional. This API is intended to be a half-way house offering direct-to-server queries, the ability to process the response in detail but still with a simple interface for the programmer. -- *David Cutting* + +The library is now held at [github.com/purplepixie/phpdns](https://github.com/purplepixie/phpdns/) and contains contributions from a number of people both credited in the commits and prior to moving to Github. + +## Licence and Copyright + +Unless otherwise stated in a specific file the PHP DNS Query Library is (C) Copyright 2008-2023 [PurplePixie Systems / David Cutting](https://purplepixie.org) and all rights are reserved. + +The software is provided on an "as-is" basis without warranty or liability of any kind under the [GNU General Public Licence (GPL)](http://www.gnu.org/licences/gpl.html) (version 3 or later at your discretion). + +## Errors and Bugs + +Should be reported through raising an [issue on Github](https://github.com/purplepixie/phpdns/issues). You are also very welcome to contribute fixes to the code (or documentation) via a PR. + +# Installing / Obtaining phpdns + +There are two main ways to get phpdns: via composer or using the source code directly. + +## Composer + +By far the easiest way to use phpdns is through the use of [composer](https://getcomposer.org/) which allows you to use the latest release via [Packagist](https://packagist.org/packages/purplepixie/phpdns). With composer installed: + +```bash +composer require purplepixie/phpdns +`````` + +Will install the latest release of phpdns which can then be used with autoload. + +You can now use the classes in the ```PurplePixie\PhpDns``` namespace through autoload, for example: + +```php +// Autoload +require("vendor/autoload.php"); +// Namespace for DNSQuery +use PurplePixie\PhpDns\DNSQuery; +// Do a query +$query = new DNSQuery("8.8.8.8"); +$result = $query->query('purplepixie.org', \PurplePixie\PhpDns\DNSTypes::NAME_A); +print_r($result); +``` + +## Using Source Directly + +You can just download and use the PHP source files directly, either by downloading an archive (this could be the [current codebase](https://github.com/purplepixie/phpdns/archive/refs/heads/master.zip) or a [specific release tag](https://github.com/purplepixie/phpdns/tags)) or by cloning [the repository](https://github.com/purplepixie/phpdns). + +To then use the library you must include the relevant files. You can do this manually or just include the ```dns.inc.php``` file (all this assumes you are above the ```src``` folder, otherwise you will need to adjust as required). + +Example of using source code manually: + +```php +// Include the files - note: require_once 'dns.inc.php' will do this for you +require_once __DIR__ . '/src/PurplePixie/PhpDns/DNSAnswer.php'; +require_once __DIR__ . '/src/PurplePixie/PhpDns/DNSQuery.php'; +require_once __DIR__ . '/src/PurplePixie/PhpDns/DNSResult.php'; +require_once __DIR__ . '/src/PurplePixie/PhpDns/DNSTypes.php'; +// Namespace for DNSQuery for ease +use PurplePixie\PhpDns\DNSQuery; +// Do a query +$query = new DNSQuery("8.8.8.8"); +$result = $query->query('purplepixie.org', \PurplePixie\PhpDns\DNSTypes::NAME_A); +print_r($result); +``` + +# Using phpdns + +For usage examples and instructions please see the [user documentation](./usage.md). There is also a [technical reference document](./technical.md). + +# Contributing to phpdns + +Contributions to phpdns are very welcome and the codebase is made stronger by the many contributions we have already seen. Feel free to contribute via Pull Requests in github for the main codebase or the documentation (main codebase is in the ```master``` branch and other dev branches, the docs are in the ```website``` branch). + +If you have found phpdns particularly useful then please feel free to give us a star on Github and if you're feeling financially generous make a donation to a charity of your choice (phpdns is *free* software in every sense). \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..b5d4328 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,16 @@ +lsi: false +safe: true +source: /docs +incremental: false +highlighter: rouge +remote_theme: pages-themes/slate@v0.2.0 +plugins: +- jekyll-remote-theme # add this line to the plugins list if you already have one +gist: + noscript: false +kramdown: + math_engine: mathjax + syntax_highlighter: rouge +title: 'phpdns' +description: 'PHP DNS Network Client' +show_downloads: false \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..17c3baf --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,87 @@ +### [<< Back to the main page <<](./) + +# phpdns Examples + +Here are some examples of phpdns being used. Please feel free to contribute to the documentation with specific interesting usage examples. + +## Recursive Resolution + +In normal operation phpdns will ask a single question of a given nameserver. This is fine when the nameserver is able to provide the answer (for example you have asked a local recursive resolver or an authoritative server for a zone) but is not representative of how DNS may actually work. + +A full recursive DNS resolution, for example as you see with ```dig +trace``` will start at the *root servers* and gradually work down. For example to resolve ```davecutting.uk``` A record the root servers won't know the answer but will provide the nameservers for .uk. The .uk nameservers won't know the answer but will know the next step, and so on. + +You can use phpdns to perform a full recursive search but you need to handle the recursive elements, it will only make the connections to each server in turn. + +Note in this example we start and continue to use *names* for the nameservers, so an internal DNS resolution is performed. We could once running add another step to use the additional section which usually provides glue records. + +```php +query($question, $type); + // error, stop here + if ($answer === false || $query->hasError()) + { + echo "Error: ".$query->getLasterror()."\n"; + } + else // successful query + { + if ($answer->count() > 0) // found an answer! + { + foreach($answer as $result) + { + echo $result->getString()." from ".$server."\n"; + } + } + else // not found one - let's check the returned nameservers + { + $nameservers = $query->getLastnameservers(); + if ($nameservers->count() == 0) // no route forward, exit + { + echo "No more nameservers returned.\n"; + } + else // found a nameserver(s) to move on to + { + $ns = $nameservers->current(); + $newserver = $ns->getData(); // get the nameserver + // and recurse + DNSRecurse($question, $type, $newserver); + } + } + } +} + +// start with a list of nameservers +$rootServers = array( + "a.root-servers.net", + "b.root-servers.net", + "c.root-servers.net", + "d.root-servers.net", + "e.root-servers.net", + "f.root-servers.net", + "g.root-servers.net", + "h.root-servers.net", + "i.root-servers.net" +); + +// pick one at random +$startServer = $rootServers[array_rand($rootServers)]; + +echo "Starting from root server: ".$startServer."\n"; + +// question and type we're asking +$question = "www.purplepixie.org"; +$type = \PurplePixie\PhpDns\DNSTypes::NAME_A; + +// and kick the process off +DNSRecurse($question, $type, $startServer); +``` \ No newline at end of file diff --git a/docs/technical.md b/docs/technical.md new file mode 100644 index 0000000..540b26b --- /dev/null +++ b/docs/technical.md @@ -0,0 +1,7 @@ +### [<< Back to the main page <<](./) + +# phpdns Technical Documentation + +Technical documentation *may* be forthcoming but for the moment please just refer to the code itself and comments contained [in the repository](https://github.com/purplepixie/phpdns/). + +You may also find the [user documentation](./usage) and [examples](./examples) helpful. \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..38da5ac --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,245 @@ +### [<< Back to the main page <<](./) + +# phpdns User Documentation + +Having successfully installed phpdns (if not see the "Installing / Obtaining phpdns" section of the [main index page](./)) you are now able to use it. + +**Note:** This documentation assumes you have installed phpdns and are able to include it. + +The phpdns library will on request make a network connection to a remote DNS server (using UDP or TCP) to ask a question (the query) and receive a response (an answer containing one or more results). The library consists of four main classes: + +- ```DNSQuery``` - the primary class with which a user interfaces, builds and makes the actual query to the remote server. +- ```DNSAnswer``` - a list/wrapper class containing the ```DNSResult```(s) from the remote server. +- ```DNSResult``` - an actual result response from the remote server. +- ```DNSType``` - a list of the types of DNS records that can be used in the query or in a result response. + +## A Simple Example + +Here is a simple example to find an A record and output the result: + +```php +// Use this namespace for ease +use PurplePixie\PhpDns\DNSQuery; + +// Remote DNS Server +$dnsServer = "8.8.8.8"; +// The question we are going to ask for an answer to +$question = "www.purplepixie.org"; +// The type of record/answer we are asking for +$type = \PurplePixie\PhpDns\DNSTypes::NAME_A; + +try +{ + // Create the DNSQuery instance for the remote server + $query = new DNSQuery($dnsServer); + + // Perform an actual query for question and type + // note this will return a DNSAnswer instance + $answer = $query->query($question, $type); + + // Check for an error + if ($answer === false || $query->hasError()) + { + echo "Error: ".$query->getLasterror()."\n"; + } + else // successful query + { + foreach($answer as $result) + { + // we are only after results of the type we want + if ($result->getType() == $type) + { + echo $question." has IP address ".$result->getData()."\n"; + // or we can use the already built English string + echo $result->getString()."\n"; + } + } + } +} +catch(\Exception $e) +{ + print_r($e->getMessage()); +} +``` + +If all goes well the output should be something like: +``` +www.purplepixie.org has IP address 151.80.237.60 +www.purplepixie.org has IPv4 address 151.80.237.60 +``` + +The first output is from our script writing the question and the "data" result, the second when we output the "string" property of the answer which is the specific record in human-readable form (if the type is known). + +## A Note of Warning and Why Check the Answer Type Above + +DNS is not an entirely straightforward protocol and things which on the surface may seem simple may not be when you delve deeper (if you already understand DNS then skip ahead). + +For example an IP lookup (A record lookup) for a host on a specific DNS server may well not just return a single answer record containing the IP address. The host may be multi-honed and return multiple records any which may not be an IP address but a CNAME alias. The namesever you are querying may not do a recursive or cache lookup for you and so return no answers even though the domain and host do exist. + +For this reason we must actually process the results (unless of course we just want to see what data is provided for a query and not actually do anything with that answer). + +Hosts with just a CNAME alias will not be resolved to an IP address in the answer section. If we ask for the A record of www.somehost.com we may just get back a CNAME of webhost.somehost.com. To turn this into an IP address we must then either hope it was provided in the additional answer section (and check - see below for details) or perform another A record lookup on webhost.somehost.com. + +If you just want an IP address for a host then either PHP's inbuilt gethostbyname() or this API's SmartALookup() (see below) are probably what you're after rather than a full blown query. + +## DNS Query Options + +The ```DNSQuery``` constructor takes a number of options controlling how the query is made, all of which (other than the server itself) have sensible defaults and can also be read and changed after the object has been created using the corresponding getter and setter methods. + +```php +public function __construct(string $server, int $port = 53, int $timeout = 60, bool $udp = true, bool $debug = false, bool $binarydebug = false) +``` + +- ```server``` - the hostname or IP address of the remote DNS server to query. There is no default, this is a required parameter. +- ```port``` - the remote port to connect to on the DNS server. Defaults to ```53```, the standard DNS port. +- ```timeout``` - the connection/response timeout in seconds. Defaults to ```60```. +- ```udp``` - whether to use UDP (```true```, the default) or TCP (```false```) to make the query. +- ```debug``` - whether to output debug information about the query process. Defaults to ```false```. +- ```binarydebug``` - whether to output binary/hex debug information about the raw data sent and received. Defaults to ```false```. + +For example to query a server on a non-standard port over TCP with a shorter timeout: + +```php +$query = new DNSQuery("some.server.com", 5353, 10, false); +``` + +### Getters and Setters + +Each of the above options (plus the server itself) can be read and changed after construction using the following methods: + +- ```DNSQuery::getServer()``` / ```DNSQuery::setServer(string $value)``` - the remote server hostname or IP address. +- ```DNSQuery::getPort()``` / ```DNSQuery::setPort(int $value)``` - the remote port. +- ```DNSQuery::getTimeout()``` / ```DNSQuery::setTimeout(int $value)``` - the connection/response timeout in seconds. +- ```DNSQuery::getUdp()``` / ```DNSQuery::setUdp(bool $value)``` - whether UDP (```true```) or TCP (```false```) is used. +- ```DNSQuery::getDebug()``` / ```DNSQuery::setDebug(bool $value)``` - whether debug output is enabled. +- ```DNSQuery::getBinarydebug()``` / ```DNSQuery::setBinarydebug(bool $value)``` - whether binary/hex debug output is enabled. + +For example to change the port and switch to TCP after the object has already been created: + +```php +$query = new DNSQuery("some.server.com"); +$query->setPort(5353); +$query->setUdp(false); +``` + +There are two further boolean options, not passed to the constructor but with their own getters and setters, which control whether exceptions are thrown on connection or response errors (see the "Error Handling" section below for more detail): + +- ```DNSQuery::getConnectionException()``` / ```DNSQuery::setConnectionException(bool $value)``` - whether a connection error throws a ```ConnectionException```. Defaults to ```false```. +- ```DNSQuery::getResponseException()``` / ```DNSQuery::setResponseException(bool $value)``` - whether an invalid response throws an ```InvalidResponse``` exception. Defaults to ```false```. + +## Answer and Query Types + +Record (query and result) types the API supports and should return sensible data for numerous and include the major record types such as A, NS, PTR, MX, CNAME, TXT and SOA. For a full list see the [DNSTypes Class](https://github.com/purplepixie/phpdns/blob/master/src/PurplePixie/PhpDns/DNSTypes.php). + +Asking for an unsupported type will cause the query to fail. Unsupported types which are returned as result records will have null "string" and "typeid" properties but will contain the binary data in "data" and the decimal record type in "type". + +## Answer Results + +If a query succeeds it returns an iteratable ```DNSAnswer``` object containing zero or more ```DNSResult``` objects with the specific answers. a counter property "count" indicating the number of answer records returned and an array of DNSResult objects containing each of these records in turn. + +You can get a count of the results using ```DNSAnswer::count()``` and either iterate using standard PHP iterations such as ```foreach``` or accessing by numeric key. + +Each ```DNSResult``` object contains a lot of data, key items of which are: + +- ```DNSResult::getType()``` - returns a textual version of the record type (i.e. "A"). +- ```DNSResult::getTypeid()``` - returns the numeric record type which can be mapped to ```DNSType``` if supported. +- ```DNSResult::getClass()``` - returns the numeric class type (decimal) . +- ```DNSResult::getData()``` - returns the data returned by the query (i.e. IP address or hostname). +- ```DNSResult::getDomain()``` - returns the domain name data is for. +- ```DNSResult::getString()``` - returns a string representation of the answer (i.e. www.fish.sea has address x.y.z). +- ```DNSResult::getExtras()``` - returns a specific array of extra fields (i.e. "level" for MX records) - see below. + +### Type-specific Extras + +Some result types have extended extra information which will be in array form in the "extras" property of a ```DNSResult``` object. + +MX record types for example have the decimal mail exchange priority in ```extas['level']``` + +SOA record types have the responsible contact for the domain in ```extras['responsible']``` as well as the following: + +- ```extras['serial']``` - domain serial +- ```extras['refresh']``` - domain refresh +- ```extras['retry']``` - domain retry +- ```extras['expiry']``` - domain expiry +- ```extras['minttl']``` - domain mimumum time-to-live (TTL) + + +## Smart A Lookup / Smart AAAA Lookup + +Because doing an A/AAAA lookup won't always return an IP address and sometimes you're just after an IP address (not potentially a list of them and aliases etc) the ```DNSQuery``` class provides the ```DNSQuery::SmartALookup()``` and ```DNSQuery::SmartAAAALookup()``` methods. + +These methods takes a hostname (and an optional recursion depth which defaults to 5 if not passed) and returns an IPv4/v6 address or a null string if lookup failed (you can then check the ```DNSQuery``` error property to see if the query actually failed or just returned no results). + +If the result data contains an IP address it will be returned (first preference). If no IP addresses were provided but an alias CNAME is given then this will be looked up (recursing up to a depth of five aliases; with requests to the same DNS server). + +In effect this is a nameserver-specific version of [gethostbyname()](https://www.php.net/manual/en/function.gethostbyname.php) but returns a null string rather than the unmodified IP on failure. + +**Note:** Smart A/AAAA Lookup only works on a single server, if you want to do a recursive resolution to different nameservers you will need to do that yourself; see the example on the [examples page](./examples). + +## Error Handling + +By default the ```DNSQuery``` class will return errors by (1) return a boolean ```false``` result to a method call, (2) setting the error flag and (3) putting a message into the ```lastError``` buffer. + +The easiest way to check if an error occured in the last transaction therefore is to check the result of the ```hasError()``` method. + +```php +$query = new DNSQuery("some.server.com"); +$answer = $query->query("a.question.here", DNSTypes::NAME_A); +if ($query->hasError()) +{ + // error handling code goes here + echo "Error: ".$query->getLasterror()."\n"; +} +``` + +In the above example assuming that a fatal error occured the ```$answer``` would be a boolean ```false``` also (though there are times where an error can occur but an answer is still returned). + +There are some errors which will throw exceptions such as an invalid or unknown type being used in a query. + +Connection errors can **optionally** throw an exception with a message (in which case the ```hasError()``` flag will also be set, with the message both in the exception and in the ```lastError``` buffer). + +This behaviour can be triggered by setting the ```ConnectionException``` flag using ```setConnectionException(true)```. + +```php +$query = new DNSQuery("some.server.com"); + +$query->setConnectionException(true); + +try +{ + $a = $query->query("a.question.here", DNSTypes::NAME_A); +} +catch(\Exception $e) +{ + // error handling + print_r($e); +} +``` + +Sometimes (especially on UDP requests) data will be returned but the format is incorrect and thus the ``unpack()`` routine fails to find the right header. + +This will output an error and optionally throw an ``InvalidReponse`` exception if the flag is set with ``setResponseException(true)``. + +## RCODE in Answer + +DNS servers will return in their answer a Response Code (RCODE) from [RFC 1035](https://www.ietf.org/rfc/rfc1035.txt) defined as follows: + +``` +RCODE Response code - this 4 bit field is set as part of responses. The values have the following interpretation: + +0 No error condition + +1 Format error - The name server was unable to interpret the query. + +2 Server failure - The name server was unable to process this query due to a problem with the name server. + +3 Name Error - Meaningful only for responses from an authoritative name server, this code signifies that the domain name referenced in the query does not exist. + +4 Not Implemented - The name server does not support the requested kind of query. + +5 Refused - The name server refuses to perform the specified operation for policy reasons. For example, a name server may not wish to provide the information to the particular requester, or a name server may not wish to perform a particular operation (e.g., zone transfer) for particular data. + +6-15 Reserved for future use. +``` + +Once a ```DNSAnswer``` object is returned the RCODE can be read through ```DNSAnswer::getRcode()``` to get the numeric value and a brief English textual description of the code can be fetched through ```DNSAnswer::getRcodeDescription()```. Note that the default initialised value in ```DNSAnswer``` is ```-1``` which indicates that no answer has been returned/lookup performed.