Compare commits
250 Commits
Author | SHA1 | Date |
---|---|---|
Renovate Bot | e91ab27f13 | |
Renovate Bot | 6677c6b5e5 | |
Renovate Bot | 7126c753e9 | |
Renovate Bot | c1a4a7f93a | |
Sam Therapy | 9aebf89f57 | |
Renovate Bot | 37e0c1e9b5 | |
Sam Therapy | 40278ed22f | |
Renovate Bot | 7631dd7a7b | |
Renovate Bot | 2140c1582f | |
Renovate Bot | 8c00f85636 | |
Renovate Bot | 8e54337787 | |
Sam Therapy | a5980be3cc | |
Sam Therapy | 58cee5b3c9 | |
Renovate Bot | 3881caff0d | |
Sam Therapy | d8c40abf5a | |
Sam Therapy | 92cf0b465b | |
Sam Therapy | b5f4ea9c42 | |
Sam Therapy | 8434739516 | |
Sam Therapy | 556b7a48af | |
Sam Therapy | 21047275bf | |
Sam Therapy | 835cb8c950 | |
Sam Therapy | 20f5b21be1 | |
Renovate Bot | b975dd8c00 | |
Renovate Bot | 42e50d3e93 | |
Sam Therapy | 7030e68100 | |
Renovate Bot | c4a267cec9 | |
Renovate Bot | 434de0e5b4 | |
Renovate Bot | 52cef2e3a3 | |
Renovate Bot | e0b14b8b00 | |
Renovate Bot | c580ac78d4 | |
Renovate Bot | 99e75afcd3 | |
Renovate Bot | 6ddbf854d8 | |
Sam Therapy | 16ef3de591 | |
Renovate Bot | f9263300c7 | |
Sam Therapy | d43543e48a | |
Sam Therapy | 0a6d6b432c | |
Sam Therapy | ffb16ff7fa | |
Sam Therapy | efb4cd6c4a | |
Sam Therapy | 59fa146d7f | |
Sam Therapy | c5c90b97af | |
Sam Therapy | e1122a9de7 | |
Sam Therapy | 26ef04b969 | |
Sam Therapy | 2565d34ade | |
Sam Therapy | 7dd9b138a3 | |
Sam Therapy | b8a2407169 | |
Renovate Bot | 9ef46d33e8 | |
Renovate Bot | 939a09da5b | |
Renovate Bot | 87c59551ec | |
Renovate Bot | 7efc9523bf | |
Renovate Bot | 2084ae69f6 | |
Renovate Bot | 5130b6c3cd | |
Renovate Bot | 8c949ce4ca | |
Renovate Bot | b3fad7abcf | |
Renovate Bot | b713aeb7d4 | |
Renovate Bot | 260eb979b3 | |
Renovate Bot | 83ecfbaea1 | |
Renovate Bot | 10faea804b | |
Renovate Bot | 0f80004790 | |
Renovate Bot | 12863559c5 | |
Renovate Bot | 8e9574f870 | |
Renovate Bot | fadb04f49f | |
Renovate Bot | 5704c6a0dd | |
Renovate Bot | 9c8e5349bf | |
Sam Therapy | dcd932c42e | |
Sam Therapy | f4c3283954 | |
Sam Therapy | 824a60aef1 | |
Sam Therapy | 43e53a4055 | |
Sam Therapy | 60b0300eec | |
Sam Therapy | 9740e6b348 | |
Renovate Bot | 8c888a90f1 | |
Renovate Bot | 71477684d9 | |
Renovate Bot | e3951db486 | |
Renovate Bot | dfe4cca176 | |
Renovate Bot | b5102c91e6 | |
Renovate Bot | 03e8f6be61 | |
Renovate Bot | f60cd1c315 | |
Renovate Bot | 00b116abf4 | |
Renovate Bot | 821149b463 | |
Renovate Bot | f2f53795ef | |
Renovate Bot | 322345a758 | |
Renovate Bot | b4de2ba89e | |
Sam Therapy | 3421066420 | |
Sam Therapy | aef8a73a28 | |
Sam Therapy | ba93384f9b | |
Sam Therapy | 66855b5542 | |
Sam Therapy | ccc9df39b6 | |
Renovate Bot | c6bbda17b2 | |
Sam Therapy | db77f2315c | |
Sam Therapy | a157dcb7fb | |
Sam Therapy | 428c218405 | |
Sam Therapy | 8df4863cf4 | |
Sam Therapy | c0cd4d7771 | |
Renovate Bot | 5f1b81a2be | |
Renovate Bot | 45d1aaf535 | |
Renovate Bot | 4f42657294 | |
Renovate Bot | b6bd22a8fe | |
Sam Therapy | b0d8e325d6 | |
Sam Therapy | fb9fd7689f | |
Sam Therapy | 329d81c001 | |
Renovate Bot | 5329a6f2e0 | |
Renovate Bot | 95ff6f554c | |
Sam Therapy | 928123edbd | |
Renovate Bot | fd3ceeb066 | |
Renovate Bot | 1acd48b16d | |
Sam Therapy | 27ece358c6 | |
Sam Therapy | 58fa7af2ea | |
Sam Therapy | 75fb8f2fc6 | |
Sam Therapy | 3423958809 | |
Renovate Bot | 60491c39c1 | |
Renovate Bot | 39f605740a | |
Sam Therapy | 171f5d0d11 | |
Renovate Bot | 1e4ff59631 | |
Renovate Bot | f1583fef52 | |
Sam Therapy | fa709ca327 | |
Sam Therapy | 30277b5028 | |
Sam Therapy | 028c423d89 | |
Renovate Bot | 641b003381 | |
Sam Therapy | b15584f436 | |
Sam Therapy | 9c8f30f432 | |
Sam Therapy | da172f9de5 | |
Sam Therapy | feb0617ce2 | |
Sam Therapy | daff2b7687 | |
Sam Therapy | e782c9cb23 | |
Sam Therapy | 00ec4ef893 | |
Sam Therapy | bf04f59468 | |
Renovate Bot | db66371420 | |
Renovate Bot | 42b17a7912 | |
Renovate Bot | 4286c16ae2 | |
Sam Therapy | 6d9690c44f | |
Sam Therapy | bc6e7cd759 | |
Sam Therapy | 2312fecfc8 | |
Sam Therapy | 75479f5060 | |
Renovate Bot | 0c8000d598 | |
Sam Therapy | 30be18b474 | |
grumbulon | d93eccc064 | |
Sam Therapy | fdba9a0a41 | |
Sam Therapy | a7f7498d8b | |
Sam Therapy | 792ddecf6c | |
Sam Therapy | 10d1c4cd2b | |
Sam Therapy | 530ef06ee1 | |
Sam Therapy | 0b042af3e2 | |
Renovate Bot | 4349aec1f2 | |
Sam Therapy | 185a79e1c2 | |
Renovate Bot | 11ebd2c69f | |
Sam Therapy | 64768f0956 | |
Renovate Bot | 45acd03dff | |
Sam Therapy | 78bbb8f23d | |
Renovate Bot | 7e7c88ddd5 | |
Sam Therapy | a2e7b262ea | |
Sam Therapy | 90f5b92ffd | |
grumbulon | 5d67ffaad3 | |
Sam Therapy | 303eb974c8 | |
Sam Therapy | dfa14cbcdf | |
Sam Therapy | 216431f591 | |
Renovate Bot | 07260fb593 | |
Renovate Bot | 9a9658c6f8 | |
Renovate Bot | e456d409ef | |
Sam Therapy | ea3943dd63 | |
Sam Therapy | feef2cddaf | |
Sam Therapy | 2836633c62 | |
Sam Therapy | a64a260c55 | |
Sam Therapy | dd542b9f18 | |
Sam Therapy | c658b6b796 | |
Renovate Bot | da7ce61152 | |
Sam Therapy | 852c87c3ab | |
Sam Therapy | ec6e723eaf | |
Sam Therapy | 698b0d3ad1 | |
Sam Therapy | 8c3a5cb369 | |
Sam Therapy | 60e22e6d33 | |
Sam Therapy | 7feaab7103 | |
Sam Therapy | b1fa25a9a0 | |
Sam Therapy | 59212798f7 | |
Sam Therapy | a386960076 | |
Sam Therapy | 6144c7219a | |
Renovate Bot | 967363f34b | |
Sam Therapy | 2db6ee3935 | |
Renovate Bot | 3a1ebaafe1 | |
Renovate Bot | 354128d449 | |
Sam Therapy | def649de4f | |
Renovate Bot | d6a9bd9b0b | |
Sam Therapy | 0bcc63de8a | |
Sam Therapy | 39639c0f7f | |
Sam Therapy | b80219019e | |
Sam Therapy | bf0e44e80c | |
Sam Therapy | 9ec28f9b1a | |
Sam Therapy | dc4edd55bb | |
Sam Therapy | add1ef61a2 | |
Sam Therapy | 2d94ea6838 | |
Renovate Bot | 216a4b6dd7 | |
Renovate Bot | 66422e1512 | |
Sam Therapy | 81da49093d | |
Sam Therapy | e6a3d6040a | |
Renovate Bot | c1b5961717 | |
Renovate Bot | 3d97359070 | |
Renovate Bot | 561958c1b0 | |
Renovate Bot | d2c6ed317e | |
Renovate Bot | 9734f8ddad | |
Renovate Bot | 296f5f0a0b | |
Sam Therapy | 4d0605bd1e | |
Renovate Bot | a74fbf525b | |
Renovate Bot | cf095313a4 | |
Renovate Bot | 6749361395 | |
Sam Therapy | 4495da2f3e | |
Renovate Bot | 11c06f9662 | |
Sam Therapy | d701059b5f | |
Sam Therapy | 56baff4e18 | |
Sam Therapy | 6a839ba8e5 | |
Sam Therapy | f2cf35ca31 | |
Sam Therapy | 607c321de3 | |
Sam Therapy | f01f2bc15a | |
Sam Therapy | c053c077c8 | |
Renovate Bot | 0a371cd335 | |
Sam Therapy | f2218481ee | |
Renovate Bot | 14416d5aec | |
Renovate Bot | 7b11583d6e | |
Renovate Bot | 7bd481cd7a | |
Sam Therapy | 60643e2b3e | |
Sam Therapy | 434632884c | |
Renovate Bot | 284c0646f2 | |
grumbulon | 3a0a8f015a | |
Sam Therapy | ac55b21b25 | |
Renovate Bot | 840397a85a | |
Renovate Bot | f24608667a | |
Sam Therapy | 0845ae2a82 | |
Renovate Bot | 94b7f523b7 | |
Renovate Bot | c75db286dc | |
Renovate Bot | 261e5fd6c1 | |
Renovate Bot | 75ca107e55 | |
Renovate Bot | 98c641848e | |
Renovate Bot | 86f115f361 | |
Sam Therapy | ef87175190 | |
Sam Therapy | 933967016f | |
Sam Therapy | 8df0347891 | |
Sam Therapy | ddd0277de9 | |
Renovate Bot | 55b965d084 | |
Sam Therapy | 9746ae0a6f | |
Renovate Bot | 0d011bb097 | |
Sam Therapy | 6fa6b2d1f6 | |
Renovate Bot | 4f743f861d | |
Renovate Bot | 90d2fed6ab | |
Sam Therapy | c8282e8030 | |
Sam Therapy | 1d0ccbef44 | |
Sam Therapy | ae7209c334 | |
Sam Therapy | dd2a7122c2 | |
Renovate Bot | 48b0ba8f63 | |
Renovate Bot | 062008b4dc | |
Renovate Bot | 88d090c04a | |
Renovate Bot | 74a7ba3c32 | |
Renovate Bot | f83453c1ee | |
Sam Therapy | 789710cbeb |
102
.drone.jsonnet
102
.drone.jsonnet
|
@ -1,102 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
local testing(version, arch) = {
|
||||
kind: "pipeline",
|
||||
type: "docker",
|
||||
name: version + "-" + arch ,
|
||||
platform: {
|
||||
arch: arch
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "compile",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make awl"
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "lint",
|
||||
image: "rancher/drone-golangci-lint:latest",
|
||||
depends_on: [
|
||||
"compile",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make test-ci"
|
||||
],
|
||||
depends_on: [
|
||||
"lint",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "fuzz",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make fuzz",
|
||||
],
|
||||
depends_on: [
|
||||
"lint",
|
||||
],
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
event: {
|
||||
exclude: [
|
||||
"tag"
|
||||
],
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// "Inspired by" https://goreleaser.com/ci/drone/
|
||||
local release() = {
|
||||
kind: "pipeline",
|
||||
type: "docker",
|
||||
name: "release",
|
||||
trigger: {
|
||||
event: [
|
||||
"tag"
|
||||
],
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "fetch",
|
||||
image: "alpine/git",
|
||||
commands : [
|
||||
"git fetch --tags",
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
image: "golang",
|
||||
commands: [
|
||||
"make test"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "release",
|
||||
image: "goreleaser/goreleaser",
|
||||
environment: {
|
||||
"GITEA_TOKEN": {
|
||||
from_secret: "GITEA_TOKEN"
|
||||
}
|
||||
},
|
||||
commands: [
|
||||
"goreleaser release"
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
[
|
||||
testing("1.19", "amd64"),
|
||||
// testing("1.19", "arm64"),
|
||||
// testing("1.18", "amd64"),
|
||||
// testing("1.18", "arm64"),
|
||||
|
||||
release()
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
|
@ -0,0 +1,35 @@
|
|||
name: Forgejo Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install scdoc
|
||||
run: apt-get update && apt-get install -y scdoc
|
||||
|
||||
- name: Release with GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --skip docker,snapcraft
|
||||
env:
|
||||
GORELEASER_FORCE_TOKEN: gitea
|
||||
GITEA_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
UPLOAD_PACKAGES_SECRET: ${{ secrets.PUBLISH_TOKEN }}
|
|
@ -0,0 +1,14 @@
|
|||
name: Mirror Push
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url: git@git.sr.ht:~sammefishe/awl
|
||||
ssh_private_key: ${{ secrets.SRHT_SSH_KEY }}
|
|
@ -0,0 +1,24 @@
|
|||
name: Test
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
goVer: ["oldstable", "stable"]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Test
|
||||
run: make test-ci
|
|
@ -4,29 +4,53 @@ on:
|
|||
push:
|
||||
tags:
|
||||
- "*"
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS : ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||
steps:
|
||||
# Workaround a dumb docker problem where everything has to be lowercase
|
||||
- id: lowercase
|
||||
run: |
|
||||
echo IMAGE_NAME=$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]') >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: stable
|
||||
|
||||
- name: Install scdoc
|
||||
run: sudo apt-get install -y scdoc
|
||||
|
||||
- name: Workaround a dumb Snap bug
|
||||
run: mkdir -p $HOME/.cache/snapcraft/download && mkdir -p $HOME/.cache/snapcraft/stage-packages
|
||||
|
||||
- name: Release with GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean --skip=aur,homebrew,nix,scoop
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -7,19 +7,18 @@ jobs:
|
|||
fail-fast: true
|
||||
matrix:
|
||||
platform: [macos, windows]
|
||||
goVer: [1.18, 1.19]
|
||||
goVer: ["oldstable", "stable"]
|
||||
runs-on: ${{ matrix.platform }}-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Test
|
||||
run: make test-ci
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
@ -23,6 +23,7 @@ coverage/*
|
|||
!coverage/.gitkeep
|
||||
|
||||
awl
|
||||
doc/awl.1
|
||||
docs/awl.1
|
||||
docs/awl.1.gz
|
||||
|
||||
.dccache
|
||||
.dccache
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
[submodule "doc/wiki"]
|
||||
path = doc/wiki
|
||||
[submodule "docs/wiki"]
|
||||
path = docs/wiki
|
||||
url = ../awl.wiki
|
||||
[submodule "pkg/awl-dns-git"]
|
||||
path = pkg/awl-dns-git
|
||||
url = https://aur.archlinux.org/awl-dns-git.git
|
||||
|
|
|
@ -32,6 +32,7 @@ linters:
|
|||
- predeclared
|
||||
- revive
|
||||
- staticcheck
|
||||
- tagliatelle
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
|
@ -70,6 +71,16 @@ linters-settings:
|
|||
- name: unexported-return
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
linters-settings:
|
||||
tagliatelle:
|
||||
case:
|
||||
use-field-name: false
|
||||
rules:
|
||||
# Any struct tag type can be used.
|
||||
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
|
||||
json: goCamel
|
||||
yaml: goCamel
|
||||
xml: goCamel
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
|
|
231
.goreleaser.yaml
231
.goreleaser.yaml
|
@ -2,44 +2,235 @@
|
|||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- make clean
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
# - go generate ./...
|
||||
# Make manpages
|
||||
- docs/makeman.sh
|
||||
# Vendor dependencies
|
||||
- go mod vendor
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
targets:
|
||||
- go_first_class
|
||||
- plan9_amd64
|
||||
- freebsd_amd64
|
||||
|
||||
universal_binaries:
|
||||
- replace: true
|
||||
|
||||
archives:
|
||||
-
|
||||
files:
|
||||
- LICENCE
|
||||
- files:
|
||||
- LICENSE
|
||||
- completions/**
|
||||
replacements:
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
amd64: x86_64
|
||||
- docs/awl.1.gz
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- if eq .Os "darwin" }}MacOS_
|
||||
{{- else if eq .Os "freebsd" }}FreeBSD_
|
||||
{{- else }}{{- title .Os }}_{{ end }}
|
||||
{{- if eq .Arch "386" }}i386
|
||||
{{- else if eq .Arch "mips64" }}mips64_hardfloat
|
||||
{{- else if eq .Arch "mips64le" }}mips64le_hardfloat
|
||||
{{- else }}{{ .Arch }}{{ end -}}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
- files:
|
||||
- vendor/**
|
||||
id: vendor
|
||||
format: tar.xz
|
||||
name_template: "{{ .ProjectName }}-{{ .Version }}-deps"
|
||||
meta: true
|
||||
wrap_in_directory: "{{ .ProjectName }}"
|
||||
|
||||
nfpms:
|
||||
- id: packages
|
||||
package_name: awl-dns
|
||||
vendor: Sam Therapy <sam@samtherapy.net>
|
||||
maintainer: Sam Therapy <sam@samtherapy.net>
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: |-
|
||||
Command-line DNS query tool.
|
||||
Awl supports DNS-over-[UDP,TCP,HTTPS,QUIC] and DNSCrypt.
|
||||
license: BSD-3-Clause
|
||||
section: utils
|
||||
bindir: /usr/bin
|
||||
formats:
|
||||
- apk
|
||||
- archlinux
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: completions/bash.bash
|
||||
dst: /usr/share/bash-completion/completions/awl
|
||||
- src: docs/awl.1.gz
|
||||
dst: /usr/share/man/man1/awl.1.gz
|
||||
- src: LICENSE
|
||||
dst: /usr/share/docs/awl/copyright
|
||||
- src: completions/fish.fish
|
||||
dst: /usr/share/fish/vendor_completions.d/awl.fish
|
||||
# DEB only
|
||||
- src: completions/zsh.zsh
|
||||
dst: /usr/share/zsh/vendor-completions/_awl
|
||||
packager: deb
|
||||
# Alpine .apk only
|
||||
- src: completions/zsh.zsh
|
||||
dst: /usr/share/zsh/site-functions/_awl
|
||||
packager: apk
|
||||
# RPM only
|
||||
- src: completions/zsh.zsh
|
||||
dst: /usr/share/zsh/site-functions/_awl
|
||||
packager: rpm
|
||||
deb:
|
||||
lintian_overrides:
|
||||
- statically-linked-binary
|
||||
- changelog-file-missing-in-native-package
|
||||
overrides:
|
||||
deb:
|
||||
file_name_template: >-
|
||||
{{- .PackageName }}_
|
||||
{{- .Version }}_
|
||||
{{- if eq .Arch "386" }}i386
|
||||
{{- else if eq .Arch "arm" }}armel
|
||||
{{- else }}{{ .Arch }}{{ end -}}
|
||||
rpm:
|
||||
file_name_template: >-
|
||||
{{- .PackageName }}-
|
||||
{{- .Version }}-
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i686
|
||||
{{- else if eq .Arch "arm" }}armhfp
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end -}}
|
||||
- id: termux
|
||||
package_name: awl-dns
|
||||
vendor: Sam Therapy <sam@samtherapy.net>
|
||||
maintainer: Sam Therapy <sam@samtherapy.net>
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: |-
|
||||
Command-line DNS query tool.
|
||||
Awl supports DNS-over-[UDP,TCP,HTTPS,QUIC] and DNSCrypt.
|
||||
license: BSD-3-Clause
|
||||
section: utils
|
||||
formats:
|
||||
- termux.deb
|
||||
file_name_template: >-
|
||||
{{- .PackageName }}_
|
||||
{{- .Version }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i686
|
||||
{{- else if eq .Arch "arm" }}arm
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end -}}
|
||||
|
||||
snapcrafts:
|
||||
-
|
||||
name: awl-dns
|
||||
grade: stable
|
||||
publish: true
|
||||
summary: A command-line DNS query tool
|
||||
description: |-
|
||||
Awl is a command-line DNS query tool.
|
||||
Awl supports DNS-over-[UDP,TCP,HTTPS,QUIC] and DNSCrypt.
|
||||
confinement: strict
|
||||
license: BSD-3-Clause
|
||||
base: bare
|
||||
apps:
|
||||
awl-dns:
|
||||
command: awl
|
||||
plugs:
|
||||
- network
|
||||
completer: completions/bash.bash
|
||||
|
||||
dockers:
|
||||
-
|
||||
image_templates:
|
||||
- "{{ .Env.REGISTRY }}/{{ .Env.IMAGE_NAME }}:latest"
|
||||
- "{{ .Env.REGISTRY }}/{{ .Env.IMAGE_NAME }}:{{ .Tag }}"
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
|
||||
brews:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: homebrew
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query tool
|
||||
license: BSD-3-Clause
|
||||
# custom_block: |
|
||||
# head "https://git.froth.zone/sam/awl.git"
|
||||
install: |-
|
||||
bin.install "awl"
|
||||
bash_completion.install "completions/bash.bash" => "awl"
|
||||
zsh_completion.install "completions/zsh.zsh" => "_awl"
|
||||
fish_completion.install "completions/fish.fish" => "awl.fish"
|
||||
man1.install "docs/awl.1.gz"
|
||||
|
||||
nix:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: nur
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query client
|
||||
license: bsd3
|
||||
extra_install: |-
|
||||
installManPage ./docs/awl.1.gz
|
||||
installShellCompletion ./completions/*
|
||||
|
||||
scoops:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: scoop
|
||||
folder: bucket
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query client
|
||||
license: BSD-3-Clause
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
groups:
|
||||
- title: "Dependency Updates"
|
||||
regexp: "^.*fix\\(deps\\)*:+.*$"
|
||||
order: 2
|
||||
- title: "Features"
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "Bug fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "Other"
|
||||
order: 999
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^docs?:"
|
||||
- "typo"
|
||||
- "^ci:"
|
||||
|
||||
uploads:
|
||||
- name: packages
|
||||
method: PUT
|
||||
mode: archive
|
||||
exts:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
- termux.deb
|
||||
username: sam
|
||||
target: >-
|
||||
https://git.froth.zone/api/packages/sam/
|
||||
{{- if eq .ArtifactExt "deb" }}debian/pool/sid/main/upload
|
||||
{{- else if eq .ArtifactExt "termux.deb" }}debian/pool/termux/main/upload
|
||||
{{- else if eq .ArtifactExt "rpm" }}rpm/upload
|
||||
{{- else if eq .ArtifactExt "apk" }}alpine/edge/main{{ end -}}
|
||||
custom_artifact_name: true # Truncate the artifact name from the upload URL
|
||||
|
||||
gitea_urls:
|
||||
api: https://git.froth.zone/api/v1/
|
||||
api: https://git.froth.zone/api/v1
|
||||
download: https://git.froth.zone
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
FROM scratch
|
||||
ENTRYPOINT ["/awl"]
|
||||
COPY awl /
|
13
GNUmakefile
13
GNUmakefile
|
@ -7,6 +7,11 @@ ifeq ($(OS),Windows_NT)
|
|||
EXE := $(PROG).exe
|
||||
else
|
||||
EXE := $(PROG)
|
||||
ifeq ($(shell uname), Darwin)
|
||||
INSTALLFLAGS :=
|
||||
else
|
||||
INSTALLFLAGS := D
|
||||
endif
|
||||
endif
|
||||
|
||||
## install: installs awl
|
||||
|
@ -16,8 +21,10 @@ install:
|
|||
$(GO) install $(GOFLAGS) .
|
||||
else
|
||||
install: all
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -Dm644 doc/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
install -$(INSTALLFLAGS)m755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -$(INSTALLFLAGS)m644 docs/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
# completions need to go in one specific place :)
|
||||
install -Dm644 completions/zsh.zsh $(DESTDIR)/$(PREFIX)/$(SHARE)/zsh/site-functions/_$(PROG)
|
||||
install -$(INSTALLFLAGS)m644 completions/bash.bash $(DESTDIR)$(PREFIX)/$(SHARE)/bash-completion/completions/$(PROG)
|
||||
install -$(INSTALLFLAGS)m644 completions/fish.fish $(DESTDIR)$(PREFIX)/$(SHARE)/fish/vendor_completions.d/$(PROG).fish
|
||||
install -$(INSTALLFLAGS)m644 completions/zsh.zsh $(DESTDIR)$(PREFIX)/$(SHARE)/zsh/site-functions/_$(PROG)
|
||||
endif
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
Copyright 2022 Sam Therapy, Gregward Bulon
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
Copyright 2022 Sam Therapy, Gregward Bulon
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
6
Makefile
6
Makefile
|
@ -9,6 +9,8 @@ EXE := $(PROG)
|
|||
.PHONY: install
|
||||
install: all
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -Dm644 doc/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
install -Dm644 docs/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
# completions need to go in one specific place :)
|
||||
install -Dm644 completions/zsh.zsh $(DESTDIR)/$(PREFIX)/$(SHARE)/zsh/site-functions/_$(PROG)
|
||||
install -Dm644 completions/bash.bash $(DESTDIR)$(PREFIX)$(SHARE)/bash-completion/completions/$(PROG)
|
||||
install -Dm644 completions/fish.fish $(DESTDIR)$(PREFIX)$(SHARE)/fish/vendor_completions.d/$(PROG).fish
|
||||
install -Dm644 completions/zsh.zsh $(DESTDIR)$(PREFIX)$(SHARE)/zsh/site-functions/_$(PROG)
|
||||
|
|
220
README.md
220
README.md
|
@ -1,63 +1,195 @@
|
|||
# awl
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
# <img src="./docs/img/awl-text.png" width="50%" title="awl logo" alt="awl">
|
||||
|
||||
[![Build Status](https://ci.git.froth.zone/api/badges/sam/awl/status.svg)](https://ci.git.froth.zone/sam/awl)
|
||||
> awl *(noun)*: A pointed tool for making small holes in wood or leather
|
||||
|
||||
`awl` is a command-line DNS client, much like
|
||||
[`drill`](https://github.com/NLnetLabs/ldns),
|
||||
[`dig`](https://bind9.readthedocs.io/en/v9_18_3/manpages.html#dig-dns-lookup-utility),
|
||||
[`dog`](https://github.com/ogham/dog),
|
||||
[`doggo`](https://github.com/mr-karan/doggo), or
|
||||
[`q`](https://github.com/natesales/q).
|
||||
A command-line DNS lookup tool that supports DNS queries over UDP, TCP, TLS, HTTPS, DNSCrypt, and QUIC.
|
||||
|
||||
`awl` is designed to be a drop-in replacement for the venerable dig, but support
|
||||
newer RFC query types, such as DNS-over-HTTPS and DNS-over-QUIC.
|
||||
[![Gitea Release](https://img.shields.io/gitea/v/release/sam/awl?gitea_url=https%3A%2F%2Fgit.froth.zone&display_name=release&style=for-the-badge)](https://git.froth.zone/sam/awl)
|
||||
[![Last Commit](https://img.shields.io/gitea/last-commit/sam/awl?gitea_url=https%3A%2F%2Fgit.froth.zone&style=for-the-badge)](https://git.froth.zone/sam/awl/commits/branch/master)
|
||||
[![License](https://img.shields.io/github/license/samtherapy/awl?style=for-the-badge)](https://spdx.org/licenses/BSD-3-Clause.html)
|
||||
[![Go Report](https://goreportcard.com/badge/dns.froth.zone/awl?style=for-the-badge)](https://goreportcard.com/report/dns.froth.zone/awl)
|
||||
|
||||
## Usage
|
||||
|
||||
- [Feature wiki](https://git.froth.zone/sam/awl/wiki/Supported)
|
||||
- [Manpage](https://git.froth.zone/sam/awl/wiki/awl.1)
|
||||
Awl is designed to be a drop-in replacement for [dig](https://bind9.readthedocs.io/en/v9_18_3/manpages.html#dig-dns-lookup-utility).
|
||||
|
||||
## Building and installing
|
||||
## Examples
|
||||
|
||||
### From releases
|
||||
```shell
|
||||
# Query a domain over UDP
|
||||
awl example.com
|
||||
|
||||
Grab a prebuilt binary from the
|
||||
[release](https://git.froth.zone/sam/awl/releases) section.
|
||||
# Query a domain over HTTPS, print only the results
|
||||
awl example.com +https --short
|
||||
|
||||
### Package Managers
|
||||
|
||||
- AUR: [awl-dns-git](https://aur.archlinux.org/packages/awl-dns-git)
|
||||
|
||||
### From source
|
||||
|
||||
Dependencies:
|
||||
|
||||
- Go >= 1.18
|
||||
- GNU/BSD make or Plan 9 mk (if using the makefile/mkfile)
|
||||
- scdoc (optional, for man page)
|
||||
|
||||
Using `go install` (recommended):
|
||||
|
||||
```sh
|
||||
go install git.froth.zone/sam/awl@latest
|
||||
# Query a domain over TLS, print as JSON
|
||||
awl example.com +tls +json
|
||||
```
|
||||
|
||||
Using the makefile:
|
||||
For more and the usage, see the [manpage](https://git.froth.zone/sam/awl/wiki/awl.1).
|
||||
|
||||
```sh
|
||||
make
|
||||
make && sudo make install
|
||||
## Installing
|
||||
|
||||
On any platform, with [Go](https://go.dev) installed, run the following command to install:
|
||||
|
||||
```shell
|
||||
go install dns.froth.zone/awl@latest
|
||||
```
|
||||
|
||||
### Packaging
|
||||
|
||||
Alternatively, many package managers are supported:
|
||||
|
||||
<details>
|
||||
<summary>Linux</summary>
|
||||
|
||||
#### Distro-specific
|
||||
|
||||
<details>
|
||||
<summary>Alpine Linux</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/alpine/awl-dns) \
|
||||
***Any distro that uses apk should also work***
|
||||
|
||||
```shell
|
||||
# Add the repository
|
||||
echo "https://git.froth.zone/api/packages/sam/alpine/edge/main" | tee -a /etc/apk/repositories
|
||||
# Get the signing key
|
||||
curl -JO https://git.froth.zone/api/packages/sam/alpine/key --output-dir /etc/apk/keys
|
||||
# Install
|
||||
apk add awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch</summary>
|
||||
|
||||
AUR package available as [awl-dns-git](https://aur.archlinux.org/packages/awl-dns-git/)
|
||||
|
||||
```shell
|
||||
yay -S awl-dns-git ||
|
||||
paru -S awl-dns-git
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Debian / Ubuntu</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/debian/awl-dns/) \
|
||||
***Any distro that uses deb/dpkg should also work***
|
||||
|
||||
```shell
|
||||
# Add PGP key
|
||||
sudo curl https://git.froth.zone/api/packages/sam/debian/repository.key -o /usr/share/keyrings/git-froth-zone-sam.asc
|
||||
# Add repo
|
||||
echo "deb [signed-by=/usr/share/keyrings/git-froth-zone-sam.asc] https://git.froth.zone/api/packages/sam/debian sid main" | sudo tee /etc/apt/sources.list.d/git-froth-zone-sam.list
|
||||
# Update and install
|
||||
sudo apt update
|
||||
sudo apt install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fedora / RHEL / SUSE</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/rpm/awl-dns/) \
|
||||
***Any distro that uses rpm/dnf might also work, I've never tried it***
|
||||
|
||||
```shell
|
||||
# Add the repository
|
||||
dnf config-manager --add-repo https://git.froth.zone/api/packages/sam/rpm.repo ||
|
||||
zypper addrepo https://git.froth.zone/api/packages/sam/rpm.repo
|
||||
# Install
|
||||
dnf install awl-dns ||
|
||||
zypper install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Gentoo</summary>
|
||||
|
||||
```shell
|
||||
# Add the ebuild repository
|
||||
eselect repository add froth-zone git https://git.froth.zone/packaging/portage.git
|
||||
emaint sync -r froth-zone
|
||||
# Install
|
||||
emerge -av net-dns/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Distro-agnostic
|
||||
|
||||
|
||||
<details>
|
||||
<summary><a href="https://brew.sh" nofollow>Homebrew</a></summary>
|
||||
|
||||
```shell
|
||||
brew install SamTherapy/tap/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Snap</summary>
|
||||
|
||||
Snap package available as [awl-dns](https://snapcraft.io/awl-dns)
|
||||
|
||||
```shell
|
||||
snap install awl-dns ||
|
||||
sudo snap install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
<hr />
|
||||
<details>
|
||||
<summary>macOS</summary>
|
||||
|
||||
<details open>
|
||||
<summary><a href="https://brew.sh" nofollow>Homebrew</a></summary>
|
||||
|
||||
```shell
|
||||
brew install SamTherapy/tap/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
<hr />
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
|
||||
<details open>
|
||||
<summary><a href="https://scoop.sh" nofollow>Scoop</a></summary>
|
||||
|
||||
```pwsh
|
||||
scoop bucket add froth https://git.froth.zone/packages/scoop.git
|
||||
scoop install awl
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
|
||||
Send a [pull request](https://git.froth.zone/sam/awl/pulls) our way. Prefer
|
||||
emails? Send a patch to the
|
||||
[mailing list](https://lists.sr.ht/~sammefishe/awl-dev).
|
||||
Please see the [CONTRIBUTING.md](./docs/CONTRIBUTING.md) file for more information.
|
||||
|
||||
Found a bug or want a new feature? Create an issue
|
||||
[here](https://git.froth.zone/sam/awl/issues).
|
||||
TL;DR: If you like the project, spread the word! If you want to contribute, [use the issue tracker](https://git.froth.zone/sam/awl/issues) or [open a pull request](https://git.froth.zone/sam/awl/pulls).
|
||||
Want to use email instead? Use our [mailing list](https://lists.sr.ht/~sammefishe/awl-devel)!
|
||||
|
||||
### Licence
|
||||
### Mirrors
|
||||
|
||||
Revised BSD, See [LICENCE](./LICENCE)
|
||||
The canonical repository is located on [my personal Forgejo instance](https://git.froth.zone/sam/awl). \
|
||||
Official mirrors are located on [GitHub](https://github.com/SamTherapy/awl) and [SourceHut](https://git.sr.ht/~sammefishe/awl/).
|
||||
Contributions are accepted on all mirrors, but the Forgejo instance is preferred.
|
||||
|
||||
## License
|
||||
|
||||
[BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)
|
||||
|
||||
### Credits
|
||||
|
||||
- Awl image taken from [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Awl.tif), imaged is licensed CC0.
|
||||
|
|
235
cli/cli.go
235
cli/cli.go
|
@ -1,235 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
flag "github.com/stefansundin/go-zflag"
|
||||
)
|
||||
|
||||
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
|
||||
// struct.
|
||||
func ParseCLI(version string) (util.Options, error) {
|
||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Println(`awl - drill, writ small
|
||||
|
||||
Usage: awl name [@server] [record]
|
||||
<name> domain, IP address, phone number
|
||||
<record> defaults to A
|
||||
|
||||
Arguments may be in any order, including flags.
|
||||
Dig-like +[no]commands are also supported, see dig(1) or dig -h
|
||||
|
||||
Options:`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// CLI flags
|
||||
//
|
||||
// Remember, when adding a flag edit the manpage and the completions :)
|
||||
var (
|
||||
port = flag.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC)", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true))
|
||||
query = flag.String("query", "", "domain name to `query` (default: .)", flag.OptShorthand('q'))
|
||||
class = flag.String("class", "IN", "DNS `class` to query", flag.OptShorthand('c'))
|
||||
qType = flag.String("qType", "", "`type` to query (default: A)", flag.OptShorthand('t'))
|
||||
|
||||
ipv4 = flag.Bool("4", false, "force IPv4", flag.OptShorthand('4'))
|
||||
ipv6 = flag.Bool("6", false, "force IPv6", flag.OptShorthand('6'))
|
||||
reverse = flag.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x'))
|
||||
|
||||
timeout = flag.Float32("timeout", 1, "Timeout, in `seconds`")
|
||||
retry = flag.Int("retries", 2, "number of `times` to retry")
|
||||
|
||||
edns = flag.Bool("no-edns", false, "disable EDNS entirely")
|
||||
ednsVer = flag.Uint8("edns-ver", 0, "set EDNS version")
|
||||
dnssec = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
|
||||
expire = flag.Bool("expire", false, "set EDNS expire")
|
||||
nsid = flag.Bool("nsid", false, "set EDNS NSID", flag.OptShorthand('n'))
|
||||
cookie = flag.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)")
|
||||
tcpKeepAlive = flag.Bool("keep-alive", false, "send EDNS TCP keep-alive")
|
||||
udpBufSize = flag.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b'))
|
||||
mbzflag = flag.String("zflag", "0", "set EDNS z-flag `value`")
|
||||
subnet = flag.String("subnet", "", "set EDNS client subnet")
|
||||
padding = flag.Bool("pad", false, "set EDNS padding")
|
||||
|
||||
truncate = flag.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default: retry with TCP)")
|
||||
|
||||
tcp = flag.Bool("tcp", false, "use TCP")
|
||||
dnscrypt = flag.Bool("dnscrypt", false, "use DNSCrypt")
|
||||
tls = flag.Bool("tls", false, "use DNS-over-TLS", flag.OptShorthand('T'))
|
||||
https = flag.Bool("https", false, "use DNS-over-HTTPS", flag.OptShorthand('H'))
|
||||
quic = flag.Bool("quic", false, "use DNS-over-QUIC", flag.OptShorthand('Q'))
|
||||
|
||||
tlsHost = flag.String("tls-host", "", "Server name to use for TLS verification")
|
||||
noVerify = flag.Bool("tls-no-verify", false, "Disable TLS cert verification")
|
||||
|
||||
aaflag = flag.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
adflag = flag.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cdflag = flag.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qrflag = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rdflag = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
raflag = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tcflag = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
zflag = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
|
||||
short = flag.Bool("short", false, "print just the results", flag.OptShorthand('s'))
|
||||
json = flag.Bool("json", false, "print the result(s) as JSON", flag.OptShorthand('j'))
|
||||
xml = flag.Bool("xml", false, "print the result(s) as XML", flag.OptShorthand('X'))
|
||||
yaml = flag.Bool("yaml", false, "print the result(s) as yaml", flag.OptShorthand('y'))
|
||||
|
||||
noC = flag.Bool("no-comments", false, "disable printing the comments")
|
||||
noQ = flag.Bool("no-question", false, "disable printing the question section")
|
||||
noOpt = flag.Bool("no-opt", false, "disable printing the OPT pseudosection")
|
||||
noAns = flag.Bool("no-answer", false, "disable printing the answer section")
|
||||
noAuth = flag.Bool("no-authority", false, "disable printing the authority section")
|
||||
noAdd = flag.Bool("no-additional", false, "disable printing the additional section")
|
||||
noStats = flag.Bool("no-statistics", false, "disable printing the statistics section")
|
||||
|
||||
verbosity = flag.Int("verbosity", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2"))
|
||||
versionFlag = flag.Bool("version", false, "print version information", flag.OptShorthand('V'))
|
||||
)
|
||||
|
||||
// Don't sort the flags when -h is given
|
||||
flag.CommandLine.SortFlags = false
|
||||
|
||||
// Parse the flags
|
||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
||||
return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag: %w", err)
|
||||
}
|
||||
|
||||
// TODO: DRY, dumb dumb.
|
||||
mbz, err := strconv.ParseInt(*mbzflag, 0, 16)
|
||||
if err != nil {
|
||||
return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ: %w", err)
|
||||
}
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
Port: *port,
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
TLSHost: *tlsHost,
|
||||
TLSNoVerify: *noVerify,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
ShowQuery: false,
|
||||
AA: *aaflag,
|
||||
AD: *adflag,
|
||||
TC: *tcflag,
|
||||
Z: *zflag,
|
||||
CD: *cdflag,
|
||||
QR: *qrflag,
|
||||
RD: *rdflag,
|
||||
RA: *raflag,
|
||||
Reverse: *reverse,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
Request: util.Request{
|
||||
Type: dns.StringToType[strings.ToUpper(*qType)],
|
||||
Class: dns.StringToClass[strings.ToUpper(*class)],
|
||||
Name: *query,
|
||||
Timeout: time.Duration(*timeout * float32(time.Second)),
|
||||
Retries: *retry,
|
||||
},
|
||||
Display: util.Displays{
|
||||
Comments: !*noC,
|
||||
Question: !*noQ,
|
||||
Opt: !*noOpt,
|
||||
Answer: !*noAns,
|
||||
Authority: !*noAuth,
|
||||
Additional: !*noAdd,
|
||||
Statistics: !*noStats,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: !*edns,
|
||||
Cookie: !*cookie,
|
||||
DNSSEC: *dnssec,
|
||||
BufSize: *udpBufSize,
|
||||
Version: *ednsVer,
|
||||
Expire: *expire,
|
||||
KeepOpen: *tcpKeepAlive,
|
||||
Nsid: *nsid,
|
||||
ZFlag: uint16(mbz & 0x7FFF),
|
||||
Padding: *padding,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
if *subnet != "" {
|
||||
if err = util.ParseSubnet(*subnet, &opts); err != nil {
|
||||
return opts, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("POSIX flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Printf("awl version %s, built with %s\n", version, runtime.Version())
|
||||
|
||||
return opts, ErrNotError
|
||||
}
|
||||
|
||||
// Parse all the arguments that don't start with - or --
|
||||
// This includes the dig-style (+) options
|
||||
err = ParseMiscArgs(flag.Args(), &opts)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
opts.Logger.Info("Dig/Drill flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
if opts.Port == 0 {
|
||||
if opts.TLS || opts.QUIC {
|
||||
opts.Port = 853
|
||||
} else {
|
||||
opts.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("Port set to", opts.Port)
|
||||
|
||||
// Set timeout to 0.5 seconds if set below 0.5
|
||||
if opts.Request.Timeout < (time.Second / 2) {
|
||||
opts.Request.Timeout = (time.Second / 2)
|
||||
}
|
||||
|
||||
if opts.Request.Retries < 0 {
|
||||
opts.Request.Retries = 0
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// ErrNotError is for returning not error.
|
||||
var ErrNotError = errors.New("not an error")
|
||||
|
||||
var errNoArg = errors.New("no argument given")
|
||||
|
||||
type errInvalidArg struct {
|
||||
arg string
|
||||
}
|
||||
|
||||
func (e *errInvalidArg) Error() string {
|
||||
return fmt.Sprintf("digflags: invalid argument %s", e.arg)
|
||||
}
|
184
cli/cli_test.go
184
cli/cli_test.go
|
@ -1,184 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", "-4"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Port, 53)
|
||||
assert.Assert(t, opts.IPv4)
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
|
||||
func TestTLSPort(t *testing.T) {
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", "-T"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Port, 853)
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
|
||||
func TestSubnet(t *testing.T) {
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", "--subnet", "127.0.0.1/32"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
|
||||
os.Args = args
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "0"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
|
||||
os.Args = args
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "::/0"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
|
||||
|
||||
os.Args = args
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "/"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.ErrorContains(t, err, "EDNS subnet")
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
|
||||
func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", "--zflag", "G"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "EDNS MBZ")
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := os.Args
|
||||
stdout := os.Stdout
|
||||
stderr := os.Stderr
|
||||
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
os.Stderr = os.NewFile(0, os.DevNull)
|
||||
|
||||
os.Args = []string{"awl", "--treebug"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
|
||||
os.Args = args
|
||||
os.Stdout = stdout
|
||||
os.Stderr = stderr
|
||||
}
|
||||
|
||||
func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", "+a"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "digflags: invalid argument")
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := os.Args
|
||||
stdout := os.Stdout
|
||||
stderr := os.Stderr
|
||||
|
||||
os.Args = []string{"awl", "--version"}
|
||||
|
||||
_, err := cli.ParseCLI("test")
|
||||
|
||||
assert.ErrorType(t, err, cli.ErrNotError)
|
||||
|
||||
os.Args = args
|
||||
os.Stdout = stdout
|
||||
os.Stderr = stderr
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
}
|
||||
for _, test := range args {
|
||||
args := os.Args
|
||||
os.Args = test
|
||||
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := [][]string{
|
||||
{"awl", "+retry=-2"},
|
||||
{"awl", "+tries=-2"},
|
||||
{"awl", "--retries", "-2"},
|
||||
}
|
||||
for _, test := range args {
|
||||
args := os.Args
|
||||
os.Args = test
|
||||
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
|
||||
os.Args = args
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzFlags(f *testing.F) {
|
||||
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
|
||||
|
||||
for _, tc := range testcases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
// Get rid of outputs
|
||||
|
||||
args := os.Args
|
||||
os.Args = []string{"awl", orig}
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseCLI("TEST")
|
||||
os.Args = args
|
||||
})
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
flag "github.com/stefansundin/go-zflag"
|
||||
)
|
||||
|
||||
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
|
||||
// struct.
|
||||
func ParseCLI(args []string, version string) (opts *util.Options, err error) {
|
||||
// Parse the standard flags
|
||||
opts, misc, err := parseFlags(args, version)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// Parse all the arguments that don't start with - or --
|
||||
// This includes the dig-style (+) options
|
||||
err = ParseMiscArgs(misc, opts)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
opts.Logger.Info("Dig/Drill flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
// Special options and exceptions time
|
||||
|
||||
if opts.Request.Port == 0 {
|
||||
if opts.TLS || opts.QUIC {
|
||||
opts.Request.Port = 853
|
||||
} else {
|
||||
opts.Request.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("Port set to", opts.Request.Port)
|
||||
|
||||
// Set timeout to 0.5 seconds if set below 0.5
|
||||
if opts.Request.Timeout < (time.Second / 2) {
|
||||
opts.Request.Timeout = (time.Second / 2)
|
||||
}
|
||||
|
||||
if opts.Request.Retries < 0 {
|
||||
opts.Request.Retries = 0
|
||||
}
|
||||
|
||||
if opts.Trace {
|
||||
if opts.TLS || opts.HTTPS || opts.QUIC {
|
||||
opts.Logger.Warn("Every query after the root query will only use UDP/TCP")
|
||||
}
|
||||
|
||||
opts.RD = true
|
||||
}
|
||||
|
||||
opts.Logger.Info("Options fully populated")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Everything that has to do with CLI flags goes here (the posix style, eg. -a and --bbbb).
|
||||
func parseFlags(args []string, version string) (opts *util.Options, flags []string, err error) {
|
||||
flagSet := flag.NewFlagSet(args[0], flag.ContinueOnError)
|
||||
|
||||
flagSet.Usage = func() {
|
||||
fmt.Println(`awl - drill, writ small
|
||||
|
||||
Usage: awl name [@server] [record]
|
||||
<name> domain, IP address, phone number
|
||||
<record> defaults to A
|
||||
|
||||
Arguments may be in any order, including flags.
|
||||
Dig-like +[no]commands are also supported, see dig(1) or dig -h
|
||||
|
||||
Options:`)
|
||||
flagSet.PrintDefaults()
|
||||
}
|
||||
|
||||
// CLI flags
|
||||
//
|
||||
// Remember, when adding a flag edit the manpage and the completions :)
|
||||
var (
|
||||
port = flagSet.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC)", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true))
|
||||
query = flagSet.String("query", "", "domain name to `query` (default: .)", flag.OptShorthand('q'))
|
||||
class = flagSet.String("class", "IN", "DNS `class` to query", flag.OptShorthand('c'))
|
||||
qType = flagSet.String("qType", "", "`type` to query (default: A)", flag.OptShorthand('t'))
|
||||
|
||||
ipv4 = flagSet.Bool("4", false, "force IPv4", flag.OptShorthand('4'))
|
||||
ipv6 = flagSet.Bool("6", false, "force IPv6", flag.OptShorthand('6'))
|
||||
reverse = flagSet.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x'))
|
||||
trace = flagSet.Bool("trace", false, "trace from the root")
|
||||
|
||||
timeout = flagSet.Float32("timeout", 5, "Timeout, in `seconds`")
|
||||
retry = flagSet.Int("retries", 2, "number of `times` to retry")
|
||||
|
||||
edns = flagSet.Bool("no-edns", false, "disable EDNS entirely")
|
||||
ednsVer = flagSet.Uint8("edns-ver", 0, "set EDNS version")
|
||||
dnssec = flagSet.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
|
||||
expire = flagSet.Bool("expire", false, "set EDNS expire")
|
||||
nsid = flagSet.Bool("nsid", false, "set EDNS NSID", flag.OptShorthand('n'))
|
||||
cookie = flagSet.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)")
|
||||
tcpKeepAlive = flagSet.Bool("keep-alive", false, "send EDNS TCP keep-alive")
|
||||
udpBufSize = flagSet.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b'))
|
||||
mbzflag = flagSet.String("zflag", "0", "set EDNS z-flag `value`")
|
||||
subnet = flagSet.String("subnet", "", "set EDNS client subnet")
|
||||
padding = flagSet.Bool("pad", false, "set EDNS padding")
|
||||
|
||||
badCookie = flagSet.Bool("no-bad-cookie", false, "ignore BADCOOKIE EDNS responses (default: retry with correct cookie")
|
||||
truncate = flagSet.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default: retry with TCP)")
|
||||
|
||||
tcp = flagSet.Bool("tcp", false, "use TCP")
|
||||
dnscrypt = flagSet.Bool("dnscrypt", false, "use DNSCrypt")
|
||||
tls = flagSet.Bool("tls", false, "use DNS-over-TLS", flag.OptShorthand('T'))
|
||||
https = flagSet.Bool("https", false, "use DNS-over-HTTPS", flag.OptShorthand('H'))
|
||||
quic = flagSet.Bool("quic", false, "use DNS-over-QUIC", flag.OptShorthand('Q'))
|
||||
|
||||
tlsHost = flagSet.String("tls-host", "", "Server name to use for TLS verification")
|
||||
noVerify = flagSet.Bool("tls-no-verify", false, "Disable TLS cert verification")
|
||||
|
||||
aaflag = flagSet.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
adflag = flagSet.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cdflag = flagSet.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qrflag = flagSet.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rdflag = flagSet.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
raflag = flagSet.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tcflag = flagSet.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
zflag = flagSet.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
|
||||
short = flagSet.Bool("short", false, "print just the results", flag.OptShorthand('s'))
|
||||
json = flagSet.Bool("json", false, "print the result(s) as JSON", flag.OptShorthand('j'))
|
||||
xml = flagSet.Bool("xml", false, "print the result(s) as XML", flag.OptShorthand('X'))
|
||||
yaml = flagSet.Bool("yaml", false, "print the result(s) as yaml", flag.OptShorthand('y'))
|
||||
|
||||
noC = flagSet.Bool("no-comments", false, "disable printing the comments")
|
||||
noQ = flagSet.Bool("no-question", false, "disable printing the question section")
|
||||
noOpt = flagSet.Bool("no-opt", false, "disable printing the OPT pseudosection")
|
||||
noAns = flagSet.Bool("no-answer", false, "disable printing the answer section")
|
||||
noAuth = flagSet.Bool("no-authority", false, "disable printing the authority section")
|
||||
noAdd = flagSet.Bool("no-additional", false, "disable printing the additional section")
|
||||
noStats = flagSet.Bool("no-statistics", false, "disable printing the statistics section")
|
||||
|
||||
verbosity = flagSet.Int("verbosity", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2"))
|
||||
versionFlag = flagSet.Bool("version", false, "print version information", flag.OptShorthand('V'))
|
||||
)
|
||||
|
||||
// Don't sort the flags when -h is given
|
||||
flagSet.SortFlags = true
|
||||
|
||||
// Parse the flags
|
||||
if err = flagSet.Parse(args[1:]); err != nil {
|
||||
return &util.Options{Logger: util.InitLogger(*verbosity)}, nil, fmt.Errorf("flag: %w", err)
|
||||
}
|
||||
|
||||
// TODO: DRY, dumb dumb.
|
||||
mbz, err := strconv.ParseInt(*mbzflag, 0, 16)
|
||||
if err != nil {
|
||||
return &util.Options{Logger: util.InitLogger(*verbosity)}, nil, fmt.Errorf("EDNS MBZ: %w", err)
|
||||
}
|
||||
|
||||
opts = &util.Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Trace: *trace,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
TLSHost: *tlsHost,
|
||||
TLSNoVerify: *noVerify,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
BadCookie: *badCookie,
|
||||
Reverse: *reverse,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
AA: *aaflag,
|
||||
AD: *adflag,
|
||||
TC: *tcflag,
|
||||
Z: *zflag,
|
||||
CD: *cdflag,
|
||||
QR: *qrflag,
|
||||
RD: *rdflag,
|
||||
RA: *raflag,
|
||||
},
|
||||
Request: util.Request{
|
||||
Type: dns.StringToType[strings.ToUpper(*qType)],
|
||||
Class: dns.StringToClass[strings.ToUpper(*class)],
|
||||
Name: *query,
|
||||
Timeout: time.Duration(*timeout * float32(time.Second)),
|
||||
Retries: *retry,
|
||||
Port: *port,
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: !*noC,
|
||||
Question: !*noQ,
|
||||
Opt: !*noOpt,
|
||||
Answer: !*noAns,
|
||||
Authority: !*noAuth,
|
||||
Additional: !*noAdd,
|
||||
Statistics: !*noStats,
|
||||
TTL: true,
|
||||
ShowClass: true,
|
||||
ShowQuery: false,
|
||||
HumanTTL: false,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: !*edns,
|
||||
Cookie: !*cookie,
|
||||
DNSSEC: *dnssec,
|
||||
BufSize: *udpBufSize,
|
||||
Version: *ednsVer,
|
||||
Expire: *expire,
|
||||
KeepOpen: *tcpKeepAlive,
|
||||
Nsid: *nsid,
|
||||
ZFlag: uint16(mbz & 0x7FFF),
|
||||
Padding: *padding,
|
||||
},
|
||||
HTTPSOptions: util.HTTPSOptions{
|
||||
Endpoint: "/dns-query",
|
||||
Get: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
if *subnet != "" {
|
||||
if err = util.ParseSubnet(*subnet, opts); err != nil {
|
||||
return opts, nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("POSIX flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Printf("awl version %s, built with %s\n", version, runtime.Version())
|
||||
|
||||
return opts, nil, util.ErrNotError
|
||||
}
|
||||
|
||||
flags = flagSet.Args()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var errNoArg = errors.New("no argument given")
|
||||
|
||||
type errInvalidArg struct {
|
||||
arg string
|
||||
}
|
||||
|
||||
func (e *errInvalidArg) Error() string {
|
||||
return fmt.Sprintf("digflags: invalid argument %s", e.arg)
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "-4"}
|
||||
|
||||
opts, err := cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, opts.IPv4)
|
||||
assert.Equal(t, opts.Request.Port, 53)
|
||||
}
|
||||
|
||||
func TestTLSPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "-T"}
|
||||
|
||||
opts, err := cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Port, 853)
|
||||
}
|
||||
|
||||
func TestValidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
args []string
|
||||
want uint16
|
||||
}{
|
||||
{[]string{"awl", "--subnet", "127.0.0.1/32"}, uint16(1)},
|
||||
{[]string{"awl", "--subnet", "0"}, uint16(1)},
|
||||
{[]string{"awl", "--subnet", "::/0"}, uint16(2)},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.args[2], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts, err := cli.ParseCLI(test.args, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, test.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--subnet", "/"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
assert.ErrorContains(t, err, "EDNS subnet")
|
||||
}
|
||||
|
||||
func TestMBZ(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--zflag", "G"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "EDNS MBZ")
|
||||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--treebug"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
}
|
||||
|
||||
func TestInvalidDig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "+a"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "digflags: invalid argument")
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--version"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "test")
|
||||
|
||||
assert.ErrorIs(t, err, util.ErrNotError)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "+retry=-2"},
|
||||
{"awl", "+tries=-2"},
|
||||
{"awl", "--retries", "-2"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "-H", "@dns.froth.zone/dns-query"},
|
||||
{"awl", "+https", "@dns.froth.zone"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Server, "dns.froth.zone")
|
||||
assert.Equal(t, opt.HTTPSOptions.Endpoint, "/dns-query")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzFlags(f *testing.F) {
|
||||
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
|
||||
|
||||
for _, tc := range testcases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
// Get rid of outputs
|
||||
|
||||
args := []string{"awl", orig}
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseCLI(args, "TEST")
|
||||
})
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
)
|
||||
|
||||
// ParseDig parses commands from the popular DNS tool dig.
|
||||
|
@ -25,6 +25,18 @@ func ParseDig(arg string, opts *util.Options) error {
|
|||
opts.Logger.Info("Setting", arg)
|
||||
|
||||
switch arg {
|
||||
case "trace", "notrace":
|
||||
opts.Trace = isNo
|
||||
if isNo {
|
||||
opts.DNSSEC = true
|
||||
opts.Display.Comments = false
|
||||
opts.Display.Question = false
|
||||
opts.Display.Opt = false
|
||||
opts.Display.Answer = true
|
||||
opts.Display.Authority = true
|
||||
opts.Display.Additional = false
|
||||
opts.Display.Statistics = false
|
||||
}
|
||||
// Set DNS query flags
|
||||
case "aa", "aaflag", "aaonly":
|
||||
opts.AA = isNo
|
||||
|
@ -45,16 +57,16 @@ func ParseDig(arg string, opts *util.Options) error {
|
|||
// End DNS query flags
|
||||
|
||||
case "qr":
|
||||
opts.ShowQuery = isNo
|
||||
opts.Display.ShowQuery = isNo
|
||||
case "ttlunits":
|
||||
opts.HumanTTL = isNo
|
||||
opts.Display.HumanTTL = isNo
|
||||
case "ttl", "ttlid":
|
||||
opts.ShowTTL = isNo
|
||||
opts.Display.TTL = isNo
|
||||
case "class":
|
||||
opts.ShowClass = isNo
|
||||
opts.Display.ShowClass = isNo
|
||||
|
||||
// EDNS queries
|
||||
case "dnssec":
|
||||
case "do", "dnssec":
|
||||
opts.EDNS.DNSSEC = isNo
|
||||
case "expire":
|
||||
opts.EDNS.Expire = isNo
|
||||
|
@ -73,12 +85,12 @@ func ParseDig(arg string, opts *util.Options) error {
|
|||
opts.TCP = isNo
|
||||
case "ignore":
|
||||
opts.Truncate = isNo
|
||||
case "badcookie":
|
||||
opts.BadCookie = !isNo
|
||||
case "tls":
|
||||
opts.TLS = isNo
|
||||
case "dnscrypt":
|
||||
opts.DNSCrypt = isNo
|
||||
case "https":
|
||||
opts.HTTPS = isNo
|
||||
case "quic":
|
||||
opts.QUIC = isNo
|
||||
// End DNS-over-X
|
||||
|
@ -159,7 +171,7 @@ func parseDigEq(startNo bool, arg string, opts *util.Options) error {
|
|||
|
||||
// TODO: Is there a better way to do this?
|
||||
if arg == "tries" {
|
||||
opts.Request.Retries++
|
||||
opts.Request.Retries--
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: retry: %w", errNoArg)
|
||||
|
@ -204,6 +216,18 @@ func parseDigEq(startNo bool, arg string, opts *util.Options) error {
|
|||
opts.EDNS.Version = 0
|
||||
}
|
||||
|
||||
case "https", "https-get", "https-post":
|
||||
opts.HTTPS = startNo
|
||||
if isSplit && val != "" {
|
||||
opts.HTTPSOptions.Endpoint = val
|
||||
} else {
|
||||
opts.HTTPSOptions.Endpoint = "/dns-query"
|
||||
}
|
||||
|
||||
if strings.HasSuffix(arg, "get") {
|
||||
opts.HTTPSOptions.Get = true
|
||||
}
|
||||
|
||||
case "subnet":
|
||||
if isSplit && val != "" {
|
||||
err := util.ParseSubnet(val, opts)
|
|
@ -5,8 +5,8 @@ package cli_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
|
@ -25,12 +25,12 @@ func FuzzDig(f *testing.F) {
|
|||
"qr", "noqr",
|
||||
"ttlunits", "nottlunits",
|
||||
"ttlid", "nottlid",
|
||||
"dnssec", "nodnssec",
|
||||
"do", "dnssec", "nodo", "nodnssec",
|
||||
"edns", "edns=a", "edns=0", "noedns",
|
||||
"expire", "noexpire",
|
||||
"ednsflags", "ednsflags=\"", "ednsflags=1", "noednsflags",
|
||||
"subnet=0.0.0.0/0", "subnet=::0/0", "subnet=b", "subnet=0", "subnet",
|
||||
"cookie", "nocookeie",
|
||||
"cookie", "nocookie",
|
||||
"keepopen", "keepalive", "nokeepopen", "nokeepalive",
|
||||
"nsid", "nonsid",
|
||||
"padding", "nopadding",
|
||||
|
@ -40,9 +40,10 @@ func FuzzDig(f *testing.F) {
|
|||
"tries=2", "tries=b", "tries",
|
||||
"tcp", "vc", "notcp", "novc",
|
||||
"ignore", "noignore",
|
||||
"badcookie", "nobadcookie",
|
||||
"tls", "notls",
|
||||
"dnscrypt", "nodnscrypt",
|
||||
"https", "nohttps",
|
||||
"https", "https=/dns", "https-get", "https-get=/", "nohttps",
|
||||
"quic", "noquic",
|
||||
"short", "noshort",
|
||||
"identify", "noidentify",
|
||||
|
@ -59,6 +60,7 @@ func FuzzDig(f *testing.F) {
|
|||
"all", "noall",
|
||||
"idnout", "noidnout",
|
||||
"class", "noclass",
|
||||
"trace", "notrace",
|
||||
"invalid",
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/conf"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
@ -29,22 +29,44 @@ func ParseMiscArgs(args []string, opts *util.Options) error {
|
|||
switch {
|
||||
case strings.HasPrefix(arg, "tls://"):
|
||||
opts.TLS = true
|
||||
opts.Request.Server = strings.TrimPrefix(opts.Request.Server, "tls://")
|
||||
opts.Request.Server = strings.TrimPrefix(arg, "tls://")
|
||||
opts.Logger.Info("DNS-over-TLS implicitly set")
|
||||
case strings.HasPrefix(arg, "https://"):
|
||||
opts.HTTPS = true
|
||||
opts.Request.Server = arg
|
||||
opts.Logger.Info("DNS-over-HTTPS implicitly set")
|
||||
|
||||
_, endpoint, isSplit := strings.Cut(arg, "/")
|
||||
if isSplit {
|
||||
opts.HTTPSOptions.Endpoint = "/" + endpoint
|
||||
}
|
||||
case strings.HasPrefix(arg, "quic://"):
|
||||
opts.QUIC = true
|
||||
opts.Request.Server = strings.TrimPrefix(opts.Request.Server, "quic://")
|
||||
opts.Request.Server = strings.TrimPrefix(arg, "quic://")
|
||||
opts.Logger.Info("DNS-over-QUIC implicitly set.")
|
||||
case strings.HasPrefix(arg, "sdns://"):
|
||||
opts.DNSCrypt = true
|
||||
opts.Request.Server = arg
|
||||
opts.Logger.Info("DNSCrypt implicitly set")
|
||||
case strings.HasPrefix(arg, "tcp://"):
|
||||
opts.TCP = true
|
||||
opts.Request.Server = strings.TrimPrefix(arg, "tcp://")
|
||||
opts.Logger.Info("TCP implicitly set")
|
||||
case strings.HasPrefix(arg, "udp://"):
|
||||
opts.Request.Server = strings.TrimPrefix(arg, "udp://")
|
||||
default:
|
||||
opts.Request.Server = arg
|
||||
// Allow HTTPS queries to have a fallback default
|
||||
if opts.HTTPS {
|
||||
server, endpoint, isSplit := strings.Cut(arg, "/")
|
||||
if isSplit {
|
||||
opts.HTTPSOptions.Endpoint = "/" + endpoint
|
||||
opts.Request.Server = server
|
||||
} else {
|
||||
opts.Request.Server = server
|
||||
}
|
||||
} else {
|
||||
opts.Request.Server = arg
|
||||
}
|
||||
}
|
||||
|
||||
// Dig-style +queries
|
||||
|
@ -61,7 +83,6 @@ func ParseMiscArgs(args []string, opts *util.Options) error {
|
|||
|
||||
opts.Logger.Info(arg, "detected as a domain name")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
|
@ -77,7 +98,6 @@ func ParseMiscArgs(args []string, opts *util.Options) error {
|
|||
|
||||
opts.Logger.Info(arg, "is unknown. Assuming domain")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
|
@ -108,16 +128,16 @@ func ParseMiscArgs(args []string, opts *util.Options) error {
|
|||
case opts.TLS:
|
||||
opts.Request.Server = "dns.google"
|
||||
case opts.HTTPS:
|
||||
opts.Request.Server = "https://dns.cloudflare.com/dns-query"
|
||||
opts.Request.Server = "https://dns.cloudflare.com"
|
||||
case opts.QUIC:
|
||||
opts.Request.Server = "dns.adguard.com"
|
||||
opts.Request.Server = "dns.froth.zone"
|
||||
default:
|
||||
var err error
|
||||
resolv, err := conf.GetDNSConfig()
|
||||
|
||||
if err != nil {
|
||||
// :^)
|
||||
opts.Logger.Warn("Could not query system for server. Using localhost")
|
||||
opts.Logger.Warn("Could not query system for server. Using localhost\n", "Error:", err)
|
||||
opts.Request.Server = "127.0.0.1"
|
||||
} else {
|
||||
// Make sure that if IPv4 or IPv6 is asked for it actually uses it
|
|
@ -3,11 +3,11 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
@ -87,21 +87,22 @@ func TestDefaultServer(t *testing.T) {
|
|||
in string
|
||||
want string
|
||||
}{
|
||||
{"DNSCRYPT", "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"},
|
||||
{"DNSCrypt", "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"},
|
||||
{"TLS", "dns.google"},
|
||||
{"HTTPS", "https://dns.cloudflare.com/dns-query"},
|
||||
{"QUIC", "dns.adguard.com"},
|
||||
{"HTTPS", "https://dns.cloudflare.com"},
|
||||
{"QUIC", "dns.froth.zone"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := []string{}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
switch test.in {
|
||||
case "DNSCRYPT":
|
||||
case "DNSCrypt":
|
||||
opts.DNSCrypt = true
|
||||
case "TLS":
|
||||
opts.TLS = true
|
||||
|
@ -121,32 +122,49 @@ func TestFlagSetting(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in []string
|
||||
in string
|
||||
expected string
|
||||
over string
|
||||
}{
|
||||
{[]string{"@sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"}},
|
||||
{[]string{"@tls://dns.google"}},
|
||||
{[]string{"@https://dns.cloudflare.com/dns-query"}},
|
||||
{[]string{"@quic://dns.adguard.com"}},
|
||||
{"@sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", "DNSCrypt"},
|
||||
{"@tls://dns.google", "dns.google", "TLS"},
|
||||
{"@https://dns.cloudflare.com/dns-query", "https://dns.cloudflare.com/dns-query", "HTTPS"},
|
||||
{"@https://dns.example.net/a", "https://dns.example.net/a", "HTTPS with a set path"},
|
||||
{"@quic://dns.adguard.com", "dns.adguard.com", "QUIC"},
|
||||
{"@tcp://dns.froth.zone", "dns.froth.zone", "TCP"},
|
||||
{"@udp://dns.example.com", "dns.example.com", "UDP"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
i := i
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
|
||||
t.Run(test.over, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
t.Parallel()
|
||||
err := cli.ParseMiscArgs(test.in, opts)
|
||||
|
||||
err := cli.ParseMiscArgs([]string{test.in}, opts)
|
||||
assert.NilError(t, err)
|
||||
switch i {
|
||||
case 0:
|
||||
switch {
|
||||
case strings.HasPrefix(test.over, "DNSCrypt"):
|
||||
assert.Assert(t, opts.DNSCrypt)
|
||||
case 1:
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "TLS"):
|
||||
assert.Assert(t, opts.TLS)
|
||||
case 2:
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "HTTPS"):
|
||||
assert.Assert(t, opts.HTTPS)
|
||||
case 3:
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "QUIC"):
|
||||
assert.Assert(t, opts.QUIC)
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "TCP"):
|
||||
assert.Assert(t, opts.TCP)
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "UDP"):
|
||||
assert.Assert(t, true)
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# bash completion for awl -*- shell-script -*-
|
||||
|
||||
|
||||
# TODO: MAKE THIS A REAL THING
|
||||
complete -F _known_hosts awl
|
|
@ -0,0 +1,75 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
function __fish_complete_awl
|
||||
set -l token (commandline -ct)
|
||||
switch $token
|
||||
case '+tries=*' '+retry=*' '+time=*' '+bufsize=*' '+edns=*'
|
||||
printf '%s\n' $token(seq 0 255)
|
||||
case '-v=*'
|
||||
printf '%s\n' $token(seq -1 3)
|
||||
end
|
||||
end
|
||||
|
||||
complete -c awl -x -a "(__fish_print_hostnames) A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT URI"
|
||||
complete -c awl -x -a "@(__fish_print_hostnames)"
|
||||
|
||||
complete -f -c awl -s 4 -d 'Use IPv4 query transport only'
|
||||
complete -f -c awl -s 6 -d 'Use IPv6 query transport only'
|
||||
complete -c awl -s c -l class -x -a 'IN CH HS QCLASS' -d 'Specify query class'
|
||||
complete -c awl -s p -l port -x -d 'Specify port number'
|
||||
complete -c awl -s q -l query -x -a "(__fish_print_hostnames)" -d 'Query domain'
|
||||
complete -c awl -s t -l qType -x -a 'A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT URI' -d 'Specify query type'
|
||||
complete -c awl -l timeout -x -d 'Set timeout'
|
||||
complete -c awl -l retries -x -d 'Set number of query retries'
|
||||
complete -c awl -l no-edns -x -d 'Disable EDNS'
|
||||
complete -f -c awl -l tcp -a '+vc +novc +tcp +notcp' -d 'TCP mode'
|
||||
complete -f -c awl -l dnscrypt -a '+dnscrypt +nodnscrypt' -d 'Use DNSCrypt'
|
||||
complete -c awl -s T -l tls -a '+tls +notls' -d 'Use DNS-over-TLS'
|
||||
complete -c awl -s H -l https -a '+https +nohttps' -d 'Use DNS-over-HTTPS'
|
||||
complete -c awl -s Q -l quic -a '+quic +noquic' -d 'Use DNS-over-QUIC'
|
||||
|
||||
complete -c awl -s j -l json -a '+json +nojson' -d 'Print as JSON'
|
||||
complete -c awl -s j -l xml -a '+xml +noxml' -d 'Print as XML'
|
||||
complete -c awl -s j -l yaml -a '+yaml +noyaml' -d 'Print as YAML'
|
||||
|
||||
complete -c awl -s x -l reverse -x -d 'Reverse lookup'
|
||||
complete -f -c awl -s h -l help -d 'Print help and exit'
|
||||
complete -f -c awl -s V -l version -d 'Print version and exit'
|
||||
|
||||
|
||||
# complete -f -c awl -a '+search +nosearch' -d 'Set whether to use searchlist'
|
||||
# complete -f -c awl -a '+showsearch +noshowsearch' -d 'Search with intermediate results'
|
||||
complete -f -c awl -a '+recurse +norecurse' -d 'Recursive mode'
|
||||
complete -f -c awl -l no-truncate -a '+ignore +noignore' -d 'Dont revert to TCP for TC responses.'
|
||||
# complete -f -c awl -a '+fail +nofail' -d 'Dont try next server on SERVFAIL'
|
||||
# complete -f -c awl -a '+besteffort +nobesteffort' -d 'Try to parse even illegal messages'
|
||||
complete -f -c awl -a '+aaonly +noaaonly' -d 'Set AA flag in query (+[no]aaflag)'
|
||||
complete -f -c awl -a '+adflag +noadflag' -d 'Set AD flag in query'
|
||||
complete -f -c awl -a '+cdflag +nocdflag' -d 'Set CD flag in query'
|
||||
complete -f -c awl -a '+cl +nocl' -d 'Control display of class in records'
|
||||
# complete -f -c awl -a '+cmd +nocmd' -d 'Control display of command line'
|
||||
complete -f -c awl -a '+comments +nocomments' -d 'Control display of comment lines'
|
||||
complete -f -c awl -a '+question +noquestion' -d 'Control display of question'
|
||||
complete -f -c awl -a '+answer +noanswer' -d 'Control display of answer'
|
||||
complete -f -c awl -a '+authority +noauthority' -d 'Control display of authority'
|
||||
complete -f -c awl -a '+additional +noadditional' -d 'Control display of additional'
|
||||
complete -f -c awl -a '+stats +nostats' -d 'Control display of statistics'
|
||||
complete -f -c awl -s s -l short -a '+short +noshort' -d 'Disable everything except short form of answer'
|
||||
complete -f -c awl -a '+ttlid +nottlid' -d 'Control display of ttls in records'
|
||||
complete -f -c awl -a '+all +noall' -d 'Set or clear all display flags'
|
||||
complete -f -c awl -a '+qr +noqr' -d 'Print question before sending'
|
||||
# complete -f -c awl -a '+nssearch +nonssearch' -d 'Search all authoritative nameservers'
|
||||
complete -f -c awl -a '+identify +noidentify' -d 'ID responders in short answers'
|
||||
complete -f -c awl -a '+trace +notrace' -d 'Trace delegation down from root'
|
||||
complete -f -c awl -l dnssec -a '+dnssec +nodnssec +do +nodo' -d 'Request DNSSEC records'
|
||||
complete -f -c awl -a '+nsid +nonsid' -d 'Request Name Server ID'
|
||||
# complete -f -c awl -a '+multiline +nomultiline' -d 'Print records in an expanded format'
|
||||
# complete -f -c awl -a '+onesoa +noonesoa' -d 'AXFR prints only one soa record'
|
||||
|
||||
complete -f -c awl -a '+tries=' -d 'Set number of UDP attempts'
|
||||
complete -f -c awl -a '+retry=' -d 'Set number of UDP retries'
|
||||
complete -f -c awl -a '+time=' -d 'Set query timeout'
|
||||
complete -f -c awl -a '+bufsize=' -d 'Set EDNS0 Max UDP packet size'
|
||||
complete -f -c awl -a '+ndots=' -d 'Set NDOTS value'
|
||||
complete -f -c awl -a '+edns=' -d 'Set EDNS version'
|
||||
|
||||
complete -c awl -a '(__fish_complete_awl)'
|
|
@ -8,11 +8,12 @@ local -a alts args
|
|||
'*+'{no,}'ignore[ignore truncation in UDP responses]'
|
||||
'*+'{no,}'tls[use DNS-over-TLS for queries]'
|
||||
'*+'{no,}'dnscrypt[use DNSCrypt for queries]'
|
||||
'*+'{no,}'https[use DNS-over-HTTPS for queries]'
|
||||
'*+'{no,}'https=[use DNS-over-HTTPS for queries]:endpoint [/dns-query]'
|
||||
'*+'{no,}'quic[use DNS-over-QUIC for queries]'
|
||||
'*+'{no,}'aaonly[set aa flag in the query]'
|
||||
'*+'{no,}'additional[print additional section of a reply]'
|
||||
'*+'{no,}'adflag[set the AD (authentic data) bit in the query]'
|
||||
'*+'{no,}'badcookie[retry BADCOOKIE responses]'
|
||||
'*+'{no,}'cdflag[set the CD (checking disabled) bit in the query]'
|
||||
'*+'{no,}'cookie[add a COOKIE option to the request]'
|
||||
'*+edns=[specify EDNS version for query]:version (0-255)'
|
||||
|
@ -27,13 +28,13 @@ local -a alts args
|
|||
'*+'{no,}'keepopen[keep TCP socket open between queries]'
|
||||
'*+'{no,}'recurse[set the RD (recursion desired) bit in the query]'
|
||||
# '*+'{no,}'nssearch[search all authoritative nameservers]'
|
||||
# '*+'{no,}'trace[trace delegation down from root]'
|
||||
'*+'{no,}'trace[trace delegation down from root]'
|
||||
# '*+'{no,}'cmd[print initial comment in output]'
|
||||
'*+'{no,}'short[print terse output]'
|
||||
'*+'{no,}'identify[print IP and port of responder]'
|
||||
'*+'{no,}'comments[print comment lines in output]'
|
||||
'*+'{no,}'stats[print statistics]'
|
||||
'*+padding[set padding block size]:size [0]'
|
||||
'*+padding[set padding block size]'
|
||||
'*+'{no,}'qr[print query as it was sent]'
|
||||
'*+'{no,}'question[print question section of a query]'
|
||||
'*+'{no,}'raflag[set RA flag in the query]'
|
||||
|
@ -49,7 +50,7 @@ local -a alts args
|
|||
# '*+'{no,}'rrcomments[set display of per-record comments]'
|
||||
# '*+ndots=[specify number of dots to be considered absolute]:dots'
|
||||
'*+bufsize=[specify UDP buffer size]:size (bytes)'
|
||||
'*+'{no,}'dnssec[enable DNSSEC]'
|
||||
'*+'{no,}''{dnssec,do}'[enable DNSSEC]'
|
||||
'*+'{no,}'nsid[include EDNS name server ID request in query]'
|
||||
'*+'{no,}'class[display the class whening printing the answer]'
|
||||
'*+'{no,}'ttlid[display the TTL whening printing the record]'
|
||||
|
@ -74,7 +75,7 @@ _arguments -s -C $args \
|
|||
'*-6+[force IPv6 only]' \
|
||||
'*-'{x,-reverse}'+[reverse lookup]' \
|
||||
'*--timeout+[timeout in seconds]:number [1]' \
|
||||
'*--retry+[specify number of UDP query retries]:number [2]' \
|
||||
'*--retries+[specify number of query retries]:number [2]' \
|
||||
'*--no-edns+[disable EDNS]' \
|
||||
'*--edns-ver+[specify EDNS version for query]:version (0-255) [0]' \
|
||||
'*-'{D,-dnssec}'+[enable DNSSEC]' \
|
||||
|
@ -95,8 +96,9 @@ _arguments -s -C $args \
|
|||
'*--tls-host+[set TLS lookup hostname]:host:_hosts' \
|
||||
'*-'{s,-short}'+[print terse output]' \
|
||||
'*-'{j,-json}'+[present the results as JSON]' \
|
||||
'*-'{x,-xml}'+[present the results as XML]' \
|
||||
'*-'{X,-xml}'+[present the results as XML]' \
|
||||
'*-'{y,-yaml}'+[present the results as YAML]' \
|
||||
'*--trace+[trace from the root]' \
|
||||
'*: :->args' && ret=0
|
||||
|
||||
if [[ -n $state ]]; then
|
||||
|
@ -107,4 +109,4 @@ if [[ -n $state ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// GetDNSConfig gets DNS information from Plan 9, because it's different from UNIX and Windows.
|
||||
//
|
||||
// Plan 9 stores its network data in /net/ndb, which seems to be formatted a specific way
|
||||
// Yoink it and use it.
|
||||
//
|
||||
|
|
|
@ -4,53 +4,22 @@
|
|||
package conf_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestGetPlan9Config(t *testing.T) {
|
||||
func TestPlan9Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if runtime.GOOS != "plan9" {
|
||||
t.Skip("Not running Plan 9, skipping")
|
||||
}
|
||||
|
||||
ndbs := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{`ip=192.168.122.45 ipmask=255.255.255.0 ipgw=192.168.122.1
|
||||
sys=chog9
|
||||
dns=192.168.122.1`, "192.168.122.1"},
|
||||
{`ipnet=murray-hill ip=135.104.0.0 ipmask=255.255.0.0
|
||||
dns=135.104.10.1
|
||||
ntp=ntp.cs.bell-labs.com
|
||||
ipnet=plan9 ip=135.104.9.0 ipmask=255.255.255.0
|
||||
ntp=oncore.cs.bell-labs.com
|
||||
smtp=smtp1.cs.bell-labs.com
|
||||
ip=135.104.9.6 sys=anna dom=anna.cs.bell-labs.com
|
||||
smtp=smtp2.cs.bell-labs.com`, "135.104.10.1"},
|
||||
}
|
||||
conf, err := conf.GetDNSConfig()
|
||||
|
||||
for _, ndb := range ndbs {
|
||||
// Go is a little quirky
|
||||
ndb := ndb
|
||||
t.Run(ndb.want, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
act, err := conf.GetPlan9Config(ndb.in)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, ndb.want, act.Servers[0])
|
||||
})
|
||||
}
|
||||
|
||||
invalid := `sys = spindle
|
||||
dom=spindle.research.bell-labs.com
|
||||
bootf=/mips/9powerboot
|
||||
ip=135.104.117.32 ether=080069020677
|
||||
proto=il`
|
||||
|
||||
act, err := conf.GetPlan9Config(invalid)
|
||||
assert.ErrorContains(t, err, "no DNS servers found")
|
||||
assert.Assert(t, act == nil)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ import (
|
|||
"runtime"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestNonWinConfig(t *testing.T) {
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
||||
func TestUnixConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" || runtime.GOOS == "js" || runtime.GOOS == "zos" {
|
||||
t.Skip("Not running Unix-like, skipping")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build js
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// GetDNSConfig doesn't do anything, because it is impossible (and bad security)
|
||||
// if it could, as that is the definition of a DNS leak.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
var errNotImplemented = errors.New("not implemented")
|
|
@ -21,7 +21,7 @@ https://gist.github.com/moloch--/9fb1c8497b09b45c840fe93dd23b1e98
|
|||
//
|
||||
// Here be dragons.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
length := uint32(20000)
|
||||
length := uint32(100000)
|
||||
byt := make([]byte, length)
|
||||
|
||||
// Windows is an utter fucking trash fire of an operating system.
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"runtime"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
|
|
1
doc/wiki
1
doc/wiki
|
@ -1 +0,0 @@
|
|||
Subproject commit 96f2699a2f22a5bdcf37a48d0a20fd85c96bcc81
|
|
@ -0,0 +1 @@
|
|||
/dev/null
|
|
@ -0,0 +1,57 @@
|
|||
# Contributing to awl
|
||||
|
||||
First off, thank you! We appreciate your interest in wanting to contribute to awl.
|
||||
|
||||
> If you like the project, spread the word! Help us grow by sharing the project with anyone you thing might be interested. Here are some ways you can help:
|
||||
>
|
||||
> - Star the project on GitHub
|
||||
> - Share the project on social media
|
||||
> - Tell your friends about the project
|
||||
|
||||
## How to contribute
|
||||
|
||||
If you want to contribute to awl, you can do so by:
|
||||
|
||||
- [Reporting a bug](#reporting-a-bug)
|
||||
- [Requesting a feature](#requesting-a-feature)
|
||||
- [Submitting a pull request](#submitting-a-pull-request)
|
||||
|
||||
### Reporting a bug
|
||||
|
||||
If you find a bug in awl, please [open an issue](https://git.froth.zone/sam/awl/issues) on the project's issue tracker. When reporting a bug, please include as much information as possible, such as:
|
||||
|
||||
- The version of awl you are using
|
||||
- The operating system you are using
|
||||
- The steps to reproduce the bug
|
||||
- Any error messages you received
|
||||
|
||||
### Requesting a feature
|
||||
|
||||
If you have an idea for a feature you would like to see in awl, please [open an issue](https://git.froth.zone/sam/awl/issues) on the project's issue tracker. When requesting a feature, please include as much information as possible, such as:
|
||||
|
||||
- A description of the feature
|
||||
- Why you think the feature would be useful
|
||||
- Any other relevant information
|
||||
|
||||
### Submitting a pull request
|
||||
|
||||
If you would like to contribute code to awl, you can do so by submitting a pull request. To submit a pull request, follow these steps:
|
||||
|
||||
1. Fork the project on Git
|
||||
2. Create a new branch for your changes
|
||||
3. Make your changes
|
||||
4. Push your changes to your fork
|
||||
5. [Open a pull request](https://git.froth.zone/sam/awl/pulls) on the project's Git repository
|
||||
|
||||
When submitting a pull request, please include as much information as possible, such as:
|
||||
|
||||
- A description of the changes you made
|
||||
- Why you made the changes
|
||||
- Any other relevant information
|
||||
|
||||
Alternatively, you can also contribute by sending an email to the project's [mailing list](https://lists.sr.ht/~sammefishe/awl-devel). For more information about using Git over email, refer to [git-send-email.io](https://git-send-email.io/)
|
||||
|
||||
#### Code Style
|
||||
|
||||
Before submitting a pull request, please run `make lint` to ensure your code adheres to the project's code style.
|
||||
Make sure that you have `golangci-lint` installed, that is our linter of choice.
|
|
@ -25,17 +25,38 @@ some more recent RFCs and output options.
|
|||
When no arguments are given, *awl* will perform an _NS_ query on the root ('_._').
|
||||
|
||||
When a nameserver is not given, *awl* will query a random system nameserver.
|
||||
If one cannot be found, *awl* will query localhost.
|
||||
If one cannot be found, *awl* will query the localhost.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
Anything in [brackets] is optional.
|
||||
*-4*
|
||||
Force only IPv4
|
||||
|
||||
*-D*, *--dnssec*, *+dnssec*
|
||||
Enable DNSSEC. This needs to be manually enabled.
|
||||
*-6*
|
||||
Force only IPv6
|
||||
|
||||
*-c*, *--class* _class_
|
||||
DNS class to query (eg. IN, CH)
|
||||
The default is IN.
|
||||
|
||||
*-h*
|
||||
Show a "short" help message.
|
||||
|
||||
*-p*, *--port* _port_
|
||||
Sets the port to query. Default ports listed below.
|
||||
- _53_ for *UDP* and *TCP*
|
||||
- _853_ for *TLS* and *QUIC*
|
||||
- _443_ for *HTTPS*
|
||||
|
||||
*-q*, *--query* _domain_
|
||||
Explicitly set a domain to query (eg. example.com)
|
||||
|
||||
*-t*, *--qType* _type_
|
||||
Explicitly set a DNS type to query (eg. A, AAAA, NS)
|
||||
The default is A.
|
||||
|
||||
*-v*[=_int_]
|
||||
Set verbosity
|
||||
Set verbosity of output
|
||||
Accepted values are as follows:
|
||||
- _0_: Only log errors.
|
||||
- _1_: Log warnings. *This is the default.*
|
||||
|
@ -46,47 +67,122 @@ Anything in [brackets] is optional.
|
|||
|
||||
By default, specifying just *-v* sets the verbosity to 2 (info).
|
||||
|
||||
*-x*, *--reverse*
|
||||
Do a reverse lookup. Sets default *type* to PTR.
|
||||
*awl* automatically makes an IP or phone number canonical.
|
||||
|
||||
*-V*
|
||||
Print the version and exit.
|
||||
|
||||
*-h*
|
||||
Show a "short" help message.
|
||||
# QUERY OPTIONS
|
||||
|
||||
## Query Options
|
||||
Anything in [brackets] is optional.
|
||||
Many options are inherited from *dig*(1).
|
||||
|
||||
*-4*
|
||||
Only make query over IPv4
|
||||
*--aa*[=_bool_], *+*[no]*aaflag*, *+*[no]*aaonly*
|
||||
Sets the AA (Authoritative Answer) flag.
|
||||
|
||||
*-6*
|
||||
Only make query over IPv6
|
||||
*--ad*[=_bool_], *+*[no]*adflag*
|
||||
Sets the AD (Authenticated Data) flag.
|
||||
|
||||
*-p*, *--port* *port*
|
||||
Sets the port to query. Default ports listed below.
|
||||
- _53_ for *UDP* and *TCP*
|
||||
- _853_ for *TLS* and *QUIC*
|
||||
- _443_ for *HTTPS*
|
||||
*--no-additional*, *+*[no]*additional*
|
||||
Toggle the display of the Additional section.
|
||||
|
||||
*-q*, *--query* _domain_
|
||||
Domain to query (eg. example.com)
|
||||
*--no-answer*, *+*[no]*answer*
|
||||
Toggle the display of the Answer section.
|
||||
|
||||
*-c*, *--class* _class_
|
||||
DNS class to query (eg. IN, CH)
|
||||
The default is IN.
|
||||
*--no-authority*, *+*[no]*authority*
|
||||
Toggle the display of the Authority section.
|
||||
|
||||
*-t*, *--qType* _type_
|
||||
DNS type to query (eg. A, AAAA, NS)
|
||||
The default is A.
|
||||
*--no-bad-cookie*, *+*[no]*badcookie*
|
||||
\[Do not\] ignore BADCOOKIE responses
|
||||
|
||||
*--buffer-size* _int_, *+bufize*=_int_
|
||||
Set the UDP message buffer size, using EDNS.
|
||||
Max is 65535, minimum is zero.
|
||||
The default value is 1232.
|
||||
|
||||
*--cd*[=_bool_], *+*[no]*cdflag*
|
||||
(Set, Unset) CD (Checking Disabled) flag.
|
||||
|
||||
*--no-cookie*, *+*[no]*cookie*[=_string_]
|
||||
Send an EDNS cookie.
|
||||
This is enabled by default with a random string.
|
||||
|
||||
*-D*, *--dnssec*, *+dnssec*, *+do*
|
||||
Request DNSSEC records as well.
|
||||
This sets the DNSSEC OK bit (DO)
|
||||
|
||||
*--dnscrypt*, *+*[no]*dnscrypt*
|
||||
Use DNSCrypt.
|
||||
|
||||
*--expire*. *+*[no]*expire*
|
||||
Send an EDNS Expire.
|
||||
|
||||
|
||||
*--edns-ver*, *+edns*[=_int_]
|
||||
Enable EDNS and set EDNS version.
|
||||
The maximum value is 255, and the minimum (default) value is 0.
|
||||
|
||||
*--no-edns*, *+noedns*
|
||||
Disable EDNS.
|
||||
|
||||
*-H*, *--https*, *+*[no]*https*[=_endpoint_], *+*[no]*https-post*[=_endpoint_]
|
||||
Use DNS-over-HTTPS (see RFC 8484).
|
||||
The default endpoint is _/dns-query_
|
||||
|
||||
*+*[no]*https-get*[=_endpoint_]
|
||||
Use an HTTP GET instead of an HTTP POST when making a DNS-over-HTTPS query.
|
||||
|
||||
*+*[no]*idnout*
|
||||
Converts [or leaves] punycode on output.
|
||||
Input is automatically translated to punycode.
|
||||
|
||||
*--no-truncate*, *+ignore*
|
||||
Ignore UDP truncation (by default, awl *retries with TCP*).
|
||||
|
||||
*--tcp*, *+tcp*, *+vc*
|
||||
*-j*, *--json*, *+*[no]*json*
|
||||
Print the query results as JSON.
|
||||
The result is *not* in compliance with RFC 8427.
|
||||
|
||||
*--keep-alive*, *+*[no]*keepalive*, *+*[no]*keepopen*
|
||||
Send an EDNS keep-alive.
|
||||
This does nothing unless using TCP.
|
||||
|
||||
*--nsid*, *+*[no]*nsid*
|
||||
Send an EDNS name server ID request.
|
||||
|
||||
*--qr*[=_bool_], *+*[no]*qrflag*
|
||||
Sets the QR (QueRy) flag.
|
||||
|
||||
*--no-question*, *+*[no]*question*
|
||||
Toggle the display of the Question section.
|
||||
|
||||
*-Q*. *--quic*, *+*[no]*quic*
|
||||
Use DNS-over-QUIC (see RFC 9250).
|
||||
|
||||
*-s*, *--short*, *+*[no]*short*
|
||||
Print just the address of the answer.
|
||||
|
||||
*--no-statistics*, *+*[no]*stats*
|
||||
Toggle the display of the Statistics (additional comments) section.
|
||||
|
||||
*--subnet* _ip_[_/prefix_], *+*[no]*subnet*[=_ip_[_/prefix_]]
|
||||
Send an EDNS Client Subnet option with the specified address.
|
||||
|
||||
Like *dig*(1), setting the IP to _0.0.0.0/0_, _::/0_ or _0_ will signal the resolver to not use any client information when returning the query.
|
||||
|
||||
*--tc*[=_bool_], *+*[no]*tcflag*
|
||||
Sets the TC (TrunCated) flag
|
||||
|
||||
*--tcp*, *+*[no]*tcp*, *+*[no]*vc*
|
||||
Use TCP for the query (see RFC 7766).
|
||||
|
||||
*--dnscrypt*, *+dnscrypt*
|
||||
Use DNSCrypt.
|
||||
*--timeout* _seconds_, *+timeout*=_seconds_
|
||||
Set the timeout period. Floating point numbers are accepted.
|
||||
0.5 seconds is the minimum.
|
||||
|
||||
*-T*, *--tls*, *+tls*
|
||||
*-T*, *--tls*, *+*[no]*tls*
|
||||
Use DNS-over-TLS, implies *--tcp* (see RFC 7858)
|
||||
|
||||
*--tls-host* _string_
|
||||
|
@ -96,123 +192,29 @@ Anything in [brackets] is optional.
|
|||
*--tls-no-verify*
|
||||
Ignore TLS validation when performing a DNS query.
|
||||
|
||||
*-H*. *--https*, *+https*
|
||||
Use DNS-over-HTTPS (see RFC 8484).
|
||||
|
||||
*-Q*. *--quic*, *+quic*
|
||||
Use DNS-over-QUIC (see RFC 9250).
|
||||
|
||||
*-x*, *--reverse*
|
||||
Do a reverse lookup. Sets default *type* to PTR.
|
||||
*awl* automatically makes an IP or phone number canonical.
|
||||
|
||||
*--timeout* _seconds_, *+timeout*=_seconds_
|
||||
Set the timeout period. Floating point numbers are accepted.
|
||||
0.5 seconds is the minimum.
|
||||
*--trace*, *+trace*
|
||||
Trace the path of the query from the root, acting like its own resolver.
|
||||
This option enables DNSSEC.
|
||||
When *@server* is specified, this will only affect the initial query.
|
||||
|
||||
*--retries* _int_, *+tries*=_int_, *+retry*=_int_
|
||||
Set the number of retries.
|
||||
Retry is one more than tries, dig style.
|
||||
|
||||
## DNS Flags
|
||||
*-X*, *--xml*, *+*[no]*xml*
|
||||
Print the query results as XML.
|
||||
|
||||
*--aa*[=_bool_], *+[no]aaflag*
|
||||
(Set, Unset) AA (Authoritative Answer) flag.
|
||||
*-y*, *--yaml*, *+*[no]*yaml*
|
||||
Print the query results as YAML.
|
||||
|
||||
*--ad*[=_bool_], *+[no]adflag*
|
||||
(Set, Unset) AD (Authenticated Data) flag.
|
||||
|
||||
*--tc*[=_bool_], *+[no]tcflag*
|
||||
(Set, Unset) TC (TrunCated) flag
|
||||
|
||||
*-z*[=_bool_], *+[no]zflag*
|
||||
(Set, Unset) Z (Zero) flag.
|
||||
|
||||
*--cd*[=_bool_], *+[no]cdflag*
|
||||
(Set, Unset) CD (Checking Disabled) flag.
|
||||
|
||||
*--qr*[=_bool_], *+[no]qrflag*
|
||||
(Set, Unset) QR (QueRy) flag.
|
||||
|
||||
*--rd*[=_bool_], *+[no]rdflag*
|
||||
(Set, Unset) RD (Recursion Desired) flag.
|
||||
|
||||
*--ra*[=_bool_], *+[no]raflag*
|
||||
(Set, Unset) RA (Recursion Available) flag.
|
||||
|
||||
## EDNS
|
||||
All of these options except disabling EDNS imply *+edns*.
|
||||
|
||||
*--no-edns*, *+noedns*
|
||||
Disable EDNS.
|
||||
|
||||
*--edns-ver*, *+edns*[=_int_]
|
||||
Enable EDNS and set EDNS version.
|
||||
The maximum value is 255, and the minimum (default) value is 0.
|
||||
|
||||
*--expire*. *+[no]expire*
|
||||
Send an EDNS Expire.
|
||||
|
||||
*--nsid*, *+[no]nsid*
|
||||
Send an EDNS name server ID request.
|
||||
|
||||
*--no-cookie*, *+[no]cookie*[=_string_]
|
||||
Send an EDNS cookie.
|
||||
This is enabled by default with a random string.
|
||||
|
||||
*--keep-alive*, *+[no]keepalive*, *+[no]keepopen*
|
||||
Send an EDNS keep-alive.
|
||||
This does nothing unless using TCP.
|
||||
|
||||
*--buffer-size* _int_, *+bufize*=_int_
|
||||
Set the UDP message buffer size, using EDNS.
|
||||
Max is 65535, minimum is zero.
|
||||
The default value is 1232.
|
||||
*-z*[=_bool_], *+*[no]*zflag*
|
||||
Sets the Z (Zero) flag.
|
||||
|
||||
*--zflag* _int_, *+ednsflags*=_int_
|
||||
Set the must-be-zero EDNS flags.
|
||||
Decimal, hexadecimal and octal are supported.
|
||||
Trying to set DO will be ignored.
|
||||
|
||||
*--subnet* _ip_[_/prefix_], *+[no]subnet*=_ip_[_/prefix_]
|
||||
Send an EDNS Client Subnet option with the specified address.
|
||||
|
||||
Like *dig*(1), setting the IP to _0.0.0.0/0_, _::/0_ or _0_ will signal the resolver to not use any client information when returning the query.
|
||||
|
||||
## Output Display
|
||||
|
||||
*--no-question*, *+[no]question*
|
||||
Toggle the display of the Question section.
|
||||
|
||||
*--no-answer*, *+[no]answer*
|
||||
Toggle the display of the Answer section.
|
||||
|
||||
*--no-answer*, *+[no]answer*
|
||||
Toggle the display of the Answer section.
|
||||
|
||||
*--no-authority*, *+[no]authority*
|
||||
Toggle the display of the Authority section.
|
||||
|
||||
*--no-additional*, *+[no]additional*
|
||||
Toggle the display of the Additional section.
|
||||
|
||||
*--no-statistics*, *+[no]stats*
|
||||
Toggle the display of the Statistics (additional comments) section.
|
||||
|
||||
## Output Formats
|
||||
|
||||
*-j*, *--json*, *+json*
|
||||
Print the query results as JSON.
|
||||
|
||||
*-X*, *--xml*, *+xml*
|
||||
Print the query results as XML.
|
||||
|
||||
*-y*, *--yaml*, *+yaml*
|
||||
Print the query results as YAML.
|
||||
|
||||
*-s*, *--short*, *+short*
|
||||
Print just the address of the answer.
|
||||
|
||||
# EXIT STATUS
|
||||
|
||||
The exit code is 0 when a query is successfully made and received.
|
||||
|
@ -251,12 +253,10 @@ Probably more, _https://www.statdns.com/rfc_
|
|||
|
||||
# BUGS
|
||||
|
||||
OPT records are only printed when using a standard output, not JSON/XML/YAML.
|
||||
|
||||
Full parity with *dig*(1) is not complete.
|
||||
|
||||
This man page is probably not complete.
|
||||
|
||||
Likely numerous more, report them either to the tracker
|
||||
_https://git.froth.zone/sam/awl/issues_ or via email
|
||||
_~sammefishe/awl-dev@lists.sr.ht_
|
||||
_~sammefishe/awl-develop@lists.sr.ht_
|
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 505 KiB |
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
rm -f docs/awl.1.gz
|
||||
scdoc <docs/awl.1.scd >docs/awl.1
|
||||
gzip -9 -n docs/awl.1
|
|
@ -0,0 +1 @@
|
|||
Subproject commit ab0ac7e0bd1b92339cc97ba026f148478df5a860
|
53
go.mod
53
go.mod
|
@ -1,40 +1,33 @@
|
|||
module git.froth.zone/sam/awl
|
||||
module dns.froth.zone/awl
|
||||
|
||||
go 1.18
|
||||
go 1.21.9
|
||||
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.5
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
|
||||
github.com/lucas-clemente/quic-go v0.29.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
dns.froth.zone/dnscrypt v0.0.2
|
||||
github.com/dchest/uniuri v1.2.0
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/quic-go/quic-go v0.43.1
|
||||
github.com/stefansundin/go-zflag v1.1.1
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.3.0
|
||||
gotest.tools/v3 v3.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.10.9 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
|
||||
github.com/AdguardTeam/golibs v0.20.3 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
)
|
||||
|
|
200
go.sum
200
go.sum
|
@ -1,161 +1,75 @@
|
|||
github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
|
||||
github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM=
|
||||
dns.froth.zone/dnscrypt v0.0.2 h1:ytqjic/Qway4OuLw8ee0ubxdNzy+F3igUGDrEVwyLls=
|
||||
dns.froth.zone/dnscrypt v0.0.2/go.mod h1:QZ0HAm7mWe8wz1dTqbKmTZhlr06x5qpe6ZCTPJ7uY30=
|
||||
github.com/AdguardTeam/golibs v0.20.3 h1:5RiDypxBebd4Y2eftwm6JJla18oBqRHwanR7q0rnrxw=
|
||||
github.com/AdguardTeam/golibs v0.20.3/go.mod h1:/votX6WK1PdcZ3T2kBOPjPCGmfhlKixhI6ljYrFRPvI=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
|
||||
github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.29.0 h1:Vw0mGTfmWqGzh4jx/kMymsIkFK6rErFVmg+t9RLrnZE=
|
||||
github.com/lucas-clemente/quic-go v0.29.0/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
|
||||
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/stefansundin/go-zflag v1.1.1 h1:XabhzWS588bVvV1z1UctSa6i8zHkXc5W9otqtnDSHw8=
|
||||
github.com/stefansundin/go-zflag v1.1.1/go.mod h1:HXX5rABl1AoTcZ2jw+CqJ7R8irczaLquGNZlFabZooc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
|
||||
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
||||
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
|
|
134
main.go
134
main.go
|
@ -5,20 +5,23 @@ package main
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func main() {
|
||||
if opts, code, err := run(); err != nil {
|
||||
if opts, code, err := run(os.Args); err != nil {
|
||||
// TODO: Make not ew
|
||||
if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
if errors.Is(err, util.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
opts.Logger.Error(err)
|
||||
|
@ -27,43 +30,106 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func run() (opts util.Options, code int, err error) {
|
||||
opts, err = cli.ParseCLI(version)
|
||||
func run(args []string) (opts *util.Options, code int, err error) {
|
||||
//nolint:gosec //Secure source not needed
|
||||
r := rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
opts, err = cli.ParseCLI(args, version)
|
||||
if err != nil {
|
||||
return opts, 1, fmt.Errorf("parse: %w", err)
|
||||
}
|
||||
|
||||
var resp util.Response
|
||||
var (
|
||||
resp util.Response
|
||||
keepTracing bool
|
||||
tempDomain string
|
||||
tempQueryType uint16
|
||||
)
|
||||
|
||||
// Retry queries if a query fails
|
||||
for i := 0; i < opts.Request.Retries; i++ {
|
||||
resp, err = query.CreateQuery(opts)
|
||||
if err == nil {
|
||||
break
|
||||
for ok := true; ok; ok = keepTracing {
|
||||
if opts.Trace {
|
||||
if keepTracing {
|
||||
opts.Request.Name = tempDomain
|
||||
opts.Request.Type = tempQueryType
|
||||
} else {
|
||||
tempDomain = opts.Request.Name
|
||||
tempQueryType = opts.Request.Type
|
||||
|
||||
// Override the query because it needs to be done
|
||||
opts.Request.Name = "."
|
||||
opts.Request.Type = dns.TypeNS
|
||||
}
|
||||
}
|
||||
// Retry queries if a query fails
|
||||
for i := 0; i <= opts.Request.Retries; i++ {
|
||||
resp, err = query.CreateQuery(opts)
|
||||
if err == nil {
|
||||
keepTracing = opts.Trace && (!resp.DNS.Authoritative || (opts.Request.Name == "." && tempDomain != ".")) && resp.DNS.MsgHdr.Rcode == 0
|
||||
|
||||
break
|
||||
} else if i != opts.Request.Retries {
|
||||
opts.Logger.Warn("Retrying request, error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
|
||||
var str string
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = query.PrintSpecial(resp, opts)
|
||||
if err != nil {
|
||||
return opts, 10, fmt.Errorf("format print: %w", err)
|
||||
}
|
||||
} else {
|
||||
opts.Logger.Warn("Retrying request, error:", err)
|
||||
str, err = query.ToString(resp, opts)
|
||||
if err != nil {
|
||||
return opts, 15, fmt.Errorf("standard print: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
if keepTracing {
|
||||
var records []dns.RR
|
||||
|
||||
if opts.Request.Name == "." {
|
||||
records = resp.DNS.Answer
|
||||
} else {
|
||||
records = resp.DNS.Ns
|
||||
}
|
||||
|
||||
want := func(rr dns.RR) bool {
|
||||
temp := strings.Split(rr.String(), "\t")
|
||||
|
||||
return temp[len(temp)-2] == "NS"
|
||||
}
|
||||
|
||||
i := 0
|
||||
|
||||
for _, x := range records {
|
||||
if want(x) {
|
||||
records[i] = x
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
records = records[:i]
|
||||
randomRR := records[r.Intn(len(records))]
|
||||
|
||||
v := strings.Split(randomRR.String(), "\t")
|
||||
opts.Request.Server = strings.TrimSuffix(v[len(v)-1], ".")
|
||||
|
||||
opts.TLS = false
|
||||
opts.HTTPS = false
|
||||
opts.QUIC = false
|
||||
|
||||
opts.RD = false
|
||||
opts.Request.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
|
||||
var str string
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = query.PrintSpecial(resp.DNS, opts)
|
||||
if err != nil {
|
||||
return opts, 10, fmt.Errorf("format print: %w", err)
|
||||
}
|
||||
} else {
|
||||
str, err = query.ToString(resp, opts)
|
||||
if err != nil {
|
||||
return opts, 15, fmt.Errorf("standard print: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
return opts, 0, nil
|
||||
}
|
||||
|
|
50
main_test.go
50
main_test.go
|
@ -3,44 +3,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stefansundin/go-zflag"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
os.Stderr = os.NewFile(0, os.DevNull)
|
||||
func TestRun(t *testing.T) {
|
||||
// t.Parallel()
|
||||
args := [][]string{
|
||||
{"awl", "+yaml", "@1.1.1.1"},
|
||||
{"awl", "+short", "@1.1.1.1"},
|
||||
}
|
||||
|
||||
old := os.Args
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, code, err := run(test)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_, code, err := run()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
func TestTrace(t *testing.T) {
|
||||
domains := []string{"git.froth.zone", "google.com", "amazon.com", "freecumextremist.com", "dns.froth.zone", "sleepy.cafe", "pkg.go.dev"}
|
||||
|
||||
os.Args = []string{"awl", "+short", "@1.1.1.1"}
|
||||
|
||||
_, code, err = run()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
|
||||
os.Args = old
|
||||
for i := range domains {
|
||||
args := []string{"awl", "+trace", domains[i], "@1.1.1.1"}
|
||||
_, code, err := run(args)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
os.Stderr = os.NewFile(0, os.DevNull)
|
||||
// t.Parallel()
|
||||
args := []string{"awl", "-h"}
|
||||
|
||||
os.Args = []string{"awl", "-h"}
|
||||
|
||||
_, code, err := run()
|
||||
_, code, err := run(args)
|
||||
assert.ErrorIs(t, err, zflag.ErrHelp)
|
||||
assert.Equal(t, code, 1)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
|
8
mkfile
8
mkfile
|
@ -1,10 +1,12 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Plan 9 mkfile
|
||||
|
||||
</$objtype/mkfile
|
||||
|
||||
GO = go
|
||||
PROG = awl
|
||||
|
||||
GOFLAGS = -ldflags=-s -ldflags=-w -ldflags=-X=main.version=PLAN9 -trimpath
|
||||
VERSION = `{awk '{print substr($0,0,8)}' .git/refs/heads/master}
|
||||
GOFLAGS = -ldflags=-s -ldflags=-w -ldflags=-X=main.version=$VERSION -trimpath
|
||||
|
||||
CGO_ENABLED = 0
|
||||
|
||||
|
@ -15,7 +17,7 @@ $PROG:
|
|||
|
||||
install:V:
|
||||
$GO install $GOFLAGS .
|
||||
cp doc/$PROG.1 /sys/man/1/$PROG
|
||||
# cp docs/$PROG.1 /sys/man/1/$PROG
|
||||
|
||||
test:V:
|
||||
$GO test -v -cover ./...
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit c8686b1e149b6c42db019c77caf36475aafadd03
|
|
@ -14,7 +14,7 @@ because awl is a cli utility it writes directly to std err.
|
|||
// You can call specific logging levels from your new logger using
|
||||
//
|
||||
// logger.Debug("Message to log")
|
||||
// logger.Fatal("Message to log")
|
||||
// logger.Warning("Message to log")
|
||||
// logger.Info("Message to log")
|
||||
// logger.Error("Message to log")
|
||||
//
|
|
@ -25,22 +25,22 @@ type (
|
|||
)
|
||||
|
||||
// SetLevel stores whatever input value is in mem address of l.level.
|
||||
func (l *Logger) SetLevel(level Level) {
|
||||
atomic.StoreInt32((*int32)(&l.Level), int32(level))
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreInt32((*int32)(&logger.Level), int32(level))
|
||||
}
|
||||
|
||||
// GetLevel gets the logger level.
|
||||
func (l *Logger) GetLevel() Level {
|
||||
return l.level()
|
||||
func (logger *Logger) GetLevel() Level {
|
||||
return logger.level()
|
||||
}
|
||||
|
||||
// Retrieves whatever was stored in mem address of l.level.
|
||||
func (l *Logger) level() Level {
|
||||
return Level(atomic.LoadInt32((*int32)(&l.Level)))
|
||||
func (logger *Logger) level() Level {
|
||||
return Level(atomic.LoadInt32((*int32)(&logger.Level)))
|
||||
}
|
||||
|
||||
// UnMarshalLevel unmarshalls the int value of level for writing the header.
|
||||
func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
|
||||
func (logger *Logger) UnMarshalLevel(lv Level) (string, error) {
|
||||
switch lv {
|
||||
case ErrLevel:
|
||||
return "ERROR ", nil
|
||||
|
@ -56,8 +56,8 @@ func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
|
|||
}
|
||||
|
||||
// IsLevel returns true if the logger level is above the level given.
|
||||
func (l *Logger) IsLevel(level Level) bool {
|
||||
return l.level() >= level
|
||||
func (logger *Logger) IsLevel(level Level) bool {
|
||||
return logger.level() >= level
|
||||
}
|
||||
|
||||
// AllLevels is an array of all valid log levels.
|
|
@ -20,28 +20,28 @@ func New() *Logger {
|
|||
}
|
||||
|
||||
// Println takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)).
|
||||
func (l *Logger) Println(level Level, v ...any) {
|
||||
if atomic.LoadInt32(&l.isDiscard) != 0 {
|
||||
func (logger *Logger) Println(level Level, in ...any) {
|
||||
if atomic.LoadInt32(&logger.isDiscard) != 0 {
|
||||
return
|
||||
}
|
||||
// If verbose is not set --debug etc print _nothing_
|
||||
if l.IsLevel(level) {
|
||||
if logger.IsLevel(level) {
|
||||
switch level { // Goes through log levels and does stuff based on them (currently nothing)
|
||||
case ErrLevel:
|
||||
if err := l.Printer(ErrLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
if err := logger.Printer(ErrLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case WarnLevel:
|
||||
if err := l.Printer(WarnLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
if err := logger.Printer(WarnLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case InfoLevel:
|
||||
if err := l.Printer(InfoLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
if err := logger.Printer(InfoLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case DebugLevel:
|
||||
if err := l.Printer(DebugLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
if err := logger.Printer(DebugLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -50,8 +50,8 @@ func (l *Logger) Println(level Level, v ...any) {
|
|||
}
|
||||
|
||||
// FormatHeader formats the log header as such <LogLevel> YYYY/MM/DD HH:MM:SS (local time) <the message to log>.
|
||||
func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) error {
|
||||
if lvl, err := l.UnMarshalLevel(level); err == nil {
|
||||
func (logger *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) error {
|
||||
if lvl, err := logger.UnMarshalLevel(level); err == nil {
|
||||
// This is ugly but functional
|
||||
// maybe there can be an append func or something in the future
|
||||
*buf = append(*buf, lvl...)
|
||||
|
@ -81,27 +81,27 @@ func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) e
|
|||
}
|
||||
|
||||
// Printer prints the formatted message directly to stdErr.
|
||||
func (l *Logger) Printer(level Level, s string) error {
|
||||
func (logger *Logger) Printer(level Level, s string) error {
|
||||
now := time.Now()
|
||||
|
||||
var line int
|
||||
|
||||
l.Mu.Lock()
|
||||
defer l.Mu.Unlock()
|
||||
logger.Mu.Lock()
|
||||
defer logger.Mu.Unlock()
|
||||
|
||||
l.buf = l.buf[:0]
|
||||
logger.buf = logger.buf[:0]
|
||||
|
||||
if err := l.FormatHeader(&l.buf, now, line, level); err != nil {
|
||||
if err := logger.FormatHeader(&logger.buf, now, line, level); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.buf = append(l.buf, s...)
|
||||
logger.buf = append(logger.buf, s...)
|
||||
|
||||
if len(s) == 0 || s[len(s)-1] != '\n' {
|
||||
l.buf = append(l.buf, '\n')
|
||||
logger.buf = append(logger.buf, '\n')
|
||||
}
|
||||
|
||||
_, err := l.Out.Write(l.buf)
|
||||
_, err := logger.Out.Write(logger.buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logger printing: %w", err)
|
||||
}
|
||||
|
@ -135,21 +135,41 @@ func formatter(buf *[]byte, i int, wid int) {
|
|||
}
|
||||
|
||||
// Debug calls print directly with Debug level.
|
||||
func (l *Logger) Debug(v ...any) {
|
||||
l.Println(DebugLevel, v...)
|
||||
func (logger *Logger) Debug(in ...any) {
|
||||
logger.Println(DebugLevel, in...)
|
||||
}
|
||||
|
||||
// Debugf calls print after formatting the string with Debug level.
|
||||
func (logger *Logger) Debugf(format string, in ...any) {
|
||||
logger.Println(DebugLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Info calls print directly with Info level.
|
||||
func (l *Logger) Info(v ...any) {
|
||||
l.Println(InfoLevel, v...)
|
||||
func (logger *Logger) Info(in ...any) {
|
||||
logger.Println(InfoLevel, in...)
|
||||
}
|
||||
|
||||
// Infof calls print after formatting the string with Info level.
|
||||
func (logger *Logger) Infof(format string, in ...any) {
|
||||
logger.Println(InfoLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Warn calls print directly with Warn level.
|
||||
func (l *Logger) Warn(v ...any) {
|
||||
l.Println(WarnLevel, v...)
|
||||
func (logger *Logger) Warn(in ...any) {
|
||||
logger.Println(WarnLevel, in...)
|
||||
}
|
||||
|
||||
// Warnf calls print after formatting the string with Warn level.
|
||||
func (logger *Logger) Warnf(format string, in ...any) {
|
||||
logger.Println(WarnLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Error calls print directly with Error level.
|
||||
func (l *Logger) Error(v ...any) {
|
||||
l.Println(ErrLevel, v...)
|
||||
func (logger *Logger) Error(in ...any) {
|
||||
logger.Println(ErrLevel, in...)
|
||||
}
|
||||
|
||||
// Errorf calls print after formatting the string with Error level.
|
||||
func (logger *Logger) Errorf(format string, in ...any) {
|
||||
logger.Println(ErrLevel, fmt.Sprintf(format, in...))
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
|
@ -52,6 +52,7 @@ func TestLogger(t *testing.T) {
|
|||
case 0:
|
||||
fn := func() {
|
||||
logger.Error("Test", "E")
|
||||
logger.Errorf("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
@ -62,6 +63,7 @@ func TestLogger(t *testing.T) {
|
|||
case 1:
|
||||
fn := func() {
|
||||
logger.Warn("Test")
|
||||
logger.Warnf("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
@ -72,6 +74,7 @@ func TestLogger(t *testing.T) {
|
|||
case 2:
|
||||
fn := func() {
|
||||
logger.Info("Test")
|
||||
logger.Infof("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
@ -83,6 +86,8 @@ func TestLogger(t *testing.T) {
|
|||
fn := func() {
|
||||
logger.Debug("Test")
|
||||
logger.Debug("Test 2")
|
||||
logger.Debugf("%s", "Test")
|
||||
logger.Debugf("%s %d", "Test", 2)
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
|
@ -3,60 +3,29 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Message is for overall DNS responses.
|
||||
type Message struct {
|
||||
Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:",omitempty"`
|
||||
Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:",omitempty"`
|
||||
Ns []Answer `json:"ns,omitempty" xml:"ns,omitempty" yaml:",omitempty"`
|
||||
Extra []Answer `json:"extra,omitempty" xml:"extra,omitempty" yaml:",omitempty"`
|
||||
Header dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// Question is a DNS Query.
|
||||
type Question struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
|
||||
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// RRHeader is for DNS Resource Headers.
|
||||
type RRHeader struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
|
||||
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
|
||||
TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:",omitempty"`
|
||||
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Answer is for a DNS Response.
|
||||
type Answer struct {
|
||||
Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,omitempty"`
|
||||
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
|
||||
}
|
||||
|
||||
// ToString turns the response into something that looks a lot like dig
|
||||
//
|
||||
// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
|
||||
func ToString(res util.Response, opts util.Options) (string, error) {
|
||||
func ToString(res util.Response, opts *util.Options) (s string, err error) {
|
||||
if res.DNS == nil {
|
||||
return "<nil> MsgHdr", errNoMessage
|
||||
}
|
||||
|
||||
var (
|
||||
s string
|
||||
opt *dns.OPT
|
||||
)
|
||||
var opt *dns.OPT
|
||||
|
||||
if !opts.Short {
|
||||
if opts.Display.Comments {
|
||||
|
@ -149,23 +118,7 @@ func ToString(res util.Response, opts util.Options) (string, error) {
|
|||
|
||||
if opts.Display.Statistics {
|
||||
s += "\n;; Query time: " + res.RTT.String()
|
||||
// Add extra information to server string
|
||||
var extra string
|
||||
|
||||
switch {
|
||||
case opts.TCP:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
|
||||
case opts.TLS:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
|
||||
case opts.HTTPS, opts.DNSCrypt:
|
||||
extra = ""
|
||||
case opts.QUIC:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
|
||||
default:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
|
||||
}
|
||||
|
||||
s += "\n;; SERVER: " + opts.Request.Server + extra
|
||||
s += "\n;; SERVER: " + opts.Request.Server + serverExtra(opts)
|
||||
s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
|
||||
s += "\n;; MSG SIZE rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
|
||||
}
|
||||
|
@ -186,10 +139,26 @@ func ToString(res util.Response, opts util.Options) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
return
|
||||
}
|
||||
|
||||
func stringParse(str string, isAns bool, opts util.Options) (string, error) {
|
||||
func serverExtra(opts *util.Options) string {
|
||||
switch {
|
||||
case opts.TCP:
|
||||
return " (TCP)"
|
||||
case opts.TLS:
|
||||
return " (TLS)"
|
||||
case opts.HTTPS, opts.DNSCrypt:
|
||||
return ""
|
||||
case opts.QUIC:
|
||||
return " (QUIC)"
|
||||
default:
|
||||
return " (UDP)"
|
||||
}
|
||||
}
|
||||
|
||||
// stringParse edits the raw responses to user requests.
|
||||
func stringParse(str string, isAns bool, opts *util.Options) (string, error) {
|
||||
split := strings.Split(str, "\t")
|
||||
|
||||
// Make edits if so requested
|
||||
|
@ -198,21 +167,21 @@ func stringParse(str string, isAns bool, opts util.Options) (string, error) {
|
|||
// This exists because the question section should be left alone EXCEPT for punycode.
|
||||
|
||||
if isAns {
|
||||
if !opts.ShowTTL {
|
||||
if !opts.Display.TTL {
|
||||
// Remove from existence
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
|
||||
if !opts.ShowClass {
|
||||
if !opts.Display.ShowClass {
|
||||
// Position depends on if the TTL is there or not.
|
||||
if opts.ShowTTL {
|
||||
if opts.Display.TTL {
|
||||
split = append(split[:2], split[3:]...)
|
||||
} else {
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.HumanTTL {
|
||||
if opts.Display.TTL && opts.Display.HumanTTL {
|
||||
ttl, _ := strconv.Atoi(split[1])
|
||||
split[1] = (time.Duration(ttl) * time.Second).String()
|
||||
}
|
||||
|
@ -240,4 +209,105 @@ func stringParse(str string, isAns bool, opts util.Options) (string, error) {
|
|||
return strings.Join(split, "\t"), nil
|
||||
}
|
||||
|
||||
var errNoMessage = errors.New("no message")
|
||||
// PrintSpecial is for printing as JSON, XML or YAML.
|
||||
// As of now JSON and XML use the stdlib version.
|
||||
func PrintSpecial(res util.Response, opts *util.Options) (string, error) {
|
||||
formatted, err := MakePrintable(res, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.JSON:
|
||||
opts.Logger.Info("Printing as JSON")
|
||||
|
||||
json, err := json.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(json), err
|
||||
case opts.XML:
|
||||
opts.Logger.Info("Printing as XML")
|
||||
|
||||
xml, err := xml.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(xml), err
|
||||
case opts.YAML:
|
||||
opts.Logger.Info("Printing as YAML")
|
||||
|
||||
yaml, err := yaml.Marshal(formatted)
|
||||
|
||||
return string(yaml), err
|
||||
default:
|
||||
return "", errInvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
// MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML,
|
||||
// and XML. Little is changed beyond naming.
|
||||
func MakePrintable(res util.Response, opts *util.Options) (*Message, error) {
|
||||
var (
|
||||
err error
|
||||
msg = res.DNS
|
||||
)
|
||||
// The things I do for compatibility
|
||||
ret := &Message{
|
||||
DateString: time.Now().Format(time.RFC3339),
|
||||
DateSeconds: time.Now().Unix(),
|
||||
MsgSize: res.DNS.Len(),
|
||||
ID: msg.Id,
|
||||
Opcode: msg.Opcode,
|
||||
Response: msg.Response,
|
||||
|
||||
Authoritative: msg.Authoritative,
|
||||
Truncated: msg.Truncated,
|
||||
RecursionDesired: msg.RecursionDesired,
|
||||
RecursionAvailable: msg.RecursionAvailable,
|
||||
AuthenticatedData: msg.AuthenticatedData,
|
||||
CheckingDisabled: msg.CheckingDisabled,
|
||||
Zero: msg.Zero,
|
||||
|
||||
QdCount: len(msg.Question),
|
||||
AnCount: len(msg.Answer),
|
||||
NsCount: len(msg.Ns),
|
||||
ArCount: len(msg.Extra),
|
||||
}
|
||||
|
||||
opt := msg.IsEdns0()
|
||||
if opt != nil && opts.Display.Opt {
|
||||
ret.EDNS0, err = ret.ParseOpt(msg.Rcode, *opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("edns print: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Question {
|
||||
err = ret.displayQuestion(msg, opts, opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to display questions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Answer {
|
||||
err = ret.displayAnswers(msg, opts, opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to display answers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Authority {
|
||||
err = ret.displayAuthority(msg, opts, opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to display authority: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Additional {
|
||||
err = ret.displayAdditional(msg, opts, opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to display additional: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var errInvalidFormat = errors.New("this should never happen")
|
|
@ -5,8 +5,8 @@ package query_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
@ -14,17 +14,18 @@ import (
|
|||
func TestRealPrint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := []util.Options{
|
||||
opts := []*util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
ShowTTL: true,
|
||||
HumanTTL: true,
|
||||
JSON: true,
|
||||
Display: util.Displays{
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
TCP: true,
|
||||
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
|
||||
JSON: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
|
@ -32,75 +33,84 @@ func TestRealPrint(t *testing.T) {
|
|||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
HumanTTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "a.gtld-servers.net",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
Verbosity: 0,
|
||||
ShowTTL: true,
|
||||
Short: true,
|
||||
Identify: true,
|
||||
YAML: false,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "ns1.google.com",
|
||||
Server: "a.gtld-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
HTTPS: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
ShowTTL: true,
|
||||
HumanTTL: true,
|
||||
Identify: true,
|
||||
XML: true,
|
||||
Display: util.Displays{
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
TCP: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
|
||||
Short: true,
|
||||
Identify: true,
|
||||
YAML: false,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "ns1.google.com",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
HTTPS: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Identify: true,
|
||||
XML: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
HumanTTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "https://dns.froth.zone/dns-query",
|
||||
Port: 443,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
|
@ -108,14 +118,13 @@ func TestRealPrint(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
TLS: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
ShowTTL: false,
|
||||
Display: util.Displays{
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
|
@ -123,25 +132,30 @@ func TestRealPrint(t *testing.T) {
|
|||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: false,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
AA: true,
|
||||
RD: true,
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
AA: true,
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
ShowTTL: true,
|
||||
YAML: true,
|
||||
Display: util.Displays{
|
||||
|
||||
YAML: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
|
@ -149,14 +163,16 @@ func TestRealPrint(t *testing.T) {
|
|||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
TTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "rin.froth.zone",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["A"],
|
||||
Class: 1,
|
||||
Name: "froth.zone.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
|
@ -171,16 +187,26 @@ func TestRealPrint(t *testing.T) {
|
|||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := query.CreateQuery(test)
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
|
||||
if test.JSON || test.XML || test.YAML {
|
||||
str := ""
|
||||
str, err = query.PrintSpecial(resp.DNS, test)
|
||||
str, err = query.PrintSpecial(res, test)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
str, err := query.ToString(resp, test)
|
||||
str, err := query.ToString(res, test)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
|
@ -190,14 +216,14 @@ func TestRealPrint(t *testing.T) {
|
|||
func TestBadFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := query.PrintSpecial(new(dns.Msg), util.Options{})
|
||||
_, err := query.PrintSpecial(util.Response{DNS: new(dns.Msg)}, new(util.Options))
|
||||
assert.ErrorContains(t, err, "never happen")
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
str, err := query.ToString(util.Response{}, util.Options{})
|
||||
str, err := query.ToString(util.Response{}, new(util.Options))
|
||||
|
||||
assert.Error(t, err, "no message")
|
||||
assert.Assert(t, str == "<nil> MsgHdr")
|
|
@ -6,19 +6,15 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/resolvers"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
tcp = "tcp"
|
||||
udp = "udp"
|
||||
)
|
||||
|
||||
// CreateQuery creates a DNS query from the options given.
|
||||
// It sets query flags and EDNS flags from the respective options.
|
||||
func CreateQuery(opts util.Options) (util.Response, error) {
|
||||
func CreateQuery(opts *util.Options) (util.Response, error) {
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
req.Question[0].Qclass = opts.Request.Class
|
||||
|
@ -35,64 +31,64 @@ func CreateQuery(opts util.Options) (util.Response, error) {
|
|||
|
||||
// EDNS time :)
|
||||
if opts.EDNS.EnableEDNS {
|
||||
o := new(dns.OPT)
|
||||
o.Hdr.Name = "."
|
||||
o.Hdr.Rrtype = dns.TypeOPT
|
||||
edns := new(dns.OPT)
|
||||
edns.Hdr.Name = "."
|
||||
edns.Hdr.Rrtype = dns.TypeOPT
|
||||
|
||||
o.SetVersion(opts.EDNS.Version)
|
||||
edns.SetVersion(opts.EDNS.Version)
|
||||
|
||||
if opts.EDNS.Cookie {
|
||||
e := new(dns.EDNS0_COOKIE)
|
||||
e.Code = dns.EDNS0COOKIE
|
||||
e.Cookie = uniuri.NewLenChars(8, []byte("1234567890abcdef"))
|
||||
o.Option = append(o.Option, e)
|
||||
cookie := new(dns.EDNS0_COOKIE)
|
||||
cookie.Code = dns.EDNS0COOKIE
|
||||
cookie.Cookie = uniuri.NewLenChars(16, []byte("1234567890abcdef"))
|
||||
edns.Option = append(edns.Option, cookie)
|
||||
|
||||
opts.Logger.Info("Setting EDNS cookie to", e.Cookie)
|
||||
opts.Logger.Info("Setting EDNS cookie to", cookie.Cookie)
|
||||
}
|
||||
|
||||
if opts.EDNS.Expire {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_EXPIRE))
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_EXPIRE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS Expire option")
|
||||
}
|
||||
|
||||
if opts.EDNS.KeepOpen {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_TCP_KEEPALIVE))
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_TCP_KEEPALIVE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS TCP Keepalive option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Nsid {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_NSID))
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_NSID))
|
||||
|
||||
opts.Logger.Info("Setting EDNS NSID option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Padding {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_PADDING))
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_PADDING))
|
||||
|
||||
opts.Logger.Info("Setting EDNS padding")
|
||||
}
|
||||
|
||||
o.SetUDPSize(opts.EDNS.BufSize)
|
||||
edns.SetUDPSize(opts.EDNS.BufSize)
|
||||
|
||||
opts.Logger.Info("EDNS UDP buffer set to", opts.EDNS.BufSize)
|
||||
|
||||
o.SetZ(opts.EDNS.ZFlag)
|
||||
edns.SetZ(opts.EDNS.ZFlag)
|
||||
|
||||
opts.Logger.Info("EDNS Z flag set to", opts.EDNS.ZFlag)
|
||||
|
||||
if opts.EDNS.DNSSEC {
|
||||
o.SetDo()
|
||||
edns.SetDo()
|
||||
|
||||
opts.Logger.Info("EDNS DNSSEC OK set")
|
||||
}
|
||||
|
||||
if opts.EDNS.Subnet.Address != nil {
|
||||
o.Option = append(o.Option, &opts.EDNS.Subnet)
|
||||
edns.Option = append(edns.Option, &opts.EDNS.Subnet)
|
||||
}
|
||||
|
||||
req.Extra = append(req.Extra, o)
|
||||
req.Extra = append(req.Extra, edns)
|
||||
} else if opts.EDNS.DNSSEC {
|
||||
req.SetEdns0(1232, true)
|
||||
opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled")
|
||||
|
@ -102,7 +98,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
|
|||
opts.Logger.Debug(req)
|
||||
|
||||
if !opts.Short {
|
||||
if opts.ShowQuery {
|
||||
if opts.Display.ShowQuery {
|
||||
opts.Logger.Info("Printing constructed query")
|
||||
|
||||
var (
|
||||
|
@ -111,7 +107,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
|
|||
)
|
||||
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = PrintSpecial(req, opts)
|
||||
str, err = PrintSpecial(util.Response{DNS: req}, opts)
|
||||
if err != nil {
|
||||
return util.Response{}, err
|
||||
}
|
||||
|
@ -133,13 +129,13 @@ func CreateQuery(opts util.Options) (util.Response, error) {
|
|||
|
||||
fmt.Println(str)
|
||||
|
||||
opts.ShowQuery = false
|
||||
opts.Display.ShowQuery = false
|
||||
}
|
||||
}
|
||||
|
||||
resolver, err := LoadResolver(opts)
|
||||
resolver, err := resolvers.LoadResolver(opts)
|
||||
if err != nil {
|
||||
return util.Response{}, err
|
||||
return util.Response{}, fmt.Errorf("unable to load resolvers: %w", err)
|
||||
}
|
||||
|
||||
opts.Logger.Info("Query successfully loaded")
|
|
@ -0,0 +1,157 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCreateQ(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"1",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
Z: true,
|
||||
},
|
||||
YAML: true,
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
ZFlag: 1,
|
||||
BufSize: 1500,
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
DNSSEC: true,
|
||||
Expire: true,
|
||||
KeepOpen: true,
|
||||
Nsid: true,
|
||||
Padding: true,
|
||||
Version: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"2",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
Z: true,
|
||||
},
|
||||
XML: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"3",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
JSON: true,
|
||||
QUIC: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "dns.froth.zone",
|
||||
Port: 853,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
Timeout: time.Second,
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
DNSSEC: true,
|
||||
Cookie: true,
|
||||
Expire: true,
|
||||
Nsid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
|
||||
str, err := query.PrintSpecial(res, test.opts)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str, err = query.ToString(res, test.opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Message is for overall DNS responses.
|
||||
//
|
||||
//nolint:govet,tagliatelle // Better looking output is worth a few bytes.
|
||||
type Message struct {
|
||||
DateString string `json:"dateString,omitempty" xml:"dateString,omitempty" yaml:"dateString,omitempty"`
|
||||
DateSeconds int64 `json:"dateSeconds,omitempty" xml:"dateSeconds,omitempty" yaml:"dateSeconds,omitempty"`
|
||||
MsgSize int `json:"msgLength,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
|
||||
ID uint16 `json:"ID" xml:"ID" yaml:"ID" example:"12"`
|
||||
|
||||
Opcode int `json:"opcode" xml:"opcode" yaml:"opcode" example:"QUERY"`
|
||||
Response bool `json:"QR" xml:"QR" yaml:"QR" example:"true"`
|
||||
Authoritative bool `json:"AA" xml:"AA" yaml:"AA" example:"false"`
|
||||
Truncated bool `json:"TC" xml:"TC" yaml:"TC" example:"false"`
|
||||
RecursionDesired bool `json:"RD" xml:"RD" yaml:"RD" example:"true"`
|
||||
RecursionAvailable bool `json:"RA" xml:"RA" yaml:"RA" example:"true"`
|
||||
AuthenticatedData bool `json:"AD" xml:"AD" yaml:"AD" example:"false"`
|
||||
CheckingDisabled bool `json:"CD" xml:"CD" yaml:"CD" example:"false"`
|
||||
Zero bool `json:"Z" xml:"Z" yaml:"Z" example:"false"`
|
||||
|
||||
QdCount int `json:"QDCOUNT" xml:"QDCOUNT" yaml:"QDCOUNT" example:"0"`
|
||||
AnCount int `json:"ANCOUNT" xml:"ANCOUNT" yaml:"ANCOUNT" example:"0"`
|
||||
NsCount int `json:"NSCOUNT" xml:"NSCOUNT" yaml:"NSCOUNT" example:"0"`
|
||||
ArCount int `json:"ARCOUNT" xml:"ARCOUNT" yaml:"ARCOUNT" example:"0"`
|
||||
|
||||
Name string `json:"QNAME,omitempty" xml:"QNAME,omitempty" yaml:"QNAME,omitempty" example:"localhost"`
|
||||
Type uint16 `json:"QTYPE,omitempty" xml:"QTYPE,omitempty" yaml:"QTYPE,omitempty" example:"IN"`
|
||||
TypeName string `json:"QTYPEname,omitempty" xml:"QTYPEname,omitempty" yaml:"QTYPEname,omitempty" example:"IN"`
|
||||
Class uint16 `json:"QCLASS,omitempty" xml:"QCLASS,omitempty" yaml:"QCLASS,omitempty" example:"A"`
|
||||
ClassName string `json:"QCLASSname,omitempty" xml:"QCLASSname,omitempty" yaml:"QCLASSname,omitempty" example:"1"`
|
||||
|
||||
EDNS0 EDNS0 `json:",omitempty" xml:",omitempty" yaml:",omitempty"`
|
||||
|
||||
// Answer Section
|
||||
AnswerRRs []Answer `json:"answersRRs,omitempty" xml:"answersRRs,omitempty" yaml:"answersRRs,omitempty" example:"false"`
|
||||
AuthoritativeRRs []Answer `json:"authorityRRs,omitempty" xml:"authorityRRs,omitempty" yaml:"authorityRRs,omitempty" example:"false"`
|
||||
AdditionalRRs []Answer `json:"additionalRRs,omitempty" xml:"additionalRRs,omitempty" yaml:"additionalRRs,omitempty" example:"false"`
|
||||
}
|
||||
|
||||
// Answer is for DNS Resource Headers.
|
||||
//
|
||||
//nolint:govet,tagliatelle
|
||||
type Answer struct {
|
||||
Name string `json:"NAME,omitempty" xml:"NAME,omitempty" yaml:"NAME,omitempty" example:"127.0.0.1"`
|
||||
Type uint16 `json:"TYPE,omitempty" xml:"TYPE,omitempty" yaml:"TYPE,omitempty" example:"1"`
|
||||
TypeName string `json:"TYPEname,omitempty" xml:"TYPEname,omitempty" yaml:"TYPEname,omitempty" example:"A"`
|
||||
Class uint16 `json:"CLASS,omitempty" xml:"CLASS,omitempty" yaml:"CLASS,omitempty" example:"1"`
|
||||
ClassName string `json:"CLASSname,omitempty" xml:"CLASSname,omitempty" yaml:"CLASSname,omitempty" example:"IN"`
|
||||
TTL any `json:"TTL,omitempty" xml:"TTL,omitempty" yaml:"TTL,omitempty" example:"0ms"`
|
||||
Value string `json:"rdata,omitempty" xml:"rdata,omitempty" yaml:"rdata,omitempty"`
|
||||
Rdlength uint16 `json:"RDLENGTH,omitempty" xml:"RDLENGTH,omitempty" yaml:"RDLENGTH,omitempty"`
|
||||
Rdhex string `json:"RDATAHEX,omitempty" xml:"RDATAHEX,omitempty" yaml:"RDATAHEX,omitempty"`
|
||||
}
|
||||
|
||||
// EDNS0 is for all EDNS options.
|
||||
//
|
||||
// RFC: https://datatracker.ietf.org/docs/draft-peltan-edns-presentation-format/
|
||||
//
|
||||
//nolint:govet,tagliatelle
|
||||
type EDNS0 struct {
|
||||
Flags []string `json:"FLAGS" xml:"FLAGS" yaml:"FLAGS"`
|
||||
Rcode string `json:"RCODE" xml:"RCODE" yaml:"RCODE"`
|
||||
PayloadSize uint16 `json:"UDPSIZE" xml:"UDPSIZE" yaml:"UDPSIZE"`
|
||||
LLQ *EdnsLLQ `json:"LLQ,omitempty" xml:"LLQ,omitempty" yaml:"LLQ,omitempty"`
|
||||
NsidHex string `json:"NSIDHEX,omitempty" xml:"NSIDHEX,omitempty" yaml:"NSIDHEX,omitempty"`
|
||||
Nsid string `json:"NSID,omitempty" xml:"NSID,omitempty" yaml:"NSID,omitempty"`
|
||||
Dau []uint8 `json:"DAU,omitempty" xml:"DAU,omitempty" yaml:"DAU,omitempty"`
|
||||
Dhu []uint8 `json:"DHU,omitempty" xml:"DHU,omitempty" yaml:"DHU,omitempty"`
|
||||
N3u []uint8 `json:"N3U,omitempty" xml:"N3U,omitempty" yaml:"N3U,omitempty"`
|
||||
Subnet *EDNSSubnet `json:"ECS,omitempty" xml:"ECS,omitempty" yaml:"ECS,omitempty"`
|
||||
Expire uint32 `json:"EXPIRE,omitempty" xml:"EXPIRE,omitempty" yaml:"EXPIRE,omitempty"`
|
||||
Cookie []string `json:"COOKIE,omitempty" xml:"COOKIE,omitempty" yaml:"COOKIE,omitempty"`
|
||||
KeepAlive uint16 `json:"KEEPALIVE,omitempty" xml:"KEEPALIVE,omitempty" yaml:"KEEPALIVE,omitempty"`
|
||||
Padding string `json:"PADDING,omitempty" xml:"PADDING,omitempty" yaml:"PADDING,omitempty"`
|
||||
Chain string `json:"CHAIN,omitempty" xml:"CHAIN,omitempty" yaml:"CHAIN,omitempty"`
|
||||
EDE *EDNSErr `json:"EDE,omitempty" xml:"EDE,omitempty" yaml:"EDE,omitempty"`
|
||||
}
|
||||
|
||||
// EdnsLLQ is for Long-lived queries.
|
||||
//
|
||||
//nolint:tagliatelle
|
||||
type EdnsLLQ struct {
|
||||
Version uint16 `json:"LLQ-VERSION" xml:"LLQ-VERSION" yaml:"LLQ-VERSION"`
|
||||
Opcode uint16 `json:"LLQ-OPCODE" xml:"LLQ-OPCODE" yaml:"LLQ-OPCODE"`
|
||||
Error uint16 `json:"LLQ-ERROR" xml:"LLQ-ERROR" yaml:"LLQ-ERROR"`
|
||||
ID uint64 `json:"LLQ-ID" xml:"LLQ-ID" yaml:"LLQ-ID"`
|
||||
Lease uint32 `json:"LLQ-LEASE" xml:"LLQ-LEASE" yaml:"LLQ-LEASE"`
|
||||
}
|
||||
|
||||
// EDNSSubnet is for EDNS subnet options,
|
||||
//
|
||||
//nolint:govet,tagliatelle
|
||||
type EDNSSubnet struct {
|
||||
Family uint16 `json:"FAMILY" xml:"FAMILY" yaml:"FAMILY"`
|
||||
IP string
|
||||
Source uint8 `json:"SOURCE" xml:"SOURCE" yaml:"SOURCE"`
|
||||
Scope uint8 `json:"SCOPE,omitempty" xml:"SCOPE,omitempty" yaml:"SCOPE,omitempty"`
|
||||
}
|
||||
|
||||
// EDNSErr is for EDE codes
|
||||
//
|
||||
//nolint:govet,tagliatelle
|
||||
type EDNSErr struct {
|
||||
Code uint16 `json:"INFO-CODE" xml:"INFO-CODE" yaml:"INFO-CODE"`
|
||||
Purpose string
|
||||
Text string `json:"EXTRA-TEXT,omitempty" xml:"EXTRA-TEXT,omitempty" yaml:"EXTRA-TEXT,omitempty"`
|
||||
}
|
||||
|
||||
var errNoMessage = errors.New("no message")
|
|
@ -0,0 +1,258 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
func (message *Message) displayQuestion(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
|
||||
var (
|
||||
name string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, question := range msg.Question {
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(question.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = question.Name
|
||||
}
|
||||
|
||||
message.Name = name
|
||||
message.Type = question.Qtype
|
||||
message.TypeName = dns.TypeToString[question.Qtype]
|
||||
message.Class = question.Qclass
|
||||
message.ClassName = dns.ClassToString[question.Qclass]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (message *Message) displayAnswers(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
|
||||
var (
|
||||
ttl any
|
||||
name string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, answer := range msg.Answer {
|
||||
temp := strings.Split(answer.String(), "\t")
|
||||
|
||||
if opts.Display.TTL {
|
||||
if opts.Display.HumanTTL {
|
||||
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = answer.Header().Ttl
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(answer.Header().Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = answer.Header().Name
|
||||
}
|
||||
|
||||
message.AnswerRRs = append(message.AnswerRRs, Answer{
|
||||
Name: name,
|
||||
ClassName: dns.ClassToString[answer.Header().Class],
|
||||
Class: answer.Header().Class,
|
||||
TypeName: dns.TypeToString[answer.Header().Rrtype],
|
||||
Type: answer.Header().Rrtype,
|
||||
Rdlength: answer.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (message *Message) displayAuthority(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
|
||||
var (
|
||||
ttl any
|
||||
name string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, ns := range msg.Ns {
|
||||
temp := strings.Split(ns.String(), "\t")
|
||||
|
||||
if opts.Display.TTL {
|
||||
if opts.Display.HumanTTL {
|
||||
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = ns.Header().Ttl
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(ns.Header().Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = ns.Header().Name
|
||||
}
|
||||
|
||||
message.AuthoritativeRRs = append(message.AuthoritativeRRs, Answer{
|
||||
Name: name,
|
||||
TypeName: dns.TypeToString[ns.Header().Rrtype],
|
||||
Type: ns.Header().Rrtype,
|
||||
Class: ns.Header().Class,
|
||||
ClassName: dns.ClassToString[ns.Header().Class],
|
||||
Rdlength: ns.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (message *Message) displayAdditional(msg *dns.Msg, opts *util.Options, opt *dns.OPT) error {
|
||||
var (
|
||||
ttl any
|
||||
name string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, additional := range msg.Extra {
|
||||
if additional.Header().Rrtype == dns.StringToType["OPT"] {
|
||||
continue
|
||||
} else {
|
||||
temp := strings.Split(additional.String(), "\t")
|
||||
|
||||
if opts.Display.TTL {
|
||||
if opts.Display.HumanTTL {
|
||||
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = additional.Header().Ttl
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(additional.Header().Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = additional.Header().Name
|
||||
}
|
||||
message.AdditionalRRs = append(message.AdditionalRRs, Answer{
|
||||
Name: name,
|
||||
TypeName: dns.TypeToString[additional.Header().Rrtype],
|
||||
Type: additional.Header().Rrtype,
|
||||
Class: additional.Header().Class,
|
||||
ClassName: dns.ClassToString[additional.Header().Class],
|
||||
Rdlength: additional.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseOpt parses opts.
|
||||
func (message *Message) ParseOpt(rcode int, rr dns.OPT) (ret EDNS0, err error) {
|
||||
ret.Rcode = dns.RcodeToString[rcode]
|
||||
|
||||
// Most of this is taken from https://github.com/miekg/dns/blob/master/edns.go#L76
|
||||
if rr.Do() {
|
||||
ret.Flags = append(ret.Flags, "DO")
|
||||
}
|
||||
|
||||
for i := uint32(1); i <= 0x7FFF; i <<= 1 {
|
||||
if rr.Hdr.Ttl&i != 0 {
|
||||
ret.Flags = append(ret.Flags, fmt.Sprintf("BIT%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
ret.PayloadSize = rr.UDPSize()
|
||||
|
||||
for _, opt := range rr.Option {
|
||||
switch opt := opt.(type) {
|
||||
case *dns.EDNS0_NSID:
|
||||
str := opt.String()
|
||||
|
||||
hex, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
ret.NsidHex = string(hex)
|
||||
ret.Nsid = str
|
||||
|
||||
case *dns.EDNS0_SUBNET:
|
||||
ret.Subnet = &EDNSSubnet{
|
||||
Source: opt.SourceNetmask,
|
||||
Family: opt.Family,
|
||||
}
|
||||
|
||||
// 1: IPv4 2: IPv6
|
||||
if ret.Subnet.Family <= 2 {
|
||||
ret.Subnet.IP = opt.Address.String()
|
||||
} else {
|
||||
ret.Subnet.IP = hex.EncodeToString([]byte(opt.Address))
|
||||
}
|
||||
|
||||
if opt.SourceScope != 0 {
|
||||
ret.Subnet.Scope = opt.SourceScope
|
||||
}
|
||||
|
||||
case *dns.EDNS0_COOKIE:
|
||||
ret.Cookie = append(ret.Cookie, opt.String())
|
||||
|
||||
case *dns.EDNS0_EXPIRE:
|
||||
ret.Expire = opt.Expire
|
||||
|
||||
case *dns.EDNS0_TCP_KEEPALIVE:
|
||||
ret.KeepAlive = opt.Timeout
|
||||
|
||||
case *dns.EDNS0_LLQ:
|
||||
ret.LLQ = &EdnsLLQ{
|
||||
Version: opt.Version,
|
||||
Opcode: opt.Opcode,
|
||||
Error: opt.Error,
|
||||
ID: opt.Id,
|
||||
Lease: opt.LeaseLife,
|
||||
}
|
||||
|
||||
case *dns.EDNS0_DAU:
|
||||
ret.Dau = opt.AlgCode
|
||||
|
||||
case *dns.EDNS0_DHU:
|
||||
ret.Dhu = opt.AlgCode
|
||||
|
||||
case *dns.EDNS0_N3U:
|
||||
ret.N3u = opt.AlgCode
|
||||
|
||||
case *dns.EDNS0_PADDING:
|
||||
ret.Padding = string(opt.Padding)
|
||||
|
||||
case *dns.EDNS0_EDE:
|
||||
ret.EDE = &EDNSErr{
|
||||
Code: opt.InfoCode,
|
||||
Purpose: dns.ExtendedErrorCodeToString[opt.InfoCode],
|
||||
Text: opt.ExtraText,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"dns.froth.zone/dnscrypt"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DNSCryptResolver is for making DNSCrypt queries.
|
||||
type DNSCryptResolver struct {
|
||||
opts *util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*DNSCryptResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (resolver *DNSCryptResolver) LookUp(msg *dns.Msg) (resp util.Response, err error) {
|
||||
client := dnscrypt.Client{
|
||||
Timeout: resolver.opts.Request.Timeout,
|
||||
UDPSize: 1232,
|
||||
}
|
||||
|
||||
if resolver.opts.TCP || resolver.opts.TLS {
|
||||
client.Net = tcp
|
||||
} else {
|
||||
client.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
case resolver.opts.IPv4:
|
||||
client.Net += "4"
|
||||
case resolver.opts.IPv6:
|
||||
client.Net += "6"
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("Using", client.Net, "for making the request")
|
||||
|
||||
resolverInf, err := client.Dial(resolver.opts.Request.Server)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("dnscrypt: dial: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
res, err := client.Exchange(msg, resolverInf)
|
||||
rtt := time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("dnscrypt: exchange: %w", err)
|
||||
}
|
||||
|
||||
resp = util.Response{
|
||||
DNS: res,
|
||||
RTT: rtt,
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Info("Request successful")
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"dns.froth.zone/dnscrypt"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestDNSCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"Valid",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Valid (TCP)",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Invalid",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "QMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil || errors.Is(err, dnscrypt.ErrInvalidDNSStamp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.ErrorContains(t, err, "unsupported stamp")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// HTTPSResolver is for DNS-over-HTTPS queries.
|
||||
type HTTPSResolver struct {
|
||||
opts *util.Options
|
||||
client http.Client
|
||||
}
|
||||
|
||||
var _ Resolver = (*HTTPSResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (resolver *HTTPSResolver) LookUp(msg *dns.Msg) (resp util.Response, err error) {
|
||||
resolver.client = http.Client{
|
||||
Timeout: resolver.opts.Request.Timeout,
|
||||
Transport: &http.Transport{
|
||||
MaxConnsPerHost: 1,
|
||||
MaxIdleConns: 1,
|
||||
MaxIdleConnsPerHost: 1,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: resolver.opts.TLSNoVerify,
|
||||
ServerName: resolver.opts.TLSHost,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doh: packing: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("https: sending HTTPS request")
|
||||
|
||||
var method string
|
||||
if resolver.opts.HTTPSOptions.Get {
|
||||
method = "GET"
|
||||
} else {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, resolver.opts.Request.Server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doh: request creation: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
now := time.Now()
|
||||
res, err := resolver.client.Do(req)
|
||||
resp.RTT = time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
// overwrite RTT or else tests will fail
|
||||
resp.RTT = 0
|
||||
|
||||
return resp, fmt.Errorf("doh: HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// overwrite RTT or else tests will fail
|
||||
resp.RTT = 0
|
||||
|
||||
return resp, &util.ErrHTTPStatus{Code: res.StatusCode}
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("https: reading response")
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doh: body read: %w", err)
|
||||
}
|
||||
|
||||
err = res.Body.Close()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doh: body close: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("https: unpacking response")
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doh: dns message unpack: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"Good",
|
||||
&util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"404",
|
||||
&util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Bad request domain",
|
||||
&util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns9.quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Bad server domain",
|
||||
&util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns9..quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil || errors.Is(err, &util.ErrHTTPStatus{}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
if errors.Is(err, &util.ErrHTTPStatus{}) {
|
||||
assert.ErrorContains(t, err, "404")
|
||||
}
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// QUICResolver is for DNS-over-QUIC queries.
|
||||
type QUICResolver struct {
|
||||
opts *util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*QUICResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (resolver *QUICResolver) LookUp(msg *dns.Msg) (resp util.Response, err error) {
|
||||
tls := &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: resolver.opts.TLSNoVerify,
|
||||
ServerName: resolver.opts.TLSHost,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
|
||||
// Make sure that TLSHost is ALWAYS set
|
||||
if resolver.opts.TLSHost == "" {
|
||||
tls.ServerName = strings.Split(resolver.opts.Request.Server, ":")[0]
|
||||
}
|
||||
|
||||
conf := new(quic.Config)
|
||||
conf.HandshakeIdleTimeout = resolver.opts.Request.Timeout
|
||||
|
||||
resolver.opts.Logger.Debug("quic: making query")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.opts.Request.Timeout)
|
||||
defer cancel()
|
||||
|
||||
connection, err := quic.DialAddr(ctx, resolver.opts.Request.Server, tls, conf)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: dial: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("quic: packing query")
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: pack: %w", err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
|
||||
resolver.opts.Logger.Debug("quic: creating stream")
|
||||
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: quic stream creation: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("quic: writing to stream")
|
||||
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: quic stream write: %w", err)
|
||||
}
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: quic stream close: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("quic: reading stream")
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: quic stream read: %w", err)
|
||||
}
|
||||
|
||||
resp.RTT = time.Since(t)
|
||||
|
||||
resolver.opts.Logger.Debug("quic: closing connection")
|
||||
// Close with error: no error
|
||||
err = connection.CloseWithError(0, "")
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: quic connection close: %w", err)
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Debug("quic: closing stream")
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
|
||||
resolver.opts.Logger.Debug("quic: unpacking response")
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("doq: unpack: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !gccgo
|
||||
|
||||
package resolvers_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"Valid, AdGuard",
|
||||
&util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns.adguard.com",
|
||||
Type: dns.TypeNS,
|
||||
Port: 853,
|
||||
Timeout: 750 * time.Millisecond,
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Valid, Froth",
|
||||
&util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns.froth.zone",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.freecumextremist.com",
|
||||
Port: 853,
|
||||
Timeout: 750 * time.Millisecond,
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Bad domain",
|
||||
&util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns.//./,,adguard\a.com",
|
||||
Port: 853,
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone",
|
||||
Timeout: 100 * time.Millisecond,
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Not canonical",
|
||||
&util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "dns.adguard.com",
|
||||
Port: 853,
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone",
|
||||
Timeout: 100 * time.Millisecond,
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Invalid query domain",
|
||||
&util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "example.com",
|
||||
Port: 853,
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone",
|
||||
Timeout: 10 * time.Millisecond,
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.Assert(t, res == util.Response{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package resolvers contain the various DNS resolvers to use.
|
||||
*/
|
||||
package resolvers
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// StandardResolver is for UDP/TCP resolvers.
|
||||
type StandardResolver struct {
|
||||
opts *util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*StandardResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (resolver *StandardResolver) LookUp(msg *dns.Msg) (resp util.Response, err error) {
|
||||
dnsClient := new(dns.Client)
|
||||
dnsClient.Dialer = &net.Dialer{
|
||||
Timeout: resolver.opts.Request.Timeout,
|
||||
}
|
||||
|
||||
if resolver.opts.TCP || resolver.opts.TLS {
|
||||
dnsClient.Net = tcp
|
||||
} else {
|
||||
dnsClient.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
case resolver.opts.IPv4:
|
||||
dnsClient.Net += "4"
|
||||
case resolver.opts.IPv6:
|
||||
dnsClient.Net += "6"
|
||||
}
|
||||
|
||||
if resolver.opts.TLS {
|
||||
dnsClient.Net += "-tls"
|
||||
dnsClient.TLSConfig = &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: resolver.opts.TLSNoVerify,
|
||||
ServerName: resolver.opts.TLSHost,
|
||||
}
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Info("Using", dnsClient.Net, "for making the request")
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, resolver.opts.Request.Server)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
switch dns.RcodeToString[resp.DNS.MsgHdr.Rcode] {
|
||||
case "BADCOOKIE":
|
||||
if !resolver.opts.BadCookie {
|
||||
fmt.Printf(";; BADCOOKIE, retrying.\n\n")
|
||||
|
||||
msg.Extra = resp.DNS.Extra
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, resolver.opts.Request.Server)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("badcookie: DNS exchange: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case "NOERR":
|
||||
break
|
||||
}
|
||||
|
||||
resolver.opts.Logger.Info("Request successful")
|
||||
|
||||
if resp.DNS.MsgHdr.Truncated && !resolver.opts.Truncate {
|
||||
fmt.Printf(";; Truncated, retrying with TCP\n\n")
|
||||
|
||||
dnsClient.Net = tcp
|
||||
|
||||
switch {
|
||||
case resolver.opts.IPv4:
|
||||
dnsClient.Net += "4"
|
||||
case resolver.opts.IPv6:
|
||||
dnsClient.Net += "6"
|
||||
}
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, resolver.opts.Request.Server)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"dns.froth.zone/dnscrypt"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"UDP",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"UDP (Bad Cookie)",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
BadCookie: false,
|
||||
Request: util.Request{
|
||||
Server: "b.root-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.TypeNS,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"UDP (Truncated)",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "madns.binarystar.systems",
|
||||
Port: 5301,
|
||||
Type: dns.TypeTXT,
|
||||
Name: "limit.txt.example.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"TCP",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"TLS",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Timeout",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.1",
|
||||
Port: 1,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Timeout: time.Millisecond * 100,
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil || errors.Is(err, dnscrypt.ErrInvalidDNSStamp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,23 +1,28 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
tcp = "tcp"
|
||||
udp = "udp"
|
||||
)
|
||||
|
||||
// Resolver is the main resolver interface.
|
||||
type Resolver interface {
|
||||
LookUp(*dns.Msg) (util.Response, error)
|
||||
}
|
||||
|
||||
// LoadResolver loads the respective resolver for performing a DNS query.
|
||||
func LoadResolver(opts util.Options) (Resolver, error) {
|
||||
func LoadResolver(opts *util.Options) (resolver Resolver, err error) {
|
||||
switch {
|
||||
case opts.HTTPS:
|
||||
opts.Logger.Info("loading DNS-over-HTTPS resolver")
|
||||
|
@ -26,16 +31,28 @@ func LoadResolver(opts util.Options) (Resolver, error) {
|
|||
opts.Request.Server = "https://" + opts.Request.Server
|
||||
}
|
||||
|
||||
return &HTTPSResolver{
|
||||
// Make sure that the endpoint is defaulted to /dns-query
|
||||
if !strings.HasSuffix(opts.Request.Server, opts.HTTPSOptions.Endpoint) {
|
||||
opts.Request.Server += opts.HTTPSOptions.Endpoint
|
||||
}
|
||||
|
||||
resolver = &HTTPSResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
case opts.QUIC:
|
||||
opts.Logger.Info("loading DNS-over-QUIC resolver")
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
return &QUICResolver{
|
||||
if !strings.HasSuffix(opts.Request.Server, ":"+strconv.Itoa(opts.Request.Port)) {
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
|
||||
}
|
||||
|
||||
resolver = &QUICResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
case opts.DNSCrypt:
|
||||
opts.Logger.Info("loading DNSCrypt resolver")
|
||||
|
||||
|
@ -43,15 +60,22 @@ func LoadResolver(opts util.Options) (Resolver, error) {
|
|||
opts.Request.Server = "sdns://" + opts.Request.Server
|
||||
}
|
||||
|
||||
return &DNSCryptResolver{
|
||||
resolver = &DNSCryptResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
default:
|
||||
opts.Logger.Info("loading standard/DNS-over-TLS resolver")
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
return &StandardResolver{
|
||||
if !strings.HasSuffix(opts.Request.Server, ":"+strconv.Itoa(opts.Request.Port)) {
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
|
||||
}
|
||||
|
||||
resolver = &StandardResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrHTTPStatus is returned when DoH returns a bad status code.
|
||||
type ErrHTTPStatus struct {
|
||||
// Status code
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e *ErrHTTPStatus) Error() string {
|
||||
return fmt.Sprintf("doh server responded with HTTP %d", e.Code)
|
||||
}
|
||||
|
||||
// ErrNotError is an error that is not actually an error.
|
||||
var ErrNotError = errors.New("not an error")
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package util
|
||||
|
||||
import "git.froth.zone/sam/awl/logawl"
|
||||
import "dns.froth.zone/awl/pkg/logawl"
|
||||
|
||||
// InitLogger initializes the logawl instance.
|
||||
func InitLogger(verbosity int) (log *logawl.Logger) {
|
|
@ -5,8 +5,8 @@ package util_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Options is the grand structure for all query options.
|
||||
type Options struct {
|
||||
// The logger
|
||||
Logger *logawl.Logger `json:"-"`
|
||||
// Host to verify TLS cert with
|
||||
TLSHost string `json:"tlsHost" example:""`
|
||||
// EDNS Options
|
||||
EDNS
|
||||
|
||||
// HTTPS options :)
|
||||
HTTPSOptions
|
||||
|
||||
// DNS request :)
|
||||
Request
|
||||
|
||||
// Verbosity levels, see [logawl.AllLevels]
|
||||
Verbosity int `json:"-" example:"0"`
|
||||
// Display options
|
||||
Display Display
|
||||
// Ignore Truncation
|
||||
Truncate bool `json:"ignoreTruncate" example:"false"`
|
||||
// Ignore BADCOOKIE
|
||||
BadCookie bool `json:"ignoreBadCookie" example:"false"`
|
||||
// Print only the answer
|
||||
Short bool `json:"short" example:"false"`
|
||||
// When Short is true, display where the query came from
|
||||
Identify bool `json:"identify" example:"false"`
|
||||
// Perform a reverse DNS query when true
|
||||
Reverse bool `json:"reverse" example:"false"`
|
||||
|
||||
HeaderFlags
|
||||
|
||||
// Display resposne as JSON
|
||||
JSON bool `json:"-" xml:"-" yaml:"-"`
|
||||
// Display response as XML
|
||||
XML bool `json:"-" xml:"-" yaml:"-"`
|
||||
// Display response as YAML
|
||||
YAML bool `json:"-" xml:"-" yaml:"-"`
|
||||
|
||||
// Use TCP instead of UDP to make the query
|
||||
TCP bool `json:"tcp" example:"false"`
|
||||
// Use DNS-over-TLS to make the query
|
||||
TLS bool `json:"dnsOverTLS" example:"false"`
|
||||
// When using TLS, ignore certificates
|
||||
TLSNoVerify bool `json:"tlsNoVerify" example:"false"`
|
||||
// Use DNS-over-HTTPS to make the query
|
||||
HTTPS bool `json:"dnsOverHTTPS" example:"false"`
|
||||
// Use DNS-over-QUIC to make the query
|
||||
//nolint:tagliatelle // QUIC is an acronym
|
||||
QUIC bool `json:"dnsOverQUIC" example:"false"`
|
||||
// Use DNSCrypt to make the query
|
||||
DNSCrypt bool `json:"dnscrypt" example:"false"`
|
||||
|
||||
// Force IPv4 only
|
||||
IPv4 bool `json:"forceIPv4" example:"false"`
|
||||
// Force IPv6 only
|
||||
IPv6 bool `json:"forceIPv6" example:"false"`
|
||||
|
||||
// Trace from the root
|
||||
Trace bool `json:"trace" example:"false"`
|
||||
}
|
||||
|
||||
// HTTPSOptions are options exclusively for DNS-over-HTTPS queries.
|
||||
type HTTPSOptions struct {
|
||||
// URL endpoint
|
||||
Endpoint string `json:"endpoint" example:"/dns-query"`
|
||||
|
||||
// True, make GET request.
|
||||
// False, make POST request.
|
||||
Get bool `json:"get" example:"false"`
|
||||
}
|
||||
|
||||
// HeaderFlags are the flags that are in DNS headers.
|
||||
type HeaderFlags struct {
|
||||
// Authoritative Answer DNS query flag
|
||||
AA bool `json:"authoritative" example:"false"`
|
||||
// Authenticated Data DNS query flag
|
||||
AD bool `json:"authenticatedData" example:"false"`
|
||||
// Checking Disabled DNS query flag
|
||||
CD bool `json:"checkingDisabled" example:"false"`
|
||||
// QueRy DNS query flag
|
||||
QR bool `json:"query" example:"false"`
|
||||
// Recursion Desired DNS query flag
|
||||
RD bool `json:"recursionDesired" example:"true"`
|
||||
// Recursion Available DNS query flag
|
||||
RA bool `json:"recursionAvailable" example:"false"`
|
||||
// TrunCated DNS query flag
|
||||
TC bool `json:"truncated" example:"false"`
|
||||
// Zero DNS query flag
|
||||
Z bool `json:"zero" example:"false"`
|
||||
}
|
||||
|
||||
// Display contains toggles for what to (and not to) display.
|
||||
type Display struct {
|
||||
/* Section displaying */
|
||||
|
||||
// Comments?
|
||||
Comments bool `json:"comments" example:"true"`
|
||||
// QUESTION SECTION
|
||||
Question bool `json:"question" example:"true"`
|
||||
// OPT PSEUDOSECTION
|
||||
Opt bool `json:"opt" example:"true"`
|
||||
// ANSWER SECTION
|
||||
Answer bool `json:"answer" example:"true"`
|
||||
// AUTHORITY SECTION
|
||||
Authority bool `json:"authority" example:"true"`
|
||||
// ADDITIONAL SECTION
|
||||
Additional bool `json:"additional" example:"true"`
|
||||
// Query time, message size, etc.
|
||||
Statistics bool `json:"statistics" example:"true"`
|
||||
// Display TTL in response
|
||||
TTL bool `json:"ttl" example:"true"`
|
||||
|
||||
/* Answer formatting */
|
||||
|
||||
// Display Class in response
|
||||
ShowClass bool `json:"showClass" example:"true"`
|
||||
// Display query before it is sent
|
||||
ShowQuery bool `json:"showQuery" example:"false"`
|
||||
// Display TTL as human-readable
|
||||
HumanTTL bool `json:"humanTTL" example:"false"`
|
||||
// Translate Punycode back to Unicode
|
||||
UcodeTranslate bool `json:"unicode" example:"true"`
|
||||
}
|
||||
|
||||
// EDNS contains toggles for various EDNS options.
|
||||
type EDNS struct {
|
||||
// Subnet to originate query from.
|
||||
Subnet dns.EDNS0_SUBNET `json:"subnet"`
|
||||
// Must Be Zero flag
|
||||
ZFlag uint16 `json:"zflag" example:"0"`
|
||||
// UDP buffer size
|
||||
BufSize uint16 `json:"bufSize" example:"1232"`
|
||||
// Enable/Disable EDNS entirely
|
||||
EnableEDNS bool `json:"edns" example:"false"`
|
||||
// Sending EDNS cookie
|
||||
Cookie bool `json:"cookie" example:"true"`
|
||||
// Enabling DNSSEC
|
||||
DNSSEC bool `json:"dnssec" example:"false"`
|
||||
// Sending EDNS Expire
|
||||
Expire bool `json:"expire" example:"false"`
|
||||
// Sending EDNS TCP keepopen
|
||||
KeepOpen bool `json:"keepOpen" example:"false"`
|
||||
// Sending EDNS NSID
|
||||
Nsid bool `json:"nsid" example:"false"`
|
||||
// Send EDNS Padding
|
||||
Padding bool `json:"padding" example:"false"`
|
||||
// Set EDNS version (default: 0)
|
||||
Version uint8 `json:"version" example:"0"`
|
||||
}
|
||||
|
||||
// ParseSubnet takes a subnet argument and makes it into one that the DNS library
|
||||
// understands.
|
||||
func ParseSubnet(subnet string, opts *Options) error {
|
||||
ip, inet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
// TODO: make not a default?
|
||||
if subnet == "0" {
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{
|
||||
Code: dns.EDNS0SUBNET,
|
||||
Family: 1,
|
||||
SourceNetmask: 0,
|
||||
SourceScope: 0,
|
||||
Address: net.IPv4(0, 0, 0, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("EDNS subnet parsing: %w", err)
|
||||
}
|
||||
|
||||
sub, _ := inet.Mask.Size()
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{}
|
||||
opts.EDNS.Subnet.Address = ip
|
||||
opts.EDNS.Subnet.SourceNetmask = uint8(sub)
|
||||
|
||||
switch ip.To4() {
|
||||
case nil:
|
||||
// Not a valid IPv4 so assume IPv6
|
||||
opts.EDNS.Subnet.Family = 2
|
||||
default:
|
||||
// Valid IPv4
|
||||
opts.EDNS.Subnet.Family = 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,7 +5,7 @@ package util_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
|
@ -17,21 +17,20 @@ func TestSubnet(t *testing.T) {
|
|||
"::0/0",
|
||||
"0",
|
||||
"127.0.0.1/32",
|
||||
"Invalid",
|
||||
}
|
||||
|
||||
for _, test := range subnet {
|
||||
test := test
|
||||
|
||||
t.Run(test, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := util.ParseSubnet(test, new(util.Options))
|
||||
assert.NilError(t, err)
|
||||
if err != nil {
|
||||
assert.ErrorContains(t, err, "invalid CIDR address")
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSub(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := util.ParseSubnet("1", new(util.Options))
|
||||
assert.ErrorContains(t, err, "invalid CIDR address")
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Response is the DNS response.
|
||||
type Response struct {
|
||||
// The full DNS response
|
||||
DNS *dns.Msg `json:"response"`
|
||||
// The time it took to make the DNS query
|
||||
RTT time.Duration `json:"rtt" example:"2000000000"`
|
||||
}
|
||||
|
||||
// Request is a structure for a DNS query.
|
||||
type Request struct {
|
||||
// Server to query
|
||||
Server string `json:"server" example:"1.0.0.1"`
|
||||
// Domain to query
|
||||
Name string `json:"name" example:"example.com"`
|
||||
// Duration to wait until marking request as failed
|
||||
Timeout time.Duration `json:"timeout" example:"2000000000"`
|
||||
// Port to make DNS request on
|
||||
Port int `json:"port" example:"53"`
|
||||
// Number of failures to make before giving up
|
||||
Retries int `json:"retries" example:"2"`
|
||||
// Request type, eg. A, AAAA, NAPTR
|
||||
Type uint16 `json:"type" example:"1"`
|
||||
// Request class, eg. IN
|
||||
Class uint16 `json:"class" example:"1"`
|
||||
}
|
|
@ -13,8 +13,8 @@ type errReverseDNS struct {
|
|||
addr string
|
||||
}
|
||||
|
||||
func (e *errReverseDNS) Error() string {
|
||||
return fmt.Sprintf("reverseDNS: invalid value %s given", e.addr)
|
||||
func (errDNS *errReverseDNS) Error() string {
|
||||
return fmt.Sprintf("reverseDNS: invalid value %s given", errDNS.addr)
|
||||
}
|
||||
|
||||
// ReverseDNS is given an IP or phone number and returns a canonical string to be queried.
|
|
@ -0,0 +1,79 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"IPv4",
|
||||
"8.8.4.4", "4.4.8.8.in-addr.arpa.",
|
||||
},
|
||||
{
|
||||
"IPv6",
|
||||
"2606:4700:4700::1111", "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa.",
|
||||
},
|
||||
{
|
||||
"Inavlid value",
|
||||
"AAAAA", "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := util.ReverseDNS(test.in, dns.StringToType["PTR"])
|
||||
if err == nil {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, "unrecognized address")
|
||||
}
|
||||
assert.Equal(t, act, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNAPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"1-800-555-1234", "4.3.2.1.5.5.5.0.0.8.1.e164.arpa."},
|
||||
{"+1 800555 1234", "4.3.2.1.5.5.5.0.0.8.1.e164.arpa."},
|
||||
{"+46766861004", "4.0.0.1.6.8.6.6.7.6.4.e164.arpa."},
|
||||
{"17705551212", "2.1.2.1.5.5.5.0.7.7.1.e164.arpa."},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Thanks Goroutines, very cool!
|
||||
test := test
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
act, err := util.ReverseDNS(test.in, dns.StringToType["NAPTR"])
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, test.want, act)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidAll(t *testing.T) {
|
||||
_, err := util.ReverseDNS("q", 15236)
|
||||
assert.ErrorContains(t, err, "invalid value")
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DNSCryptResolver is for making DNSCrypt queries.
|
||||
type DNSCryptResolver struct {
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*DNSCryptResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
client := dnscrypt.Client{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
UDPSize: 1232,
|
||||
}
|
||||
|
||||
if r.opts.TCP || r.opts.TLS {
|
||||
client.Net = tcp
|
||||
} else {
|
||||
client.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
case r.opts.IPv4:
|
||||
client.Net += "4"
|
||||
case r.opts.IPv6:
|
||||
client.Net += "6"
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("Using", client.Net, "for making the request")
|
||||
|
||||
resolverInf, err := client.Dial(r.opts.Request.Server)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("dnscrypt: dial: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
res, err := client.Exchange(msg, resolverInf)
|
||||
rtt := time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("dnscrypt: exchange: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Info("Request successful")
|
||||
|
||||
return util.Response{
|
||||
DNS: res,
|
||||
RTT: rtt,
|
||||
}, nil
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestDNSCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
opt util.Options
|
||||
}{
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "QMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := query.CreateQuery(test.opt)
|
||||
if err == nil {
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.ErrorContains(t, err, "unsupported stamp")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// HTTPSResolver is for DNS-over-HTTPS queries.
|
||||
type HTTPSResolver struct {
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*HTTPSResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var resp util.Response
|
||||
|
||||
httpR := &http.Client{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
}
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: packing: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("https: sending HTTPS request")
|
||||
|
||||
req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: request creation: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
now := time.Now()
|
||||
res, err := httpR.Do(req)
|
||||
resp.RTT = time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return util.Response{}, &errHTTPStatus{res.StatusCode}
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("https: reading response")
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: body read: %w", err)
|
||||
}
|
||||
|
||||
err = res.Body.Close()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: body close: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("https: unpacking response")
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: dns message unpack: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type errHTTPStatus struct {
|
||||
code int
|
||||
}
|
||||
|
||||
func (e *errHTTPStatus) Error() string {
|
||||
return fmt.Sprintf("doh server responded with HTTP %d", e.code)
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := util.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
|
||||
func Test2ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."},
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
testCase := util.Request{Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "fully qualified")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
||||
func Test3ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."},
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// testCase :=
|
||||
// if the domain is not canonical, make it canonical
|
||||
// if !strings.HasSuffix(testCase.Name, ".") {
|
||||
// testCase.Name = fmt.Sprintf("%s.", testCase.Name)
|
||||
// }
|
||||
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
// msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "doh: HTTP request")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
||||
func Test404ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := util.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "404")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
102
query/QUIC.go
102
query/QUIC.go
|
@ -1,102 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// QUICResolver is for DNS-over-QUIC queries.
|
||||
type QUICResolver struct {
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*QUICResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *QUICResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var resp util.Response
|
||||
|
||||
tls := &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: r.opts.TLSNoVerify,
|
||||
ServerName: r.opts.TLSHost,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
|
||||
conf := new(quic.Config)
|
||||
conf.HandshakeIdleTimeout = r.opts.Request.Timeout
|
||||
|
||||
r.opts.Logger.Debug("quic: making query")
|
||||
|
||||
connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: dial: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: packing query")
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: pack: %w", err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
|
||||
r.opts.Logger.Debug("quic: creating stream")
|
||||
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream creation: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: writing to stream")
|
||||
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream write: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: reading stream")
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream read: %w", err)
|
||||
}
|
||||
|
||||
resp.RTT = time.Since(t)
|
||||
|
||||
r.opts.Logger.Debug("quic: closing connection")
|
||||
// Close with error: no error
|
||||
err = connection.CloseWithError(0, "")
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: quic connection close: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: closing stream")
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream close: %w", err)
|
||||
}
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
|
||||
r.opts.Logger.Debug("quic: unpacking response")
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doq: unpack: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: util.Request{Server: "dns.adguard.com"},
|
||||
}
|
||||
testCase := util.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
testCase2 := util.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
|
||||
var testCases []util.Request
|
||||
|
||||
testCases = append(testCases, testCase)
|
||||
testCases = append(testCases, testCase2)
|
||||
|
||||
for i := range testCases {
|
||||
switch i {
|
||||
case 0:
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(testCase.Name, ".") {
|
||||
testCases[i].Name = fmt.Sprintf("%s.", testCases[i].Name)
|
||||
}
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.ErrorContains(t, err, "fully qualified")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
case 1:
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(testCase2.Name, ".") {
|
||||
testCase2.Name = fmt.Sprintf("%s.", testCase2.Name)
|
||||
}
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase2.Name, testCase2.Type)
|
||||
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: util.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
|
||||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "timeout")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// StandardResolver is for UDP/TCP resolvers.
|
||||
type StandardResolver struct {
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*StandardResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *StandardResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var (
|
||||
resp util.Response
|
||||
err error
|
||||
)
|
||||
|
||||
dnsClient := new(dns.Client)
|
||||
dnsClient.Dialer = &net.Dialer{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
}
|
||||
|
||||
if r.opts.TCP || r.opts.TLS {
|
||||
dnsClient.Net = tcp
|
||||
} else {
|
||||
dnsClient.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
case r.opts.IPv4:
|
||||
dnsClient.Net += "4"
|
||||
case r.opts.IPv6:
|
||||
dnsClient.Net += "6"
|
||||
}
|
||||
|
||||
if r.opts.TLS {
|
||||
dnsClient.Net += "-tls"
|
||||
dnsClient.TLSConfig = &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: r.opts.TLSNoVerify,
|
||||
ServerName: r.opts.TLSHost,
|
||||
}
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("Using", dnsClient.Net, "for making the request")
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Info("Request successful")
|
||||
|
||||
if resp.DNS.MsgHdr.Truncated && !r.opts.Truncate {
|
||||
fmt.Printf(";; Truncated, retrying with TCP\n\n")
|
||||
|
||||
dnsClient.Net = tcp
|
||||
|
||||
switch {
|
||||
case r.opts.IPv4:
|
||||
dnsClient.Net += "4"
|
||||
case r.opts.IPv6:
|
||||
dnsClient.Net += "6"
|
||||
}
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.1",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Timeout: time.Second / 2,
|
||||
Retries: 0,
|
||||
},
|
||||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
|
||||
_, err = resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "timeout")
|
||||
}
|
||||
|
||||
// FIXME: See when this is fixed and fix it.
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
t.Skip("Sadly this is broken.")
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
IPv4: true,
|
||||
Port: 5301,
|
||||
Request: util.Request{
|
||||
Server: "madns.binarystar.systems",
|
||||
Type: dns.TypeTXT,
|
||||
Name: "limit.txt.example.",
|
||||
},
|
||||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
|
||||
func TestResolveAgain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
opt util.Options
|
||||
}{
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
Port: 53,
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
Port: 853,
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := query.CreateQuery(test.opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
})
|
||||
}
|
||||
}
|
197
query/print.go
197
query/print.go
|
@ -1,197 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// PrintSpecial is for printing as JSON, XML or YAML.
|
||||
// As of now JSON and XML use the stdlib version.
|
||||
func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
|
||||
formatted, err := MakePrintable(msg, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.JSON:
|
||||
opts.Logger.Info("Printing as JSON")
|
||||
|
||||
json, err := json.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(json), err
|
||||
case opts.XML:
|
||||
opts.Logger.Info("Printing as XML")
|
||||
|
||||
xml, err := xml.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(xml), err
|
||||
case opts.YAML:
|
||||
opts.Logger.Info("Printing as YAML")
|
||||
|
||||
yaml, err := yaml.Marshal(formatted)
|
||||
|
||||
return string(yaml), err
|
||||
default:
|
||||
return "", errInvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
// MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML,
|
||||
// and XML. Little is changed beyond naming.
|
||||
func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
|
||||
var err error
|
||||
|
||||
ret := Message{
|
||||
Header: msg.MsgHdr,
|
||||
}
|
||||
|
||||
for _, question := range msg.Question {
|
||||
var name string
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(question.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = question.Name
|
||||
}
|
||||
|
||||
ret.Question = append(ret.Question, Question{
|
||||
Name: name,
|
||||
Type: dns.TypeToString[question.Qtype],
|
||||
Class: dns.ClassToString[question.Qclass],
|
||||
})
|
||||
}
|
||||
|
||||
for _, answer := range msg.Answer {
|
||||
temp := strings.Split(answer.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = strconv.Itoa(int(answer.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(answer.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = answer.Header().Name
|
||||
}
|
||||
|
||||
ret.Answer = append(ret.Answer, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
Type: dns.TypeToString[answer.Header().Rrtype],
|
||||
Class: dns.ClassToString[answer.Header().Class],
|
||||
Rdlength: answer.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
},
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
|
||||
for _, ns := range msg.Ns {
|
||||
temp := strings.Split(ns.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = strconv.Itoa(int(ns.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(ns.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = ns.Header().Name
|
||||
}
|
||||
|
||||
ret.Ns = append(ret.Ns, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
Type: dns.TypeToString[ns.Header().Rrtype],
|
||||
Class: dns.ClassToString[ns.Header().Class],
|
||||
Rdlength: ns.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
},
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
|
||||
for _, additional := range msg.Extra {
|
||||
if additional.Header().Rrtype == dns.StringToType["OPT"] {
|
||||
continue
|
||||
} else {
|
||||
temp := strings.Split(additional.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
|
||||
} else {
|
||||
ttl = strconv.Itoa(int(additional.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(additional.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = additional.Header().Name
|
||||
}
|
||||
|
||||
ret.Extra = append(ret.Extra, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
Type: dns.TypeToString[additional.Header().Rrtype],
|
||||
Class: dns.ClassToString[additional.Header().Class],
|
||||
Rdlength: additional.Header().Rdlength,
|
||||
TTL: ttl,
|
||||
},
|
||||
Value: temp[len(temp)-1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
var errInvalidFormat = errors.New("this should never happen")
|
|
@ -1,122 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCreateQ(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
in := []util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Z: true,
|
||||
ShowQuery: true,
|
||||
YAML: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
DNSSEC: true,
|
||||
Cookie: true,
|
||||
Expire: true,
|
||||
KeepOpen: true,
|
||||
Nsid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Z: true,
|
||||
ShowQuery: true,
|
||||
XML: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
// Z: true,
|
||||
ShowQuery: true,
|
||||
JSON: true,
|
||||
QUIC: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "dns.adguard.com",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
DNSSEC: true,
|
||||
Cookie: true,
|
||||
Expire: true,
|
||||
Nsid: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range in {
|
||||
opt := opt
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := query.CreateQuery(opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
|
||||
str, err := query.PrintSpecial(res.DNS, opt)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str, err = query.ToString(res, opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base", ":npm", ":gomod"]
|
||||
"extends": ["config:base", ":npm", ":gomod"],
|
||||
"automerge": true
|
||||
}
|
||||
|
|
48
template.mk
48
template.mk
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Template for the BSD/GNU makefiles
|
||||
|
||||
HASH ?= `git describe --always --dirty --broken | sed 's/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
|
||||
HASH ?= `git describe --tags --always --dirty --broken | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
|
||||
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*')
|
||||
TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f)
|
||||
|
@ -10,7 +10,7 @@ CGO_ENABLED ?= 0
|
|||
GO ?= go
|
||||
TEST ?= $(GO) test -race -cover
|
||||
COVER ?= $(GO) tool cover
|
||||
GOFLAGS ?= -ldflags="-s -w -X=main.version=$(HASH)" -trimpath
|
||||
GOFLAGS ?= -trimpath -ldflags="-s -w -X=main.version=$(HASH)"
|
||||
DESTDIR :=
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
|
@ -23,19 +23,19 @@ MAN ?= $(PREFIX)/$(SHARE)/man
|
|||
PROG ?= awl
|
||||
|
||||
# hehe
|
||||
all: $(PROG) doc/$(PROG).1
|
||||
all: $(PROG) docs/$(PROG).1
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(GO) build -o $(EXE) $(GOFLAGS) .
|
||||
|
||||
doc/$(PROG).1: doc/$(PROG).1.scd
|
||||
$(SCDOC) <$< >$@
|
||||
docs/$(PROG).1: docs/$(PROG).1.scd
|
||||
$(SCDOC) <$? >$@
|
||||
|
||||
doc/wiki/$(PROG).1.md: doc/$(PROG).1
|
||||
pandoc --from man --to gfm -o $@ $<
|
||||
docs/wiki/$(PROG).1.md: docs/$(PROG).1
|
||||
pandoc --from man --to gfm -o $@ $?
|
||||
|
||||
## update_doc: update documentation (requires pandoc)
|
||||
update_doc: doc/wiki/$(PROG).1.md
|
||||
update_doc: docs/wiki/$(PROG).1.md
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
|
@ -50,9 +50,12 @@ vet:
|
|||
lint: fmt vet
|
||||
golangci-lint run --fix
|
||||
|
||||
coverage/coverage.out: $(TEST_SOURCES)
|
||||
$(TEST) -coverprofile=$@ ./...
|
||||
|
||||
.PHONY: test
|
||||
## test: run go test
|
||||
test: $(TEST_SOURCES)
|
||||
$(TEST) -v -coverprofile=coverage/coverage.out ./...
|
||||
test: coverage/coverage.out
|
||||
|
||||
.PHONY: test-ci
|
||||
test-ci:
|
||||
|
@ -60,29 +63,36 @@ test-ci:
|
|||
|
||||
## fuzz: runs fuzz tests
|
||||
fuzz: $(TEST_SOURCES)
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 10000x ./cli
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 10000x ./cli
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 5000x ./cli
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 10000x ./cmd
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 10000x ./cmd
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 10000x ./cmd
|
||||
|
||||
fuzz-ci: $(TEST_SOURCES)
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 1000x ./cmd
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 1000x ./cmd
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 1000x ./cmd
|
||||
|
||||
.PHONY: full_test
|
||||
full_test: test fuzz
|
||||
|
||||
coverage/coverage.out: test
|
||||
$(COVER) -func=$@
|
||||
$(COVER) -html=$@ -o coverage/cover.html
|
||||
coverage/cover.html: coverage/coverage.out
|
||||
$(COVER) -func=$?
|
||||
$(COVER) -html=$? -o $@
|
||||
|
||||
## cover: generates test coverage, output as HTML
|
||||
cover: coverage/coverage.out
|
||||
cover: coverage/cover.html
|
||||
|
||||
## clean: clean the build files
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(GO) clean
|
||||
# Ignore errors if you remove something that doesn't exist
|
||||
rm -f doc/$(PROG).1
|
||||
rm -f docs/$(PROG).1
|
||||
rm -f coverage/cover*
|
||||
rm -rf vendor
|
||||
|
||||
## help: Prints this help message
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Usage: "
|
||||
@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'
|
||||
@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'
|
||||
|
|
114
util/options.go
114
util/options.go
|
@ -1,114 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Options is the grand CLI options structure.
|
||||
type Options struct {
|
||||
Logger *logawl.Logger
|
||||
TLSHost string
|
||||
EDNS
|
||||
Request Request
|
||||
Port int
|
||||
Verbosity int
|
||||
Display Displays
|
||||
TC bool
|
||||
ShowTTL bool
|
||||
ShowClass bool
|
||||
ShowQuery bool
|
||||
AA bool
|
||||
AD bool
|
||||
CD bool
|
||||
QR bool
|
||||
RD bool
|
||||
RA bool
|
||||
IPv4 bool
|
||||
Z bool
|
||||
Reverse bool
|
||||
HumanTTL bool
|
||||
Truncate bool
|
||||
Short bool
|
||||
Identify bool
|
||||
JSON bool
|
||||
XML bool
|
||||
YAML bool
|
||||
QUIC bool
|
||||
HTTPS bool
|
||||
TLSNoVerify bool
|
||||
TLS bool
|
||||
DNSCrypt bool
|
||||
TCP bool
|
||||
IPv6 bool
|
||||
}
|
||||
|
||||
// Displays contains toggles for what to (and not to) display.
|
||||
type Displays struct {
|
||||
Comments bool
|
||||
Question bool // QUESTION SECTION
|
||||
Opt bool // OPT PSEUDOSECTION
|
||||
Answer bool // ANSWER SECTION
|
||||
Authority bool // AUTHORITY SECTION
|
||||
Additional bool // ADDITIONAL SECTION
|
||||
Statistics bool // Query time, message size, etc.
|
||||
UcodeTranslate bool // Translate Punycode back to Unicode
|
||||
}
|
||||
|
||||
// EDNS contains toggles for various EDNS options.
|
||||
type EDNS struct {
|
||||
Subnet dns.EDNS0_SUBNET
|
||||
ZFlag uint16
|
||||
BufSize uint16
|
||||
EnableEDNS bool
|
||||
Cookie bool
|
||||
DNSSEC bool
|
||||
Expire bool
|
||||
KeepOpen bool
|
||||
Nsid bool
|
||||
Padding bool
|
||||
Version uint8
|
||||
}
|
||||
|
||||
// ParseSubnet takes a subnet argument and makes it into one that the DNS library
|
||||
// understands.
|
||||
func ParseSubnet(subnet string, opts *Options) error {
|
||||
ip, inet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
// TODO: make not a default?
|
||||
if subnet == "0" {
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{
|
||||
Code: dns.EDNS0SUBNET,
|
||||
Family: 1,
|
||||
SourceNetmask: 0,
|
||||
SourceScope: 0,
|
||||
Address: net.IPv4(0, 0, 0, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("EDNS subnet parsing: %w", err)
|
||||
}
|
||||
|
||||
sub, _ := inet.Mask.Size()
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{}
|
||||
opts.EDNS.Subnet.Address = ip
|
||||
opts.EDNS.Subnet.SourceNetmask = uint8(sub)
|
||||
|
||||
switch ip.To4() {
|
||||
case nil:
|
||||
// Not a valid IPv4 so assume IPv6
|
||||
opts.EDNS.Subnet.Family = 2
|
||||
default:
|
||||
// Valid IPv4
|
||||
opts.EDNS.Subnet.Family = 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Response is the DNS response.
|
||||
type Response struct {
|
||||
DNS *dns.Msg // The full DNS response
|
||||
RTT time.Duration `json:"rtt"` // The time it took to make the DNS query
|
||||
}
|
||||
|
||||
// Request is a structure for a DNS query.
|
||||
type Request struct {
|
||||
Server string `json:"server"`
|
||||
Name string `json:"name"`
|
||||
Timeout time.Duration
|
||||
Retries int
|
||||
Type uint16 `json:"request"`
|
||||
Class uint16 `json:"class"`
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
PTR = dns.StringToType["PTR"]
|
||||
NAPTR = dns.StringToType["NAPTR"]
|
||||
)
|
||||
|
||||
func TestIPv4(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := util.ReverseDNS("8.8.4.4", PTR)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, act, "4.4.8.8.in-addr.arpa.", "IPv4 reverse")
|
||||
}
|
||||
|
||||
func TestIPv6(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := util.ReverseDNS("2606:4700:4700::1111", PTR)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, act, "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa.", "IPv6 reverse")
|
||||
}
|
||||
|
||||
func TestNAPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"1-800-555-1234", "4.3.2.1.5.5.5.0.0.8.1.e164.arpa."},
|
||||
{"+1 800555 1234", "4.3.2.1.5.5.5.0.0.8.1.e164.arpa."},
|
||||
{"+46766861004", "4.0.0.1.6.8.6.6.7.6.4.e164.arpa."},
|
||||
{"17705551212", "2.1.2.1.5.5.5.0.7.7.1.e164.arpa."},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Thanks Goroutines, very cool!
|
||||
test := test
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
act, err := util.ReverseDNS(test.in, NAPTR)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, test.want, act)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := util.ReverseDNS("AAAAA", 1)
|
||||
assert.ErrorContains(t, err, "invalid value AAAAA given")
|
||||
}
|
||||
|
||||
func TestInvalid2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := util.ReverseDNS("1.0", PTR)
|
||||
assert.ErrorContains(t, err, "PTR reverse")
|
||||
}
|
Loading…
Reference in New Issue