~hrbrmstr/playdoh

15206ff4a198ed5fb2a37646aa2e66d09cf6d75a — boB Rudis 1 year, 1 month ago cda0617 pure-r
GET only; done-ish
12 files changed, 197 insertions(+), 156 deletions(-)

M DESCRIPTION
M NAMESPACE
A R/aaa.R
A R/doh-get.R
D R/doh-post.R
M R/globals.R
M R/playdoh-package.R
D R/zzz.R
M README.Rmd
M README.md
R man/{doh_post.Rd => doh_get.Rd}
A man/doh_servers.Rd
M DESCRIPTION => DESCRIPTION +2 -3
@@ 8,7 8,7 @@ Authors@R: c(
           comment = c(ORCID = "0000-0001-5670-2640"))
  )
Maintainer: Bob Rudis <bob@rud.is>
Description: Make 'DNS over HTTPS' (<https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-05.html>) queries.
Description: Make 'DNS over HTTPS' (<https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-05.html>) queries via 'DoH' 'GET'/'REST API'.
URL: https://gitlab.com/hrbrmstr/playdoh
BugReports: https://gitlab.com/hrbrmstr/playdoh/issues
Encoding: UTF-8


@@ 20,7 20,6 @@ Depends:
    R (>= 3.2.0)
Imports:
    httr,
    jsonlite,
    reticulate
    jsonlite
Roxygen: list(markdown = TRUE)
RoxygenNote: 6.1.1

M NAMESPACE => NAMESPACE +2 -2
@@ 1,6 1,6 @@
# Generated by roxygen2: do not edit by hand

export(doh_post)
export(doh_get)
export(doh_servers)
import(httr)
import(reticulate)
importFrom(jsonlite,fromJSON)

A R/aaa.R => R/aaa.R +7 -0
@@ 0,0 1,7 @@
httr::user_agent(
  sprintf(
    "playdoh package v%s: (<%s>)",
    utils::packageVersion("playdoh"),
    utils::packageDescription("playdoh")$URL
  )
) -> .PLAYDOH_UA

A R/doh-get.R => R/doh-get.R +51 -0
@@ 0,0 1,51 @@
#' Make a DoH Request (GET/REST)
#'
#' Issue a `GET` REST API query of type `type` for `name` to the
#' DoH endpoint specified at `server_path`.
#'
#' @param name name to query for
#' @param type DNS query type (defaults to "`A`")
#' @param server_path full URL path to the DoH server quer endpoint (defaults to Quad9).
#' @param extra_params any special `GET` query parameter needed for a given server API endpoint.
#'        this should be a named `list`.
#' @return `NULL` (if the query failed) or a `data.frame` (tibble)
#' @references <https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-05.html>
#' @export
#' @examples
#' doh_get("rud.is", "A")
doh_get <- function(name, type = "a", extra_params = list(), service_path = "https://9.9.9.9/dns-query") {

  stopifnot(is.list(extra_params))

  extra_params[["name"]] <- tolower(name)
  extra_params[["type"]] <- tolower(type[1])

  httr::GET(
    url = service_path,
    query = extra_params,
    .PLAYDOH_UA
  ) -> res

  httr::stop_for_status(res)

  out <- httr::content(res, as = "text", encoding = "UTF-8")
  out <- jsonlite::fromJSON(out)

  # will be NULL or a data frame
  ret <- out[["Answer"]]

  # cleanup column names and add query metadata if query was OK
  if (length(ret)) {

    colnames(ret) <- tolower(colnames(ret))

    attr(ret, "question") <- out[["Question"]]
    attr(ret, "flags") <- out[c("Status", "TC", "RD", "RA", "AD", "CD")]
    attr(ret, "edns_client_subnet") <- out[["edns_client_subnet"]]
    attr(ret, "comment") <- out[["comment"]]

  }

  ret

}

D R/doh-post.R => R/doh-post.R +0 -70
@@ 1,70 0,0 @@
#' Make a POST DoH Request (wireformat)
#'
#' Issue the query of type `type` for `name` to the DoH endpoint specified at `server_path`.
#'
#' @param name name to query for
#' @param type DNS query type (defaults to "`A`")
#' @param server_path full URL path to the DoH server quer endpoint (defaults to Quad9).
#' @return `NULL` (if the query failed) or a `data.frame` (tibble)
#' @references <https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-05.html>
#' @export
doh_post <- function(name, type = "A", server_path = "https://dns.quad9.net/dns-query") {

  # for now, use python's {dnslib} as a crutch to
  # encode/decode wireformat DNS questions and answers

  .dns$DNSRecord$question(
    qname = tolower(name[1]),
    qtype = toupper(type[1]),
    qclass = "IN"
  ) -> q

  qpak <- q$pack()

  # now, send it off to the server

  httr::POST(
    url = server_path[1],
    httr::add_headers(
      `Content-Type` = "application/dns-message",
      `Accept` = "application/dns-message"
    ),
    encode = "raw",
    body = qpak
  ) -> res

  httr::warn_for_status(res)

  # if the response is OK, make it a data frame

  if (httr::status_code(res) == 200) {

    r <- .dns$DNSRecord$parse(httr::content(res))

    q <- r$get_q()

    do.call(
      rbind.data.frame,
      lapply(r$rr, function(.x) {
        data.frame(
          query = py_str(q$qname),
          qtype = q$qtype,
          rname = py_str(.x$rname),
          rtype = .x$rtype,
          rdata = py_str(.x$rdata),
          ttl = .x$ttl,
          stringsAsFactors = FALSE
        )
      })

    ) -> xdf

    class(xdf) <- c("tbl_df", "tbl", "data.frame")

    xdf

  } else {
    NULL
  }

}

M R/globals.R => R/globals.R +62 -20
@@ 1,3 1,65 @@

c(
  'A' = 1L, 'NS' = 2L, 'CNAME' = 5L, 'SOA' = 6L, 'PTR' = 12L, 'HINFO' = 13L,
  'MX' = 15L, 'TXT' = 16L, 'RP' = 17L, 'AFSDB' = 18L, 'SIG' = 24L,
  'KEY' = 25L, 'AAAA' = 28L, 'LOC' = 29, 'SRV' = 33L, 'NAPTR' = 35L,
  'KX' = 36L, 'CERT' = 37L, 'A6' = 38L, 'DNAME' = 39L, 'OPT' = 41,
  'APL' = 42L, 'DS' = 43L, 'SSHFP' = 44L, 'IPSECKEY' = 45L, 'RRSIG' = 46L,
  'NSEC' = 47L, 'DNSKEY' = 48L, 'DHCID' = 49L, 'NSEC3' = 50L,
  'NSEC3PARAM' = 51L, 'TLSA' = 52L, 'HIP' = 55L, 'CDS' = 59L,
  'CDNSKEY' = 60L, 'OPENPGPKEY' = 61L, 'SPF' = 99L, 'TKEY' = 249L,
  'TSIG' = 250L, 'IXFR' = 251L, 'AXFR' = 252L, 'ANY' = 255L,
  'URI' = 256L, 'CAA' = 257L, 'TA' = 32768L, 'DLV' = 32769L
) -> .qtype

c(
  'IN' = 1L,
  'CS' = 2L,
  'CH' = 3L,
  'Hesiod' = 4L,
  'None' = 254L,
  '*' = 255L
) -> .class

.qr <- c('QUERY' = 0, 'RESPONSE' = 1)

c(
  'NOERROR' = 0L,
  'FORMERR' = 1L,
  'SERVFAIL' = 2L,
  'NXDOMAIN' = 3L,
  'NOTIMP' = 4L,
  'REFUSED' = 5L,
  'YXDOMAIN' = 6L,
  'YXRRSET' = 7L,
  'NXRRSET' = 8L,
  'NOTAUTH' = 9L,
  'NOTZONE' = 10L
) -> .rcode

c(
  'QUERY' = 0L,
  'IQUERY' = 1L,
  'STATUS' = 2L,
  'UPDATE' = 5L
) -> .opcode

#' Built-in list of DoH Servers
#'
#' The `url` element has the URL for the `GET` requests and
#' the `extra_params` element has any needed query parameters
#' for the `GET` requests.
#'
#' The list so far.
#' - `google`: <https://dns.google.com/experimental>
#' - `cloudflare`: <https://cloudflare-dns.com/dns-query>
#' - `quad9`: <https://dns.quad9.net/dns-query>
#' - `securedns_eu`: <https://doh.securedns.eu/dns-query>
#' - `dnswarden_adblock`: <https://doh.dnswarden.com/adblock>
#' - `dnswarden_uncensored`: <https://doh.dnswarden.com/uncensored>
#'
#' @docType data
#' @export
list(
  google = list(
    url = "https://dns.google.com/experimental",


@@ 28,25 90,5 @@ list(
  dnswarden_uncensored = list(
    url = "https://doh.dnswarden.com/uncensored",
    extra_params = list()
  ),
  cleanbrowsing_security = list(
    url = "https://doh.cleanbrowsing.org/doh/security-filter/",
    extra_params = list(cd = "false")
  ),
  cleanbrowsing_family = list(
    url = "https://doh.cleanbrowsing.org/doh/family-filter/",
    extra_params = list()
  ),
  cleanbrowsing_adult = list(
    url = "https://doh.cleanbrowsing.org/doh/adult-filter/",
    extra_params = list()
  ),
  power_dns = list(
    url = "https://doh.powerdns.org",
    extra_params = list()
  ),
  appliedprivacy = list(
    url = "https://doh.appliedprivacy.net/query",
    extra_params = list()
  )
) -> doh_servers
\ No newline at end of file

M R/playdoh-package.R => R/playdoh-package.R +1 -1
@@ 9,6 9,6 @@
#' @docType package
#' @keywords internal
#' @author Bob Rudis (bob@@rud.is)
#' @import httr reticulate
#' @import httr
#' @importFrom jsonlite fromJSON
NULL

D R/zzz.R => R/zzz.R +0 -29
@@ 1,29 0,0 @@
py_c <- reticulate::py_config()

.dns <- NULL

.onLoad <- function(libname, pkgname) {

  if (utils::compareVersion(py_c$version, "3.5") < 0) {
    stop(
      paste0(
        c(
          "Python 3.5+ is required. If this is installed please set RETICULATE_PYTHON ",
          "to the path to the Python 3 binary on your system and try re-installing/",
          "re-loading the package."
        ),
        collapse = ""
      )
    )
    return()
  }

  if (!reticulate::py_module_available("dnslib")) {
    packageStartupMessage(
      "The 'dnslib' Python module must be installed."
    )
  } else {
    .dns <<- reticulate::import("dnslib", delay_load = TRUE)
  }

}
\ No newline at end of file

M README.Rmd => README.Rmd +2 -2
@@ 52,9 52,9 @@ packageVersion("playdoh")
### Basic functionality

```{r}
doh_post("rud.is")
doh_get("rud.is")

doh_post("lenovo.com", "txt")
doh_get("lenovo.com", "txt")
```

## playdoh Metrics

M README.md => README.md +29 -22
@@ 46,34 46,41 @@ packageVersion("playdoh")
### Basic functionality

``` r
doh_post("rud.is")
## # A tibble: 1 x 6
##   query   qtype rname   rtype rdata           ttl
##   <chr>   <int> <chr>   <int> <chr>         <int>
## 1 rud.is.     1 rud.is.     1 172.93.49.183  3600

doh_post("lenovo.com", "txt")
## # A tibble: 10 x 6
##    query      qtype rname      rtype rdata                                                                           ttl
##    <chr>      <int> <chr>      <int> <chr>                                                                         <int>
##  1 lenovo.co…    16 lenovo.co…    16 "\"qh7hdmqm4lzs85p704d6wsybgrpsly0j\""                                            1
##  2 lenovo.co…    16 lenovo.co…    16 "\"ece42d7743c84d6889abda7011fe6f53\""                                            1
##  3 lenovo.co…    16 lenovo.co…    16 "\"a82c74b37aa84e7c8580f0e32f4d795d\""                                            1
##  4 lenovo.co…    16 lenovo.co…    16 "\"google-site-verification=VxW_e6r_Ka7A518qfX2MmIMHGnkpGbnACsjSxKFCBw0\""        1
##  5 lenovo.co…    16 lenovo.co…    16 "\"google-site-verification=sHIlSlj0U6UnCDkfHp1AolWgVEvDjWvc0TR4KaysD2c\""        1
##  6 lenovo.co…    16 lenovo.co…    16 "\"Visit www.lenovo.com/think for information about Lenovo products and serv…     1
##  7 lenovo.co…    16 lenovo.co…    16 "\"google-site-verification=nGgukcp60rC-gFxMOJw1NHH0B4VnSchRrlfWV-He_tE\""        1
##  8 lenovo.co…    16 lenovo.co…    16 "\"iHzQJvsKnyGP2Nm2qBgL3fyBJ0CC9z4GkY/flfk4EzLP8lPxWHDDPKqZWm1TkeF5kEIL+NotY…     1
##  9 lenovo.co…    16 lenovo.co…    16 "\"v=spf1 include:spf.messagelabs.com include:_netblocks.eloqua.com ~all\""       1
## 10 lenovo.co…    16 lenovo.co…    16 "\"facebook-domain-verification=1r2am7c2bhzrxpqyt0mda0djoquqsi\""                 1
doh_get("rud.is")
##      name type  ttl                       expires          data
## 1 rud.is.    1 2300 Sun, 26 May 2019 18:08:58 UTC 172.93.49.183

doh_get("lenovo.com", "txt")
##           name type  ttl                       expires
## 1  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 2  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 3  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 4  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 5  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 6  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 7  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 8  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 9  lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
## 10 lenovo.com.   16 7200 Sun, 26 May 2019 19:30:38 UTC
##                                                                                          data
## 1                               "facebook-domain-verification=1r2am7c2bhzrxpqyt0mda0djoquqsi"
## 2                      "google-site-verification=sHIlSlj0U6UnCDkfHp1AolWgVEvDjWvc0TR4KaysD2c"
## 3                      "google-site-verification=nGgukcp60rC-gFxMOJw1NHH0B4VnSchRrlfWV-He_tE"
## 4                                                          "a82c74b37aa84e7c8580f0e32f4d795d"
## 5             "Visit www.lenovo.com/think for information about Lenovo products and services"
## 6                                                          "qh7hdmqm4lzs85p704d6wsybgrpsly0j"
## 7                      "google-site-verification=VxW_e6r_Ka7A518qfX2MmIMHGnkpGbnACsjSxKFCBw0"
## 8                                                          "ece42d7743c84d6889abda7011fe6f53"
## 9  "iHzQJvsKnyGP2Nm2qBgL3fyBJ0CC9z4GkY/flfk4EzLP8lPxWHDDPKqZWm1TkeF5kEIL+NotYOF1wo7JtUDXXw=="
## 10                    "v=spf1 include:spf.messagelabs.com include:_netblocks.eloqua.com ~all"
```

## playdoh Metrics

| Lang | \# Files |  (%) | LoC |  (%) | Blank lines |  (%) | \# Lines |  (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
| R    |        6 | 0.86 | 122 | 0.92 |          23 | 0.51 |       26 | 0.42 |
| Rmd  |        1 | 0.14 |  11 | 0.08 |          22 | 0.49 |       36 | 0.58 |
| R    |        6 | 0.86 | 107 | 0.91 |          20 | 0.48 |       47 | 0.57 |
| Rmd  |        1 | 0.14 |  11 | 0.09 |          22 | 0.52 |       36 | 0.43 |

## Code of Conduct


R man/doh_post.Rd => man/doh_get.Rd +14 -7
@@ 1,24 1,31 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/doh-post.R
\name{doh_post}
\alias{doh_post}
\title{Make a POST DoH Request (wireformat)}
% Please edit documentation in R/doh-get.R
\name{doh_get}
\alias{doh_get}
\title{Make a DoH Request (GET/REST)}
\usage{
doh_post(name, type = "A",
  server_path = "https://dns.quad9.net/dns-query")
doh_get(name, type = "a", extra_params = list(),
  service_path = "https://9.9.9.9/dns-query")
}
\arguments{
\item{name}{name to query for}

\item{type}{DNS query type (defaults to "\code{A}")}

\item{extra_params}{any special \code{GET} query parameter needed for a given server API endpoint.
this should be a named \code{list}.}

\item{server_path}{full URL path to the DoH server quer endpoint (defaults to Quad9).}
}
\value{
\code{NULL} (if the query failed) or a \code{data.frame} (tibble)
}
\description{
Issue the query of type \code{type} for \code{name} to the DoH endpoint specified at \code{server_path}.
Issue a \code{GET} REST API query of type \code{type} for \code{name} to the
DoH endpoint specified at \code{server_path}.
}
\examples{
doh_get("rud.is", "A")
}
\references{
\url{https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-05.html}

A man/doh_servers.Rd => man/doh_servers.Rd +27 -0
@@ 0,0 1,27 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/globals.R
\docType{data}
\name{doh_servers}
\alias{doh_servers}
\title{Built-in list of DoH Servers}
\format{An object of class \code{list} of length 6.}
\usage{
doh_servers
}
\description{
The \code{url} element has the URL for the \code{GET} requests and
the \code{extra_params} element has any needed query parameters
for the \code{GET} requests.
}
\details{
The list so far.
\itemize{
\item \code{google}: \url{https://dns.google.com/experimental}
\item \code{cloudflare}: \url{https://cloudflare-dns.com/dns-query}
\item \code{quad9}: \url{https://dns.quad9.net/dns-query}
\item \code{securedns_eu}: \url{https://doh.securedns.eu/dns-query}
\item \code{dnswarden_adblock}: \url{https://doh.dnswarden.com/adblock}
\item \code{dnswarden_uncensored}: \url{https://doh.dnswarden.com/uncensored}
}
}
\keyword{datasets}