Commit f81321cf authored by Jiří Helebrant's avatar Jiří Helebrant

Use binary DoH

parent 57f41d60
Pipeline #49354 passed with stages
in 3 minutes and 13 seconds
const dnspacket = require('native-dns-packet')
const Buffer = require('buffer/').Buffer
const dohQuery = (server, b64packet) =>
fetch(`${server}?dns=${b64packet}`, {
Headers: {
Accept: 'application/dns-message',
'Content-type': 'application/dns-message'
// Adapted from https://github.com/mafintosh/dns-packet to require dnssec by default
const base64url = require('base64url')
const dnsPacket = require('dns-packet')
const getDnsQuery = ({ type, name, klass, id }) => ({
type: 'query',
id,
flags: dnsPacket.RECURSION_DESIRED,
questions: [
{
class: klass,
name,
type
}
})
.then(r => r.arrayBuffer())
.then(b => dnspacket.parse(Buffer.from(b)))
.catch(e => {
console.error(e)
],
additionals: [
{
type: 'OPT',
name: '.',
udpPayloadSize: 4096,
flags: dnsPacket.DNSSEC_OK
}
]
})
const getDnsWireformat = ({ name, type, klass }) => {
const id = 0 // As mandated by RFC-8484.
const dnsQuery = getDnsQuery({ type, name, klass, id })
const dnsQueryBuf = dnsPacket.encode(dnsQuery)
return dnsQueryBuf
}
const getOptions = ({ method, userAgent, port, hostname, path, name, type, klass }) => {
const dnsWireformat = getDnsWireformat({ name, type, klass })
const isPost = method === 'POST'
const dohPath = isPost ? path : `${path}?dns=${base64url(dnsWireformat)}`
const headers = {
accept: 'application/dns-message',
'User-Agent': userAgent,
...(isPost && {
'content-type': 'application/dns-message',
'content-length': dnsWireformat.length
})
}
return { hostname, headers, method, path: dohPath, port }
}
const query = ({
name,
method = 'POST',
hostname,
path,
port = 443,
userAgent = 'dnssec-validator',
type = 'A',
klass = 'IN',
useHttps = true
}) =>
new Promise((resolve, reject) => {
const options = getOptions({
method,
hostname,
path,
port,
userAgent,
name,
type,
klass
})
const httpAgent = useHttps ? require('https') : require('http')
const req = httpAgent.request(options, res =>
res.on('data', data => {
const { statusCode } = res
const makeDnsPacket = (name, type = 1) => {
const buff = new Buffer(4096)
const size = dnspacket.write(buff, {
header: {
rd: 1
},
question: [{ class: 1, name, type }],
answer: [],
authority: [],
additional: [],
edns_options: [],
payload: undefined
switch (statusCode) {
case 200:
resolve(dnsPacket.decode(data))
break
case 400:
case 413:
case 415:
case 504:
resolve(`Error[${statusCode}]: ${data.toString()}`)
break
default:
resolve(`Error[${statusCode}]: Unsupported HTTP status code - ${statusCode}`)
}
})
)
if (method === 'POST') {
const dnsWireformat = getDnsWireformat({ name, type, klass })
req.write(dnsWireformat)
}
req.on('error', e => reject(e))
req.end()
})
const packet = buff.slice(0, size)
return btoa(packet).replace(/={1,2}$/, '')
}
export { makeDnsPacket, dohQuery }
module.exports = { getDnsWireformat, getDnsQuery, getOptions, query }
import storage from './storage'
// import settingsStore from './settings'
import config from '../../config.json'
import doh from './doh'
import { toRcode } from 'dns-packet/rcodes'
const getTTL = j =>
j.hasOwnProperty('Answer') && j['Answer'].length > 0 && j['Answer'][0].hasOwnProperty('TTL')
? Math.max(j['Answer'][0]['TTL'], 15)
: config.defaultSettings.cacheTime.domain
const DoHquery = async (name, hostname, path) =>
doh.query({
name,
hostname,
path,
method: 'POST',
userAgent: 'dnssec-validator'
})
const getTTL = (j, hostname) => {
let ttl = config.defaultSettings.cacheTime.domain
const answerPart = j['answers'].filter(a => a.name === hostname)
if (answerPart.length > 0 && answerPart[0].hasOwnProperty('ttl')) {
ttl = Math.min(answerPart[0].ttl, 15)
}
return ttl
}
const resolveDnssecStatus = async hostname =>
fetch(`${config.DoHservers[0].url}?name=${hostname}&cd=0`, {
// https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format/
headers: { accept: 'application/dns-json' }
})
.then(r => r.json())
DoHquery(hostname, 'odvr.nic.cz', '/doh')
// DoHquery(hostname, 'dns.google.com', '/experimental')
// DoHquery(hostname, 'cloudflare-dns.com', '/dns-query')
.then(j => {
const status = j['Status']
const verified = j['AD']
const ttl = getTTL(j)
const rcode = toRcode(j.rcode)
const verified = j.flag_ad
const ttl = getTTL(j, hostname)
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
if (status === 0 && verified === true) return { dnssec: true, valid: true, ttl } // valid dnssec
if (status === 0 && verified === false) return { dnssec: false, ttl } // no dnssec
if (status === 2) return { dnssec: true, valid: false, ttl } // broken dnssec
if (status === 1 || status > 3) return { dnssec: false, ttl }
if (rcode === 0 && verified === true) return { dnssec: true, valid: true, ttl } // valid dnssec
if (rcode === 0 && verified === false) return { dnssec: false, ttl } // no dnssec
if (rcode === 2) return { dnssec: true, valid: false, ttl } // broken dnssec
if (rcode === 1 || rcode > 3) return { dnssec: false, ttl }
})
.catch(async () => ({ dnssec: false, ttl: config.defaultSettings.cacheTime.domain }))
.catch(() => ({ dnssec: false, ttl: config.defaultSettings.cacheTime.domain }))
const updateDomainStatus = async hostname => {
const status = {
......@@ -35,14 +48,14 @@ const updateDomainStatus = async hostname => {
}
const getDomainStatus = async hostname => {
// -> trigger reload in chrome for broken domains?
hostname = hostname.trim()
const result = await storage('domains', 'get', { hostname })
if (result && result.status !== undefined) {
const age = (+new Date() - result.timestamp) / 1000
if (age <= result.status.ttl) {
return result.status
}
console.log('domain outdated:', hostname, `${age} >= ${result.status.ttl}`)
// console.log('domain outdated:', hostname, `${age} >= ${result.status.ttl}`)
updateDomainStatus(hostname)
return result.status
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment