.
Copyright (c) 2015 Silent Circle
Authors: Ed Fine (efine@silentcircle.com
).
SRV records essentially allow transparent DNS-level redirects of services to another domain or port. A simple example is when you have an XMPP server and you want users to have addresses like username@example.com, but your XMPP server is really installed on xmpp.example.com. In principle they work the same way as MX records do for email.
For a server example.com
wanting to delegate its XMPP services to the server at 'xmpp.example.com', here are some example records:
_xmpp-client._tcp.example.com. 18000 IN SRV 0 5 5222 xmpp.example.com.
_xmpp-server._tcp.example.com. 18000 IN SRV 0 5 5269 xmpp.example.com.
The target domain MUST be an existing A record of the target server; it cannot be an IP address, and cannot be a CNAME record.
The 18000 in this example is the TTL (time-to-live), it tells other servers how long they should cache your record for - a higher number will reduce DNS traffic, and result in slightly faster connections (since DNS info will be more likely to be cached, and won't need to be re-fetched). A lower TTL is more useful if you are going to be changing your record, since you have to wait for the TTL until all caches have expired your old record.
The 0 and 5 are the record's priority and weight. These values are specific to SRV records, and allow you to have multiple targets with different priorities (e.g. for load balancing or fallback in case of a down server) - lower priority targets are tried first. The weight is used to bias resolvers towards certain targets in case of a priority tie. Most services will not need to change these values, and 0 and 5 are sensible defaults.
Next is the port the service is running on. Clients will typically connect to 5222.
Clients resolve the SRV records for _xmpp-client._tcp.example.com. One or more SRV records will be returned. Clients then select a record based on priority and weight as described in the table below.
Priority The priority of the server. Clients attempt to contact the server with the lowest priority.
Weight A load-balancing mechanism that is used when selecting a target host from those that have the same priority. Clients randomly choose SRV records that specify target hosts to be contacted, with probability proportional to the weight
Port Number The port where the server is listening for this service.
Target The fully qualified domain name of the host computer.
Let's say an SRV resolution request returns the following:
Priority Weight Port Target
0 30 5222 JHGAJSGHD.example.net
0 40 5222 KJGOIUTRG.example.net
0 15 5222 NBGDPRLGH.example.net
0 15 5222 WMFPSNMGJ.example.net
-
The lowest priority records are chosen. In this case, all of the records are at priority 0.
-
A record is randomly selected from the group such that its probability of selection is proportional to its relative weight. In this example, the weights add up nicely to 100, so they could be thought of as percentages. In this arrangement, KJGOIUTRG.example.net would be chosen 40% of the time, and NBGDPRLGH.example.net 15% of the time.
Priority Weight Port Target
0 30 5222 JHGAJSGHD.example.net
1 40 5222 KJGOIUTRG.example.net
2 15 5222 NBGDPRLGH.example.net
3 15 5222 WMFPSNMGJ.example.net
Here, the weights are irrelevant. JHGAJSGHD.example.net will be chosen every time. If connection attempts to it fail, then the next highest priority record, KJGOIUTRG.example.net, is chosen, and so on.
The idea is to call fetch_srv_rrs/1, then pick_server/1 using the Rest
list, until a host is reachable.
RRs = sc_util_srv:fetch_srv_rrs("_some-client._tcp.some.domain"),
case try_connect(RRs) of
{ok, Conn, SrvInfo} ->
use_connection(Conn, SrvInfo);
{error, Reason} ->
handle_this(Reason)
end.
try_connect([]) ->
{error, no_more_hosts_to_try};
try_connect(RRs) ->
case sc_util_srv:pick_server(RRs) of
{Host, Port, _TTL, Rest} = SrvInfo ->
case my_connect(Host, Port) of
{ok, Connection} ->
{ok, Connection, SrvInfo};
{error, connection_failure} ->
try_connect(Rest);
{error, Reason} ->
handle_error(Reason)
end;
undefined ->
{error, no_hosts_available}
end.
dns_rr_prop() = {domain, string()} | {type, atom()} | {class, atom()} | {ttl, dns_ttl()} | {data, dns_srv_tuple()}
dns_rr_props() = [dns_rr_prop()]
dns_srv_tuple() = {srv_prio(), srv_weight(), srv_port(), srv_host()}
rr() = #rr{}
rrs() = [rr()]
service_name() = binary() | string()
srv_info() = {srv_host(), srv_port(), dns_ttl(), rrs()}
weighted_rr() = {rr(), RunningTotal::non_neg_integer()}
weighted_rrs() = [weighted_rr()]
weighted_sums() = {weighted_rrs(), Sum::non_neg_integer()}
fetch_srv_rrs/1 | |
pick_server/1 | Pick the server with the highest weighting and priority from the list of RRs. |
fetch_srv_rrs(ServiceName) -> Result
ServiceName = service_name()
Result = {ok, rrs()} | {error, any()}
pick_server(RRs) -> SrvInfo
RRs = rrs()
SrvInfo = srv_info()
Pick the server with the highest weighting and priority from
the list of RRs. Return undefined
if RRs is an empty list.