Skip to content

Instantly share code, notes, and snippets.

@sasa1977
Created February 28, 2021 19:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sasa1977/293b843bc42ab53cd8b565e733c22c41 to your computer and use it in GitHub Desktop.
Save sasa1977/293b843bc42ab53cd8b565e733c22c41 to your computer and use it in GitHub Desktop.
# This is the proper BEAM test for https://github.com/uber/denial-by-dns. The Erlang test in that repo is sequential
# (https://github.com/uber/denial-by-dns/blob/b809cc561a691d9d6201d06d38d06c33c9c9f9ec/erlang-httpc/main.erl)
# which is not consistent with the test description (https://github.com/uber/denial-by-dns/tree/b809cc561a691d9d6201d06d38d06c33c9c9f9ec#how-it-works)
# and also differs from e.g. go test in the same repo which issues requests concurrently.
#
# Consequently, the conclusion in that repo as well as the original article (https://eng.uber.com/denial-by-dns/) is incorrect.
# A properly written Erlang test would pass, as demonstrated by this Elixir script.
#
# This code performs a slightly refined and a correct version of that tests in Elixir:
#
# 1. Starts a local tcp server on port 4000
# 2. Starts 100 concurrent tcp connection attempts to example.org
# 3. Immediately starts repeated connection attempts to server from step 1, sleeping 1 sec before each attempt, and printing the result
# 4. Awaits for processes from 2 to finish, prints the deduplicated result, and stops.
#
# Reproduce procedure:
#
# WARNING: don't perform this outside of a container, because step 3 overwrites /etc/resolv.conf
#
# 1. Save this file as `test.exs` somewhere on your disk.
# 2. From the file's folder run `docker run --rm -it -v "$(pwd):/test" hexpm/elixir:1.11.3-erlang-23.2.6-debian-buster-20210208`
# 3. echo "nameserver 172.17.0.255" > /etc/resolv.conf
# 4. elixir /test/test.exs
{:ok, listen_socket} = :gen_tcp.listen(4000, reuseaddr: true)
IO.puts("started local server on port 4000")
Task.start_link(fn ->
for visit <- Stream.iterate(1, &(&1 + 1)) do
{:ok, socket} = :gen_tcp.accept(listen_socket)
:gen_tcp.send(socket, "hello client ##{visit} from localhost")
:gen_tcp.close(socket)
end
end)
num_clients = 100
tasks =
Enum.map(
1..num_clients,
fn _ -> Task.async(fn -> :gen_tcp.connect('example.org', 80, []) end) end
)
IO.puts("started #{num_clients} parallel connect attempts to example.org")
IO.puts("pinging localhost server")
Task.start_link(fn ->
Stream.repeatedly(fn ->
Process.sleep(:timer.seconds(1))
{:ok, socket} = :gen_tcp.connect('localhost', 4000, active: false)
IO.inspect(:gen_tcp.recv(socket, 0))
end)
|> Stream.run()
end)
results = Enum.map(tasks, &Task.await(&1, :infinity))
IO.puts("example.org connect results: #{inspect(Enum.uniq(results))}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment