Skip to content

Commit 5cd84d5

Browse files
dns: use getbyname
1 parent 186579e commit 5cd84d5

File tree

8 files changed

+185
-79
lines changed

8 files changed

+185
-79
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## v2.0.0 - 2024-12-07
4+
5+
- Use the more permissive `inet_res:getbyname` function for DNS lookups.
6+
7+
- This uses the `search` option, which will allow things like `service.namespace`
8+
to be used in Kubernetes, as opposed to `service.namespace.svc.cluster.local`.
9+
Thanks to @tsoughter in #3.
10+
11+
- Exposes the `barnacle/dns` and `barnacle/local_epmd` modules.
12+
- Users can now use the functions in these modules to create custom strategies.
13+
314
## v1.1.0 - 2024-10-25
415

516
- Add `run_once` function for when polling is not needed.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,16 @@ Barnacle provides a helper function to get the basename of the current node.
159159

160160
```gleam
161161
import barnacle
162+
import gleam/option
162163
163164
pub fn main() {
164165
let assert Ok(basename) = barnacle.get_node_basename(node.self())
165166
barnacle.dns(
166167
basename,
167168
// The hostname to query against
168169
"my_app.example.com",
170+
// An optional timeout for the DNS lookup
171+
option.Some(7500),
169172
)
170173
|> barnacle.start
171174
}

demo/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ghcr.io/gleam-lang/gleam:v1.5.1-erlang-alpine
1+
FROM ghcr.io/gleam-lang/gleam:v1.6.3-erlang-alpine
22

33
COPY gleam.toml manifest.toml /build/
44

src/barnacle.gleam

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import barnacle/internal/dns
2-
import barnacle/internal/local_epmd
1+
import barnacle/dns
2+
import barnacle/local_epmd
33
import gleam/erlang/atom
44
import gleam/erlang/node
55
import gleam/erlang/process.{type Subject}
@@ -198,13 +198,24 @@ pub fn epmd(nodes: List(atom.Atom)) -> Barnacle(Nil) {
198198
/// with the same basename.
199199
///
200200
/// The second argument is the hostname to query against. Both A and AAAA records
201-
/// will be queried.
202-
pub fn dns(basename: String, hostname_query: String) -> Barnacle(Nil) {
201+
/// will be queried. The final argument is an optional timeout for the DNS
202+
/// lookup. This defaults to 5000ms if not supplied.
203+
pub fn dns(
204+
basename: String,
205+
hostname_query: String,
206+
timeout: Option(Int),
207+
) -> Barnacle(dns.LookupError) {
203208
Barnacle(
204209
..default_barnacle(),
205210
strategy: Strategy(
206211
..default_strategy(),
207-
discover_nodes: fn() { dns.discover_nodes(basename, hostname_query) },
212+
discover_nodes: fn() {
213+
dns.discover_nodes(
214+
basename,
215+
hostname_query,
216+
option.unwrap(timeout, 5000),
217+
)
218+
},
208219
),
209220
)
210221
}

src/barnacle/dns.gleam

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import gleam/erlang/atom
2+
import gleam/int
3+
import gleam/list
4+
import gleam/result
5+
import gleam/string
6+
7+
/// A DNS error that can occur when discovering nodes.
8+
pub type LookupError {
9+
FormatError
10+
QueryFormatError
11+
ServerFailure
12+
NoSuchDomain
13+
Timeout
14+
NotImplemented
15+
Refused
16+
BadVersion
17+
PosixError(String)
18+
Unknown
19+
}
20+
21+
@external(erlang, "barnacle_ffi", "lookup_a")
22+
fn lookup_a(
23+
hostname: String,
24+
timeout: Int,
25+
) -> Result(List(#(Int, Int, Int, Int)), LookupError)
26+
27+
@external(erlang, "barnacle_ffi", "lookup_aaaa")
28+
fn lookup_aaaa(
29+
hostname: String,
30+
timeout: Int,
31+
) -> Result(List(#(Int, Int, Int, Int, Int, Int, Int, Int)), LookupError)
32+
33+
pub type IpAddress {
34+
IpV4(Int, Int, Int, Int)
35+
IpV6(Int, Int, Int, Int, Int, Int, Int, Int)
36+
}
37+
38+
/// Perform a DNS lookup against a hostname. This will return a list of IP
39+
/// addresses. Queries will be performed using the `A` and `AAAA` record types.
40+
pub fn dns_lookup(
41+
hostname: String,
42+
timeout: Int,
43+
) -> Result(List(IpAddress), LookupError) {
44+
use a_records <- result.try(lookup_a(hostname, timeout))
45+
46+
let a_records =
47+
a_records
48+
|> list.map(fn(ip) {
49+
let #(a, b, c, d) = ip
50+
IpV4(a, b, c, d)
51+
})
52+
53+
use aaaa_records <- result.try(lookup_aaaa(hostname, timeout))
54+
55+
let aaaa_records =
56+
aaaa_records
57+
|> list.map(fn(ip) {
58+
let #(a, b, c, d, e, f, g, h) = ip
59+
IpV6(a, b, c, d, e, f, g, h)
60+
})
61+
62+
Ok(list.concat([a_records, aaaa_records]))
63+
}
64+
65+
fn format_ip_address(ip: IpAddress) -> String {
66+
case ip {
67+
IpV4(a, b, c, d) -> {
68+
[a, b, c, d]
69+
|> list.map(int.to_string)
70+
|> string.join(".")
71+
}
72+
IpV6(a, b, c, d, e, f, g, h) -> {
73+
[a, b, c, d, e, f, g, h]
74+
|> list.map(fn(value) {
75+
case value {
76+
0 -> ""
77+
_ -> int.to_base16(value)
78+
}
79+
})
80+
|> string.join(":")
81+
|> string.lowercase
82+
}
83+
}
84+
}
85+
86+
/// Discover nodes using DNS. The first argument is the basename of the nodes.
87+
/// Currently, Barnacle only supports connecting to nodes with the same basename.
88+
///
89+
/// The second argument is the hostname to query against. Both A and AAAA records
90+
/// will be queried. The final argument is an optional timeout for the DNS
91+
/// lookup. This defaults to 5000ms if not supplied.
92+
pub fn discover_nodes(
93+
basename: String,
94+
hostname_query: String,
95+
timeout: Int,
96+
) -> Result(List(atom.Atom), LookupError) {
97+
use ip_addresses <- result.try(dns_lookup(hostname_query, timeout))
98+
99+
ip_addresses
100+
|> list.map(fn(ip) {
101+
let ip_string = format_ip_address(ip)
102+
{ basename <> "@" <> ip_string } |> atom.create_from_string
103+
})
104+
|> Ok
105+
}

src/barnacle/internal/dns.gleam

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ fn get_hostname() -> String {
1717
hostname
1818
}
1919

20+
/// Discover nodes using the local EPMD. This will discover nodes on the same
21+
/// hostname as the current node.
2022
pub fn discover_nodes() -> Result(List(atom.Atom), Nil) {
2123
let hostname = get_hostname()
2224
list_local_nodes()

src/barnacle_ffi.erl

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
-module(barnacle_ffi).
22

3-
-export([list_local_nodes/0, disconnect_from_node/1, lookup_a/1, lookup_aaaa/1]).
3+
-export([list_local_nodes/0, disconnect_from_node/1, lookup_a/2, lookup_aaaa/2]).
4+
5+
-include_lib("kernel/include/inet.hrl").
46

57
disconnect_from_node(Node) ->
68
case disconnect_node(Node) of
@@ -24,8 +26,48 @@ list_local_nodes() ->
2426

2527
%% DNS functions
2628

27-
lookup_a(Name) when is_binary(Name) ->
28-
inet_res:lookup(binary_to_list(Name), in, a).
29+
lookup_a(Name, Timeout) when is_binary(Name) ->
30+
lookup(Name, a, Timeout).
31+
32+
lookup_aaaa(Name, Timeout) when is_binary(Name) ->
33+
lookup(Name, aaaa, Timeout).
2934

30-
lookup_aaaa(Name) when is_binary(Name) ->
31-
inet_res:lookup(binary_to_list(Name), in, aaaa).
35+
%% Erlang's internal DNS function throw with some inputs like
36+
%% "ssssrtrssssssssstrtrtrtrdededuddmdidudmdidudmdidudmdidudmddfwddf.com"
37+
%% so we just catch the error and return an unknown error. I'm not sure
38+
%% what causes these.
39+
lookup(Name, Type, Timeout) when is_binary(Name) ->
40+
try lookup_throws(Name, Type, Timeout) of
41+
Result ->
42+
Result
43+
catch
44+
_:_ ->
45+
{error, unknown}
46+
end.
47+
48+
lookup_throws(Name, Type, Timeout) when is_binary(Name) ->
49+
io:format("Name: ~p~n", [Name]),
50+
case inet_res:getbyname(binary_to_list(Name), Type, Timeout) of
51+
{ok, #hostent{h_addr_list = Addrs}} ->
52+
{ok, Addrs};
53+
{error, formerr} ->
54+
{error, format_error};
55+
{error, qfmterror} ->
56+
{error, query_format_error};
57+
{error, servfail} ->
58+
{error, server_failure};
59+
{error, nxdomain} ->
60+
{error, no_such_domain};
61+
{error, timeout} ->
62+
{error, timeout};
63+
{error, notimp} ->
64+
{error, not_implemented};
65+
{error, refused} ->
66+
{error, refused};
67+
{error, badvers} ->
68+
{error, bad_version};
69+
{error, PosixError} when is_atom(PosixError) ->
70+
{error, {posix_error, atom_to_binary(PosixError, utf8)}};
71+
_ ->
72+
{error, unknown}
73+
end.

0 commit comments

Comments
 (0)