inet_res(3)
π¦ inet_res β the Erlang manual page, bestie edition π¦
okay so this is inet_res(3) from the Erlang Module Definition section, and we are gonna get through it together, no stress.
β¨ NAME β¨
inet_res β a rudimentary DNS client. yes, rudimentary, she said it herself, we're being honest here π
πΈ DESCRIPTION πΈ
so this module does DNS name resolving, and it points that at recursive name servers. that's the whole vibe.
also go look at the ERTS User's Guide: Inet Configuration if you wanna know how to set up an Erlang runtime system for IP communication, and how to turn this DNS client on by defining <purple>'dns'</purple> as a lookup method. once you do that, this DNS client becomes the backend for the resolving functions over in inet. slay.
and here's a cute thing: this DNS client can resolve DNS records even if it's not being used for normal name resolving in the node. she contains multitudes π
WARNING π¨ β this is NOT a full-fledged resolver. it's just a DNS client that relies on being in a trusted network, shielded from DNS reply spoofing by firewalls, and on only asking trusted recursive name servers. if the Crypto application is loaded, this implementation does its best to not get fooled by spoofed DNS replies. but ngl that's still a meager remedy compared to using encrypted and signed DNS methods like TSIG or DNSSEC, the way full-fledged resolvers do. so likeβ¦ know that going in π
NOTE π«Ά β if the Crypto application is loaded, this resolver client uses cryptographically random transaction IDs and port numbers, which should make the probability of a successfully brute-force spoofed reply too low to be a usable exploit. an Erlang node installed in an exposed network environment should already have Crypto loaded anyway, like, for running the SSL application for example. if that's not your situation, you can explicitly load Crypto with application:load(crypto).
π‘ Name Resolving π‘
UDP queries are the default, unless resolver option usevc is true, which forces TCP queries. and if the query is too large for UDP, TCP gets used instead. for regular DNS queries, 512 bytes is the size limit.
when EDNS is enabled (that's resolver option edns set to the EDNS version β so 0 instead of false), resolver option udp_payload_size sets the payload size limit. if a name server replies with the TC bit set (that's truncation, bestie), indicating the answer is incomplete, the query is retried towards the same name server using TCP. resolver option udp_payload_size also sets the advertised size for the maximum allowed reply size, if EDNS is enabled β otherwise the name server uses the limit 512 bytes. if the reply is larger, it gets truncated, forcing a TCP requery. it's giving "square up, try again" π₯
for UDP queries, resolver options timeout and retry control retransmission. each name server in the nameservers list is tried with a time-out of timeout/retry. then all name servers are tried again, doubling the time-out, for a total of retry times.
but hold on β before all name servers are tried again, there's a (user configurable) time-out: servfail_retry_timeout. the whole point of this is to prevent the new query from being handled by a server's servfail cache (a client that's too eager will literally only get what's in the servfail cache π). and if there's too little time left of the resolver call's time-out to do a retry, the resolver call may return before the call's time-out has expired. so, heads up on that.
for queries not using the search list: if the query to all nameservers results in {error,nxdomain} or an empty answer, the same query is tried for alt_nameservers.
and finally β if randomization of transaction ID:s and port numbers is too costly for an Erlang node in a trusted network shielded from DNS reply spoofing, the legacy behaviour (reuse the UDP socket on retries, use sequential transaction ID:s) can be configured by setting resolver option random to false. living in the past is a choice and it's available π
π§ Resolver Types π§
the following data types concern the resolver β they're all waiting for you down in DATA TYPES below π
π DNS Types π
the following data types concern the DNS client β also down in DATA TYPES, keep scrolling bestie π
β Example β
this access functions example shows how lookup/3 can be implemented using resolve/3 from outside the module:
example_lookup(Name, Class, Type) ->
case inet_res:resolve(Name, Class, Type) of
{ok,Msg} ->
[inet_dns:rr(RR, data)
|| RR <- inet_dns:msg(Msg, anlist),
inet_dns:rr(RR, type) =:= Type,
inet_dns:rr(RR, class) =:= Class];
{error,_} ->
[]
end.
π DATA TYPES π
dns_class() :: in | chaos | hs | any.
dns_data() ::
dns_name() |
inet:ip4_address() |
inet:ip6_address() |
{MName :: dns_name(),
RName :: dns_name(),
Serial :: integer(),
Refresh :: integer(),
Retry :: integer(),
Expiry :: integer(),
Minimum :: integer()} |
{inet:ip4_address(), Proto :: integer(), BitMap :: binary()} |
{CpuString :: string(), OsString :: string()} |
{RM :: dns_name(), EM :: dns_name()} |
{Prio :: integer(), dns_name()} |
{Prio :: integer(),
Weight :: integer(),
Port :: integer(),
dns_name()} |
{Order :: integer(),
Preference :: integer(),
Flags :: string(),
Services :: string(),
Regexp :: string(),
dns_name()} |
[string()] |
binary().
DNS record data (content), periodt. π
the basic type of each data element is specified in this type.
ngl, Regexp is a string/0 that's UTF-8. the other string/0s are actually Latin-1 strings. yes, that's a whole thing, and yes, you needed to know it.
dns_msg() :: term().
a DNS message. β¨
this is the start of a hierarchy of opaque data structures that can be examined with access functions in inet_dns, which return lists of {Field,Value} tuples. the arity 2 functions return the value for a specified field. so basically: vibes in, tuples out.
dns_msg() = DnsMsg
inet_dns:msg(DnsMsg) ->
[ {header, dns_header()}
| {qdlist, dns_query()}
| {anlist, dns_rr()}
| {nslist, dns_rr()}
| {arlist, dns_rr()} ]
inet_dns:msg(DnsMsg, header) -> dns_header() % for example
inet_dns:msg(DnsMsg, Field) -> Value
dns_header() = DnsHeader
inet_dns:header(DnsHeader) ->
[ {id, integer()}
| {qr, boolean()}
| {opcode, query | iquery | status | integer()}
| {aa, boolean()}
| {tc, boolean()}
| {rd, boolean()}
| {ra, boolean()}
| {pr, boolean()}
| {rcode, integer(0..16)} ]
inet_dns:header(DnsHeader, Field) -> Value
query_type() = axfr | mailb | maila | any | dns_rr_type()
dns_query() = DnsQuery
inet_dns:dns_query(DnsQuery) ->
[ {domain, dns_name()}
| {type, query_type()}
| {class, dns_class()} ]
inet_dns:dns_query(DnsQuery, Field) -> Value
dns_rr() = DnsRr
inet_dns:rr(DnsRr) -> DnsRrFields | DnsRrOptFields
DnsRrFields = [ {domain, dns_name()}
| {type, dns_rr_type()}
| {class, dns_class()}
| {ttl, integer()}
| {data, dns_data()} ]
DnsRrOptFields = [ {domain, dns_name()}
| {type, opt}
| {udp_payload_size, integer()}
| {ext_rcode, integer()}
| {version, integer()}
| {z, integer()}
| {data, dns_data()} ]
inet_dns:rr(DnsRr, Field) -> Value
and there's an information function for the types above π
inet_dns:record_type(dns_msg()) -> msg;
inet_dns:record_type(dns_header()) -> header;
inet_dns:record_type(dns_query()) -> dns_query;
inet_dns:record_type(dns_rr()) -> rr;
inet_dns:record_type(_) -> undefined.
so, inet_dns:(inet_dns:record_type(X))(X) converts any of these data structures into a {Field,Value} list. iconic, honestly. π
dns_name() :: string().
a string with no adjacent dots. that's the whole rule, don't test her.
dns_rr_type() ::
a | aaaa | caa | cname | gid | hinfo | ns | mb | md | mg |
mf | minfo | mx | naptr | null | ptr | soa | spf | srv | txt |
uid | uinfo | unspec | uri | wks.
hostent() ::
inet:hostent() |
{hostent,
H_name :: inet:hostname(),
H_aliases :: [inet:hostname()],
H_addrtype :: dns_rr_type(),
H_length :: non_neg_integer(),
H_addr_list :: [dns_data()]}.
extended variant of inet:hostent/0. π¦
it allows dns_rr_type/0 for the #hostent{}.h_addrtype field, and [dns_data/0] for the #hostent{}.h_addr_list field. so much more room for activities.
nameserver() :: {inet:ip_address(), Port :: 1..65535}.
res_error() ::
formerr | qfmterror | servfail | nxdomain | notimp | refused |
badvers | timeout.
res_option() ::
{alt_nameservers, [nameserver()]} |
{edns, 0 | false} |
{inet6, boolean()} |
{nameservers, [nameserver()]} |
{recurse, boolean()} |
{retry, integer()} |
{timeout, integer()} |
{udp_payload_size, integer()} |
{dnssec_ok, boolean()} |
{usevc, boolean()} |
{random, boolean()} |
{nxdomain_reply, boolean()}.
okay that's part 1, and there's more coming, so do NOT go anywhere β part 2 is right behind me π«Ά
β¨ FUNCTIONS β¨
okay bestie, this is the good part π every function you can call, with its whole signature, spelled out character for character. no skipping, no "and so on", we're doing all of it π«Ά
getbyname
getbyname(Name, Type, Opts, Timeout) ->
{ok, Hostent} | {error, Reason}
when
Name :: dns_name(),
Type :: dns_rr_type(),
Opts :: [Opt],
Opt :: res_option() | verbose | atom(),
Timeout :: timeout(),
Hostent :: inet:hostent() | hostent(),
Reason :: inet:posix() | res_error().
<cute>Resolve a DNS query.</cute> that's the whole job description, iconic.
It resolves a DNS query of the given Type for the given host, of class in. On success β when you're resolving a Type = a or aaaa DNS record β you get back a #hostent{} record where #hostent.h_addrtype = inet or inet6 respectively. peek at inet:hostent/0 for the shape π
When you're resolving other Type = dns_rr_type():s (still of class in), you also get a #hostent{} record back, but this time dns_rr_type/0 shows up in #hostent.h_addrtype, and the resolved dns_data/0 lives in #hostent.h_addr_list. the hostent/0 docs have the deets πΈ
This function uses the resolver option search, which is literally a list of domain names. Here's the tea on how it gets used:
- if the name you're resolving has no dots, it gets prepended to each domain name in the search list, and those are tried in order
- if the name does have dots, it's tried first as an absolute name, and if that fails, the search list kicks in
- if the name has a trailing dot, it's supposed to be absolute, so the search list is not used. periodt.
For all the Opts lore, go read resolve/5 π
gethostbyaddr
gethostbyaddr(Address, Timeout) -> {ok, Hostent} | {error, Reason}
when
Address :: inet:ip_address(),
Timeout :: timeout(),
Hostent :: inet:hostent(),
Reason :: inet:posix() | res_error().
Backend function used by inet:gethostbyaddr/1. it's the plumbing behind the curtain π§
gethostbyaddr(Address, Opts, Timeout) ->
{ok, Hostent} | {error, Reason}
when
Address :: inet:ip_address(),
Opts :: [Opt],
Opt :: res_option() | verbose | atom(),
Timeout :: timeout(),
Hostent :: inet:hostent(),
Reason :: inet:posix() | res_error().
Also a backend function used by inet:gethostbyaddr/1. same energy, now with Opts π
gethostbyname
gethostbyname(Name) -> {ok, Hostent} | {error, Reason}
when
Name :: dns_name(),
Hostent :: inet:hostent(),
Reason :: inet:posix() | res_error().
Backend functions used by inet:gethostbyname/1,2.
Here's the vibe check: if the resolver option inet6 is true, this is equivalent to gethostbyname(Name, inet6, infinity), otherwise it's gethostbyname(Name, inet, infinity). lowkey just picking a family for you π
gethostbyname(Name, Family, Opts, Timeout) ->
{ok, Hostent} | {error, Reason}
when
Name :: dns_name(),
Family :: inet:address_family(),
Opts :: [Opt],
Opt :: res_option() | verbose | atom(),
Timeout :: timeout(),
Hostent :: inet:hostent(),
Reason :: inet:posix() | res_error().
Backend functions used by inet:gethostbyname/1,2. (yes, again, they're the same two functions.)
This one uses the resolver option search in exactly the same way as getbyname/2,3 π
lookup
lookup(Name, Class, Type, Opts, Timeout) -> [dns_data()]
when
Name :: dns_name() | inet:ip_address(),
Class :: dns_class(),
Type :: dns_rr_type(),
Opts :: [res_option() | verbose],
Timeout :: timeout().
Look up DNS data. simple as that.
It resolves the DNS data for the record Name of the specified Type and Class. On success it filters out the answer records that have the correct Class and Type, and hands you back a list of their data fields. so heads up: a lookup for type any gives you an empty answer π because the answer records have specific types, and none of those are any. an empty answer, or a lookup that just failed, returns an empty list.
It calls resolve/* with the same arguments and then filters the result β so yeah, the Opts storyline is the one written up for those functions β¨
nnslookup
nnslookup(Name, Class, Type, Nameservers, Timeout) ->
{ok, dns_msg()} | {error, Reason}
when
Name :: dns_name() | inet:ip_address(),
Class :: dns_class(),
Type :: dns_rr_type(),
Timeout :: timeout(),
Nameservers :: [nameserver()],
Reason :: inet:posix().
<cute>Resolve a DNS query.</cute>
It's like nslookup/4 but it calls resolve/5 with both the arguments Opts = [{nameservers, Nameservers}] and Timeout. cute, efficient, we love her π
nslookup
nslookup(Name, Class, Type, Timeout) ->
{ok, dns_msg()} | {error, Reason}
when
Name :: dns_name() | inet:ip_address(),
Class :: dns_class(),
Type :: dns_rr_type(),
Timeout :: timeout(),
Reason :: inet:posix() | res_error();
(Name, Class, Type, Nameservers) ->
{ok, dns_msg()} | {error, Reason}
when
Name :: dns_name() | inet:ip_address(),
Class :: dns_class(),
Type :: dns_rr_type(),
Nameservers :: [nameserver()],
Reason :: inet:posix() | res_error().
<cute>Resolve a DNS query.</cute> (we say this a lot here, it's the family motto fr)
This function is a legacy wrapper to resolve/5 that simplifies error matching: {error, {Reason, _}} gets squished down into {error, Reason} or {error, einval}. the glow-up of error messages π
- when you call it with the
Timeoutargument, it callsresolve/5withOpts = [] - when you call it with the
Nameserversargument, it callsresolve/5withOpts = [{nameservers, Nameservers}]andTimeout = infinity
resolve
resolve(Name, Class, Type, Opts, Timeout) ->
{ok, dns_msg()} | Error
when
Name :: dns_name() | inet:ip_address(),
Class :: dns_class(),
Type :: dns_rr_type(),
Opts :: [Opt],
Opt :: res_option() | verbose | atom(),
Timeout :: timeout(),
Error ::
{error, Reason} | {error, {Reason, dns_msg()}},
Reason :: inet:posix() | res_error().
<cute>Resolve a DNS query.</cute> the main character of this whole section, honestly π
It resolves a DNS query for the specified Type, Class, and Name, into a DNS message that possibly contains Resource Records. that returned dns_msg/0 can be poked at with the access functions in inet_db, as described in the section on DNS Types π¦
If Name is an ip_address(), the domain name that gets queried is generated as the standard reverse name β .IN-ADDR.ARPA. for an IPv4 address, or .IP6.ARPA. for an IPv6 address. and in that case, you most probably want to use Class = in and Type = ptr, but it is not done automatically. you gotta say it yourself, bestie π
Opts overrides the corresponding resolver options. If the option nameservers is specified, it's assumed to be the complete list of name servers, so the resolver option alt_nameserves gets ignored. however β if the option alt_nameserves is also specified to this function, then it is used π
Option verbose (or rather {verbose,true}) causes diagnostics printout through io:format/2 of queries, replies, retransmissions, and so on, similar to utilities such as dig and nslookup. it's giving very chatty, very extra β¨
Option nxdomain_reply (or rather {nxdomain_reply, true}) causes NXDOMAIN errors from DNS servers to be returned as {error, {nxdomain, dns_msg()}}. the dns_msg/0 contains the additional sections that were included by the answering server. this is mainly useful to inspect the SOA record to get the TTL for negative caching π
If Opt is any atom, it's interpreted as {Opt,true} β unless the atom string starts with "no", which flips the interpretation to {Opt,false}. for example, usevc is an alias for {usevc, true} and nousevc is an alias for {usevc, false} π
β οΈ okay important one: optioninet6has no effect on this function. you probably want to useType = a | aaaainstead. don't do the thing, do the other thing π
πΈ that's the whole page, bestie πΈ
no cap, that was Ericsson AB's manual page for inet_res(3), from kernel 11.0.3. go forth and resolve things responsibly, you absolute icon π
ππ«Ά
π¦ where this page comes from
- ubuntu stonking: erlang-doc 1:29.0.6+dfsg-1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms