Compare commits
232 Commits
Author | SHA1 | Date |
---|---|---|
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 |
|
@ -1,92 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
local testing(version, arch) = {
|
||||
kind: "pipeline",
|
||||
type: "docker",
|
||||
name: version + "-" + arch ,
|
||||
platform: {
|
||||
arch: arch
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "lint",
|
||||
image: "rancher/drone-golangci-lint:latest",
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make test-ci"
|
||||
],
|
||||
depends_on: [
|
||||
"lint",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "fuzz",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make fuzz-ci",
|
||||
],
|
||||
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-ci"
|
||||
]
|
||||
},
|
||||
{
|
||||
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()
|
||||
]
|
|
@ -4,9 +4,7 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
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
|
||||
|
|
220
.goreleaser.yaml
220
.goreleaser.yaml
|
@ -2,37 +2,153 @@
|
|||
# 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"
|
||||
|
@ -40,29 +156,81 @@ checksum:
|
|||
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'
|
||||
- title: "Dependency Updates"
|
||||
regexp: "^.*fix\\(deps\\)*:+.*$"
|
||||
order: 2
|
||||
- title: 'Features'
|
||||
- title: "Features"
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: 'Bug fixes'
|
||||
- title: "Bug fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: 'Other'
|
||||
- title: "Other"
|
||||
order: 999
|
||||
|
||||
filters:
|
||||
exclude:
|
||||
- "SKIP CI"
|
||||
- "^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.
|
||||
|
|
239
cli/cli.go
239
cli/cli.go
|
@ -1,239 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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(args []string, version string) (util.Options, error) {
|
||||
flag.CommandLine = flag.NewFlagSet(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(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),
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
TLSHost: *tlsHost,
|
||||
TLSNoVerify: *noVerify,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
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,
|
||||
HumanTTL: false,
|
||||
ShowQuery: false,
|
||||
TTL: 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,
|
||||
},
|
||||
}
|
||||
|
||||
// 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.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
|
||||
}
|
||||
|
||||
opts.Logger.Info("Options fully populated")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -6,56 +6,70 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
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.Equal(t, opts.Request.Port, 53)
|
||||
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 TestSubnet(t *testing.T) {
|
||||
args := []string{"awl", "--subnet", "127.0.0.1/32"}
|
||||
func TestValidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts, err := cli.ParseCLI(args, "TEST")
|
||||
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)},
|
||||
}
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
args = []string{"awl", "--subnet", "0"}
|
||||
t.Run(test.args[2], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts, err = cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
opts, err := cli.ParseCLI(test.args, "TEST")
|
||||
|
||||
args = []string{"awl", "--subnet", "::/0"}
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, test.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
opts, err = cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
|
||||
func TestInvalidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args = []string{"awl", "--subnet", "/"}
|
||||
args := []string{"awl", "--subnet", "/"}
|
||||
|
||||
opts, err = cli.ParseCLI(args, "TEST")
|
||||
_, 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")
|
||||
|
@ -64,6 +78,8 @@ func TestMBZ(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--treebug"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
@ -72,6 +88,8 @@ func TestInvalidFlag(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInvalidDig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "+a"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
@ -80,14 +98,18 @@ func TestInvalidDig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--version"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "test")
|
||||
|
||||
assert.ErrorType(t, err, cli.ErrNotError)
|
||||
assert.ErrorIs(t, err, util.ErrNotError)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
|
@ -95,14 +117,20 @@ func TestTimeout(t *testing.T) {
|
|||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
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"},
|
||||
|
@ -111,10 +139,36 @@ func TestRetries(t *testing.T) {
|
|||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
@ -54,7 +66,7 @@ func ParseDig(arg string, opts *util.Options) error {
|
|||
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
|
||||
|
@ -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,7 +28,7 @@ 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]'
|
||||
|
@ -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]'
|
||||
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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 6f3070f5933d0cc48bc8bb5290a1d0cc1825cd75
|
|
@ -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
|
50
go.mod
50
go.mod
|
@ -1,37 +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.0
|
||||
github.com/stefansundin/go-zflag v1.1.1
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||
golang.org/x/sys v0.0.0-20220913175220-63ea55921009
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.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/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // 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-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // 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/exp v0.0.0-20220914170420-dc92f8653013 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
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.22.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.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
)
|
||||
|
|
198
go.sum
198
go.sum
|
@ -1,159 +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.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g=
|
||||
github.com/quic-go/quic-go v0.43.0/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-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-20220914170420-dc92f8653013 h1:ZjglnWxEUdPyXl4o/j4T89SRCI+4X6NW6185PNLEOF4=
|
||||
golang.org/x/exp v0.0.0-20220914170420-dc92f8653013/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-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/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-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw=
|
||||
golang.org/x/sys v0.0.0-20220913175220-63ea55921009/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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.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=
|
||||
|
|
123
main.go
123
main.go
|
@ -1,17 +1,19 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !js
|
||||
|
||||
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"
|
||||
|
@ -19,7 +21,7 @@ var version = "DEV"
|
|||
func main() {
|
||||
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)
|
||||
|
@ -28,43 +30,106 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func run(args []string) (opts util.Options, code int, err error) {
|
||||
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
|
||||
} else if i != opts.Request.Retries {
|
||||
opts.Logger.Warn("Retrying request, error:", err)
|
||||
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
|
||||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
break
|
||||
} else if i != opts.Request.Retries {
|
||||
opts.Logger.Warn("Retrying request, error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
var str string
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = query.PrintSpecial(resp, opts)
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
return opts, 10, fmt.Errorf("format print: %w", err)
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
} else {
|
||||
str, err = query.ToString(resp, opts)
|
||||
if err != nil {
|
||||
return opts, 15, fmt.Errorf("standard print: %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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
return opts, 0, nil
|
||||
}
|
||||
|
|
36
main_test.go
36
main_test.go
|
@ -3,31 +3,43 @@
|
|||
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"},
|
||||
}
|
||||
|
||||
args := []string{"awl", "+yaml", "@1.1.1.1"}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
_, code, err := run(args)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, code, err := run(test)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
args = []string{"awl", "+short", "@1.1.1.1"}
|
||||
func TestTrace(t *testing.T) {
|
||||
domains := []string{"git.froth.zone", "google.com", "amazon.com", "freecumextremist.com", "dns.froth.zone", "sleepy.cafe", "pkg.go.dev"}
|
||||
|
||||
_, code, err = run(args)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
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) {
|
||||
// t.Parallel()
|
||||
args := []string{"awl", "-h"}
|
||||
|
||||
_, code, err := run(args)
|
||||
|
|
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 d876d6de34a78298ed041f575662015fb7eccdb5
|
|
@ -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,41 +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 (l *Logger) Debugf(format string, v ...any) {
|
||||
l.Println(ErrLevel, fmt.Sprintf(format, v...))
|
||||
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 (l *Logger) Infof(format string, v ...any) {
|
||||
l.Println(ErrLevel, fmt.Sprintf(format, v...))
|
||||
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 (l *Logger) Warnf(format string, v ...any) {
|
||||
l.Println(WarnLevel, fmt.Sprintf(format, v...))
|
||||
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 (l *Logger) Errorf(format string, v ...any) {
|
||||
l.Println(ErrLevel, fmt.Sprintf(format, v...))
|
||||
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"
|
||||
)
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// 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) (s string, err error) {
|
||||
if res.DNS == nil {
|
||||
return "<nil> MsgHdr", errNoMessage
|
||||
}
|
||||
|
||||
var opt *dns.OPT
|
||||
|
||||
if !opts.Short {
|
||||
if opts.Display.Comments {
|
||||
s += res.DNS.MsgHdr.String() + " "
|
||||
s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
|
||||
s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
|
||||
s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
|
||||
opt = res.DNS.IsEdns0()
|
||||
|
||||
if opt != nil && opts.Display.Opt {
|
||||
// OPT PSEUDOSECTION
|
||||
s += opt.String() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Question {
|
||||
if len(res.DNS.Question) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Question {
|
||||
str, err := stringParse(r.String(), false, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Answer {
|
||||
if len(res.DNS.Answer) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ANSWER SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Answer {
|
||||
if r != nil {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Authority {
|
||||
if len(res.DNS.Ns) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; AUTHORITY SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Ns {
|
||||
if r != nil {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Additional {
|
||||
if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ADDITIONAL SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Extra {
|
||||
if r != nil && r.Header().Rrtype != dns.TypeOPT {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Statistics {
|
||||
s += "\n;; Query time: " + res.RTT.String()
|
||||
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"
|
||||
}
|
||||
} else {
|
||||
// Print just the responses, nothing else
|
||||
for i, resp := range res.DNS.Answer {
|
||||
temp := strings.Split(resp.String(), "\t")
|
||||
s += temp[len(temp)-1]
|
||||
|
||||
if opts.Identify {
|
||||
s += " from server " + opts.Request.Server + " in " + res.RTT.String()
|
||||
}
|
||||
|
||||
// Don't print newline on last line
|
||||
if i != len(res.DNS.Answer)-1 {
|
||||
s += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// TODO: make less ew?
|
||||
// This exists because the question section should be left alone EXCEPT for punycode.
|
||||
|
||||
if isAns {
|
||||
if !opts.Display.TTL {
|
||||
// Remove from existence
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
|
||||
if !opts.Display.ShowClass {
|
||||
// Position depends on if the TTL is there or not.
|
||||
if opts.Display.TTL {
|
||||
split = append(split[:2], split[3:]...)
|
||||
} else {
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.TTL && opts.Display.HumanTTL {
|
||||
ttl, _ := strconv.Atoi(split[1])
|
||||
split[1] = (time.Duration(ttl) * time.Second).String()
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
var (
|
||||
err error
|
||||
semi string
|
||||
)
|
||||
|
||||
if strings.HasPrefix(split[0], ";") {
|
||||
split[0] = strings.TrimPrefix(split[0], ";")
|
||||
semi = ";"
|
||||
}
|
||||
|
||||
split[0], err = idna.ToUnicode(split[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("punycode: %w", err)
|
||||
}
|
||||
|
||||
split[0] = semi + split[0]
|
||||
}
|
||||
|
||||
return strings.Join(split, "\t"), nil
|
||||
}
|
||||
|
||||
// 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,7 +14,7 @@ import (
|
|||
func TestRealPrint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := []util.Options{
|
||||
opts := []*util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
|
@ -38,11 +38,12 @@ func TestRealPrint(t *testing.T) {
|
|||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "a.gtld-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Server: "a.gtld-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
|
@ -77,8 +78,7 @@ func TestRealPrint(t *testing.T) {
|
|||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
|
@ -99,7 +99,7 @@ func TestRealPrint(t *testing.T) {
|
|||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
HumanTTL: true,
|
||||
ShowQuery: true,
|
||||
|
@ -110,8 +110,7 @@ func TestRealPrint(t *testing.T) {
|
|||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
|
@ -137,11 +136,12 @@ func TestRealPrint(t *testing.T) {
|
|||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -172,8 +172,7 @@ func TestRealPrint(t *testing.T) {
|
|||
Type: dns.StringToType["A"],
|
||||
Class: 1,
|
||||
Name: "froth.zone.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
|
@ -188,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, 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 != "")
|
||||
})
|
||||
|
@ -207,14 +216,14 @@ func TestRealPrint(t *testing.T) {
|
|||
func TestBadFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := query.PrintSpecial(util.Response{DNS: 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")
|
||||
|
@ -137,9 +133,9 @@ func CreateQuery(opts util.Options) (util.Response, error) {
|
|||
}
|
||||
}
|
||||
|
||||
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.Request.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.Request.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"
|
||||
)
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
@ -18,8 +18,12 @@ type Options struct {
|
|||
TLSHost string `json:"tlsHost" example:""`
|
||||
// EDNS Options
|
||||
EDNS
|
||||
|
||||
// HTTPS options :)
|
||||
HTTPSOptions
|
||||
|
||||
// DNS request :)
|
||||
Request Request
|
||||
Request
|
||||
|
||||
// Verbosity levels, see [logawl.AllLevels]
|
||||
Verbosity int `json:"-" example:"0"`
|
||||
|
@ -27,6 +31,8 @@ type Options struct {
|
|||
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
|
||||
|
@ -61,6 +67,19 @@ type Options struct {
|
|||
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.
|
|
@ -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")
|
||||
}
|
|
@ -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,90 +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),
|
||||
Request: util.Request{Server: "dns.adguard.com", Port: 853},
|
||||
}
|
||||
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.Request.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),
|
||||
Request: util.Request{Server: "example.com", Port: 853, 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,117 +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),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.1",
|
||||
Port: 53,
|
||||
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")
|
||||
}
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "madns.binarystar.systems",
|
||||
Port: 5301,
|
||||
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,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
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{})
|
||||
})
|
||||
}
|
||||
}
|
550
query/print.go
550
query/print.go
|
@ -1,550 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
if res.DNS == nil {
|
||||
return "<nil> MsgHdr", errNoMessage
|
||||
}
|
||||
|
||||
var (
|
||||
s string
|
||||
opt *dns.OPT
|
||||
)
|
||||
|
||||
if !opts.Short {
|
||||
if opts.Display.Comments {
|
||||
s += res.DNS.MsgHdr.String() + " "
|
||||
s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
|
||||
s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
|
||||
s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
|
||||
opt = res.DNS.IsEdns0()
|
||||
|
||||
if opt != nil && opts.Display.Opt {
|
||||
// OPT PSEUDOSECTION
|
||||
s += opt.String() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Question {
|
||||
if len(res.DNS.Question) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Question {
|
||||
str, err := stringParse(r.String(), false, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Answer {
|
||||
if len(res.DNS.Answer) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ANSWER SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Answer {
|
||||
if r != nil {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Authority {
|
||||
if len(res.DNS.Ns) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; AUTHORITY SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Ns {
|
||||
if r != nil {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Additional {
|
||||
if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ADDITIONAL SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Extra {
|
||||
if r != nil && r.Header().Rrtype != dns.TypeOPT {
|
||||
str, err := stringParse(r.String(), true, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
s += str + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Statistics {
|
||||
s += "\n;; Query time: " + res.RTT.String()
|
||||
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"
|
||||
}
|
||||
} else {
|
||||
// Print just the responses, nothing else
|
||||
for i, resp := range res.DNS.Answer {
|
||||
temp := strings.Split(resp.String(), "\t")
|
||||
s += temp[len(temp)-1]
|
||||
|
||||
if opts.Identify {
|
||||
s += " from server " + opts.Request.Server + " in " + res.RTT.String()
|
||||
}
|
||||
|
||||
// Don't print newline on last line
|
||||
if i != len(res.DNS.Answer)-1 {
|
||||
s += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func serverExtra(opts util.Options) string {
|
||||
// Add extra information to server string
|
||||
var extra string
|
||||
|
||||
switch {
|
||||
case opts.TCP:
|
||||
extra = ":" + strconv.Itoa(opts.Request.Port) + " (TCP)"
|
||||
case opts.TLS:
|
||||
extra = ":" + strconv.Itoa(opts.Request.Port) + " (TLS)"
|
||||
case opts.HTTPS, opts.DNSCrypt:
|
||||
extra = ""
|
||||
case opts.QUIC:
|
||||
extra = ":" + strconv.Itoa(opts.Request.Port) + " (QUIC)"
|
||||
default:
|
||||
extra = ":" + strconv.Itoa(opts.Request.Port) + " (UDP)"
|
||||
}
|
||||
|
||||
return extra
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// TODO: make less ew?
|
||||
// This exists because the question section should be left alone EXCEPT for punycode.
|
||||
|
||||
if isAns {
|
||||
if !opts.Display.TTL {
|
||||
// Remove from existence
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
|
||||
if !opts.Display.ShowClass {
|
||||
// Position depends on if the TTL is there or not.
|
||||
if opts.Display.TTL {
|
||||
split = append(split[:2], split[3:]...)
|
||||
} else {
|
||||
split = append(split[:1], split[2:]...)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.TTL && opts.Display.HumanTTL {
|
||||
ttl, _ := strconv.Atoi(split[1])
|
||||
split[1] = (time.Duration(ttl) * time.Second).String()
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
var (
|
||||
err error
|
||||
semi string
|
||||
)
|
||||
|
||||
if strings.HasPrefix(split[0], ";") {
|
||||
split[0] = strings.TrimPrefix(split[0], ";")
|
||||
semi = ";"
|
||||
}
|
||||
|
||||
split[0], err = idna.ToUnicode(split[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("punycode: %w", err)
|
||||
}
|
||||
|
||||
split[0] = semi + split[0]
|
||||
}
|
||||
|
||||
return strings.Join(split, "\t"), nil
|
||||
}
|
||||
|
||||
// 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{
|
||||
Header: Header{
|
||||
ID: msg.Id,
|
||||
Response: msg.Response,
|
||||
Opcode: dns.OpcodeToString[msg.Opcode],
|
||||
Authoritative: msg.Authoritative,
|
||||
Truncated: msg.Truncated,
|
||||
RecursionDesired: msg.RecursionDesired,
|
||||
RecursionAvailable: msg.RecursionAvailable,
|
||||
Zero: msg.Zero,
|
||||
AuthenticatedData: msg.AuthenticatedData,
|
||||
CheckingDisabled: msg.CheckingDisabled,
|
||||
Status: dns.RcodeToString[msg.Rcode],
|
||||
},
|
||||
}
|
||||
|
||||
opt := msg.IsEdns0()
|
||||
if opt != nil && opts.Display.Opt {
|
||||
ret.Opt, err = parseOpt(*opt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("edns print: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
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.Display.TTL {
|
||||
if opts.Display.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.Display.TTL {
|
||||
if opts.Display.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.Authority = append(ret.Authority, 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.Display.TTL {
|
||||
if opts.Display.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.Additional = append(ret.Additional, 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],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Statistics {
|
||||
ret.Statistics = Statistics{
|
||||
RTT: res.RTT.String(),
|
||||
Server: opts.Request.Server + serverExtra(opts),
|
||||
When: time.Now().Format(time.RFC1123Z),
|
||||
MsgSize: res.DNS.Len(),
|
||||
}
|
||||
} else {
|
||||
ret.Statistics = Statistics{}
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func parseOpt(rr dns.OPT) ([]Opts, error) {
|
||||
ret := []Opts{}
|
||||
// Most of this is taken from https://github.com/miekg/dns/blob/master/edns.go#L76
|
||||
|
||||
ret = append(ret, Opts{
|
||||
Name: "Version",
|
||||
Value: strconv.Itoa(int(rr.Version())),
|
||||
})
|
||||
|
||||
if rr.Do() {
|
||||
ret = append(ret, Opts{
|
||||
Name: "Flags",
|
||||
Value: "do",
|
||||
})
|
||||
} else {
|
||||
ret = append(ret, Opts{
|
||||
Name: "Flags",
|
||||
Value: "",
|
||||
})
|
||||
}
|
||||
|
||||
if rr.Hdr.Ttl&0x7FFF != 0 {
|
||||
ret = append(ret, Opts{
|
||||
Name: "MBZ",
|
||||
Value: fmt.Sprintf("0x%04x", rr.Hdr.Ttl&0x7FFF),
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, Opts{
|
||||
Name: "UDP Buffer Size",
|
||||
Value: strconv.Itoa(int(rr.UDPSize())),
|
||||
})
|
||||
|
||||
for _, opt := range rr.Option {
|
||||
switch opt.(type) {
|
||||
case *dns.EDNS0_NSID:
|
||||
str := opt.String()
|
||||
|
||||
hex, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
ret = append(ret, Opts{
|
||||
Name: "NSID",
|
||||
Value: fmt.Sprintf("%s (%s)", str, string(hex)),
|
||||
})
|
||||
case *dns.EDNS0_SUBNET:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Subnet",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_COOKIE:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Cookie",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_EXPIRE:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Expire",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_TCP_KEEPALIVE:
|
||||
ret = append(ret, Opts{
|
||||
Name: "TCP Keepalive",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_UL:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Update Lease",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_LLQ:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Long Lived Queries",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_DAU:
|
||||
ret = append(ret, Opts{
|
||||
Name: "DNSSEC Algorithm Understood",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_DHU:
|
||||
ret = append(ret, Opts{
|
||||
Name: "DS Hash Understood",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_N3U:
|
||||
ret = append(ret, Opts{
|
||||
Name: "NSEC3 Hash Understood",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_LOCAL:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Local OPT",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_PADDING:
|
||||
ret = append(ret, Opts{
|
||||
Name: "Padding",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_EDE:
|
||||
ret = append(ret, Opts{
|
||||
Name: "EDE",
|
||||
Value: opt.String(),
|
||||
})
|
||||
case *dns.EDNS0_ESU:
|
||||
ret = append(ret, Opts{
|
||||
Name: "ESU",
|
||||
Value: opt.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var errInvalidFormat = errors.New("this should never happen")
|
|
@ -1,130 +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),
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
Z: true,
|
||||
},
|
||||
|
||||
YAML: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
JSON: true,
|
||||
QUIC: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "dns.adguard.com",
|
||||
Port: 853,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
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 _, 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, opt)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str, err = query.ToString(res, opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Message is for overall DNS responses.
|
||||
//
|
||||
//nolint:govet // Better looking output is worth a few bytes.
|
||||
type Message struct {
|
||||
// Header section
|
||||
Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
|
||||
// Opt Pseudosection
|
||||
Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"`
|
||||
// Question Section
|
||||
Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:"question,omitempty"`
|
||||
// Answer Section
|
||||
Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:"answer,omitempty"`
|
||||
// Authority Section
|
||||
Authority []Answer `json:"authority,omitempty" xml:"authority,omitempty" yaml:"authority,omitempty"`
|
||||
// Additional Section
|
||||
Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:"additional,omitempty"`
|
||||
// Statistics :)
|
||||
Statistics `json:"statistics,omitempty" xml:"statistics,omitempty" yaml:"statistics,omitempty"`
|
||||
}
|
||||
|
||||
// Header is the header.
|
||||
type Header struct {
|
||||
Opcode string `json:"opcode," xml:"opcode," yaml:"opcode" example:"QUERY"`
|
||||
Status string `json:"status," xml:"status," yaml:"status" example:"NOERR"`
|
||||
ID uint16 `json:"id," xml:"id," yaml:"id" example:"12"`
|
||||
Response bool `json:"response," xml:"response," yaml:"response" example:"true"`
|
||||
Authoritative bool `json:"authoritative," xml:"authoritative," yaml:"authoritative" example:"false"`
|
||||
Truncated bool `json:"truncated," xml:"truncated," yaml:"truncated" example:"false"`
|
||||
RecursionDesired bool `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired" example:"true"`
|
||||
RecursionAvailable bool `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable" example:"true"`
|
||||
Zero bool `json:"zero," xml:"zero," yaml:"zero" example:"false"`
|
||||
AuthenticatedData bool `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData" example:"false"`
|
||||
CheckingDisabled bool `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled" example:"false"`
|
||||
}
|
||||
|
||||
// Question is a DNS Query.
|
||||
type Question struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"localhost"`
|
||||
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
|
||||
}
|
||||
|
||||
// RRHeader is for DNS Resource Headers.
|
||||
type RRHeader struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"127.0.0.1"`
|
||||
TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty" example:"0ms"`
|
||||
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
|
||||
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Opts is for the OPT pseudosection, nearly exclusively for EDNS.
|
||||
type Opts struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
|
||||
Value string `json:"value" xml:"value" yaml:"value"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Statistics is the little bit at the bottom :).
|
||||
type Statistics struct {
|
||||
RTT string `json:"queryTime,omitempty" xml:"queryTime,omitempty" yaml:"queryTime,omitempty"`
|
||||
Server string `json:"server,omitempty" xml:"server,omitempty" yaml:"server,omitempty"`
|
||||
When string `json:"when,omitempty" xml:"when,omitempty" yaml:"when,omitempty"`
|
||||
MsgSize int `json:"msgSize,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
|
||||
}
|
||||
|
||||
var errNoMessage = errors.New("no message")
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base", ":npm", ":gomod"]
|
||||
"extends": ["config:base", ":npm", ":gomod"],
|
||||
"automerge": true
|
||||
}
|
||||
|
|
47
template.mk
47
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/^v//;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,31 +63,33 @@ 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 10000x ./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 ./cli
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 1000x ./cli
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 1000x ./cli
|
||||
$(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
|
||||
|
|
|
@ -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