rename project

master
Thurloat 6 years ago
parent aa139e9618
commit 320617fa26
  1. 4
      .formatter.exs
  2. 24
      .gitignore
  3. 8
      README.md
  4. 30
      config/config.exs
  5. 36
      lib/dough.ex
  6. 26
      lib/dough/context.ex
  7. 82
      lib/dough/dohplug.ex
  8. 13
      lib/dough/exceptions.ex
  9. 18
      lib/dough/router.ex
  10. 42
      lib/dough/ttlcache.ex
  11. 31
      mix.exs
  12. 12
      mix.lock
  13. 18
      priv/ssl/localhost.crt
  14. 28
      priv/ssl/localhost.key
  15. 8
      test/dough_tests.exs
  16. 1
      test/test_helper.exs

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
.gitignore vendored

@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
dough-*.tar

@ -1,21 +1,21 @@
# Edoh
# Dough
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `edoh` to your list of dependencies in `mix.exs`:
by adding `dough` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:edoh, "~> 0.1.0"}
{:dough, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/edoh](https://hexdocs.pm/edoh).
be found at [https://hexdocs.pm/dough](https://hexdocs.pm/dough).

@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure your application as:
#
# config :dough, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:dough, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"

@ -0,0 +1,36 @@
defmodule Dough do
@moduledoc false
use Application
import Supervisor.Spec
import Cachex.Spec
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: Dough.Worker.start_link(arg)
# {Dough.Worker, arg},
# Plug.Adapters.Cowboy.child_spec(:https, Dough.Router, [], port: 8331, keyfile: "priv/ssl/localhost.key", certfile: "priv/ssl/localhost.crt", otp_app: :dough)
worker(Cachex, [:dough, [
expiration: expiration(
default: :timer.seconds(6000),
interval: :timer.seconds(300),
lazy: true)
]]),
{Plug.Adapters.Cowboy2, scheme: :https, plug: Dough.Router, options: [
port: 8331,
otp_app: :dough,
keyfile: "priv/ssl/localhost.key",
certfile: "priv/ssl/localhost.crt"
]}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Dough.Supervisor]
Supervisor.start_link(children, opts)
end
end

@ -0,0 +1,26 @@
defmodule Dough.RequestContext do
require Logger
defstruct [:start, :close, :lookup, :cache, :ttl]
def ctx_start(ctx) do
Map.put(ctx, :start, System.monotonic_time(:milliseconds))
end
def ctx_close(ctx) do
Map.put(ctx, :close, System.monotonic_time(:milliseconds))
end
def ctx_lookup(ctx, dns_record) do
Map.put(ctx, :lookup, dns_record.qdlist |> List.first())
end
def ctx_cachehit(ctx, v) do
Map.put(ctx, :cache, v)
end
def ctx_ttl(ctx, ttl) do
Map.put(ctx, :ttl, ttl)
end
def ctx_log_out(ctx) do
Logger.info "#{ctx.lookup.type} - #{ctx.lookup.domain} - TTL #{ctx.ttl} | #{ctx.cache} - #{ctx.close - ctx.start}ms"
end
end

@ -0,0 +1,82 @@
defmodule Dough.DoHPlug do
import Socket.Datagram, only: [send!: 3, recv!: 1]
import Plug.Conn
import Dough.TTL
import Dough.RequestContext
require Logger
require DNS
def init(options), do: options
def call(conn, _opts) do
ctx = %Dough.RequestContext{} |> ctx_start()
dns_message = case parse_doh(conn) do
{:ok, msg} -> msg
{:error, _} ->
Logger.error conn.method
raise Dough.NotAllowed, ""
end
{cache_hit, ttl, dns_resp} = fetch_dns(dns_message)
decoded = DNS.Record.decode(dns_message)
ctx = ctx_lookup(ctx, decoded)
|> ctx_cachehit(cache_hit)
|> ctx_ttl(ttl)
|> ctx_close()
Dough.RequestContext.ctx_log_out(ctx)
conn
# support for [RFC5861] / cache control extensions
|> put_resp_header("cache-control", "max-age=#{ttl}, stale-while-revalidate=#{ttl * 2}, stale-if-error=#{ttl * 10}")
|> put_resp_header("content-type", content_type(conn))
|> send_resp(200, dns_resp)
end
def content_type(conn) do
case List.keyfind(conn.req_headers, "accept", 0) do
{"accept", accept} -> accept
nil -> "application/dns-message"
end
end
def fetch_dns(dns_message) do
case ttlcache_lookup(dns_message) do
nil ->
resp = handoff_dns(dns_message)
ttl = ttl_extract(resp)
ttlcache_set(dns_message, resp, ttl)
{:miss, ttl, resp}
result ->
ttl = ttl_extract(result)
{:hit, ttl, result}
end
end
def parse_doh(conn) do
conn = fetch_query_params conn
case conn.method do
"GET" -> {:ok, Base.url_decode64!(conn.query_params["dns"], padding: false, ignore: :whitespace)}
"POST" ->
{:ok, body, _conn} = read_body(conn)
{:ok, body}
_ -> {:error, nil}
end
end
def handoff_dns(dns_message) do
client = Socket.UDP.open!
send!(client, dns_message, {"8.8.8.8", 53})
{data, _server} = recv!(client)
data
end
end

@ -0,0 +1,13 @@
defmodule Dough.NotAllowed do
defexception [plug_status: 405, message: "Method Not Allowed"]
end
defmodule Dough.ServerError do
defexception [plug_status: 500, message: "Server Error"]
end
defimpl Plug.Exception, for: [Dough.NotAllowed, Dough.ServerError] do
def status(exc) do
exc.plug_status
end
end

@ -0,0 +1,18 @@
defmodule Dough.Router do
use Plug.Router
use Plug.ErrorHandler
if Mix.env == :dev do
use Plug.Debugger
end
require Logger
plug :match
plug :dispatch
get "/", do: send_resp(conn, 200, "")
match "/dns-query", to: Dough.DoHPlug
match _, do: send_resp(conn, 404, "oopsie")
end

@ -0,0 +1,42 @@
defmodule Dough.TTL do
require Logger
def ttlcache_lookup(dns_message) do
case Cachex.get(:dough, _hashify_dns(dns_message)) do
{:ok, nil} -> nil
{:ok, value} -> value
{:error, :nocache} -> raise Dough.ServerError, message: "Cache failure"
end
end
def ttlcache_set(dns_message, dns_result, ttl) do
Cachex.put(:dough, _hashify_dns(dns_message), dns_result, ttl: :timer.seconds(ttl))
dns_result
end
def ttl_extract(dns_result) do
decoded = DNS.Record.decode(dns_result)
answers = {decoded.anlist |> List.first(), decoded.nslist |> List.first()}
case answers do
{nil, nss} ->
# {:dns_rr, 'd.akamai.net', :soa, :in, 0, 901, {'n0d.akamai.net', 'hostmaster.akamai.com', 1534350643, 1000, 1000, 1000, 1800}, :undefined, [], false}.
nss
|> elem(6)
# {'n0d.akamai.net', 'hostmaster.akamai.com', 1534350643, 1000, 1000, 1000, 1800}
|> elem(6)
{ans, nil} ->
ans.ttl
{ans, _} ->
ans.ttl
end
end
def _hashify_dns(dns_message) do
:crypto.hash(:sha256, dns_message)
|> Base.encode16
end
end

@ -0,0 +1,31 @@
defmodule Dough.MixProject do
use Mix.Project
def project do
[
app: :dough,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger, :cowboy, :plug, :dns, :cachex],
mod: {Dough, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:cowboy, "~> 2.4.0"},
{:plug, "~> 1.5"},
{:dns, "~> 2.1.0"},
{:cachex, "~> 3.0.3"}
]
end
end

@ -0,0 +1,12 @@
%{
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "2.4.0", "f1b72fabe9c8a5fc64ac5ac85fb65474d64733d1df52a26fad5d4ba3d9f70a9f", [:rebar3], [{:cowlib, "~> 2.3.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.3.0", "bbd58ef537904e4f7c1dd62e6aa8bc831c8183ce4efa9bd1150164fe15be4caa", [:rebar3], [], "hexpm"},
"dns": {:hex, :dns, "2.1.0", "4777fe07ae3060c1d5d75024f05c26d7e11fa701d48a6edb9fc305d24cd12c8c", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"},
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.5.0", "f04166f456790fee2ac1aa05a02745cc75783c2bfb26d39faf6aefc9a3d3a58a", [:rebar3], [], "hexpm"},
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
}

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC5TCCAc2gAwIBAgIJANmlfWvetApSMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xODA4MTQxNjIwNDdaFw0xODA5MTMxNjIwNDdaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALkU8/+EXKF7gplnEvJ6CXs8fKmHZNrEnH5OGZ32E2f1c+ursblHo30zLtn9
ahrQUyMn/IaZsN82xIfhHFrkOfSkZ1w1+2n6hhXjNlu7s81tXrhf3LKzWm40SXmE
lWbcCaxUinYYd11VXO3FJ3Puxie8NAm10RAYksSP+Q3zLjZB4vfD/wT5011+9ds4
shr22ldUfG5Isr6/ByI/agkTrHm67YRKwdQ0AR7kaTlHgzKKcWaebH9sdPI4owuZ
p5VH3nFqA3rtn8x01zYv1u/iN3P0iHl/T9UV2jy0OMq/lJlQR473Ht2Pn/ZiiX+v
X9vYbI+y+0P3p29c3zVOPTr8XOMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B
AQsFAAOCAQEANDXhuI9N/4KX15N7ZUd9CD7ljUg9ESNyt1U68r+9229pFZP9JBIw
Z6XxN3jMR9DSCmiD62qWcbRVdV3GyTbogTwe5N6DATcnIdsz+4ToqXxzXOduolMc
dzrk7emYYfIVZQ61vCOoSCWYOnQtPTKU7ZetnwFIzBzgqr/ASZjYY2+z7IUnGBSr
xF6GDh9FMCmsvb5IEaVgbT8aWeo3TwF5UhNJkUnjp+Fr7kKP74EABiUkSNJulUVX
QFd0PX80I2IbQYKZZNh0Q19ykodDk6gKmhw7ZVVDSCfkpiQIUzFjlCcZZLl2Rn7V
WK8NU0vqx5/0aNS9ZTir7efp1bkYi6w4+Q==
-----END CERTIFICATE-----

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5FPP/hFyhe4KZ
ZxLyegl7PHyph2TaxJx+Thmd9hNn9XPrq7G5R6N9My7Z/Woa0FMjJ/yGmbDfNsSH
4Rxa5Dn0pGdcNftp+oYV4zZbu7PNbV64X9yys1puNEl5hJVm3AmsVIp2GHddVVzt
xSdz7sYnvDQJtdEQGJLEj/kN8y42QeL3w/8E+dNdfvXbOLIa9tpXVHxuSLK+vwci
P2oJE6x5uu2ESsHUNAEe5Gk5R4MyinFmnmx/bHTyOKMLmaeVR95xagN67Z/MdNc2
L9bv4jdz9Ih5f0/VFdo8tDjKv5SZUEeO9x7dj5/2Yol/r1/b2GyPsvtD96dvXN81
Tj06/FzjAgMBAAECggEAYNrONVEXCIqR9aUzDSFABPXKZw0rgjCRlKdaUIeN8EFK
wHHBN6x9qe82/WzYMeADIcqzI90Z6jXG8zSimg4FJjlCvcaiIvuvX5TfxXGczkwF
3YaNABdLo5BJQwZXCNTtWvmC2/pBvN4HG4ao9spleDXNJae4GCxD4glV1C8GZ+1+
4vakqKsUwU9LqQBC7JwrnDFwlLSutGE7yqpblzZeyO3ByzK8aKfktkZXurfshumk
LYzvE01e7cb4oOAOmMlEnpQ61y22G12xaEj3HU20/WH3zkS7ijjmxWawErcYxgjh
KzfEeLg9ERGFQf73fhod1oCkaSjqw/SgnwY2+3kGwQKBgQDgfTcySZNpGCJRm5jH
CT+j7vubWixzBwcNdILW78V1SaS7s1RMJXgq6bPoyPqKAi1tTsVvpOr3YmuTMexJ
WG5gDZWWKcpc+ZMvZJGffdoTL4ZbYBrv1mlT0MmC4i/iHIyUNsHsaTcpa1lSvn3g
mUykg+izOWgOvt9bbwLg+IbIjQKBgQDTD62Gz3dkvo3PXGDUV0p4F4O4IKvHgKlX
//qDM+yJPfdru6uUWdiUWvdtvWVXyW15cKLZ28lPvIsl4u2Q7ga3t8iIdkbP3RB/
G6W+xgPxyOMPazphrLYSd4VF3zEzjcVg6B1uw27uvZDXEHbBgZfvGGbGFkj7x9fz
Do8IaBR3LwKBgQCVmnzdU3EcmYvNbO88vWoe+tXMqyRyJ93IVrwXN0UVK9XPjOwB
rvrNRl+yI7XroRXbAaLMC1DXOkzMlHmOS5OLCaKFpyYIHf67l78AViOto39bh8mL
ygz5YWvZgJk+i54X7AICZf/v521omjBTLHaKMKo0Pm6dXRCG4408lgNkxQKBgHJy
UOUGALbHQTxM2lCqGL5v+cPRK3SdxrXqHxwf/sYYrN5lQE/MPE2N2hdOmPJ2Tf+I
3TWHIW00Tru3hpyNBWV/qaSdhh5WwAg8pK6Cz0a/aKhGu7yVG5F97+2IbjSHTp0S
oEscfD3G2xBTZCHftNQ4hhopoV+t6OJqZZLoZRiPAoGBANZd2Xj8SCENVjVQS8+r
CoMwZb8lpisdUh1aqpyL7KokvWA2/c6/cEd5A0I+1o63T7N7YUJv+CV3qBeC0u10
OIYkn98uzjBMekTYJs/RSDV8nRu766x5KiGqezftH8fMeIMn/fJ9XEIAk9aSMT+F
KjzV3G0y/iLykshNL+vDW3Ou
-----END PRIVATE KEY-----

@ -0,0 +1,8 @@
defmodule DoughTest do
use ExUnit.Case
doctest Dough
test "greets the world" do
assert Dough.hello() == :world
end
end

@ -0,0 +1 @@
ExUnit.start()
Loading…
Cancel
Save