3 * Service discovery using DNS SRV records
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
26 class DnsSrvDiscoverer
{
48 * Construct a new discoverer for the given domain, service, and protocol.
50 * @param string $service Name of the service to discover.
51 * @param string $protocol Service protocol. Defaults to 'tcp'
52 * @param ?string $domain The hostname/domain on which to perform discovery
53 * of the given service and protocol. Defaults to null which effectively
54 * performs a query relative to the host's configured search domain.
55 * @param ?callable $resolver Resolver function. Defaults to using
56 * dns_get_record. Primarily useful in testing.
58 public function __construct(
60 string $protocol = 'tcp',
61 ?
string $domain = null,
62 ?callable
$resolver = null
64 $this->service
= $service;
65 $this->protocol
= $protocol;
66 $this->domain
= $domain;
68 $this->resolver
= $resolver ??
static function ( $srv ) {
69 return dns_get_record( $srv, DNS_SRV
);
74 * Queries the resolver for an SRV resource record matching the service,
75 * protocol, and domain and returns all target/port/priority/weight
80 public function getRecords() {
83 $records = ( $this->resolver
)( $this->getSrvName() );
85 // Respect RFC 2782 with regard to a single '.' entry denoting a valid
89 ||
( count( $records ) === 1 && $records[0]['target'] === '.' )
94 foreach ( $records as $record ) {
96 'target' => $record['target'],
97 'port' => (int)$record['port'],
98 'pri' => (int)$record['pri'],
99 'weight' => (int)$record['weight'],
107 * Performs discovery for the domain, service, and protocol, and returns a
108 * list of resolved server name/ip and port number pairs sorted by each
109 * record's priority, with servers of the same priority randomly shuffled.
113 public function getServers() {
114 $records = $this->getRecords();
116 usort( $records, static function ( $a, $b ) {
117 if ( $a['pri'] === $b['pri'] ) {
118 return mt_rand( 0, 1 ) ?
1 : -1;
121 return $a['pri'] - $b['pri'];
124 $serversAndPorts = [];
126 foreach ( $records as $record ) {
127 $serversAndPorts[] = [ $record['target'], $record['port'] ];
130 return $serversAndPorts;
134 * Returns the SRV resource record name.
136 public function getSrvName(): string {
137 $srv = "_{$this->service}._{$this->protocol}";
139 if ( $this->domain
=== null ||
$this->domain
=== '' ) {
143 return "$srv.{$this->domain}";