Compare commits
289 Commits
Author | SHA1 | Date |
---|---|---|
Renovate Bot | c88958f5df | |
Renovate Bot | e91ab27f13 | |
Renovate Bot | 6677c6b5e5 | |
Renovate Bot | 7126c753e9 | |
Renovate Bot | c1a4a7f93a | |
Sam Therapy | 9aebf89f57 | |
Renovate Bot | 37e0c1e9b5 | |
Sam Therapy | 40278ed22f | |
Renovate Bot | 7631dd7a7b | |
Renovate Bot | 2140c1582f | |
Renovate Bot | 8c00f85636 | |
Renovate Bot | 8e54337787 | |
Sam Therapy | a5980be3cc | |
Sam Therapy | 58cee5b3c9 | |
Renovate Bot | 3881caff0d | |
Sam Therapy | d8c40abf5a | |
Sam Therapy | 92cf0b465b | |
Sam Therapy | b5f4ea9c42 | |
Sam Therapy | 8434739516 | |
Sam Therapy | 556b7a48af | |
Sam Therapy | 21047275bf | |
Sam Therapy | 835cb8c950 | |
Sam Therapy | 20f5b21be1 | |
Renovate Bot | b975dd8c00 | |
Renovate Bot | 42e50d3e93 | |
Sam Therapy | 7030e68100 | |
Renovate Bot | c4a267cec9 | |
Renovate Bot | 434de0e5b4 | |
Renovate Bot | 52cef2e3a3 | |
Renovate Bot | e0b14b8b00 | |
Renovate Bot | c580ac78d4 | |
Renovate Bot | 99e75afcd3 | |
Renovate Bot | 6ddbf854d8 | |
Sam Therapy | 16ef3de591 | |
Renovate Bot | f9263300c7 | |
Sam Therapy | d43543e48a | |
Sam Therapy | 0a6d6b432c | |
Sam Therapy | ffb16ff7fa | |
Sam Therapy | efb4cd6c4a | |
Sam Therapy | 59fa146d7f | |
Sam Therapy | c5c90b97af | |
Sam Therapy | e1122a9de7 | |
Sam Therapy | 26ef04b969 | |
Sam Therapy | 2565d34ade | |
Sam Therapy | 7dd9b138a3 | |
Sam Therapy | b8a2407169 | |
Renovate Bot | 9ef46d33e8 | |
Renovate Bot | 939a09da5b | |
Renovate Bot | 87c59551ec | |
Renovate Bot | 7efc9523bf | |
Renovate Bot | 2084ae69f6 | |
Renovate Bot | 5130b6c3cd | |
Renovate Bot | 8c949ce4ca | |
Renovate Bot | b3fad7abcf | |
Renovate Bot | b713aeb7d4 | |
Renovate Bot | 260eb979b3 | |
Renovate Bot | 83ecfbaea1 | |
Renovate Bot | 10faea804b | |
Renovate Bot | 0f80004790 | |
Renovate Bot | 12863559c5 | |
Renovate Bot | 8e9574f870 | |
Renovate Bot | fadb04f49f | |
Renovate Bot | 5704c6a0dd | |
Renovate Bot | 9c8e5349bf | |
Sam Therapy | dcd932c42e | |
Sam Therapy | f4c3283954 | |
Sam Therapy | 824a60aef1 | |
Sam Therapy | 43e53a4055 | |
Sam Therapy | 60b0300eec | |
Sam Therapy | 9740e6b348 | |
Renovate Bot | 8c888a90f1 | |
Renovate Bot | 71477684d9 | |
Renovate Bot | e3951db486 | |
Renovate Bot | dfe4cca176 | |
Renovate Bot | b5102c91e6 | |
Renovate Bot | 03e8f6be61 | |
Renovate Bot | f60cd1c315 | |
Renovate Bot | 00b116abf4 | |
Renovate Bot | 821149b463 | |
Renovate Bot | f2f53795ef | |
Renovate Bot | 322345a758 | |
Renovate Bot | b4de2ba89e | |
Sam Therapy | 3421066420 | |
Sam Therapy | aef8a73a28 | |
Sam Therapy | ba93384f9b | |
Sam Therapy | 66855b5542 | |
Sam Therapy | ccc9df39b6 | |
Renovate Bot | c6bbda17b2 | |
Sam Therapy | db77f2315c | |
Sam Therapy | a157dcb7fb | |
Sam Therapy | 428c218405 | |
Sam Therapy | 8df4863cf4 | |
Sam Therapy | c0cd4d7771 | |
Renovate Bot | 5f1b81a2be | |
Renovate Bot | 45d1aaf535 | |
Renovate Bot | 4f42657294 | |
Renovate Bot | b6bd22a8fe | |
Sam Therapy | b0d8e325d6 | |
Sam Therapy | fb9fd7689f | |
Sam Therapy | 329d81c001 | |
Renovate Bot | 5329a6f2e0 | |
Renovate Bot | 95ff6f554c | |
Sam Therapy | 928123edbd | |
Renovate Bot | fd3ceeb066 | |
Renovate Bot | 1acd48b16d | |
Sam Therapy | 27ece358c6 | |
Sam Therapy | 58fa7af2ea | |
Sam Therapy | 75fb8f2fc6 | |
Sam Therapy | 3423958809 | |
Renovate Bot | 60491c39c1 | |
Renovate Bot | 39f605740a | |
Sam Therapy | 171f5d0d11 | |
Renovate Bot | 1e4ff59631 | |
Renovate Bot | f1583fef52 | |
Sam Therapy | fa709ca327 | |
Sam Therapy | 30277b5028 | |
Sam Therapy | 028c423d89 | |
Renovate Bot | 641b003381 | |
Sam Therapy | b15584f436 | |
Sam Therapy | 9c8f30f432 | |
Sam Therapy | da172f9de5 | |
Sam Therapy | feb0617ce2 | |
Sam Therapy | daff2b7687 | |
Sam Therapy | e782c9cb23 | |
Sam Therapy | 00ec4ef893 | |
Sam Therapy | bf04f59468 | |
Renovate Bot | db66371420 | |
Renovate Bot | 42b17a7912 | |
Renovate Bot | 4286c16ae2 | |
Sam Therapy | 6d9690c44f | |
Sam Therapy | bc6e7cd759 | |
Sam Therapy | 2312fecfc8 | |
Sam Therapy | 75479f5060 | |
Renovate Bot | 0c8000d598 | |
Sam Therapy | 30be18b474 | |
grumbulon | d93eccc064 | |
Sam Therapy | fdba9a0a41 | |
Sam Therapy | a7f7498d8b | |
Sam Therapy | 792ddecf6c | |
Sam Therapy | 10d1c4cd2b | |
Sam Therapy | 530ef06ee1 | |
Sam Therapy | 0b042af3e2 | |
Renovate Bot | 4349aec1f2 | |
Sam Therapy | 185a79e1c2 | |
Renovate Bot | 11ebd2c69f | |
Sam Therapy | 64768f0956 | |
Renovate Bot | 45acd03dff | |
Sam Therapy | 78bbb8f23d | |
Renovate Bot | 7e7c88ddd5 | |
Sam Therapy | a2e7b262ea | |
Sam Therapy | 90f5b92ffd | |
grumbulon | 5d67ffaad3 | |
Sam Therapy | 303eb974c8 | |
Sam Therapy | dfa14cbcdf | |
Sam Therapy | 216431f591 | |
Renovate Bot | 07260fb593 | |
Renovate Bot | 9a9658c6f8 | |
Renovate Bot | e456d409ef | |
Sam Therapy | ea3943dd63 | |
Sam Therapy | feef2cddaf | |
Sam Therapy | 2836633c62 | |
Sam Therapy | a64a260c55 | |
Sam Therapy | dd542b9f18 | |
Sam Therapy | c658b6b796 | |
Renovate Bot | da7ce61152 | |
Sam Therapy | 852c87c3ab | |
Sam Therapy | ec6e723eaf | |
Sam Therapy | 698b0d3ad1 | |
Sam Therapy | 8c3a5cb369 | |
Sam Therapy | 60e22e6d33 | |
Sam Therapy | 7feaab7103 | |
Sam Therapy | b1fa25a9a0 | |
Sam Therapy | 59212798f7 | |
Sam Therapy | a386960076 | |
Sam Therapy | 6144c7219a | |
Renovate Bot | 967363f34b | |
Sam Therapy | 2db6ee3935 | |
Renovate Bot | 3a1ebaafe1 | |
Renovate Bot | 354128d449 | |
Sam Therapy | def649de4f | |
Renovate Bot | d6a9bd9b0b | |
Sam Therapy | 0bcc63de8a | |
Sam Therapy | 39639c0f7f | |
Sam Therapy | b80219019e | |
Sam Therapy | bf0e44e80c | |
Sam Therapy | 9ec28f9b1a | |
Sam Therapy | dc4edd55bb | |
Sam Therapy | add1ef61a2 | |
Sam Therapy | 2d94ea6838 | |
Renovate Bot | 216a4b6dd7 | |
Renovate Bot | 66422e1512 | |
Sam Therapy | 81da49093d | |
Sam Therapy | e6a3d6040a | |
Renovate Bot | c1b5961717 | |
Renovate Bot | 3d97359070 | |
Renovate Bot | 561958c1b0 | |
Renovate Bot | d2c6ed317e | |
Renovate Bot | 9734f8ddad | |
Renovate Bot | 296f5f0a0b | |
Sam Therapy | 4d0605bd1e | |
Renovate Bot | a74fbf525b | |
Renovate Bot | cf095313a4 | |
Renovate Bot | 6749361395 | |
Sam Therapy | 4495da2f3e | |
Renovate Bot | 11c06f9662 | |
Sam Therapy | d701059b5f | |
Sam Therapy | 56baff4e18 | |
Sam Therapy | 6a839ba8e5 | |
Sam Therapy | f2cf35ca31 | |
Sam Therapy | 607c321de3 | |
Sam Therapy | f01f2bc15a | |
Sam Therapy | c053c077c8 | |
Renovate Bot | 0a371cd335 | |
Sam Therapy | f2218481ee | |
Renovate Bot | 14416d5aec | |
Renovate Bot | 7b11583d6e | |
Renovate Bot | 7bd481cd7a | |
Sam Therapy | 60643e2b3e | |
Sam Therapy | 434632884c | |
Renovate Bot | 284c0646f2 | |
grumbulon | 3a0a8f015a | |
Sam Therapy | ac55b21b25 | |
Renovate Bot | 840397a85a | |
Renovate Bot | f24608667a | |
Sam Therapy | 0845ae2a82 | |
Renovate Bot | 94b7f523b7 | |
Renovate Bot | c75db286dc | |
Renovate Bot | 261e5fd6c1 | |
Renovate Bot | 75ca107e55 | |
Renovate Bot | 98c641848e | |
Renovate Bot | 86f115f361 | |
Sam Therapy | ef87175190 | |
Sam Therapy | 933967016f | |
Sam Therapy | 8df0347891 | |
Sam Therapy | ddd0277de9 | |
Renovate Bot | 55b965d084 | |
Sam Therapy | 9746ae0a6f | |
Renovate Bot | 0d011bb097 | |
Sam Therapy | 6fa6b2d1f6 | |
Renovate Bot | 4f743f861d | |
Renovate Bot | 90d2fed6ab | |
Sam Therapy | c8282e8030 | |
Sam Therapy | 1d0ccbef44 | |
Sam Therapy | ae7209c334 | |
Sam Therapy | dd2a7122c2 | |
Renovate Bot | 48b0ba8f63 | |
Renovate Bot | 062008b4dc | |
Renovate Bot | 88d090c04a | |
Renovate Bot | 74a7ba3c32 | |
Renovate Bot | f83453c1ee | |
Sam Therapy | 789710cbeb | |
Sam Therapy | 7edd983175 | |
Sam Therapy | 068706ae9a | |
Sam Therapy | 59a7fe6d7d | |
Renovate Bot | 61ddbb3693 | |
Renovate Bot | c98be03879 | |
Renovate Bot | 2824ce18e1 | |
Renovate Bot | 8cf65cf4b7 | |
Renovate Bot | a090b30071 | |
Renovate Bot | 29bac7ad6b | |
Renovate Bot | 8645e9bf46 | |
Renovate Bot | db683ca203 | |
Renovate Bot | d051b74c49 | |
Renovate Bot | fd052f1c6c | |
Renovate Bot | 1743dc564d | |
Renovate Bot | 486961fcbe | |
Sam Therapy | bdaefe815d | |
Sam Therapy | 3540770b3d | |
Sam Therapy | 3dc4115350 | |
Sam Therapy | 0a9e76e6bf | |
Renovate Bot | 0ce11b36c0 | |
Renovate Bot | a77009e32d | |
Sam Therapy | 4cf19ebf78 | |
Renovate Bot | c70ae88a38 | |
Renovate Bot | 80475e6c3d | |
Renovate Bot | 547383d842 | |
Sam Therapy | 07728cffdb | |
Renovate Bot | b7a563a989 | |
Renovate Bot | 5d9d2af959 | |
Renovate Bot | 2ccfea5de5 | |
Renovate Bot | abccb5b5cc | |
Renovate Bot | 24c8294c36 | |
Renovate Bot | 7eaba30ca3 | |
Renovate Bot | 6147e39f7a | |
Renovate Bot | b4b2cc4834 | |
Renovate Bot | 2d7acad6c6 | |
Renovate Bot | 54f1e31d8a | |
Renovate Bot | d773a86c0e | |
Renovate Bot | 5989a678e4 |
|
@ -1,81 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
local testing(version, arch) = {
|
||||
kind: "pipeline",
|
||||
name: version + "-" + arch ,
|
||||
platform: {
|
||||
arch: arch
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "submodules",
|
||||
image: "alpine/git",
|
||||
commands: [
|
||||
"git submodule update --init --recursive"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "lint",
|
||||
image: "rancher/drone-golangci-lint:latest"
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"go test -race ./... -cover"
|
||||
]
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
event: {
|
||||
exclude: "tag",
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// "Inspired by" https://goreleaser.com/ci/drone/
|
||||
local release() = {
|
||||
kind: "pipeline",
|
||||
name: "release",
|
||||
trigger: {
|
||||
event: "tag"
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "fetch",
|
||||
image: "docker:git",
|
||||
commands : [
|
||||
"git fetch --tags",
|
||||
"git submodule update --init --recursive"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
image: "golang",
|
||||
commands: [
|
||||
"go test -race ./... -cover"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "release",
|
||||
image: "goreleaser/goreleaser",
|
||||
environment: {
|
||||
"GITEA_TOKEN": {
|
||||
from_secret: "GITEA_TOKEN"
|
||||
}
|
||||
},
|
||||
commands: [
|
||||
"goreleaser release"
|
||||
],
|
||||
// when: {
|
||||
// event: "tag"
|
||||
// }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
[
|
||||
testing("1.18", "amd64"),
|
||||
testing("1.18", "arm64"),
|
||||
release()
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
|
@ -0,0 +1,35 @@
|
|||
name: Forgejo Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install scdoc
|
||||
run: apt-get update && apt-get install -y scdoc
|
||||
|
||||
- name: Release with GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --skip docker,snapcraft
|
||||
env:
|
||||
GORELEASER_FORCE_TOKEN: gitea
|
||||
GITEA_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
UPLOAD_PACKAGES_SECRET: ${{ secrets.PUBLISH_TOKEN }}
|
|
@ -0,0 +1,14 @@
|
|||
name: Mirror Push
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url: git@git.sr.ht:~sammefishe/awl
|
||||
ssh_private_key: ${{ secrets.SRHT_SSH_KEY }}
|
|
@ -0,0 +1,24 @@
|
|||
name: Test
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
goVer: ["oldstable", "stable"]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Test
|
||||
run: make test-ci
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Reproduction steps**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. 1
|
||||
2. 2
|
||||
3. Bug
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots / Logs**
|
||||
```
|
||||
Add `-v=4` and add the debug logs to the report here:
|
||||
```
|
||||
|
||||
**System information (please complete the following information):**
|
||||
|
||||
- OS: [e.g. Ubuntu 22.04, OpenBSD, Windows 11]
|
||||
- Version: [run `awl -V` and print the output]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Links to implementations in dig, drill, etc. should go here.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -0,0 +1,9 @@
|
|||
<!--
|
||||
|
||||
Please check the following:
|
||||
|
||||
1. Make sure you are targeting the `master` branch.
|
||||
2. Make sure that you test and format your contributions: `make full_test && make lint`
|
||||
3. Describe what your pull request does and which issue you're targeting (if any)
|
||||
|
||||
-->
|
|
@ -0,0 +1,56 @@
|
|||
name: GitHub Release
|
||||
|
||||
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@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
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@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --skip=aur,homebrew,nix,scoop
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -0,0 +1,24 @@
|
|||
name: Test
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [macos, windows]
|
||||
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@v5
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Test
|
||||
run: make test-ci
|
|
@ -1,23 +1,29 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/*
|
||||
!coverage/.gitkeep
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/*
|
||||
!coverage/.gitkeep
|
||||
|
||||
awl
|
||||
docs/awl.1
|
||||
docs/awl.1.gz
|
||||
|
||||
.dccache
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[submodule "doc/wiki"]
|
||||
path = doc/wiki
|
||||
[submodule "docs/wiki"]
|
||||
path = docs/wiki
|
||||
url = ../awl.wiki
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# Refer to golangci-lint's example config file for more options and information:
|
||||
# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
skip-dirs:
|
||||
- "coverage"
|
||||
- ".github"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gci
|
||||
- gocritic
|
||||
- goconst
|
||||
- godot
|
||||
- goimports
|
||||
- govet
|
||||
- gocritic
|
||||
- goerr113
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- gosec
|
||||
- maintidx
|
||||
- makezero
|
||||
- misspell
|
||||
- nlreturn
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- revive
|
||||
- staticcheck
|
||||
- tagliatelle
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
disable:
|
||||
- structcheck
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
disable-all: false
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: duplicated-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: errorf
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: modifies-value-receiver
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
linters-settings:
|
||||
tagliatelle:
|
||||
case:
|
||||
use-field-name: false
|
||||
rules:
|
||||
# Any struct tag type can be used.
|
||||
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
|
||||
json: goCamel
|
||||
yaml: goCamel
|
||||
xml: goCamel
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
235
.goreleaser.yaml
235
.goreleaser.yaml
|
@ -2,40 +2,235 @@
|
|||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- make clean
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
# - go generate ./...
|
||||
# Make manpages
|
||||
- docs/makeman.sh
|
||||
# Vendor dependencies
|
||||
- go mod vendor
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
targets:
|
||||
- go_first_class
|
||||
- plan9_amd64
|
||||
- freebsd_amd64
|
||||
|
||||
universal_binaries:
|
||||
- replace: true
|
||||
- replace: true
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
amd64: x86_64
|
||||
- files:
|
||||
- LICENSE
|
||||
- completions/**
|
||||
- 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'
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
|
||||
brews:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: homebrew
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query tool
|
||||
license: BSD-3-Clause
|
||||
# custom_block: |
|
||||
# head "https://git.froth.zone/sam/awl.git"
|
||||
install: |-
|
||||
bin.install "awl"
|
||||
bash_completion.install "completions/bash.bash" => "awl"
|
||||
zsh_completion.install "completions/zsh.zsh" => "_awl"
|
||||
fish_completion.install "completions/fish.fish" => "awl.fish"
|
||||
man1.install "docs/awl.1.gz"
|
||||
|
||||
nix:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: nur
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query client
|
||||
license: bsd3
|
||||
extra_install: |-
|
||||
installManPage ./docs/awl.1.gz
|
||||
installShellCompletion ./completions/*
|
||||
|
||||
scoops:
|
||||
- repository:
|
||||
owner: packaging
|
||||
name: scoop
|
||||
folder: bucket
|
||||
homepage: https://dns.froth.zone/awl
|
||||
description: A DNS query client
|
||||
license: BSD-3-Clause
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
groups:
|
||||
- title: "Dependency Updates"
|
||||
regexp: "^.*fix\\(deps\\)*:+.*$"
|
||||
order: 2
|
||||
- title: "Features"
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "Bug fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "Other"
|
||||
order: 999
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- "^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 /
|
24
GNUmakefile
24
GNUmakefile
|
@ -1,4 +1,5 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# GNU Makefile allowing for building on Windows (with GNU Make)
|
||||
|
||||
include template.mk
|
||||
|
||||
|
@ -6,19 +7,24 @@ ifeq ($(OS),Windows_NT)
|
|||
EXE := $(PROG).exe
|
||||
else
|
||||
EXE := $(PROG)
|
||||
ifeq ($(shell uname), Darwin)
|
||||
INSTALLFLAGS :=
|
||||
else
|
||||
INSTALLFLAGS := D
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
$(PROG):
|
||||
$(GO) build -o $(EXE) $(GOFLAGS) .
|
||||
|
||||
## install: installs awl
|
||||
install: all
|
||||
.PHONY: install
|
||||
ifeq ($(OS),Windows_NT)
|
||||
install:
|
||||
$(GO) install $(GOFLAGS) .
|
||||
else
|
||||
install -m755 $(PROG) $(PREFIX)/$(BIN)
|
||||
install -m644 doc/$(PROG).1 $(MAN)/man1
|
||||
install: all
|
||||
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 -$(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
|
||||
|
||||
.PHONY: install
|
20
LICENSE
20
LICENSE
|
@ -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.
|
14
Makefile
14
Makefile
|
@ -3,12 +3,14 @@
|
|||
|
||||
include template.mk
|
||||
|
||||
$(PROG):
|
||||
$(GO) build -o $(PROG) $(GOFLAGS) .
|
||||
EXE := $(PROG)
|
||||
|
||||
## install: installs awl
|
||||
.PHONY: install
|
||||
install: all
|
||||
install -m755 $(PROG) $(PREFIX)/$(BIN)
|
||||
install -m644 doc/$(PROG).1 $(MAN)/man1
|
||||
|
||||
.PHONY: install
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -Dm644 docs/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
# completions need to go in one specific place :)
|
||||
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)
|
||||
|
|
217
README.md
217
README.md
|
@ -1,66 +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.
|
||||
|
||||
## Usage
|
||||
|
||||
- [Feature wiki](https://git.froth.zone/sam/awl/wiki/Supported)
|
||||
- [Manpage](https://git.froth.zone/sam/awl/wiki/awl.1)
|
||||
[![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)
|
||||
|
||||
|
||||
## Building and installing
|
||||
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).
|
||||
|
||||
### From releases
|
||||
## Examples
|
||||
|
||||
Grab a prebuilt binary from the
|
||||
[release](https://git.froth.zone/sam/awl/releases) section.
|
||||
```shell
|
||||
# Query a domain over UDP
|
||||
awl example.com
|
||||
|
||||
### From source
|
||||
# Query a domain over HTTPS, print only the results
|
||||
awl example.com +https --short
|
||||
|
||||
Dependencies:
|
||||
|
||||
- Go >= 1.18
|
||||
- GNU/BSD make or Plan 9 mk
|
||||
- [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional, for manpage)
|
||||
|
||||
Make sure to recursively clone the repo:
|
||||
|
||||
```sh
|
||||
git clone --recursive https://git.froth.zone/sam/awl
|
||||
# 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
|
||||
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
|
||||
```
|
||||
|
||||
Alternatively, using `go install`:
|
||||
### Packaging
|
||||
|
||||
```sh
|
||||
go install git.froth.zone/sam/awl@latest
|
||||
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)!
|
||||
|
||||
### License
|
||||
### Mirrors
|
||||
|
||||
See [LICENSE](./LICENSE)
|
||||
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.
|
||||
|
|
170
cli/cli.go
170
cli/cli.go
|
@ -1,170 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
flag "github.com/stefansundin/go-zflag"
|
||||
)
|
||||
|
||||
// Parse the arguments passed into awl.
|
||||
func ParseCLI(version string) (Options, error) {
|
||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Println(`awl - drill, writ small
|
||||
|
||||
Usage: awl name [@server] [record]
|
||||
<name> domain, IP address, phone number
|
||||
<record> defaults to A
|
||||
|
||||
Arguments may be in any order, including flags.
|
||||
Dig-like +[no]commands are also supported, see dig(1) or dig -h
|
||||
|
||||
Options:`)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// CLI flag
|
||||
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.OptShorthandStr("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")
|
||||
dnssec = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
|
||||
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'))
|
||||
|
||||
aa = flag.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
ad = flag.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cd = flag.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qr = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rd = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
ra = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tc = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
z = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
|
||||
short = flag.Bool("short", false, "print just the results, equivalent to dig +short", 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'))
|
||||
|
||||
noQ = flag.Bool("no-question", false, "disable printing the question section")
|
||||
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 additonal section")
|
||||
noStats = flag.Bool("no-statistics", false, "disable printing the statistics section")
|
||||
|
||||
verbosity = flag.Int("verbosity", 0, "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
|
||||
err := flag.CommandLine.Parse(os.Args[1:])
|
||||
if err != nil {
|
||||
return Options{Logger: util.InitLogger(*verbosity)}, err
|
||||
}
|
||||
|
||||
opts := Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
Port: *port,
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
DNSSEC: *dnssec,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
AA: *aa,
|
||||
AD: *ad,
|
||||
TC: *tc,
|
||||
Z: *z,
|
||||
CD: *cd,
|
||||
QR: *qr,
|
||||
RD: *rd,
|
||||
RA: *ra,
|
||||
Reverse: *reverse,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
Request: helpers.Request{
|
||||
Type: dns.StringToType[strings.ToUpper(*qType)],
|
||||
Class: dns.StringToClass[strings.ToUpper(*class)],
|
||||
Name: *query,
|
||||
Timeout: time.Duration(*timeout * float32(time.Second)),
|
||||
Retries: *retry,
|
||||
},
|
||||
Display: Displays{
|
||||
Question: !*noQ,
|
||||
Answer: !*noAns,
|
||||
Authority: !*noAuth,
|
||||
Additional: !*noAdd,
|
||||
Statistics: !*noStats,
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
opts.Logger.Warn(err)
|
||||
return opts, err
|
||||
}
|
||||
opts.Logger.Info("Dig/Drill flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
if opts.Port == 0 {
|
||||
if opts.TLS || opts.QUIC {
|
||||
opts.Port = 853
|
||||
} else {
|
||||
opts.Port = 53
|
||||
}
|
||||
}
|
||||
opts.Logger.Info("Port set to", opts.Port)
|
||||
|
||||
// Set timeout to 0.5 seconds if set below 0.5
|
||||
if opts.Request.Timeout < (time.Second / 2) {
|
||||
opts.Request.Timeout = (time.Second / 2)
|
||||
}
|
||||
|
||||
if opts.Request.Retries < 0 {
|
||||
opts.Request.Retries = 0
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "-4"}
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, (opts.Port == 53))
|
||||
assert.Assert(t, opts.IPv4)
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestTLSPort(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "-T"}
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, (opts.Port == 853))
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--treebug"}
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestInvalidDig(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "+a"}
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
assert.ErrorContains(t, err, "dig: unknown flag")
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--version"}
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
assert.ErrorType(t, err, cli.ErrNotError)
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
}
|
||||
for _, test := range args {
|
||||
old := os.Args
|
||||
os.Args = test
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
os.Args = old
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetries(t *testing.T) {
|
||||
args := [][]string{
|
||||
{"awl", "+retry=-2"},
|
||||
{"awl", "+tries=-2"},
|
||||
{"awl", "--retries", "-2"},
|
||||
}
|
||||
for _, test := range args {
|
||||
old := os.Args
|
||||
os.Args = test
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
os.Args = old
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzFlags(f *testing.F) {
|
||||
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
|
||||
for _, tc := range testcases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
os.Args = []string{orig}
|
||||
//nolint:errcheck // Only make sure the program does not crash
|
||||
cli.ParseCLI("TEST")
|
||||
})
|
||||
}
|
117
cli/dig.go
117
cli/dig.go
|
@ -1,117 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Parse dig-like commands and set the options as such.
|
||||
func ParseDig(arg string, opts *Options) error {
|
||||
// returns true if the flag starts with a no
|
||||
isNo := !strings.HasPrefix(arg, "no")
|
||||
opts.Logger.Info("Setting", arg)
|
||||
|
||||
switch arg {
|
||||
// Set DNS query flags
|
||||
case "aa", "aaflag", "aaonly", "noaa", "noaaflag", "noaaonly":
|
||||
opts.AA = isNo
|
||||
case "ad", "adflag", "noad", "noadflag":
|
||||
opts.AD = isNo
|
||||
case "cd", "cdflag", "nocd", "nocdflag":
|
||||
opts.CD = isNo
|
||||
case "qr", "qrflag", "noqr", "noqrflag":
|
||||
opts.QR = isNo
|
||||
case "ra", "raflag", "nora", "noraflag":
|
||||
opts.RA = isNo
|
||||
case "rd", "rdflag", "recurse", "nord", "nordflag", "norecurse":
|
||||
opts.RD = isNo
|
||||
case "tc", "tcflag", "notc", "notcflag":
|
||||
opts.TC = isNo
|
||||
case "z", "zflag", "noz", "nozflag":
|
||||
opts.Z = isNo
|
||||
// End DNS query flags
|
||||
|
||||
// DNS-over-X
|
||||
case "dnssec", "nodnssec":
|
||||
opts.DNSSEC = isNo
|
||||
case "tcp", "vc", "notcp", "novc":
|
||||
opts.TCP = isNo
|
||||
case "ignore", "noignore":
|
||||
opts.Truncate = isNo
|
||||
case "tls", "notls":
|
||||
opts.TLS = isNo
|
||||
case "dnscrypt", "nodnscrypt":
|
||||
opts.DNSCrypt = isNo
|
||||
case "https", "nohttps":
|
||||
opts.HTTPS = isNo
|
||||
case "quic", "noquic":
|
||||
opts.QUIC = isNo
|
||||
// End DNS-over-X
|
||||
|
||||
// Formatting
|
||||
case "short", "noshort":
|
||||
opts.Short = isNo
|
||||
case "json", "nojson":
|
||||
opts.JSON = isNo
|
||||
case "xml", "noxml":
|
||||
opts.XML = isNo
|
||||
case "yaml", "noyaml":
|
||||
opts.YAML = isNo
|
||||
// End formatting
|
||||
|
||||
// Output
|
||||
// TODO: get this to work
|
||||
// case "comments", "nocomments":
|
||||
// opts.Display.Comments = isNo
|
||||
case "question", "noquestion":
|
||||
opts.Display.Question = isNo
|
||||
case "answer", "noanswer":
|
||||
opts.Display.Answer = isNo
|
||||
case "authority", "noauthority":
|
||||
opts.Display.Authority = isNo
|
||||
case "additional", "noadditional":
|
||||
opts.Display.Additional = isNo
|
||||
case "stats", "nostats":
|
||||
opts.Display.Statistics = isNo
|
||||
|
||||
case "all", "noall":
|
||||
opts.Display.Question = isNo
|
||||
opts.Display.Answer = isNo
|
||||
opts.Display.Authority = isNo
|
||||
opts.Display.Additional = isNo
|
||||
opts.Display.Statistics = isNo
|
||||
|
||||
default:
|
||||
// Recursive switch statements WOO
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "timeout"):
|
||||
timeout, err := strconv.Atoi(strings.Split(arg, "=")[1])
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("dig: Invalid timeout value")
|
||||
}
|
||||
|
||||
opts.Request.Timeout = time.Duration(timeout)
|
||||
|
||||
case strings.HasPrefix(arg, "retry"), strings.HasPrefix(arg, "tries"):
|
||||
tries, err := strconv.Atoi(strings.Split(arg, "=")[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("dig: Invalid retry value")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "tries") {
|
||||
tries++
|
||||
}
|
||||
|
||||
opts.Request.Retries = tries
|
||||
|
||||
default:
|
||||
return fmt.Errorf("dig: unknown flag %s given", arg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func FuzzDig(f *testing.F) {
|
||||
f.Log("ParseDig Fuzzing")
|
||||
seeds := []string{
|
||||
"aaflag", "aaonly", "noaaflag", "noaaonly",
|
||||
"adflag", "noadflag",
|
||||
"cdflag", "nocdflag",
|
||||
"qrflag", "noqrflag",
|
||||
"raflag", "noraflag",
|
||||
"rdflag", "recurse", "nordflag", "norecurse",
|
||||
"tcflag", "notcflag",
|
||||
"zflag", "nozflag",
|
||||
"dnssec", "nodnssec",
|
||||
"tcp", "vc", "notcp", "novc",
|
||||
"ignore", "noignore",
|
||||
"tls", "notls",
|
||||
"dnscrypt", "nodnscrypt",
|
||||
"https", "nohttps",
|
||||
"quic", "noquic",
|
||||
"short", "noshort",
|
||||
"json", "nojson",
|
||||
"xml", "noxml",
|
||||
"yaml", "noyaml",
|
||||
"question", "noquestion",
|
||||
"answer", "noanswer",
|
||||
"authority", "noauthority",
|
||||
"additional", "noadditional",
|
||||
"stats", "nostats",
|
||||
"all", "noall",
|
||||
"invalid",
|
||||
}
|
||||
for _, tc := range seeds {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
opts := new(cli.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseDig(orig, opts)
|
||||
if err != nil {
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/*
|
||||
The CLI part of the package, including both POSIX
|
||||
flag parsing and dig-like flag parsing.
|
||||
*/
|
||||
package cli
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
)
|
||||
|
||||
// CLI options structure.
|
||||
type Options struct {
|
||||
Logger *logawl.Logger // Logger
|
||||
Port int // DNS port
|
||||
IPv4 bool // Force IPv4
|
||||
IPv6 bool // Force IPv6
|
||||
DNSSEC bool // Enable DNSSEC
|
||||
TCP bool // Query with TCP
|
||||
DNSCrypt bool // Query over DNSCrypt
|
||||
TLS bool // Query over TLS
|
||||
HTTPS bool // Query over HTTPS
|
||||
QUIC bool // Query over QUIC
|
||||
Truncate bool // Ignore truncation
|
||||
AA bool // Set Authoratative Answer
|
||||
AD bool // Set Authenticated Data
|
||||
CD bool // Set CD
|
||||
QR bool // Set QueRy
|
||||
RD bool // Set Recursion Desired
|
||||
RA bool // Set Recursion Available
|
||||
TC bool // Set TC (TrunCated)
|
||||
Z bool // Set Z (Zero)
|
||||
Reverse bool // Make reverse query
|
||||
Verbosity int // Set logawl verbosity
|
||||
// HumanTTL bool // Make TTL human readable
|
||||
Short bool // Short output
|
||||
JSON bool // Outout as JSON
|
||||
XML bool // Output as XML
|
||||
YAML bool // Output at YAML
|
||||
|
||||
Display Displays // Display options
|
||||
Request helpers.Request // DNS reuqest
|
||||
}
|
||||
|
||||
// What to (and not to) display
|
||||
type Displays struct {
|
||||
// Comments bool
|
||||
Question bool // QUESTION SECTION
|
||||
Answer bool // ANSWER SECTION
|
||||
Authority bool // AUTHORITY SECTION
|
||||
Additional bool // ADDITIONAL SECTION
|
||||
Statistics bool // Query time, message size, etc.
|
||||
}
|
||||
|
||||
var ErrNotError = errors.New("not an error")
|
|
@ -0,0 +1,269 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
flag "github.com/stefansundin/go-zflag"
|
||||
)
|
||||
|
||||
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
|
||||
// struct.
|
||||
func ParseCLI(args []string, version string) (opts *util.Options, err error) {
|
||||
// Parse the standard flags
|
||||
opts, misc, err := parseFlags(args, version)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// Parse all the arguments that don't start with - or --
|
||||
// This includes the dig-style (+) options
|
||||
err = ParseMiscArgs(misc, opts)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
opts.Logger.Info("Dig/Drill flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
// Special options and exceptions time
|
||||
|
||||
if opts.Request.Port == 0 {
|
||||
if opts.TLS || opts.QUIC {
|
||||
opts.Request.Port = 853
|
||||
} else {
|
||||
opts.Request.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("Port set to", opts.Request.Port)
|
||||
|
||||
// Set timeout to 0.5 seconds if set below 0.5
|
||||
if opts.Request.Timeout < (time.Second / 2) {
|
||||
opts.Request.Timeout = (time.Second / 2)
|
||||
}
|
||||
|
||||
if opts.Request.Retries < 0 {
|
||||
opts.Request.Retries = 0
|
||||
}
|
||||
|
||||
if opts.Trace {
|
||||
if opts.TLS || opts.HTTPS || opts.QUIC {
|
||||
opts.Logger.Warn("Every query after the root query will only use UDP/TCP")
|
||||
}
|
||||
|
||||
opts.RD = true
|
||||
}
|
||||
|
||||
opts.Logger.Info("Options fully populated")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Everything that has to do with CLI flags goes here (the posix style, eg. -a and --bbbb).
|
||||
func parseFlags(args []string, version string) (opts *util.Options, flags []string, err error) {
|
||||
flagSet := flag.NewFlagSet(args[0], flag.ContinueOnError)
|
||||
|
||||
flagSet.Usage = func() {
|
||||
fmt.Println(`awl - drill, writ small
|
||||
|
||||
Usage: awl name [@server] [record]
|
||||
<name> domain, IP address, phone number
|
||||
<record> defaults to A
|
||||
|
||||
Arguments may be in any order, including flags.
|
||||
Dig-like +[no]commands are also supported, see dig(1) or dig -h
|
||||
|
||||
Options:`)
|
||||
flagSet.PrintDefaults()
|
||||
}
|
||||
|
||||
// CLI flags
|
||||
//
|
||||
// Remember, when adding a flag edit the manpage and the completions :)
|
||||
var (
|
||||
port = flagSet.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC)", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true))
|
||||
query = flagSet.String("query", "", "domain name to `query` (default: .)", flag.OptShorthand('q'))
|
||||
class = flagSet.String("class", "IN", "DNS `class` to query", flag.OptShorthand('c'))
|
||||
qType = flagSet.String("qType", "", "`type` to query (default: A)", flag.OptShorthand('t'))
|
||||
|
||||
ipv4 = flagSet.Bool("4", false, "force IPv4", flag.OptShorthand('4'))
|
||||
ipv6 = flagSet.Bool("6", false, "force IPv6", flag.OptShorthand('6'))
|
||||
reverse = flagSet.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x'))
|
||||
trace = flagSet.Bool("trace", false, "trace from the root")
|
||||
|
||||
timeout = flagSet.Float32("timeout", 5, "Timeout, in `seconds`")
|
||||
retry = flagSet.Int("retries", 2, "number of `times` to retry")
|
||||
|
||||
edns = flagSet.Bool("no-edns", false, "disable EDNS entirely")
|
||||
ednsVer = flagSet.Uint8("edns-ver", 0, "set EDNS version")
|
||||
dnssec = flagSet.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
|
||||
expire = flagSet.Bool("expire", false, "set EDNS expire")
|
||||
nsid = flagSet.Bool("nsid", false, "set EDNS NSID", flag.OptShorthand('n'))
|
||||
cookie = flagSet.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)")
|
||||
tcpKeepAlive = flagSet.Bool("keep-alive", false, "send EDNS TCP keep-alive")
|
||||
udpBufSize = flagSet.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b'))
|
||||
mbzflag = flagSet.String("zflag", "0", "set EDNS z-flag `value`")
|
||||
subnet = flagSet.String("subnet", "", "set EDNS client subnet")
|
||||
padding = flagSet.Bool("pad", false, "set EDNS padding")
|
||||
|
||||
badCookie = flagSet.Bool("no-bad-cookie", false, "ignore BADCOOKIE EDNS responses (default: retry with correct cookie")
|
||||
truncate = flagSet.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default: retry with TCP)")
|
||||
|
||||
tcp = flagSet.Bool("tcp", false, "use TCP")
|
||||
dnscrypt = flagSet.Bool("dnscrypt", false, "use DNSCrypt")
|
||||
tls = flagSet.Bool("tls", false, "use DNS-over-TLS", flag.OptShorthand('T'))
|
||||
https = flagSet.Bool("https", false, "use DNS-over-HTTPS", flag.OptShorthand('H'))
|
||||
quic = flagSet.Bool("quic", false, "use DNS-over-QUIC", flag.OptShorthand('Q'))
|
||||
|
||||
tlsHost = flagSet.String("tls-host", "", "Server name to use for TLS verification")
|
||||
noVerify = flagSet.Bool("tls-no-verify", false, "Disable TLS cert verification")
|
||||
|
||||
aaflag = flagSet.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
adflag = flagSet.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cdflag = flagSet.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qrflag = flagSet.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rdflag = flagSet.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
raflag = flagSet.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tcflag = flagSet.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
zflag = flagSet.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
|
||||
short = flagSet.Bool("short", false, "print just the results", flag.OptShorthand('s'))
|
||||
json = flagSet.Bool("json", false, "print the result(s) as JSON", flag.OptShorthand('j'))
|
||||
xml = flagSet.Bool("xml", false, "print the result(s) as XML", flag.OptShorthand('X'))
|
||||
yaml = flagSet.Bool("yaml", false, "print the result(s) as yaml", flag.OptShorthand('y'))
|
||||
|
||||
noC = flagSet.Bool("no-comments", false, "disable printing the comments")
|
||||
noQ = flagSet.Bool("no-question", false, "disable printing the question section")
|
||||
noOpt = flagSet.Bool("no-opt", false, "disable printing the OPT pseudosection")
|
||||
noAns = flagSet.Bool("no-answer", false, "disable printing the answer section")
|
||||
noAuth = flagSet.Bool("no-authority", false, "disable printing the authority section")
|
||||
noAdd = flagSet.Bool("no-additional", false, "disable printing the additional section")
|
||||
noStats = flagSet.Bool("no-statistics", false, "disable printing the statistics section")
|
||||
|
||||
verbosity = flagSet.Int("verbosity", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2"))
|
||||
versionFlag = flagSet.Bool("version", false, "print version information", flag.OptShorthand('V'))
|
||||
)
|
||||
|
||||
// Don't sort the flags when -h is given
|
||||
flagSet.SortFlags = true
|
||||
|
||||
// Parse the flags
|
||||
if err = flagSet.Parse(args[1:]); err != nil {
|
||||
return &util.Options{Logger: util.InitLogger(*verbosity)}, nil, fmt.Errorf("flag: %w", err)
|
||||
}
|
||||
|
||||
// TODO: DRY, dumb dumb.
|
||||
mbz, err := strconv.ParseInt(*mbzflag, 0, 16)
|
||||
if err != nil {
|
||||
return &util.Options{Logger: util.InitLogger(*verbosity)}, nil, fmt.Errorf("EDNS MBZ: %w", err)
|
||||
}
|
||||
|
||||
opts = &util.Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Trace: *trace,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
TLSHost: *tlsHost,
|
||||
TLSNoVerify: *noVerify,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
BadCookie: *badCookie,
|
||||
Reverse: *reverse,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
AA: *aaflag,
|
||||
AD: *adflag,
|
||||
TC: *tcflag,
|
||||
Z: *zflag,
|
||||
CD: *cdflag,
|
||||
QR: *qrflag,
|
||||
RD: *rdflag,
|
||||
RA: *raflag,
|
||||
},
|
||||
Request: util.Request{
|
||||
Type: dns.StringToType[strings.ToUpper(*qType)],
|
||||
Class: dns.StringToClass[strings.ToUpper(*class)],
|
||||
Name: *query,
|
||||
Timeout: time.Duration(*timeout * float32(time.Second)),
|
||||
Retries: *retry,
|
||||
Port: *port,
|
||||
},
|
||||
Display: util.Display{
|
||||
Comments: !*noC,
|
||||
Question: !*noQ,
|
||||
Opt: !*noOpt,
|
||||
Answer: !*noAns,
|
||||
Authority: !*noAuth,
|
||||
Additional: !*noAdd,
|
||||
Statistics: !*noStats,
|
||||
TTL: true,
|
||||
ShowClass: true,
|
||||
ShowQuery: false,
|
||||
HumanTTL: false,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: !*edns,
|
||||
Cookie: !*cookie,
|
||||
DNSSEC: *dnssec,
|
||||
BufSize: *udpBufSize,
|
||||
Version: *ednsVer,
|
||||
Expire: *expire,
|
||||
KeepOpen: *tcpKeepAlive,
|
||||
Nsid: *nsid,
|
||||
ZFlag: uint16(mbz & 0x7FFF),
|
||||
Padding: *padding,
|
||||
},
|
||||
HTTPSOptions: util.HTTPSOptions{
|
||||
Endpoint: "/dns-query",
|
||||
Get: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
if *subnet != "" {
|
||||
if err = util.ParseSubnet(*subnet, opts); err != nil {
|
||||
return opts, nil, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("POSIX flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Printf("awl version %s, built with %s\n", version, runtime.Version())
|
||||
|
||||
return opts, nil, util.ErrNotError
|
||||
}
|
||||
|
||||
flags = flagSet.Args()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var errNoArg = errors.New("no argument given")
|
||||
|
||||
type errInvalidArg struct {
|
||||
arg string
|
||||
}
|
||||
|
||||
func (e *errInvalidArg) Error() string {
|
||||
return fmt.Sprintf("digflags: invalid argument %s", e.arg)
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "-4"}
|
||||
|
||||
opts, err := cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, opts.IPv4)
|
||||
assert.Equal(t, opts.Request.Port, 53)
|
||||
}
|
||||
|
||||
func TestTLSPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "-T"}
|
||||
|
||||
opts, err := cli.ParseCLI(args, "TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Port, 853)
|
||||
}
|
||||
|
||||
func TestValidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
args []string
|
||||
want uint16
|
||||
}{
|
||||
{[]string{"awl", "--subnet", "127.0.0.1/32"}, uint16(1)},
|
||||
{[]string{"awl", "--subnet", "0"}, uint16(1)},
|
||||
{[]string{"awl", "--subnet", "::/0"}, uint16(2)},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.args[2], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts, err := cli.ParseCLI(test.args, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, test.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--subnet", "/"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
assert.ErrorContains(t, err, "EDNS subnet")
|
||||
}
|
||||
|
||||
func TestMBZ(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--zflag", "G"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "EDNS MBZ")
|
||||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--treebug"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
}
|
||||
|
||||
func TestInvalidDig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "+a"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "digflags: invalid argument")
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"awl", "--version"}
|
||||
|
||||
_, err := cli.ParseCLI(args, "test")
|
||||
|
||||
assert.ErrorIs(t, err, util.ErrNotError)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "+retry=-2"},
|
||||
{"awl", "+tries=-2"},
|
||||
{"awl", "--retries", "-2"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := [][]string{
|
||||
{"awl", "-H", "@dns.froth.zone/dns-query"},
|
||||
{"awl", "+https", "@dns.froth.zone"},
|
||||
}
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run(test[1], func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opt, err := cli.ParseCLI(test, "TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Server, "dns.froth.zone")
|
||||
assert.Equal(t, opt.HTTPSOptions.Endpoint, "/dns-query")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzFlags(f *testing.F) {
|
||||
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
|
||||
|
||||
for _, tc := range testcases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
// Get rid of outputs
|
||||
|
||||
args := []string{"awl", orig}
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseCLI(args, "TEST")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
)
|
||||
|
||||
// ParseDig parses commands from the popular DNS tool dig.
|
||||
// All dig commands are taken from https://man.openbsd.org/dig.1 as the source of their functionality.
|
||||
//
|
||||
// [no]flags are supported just as flag are and are disabled as such.
|
||||
func ParseDig(arg string, opts *util.Options) error {
|
||||
// returns true if the flag starts with a no
|
||||
isNo := !strings.HasPrefix(arg, "no")
|
||||
if !isNo {
|
||||
arg = strings.TrimPrefix(arg, "no")
|
||||
}
|
||||
|
||||
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
|
||||
case "ad", "adflag":
|
||||
opts.AD = isNo
|
||||
case "cd", "cdflag":
|
||||
opts.CD = isNo
|
||||
case "qrflag":
|
||||
opts.QR = isNo
|
||||
case "ra", "raflag":
|
||||
opts.RA = isNo
|
||||
case "rd", "rdflag", "recurse":
|
||||
opts.RD = isNo
|
||||
case "tc", "tcflag":
|
||||
opts.TC = isNo
|
||||
case "z", "zflag":
|
||||
opts.Z = isNo
|
||||
// End DNS query flags
|
||||
|
||||
case "qr":
|
||||
opts.Display.ShowQuery = isNo
|
||||
case "ttlunits":
|
||||
opts.Display.HumanTTL = isNo
|
||||
case "ttl", "ttlid":
|
||||
opts.Display.TTL = isNo
|
||||
case "class":
|
||||
opts.Display.ShowClass = isNo
|
||||
|
||||
// EDNS queries
|
||||
case "do", "dnssec":
|
||||
opts.EDNS.DNSSEC = isNo
|
||||
case "expire":
|
||||
opts.EDNS.Expire = isNo
|
||||
case "cookie":
|
||||
opts.EDNS.Cookie = isNo
|
||||
case "keepopen", "keepalive":
|
||||
opts.EDNS.KeepOpen = isNo
|
||||
case "nsid":
|
||||
opts.EDNS.Nsid = isNo
|
||||
case "padding":
|
||||
opts.EDNS.Padding = isNo
|
||||
// End EDNS queries
|
||||
|
||||
// DNS-over-X
|
||||
case "tcp", "vc":
|
||||
opts.TCP = isNo
|
||||
case "ignore":
|
||||
opts.Truncate = isNo
|
||||
case "badcookie":
|
||||
opts.BadCookie = !isNo
|
||||
case "tls":
|
||||
opts.TLS = isNo
|
||||
case "dnscrypt":
|
||||
opts.DNSCrypt = isNo
|
||||
case "quic":
|
||||
opts.QUIC = isNo
|
||||
// End DNS-over-X
|
||||
|
||||
// Formatting
|
||||
case "short":
|
||||
opts.Short = isNo
|
||||
case "identify":
|
||||
opts.Identify = isNo
|
||||
case "json":
|
||||
opts.JSON = isNo
|
||||
case "xml":
|
||||
opts.XML = isNo
|
||||
case "yaml":
|
||||
opts.YAML = isNo
|
||||
// End formatting
|
||||
|
||||
// Output
|
||||
case "comments":
|
||||
opts.Display.Comments = isNo
|
||||
case "question":
|
||||
opts.Display.Question = isNo
|
||||
case "opt":
|
||||
opts.Display.Opt = isNo
|
||||
case "answer":
|
||||
opts.Display.Answer = isNo
|
||||
case "authority":
|
||||
opts.Display.Authority = isNo
|
||||
case "additional":
|
||||
opts.Display.Additional = isNo
|
||||
case "stats":
|
||||
opts.Display.Statistics = isNo
|
||||
case "all":
|
||||
opts.Display.Comments = isNo
|
||||
opts.Display.Question = isNo
|
||||
opts.Display.Opt = isNo
|
||||
opts.Display.Answer = isNo
|
||||
opts.Display.Authority = isNo
|
||||
opts.Display.Additional = isNo
|
||||
opts.Display.Statistics = isNo
|
||||
case "idnin", "idnout":
|
||||
opts.Display.UcodeTranslate = isNo
|
||||
|
||||
default:
|
||||
if err := parseDigEq(isNo, arg, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// For flags that contain "=".
|
||||
func parseDigEq(startNo bool, arg string, opts *util.Options) error {
|
||||
// Recursive switch statements WOO
|
||||
arg, val, isSplit := strings.Cut(arg, "=")
|
||||
switch arg {
|
||||
case "time", "timeout":
|
||||
if isSplit && val != "" {
|
||||
timeout, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: timeout : %w", err)
|
||||
}
|
||||
|
||||
opts.Request.Timeout = time.Duration(timeout)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: timeout: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "retry", "tries":
|
||||
if isSplit && val != "" {
|
||||
tries, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: retry: %w", err)
|
||||
}
|
||||
|
||||
opts.Request.Retries = tries
|
||||
|
||||
// TODO: Is there a better way to do this?
|
||||
if arg == "tries" {
|
||||
opts.Request.Retries--
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: retry: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "bufsize":
|
||||
if isSplit && val != "" {
|
||||
size, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS UDP: %w", err)
|
||||
}
|
||||
|
||||
opts.EDNS.BufSize = uint16(size)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: EDNS UDP: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "ednsflags":
|
||||
if isSplit && val != "" {
|
||||
ver, err := strconv.ParseInt(val, 0, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS flag: %w", err)
|
||||
}
|
||||
|
||||
// Ignore setting DO bit
|
||||
opts.EDNS.ZFlag = uint16(ver & 0x7FFF)
|
||||
} else {
|
||||
opts.EDNS.ZFlag = 0
|
||||
}
|
||||
|
||||
case "edns":
|
||||
opts.EDNS.EnableEDNS = startNo
|
||||
|
||||
if isSplit && val != "" {
|
||||
ver, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS version: %w", err)
|
||||
}
|
||||
|
||||
opts.EDNS.Version = uint8(ver)
|
||||
} else {
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS Subnet: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: EDNS Subnet: %w", errNoArg)
|
||||
}
|
||||
|
||||
default:
|
||||
return &errInvalidArg{arg}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func FuzzDig(f *testing.F) {
|
||||
f.Log("ParseDig Fuzzing")
|
||||
|
||||
seeds := []string{
|
||||
"aaflag", "aaonly", "noaaflag", "noaaonly",
|
||||
"adflag", "noadflag",
|
||||
"cdflag", "nocdflag",
|
||||
"qrflag", "noqrflag",
|
||||
"raflag", "noraflag",
|
||||
"rdflag", "recurse", "nordflag", "norecurse",
|
||||
"tcflag", "notcflag",
|
||||
"zflag", "nozflag",
|
||||
"qr", "noqr",
|
||||
"ttlunits", "nottlunits",
|
||||
"ttlid", "nottlid",
|
||||
"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", "nocookie",
|
||||
"keepopen", "keepalive", "nokeepopen", "nokeepalive",
|
||||
"nsid", "nonsid",
|
||||
"padding", "nopadding",
|
||||
"bufsize=512", "bufsize=a", "bufsize",
|
||||
"time=5", "timeout=a", "timeout",
|
||||
"retry=a", "retry=3", "retry",
|
||||
"tries=2", "tries=b", "tries",
|
||||
"tcp", "vc", "notcp", "novc",
|
||||
"ignore", "noignore",
|
||||
"badcookie", "nobadcookie",
|
||||
"tls", "notls",
|
||||
"dnscrypt", "nodnscrypt",
|
||||
"https", "https=/dns", "https-get", "https-get=/", "nohttps",
|
||||
"quic", "noquic",
|
||||
"short", "noshort",
|
||||
"identify", "noidentify",
|
||||
"json", "nojson",
|
||||
"xml", "noxml",
|
||||
"yaml", "noyaml",
|
||||
"comments", "nocomments",
|
||||
"question", "noquestion",
|
||||
"opt", "noopt",
|
||||
"answer", "noanswer",
|
||||
"authority", "noauthority",
|
||||
"additional", "noadditional",
|
||||
"stats", "nostats",
|
||||
"all", "noall",
|
||||
"idnout", "noidnout",
|
||||
"class", "noclass",
|
||||
"trace", "notrace",
|
||||
"invalid",
|
||||
}
|
||||
|
||||
for _, tc := range seeds {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
// Get rid of outputs
|
||||
// os.Stdout = os.NewFile(0, os.DevNull)
|
||||
// os.Stderr = os.NewFile(0, os.DevNull)
|
||||
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
if err := cli.ParseDig(orig, opts); err != nil {
|
||||
assert.ErrorContains(t, err, "digflags:")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
Package cli is the CLI part of the package, including both POSIX
|
||||
flag parsing and dig-like flag parsing.
|
||||
*/
|
||||
package cli
|
|
@ -7,67 +7,99 @@ 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"
|
||||
)
|
||||
|
||||
// Parse the wildcard arguments, drill style.
|
||||
func ParseMiscArgs(args []string, opts *Options) error {
|
||||
var err error
|
||||
|
||||
// ParseMiscArgs parses the wildcard arguments, dig style.
|
||||
// Only one command is supported at a time, so any extra information overrides previous.
|
||||
func ParseMiscArgs(args []string, opts *util.Options) error {
|
||||
for _, arg := range args {
|
||||
r, ok := dns.StringToType[strings.ToUpper(arg)]
|
||||
|
||||
switch {
|
||||
// If it starts with @, it's a DNS server
|
||||
case strings.HasPrefix(arg, "@"):
|
||||
arg = arg[1:]
|
||||
// Automatically set flags based on URI header
|
||||
opts.Logger.Info(arg, "detected as a server")
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "tls://"):
|
||||
opts.TLS = true
|
||||
opts.Request.Server = arg[6:]
|
||||
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 = arg[7:]
|
||||
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
|
||||
case strings.HasPrefix(arg, "+"):
|
||||
opts.Logger.Info(arg, "detected as a dig query")
|
||||
|
||||
if err := ParseDig(strings.ToLower(arg[1:]), opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Domain names
|
||||
case strings.Contains(arg, "."):
|
||||
var err error
|
||||
|
||||
opts.Logger.Info(arg, "detected as a domain name")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
|
||||
// DNS query type
|
||||
case ok:
|
||||
opts.Logger.Info(arg, "detected as a type")
|
||||
// If it's a DNS request, it's a DNS request (obviously)
|
||||
opts.Request.Type = r
|
||||
case strings.HasPrefix(arg, "+"):
|
||||
opts.Logger.Info(arg, "detected as a dig query")
|
||||
// Dig-style +queries
|
||||
err = ParseDig(strings.ToLower(arg[1:]), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Domain?
|
||||
default:
|
||||
var err error
|
||||
|
||||
opts.Logger.Info(arg, "is unknown. Assuming domain")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,16 +108,16 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
if opts.Request.Name == "" {
|
||||
opts.Logger.Info("Domain not specified, making a default")
|
||||
opts.Request.Name = "."
|
||||
|
||||
if opts.Request.Type == 0 {
|
||||
opts.Logger.Info("Query not specified, making an \"NS\" query")
|
||||
opts.Request.Type = dns.StringToType["NS"]
|
||||
}
|
||||
} else {
|
||||
} else if opts.Request.Type == 0 {
|
||||
opts.Logger.Info("Query not specified, making an \"A\" query")
|
||||
if opts.Request.Type == 0 {
|
||||
opts.Request.Type = dns.StringToType["A"]
|
||||
}
|
||||
opts.Request.Type = dns.StringToType["A"]
|
||||
}
|
||||
//
|
||||
|
||||
if opts.Request.Server == "" {
|
||||
opts.Logger.Info("Server not specified, selecting a default")
|
||||
// Set "defaults" for each if there is no input
|
||||
|
@ -96,55 +128,69 @@ func ParseMiscArgs(args []string, opts *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 default")
|
||||
opts.Request.Server = "95.216.99.249"
|
||||
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
|
||||
harmful:
|
||||
for _, srv := range resolv.Servers {
|
||||
if opts.IPv4 {
|
||||
switch {
|
||||
case opts.IPv4:
|
||||
if strings.Contains(srv, ".") {
|
||||
opts.Request.Server = srv
|
||||
break
|
||||
|
||||
break harmful
|
||||
}
|
||||
} else if opts.IPv6 {
|
||||
case opts.IPv6:
|
||||
if strings.Contains(srv, ":") {
|
||||
opts.Request.Server = srv
|
||||
break
|
||||
|
||||
break harmful
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
//#nosec -- This isn't used for anything secure
|
||||
opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))]
|
||||
break
|
||||
|
||||
break harmful
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("DNS server set to", opts.Request.Server)
|
||||
|
||||
// Make reverse adresses proper addresses
|
||||
// Make reverse addresses proper addresses
|
||||
if opts.Reverse {
|
||||
var err error
|
||||
|
||||
opts.Logger.Info("Making reverse DNS query proper *.arpa domain")
|
||||
|
||||
if dns.TypeToString[opts.Request.Type] == "A" {
|
||||
opts.Request.Type = dns.StringToType["PTR"]
|
||||
}
|
||||
|
||||
opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reverse DNS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(opts.Request.Name, ".") {
|
||||
opts.Request.Name = fmt.Sprintf("%s.", opts.Request.Name)
|
||||
opts.Logger.Debug("Domain made canonical")
|
||||
|
||||
opts.Logger.Info("Domain made canonical")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -3,25 +3,25 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func TestParseArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{
|
||||
"go.dev",
|
||||
"AAAA",
|
||||
"@1.1.1.1",
|
||||
"+ignore",
|
||||
}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -33,8 +33,9 @@ func TestParseArgs(t *testing.T) {
|
|||
|
||||
func TestParseNoInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -44,10 +45,11 @@ func TestParseNoInput(t *testing.T) {
|
|||
|
||||
func TestParseA(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{
|
||||
"golang.org.",
|
||||
}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -57,8 +59,9 @@ func TestParseA(t *testing.T) {
|
|||
|
||||
func TestParsePTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.8.8.8"}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
|
@ -68,8 +71,9 @@ func TestParsePTR(t *testing.T) {
|
|||
|
||||
func TestParseInvalidPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.88.8"}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
|
@ -78,24 +82,27 @@ func TestParseInvalidPTR(t *testing.T) {
|
|||
|
||||
func TestDefaultServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
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(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
switch test.in {
|
||||
case "DNSCRYPT":
|
||||
case "DNSCrypt":
|
||||
opts.DNSCrypt = true
|
||||
case "TLS":
|
||||
opts.TLS = true
|
||||
|
@ -113,33 +120,51 @@ func TestDefaultServer(t *testing.T) {
|
|||
|
||||
func TestFlagSetting(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
in []string
|
||||
}{
|
||||
{[]string{"@sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"}},
|
||||
{[]string{"@tls://dns.google"}},
|
||||
{[]string{"@https://dns.cloudflare.com/dns-query"}},
|
||||
{[]string{"@quic://dns.adguard.com"}},
|
||||
}
|
||||
for i, test := range tests {
|
||||
test := test
|
||||
i := i
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
opts := new(cli.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
t.Parallel()
|
||||
err := cli.ParseMiscArgs(test.in, opts)
|
||||
assert.NilError(t, err)
|
||||
switch i {
|
||||
case 0:
|
||||
assert.Assert(t, opts.DNSCrypt)
|
||||
case 1:
|
||||
assert.Assert(t, opts.TLS)
|
||||
case 2:
|
||||
assert.Assert(t, opts.HTTPS)
|
||||
case 3:
|
||||
assert.Assert(t, opts.QUIC)
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
expected string
|
||||
over string
|
||||
}{
|
||||
{"@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 _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.over, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
|
||||
err := cli.ParseMiscArgs([]string{test.in}, opts)
|
||||
assert.NilError(t, err)
|
||||
switch {
|
||||
case strings.HasPrefix(test.over, "DNSCrypt"):
|
||||
assert.Assert(t, opts.DNSCrypt)
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "TLS"):
|
||||
assert.Assert(t, opts.TLS)
|
||||
assert.Equal(t, opts.Request.Server, test.expected)
|
||||
case strings.HasPrefix(test.over, "HTTPS"):
|
||||
assert.Assert(t, opts.HTTPS)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -153,14 +178,18 @@ func FuzzParseArgs(f *testing.F) {
|
|||
"+ignore",
|
||||
"e",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, arg string) {
|
||||
// Get rid of outputs
|
||||
|
||||
args := []string{arg}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
//nolint:errcheck // Only make sure the program does not crash
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseMiscArgs(args, opts)
|
||||
})
|
||||
}
|
|
@ -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)'
|
|
@ -0,0 +1,112 @@
|
|||
#compdef awl
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
local curcontext="$curcontext" state line expl
|
||||
local -a alts args
|
||||
[[ -prefix + ]] && args=(
|
||||
'*+'{no,}'tcp[use TCP instead of UDP for queries]'
|
||||
'*+'{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]: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)'
|
||||
'*+noedns[clear EDNS version to be sent]'
|
||||
'*+ednsflags=[set EDNS flags bits]:flags'
|
||||
# '*+ednsopt=[specify EDNS option]:code point'
|
||||
'*+noedns[clear EDNS options to be sent]'
|
||||
'*+'{no,}'expire[send an EDNS Expire option]'
|
||||
# '*+'{no,}'idnin[set processing of IDN domain names on input]'
|
||||
'*+'{no,}'idnout[set conversion of IDN puny code on output]'
|
||||
'*+'{no,}'keepalive[request EDNS TCP keepalive]'
|
||||
'*+'{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,}'cmd[print initial comment in output]'
|
||||
'*+'{no,}'short[print terse output]'
|
||||
'*+'{no,}'identify[print IP and port of responder]'
|
||||
'*+'{no,}'comments[print comment lines in output]'
|
||||
'*+'{no,}'stats[print statistics]'
|
||||
'*+padding[set padding block size]'
|
||||
'*+'{no,}'qr[print query as it was sent]'
|
||||
'*+'{no,}'question[print question section of a query]'
|
||||
'*+'{no,}'raflag[set RA flag in the query]'
|
||||
'*+'{no,}'answer[print answer section of a reply]'
|
||||
'*+'{no,}'authority[print authority section of a reply]'
|
||||
'*+'{no,}'all[set all print/display flags]'
|
||||
'*+'{no,}'subnet=[send EDNS client subnet option]:addr/prefix-length'
|
||||
'*+'{no,}'tcflag[set TC flag in the query]'
|
||||
'*+time=[set query timeout]:timeout (seconds) [1]'
|
||||
'*+timeout=[set query timeout]:timeout (seconds) [1]'
|
||||
'*+tries=[specify number of UDP query attempts]:tries [3]'
|
||||
'*+retry=[specify number of UDP query retries]:retries [2]'
|
||||
# '*+'{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,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]'
|
||||
'*+'{no,}'ttlunits[display the TTL in human-readable units]'
|
||||
# '*+'{no,}'unknownformat[print RDATA in RFC 3597 "unknown" format]'
|
||||
'*+'{no,}'json[present the results as JSON]'
|
||||
'*+'{no,}'xml[present the results as XML]'
|
||||
'*+'{no,}'yaml[present the results as YAML]'
|
||||
'*+'{no,}'zflag[set Z flag in query]'
|
||||
)
|
||||
# TODO: Add the regular (POSIX/GNU) flags
|
||||
_arguments -s -C $args \
|
||||
'(- *)-'{h,-help}'[display help information]' \
|
||||
'(- *)-'{V,-version}'[display version information]' \
|
||||
'-'{v,-verbosity}'=+[set verbosity to custom level]:verbosity:compadd -M "m\:{\-1-3}={\-1-3}" - \-1 0 1 2 3' \
|
||||
'-'{v,-verbosity}'+[set verbosity to info]' \
|
||||
'*-'{p,-port}'+[specify port number]:port:_ports' \
|
||||
'*-'{q,-query}'+[specify host name to query]:host:_hosts' \
|
||||
'*-'{c,-class}'+[specify class]:class:compadd -M "m\:{a-z}={A-Z}" - IN CS CH HS' \
|
||||
'*-'{t,-qType}'+[specify type]:type:_dns_types' \
|
||||
'*-4+[force IPv4 only]' \
|
||||
'*-6+[force IPv6 only]' \
|
||||
'*-'{x,-reverse}'+[reverse lookup]' \
|
||||
'*--timeout+[timeout in seconds]:number [1]' \
|
||||
'*--retries+[specify number of query retries]:number [2]' \
|
||||
'*--no-edns+[disable EDNS]' \
|
||||
'*--edns-ver+[specify EDNS version for query]:version (0-255) [0]' \
|
||||
'*-'{D,-dnssec}'+[enable DNSSEC]' \
|
||||
'*--expire+[send EDNS expire]' \
|
||||
'*-'{n,-nsid}'+[include EDNS name server ID request in query]' \
|
||||
'*--no-cookie+[disable sending EDNS cookie]' \
|
||||
'*--keep-alive+[request EDNS TCP keepalive]' \
|
||||
'*-'{b,-buffer-size}'+[specify UDP buffer size]:size (bytes) [1232]' \
|
||||
'*--zflag+[set EDNS z-flag]:decimal, hex or octal [0]' \
|
||||
'*--subnet+[set EDNS client subnet]:addr/prefix-length' \
|
||||
'*--no-truncate+[ignore truncation in UDP responses]' \
|
||||
'*--tcp+[use TCP instead of UDP for queries]' \
|
||||
'*--dnscrypt+[use DNSCrypt for queries]' \
|
||||
'*-'{T,-tls}'+[use DNS-over-TLS for queries]' \
|
||||
'*-'{H,-https}'+[use DNS-over-HTTPS for queries]' \
|
||||
'*-'{Q,-quic}'+[use DNS-over-QUIC for queries]' \
|
||||
'*--tls-no-verify+[disable TLS verification]' \
|
||||
'*--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]' \
|
||||
'*-'{y,-yaml}'+[present the results as YAML]' \
|
||||
'*--trace+[trace from the root]' \
|
||||
'*: :->args' && ret=0
|
||||
|
||||
if [[ -n $state ]]; then
|
||||
if compset -P @; then
|
||||
_wanted hosts expl 'DNS server' _hosts && ret=0;
|
||||
else
|
||||
_alternative 'hosts:host:_hosts' 'types:query type:_dns_types' && ret=0
|
||||
fi
|
||||
fi
|
||||
|
||||
return ret
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
Helper functions for getting local nameservers
|
||||
Package conf contains helper functions for getting local nameservers
|
||||
|
||||
Currently supported: Unix, Windows, Plan 9 (tested on 9front)
|
||||
*/
|
||||
|
|
|
@ -1,29 +1,44 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// GetDNSConfig gets DNS information from Plan 9, because it's different from UNIX and Windows.
|
||||
//
|
||||
// Plan 9 stores its network data in /net/ndb, which seems to be formatted a specific way
|
||||
// Yoink it and use it.
|
||||
//
|
||||
// See ndb(7).
|
||||
func GetPlan9Config(str string) (*dns.ClientConfig, error) {
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
dat, err := os.ReadFile("/net/ndb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read ndb: %w", err)
|
||||
}
|
||||
|
||||
str := string(dat)
|
||||
|
||||
str = strings.ReplaceAll(str, "\n", "")
|
||||
spl := strings.FieldsFunc(str, splitChars)
|
||||
|
||||
var servers []string
|
||||
|
||||
for _, option := range spl {
|
||||
if strings.HasPrefix(option, "dns=") {
|
||||
servers = append(servers, strings.TrimPrefix(option, "dns="))
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return nil, fmt.Errorf("plan9: no DNS servers found")
|
||||
return nil, errPlan9
|
||||
}
|
||||
|
||||
// TODO: read more about how customizable Plan 9 is
|
||||
|
@ -38,3 +53,5 @@ func GetPlan9Config(str string) (*dns.ClientConfig, error) {
|
|||
func splitChars(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
var errPlan9 = errors.New("plan9Config: no DNS servers found")
|
||||
|
|
|
@ -1,52 +1,25 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
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()
|
||||
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"},
|
||||
|
||||
if runtime.GOOS != "plan9" {
|
||||
t.Skip("Not running Plan 9, skipping")
|
||||
}
|
||||
|
||||
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])
|
||||
})
|
||||
}
|
||||
conf, err := conf.GetDNSConfig()
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
22
conf/unix.go
22
conf/unix.go
|
@ -1,24 +1,22 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !windows
|
||||
//go:build unix || (!windows && !plan9 && !js && !zos)
|
||||
|
||||
// FIXME: Can remove the or on the preprocessor when Go 1.18 becomes obsolete
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Get the DNS configuration, either from /etc/resolv.conf or somewhere else
|
||||
// GetDNSConfig gets the DNS configuration, either from /etc/resolv.conf or somewhere else.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
dat, err := os.ReadFile("/net/ndb")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetPlan9Config(string(dat))
|
||||
} else {
|
||||
return dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix config: %w", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !windows
|
||||
//go:build unix || (!windows && !plan9 && !js && !zos)
|
||||
|
||||
// FIXME: Can remove the or on the preprocessor when Go 1.18 becomes obsolete
|
||||
|
||||
package conf_test
|
||||
|
||||
|
@ -7,14 +9,17 @@ 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" {
|
||||
t.Skip("Not running Windows, skipping")
|
||||
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")
|
||||
}
|
||||
|
||||
conf, err := conf.GetDNSConfig()
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build js
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// GetDNSConfig doesn't do anything, because it is impossible (and bad security)
|
||||
// if it could, as that is the definition of a DNS leak.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
var errNotImplemented = errors.New("not implemented")
|
24
conf/win.go
24
conf/win.go
|
@ -4,6 +4,7 @@
|
|||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
|
@ -16,37 +17,48 @@ import (
|
|||
https://gist.github.com/moloch--/9fb1c8497b09b45c840fe93dd23b1e98
|
||||
*/
|
||||
|
||||
// WindowsDnsClientConfig - returns all DNS server addresses using windows fuckery.
|
||||
// GetDNSConfig (Windows version) returns all DNS server addresses using windows fuckery.
|
||||
//
|
||||
// Here be dragons.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
l := uint32(20000)
|
||||
b := make([]byte, l)
|
||||
length := uint32(100000)
|
||||
byt := make([]byte, length)
|
||||
|
||||
// Windows is an utter fucking trash fire of an operating system.
|
||||
if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l); err != nil {
|
||||
return nil, err
|
||||
//nolint:gosec // This is necessary unless we want to drop 1.18
|
||||
if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&byt[0])), &length); err != nil {
|
||||
return nil, fmt.Errorf("config, windows: %w", err)
|
||||
}
|
||||
|
||||
var addresses []*windows.IpAdapterAddresses
|
||||
for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); addr != nil; addr = addr.Next {
|
||||
//nolint:gosec // This is necessary unless we want to drop 1.18
|
||||
for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&byt[0])); addr != nil; addr = addr.Next {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
|
||||
resolvers := map[string]bool{}
|
||||
|
||||
for _, addr := range addresses {
|
||||
for next := addr.FirstUnicastAddress; next != nil; next = next.Next {
|
||||
if addr.OperStatus != windows.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
|
||||
if next.Address.IP() != nil {
|
||||
for dnsServer := addr.FirstDnsServerAddress; dnsServer != nil; dnsServer = dnsServer.Next {
|
||||
ip := dnsServer.Address.IP()
|
||||
|
||||
if ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil && strings.HasPrefix(ip.To16().String(), "fec0:") {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers[ip.String()] = true
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,19 @@ import (
|
|||
"runtime"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestWinConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Not running Windows, skipping")
|
||||
}
|
||||
|
||||
conf, err := conf.GetDNSConfig()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
}
|
||||
|
|
244
doc/awl.1
244
doc/awl.1
|
@ -1,244 +0,0 @@
|
|||
.\" Generated by scdoc 1.11.2
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "awl" "1" "2022-07-25"
|
||||
.PP
|
||||
.SH NAME
|
||||
awl - DNS lookup tool
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
\fBawl\fR [ \fIOPTIONS\fR ] \fIname\fR [ \fI@server\fR ] [ \fItype\fR ]
|
||||
.br
|
||||
where
|
||||
.PP
|
||||
\fIname\fR is the query to make (\fBexample: froth.\&zone\fR)
|
||||
.br
|
||||
\fI@server\fR is the server to query (\fBexample: dns.\&froth.\&zone\fR)
|
||||
.br
|
||||
\fItype\fR is the DNS resource type (\fBexample: AAAA\fR)
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries,
|
||||
much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes,
|
||||
typically used in leatherworking.\&
|
||||
.PP
|
||||
\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including
|
||||
some more recent RFCs and output options.\& \fBawl\fR is still heavily
|
||||
Work-In-Progress so some features may get added or removed.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.RS 4
|
||||
Dig-like +[no]flags are supported, see dig(1)
|
||||
.PP
|
||||
\fB-D\fR, \fB--dnssec\fR, \fB+dnssec\fR
|
||||
.br
|
||||
Enable DNSSEC.\& This needs to be manually enabled.\&
|
||||
.PP
|
||||
\fB-v\fR \fIvalue\fR
|
||||
.br
|
||||
Set verbosity (currently WIP)
|
||||
.PP
|
||||
\fB-V\fR
|
||||
.br
|
||||
Print the version and exit.\&
|
||||
.PP
|
||||
\fB-h\fR
|
||||
.br
|
||||
Show a "short" help message.\&
|
||||
.PP
|
||||
.RE
|
||||
.SS Query Options
|
||||
.RS 4
|
||||
\fB-4\fR
|
||||
.br
|
||||
Only make query over IPv4
|
||||
.PP
|
||||
\fB-6\fR
|
||||
.br
|
||||
Only make query over IPv6
|
||||
.PP
|
||||
\fB-p\fR, \fB--port\fR \fIport\fR
|
||||
.br
|
||||
Sets the port to query.\&
|
||||
.br
|
||||
|
||||
.br
|
||||
\fIDefault Ports\fR:
|
||||
.RS 4
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fI53\fR for \fBUDP\fR and \fBTCP\fR
|
||||
.IP \(bu 4
|
||||
\fI853\fR for \fBTLS\fR and \fBQUIC\fR
|
||||
.IP \(bu 4
|
||||
\fI443\fR for \fBHTTPS\fR
|
||||
.PD
|
||||
.PP
|
||||
.RE
|
||||
\fB-q\fR, \fB--query\fR \fIdomain\fR
|
||||
.br
|
||||
Domain to query (eg.\& example.\&com)
|
||||
.PP
|
||||
\fB-c\fR, \fB--class\fR \fIclass\fR
|
||||
.br
|
||||
DNS class to query (eg.\& IN, CH)
|
||||
.PP
|
||||
\fB-t\fR, \fB--qType\fR \fItype\fR
|
||||
.br
|
||||
DNS type to query (eg.\& A, NS)
|
||||
.PP
|
||||
\fB--no-truncate\fR, \fB+ignore\fR
|
||||
.br
|
||||
Ignore UDP truncation (by default, awl \fIretries with TCP\fR)
|
||||
.PP
|
||||
\fB--tcp\fR, \fB+tcp\fR, \fB+vc\fR
|
||||
.br
|
||||
Use TCP for the query (see \fIRFC 7766\fR)
|
||||
.PP
|
||||
\fB--dnscrypt\fR, \fB+dnscrypt\fR
|
||||
.br
|
||||
Use DNSCrypt
|
||||
.PP
|
||||
\fB-T\fR, \fB--tls\fR, \fB+tls\fR
|
||||
.br
|
||||
Use DNS-over-TLS, implies \fB--tcp\fR (see \fIRFC 7858\fR)
|
||||
.PP
|
||||
\fB-H\fR.\& \fB--https\fR, \fB+https\fR
|
||||
.br
|
||||
Use DNS-over-HTTPS (see \fIRFC 8484\fR)
|
||||
.PP
|
||||
\fB-Q\fR.\& \fB--quic\fR, \fB+quic\fR
|
||||
.br
|
||||
Use DNS-over-QUIC (see \fIRFC 9250\fR)
|
||||
.PP
|
||||
\fB-x\fR, \fB--reverse\fR
|
||||
.br
|
||||
Do a reverse lookup.\& Sets default \fItype\fR to PTR.\&
|
||||
.br
|
||||
\fBawl\fR automatically makes an IP or phone number canonical.\&
|
||||
.PP
|
||||
\fB--timeout\fR \fIseconds\fR, \fB+timeout=\fR\fIseconds\fR
|
||||
.br
|
||||
Set the timeout period.\& Floating point numbers are accepted.\&
|
||||
.br
|
||||
0.\&5 seconds is the minimum.\&
|
||||
.PP
|
||||
\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR
|
||||
.br
|
||||
Set the number of retries.\&
|
||||
.br
|
||||
Retry is one more than tries, dig style
|
||||
.PP
|
||||
.RE
|
||||
.SS DNS Flags
|
||||
.PP
|
||||
.RS 4
|
||||
\fB--aa=[false]\fR, \fB+[no]aaflag\fR
|
||||
.br
|
||||
(Set, Unset) AA (Authoritative Answer) flag
|
||||
.PP
|
||||
\fB--ad=[false]\fR, \fB+[no]adflag\fR
|
||||
.br
|
||||
(Set, Unset) AD (Authenticated Data) flag
|
||||
.PP
|
||||
\fB--tc=[false]\fR, \fB+[no]tcflag\fR
|
||||
.br
|
||||
(Set, Unset) TC (TrunCated) flag
|
||||
.PP
|
||||
\fB-z=[false]\fR, \fB+[no]zflag\fR
|
||||
.br
|
||||
(Set, Unset) Z (Zero) flag
|
||||
.PP
|
||||
\fB--cd=[false]\fR, \fB+[no]cdflag\fR
|
||||
.br
|
||||
(Set, Unset) CD (Checking Disabled) flag
|
||||
.PP
|
||||
\fB--qr=[false]\fR, \fB+[no]qrflag\fR
|
||||
.br
|
||||
(Set, Unset) QR (QueRy) flag
|
||||
.PP
|
||||
\fB--rd=[true]\fR, \fB+[no]rdflag\fR
|
||||
.br
|
||||
(Set, Unset) RD (Recursion Desired) flag
|
||||
.PP
|
||||
\fB--ra=[false]\fR, \fB+[no]raflag\fR
|
||||
.br
|
||||
(Set, Unset) RA (Recursion Available) flag
|
||||
.PP
|
||||
.RE
|
||||
.SS Output Display
|
||||
.RS 4
|
||||
\fB--no-question\fR, \fB+noquestion\fR
|
||||
.br
|
||||
Do not display the Question section
|
||||
.PP
|
||||
\fB--no-answer\fR, \fB+noanswer\fR
|
||||
.br
|
||||
Do not display the Answer section
|
||||
.PP
|
||||
\fB--no-answer\fR, \fB+noanswer\fR
|
||||
.br
|
||||
Do not display the Answer section
|
||||
.PP
|
||||
\fB--no-authority\fR, \fB+noauthority\fR
|
||||
.br
|
||||
Do not display the Authority section
|
||||
.PP
|
||||
\fB--no-additional\fR, \fB+noadditional\fR
|
||||
.br
|
||||
Do not display the Additional section
|
||||
.PP
|
||||
\fB--no-statistics\fR, \fB+nostats\fR
|
||||
.br
|
||||
Do not display the Statistics (additional comments) section
|
||||
.PP
|
||||
.RE
|
||||
.SS Output Formats
|
||||
.RS 4
|
||||
\fB-j\fR, \fB--json\fR, \fB+json\fR
|
||||
.br
|
||||
Print the query results as JSON.\&
|
||||
.PP
|
||||
\fB-X\fR, \fB--xml\fR, \fB+xml\fR
|
||||
.br
|
||||
Print the query results as XML.\&
|
||||
.PP
|
||||
\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR
|
||||
.br
|
||||
Print the query results as YAML.\&
|
||||
.PP
|
||||
\fB-s\fR, \fB--short\fR, \fB+short\fR
|
||||
.br
|
||||
Print just the address of the answer.\&
|
||||
.PP
|
||||
.RE
|
||||
.SH EXAMPLES
|
||||
.nf
|
||||
.RS 4
|
||||
awl grumbulon\&.xyz -j +cd
|
||||
.fi
|
||||
.RE
|
||||
Run a query of your local resolver for the A records of grumbulon.\&xyz, print
|
||||
them as JSON and disable DNSSEC verification.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
awl +short example\&.com AAAA @1\&.1\&.1\&.1
|
||||
.fi
|
||||
.RE
|
||||
Query 1.\&1.\&1.\&1 for the AAAA records of example.\&com, print just the answers
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google
|
||||
.fi
|
||||
.RE
|
||||
Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4
|
||||
.PP
|
||||
.SH SEE ALSO
|
||||
\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs
|
1
doc/wiki
1
doc/wiki
|
@ -1 +0,0 @@
|
|||
Subproject commit 0fba1fbe4b12e8c88514b3f7d98be3e75a5a034d
|
7
docs.go
7
docs.go
|
@ -1,9 +1,10 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
awl is a DNS lookup tool written in Go,
|
||||
similar to (and heavily inspired by) drill.
|
||||
awl is a DNS lookup tool written in Go, similar to (and heavily inspired by) drill.
|
||||
|
||||
It runs and displays similar outputs to drill, without any frills.
|
||||
Options are given to print with JSON
|
||||
Options are given to print with JSON, XML and YAML.
|
||||
|
||||
Supports results from DNS-over-[UDP, TCP, TLS, HTTPS, QUIC] servers
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,262 @@
|
|||
awl(1)
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# NAME
|
||||
|
||||
awl - DNS lookup tool
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*awl* [ _OPTIONS_ ] _name_ [ _@server_ ] [ _type_ ], where
|
||||
|
||||
_name_ is the query to make (example: froth.zone)++
|
||||
_@server_ is the server to query (example: dns.froth.zone)++
|
||||
_type_ is the DNS resource type (example: AAAA)
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
*awl* (*a*wls *w*ant *l*icorice) is a simple tool designed to make DNS queries,
|
||||
much like the venerable *dig*(1). An awl is a tool used to make small holes,
|
||||
typically used in leatherworking.
|
||||
|
||||
*awl* is designed to be a more "modern" version of *drill*(1) by including
|
||||
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 the localhost.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
*-4*
|
||||
Force only IPv4
|
||||
|
||||
*-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 of output
|
||||
Accepted values are as follows:
|
||||
- _0_: Only log errors.
|
||||
- _1_: Log warnings. *This is the default.*
|
||||
- _2_: Log information *Default when specifying just* _-v_.
|
||||
- _3_: Log information useful for debugging.
|
||||
|
||||
Setting a value lower than 0 disables logging entirely.
|
||||
|
||||
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.
|
||||
|
||||
# QUERY OPTIONS
|
||||
|
||||
Anything in [brackets] is optional.
|
||||
Many options are inherited from *dig*(1).
|
||||
|
||||
*--aa*[=_bool_], *+*[no]*aaflag*, *+*[no]*aaonly*
|
||||
Sets the AA (Authoritative Answer) flag.
|
||||
|
||||
*--ad*[=_bool_], *+*[no]*adflag*
|
||||
Sets the AD (Authenticated Data) flag.
|
||||
|
||||
*--no-additional*, *+*[no]*additional*
|
||||
Toggle the display of the Additional section.
|
||||
|
||||
*--no-answer*, *+*[no]*answer*
|
||||
Toggle the display of the Answer section.
|
||||
|
||||
*--no-authority*, *+*[no]*authority*
|
||||
Toggle the display of the Authority section.
|
||||
|
||||
*--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*).
|
||||
|
||||
*-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).
|
||||
|
||||
*--timeout* _seconds_, *+timeout*=_seconds_
|
||||
Set the timeout period. Floating point numbers are accepted.
|
||||
0.5 seconds is the minimum.
|
||||
|
||||
*-T*, *--tls*, *+*[no]*tls*
|
||||
Use DNS-over-TLS, implies *--tcp* (see RFC 7858)
|
||||
|
||||
*--tls-host* _string_
|
||||
Set hostname to use for TLS certificate validation.
|
||||
Default is the name of the domain when querying over TLS, and empty for IPs.
|
||||
|
||||
*--tls-no-verify*
|
||||
Ignore TLS validation when performing a DNS query.
|
||||
|
||||
*--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.
|
||||
|
||||
*-X*, *--xml*, *+*[no]*xml*
|
||||
Print the query results as XML.
|
||||
|
||||
*-y*, *--yaml*, *+*[no]*yaml*
|
||||
Print the query results as YAML.
|
||||
|
||||
*-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.
|
||||
|
||||
# EXIT STATUS
|
||||
|
||||
The exit code is 0 when a query is successfully made and received.
|
||||
This includes SERVFAILs, NOTIMPL among others.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
awl grumbulon.xyz -j +cd
|
||||
```
|
||||
|
||||
Run a query of your local resolver for the A records of grumbulon.xyz, print
|
||||
them as JSON and disable DNSSEC verification.
|
||||
|
||||
```
|
||||
awl +short example.com AAAA @1.1.1.1
|
||||
```
|
||||
|
||||
Query 1.1.1.1 for the AAAA records of example.com, print just the answers
|
||||
|
||||
```
|
||||
awl -xT PTR 8.8.4.4 @dns.google
|
||||
```
|
||||
|
||||
Query dns.google over TLS for the PTR record to the IP address 8.8.4.4
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*drill*(1), *dig*(1)
|
||||
|
||||
# STANDARDS
|
||||
|
||||
RFC 1034,1035 (UDP), 7766 (TCP), 7858 (TLS), 8484 (HTTPS), 9230 (QUIC)
|
||||
|
||||
Probably more, _https://www.statdns.com/rfc_
|
||||
|
||||
# BUGS
|
||||
|
||||
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-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
|
55
go.mod
55
go.mod
|
@ -1,40 +1,33 @@
|
|||
module git.froth.zone/sam/awl
|
||||
module dns.froth.zone/awl
|
||||
|
||||
go 1.18
|
||||
go 1.21.9
|
||||
|
||||
toolchain go1.22.3
|
||||
|
||||
require (
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3
|
||||
github.com/lucas-clemente/quic-go v0.28.1
|
||||
github.com/miekg/dns v1.1.50
|
||||
dns.froth.zone/dnscrypt v0.0.2
|
||||
github.com/dchest/uniuri v1.2.0
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/quic-go/quic-go v0.43.1
|
||||
github.com/stefansundin/go-zflag v1.1.1
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools/v3 v3.3.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.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/google/go-cmp v0.5.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
)
|
||||
|
|
384
go.sum
384
go.sum
|
@ -1,343 +1,75 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0=
|
||||
github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
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.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
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.3.1/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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU=
|
||||
github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
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-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
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.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
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/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/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/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
|
||||
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/stefansundin/go-zflag v1.1.1 h1:XabhzWS588bVvV1z1UctSa6i8zHkXc5W9otqtnDSHw8=
|
||||
github.com/stefansundin/go-zflag v1.1.1/go.mod h1:HXX5rABl1AoTcZ2jw+CqJ7R8irczaLquGNZlFabZooc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/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-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/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-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20190924154521-2837fb4f24fe/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-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
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/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
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/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.2.8/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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Useful structs used everywhere I couldn't find a better place to shove
|
||||
*/
|
||||
package helpers
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// The DNS response.
|
||||
type Response struct {
|
||||
DNS *dns.Msg // The full DNS response
|
||||
RTT time.Duration `json:"rtt"` // The time it took to make the DNS query
|
||||
}
|
||||
|
||||
// A structure for a DNS query.
|
||||
type Request struct {
|
||||
Server string `json:"server"` // The server to make the DNS request from
|
||||
Type uint16 `json:"request"` // The type of request
|
||||
Class uint16 `json:"class"` // DNS Class
|
||||
Name string `json:"name"` // The domain name to make a DNS request for
|
||||
Timeout time.Duration // The maximum timeout
|
||||
Retries int // Number of queries to retry
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package helpers_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestNothing(t *testing.T) {
|
||||
assert.Equal(t, 0, 0)
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type (
|
||||
Level int32
|
||||
Logger struct {
|
||||
Mu sync.Mutex
|
||||
Level Level
|
||||
Prefix string
|
||||
Out io.Writer
|
||||
buf []byte
|
||||
isDiscard int32
|
||||
}
|
||||
)
|
||||
|
||||
// Stores whatever input value is in mem address of l.level.
|
||||
func (l *Logger) SetLevel(level Level) {
|
||||
atomic.StoreInt32((*int32)(&l.Level), int32(level))
|
||||
}
|
||||
|
||||
// Mostly nothing.
|
||||
func (l *Logger) GetLevel() Level {
|
||||
return l.level()
|
||||
}
|
||||
|
||||
// Retrieves whatever was stored in mem address of l.level.
|
||||
func (l *Logger) level() Level {
|
||||
return Level(atomic.LoadInt32((*int32)(&l.Level)))
|
||||
}
|
||||
|
||||
// Unmarshalls the int value of level for writing the header.
|
||||
func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
|
||||
switch lv {
|
||||
case 0:
|
||||
return "ERROR ", nil
|
||||
case 1:
|
||||
return "WARN ", nil
|
||||
case 2:
|
||||
return "INFO ", nil
|
||||
case 3:
|
||||
return "DEBUG ", nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid log level")
|
||||
}
|
||||
|
||||
func (l *Logger) IsLevel(level Level) bool {
|
||||
return l.level() >= level
|
||||
}
|
||||
|
||||
var AllLevels = []Level{
|
||||
ErrLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
const (
|
||||
// Fatal logs (will call exit(1)).
|
||||
ErrLevel Level = iota
|
||||
|
||||
// Error logs.
|
||||
WarnLevel
|
||||
|
||||
// What is going on level.
|
||||
InfoLevel
|
||||
// Verbose log level.
|
||||
DebugLevel
|
||||
)
|
144
logawl/logger.go
144
logawl/logger.go
|
@ -1,144 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Calling New instantiates Logger
|
||||
//
|
||||
// Level can be changed to one of the other log levels (FatalLevel, ErrorLevel, InfoLevel, DebugLevel)
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Level: InfoLevel, //Default value is InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
//If verbose is not set --debug etc print _nothing_
|
||||
if l.IsLevel(level) {
|
||||
switch level { //Goes through log levels and does stuff based on them (Fatal os.Exit...etc)
|
||||
case 0:
|
||||
err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
|
||||
}
|
||||
case 1:
|
||||
err := l.Printer(1, fmt.Sprintln(v...)) //Error level
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
|
||||
}
|
||||
case 2:
|
||||
err := l.Printer(2, fmt.Sprintln(v...)) //Info level
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
|
||||
}
|
||||
case 3:
|
||||
err := l.Printer(3, fmt.Sprintln(v...)) //Debug level
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// This is ugly but functional
|
||||
// maybe there can be an append func or something in the future
|
||||
*buf = append(*buf, lvl...)
|
||||
year, month, day := t.Date()
|
||||
*buf = append(*buf, '[')
|
||||
formatter(buf, year, 4)
|
||||
*buf = append(*buf, '/')
|
||||
formatter(buf, int(month), 2)
|
||||
*buf = append(*buf, '/')
|
||||
formatter(buf, day, 2)
|
||||
*buf = append(*buf, ' ')
|
||||
hour, min, sec := t.Clock()
|
||||
formatter(buf, hour, 2)
|
||||
*buf = append(*buf, ':')
|
||||
formatter(buf, min, 2)
|
||||
*buf = append(*buf, ':')
|
||||
formatter(buf, sec, 2)
|
||||
*buf = append(*buf, ']')
|
||||
*buf = append(*buf, ':')
|
||||
*buf = append(*buf, ' ')
|
||||
} else {
|
||||
return fmt.Errorf("invalid log level choice")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Printer prints the formatted message directly to stdErr
|
||||
func (l *Logger) Printer(level Level, s string) error {
|
||||
now := time.Now()
|
||||
var line int
|
||||
l.Mu.Lock()
|
||||
defer l.Mu.Unlock()
|
||||
|
||||
l.buf = l.buf[:0]
|
||||
err := l.FormatHeader(&l.buf, now, line, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.buf = append(l.buf, s...)
|
||||
if len(s) == 0 || s[len(s)-1] != '\n' {
|
||||
l.buf = append(l.buf, '\n')
|
||||
}
|
||||
_, err = l.Out.Write(l.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// Some line formatting stuff from Golang log stdlib file
|
||||
//
|
||||
// Please view https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/log/log.go;drc=41e1d9075e428c2fc32d966b3752a3029b620e2c;l=96
|
||||
//
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||
func formatter(buf *[]byte, i int, wid int) {
|
||||
// Assemble decimal in reverse order.
|
||||
var b [20]byte
|
||||
bp := len(b) - 1
|
||||
for i >= 10 || wid > 1 {
|
||||
wid--
|
||||
q := i / 10
|
||||
b[bp] = byte('0' + i - q*10)
|
||||
bp--
|
||||
i = q
|
||||
}
|
||||
// i < 10
|
||||
b[bp] = byte('0' + i)
|
||||
*buf = append(*buf, b[bp:]...)
|
||||
}
|
||||
|
||||
// Call print directly with Debug level
|
||||
func (l *Logger) Debug(v ...any) {
|
||||
l.Println(DebugLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Info level
|
||||
func (l *Logger) Info(v ...any) {
|
||||
l.Println(InfoLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Warn level
|
||||
func (l *Logger) Warn(v ...any) {
|
||||
l.Println(WarnLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Error level
|
||||
func (l *Logger) Error(v ...any) {
|
||||
l.Println(ErrLevel, v...)
|
||||
}
|
219
main.go
219
main.go
|
@ -3,128 +3,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func main() {
|
||||
opts, err := cli.ParseCLI(version)
|
||||
if err != nil {
|
||||
if opts, code, err := run(os.Args); err != nil {
|
||||
// TODO: Make not ew
|
||||
if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
if errors.Is(err, util.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
os.Exit(0)
|
||||
}
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var resp helpers.Response
|
||||
|
||||
// Retry queries if a query fails
|
||||
for i := 0; i < opts.Request.Retries; i++ {
|
||||
resp, err = query.CreateQuery(opts)
|
||||
if err == nil {
|
||||
break
|
||||
} else {
|
||||
opts.Logger.Warn("Retrying request, error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(9)
|
||||
}
|
||||
switch {
|
||||
case opts.JSON:
|
||||
opts.Logger.Info("Printing as JSON")
|
||||
json, err := json.MarshalIndent(resp.DNS, "", " ")
|
||||
if err != nil {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(10)
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
case opts.XML:
|
||||
opts.Logger.Info("Printing as XML")
|
||||
xml, err := xml.MarshalIndent(resp.DNS, "", " ")
|
||||
if err != nil {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(10)
|
||||
}
|
||||
fmt.Println(string(xml))
|
||||
case opts.YAML:
|
||||
opts.Logger.Info("Printing as YAML")
|
||||
yaml, err := yaml.Marshal(resp.DNS)
|
||||
if err != nil {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(10)
|
||||
}
|
||||
fmt.Println(string(yaml))
|
||||
default:
|
||||
if !opts.Short {
|
||||
// Print everything
|
||||
|
||||
if !opts.Display.Question {
|
||||
resp.DNS.Question = nil
|
||||
opts.Logger.Info("Disabled question display")
|
||||
}
|
||||
if !opts.Display.Answer {
|
||||
resp.DNS.Answer = nil
|
||||
opts.Logger.Info("Disabled answer display")
|
||||
}
|
||||
if !opts.Display.Authority {
|
||||
resp.DNS.Ns = nil
|
||||
opts.Logger.Info("Disabled authority display")
|
||||
}
|
||||
if !opts.Display.Additional {
|
||||
resp.DNS.Extra = nil
|
||||
opts.Logger.Info("Disabled additional display")
|
||||
}
|
||||
|
||||
fmt.Println(resp.DNS)
|
||||
|
||||
if opts.Display.Statistics {
|
||||
fmt.Println(";; Query time:", resp.RTT)
|
||||
|
||||
// Add extra information to server string
|
||||
var extra string
|
||||
switch {
|
||||
case opts.TCP:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
|
||||
case opts.TLS:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
|
||||
case opts.HTTPS, opts.DNSCrypt:
|
||||
extra = ""
|
||||
case opts.QUIC:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
|
||||
default:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
|
||||
}
|
||||
|
||||
fmt.Println(";; SERVER:", opts.Request.Server+extra)
|
||||
fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123Z))
|
||||
fmt.Println(";; MSG SIZE rcvd:", resp.DNS.Len())
|
||||
}
|
||||
|
||||
} else {
|
||||
// Print just the responses, nothing else
|
||||
for _, res := range resp.DNS.Answer {
|
||||
temp := strings.Split(res.String(), "\t")
|
||||
fmt.Println(temp[len(temp)-1])
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
keepTracing bool
|
||||
tempDomain string
|
||||
tempQueryType uint16
|
||||
)
|
||||
|
||||
for ok := true; ok; ok = keepTracing {
|
||||
if opts.Trace {
|
||||
if keepTracing {
|
||||
opts.Request.Name = tempDomain
|
||||
opts.Request.Type = tempQueryType
|
||||
} else {
|
||||
tempDomain = opts.Request.Name
|
||||
tempQueryType = opts.Request.Type
|
||||
|
||||
// Override the query because it needs to be done
|
||||
opts.Request.Name = "."
|
||||
opts.Request.Type = dns.TypeNS
|
||||
}
|
||||
}
|
||||
// Retry queries if a query fails
|
||||
for i := 0; i <= opts.Request.Retries; i++ {
|
||||
resp, err = query.CreateQuery(opts)
|
||||
if err == nil {
|
||||
keepTracing = opts.Trace && (!resp.DNS.Authoritative || (opts.Request.Name == "." && tempDomain != ".")) && resp.DNS.MsgHdr.Rcode == 0
|
||||
|
||||
break
|
||||
} else if i != opts.Request.Retries {
|
||||
opts.Logger.Warn("Retrying request, error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
|
||||
var str string
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = query.PrintSpecial(resp, opts)
|
||||
if err != nil {
|
||||
return opts, 10, fmt.Errorf("format print: %w", err)
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return opts, 0, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stefansundin/go-zflag"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
// t.Parallel()
|
||||
args := [][]string{
|
||||
{"awl", "+yaml", "@1.1.1.1"},
|
||||
{"awl", "+short", "@1.1.1.1"},
|
||||
}
|
||||
|
||||
for _, test := range args {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, code, err := run(test)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrace(t *testing.T) {
|
||||
domains := []string{"git.froth.zone", "google.com", "amazon.com", "freecumextremist.com", "dns.froth.zone", "sleepy.cafe", "pkg.go.dev"}
|
||||
|
||||
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)
|
||||
assert.ErrorIs(t, err, zflag.ErrHelp)
|
||||
assert.Equal(t, code, 1)
|
||||
}
|
32
mkfile
32
mkfile
|
@ -1,30 +1,36 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Plan 9 mkfile
|
||||
|
||||
</$objtype/mkfile
|
||||
|
||||
GO = go
|
||||
PROG = awl
|
||||
LDFLAGS = '-s -w'
|
||||
GOFLAGS = -ldflags=$LDFLAGS
|
||||
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
|
||||
|
||||
$PROG:
|
||||
$GO build $GOFLAGS -o $PROG '-buildvcs=false' .
|
||||
all:V: $PROG
|
||||
|
||||
install: $PROG
|
||||
$GO install $GOFLAGS .
|
||||
cp doc/$PROG.1 /sys/man/1/$PROG
|
||||
$PROG:
|
||||
$GO build $GOFLAGS -o $target .
|
||||
|
||||
test:
|
||||
$GO test -v -cover -coverprofile=coverage/coverage.out ./...
|
||||
install:V:
|
||||
$GO install $GOFLAGS .
|
||||
# cp docs/$PROG.1 /sys/man/1/$PROG
|
||||
|
||||
fmt:
|
||||
test:V:
|
||||
$GO test -v -cover ./...
|
||||
|
||||
fmt:V:
|
||||
gofmt -w -s .
|
||||
|
||||
vet:
|
||||
vet:V:
|
||||
$GO vet ./...
|
||||
|
||||
lint: fmt vet
|
||||
lint:V: fmt vet
|
||||
|
||||
clean:
|
||||
clean:V:
|
||||
$GO clean
|
||||
|
||||
nuke:V: clean
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
LogAwl is a package for custom logging needs
|
||||
Package logawl is a package for custom logging needs
|
||||
|
||||
LogAwl extends the standard log library with support for log levels
|
||||
This is _different_ from the syslog package in the standard library because you do not define a file
|
||||
|
@ -12,14 +14,15 @@ 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")
|
||||
//
|
||||
// You may also set the log level on the fly with
|
||||
//
|
||||
// Logger.SetLevel(3)
|
||||
// This allows you to change the default level (Info) and prevent log messages from being posted at higher verbosity levels
|
||||
// This allows you to change the default level (Info)
|
||||
// and prevent log messages from being posted at higher verbosity levels
|
||||
// for example if
|
||||
// Logger.SetLevel(3)
|
||||
// is not called and you call
|
|
@ -0,0 +1,90 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type (
|
||||
// Level is the logging level.
|
||||
Level int32
|
||||
|
||||
// Logger is the overall logger.
|
||||
Logger struct {
|
||||
Out io.Writer
|
||||
Prefix string
|
||||
buf []byte
|
||||
Mu sync.Mutex
|
||||
Level Level
|
||||
isDiscard int32
|
||||
}
|
||||
)
|
||||
|
||||
// SetLevel stores whatever input value is in mem address of l.level.
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreInt32((*int32)(&logger.Level), int32(level))
|
||||
}
|
||||
|
||||
// GetLevel gets the logger level.
|
||||
func (logger *Logger) GetLevel() Level {
|
||||
return logger.level()
|
||||
}
|
||||
|
||||
// Retrieves whatever was stored in mem address of 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 (logger *Logger) UnMarshalLevel(lv Level) (string, error) {
|
||||
switch lv {
|
||||
case ErrLevel:
|
||||
return "ERROR ", nil
|
||||
case WarnLevel:
|
||||
return "WARN ", nil
|
||||
case InfoLevel:
|
||||
return "INFO ", nil
|
||||
case DebugLevel:
|
||||
return "DEBUG ", nil
|
||||
}
|
||||
|
||||
return "", errInvalidLevel
|
||||
}
|
||||
|
||||
// IsLevel returns true if the logger level is above the level given.
|
||||
func (logger *Logger) IsLevel(level Level) bool {
|
||||
return logger.level() >= level
|
||||
}
|
||||
|
||||
// AllLevels is an array of all valid log levels.
|
||||
var AllLevels = []Level{
|
||||
ErrLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrLevel is the fatal (error) log level.
|
||||
ErrLevel Level = iota
|
||||
|
||||
// WarnLevel is for warning logs.
|
||||
//
|
||||
// Example: when one setting implies another, when a request fails but is retried.
|
||||
WarnLevel
|
||||
|
||||
// InfoLevel is for saying what is going on when.
|
||||
// This is essentially the "verbose" option.
|
||||
//
|
||||
// When in doubt, use info.
|
||||
InfoLevel
|
||||
|
||||
// DebugLevel is for spewing debug structs/interfaces.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
var errInvalidLevel = errors.New("invalid log level")
|
|
@ -0,0 +1,175 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// New instantiates Logger
|
||||
//
|
||||
// Level can be changed to one of the other log levels (ErrorLevel, WarnLevel, InfoLevel, DebugLevel).
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Level: WarnLevel, // Default value is WarnLevel
|
||||
}
|
||||
}
|
||||
|
||||
// Println takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)).
|
||||
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 logger.IsLevel(level) {
|
||||
switch level { // Goes through log levels and does stuff based on them (currently nothing)
|
||||
case ErrLevel:
|
||||
if err := logger.Printer(ErrLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case WarnLevel:
|
||||
if err := logger.Printer(WarnLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case InfoLevel:
|
||||
if err := logger.Printer(InfoLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
case DebugLevel:
|
||||
if err := logger.Printer(DebugLevel, fmt.Sprintln(in...)); err != nil {
|
||||
fmt.Fprintln(logger.Out, "Logger failed: ", err)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FormatHeader formats the log header as such <LogLevel> YYYY/MM/DD HH:MM:SS (local time) <the message to log>.
|
||||
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...)
|
||||
year, month, day := t.Date()
|
||||
|
||||
*buf = append(*buf, '[')
|
||||
formatter(buf, year, 4)
|
||||
*buf = append(*buf, '/')
|
||||
formatter(buf, int(month), 2)
|
||||
*buf = append(*buf, '/')
|
||||
formatter(buf, day, 2)
|
||||
*buf = append(*buf, ' ')
|
||||
hour, min, sec := t.Clock()
|
||||
formatter(buf, hour, 2)
|
||||
*buf = append(*buf, ':')
|
||||
formatter(buf, min, 2)
|
||||
*buf = append(*buf, ':')
|
||||
formatter(buf, sec, 2)
|
||||
*buf = append(*buf, ']')
|
||||
*buf = append(*buf, ':')
|
||||
*buf = append(*buf, ' ')
|
||||
} else {
|
||||
return errInvalidLevel
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Printer prints the formatted message directly to stdErr.
|
||||
func (logger *Logger) Printer(level Level, s string) error {
|
||||
now := time.Now()
|
||||
|
||||
var line int
|
||||
|
||||
logger.Mu.Lock()
|
||||
defer logger.Mu.Unlock()
|
||||
|
||||
logger.buf = logger.buf[:0]
|
||||
|
||||
if err := logger.FormatHeader(&logger.buf, now, line, level); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.buf = append(logger.buf, s...)
|
||||
|
||||
if len(s) == 0 || s[len(s)-1] != '\n' {
|
||||
logger.buf = append(logger.buf, '\n')
|
||||
}
|
||||
|
||||
_, err := logger.Out.Write(logger.buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logger printing: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some line formatting stuff from Golang log stdlib file
|
||||
//
|
||||
// Please view
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/log/log.go;drc=41e1d9075e428c2fc32d966b3752a3029b620e2c;l=96
|
||||
//
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||
func formatter(buf *[]byte, i int, wid int) {
|
||||
// Assemble decimal in reverse order.
|
||||
var b [20]byte
|
||||
bp := len(b) - 1
|
||||
|
||||
for i >= 10 || wid > 1 {
|
||||
wid--
|
||||
|
||||
q := i / 10
|
||||
b[bp] = byte('0' + i - q*10)
|
||||
bp--
|
||||
|
||||
i = q
|
||||
}
|
||||
// i < 10
|
||||
b[bp] = byte('0' + i)
|
||||
*buf = append(*buf, b[bp:]...)
|
||||
}
|
||||
|
||||
// Debug calls print directly with Debug level.
|
||||
func (logger *Logger) Debug(in ...any) {
|
||||
logger.Println(DebugLevel, in...)
|
||||
}
|
||||
|
||||
// Debugf calls print after formatting the string with Debug level.
|
||||
func (logger *Logger) Debugf(format string, in ...any) {
|
||||
logger.Println(DebugLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Info calls print directly with Info level.
|
||||
func (logger *Logger) Info(in ...any) {
|
||||
logger.Println(InfoLevel, in...)
|
||||
}
|
||||
|
||||
// Infof calls print after formatting the string with Info level.
|
||||
func (logger *Logger) Infof(format string, in ...any) {
|
||||
logger.Println(InfoLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Warn calls print directly with Warn level.
|
||||
func (logger *Logger) Warn(in ...any) {
|
||||
logger.Println(WarnLevel, in...)
|
||||
}
|
||||
|
||||
// Warnf calls print after formatting the string with Warn level.
|
||||
func (logger *Logger) Warnf(format string, in ...any) {
|
||||
logger.Println(WarnLevel, fmt.Sprintf(format, in...))
|
||||
}
|
||||
|
||||
// Error calls print directly with Error level.
|
||||
func (logger *Logger) Error(in ...any) {
|
||||
logger.Println(ErrLevel, in...)
|
||||
}
|
||||
|
||||
// Errorf calls print after formatting the string with Error level.
|
||||
func (logger *Logger) Errorf(format string, in ...any) {
|
||||
logger.Println(ErrLevel, fmt.Sprintf(format, in...))
|
||||
}
|
|
@ -7,8 +7,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
|
@ -25,10 +24,11 @@ func TestLogawl(t *testing.T) {
|
|||
|
||||
func TestUnmarshalLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := make(map[int]string)
|
||||
var err error
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
var err error
|
||||
m[i], err = logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
@ -48,28 +48,52 @@ func TestLogger(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
// only test non-exiting log levels
|
||||
switch i {
|
||||
case 0:
|
||||
fn := func() {
|
||||
logger.Error("Test", "E")
|
||||
logger.Errorf("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 1:
|
||||
fn := func() {
|
||||
logger.Info("")
|
||||
logger.Warn("Test")
|
||||
logger.Warnf("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 2:
|
||||
fn := func() {
|
||||
logger.Info("Test")
|
||||
logger.Infof("%s", "Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 3:
|
||||
fn := func() {
|
||||
logger.Debug("Test")
|
||||
logger.Debug("Test 2")
|
||||
logger.Debugf("%s", "Test")
|
||||
logger.Debugf("%s %d", "Test", 2)
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
@ -77,8 +101,9 @@ func TestLogger(t *testing.T) {
|
|||
|
||||
func TestFmt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ti := time.Now()
|
||||
test := []byte("test")
|
||||
assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level") //make sure error is error
|
||||
|
||||
// make sure error is error
|
||||
assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level")
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package query is for the various query types.
|
||||
package query
|
|
@ -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")
|
|
@ -0,0 +1,230 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestRealPrint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := []*util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
TCP: true,
|
||||
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
|
||||
JSON: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
HumanTTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "a.gtld-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
TCP: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
|
||||
Short: true,
|
||||
Identify: true,
|
||||
YAML: false,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "ns1.google.com",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
HTTPS: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Identify: true,
|
||||
XML: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: true,
|
||||
HumanTTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "https://dns.froth.zone/dns-query",
|
||||
Port: 443,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
DNSSEC: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
TTL: false,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
|
||||
HeaderFlags: util.HeaderFlags{
|
||||
AA: true,
|
||||
RD: true,
|
||||
},
|
||||
Verbosity: 0,
|
||||
|
||||
YAML: true,
|
||||
Display: util.Display{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
TTL: true,
|
||||
ShowQuery: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "rin.froth.zone",
|
||||
Port: 53,
|
||||
Type: dns.StringToType["A"],
|
||||
Class: 1,
|
||||
Name: "froth.zone.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
Padding: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range opts {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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(res, test)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
str, err := query.ToString(res, test)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, 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{}, new(util.Options))
|
||||
|
||||
assert.Error(t, err, "no message")
|
||||
assert.Assert(t, str == "<nil> MsgHdr")
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"dns.froth.zone/awl/pkg/resolvers"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
req.Question[0].Qclass = opts.Request.Class
|
||||
|
||||
// Set standard flags
|
||||
req.MsgHdr.Response = opts.QR
|
||||
req.MsgHdr.Authoritative = opts.AA
|
||||
req.MsgHdr.Truncated = opts.TC
|
||||
req.MsgHdr.RecursionDesired = opts.RD
|
||||
req.MsgHdr.RecursionAvailable = opts.RA
|
||||
req.MsgHdr.Zero = opts.Z
|
||||
req.MsgHdr.AuthenticatedData = opts.AD
|
||||
req.MsgHdr.CheckingDisabled = opts.CD
|
||||
|
||||
// EDNS time :)
|
||||
if opts.EDNS.EnableEDNS {
|
||||
edns := new(dns.OPT)
|
||||
edns.Hdr.Name = "."
|
||||
edns.Hdr.Rrtype = dns.TypeOPT
|
||||
|
||||
edns.SetVersion(opts.EDNS.Version)
|
||||
|
||||
if opts.EDNS.Cookie {
|
||||
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", cookie.Cookie)
|
||||
}
|
||||
|
||||
if opts.EDNS.Expire {
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_EXPIRE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS Expire option")
|
||||
}
|
||||
|
||||
if opts.EDNS.KeepOpen {
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_TCP_KEEPALIVE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS TCP Keepalive option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Nsid {
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_NSID))
|
||||
|
||||
opts.Logger.Info("Setting EDNS NSID option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Padding {
|
||||
edns.Option = append(edns.Option, new(dns.EDNS0_PADDING))
|
||||
|
||||
opts.Logger.Info("Setting EDNS padding")
|
||||
}
|
||||
|
||||
edns.SetUDPSize(opts.EDNS.BufSize)
|
||||
|
||||
opts.Logger.Info("EDNS UDP buffer set to", opts.EDNS.BufSize)
|
||||
|
||||
edns.SetZ(opts.EDNS.ZFlag)
|
||||
|
||||
opts.Logger.Info("EDNS Z flag set to", opts.EDNS.ZFlag)
|
||||
|
||||
if opts.EDNS.DNSSEC {
|
||||
edns.SetDo()
|
||||
|
||||
opts.Logger.Info("EDNS DNSSEC OK set")
|
||||
}
|
||||
|
||||
if opts.EDNS.Subnet.Address != nil {
|
||||
edns.Option = append(edns.Option, &opts.EDNS.Subnet)
|
||||
}
|
||||
|
||||
req.Extra = append(req.Extra, edns)
|
||||
} else if opts.EDNS.DNSSEC {
|
||||
req.SetEdns0(1232, true)
|
||||
opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled")
|
||||
opts.Logger.Info("DNSSEC enabled, UDP buffer set to 1232")
|
||||
}
|
||||
|
||||
opts.Logger.Debug(req)
|
||||
|
||||
if !opts.Short {
|
||||
if opts.Display.ShowQuery {
|
||||
opts.Logger.Info("Printing constructed query")
|
||||
|
||||
var (
|
||||
str string
|
||||
err error
|
||||
)
|
||||
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = PrintSpecial(util.Response{DNS: req}, opts)
|
||||
if err != nil {
|
||||
return util.Response{}, err
|
||||
}
|
||||
} else {
|
||||
temp := opts.Display.Statistics
|
||||
opts.Display.Statistics = false
|
||||
str, err = ToString(
|
||||
util.Response{
|
||||
DNS: req,
|
||||
RTT: 0,
|
||||
}, opts)
|
||||
if err != nil {
|
||||
return util.Response{}, err
|
||||
}
|
||||
|
||||
opts.Display.Statistics = temp
|
||||
str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) + "\n"
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
opts.Display.ShowQuery = false
|
||||
}
|
||||
}
|
||||
|
||||
resolver, err := resolvers.LoadResolver(opts)
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("unable to load resolvers: %w", err)
|
||||
}
|
||||
|
||||
opts.Logger.Info("Query successfully loaded")
|
||||
|
||||
//nolint:wrapcheck // Error wrapping not needed here
|
||||
return resolver.LookUp(req)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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 Resolver, err error) {
|
||||
switch {
|
||||
case opts.HTTPS:
|
||||
opts.Logger.Info("loading DNS-over-HTTPS resolver")
|
||||
|
||||
if !strings.HasPrefix(opts.Request.Server, "https://") {
|
||||
opts.Request.Server = "https://" + opts.Request.Server
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
return
|
||||
case opts.QUIC:
|
||||
opts.Logger.Info("loading DNS-over-QUIC resolver")
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
return
|
||||
case opts.DNSCrypt:
|
||||
opts.Logger.Info("loading DNSCrypt resolver")
|
||||
|
||||
if !strings.HasPrefix(opts.Request.Server, "sdns://") {
|
||||
opts.Request.Server = "sdns://" + opts.Request.Server
|
||||
}
|
||||
|
||||
resolver = &DNSCryptResolver{
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
return
|
||||
default:
|
||||
opts.Logger.Info("loading standard/DNS-over-TLS resolver")
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package util contains helper functions that don't belong anywhere else
|
||||
package util
|
|
@ -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")
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import "dns.froth.zone/awl/pkg/logawl"
|
||||
|
||||
// InitLogger initializes the logawl instance.
|
||||
func InitLogger(verbosity int) (log *logawl.Logger) {
|
||||
log = logawl.New()
|
||||
|
||||
log.SetLevel(logawl.Level(verbosity))
|
||||
|
||||
return log
|
||||
}
|
|
@ -5,12 +5,14 @@ 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"
|
||||
)
|
||||
|
||||
func TestInitLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := util.InitLogger(0)
|
||||
assert.Equal(t, logger.Level, logawl.Level(0))
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Options is the grand structure for all query options.
|
||||
type Options struct {
|
||||
// The logger
|
||||
Logger *logawl.Logger `json:"-"`
|
||||
// Host to verify TLS cert with
|
||||
TLSHost string `json:"tlsHost" example:""`
|
||||
// EDNS Options
|
||||
EDNS
|
||||
|
||||
// HTTPS options :)
|
||||
HTTPSOptions
|
||||
|
||||
// DNS request :)
|
||||
Request
|
||||
|
||||
// Verbosity levels, see [logawl.AllLevels]
|
||||
Verbosity int `json:"-" example:"0"`
|
||||
// Display options
|
||||
Display Display
|
||||
// Ignore Truncation
|
||||
Truncate bool `json:"ignoreTruncate" example:"false"`
|
||||
// Ignore BADCOOKIE
|
||||
BadCookie bool `json:"ignoreBadCookie" example:"false"`
|
||||
// Print only the answer
|
||||
Short bool `json:"short" example:"false"`
|
||||
// When Short is true, display where the query came from
|
||||
Identify bool `json:"identify" example:"false"`
|
||||
// Perform a reverse DNS query when true
|
||||
Reverse bool `json:"reverse" example:"false"`
|
||||
|
||||
HeaderFlags
|
||||
|
||||
// Display resposne as JSON
|
||||
JSON bool `json:"-" xml:"-" yaml:"-"`
|
||||
// Display response as XML
|
||||
XML bool `json:"-" xml:"-" yaml:"-"`
|
||||
// Display response as YAML
|
||||
YAML bool `json:"-" xml:"-" yaml:"-"`
|
||||
|
||||
// Use TCP instead of UDP to make the query
|
||||
TCP bool `json:"tcp" example:"false"`
|
||||
// Use DNS-over-TLS to make the query
|
||||
TLS bool `json:"dnsOverTLS" example:"false"`
|
||||
// When using TLS, ignore certificates
|
||||
TLSNoVerify bool `json:"tlsNoVerify" example:"false"`
|
||||
// Use DNS-over-HTTPS to make the query
|
||||
HTTPS bool `json:"dnsOverHTTPS" example:"false"`
|
||||
// Use DNS-over-QUIC to make the query
|
||||
//nolint:tagliatelle // QUIC is an acronym
|
||||
QUIC bool `json:"dnsOverQUIC" example:"false"`
|
||||
// Use DNSCrypt to make the query
|
||||
DNSCrypt bool `json:"dnscrypt" example:"false"`
|
||||
|
||||
// Force IPv4 only
|
||||
IPv4 bool `json:"forceIPv4" example:"false"`
|
||||
// Force IPv6 only
|
||||
IPv6 bool `json:"forceIPv6" example:"false"`
|
||||
|
||||
// Trace from the root
|
||||
Trace bool `json:"trace" example:"false"`
|
||||
}
|
||||
|
||||
// HTTPSOptions are options exclusively for DNS-over-HTTPS queries.
|
||||
type HTTPSOptions struct {
|
||||
// URL endpoint
|
||||
Endpoint string `json:"endpoint" example:"/dns-query"`
|
||||
|
||||
// True, make GET request.
|
||||
// False, make POST request.
|
||||
Get bool `json:"get" example:"false"`
|
||||
}
|
||||
|
||||
// HeaderFlags are the flags that are in DNS headers.
|
||||
type HeaderFlags struct {
|
||||
// Authoritative Answer DNS query flag
|
||||
AA bool `json:"authoritative" example:"false"`
|
||||
// Authenticated Data DNS query flag
|
||||
AD bool `json:"authenticatedData" example:"false"`
|
||||
// Checking Disabled DNS query flag
|
||||
CD bool `json:"checkingDisabled" example:"false"`
|
||||
// QueRy DNS query flag
|
||||
QR bool `json:"query" example:"false"`
|
||||
// Recursion Desired DNS query flag
|
||||
RD bool `json:"recursionDesired" example:"true"`
|
||||
// Recursion Available DNS query flag
|
||||
RA bool `json:"recursionAvailable" example:"false"`
|
||||
// TrunCated DNS query flag
|
||||
TC bool `json:"truncated" example:"false"`
|
||||
// Zero DNS query flag
|
||||
Z bool `json:"zero" example:"false"`
|
||||
}
|
||||
|
||||
// Display contains toggles for what to (and not to) display.
|
||||
type Display struct {
|
||||
/* Section displaying */
|
||||
|
||||
// Comments?
|
||||
Comments bool `json:"comments" example:"true"`
|
||||
// QUESTION SECTION
|
||||
Question bool `json:"question" example:"true"`
|
||||
// OPT PSEUDOSECTION
|
||||
Opt bool `json:"opt" example:"true"`
|
||||
// ANSWER SECTION
|
||||
Answer bool `json:"answer" example:"true"`
|
||||
// AUTHORITY SECTION
|
||||
Authority bool `json:"authority" example:"true"`
|
||||
// ADDITIONAL SECTION
|
||||
Additional bool `json:"additional" example:"true"`
|
||||
// Query time, message size, etc.
|
||||
Statistics bool `json:"statistics" example:"true"`
|
||||
// Display TTL in response
|
||||
TTL bool `json:"ttl" example:"true"`
|
||||
|
||||
/* Answer formatting */
|
||||
|
||||
// Display Class in response
|
||||
ShowClass bool `json:"showClass" example:"true"`
|
||||
// Display query before it is sent
|
||||
ShowQuery bool `json:"showQuery" example:"false"`
|
||||
// Display TTL as human-readable
|
||||
HumanTTL bool `json:"humanTTL" example:"false"`
|
||||
// Translate Punycode back to Unicode
|
||||
UcodeTranslate bool `json:"unicode" example:"true"`
|
||||
}
|
||||
|
||||
// EDNS contains toggles for various EDNS options.
|
||||
type EDNS struct {
|
||||
// Subnet to originate query from.
|
||||
Subnet dns.EDNS0_SUBNET `json:"subnet"`
|
||||
// Must Be Zero flag
|
||||
ZFlag uint16 `json:"zflag" example:"0"`
|
||||
// UDP buffer size
|
||||
BufSize uint16 `json:"bufSize" example:"1232"`
|
||||
// Enable/Disable EDNS entirely
|
||||
EnableEDNS bool `json:"edns" example:"false"`
|
||||
// Sending EDNS cookie
|
||||
Cookie bool `json:"cookie" example:"true"`
|
||||
// Enabling DNSSEC
|
||||
DNSSEC bool `json:"dnssec" example:"false"`
|
||||
// Sending EDNS Expire
|
||||
Expire bool `json:"expire" example:"false"`
|
||||
// Sending EDNS TCP keepopen
|
||||
KeepOpen bool `json:"keepOpen" example:"false"`
|
||||
// Sending EDNS NSID
|
||||
Nsid bool `json:"nsid" example:"false"`
|
||||
// Send EDNS Padding
|
||||
Padding bool `json:"padding" example:"false"`
|
||||
// Set EDNS version (default: 0)
|
||||
Version uint8 `json:"version" example:"0"`
|
||||
}
|
||||
|
||||
// ParseSubnet takes a subnet argument and makes it into one that the DNS library
|
||||
// understands.
|
||||
func ParseSubnet(subnet string, opts *Options) error {
|
||||
ip, inet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
// TODO: make not a default?
|
||||
if subnet == "0" {
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{
|
||||
Code: dns.EDNS0SUBNET,
|
||||
Family: 1,
|
||||
SourceNetmask: 0,
|
||||
SourceScope: 0,
|
||||
Address: net.IPv4(0, 0, 0, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("EDNS subnet parsing: %w", err)
|
||||
}
|
||||
|
||||
sub, _ := inet.Mask.Size()
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{}
|
||||
opts.EDNS.Subnet.Address = ip
|
||||
opts.EDNS.Subnet.SourceNetmask = uint8(sub)
|
||||
|
||||
switch ip.To4() {
|
||||
case nil:
|
||||
// Not a valid IPv4 so assume IPv6
|
||||
opts.EDNS.Subnet.Family = 2
|
||||
default:
|
||||
// Valid IPv4
|
||||
opts.EDNS.Subnet.Family = 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
subnet := []string{
|
||||
"0.0.0.0/0",
|
||||
"::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))
|
||||
if err != nil {
|
||||
assert.ErrorContains(t, err, "invalid CIDR address")
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Response is the DNS response.
|
||||
type Response struct {
|
||||
// The full DNS response
|
||||
DNS *dns.Msg `json:"response"`
|
||||
// The time it took to make the DNS query
|
||||
RTT time.Duration `json:"rtt" example:"2000000000"`
|
||||
}
|
||||
|
||||
// Request is a structure for a DNS query.
|
||||
type Request struct {
|
||||
// Server to query
|
||||
Server string `json:"server" example:"1.0.0.1"`
|
||||
// Domain to query
|
||||
Name string `json:"name" example:"example.com"`
|
||||
// Duration to wait until marking request as failed
|
||||
Timeout time.Duration `json:"timeout" example:"2000000000"`
|
||||
// Port to make DNS request on
|
||||
Port int `json:"port" example:"53"`
|
||||
// Number of failures to make before giving up
|
||||
Retries int `json:"retries" example:"2"`
|
||||
// Request type, eg. A, AAAA, NAPTR
|
||||
Type uint16 `json:"type" example:"1"`
|
||||
// Request class, eg. IN
|
||||
Class uint16 `json:"class" example:"1"`
|
||||
}
|
|
@ -3,18 +3,30 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Given an IP or phone number, return a canonical string to be queried.
|
||||
type errReverseDNS struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
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.
|
||||
func ReverseDNS(address string, querInt uint16) (string, error) {
|
||||
query := dns.TypeToString[querInt]
|
||||
if query == "PTR" {
|
||||
return dns.ReverseAddr(address)
|
||||
str, err := dns.ReverseAddr(address)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("PTR reverse: %w", err)
|
||||
}
|
||||
|
||||
return str, nil
|
||||
} else if query == "NAPTR" {
|
||||
// get rid of characters not needed
|
||||
replacer := strings.NewReplacer("+", "", " ", "", "-", "")
|
||||
|
@ -27,10 +39,11 @@ func ReverseDNS(address string, querInt uint16) (string, error) {
|
|||
fmt.Fprintf(&arpa, "%c.", c)
|
||||
}
|
||||
arpa.WriteString("e164.arpa.")
|
||||
|
||||
return arpa.String(), nil
|
||||
}
|
||||
|
||||
return "", errors.New("ReverseDNS: -x flag given but no IP found")
|
||||
return "", &errReverseDNS{address}
|
||||
}
|
||||
|
||||
// Reverse a string, return the string in reverse.
|
||||
|
@ -39,5 +52,6 @@ func reverse(s string) string {
|
|||
for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 {
|
||||
rns[i], rns[j] = rns[j], rns[i]
|
||||
}
|
||||
|
||||
return string(rns)
|
||||
}
|
|
@ -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,58 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type DNSCryptResolver struct {
|
||||
opts cli.Options
|
||||
}
|
||||
|
||||
func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.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 helpers.Response{}, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
res, err := client.Exchange(msg, resolverInf)
|
||||
rtt := time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
r.opts.Logger.Info("Request successful")
|
||||
|
||||
return helpers.Response{
|
||||
DNS: res,
|
||||
RTT: rtt,
|
||||
}, nil
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"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) {
|
||||
tests := []struct {
|
||||
opt cli.Options
|
||||
}{
|
||||
{
|
||||
cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
Request: helpers.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: helpers.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
res, err := query.CreateQuery(test.opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type HTTPSResolver struct {
|
||||
opts cli.Options
|
||||
}
|
||||
|
||||
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
var resp helpers.Response
|
||||
httpR := &http.Client{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
}
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
r.opts.Logger.Debug("making DoH request")
|
||||
// query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("DoH: %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 helpers.Response{}, fmt.Errorf("DoH HTTP request error: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return helpers.Response{}, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode)
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("DoH body read error: %w", err)
|
||||
}
|
||||
resp.DNS = &dns.Msg{}
|
||||
r.opts.Logger.Debug("unpacking response")
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("DoH dns message unpack error: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"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 := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: helpers.Request{
|
||||
Server: "https://dns9.quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := helpers.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 != helpers.Response{})
|
||||
}
|
||||
|
||||
func Test2ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
}
|
||||
var err error
|
||||
testCase := helpers.Request{Server: "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(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, helpers.Response{})
|
||||
}
|
||||
|
||||
func Test3ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
}
|
||||
var err error
|
||||
testCase := helpers.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
// 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, "request error")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
}
|
||||
|
||||
func Test404ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
var err error
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: helpers.Request{
|
||||
Server: "https://dns9.quad9.net/dns",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := helpers.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, helpers.Response{})
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type QUICResolver struct {
|
||||
opts cli.Options
|
||||
}
|
||||
|
||||
func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
var resp helpers.Response
|
||||
tls := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
|
||||
conf := new(quic.Config)
|
||||
conf.HandshakeIdleTimeout = r.opts.Request.Timeout
|
||||
|
||||
r.opts.Logger.Debug("making DoQ request")
|
||||
connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
t := time.Now()
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
resp.RTT = time.Since(t)
|
||||
|
||||
// Close with error: no error
|
||||
err = connection.CloseWithError(0, "")
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
r.opts.Logger.Debug("unpacking DoQ response")
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"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 := cli.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: helpers.Request{Server: "dns.adguard.com"},
|
||||
}
|
||||
testCase := helpers.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
testCase2 := helpers.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
var testCases []helpers.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, helpers.Response{})
|
||||
case 1:
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port))
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(testCase2.Name, ".") {
|
||||
testCase2.Name = fmt.Sprintf("%s.", testCase2.Name)
|
||||
}
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase2.Name, testCase2.Type)
|
||||
msg = msg.SetQuestion(testCase2.Name, testCase2.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: helpers.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
|
||||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "timeout")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
// Package for the special query types
|
||||
package query
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue