Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
3d1f63144c | |||
3467eabfe7 | |||
c2eab0212a | |||
4353aa9559 | |||
f7e5a7adee | |||
grumbulon | be3b81029e | ||
grumbulon | dfea8060dd | ||
6d30a5ad9b | |||
a36f1b8685 | |||
Renovate Bot | d4b8d1469f | ||
Renovate Bot | f50b793931 | ||
68e6e54741 | |||
8b1a5f9147 | |||
6da6a55f6e | |||
grumbulon | 8c61f9f409 | ||
182ec20469 |
8
.drone.yml
Normal file
8
.drone.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
steps:
|
||||
- name: test
|
||||
image: golang
|
||||
commands:
|
||||
- go test ./...
|
|
@ -1,10 +0,0 @@
|
|||
# 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
|
|
@ -1,35 +0,0 @@
|
|||
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@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install scdoc
|
||||
run: apt-get update && apt-get install -y scdoc
|
||||
|
||||
- name: Release with GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean -p 4 --skip docker,snapcraft
|
||||
env:
|
||||
GORELEASER_FORCE_TOKEN: gitea
|
||||
GITEA_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
UPLOAD_PACKAGES_SECRET: ${{ secrets.PUBLISH_TOKEN }}
|
|
@ -1,14 +0,0 @@
|
|||
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 }}
|
|
@ -1,24 +0,0 @@
|
|||
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@v5
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Test
|
||||
run: make test-ci
|
30
.github/ISSUE_TEMPLATE/bug.md
vendored
30
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
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.
|
17
.github/ISSUE_TEMPLATE/feature.md
vendored
17
.github/ISSUE_TEMPLATE/feature.md
vendored
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
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.
|
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
|
@ -1,9 +0,0 @@
|
|||
<!--
|
||||
|
||||
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)
|
||||
|
||||
-->
|
56
.github/workflows/ghrelease.yaml
vendored
56
.github/workflows/ghrelease.yaml
vendored
|
@ -1,56 +0,0 @@
|
|||
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@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --skip=aur,homebrew,nix,scoop
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
24
.github/workflows/test.yaml
vendored
24
.github/workflows/test.yaml
vendored
|
@ -1,24 +0,0 @@
|
|||
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
|
47
.gitignore
vendored
47
.gitignore
vendored
|
@ -1,29 +1,18 @@
|
|||
# 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
|
||||
# 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
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/wiki"]
|
||||
path = docs/wiki
|
||||
url = ../awl.wiki
|
|
@ -1,88 +0,0 @@
|
|||
# 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
|
237
.goreleaser.yaml
237
.goreleaser.yaml
|
@ -1,237 +0,0 @@
|
|||
version: 2
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
- make clean
|
||||
- go mod tidy
|
||||
# Make manpages
|
||||
- docs/makeman.sh
|
||||
# Vendor dependencies
|
||||
- go mod vendor
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- go_first_class
|
||||
- plan9_amd64
|
||||
- freebsd_amd64
|
||||
|
||||
universal_binaries:
|
||||
- replace: true
|
||||
|
||||
archives:
|
||||
- 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"
|
||||
|
||||
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
|
||||
directory: 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:
|
||||
- "^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
|
||||
download: https://git.froth.zone
|
|
@ -1,3 +0,0 @@
|
|||
FROM scratch
|
||||
ENTRYPOINT ["/awl"]
|
||||
COPY awl /
|
30
GNUmakefile
30
GNUmakefile
|
@ -1,30 +0,0 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# GNU Makefile allowing for building on Windows (with GNU Make)
|
||||
|
||||
include template.mk
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE := $(PROG).exe
|
||||
else
|
||||
EXE := $(PROG)
|
||||
ifeq ($(shell uname), Darwin)
|
||||
INSTALLFLAGS :=
|
||||
else
|
||||
INSTALLFLAGS := D
|
||||
endif
|
||||
endif
|
||||
|
||||
## install: installs awl
|
||||
.PHONY: install
|
||||
ifeq ($(OS),Windows_NT)
|
||||
install:
|
||||
$(GO) install $(GOFLAGS) .
|
||||
else
|
||||
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
|
|
@ -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.
|
16
Makefile
16
Makefile
|
@ -1,16 +0,0 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# BSD/POSIX makefile
|
||||
|
||||
include template.mk
|
||||
|
||||
EXE := $(PROG)
|
||||
|
||||
## install: installs awl
|
||||
.PHONY: install
|
||||
install: all
|
||||
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)
|
229
README.md
229
README.md
|
@ -1,194 +1,35 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
# <img src="./docs/img/awl-text.png" width="50%" title="awl logo" alt="awl">
|
||||
|
||||
> awl *(noun)*: A pointed tool for making small holes in wood or leather
|
||||
|
||||
A command-line DNS lookup tool that supports DNS queries over UDP, TCP, TLS, HTTPS, DNSCrypt, and QUIC.
|
||||
|
||||
[![Gitea Release](https://img.shields.io/gitea/v/release/sam/awl?gitea_url=https%3A%2F%2Fgit.froth.zone&display_name=release&style=for-the-badge)](https://git.froth.zone/sam/awl)
|
||||
[![Last Commit](https://img.shields.io/gitea/last-commit/sam/awl?gitea_url=https%3A%2F%2Fgit.froth.zone&style=for-the-badge)](https://git.froth.zone/sam/awl/commits/branch/master)
|
||||
[![License](https://img.shields.io/github/license/samtherapy/awl?style=for-the-badge)](https://spdx.org/licenses/BSD-3-Clause.html)
|
||||
[![Go Report](https://goreportcard.com/badge/dns.froth.zone/awl?style=for-the-badge)](https://goreportcard.com/report/dns.froth.zone/awl)
|
||||
|
||||
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).
|
||||
|
||||
## Examples
|
||||
|
||||
```shell
|
||||
# Query a domain over UDP
|
||||
awl example.com
|
||||
|
||||
# Query a domain over HTTPS, print only the results
|
||||
awl example.com +https --short
|
||||
|
||||
# Query a domain over TLS, print as JSON
|
||||
awl example.com +tls +json
|
||||
```
|
||||
|
||||
For more and the usage, see the [manpage](https://git.froth.zone/sam/awl/wiki/awl.1).
|
||||
|
||||
## Installing
|
||||
|
||||
On any platform, with [Go](https://go.dev) installed, run the following command to install:
|
||||
|
||||
```shell
|
||||
go install dns.froth.zone/awl@latest
|
||||
```
|
||||
|
||||
### Packaging
|
||||
|
||||
Alternatively, many package managers are supported:
|
||||
|
||||
<details>
|
||||
<summary>Linux</summary>
|
||||
|
||||
#### Distro-specific
|
||||
|
||||
<details>
|
||||
<summary>Alpine Linux</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/alpine/awl-dns) \
|
||||
***Any distro that uses apk should also work***
|
||||
|
||||
```shell
|
||||
# Add the repository
|
||||
echo "https://git.froth.zone/api/packages/sam/alpine/edge/main" | tee -a /etc/apk/repositories
|
||||
# Get the signing key
|
||||
curl -JO https://git.froth.zone/api/packages/sam/alpine/key --output-dir /etc/apk/keys
|
||||
# Install
|
||||
apk add awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Arch</summary>
|
||||
|
||||
AUR package available as [awl-dns-git](https://aur.archlinux.org/packages/awl-dns-git/)
|
||||
|
||||
```shell
|
||||
yay -S awl-dns-git ||
|
||||
paru -S awl-dns-git
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Debian / Ubuntu</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/debian/awl-dns/) \
|
||||
***Any distro that uses deb/dpkg should also work***
|
||||
|
||||
```shell
|
||||
# Add PGP key
|
||||
sudo curl https://git.froth.zone/api/packages/sam/debian/repository.key -o /usr/share/keyrings/git-froth-zone-sam.asc
|
||||
# Add repo
|
||||
echo "deb [signed-by=/usr/share/keyrings/git-froth-zone-sam.asc] https://git.froth.zone/api/packages/sam/debian sid main" | sudo tee /etc/apt/sources.list.d/git-froth-zone-sam.list
|
||||
# Update and install
|
||||
sudo apt update
|
||||
sudo apt install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Fedora / RHEL / SUSE</summary>
|
||||
|
||||
Provided by [Gitea packages](https://git.froth.zone/sam/-/packages/rpm/awl-dns/) \
|
||||
***Any distro that uses rpm/dnf might also work, I've never tried it***
|
||||
|
||||
```shell
|
||||
# Add the repository
|
||||
dnf config-manager --add-repo https://git.froth.zone/api/packages/sam/rpm.repo ||
|
||||
zypper addrepo https://git.froth.zone/api/packages/sam/rpm.repo
|
||||
# Install
|
||||
dnf install awl-dns ||
|
||||
zypper install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Gentoo</summary>
|
||||
|
||||
```shell
|
||||
# Add the ebuild repository
|
||||
eselect repository add froth-zone git https://git.froth.zone/packaging/portage.git
|
||||
emaint sync -r froth-zone
|
||||
# Install
|
||||
emerge -av net-dns/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Distro-agnostic
|
||||
|
||||
|
||||
<details>
|
||||
<summary><a href="https://brew.sh" nofollow>Homebrew</a></summary>
|
||||
|
||||
```shell
|
||||
brew install SamTherapy/tap/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Snap</summary>
|
||||
|
||||
Snap package available as [awl-dns](https://snapcraft.io/awl-dns)
|
||||
|
||||
```shell
|
||||
snap install awl-dns ||
|
||||
sudo snap install awl-dns
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
<hr />
|
||||
<details>
|
||||
<summary>macOS</summary>
|
||||
|
||||
<details open>
|
||||
<summary><a href="https://brew.sh" nofollow>Homebrew</a></summary>
|
||||
|
||||
```shell
|
||||
brew install SamTherapy/tap/awl
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
<hr />
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
|
||||
<details open>
|
||||
<summary><a href="https://scoop.sh" nofollow>Scoop</a></summary>
|
||||
|
||||
```pwsh
|
||||
scoop bucket add froth https://git.froth.zone/packages/scoop.git
|
||||
scoop install awl
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see the [CONTRIBUTING.md](./docs/CONTRIBUTING.md) file for more information.
|
||||
|
||||
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)!
|
||||
|
||||
### Mirrors
|
||||
|
||||
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.
|
||||
# awl
|
||||
|
||||
`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)
|
||||
|
||||
This was made as my first major experiment with Go, so there are probably things that can be improved
|
||||
|
||||
The excellent [dns](https://github.com/miekg/dns) library for Go does most of the heavy
|
||||
lifting.
|
||||
|
||||
## What works
|
||||
|
||||
- UDP
|
||||
- TCP
|
||||
- TLS
|
||||
- HTTPS (maybe)
|
||||
- QUIC (extreme maybe)
|
||||
|
||||
## What doesn't
|
||||
|
||||
- Your sanity after reading my awful code
|
||||
- A motivation for making this after finding q and doggo
|
||||
|
||||
## What should change
|
||||
|
||||
- Make the CLI less abysmal (migrate to [cobra](https://github.com/spf13/cobra)?
|
||||
or just use stdlib's flags)
|
||||
- Optimize everything
|
||||
- Make the code less spaghetti (partially completed)
|
||||
- Feature parity with drill
|
||||
- Making a drop-in replacement for drill?
|
17
awl.go
Normal file
17
awl.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := prepareCLI()
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
200
cli.go
Normal file
200
cli.go
Normal file
|
@ -0,0 +1,200 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// Do all the magic CLI crap
|
||||
func prepareCLI() *cli.App {
|
||||
// Custom version string
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
fmt.Printf("%s version %s, built with %s\n", c.App.Name, c.App.Version, runtime.Version())
|
||||
}
|
||||
|
||||
cli.VersionFlag = &cli.BoolFlag{
|
||||
Name: "v",
|
||||
Usage: "show version and exit",
|
||||
}
|
||||
|
||||
cli.HelpFlag = &cli.BoolFlag{
|
||||
Name: "h",
|
||||
Usage: "show this help and exit",
|
||||
}
|
||||
|
||||
// Hack to get rid of the annoying default on the CLI
|
||||
oldFlagStringer := cli.FlagStringer
|
||||
cli.FlagStringer = func(f cli.Flag) string {
|
||||
return strings.TrimSuffix(oldFlagStringer(f), " (default: false)")
|
||||
}
|
||||
|
||||
cli.AppHelpTemplate = `{{.Name}} - {{.Usage}}
|
||||
|
||||
Usage: {{.HelpName}} name [@server] [record]
|
||||
<name> can be a name or an IP address
|
||||
<record> defaults to A
|
||||
|
||||
arguments can be in any order
|
||||
{{if .VisibleFlags}}
|
||||
Options:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}`
|
||||
app := &cli.App{
|
||||
Name: "awl",
|
||||
Usage: "drill, writ small",
|
||||
Version: "v0.2.1",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "port",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "`<port>` to make DNS query",
|
||||
DefaultText: "53 over plain TCP/UDP, 853 over TLS or QUIC",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "4",
|
||||
Usage: "force IPv4",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "6",
|
||||
Usage: "force IPv6",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dnssec",
|
||||
Aliases: []string{"D"},
|
||||
Usage: "enable DNSSEC",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "json",
|
||||
Aliases: []string{"j"},
|
||||
Usage: "return the result(s) as JSON",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "short",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "print just the results, equivalent to dig +short",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "tcp",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "use TCP (default: use UDP)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "tls",
|
||||
Aliases: []string{"T"},
|
||||
Usage: "use DNS-over-TLS",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "https",
|
||||
Aliases: []string{"H"},
|
||||
Usage: "use DNS-over-HTTPS",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "quic",
|
||||
Aliases: []string{"Q"},
|
||||
Usage: "use DNS-over-QUIC",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-truncate",
|
||||
Usage: "ignore truncation if a UDP request truncates (default: retry with TCP)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "aa",
|
||||
Usage: "set AA (Authoratative Answer) flag (default: not set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "tc",
|
||||
Usage: "set tc (TrunCated) flag (default: not set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "z",
|
||||
Usage: "set Z (Zero) flag (default: not set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "cd",
|
||||
Usage: "set CD (Checking Disabled) flag (default: not set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-rd",
|
||||
Usage: "UNset RD (Recursion Desired) flag (default: set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-ra",
|
||||
Usage: "UNset RA (Recursion Available) flag (default: set)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "reverse",
|
||||
Aliases: []string{"x"},
|
||||
Usage: "do a reverse lookup",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug logging",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: doQuery,
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
// Parse the wildcard arguments, drill style
|
||||
func parseArgs(args []string) (util.Answers, error) {
|
||||
var (
|
||||
resp util.Response
|
||||
err 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, "@"):
|
||||
resp.Answers.Server = strings.Split(arg, "@")[1]
|
||||
case strings.Contains(arg, "."):
|
||||
resp.Answers.Name, err = idna.ToUnicode(arg)
|
||||
if err != nil {
|
||||
return util.Answers{}, err
|
||||
}
|
||||
case ok:
|
||||
// If it's a DNS request, it's a DNS request (obviously)
|
||||
resp.Answers.Request = r
|
||||
default:
|
||||
//else, assume it's a name
|
||||
resp.Answers.Name, err = idna.ToUnicode(arg)
|
||||
if err != nil {
|
||||
return util.Answers{}, err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing was set, set a default
|
||||
if resp.Answers.Name == "" {
|
||||
resp.Answers.Name = "."
|
||||
if resp.Answers.Request == 0 {
|
||||
resp.Answers.Request = dns.StringToType["NS"]
|
||||
}
|
||||
} else {
|
||||
if resp.Answers.Request == 0 {
|
||||
resp.Answers.Request = dns.StringToType["A"]
|
||||
}
|
||||
}
|
||||
if resp.Answers.Server == "" {
|
||||
resolv, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil { // Query Google by default, needed for Windows since the DNS library doesn't support Windows
|
||||
// TODO: Actually find where windows stuffs its dns resolvers
|
||||
resp.Answers.Server = "8.8.4.4"
|
||||
} else {
|
||||
resp.Answers.Server = resolv.Servers[rand.Intn(len(resolv.Servers))]
|
||||
}
|
||||
}
|
||||
|
||||
return util.Answers{Server: resp.Answers.Server, Request: resp.Answers.Request, Name: resp.Answers.Name}, nil
|
||||
}
|
61
cli_test.go
Normal file
61
cli_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
app := prepareCLI()
|
||||
// What more can even be done lmao
|
||||
require.NotNil(t, app)
|
||||
}
|
||||
|
||||
func TestArgParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []string
|
||||
want util.Answers
|
||||
}{
|
||||
{
|
||||
[]string{"@::1", "localhost", "AAAA"},
|
||||
util.Answers{Server: "::1", Request: dns.TypeAAAA, Name: "localhost"},
|
||||
},
|
||||
{
|
||||
[]string{"@1.0.0.1", "google.com"},
|
||||
util.Answers{Server: "1.0.0.1", Request: dns.TypeA, Name: "google.com"},
|
||||
},
|
||||
{
|
||||
[]string{"@8.8.4.4"},
|
||||
util.Answers{Server: "8.8.4.4", Request: dns.TypeNS, Name: "."},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
act, err := parseArgs(test.in)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.want, act)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
app := prepareCLI()
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "--debug")
|
||||
err := app.Run(args)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestHTTPS(t *testing.T) {
|
||||
app := prepareCLI()
|
||||
args := os.Args[0:1]
|
||||
args = append(args, "-H")
|
||||
args = append(args, "@https://cloudflare-dns.com/dns-query")
|
||||
args = append(args, "git.froth.zone")
|
||||
err := app.Run(args)
|
||||
assert.Nil(t, err)
|
||||
}
|
269
cmd/cli.go
269
cmd/cli.go
|
@ -1,269 +0,0 @@
|
|||
// 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)
|
||||
}
|
189
cmd/cli_test.go
189
cmd/cli_test.go
|
@ -1,189 +0,0 @@
|
|||
// 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")
|
||||
})
|
||||
}
|
246
cmd/dig.go
246
cmd/dig.go
|
@ -1,246 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// 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:")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// 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
|
196
cmd/misc.go
196
cmd/misc.go
|
@ -1,196 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"dns.froth.zone/awl/conf"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// 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 = strings.TrimPrefix(arg, "tls://")
|
||||
opts.Logger.Info("DNS-over-TLS implicitly set")
|
||||
case strings.HasPrefix(arg, "https://"):
|
||||
opts.HTTPS = true
|
||||
opts.Request.Server = arg
|
||||
opts.Logger.Info("DNS-over-HTTPS implicitly set")
|
||||
|
||||
_, endpoint, isSplit := strings.Cut(arg, "/")
|
||||
if isSplit {
|
||||
opts.HTTPSOptions.Endpoint = "/" + endpoint
|
||||
}
|
||||
case strings.HasPrefix(arg, "quic://"):
|
||||
opts.QUIC = true
|
||||
opts.Request.Server = strings.TrimPrefix(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:
|
||||
// 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 fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
|
||||
// DNS query type
|
||||
case ok:
|
||||
opts.Logger.Info(arg, "detected as a type")
|
||||
opts.Request.Type = r
|
||||
|
||||
// Domain?
|
||||
default:
|
||||
var err error
|
||||
|
||||
opts.Logger.Info(arg, "is unknown. Assuming domain")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing was set, set a default
|
||||
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 if opts.Request.Type == 0 {
|
||||
opts.Logger.Info("Query not specified, making an \"A\" query")
|
||||
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
|
||||
switch {
|
||||
case opts.DNSCrypt:
|
||||
// This is adguard
|
||||
opts.Request.Server = "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"
|
||||
case opts.TLS:
|
||||
opts.Request.Server = "dns.google"
|
||||
case opts.HTTPS:
|
||||
opts.Request.Server = "https://dns.cloudflare.com"
|
||||
case opts.QUIC:
|
||||
opts.Request.Server = "dns.froth.zone"
|
||||
default:
|
||||
var err error
|
||||
resolv, err := conf.GetDNSConfig()
|
||||
|
||||
if err != nil {
|
||||
// :^)
|
||||
opts.Logger.Warn("Could not query system for server. Using localhost\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 {
|
||||
switch {
|
||||
case opts.IPv4:
|
||||
if strings.Contains(srv, ".") {
|
||||
opts.Request.Server = srv
|
||||
|
||||
break harmful
|
||||
}
|
||||
case opts.IPv6:
|
||||
if strings.Contains(srv, ":") {
|
||||
opts.Request.Server = srv
|
||||
|
||||
break harmful
|
||||
}
|
||||
default:
|
||||
//#nosec -- This isn't used for anything secure
|
||||
opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))]
|
||||
|
||||
break harmful
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("DNS server set to", opts.Request.Server)
|
||||
|
||||
// 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 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.Info("Domain made canonical")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
195
cmd/misc_test.go
195
cmd/misc_test.go
|
@ -1,195 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
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(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Name, "go.dev.")
|
||||
assert.Equal(t, opts.Request.Type, dns.StringToType["AAAA"])
|
||||
assert.Equal(t, opts.Request.Server, "1.1.1.1")
|
||||
assert.Equal(t, opts.Truncate, true)
|
||||
}
|
||||
|
||||
func TestParseNoInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Name, ".")
|
||||
assert.Equal(t, opts.Request.Type, dns.StringToType["NS"])
|
||||
}
|
||||
|
||||
func TestParseA(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{
|
||||
"golang.org.",
|
||||
}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Name, "golang.org.")
|
||||
assert.Equal(t, opts.Request.Type, dns.StringToType["A"])
|
||||
}
|
||||
|
||||
func TestParsePTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.8.8.8"}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Type, dns.StringToType["PTR"])
|
||||
}
|
||||
|
||||
func TestParseInvalidPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.88.8"}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.ErrorContains(t, err, "unrecognized address")
|
||||
}
|
||||
|
||||
func TestDefaultServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"DNSCrypt", "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"},
|
||||
{"TLS", "dns.google"},
|
||||
{"HTTPS", "https://dns.cloudflare.com"},
|
||||
{"QUIC", "dns.froth.zone"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := []string{}
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
switch test.in {
|
||||
case "DNSCrypt":
|
||||
opts.DNSCrypt = true
|
||||
case "TLS":
|
||||
opts.TLS = true
|
||||
case "HTTPS":
|
||||
opts.HTTPS = true
|
||||
case "QUIC":
|
||||
opts.QUIC = true
|
||||
}
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Request.Server, test.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagSetting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseArgs(f *testing.F) {
|
||||
cases := []string{
|
||||
"go.dev",
|
||||
"AAAA",
|
||||
"@1.1.1.1",
|
||||
"+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(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseMiscArgs(args, opts)
|
||||
})
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# bash completion for awl -*- shell-script -*-
|
||||
|
||||
|
||||
# TODO: MAKE THIS A REAL THING
|
||||
complete -F _known_hosts awl
|
|
@ -1,75 +0,0 @@
|
|||
# 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)'
|
|
@ -1,112 +0,0 @@
|
|||
#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,8 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
Package conf contains helper functions for getting local nameservers
|
||||
|
||||
Currently supported: Unix, Windows, Plan 9 (tested on 9front)
|
||||
*/
|
||||
package conf
|
|
@ -1,57 +0,0 @@
|
|||
// 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 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, errPlan9
|
||||
}
|
||||
|
||||
// TODO: read more about how customizable Plan 9 is
|
||||
return &dns.ClientConfig{
|
||||
Servers: servers,
|
||||
Search: []string{},
|
||||
Port: "53",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Split the string at either space or tabs.
|
||||
func splitChars(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
var errPlan9 = errors.New("plan9Config: no DNS servers found")
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build plan9
|
||||
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestPlan9Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if runtime.GOOS != "plan9" {
|
||||
t.Skip("Not running Plan 9, skipping")
|
||||
}
|
||||
|
||||
conf, err := conf.GetDNSConfig()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
}
|
22
conf/unix.go
22
conf/unix.go
|
@ -1,22 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build unix || (!windows && !plan9 && !js && !zos)
|
||||
|
||||
// FIXME: Can remove the or on the preprocessor when Go 1.18 becomes obsolete
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// GetDNSConfig gets the DNS configuration, either from /etc/resolv.conf or somewhere else.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix config: %w", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build unix || (!windows && !plan9 && !js && !zos)
|
||||
|
||||
// FIXME: Can remove the or on the preprocessor when Go 1.18 becomes obsolete
|
||||
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"dns.froth.zone/awl/conf"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
18
conf/wasm.go
18
conf/wasm.go
|
@ -1,18 +0,0 @@
|
|||
// 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")
|
82
conf/win.go
82
conf/win.go
|
@ -1,82 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
/*
|
||||
"Stolen" from
|
||||
https://gist.github.com/moloch--/9fb1c8497b09b45c840fe93dd23b1e98
|
||||
*/
|
||||
|
||||
// GetDNSConfig (Windows version) returns all DNS server addresses using windows fuckery.
|
||||
//
|
||||
// Here be dragons.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
length := uint32(100000)
|
||||
byt := make([]byte, length)
|
||||
|
||||
// Windows is an utter fucking trash fire of an operating system.
|
||||
//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
|
||||
//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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Take unique values only
|
||||
servers := []string{}
|
||||
for server := range resolvers {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
// TODO: Make configurable, based on defaults in https://github.com/miekg/dns/blob/master/clientconfig.go
|
||||
return &dns.ClientConfig{
|
||||
Servers: servers,
|
||||
Search: []string{},
|
||||
Port: "53",
|
||||
Ndots: 1,
|
||||
Timeout: 5,
|
||||
Attempts: 1,
|
||||
}, nil
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build windows
|
||||
|
||||
package conf_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"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)
|
||||
}
|
7
docs.go
7
docs.go
|
@ -1,10 +1,9 @@
|
|||
// 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, XML and YAML.
|
||||
Options are given to print with JSON
|
||||
|
||||
Supports results from DNS-over-[UDP, TCP, TLS, HTTPS, QUIC] servers
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/dev/null
|
|
@ -1,57 +0,0 @@
|
|||
# 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.
|
262
docs/awl.1.scd
262
docs/awl.1.scd
|
@ -1,262 +0,0 @@
|
|||
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.
Before Width: | Height: | Size: 195 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 505 KiB |
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
rm -f docs/awl.1.gz
|
||||
scdoc <docs/awl.1.scd >docs/awl.1
|
||||
gzip -9 -n docs/awl.1
|
|
@ -1 +0,0 @@
|
|||
Subproject commit ab0ac7e0bd1b92339cc97ba026f148478df5a860
|
54
go.mod
54
go.mod
|
@ -1,31 +1,37 @@
|
|||
module dns.froth.zone/awl
|
||||
module git.froth.zone/sam/awl
|
||||
|
||||
go 1.22.5
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
dns.froth.zone/dnscrypt v0.0.2
|
||||
github.com/dchest/uniuri v1.2.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/quic-go/quic-go v0.47.0
|
||||
github.com/stefansundin/go-zflag v1.1.1
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/sys v0.25.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.1
|
||||
github.com/lucas-clemente/quic-go v0.27.2
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/urfave/cli/v2 v2.10.3
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.25.1 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // 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/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
|
||||
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
|
||||
)
|
||||
|
|
358
go.sum
358
go.sum
|
@ -1,60 +1,312 @@
|
|||
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.25.1 h1:po5dBbFCoZAySsbsMN/ZRB0WTLYDA1d8BxPgvriu/EA=
|
||||
github.com/AdguardTeam/golibs v0.25.1/go.mod h1:HaTyS2wCbxFudjht9N/+/Qf1b5cMad2BAYSwe7DPCXI=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
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/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/uniuri 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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
|
||||
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
|
||||
github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os=
|
||||
github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo=
|
||||
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/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/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.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs=
|
||||
github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A=
|
||||
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/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.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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
|
||||
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
|
||||
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/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/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.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/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo=
|
||||
github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
|
||||
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/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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.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-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 h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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-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-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-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-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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/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=
|
||||
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.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.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
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=
|
||||
|
|
30
logawl/doc.go
Normal file
30
logawl/doc.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
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
|
||||
because awl is a cli utility it writes directly to std err.
|
||||
*/
|
||||
// Use the New() function to init logawl
|
||||
//
|
||||
// logger := logawl.New()
|
||||
//
|
||||
// You can call specific logging levels from your new logger using
|
||||
//
|
||||
// logger.Debug("Message to log")
|
||||
// logger.Fatal("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
|
||||
//for example if
|
||||
// Logger.SetLevel(3)
|
||||
// is not called and you call
|
||||
// Logger.Debug()
|
||||
// this runs through
|
||||
// IsLevel(level)
|
||||
// to verify if the debug log should be sent to std.Err or not based on the current expected log level
|
||||
package logawl
|
|
@ -1,7 +1,5 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/*
|
||||
Package logawl is a package for custom logging needs
|
||||
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
|
||||
|
@ -14,16 +12,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.Warning("Message to log")
|
||||
// logger.Fatal("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
|
||||
// for example if
|
||||
// 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
|
||||
// Logger.Debug()
|
74
logawl/logawl.go
Normal file
74
logawl/logawl.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Level int32
|
||||
type 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 "FATAL ", nil
|
||||
case 1:
|
||||
return "ERROR ", nil
|
||||
case 2:
|
||||
return "INFO ", nil
|
||||
case 3:
|
||||
return "DEBUG ", nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid log level choice")
|
||||
}
|
||||
|
||||
func (l *Logger) IsLevel(level Level) bool {
|
||||
return l.level() >= level
|
||||
}
|
||||
|
||||
var AllLevels = []Level{
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
const (
|
||||
// Fatal logs (will call exit(1))
|
||||
FatalLevel Level = iota
|
||||
|
||||
// Error logs
|
||||
ErrorLevel
|
||||
|
||||
// What is going on level
|
||||
InfoLevel
|
||||
// Verbose log level.
|
||||
DebugLevel
|
||||
)
|
130
logawl/logger.go
Normal file
130
logawl/logger.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
// 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:
|
||||
l.Printer(0, fmt.Sprintln(v...)) //Fatal level
|
||||
os.Exit(1)
|
||||
case 1:
|
||||
l.Printer(1, fmt.Sprintln(v...)) //Error level
|
||||
os.Exit(2)
|
||||
case 2:
|
||||
l.Printer(2, fmt.Sprintln(v...)) //Info level
|
||||
case 3:
|
||||
l.Printer(3, fmt.Sprintln(v...)) //Debug level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 {
|
||||
fmt.Printf("Unable to unmarshal log level: %v", err)
|
||||
os.Exit(2) //Fucking kill him
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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]
|
||||
l.formatHeader(&l.buf, now, line, level)
|
||||
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 Error level
|
||||
func (l *Logger) Error(v ...any) {
|
||||
l.Println(ErrorLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Fatal level
|
||||
func (l *Logger) Fatal(v ...any) {
|
||||
l.Println(FatalLevel, v...)
|
||||
}
|
82
logawl/logging_test.go
Normal file
82
logawl/logging_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package logawl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var logger = New()
|
||||
|
||||
func TestLogawl(t *testing.T) {
|
||||
|
||||
assert.Equal(t, Level(2), logger.Level) //cast 2 (int) to 2 (level)
|
||||
|
||||
//Validate setting and getting levels from memory works
|
||||
for i := range AllLevels {
|
||||
logger.SetLevel(Level(i))
|
||||
assert.Equal(t, Level(i), logger.GetLevel())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUnmarshalLevels(t *testing.T) {
|
||||
m := make(map[int]string)
|
||||
var err error
|
||||
//Fill map with unmarshalled level info
|
||||
for i := range AllLevels {
|
||||
m[i], err = logger.UnMarshalLevel(Level(i))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
//iterate over map and assert equal
|
||||
for i := range AllLevels {
|
||||
lv, err := logger.UnMarshalLevel(Level(i))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, m[i], lv)
|
||||
}
|
||||
|
||||
lv, err := logger.UnMarshalLevel(Level(9001))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", lv)
|
||||
assert.ErrorContains(t, err, "invalid log level choice")
|
||||
}
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
|
||||
for i := range AllLevels {
|
||||
// only test non-exiting log levels
|
||||
switch i {
|
||||
case 1:
|
||||
fn := func() {
|
||||
logger.Info("")
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
logger.Out = &buffer
|
||||
fn()
|
||||
case 2:
|
||||
fn := func() {
|
||||
logger.Info("Test")
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
logger.Out = &buffer
|
||||
fn()
|
||||
case 3:
|
||||
fn := func() {
|
||||
logger.Debug("Test")
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
logger.Out = &buffer
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFmt(t *testing.T) {
|
||||
ti := time.Now()
|
||||
test := []byte("test")
|
||||
logger.formatHeader(&test, ti, 0, Level(9001))
|
||||
}
|
135
main.go
135
main.go
|
@ -1,135 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cli "dns.froth.zone/awl/cmd"
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func main() {
|
||||
if opts, code, err := run(os.Args); err != nil {
|
||||
// TODO: Make not ew
|
||||
if errors.Is(err, util.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
opts.Logger.Error(err)
|
||||
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
|
||||
}
|
48
main_test.go
48
main_test.go
|
@ -1,48 +0,0 @@
|
|||
// 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)
|
||||
}
|
36
mkfile
36
mkfile
|
@ -1,36 +0,0 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Plan 9 mkfile
|
||||
|
||||
</$objtype/mkfile
|
||||
|
||||
GO = go
|
||||
PROG = awl
|
||||
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
|
||||
|
||||
all:V: $PROG
|
||||
|
||||
$PROG:
|
||||
$GO build $GOFLAGS -o $target .
|
||||
|
||||
install:V:
|
||||
$GO install $GOFLAGS .
|
||||
# cp docs/$PROG.1 /sys/man/1/$PROG
|
||||
|
||||
test:V:
|
||||
$GO test -v -cover ./...
|
||||
|
||||
fmt:V:
|
||||
gofmt -w -s .
|
||||
|
||||
vet:V:
|
||||
$GO vet ./...
|
||||
|
||||
lint:V: fmt vet
|
||||
|
||||
clean:V:
|
||||
$GO clean
|
||||
|
||||
nuke:V: clean
|
|
@ -1,90 +0,0 @@
|
|||
// 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")
|
|
@ -1,175 +0,0 @@
|
|||
// 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...))
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/logawl"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var logger = logawl.New()
|
||||
|
||||
func TestLogawl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
logger.SetLevel(logawl.Level(i))
|
||||
assert.Equal(t, logawl.Level(i), logger.GetLevel())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := make(map[int]string)
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
var err error
|
||||
m[i], err = logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
lv, err := logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, m[i], lv)
|
||||
}
|
||||
|
||||
lv, err := logger.UnMarshalLevel(logawl.Level(9001))
|
||||
assert.Equal(t, "", lv)
|
||||
assert.ErrorContains(t, err, "invalid log level")
|
||||
}
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
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.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFmt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ti := time.Now()
|
||||
test := []byte("test")
|
||||
// make sure error is error
|
||||
assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level")
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package query is for the various query types.
|
||||
package query
|
|
@ -1,313 +0,0 @@
|
|||
// 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")
|
|
@ -1,230 +0,0 @@
|
|||
// 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")
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
// 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)
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
// 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 != "")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
// 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")
|
|
@ -1,258 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
// 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
// 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{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// 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{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package resolvers contain the various DNS resolvers to use.
|
||||
*/
|
||||
package resolvers
|
|
@ -1,96 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package resolvers_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dns.froth.zone/awl/pkg/query"
|
||||
"dns.froth.zone/awl/pkg/util"
|
||||
"dns.froth.zone/dnscrypt"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:govet // I could not be assed to refactor this, and it is only for tests
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *util.Options
|
||||
}{
|
||||
{
|
||||
"UDP",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"UDP (Bad Cookie)",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
BadCookie: false,
|
||||
Request: util.Request{
|
||||
Server: "b.root-servers.net",
|
||||
Port: 53,
|
||||
Type: dns.TypeNS,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"UDP (Truncated)",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
IPv4: true,
|
||||
Request: util.Request{
|
||||
Server: "madns.binarystar.systems",
|
||||
Port: 5301,
|
||||
Type: dns.TypeTXT,
|
||||
Name: "limit.txt.example.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"TCP",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Port: 53,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"TLS",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Port: 853,
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Timeout",
|
||||
&util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.1",
|
||||
Port: 1,
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
Timeout: time.Millisecond * 100,
|
||||
Retries: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
res util.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i <= test.opts.Request.Retries; i++ {
|
||||
res, err = query.CreateQuery(test.opts)
|
||||
if err == nil || errors.Is(err, dnscrypt.ErrInvalidDNSStamp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package util contains helper functions that don't belong anywhere else
|
||||
package util
|
|
@ -1,19 +0,0 @@
|
|||
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")
|
|
@ -1,14 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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))
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// 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"`
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
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" {
|
||||
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("+", "", " ", "", "-", "")
|
||||
address = replacer.Replace(address)
|
||||
// reverse the order of the string
|
||||
address = reverse(address)
|
||||
var arpa strings.Builder
|
||||
// Make it canonical
|
||||
for _, c := range address {
|
||||
fmt.Fprintf(&arpa, "%c.", c)
|
||||
}
|
||||
arpa.WriteString("e164.arpa.")
|
||||
|
||||
return arpa.String(), nil
|
||||
}
|
||||
|
||||
return "", &errReverseDNS{address}
|
||||
}
|
||||
|
||||
// Reverse a string, return the string in reverse.
|
||||
func reverse(s string) string {
|
||||
rns := []rune(s)
|
||||
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)
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
// 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")
|
||||
}
|
186
query.go
Normal file
186
query.go
Normal file
|
@ -0,0 +1,186 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func doQuery(c *cli.Context) error {
|
||||
var (
|
||||
err error
|
||||
resp util.Response
|
||||
isHTTPS bool
|
||||
Logger = logawl.New() //init logger
|
||||
)
|
||||
|
||||
resp.Answers, err = parseArgs(c.Args().Slice())
|
||||
if err != nil {
|
||||
Logger.Error("Unable to parse args")
|
||||
return err
|
||||
}
|
||||
port := c.Int("port")
|
||||
if c.Bool("debug") {
|
||||
Logger.SetLevel(3)
|
||||
}
|
||||
|
||||
Logger.Debug("Starting awl")
|
||||
// If port is not set, set it
|
||||
if port == 0 {
|
||||
if c.Bool("tls") || c.Bool("quic") {
|
||||
port = 853
|
||||
} else {
|
||||
port = 53
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("https") || strings.HasPrefix(resp.Answers.Server, "https://") {
|
||||
// add https:// if it doesn't already exist
|
||||
if !strings.HasPrefix(resp.Answers.Server, "https://") {
|
||||
resp.Answers.Server = "https://" + resp.Answers.Server
|
||||
}
|
||||
isHTTPS = true
|
||||
} else {
|
||||
resp.Answers.Server = net.JoinHostPort(resp.Answers.Server, strconv.Itoa(port))
|
||||
}
|
||||
|
||||
// Process the IP/Phone number so a PTR/NAPTR can be done
|
||||
if c.Bool("reverse") {
|
||||
if dns.TypeToString[resp.Answers.Request] == "A" {
|
||||
resp.Answers.Request = dns.StringToType["PTR"]
|
||||
}
|
||||
resp.Answers.Name, err = util.ReverseDNS(resp.Answers.Name, dns.TypeToString[resp.Answers.Request])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(resp.Answers.Name, ".") {
|
||||
resp.Answers.Name = fmt.Sprintf("%s.", resp.Answers.Name)
|
||||
}
|
||||
|
||||
msg := new(dns.Msg)
|
||||
|
||||
msg.SetQuestion(resp.Answers.Name, resp.Answers.Request)
|
||||
|
||||
// Make this authoritative (does this do anything?)
|
||||
if c.Bool("aa") {
|
||||
msg.Authoritative = true
|
||||
}
|
||||
// Set truncated flag (why)
|
||||
if c.Bool("tc") {
|
||||
msg.Truncated = true
|
||||
}
|
||||
// Set the zero flag if requested (does nothing)
|
||||
if c.Bool("z") {
|
||||
Logger.Debug("Setting message to zero")
|
||||
msg.Zero = true
|
||||
}
|
||||
// Disable DNSSEC validation
|
||||
if c.Bool("cd") {
|
||||
msg.CheckingDisabled = true
|
||||
}
|
||||
// Disable wanting recursion
|
||||
if c.Bool("no-rd") {
|
||||
msg.RecursionDesired = false
|
||||
}
|
||||
// Disable recursion being available (I don't think this does anything)
|
||||
if c.Bool("no-ra") {
|
||||
msg.RecursionAvailable = false
|
||||
}
|
||||
// Set DNSSEC if requested
|
||||
if c.Bool("dnssec") {
|
||||
Logger.Debug("Using DNSSEC")
|
||||
msg.SetEdns0(1232, true)
|
||||
}
|
||||
|
||||
var in *dns.Msg
|
||||
|
||||
// Make the DNS request
|
||||
if isHTTPS {
|
||||
in, resp.Answers.RTT, err = query.ResolveHTTPS(msg, resp.Answers.Server)
|
||||
} else if c.Bool("quic") {
|
||||
in, resp.Answers.RTT, err = query.ResolveQUIC(msg, resp.Answers.Server)
|
||||
} else {
|
||||
|
||||
d := new(dns.Client)
|
||||
|
||||
// Set TCP/UDP, depending on flags
|
||||
if c.Bool("tcp") || c.Bool("tls") {
|
||||
d.Net = "tcp"
|
||||
} else {
|
||||
d.Net = "udp"
|
||||
}
|
||||
|
||||
// Set IPv4 or IPv6, depending on flags
|
||||
switch {
|
||||
case c.Bool("4"):
|
||||
d.Net += "4"
|
||||
case c.Bool("6"):
|
||||
d.Net += "6"
|
||||
}
|
||||
|
||||
// Add TLS, if requested
|
||||
if c.Bool("tls") {
|
||||
d.Net += "-tls"
|
||||
}
|
||||
|
||||
in, resp.Answers.RTT, err = d.Exchange(msg, resp.Answers.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If UDP truncates, use TCP instead (unless truncation is to be ignored)
|
||||
if in.MsgHdr.Truncated && !c.Bool("no-truncate") {
|
||||
fmt.Printf(";; Truncated, retrying with TCP\n\n")
|
||||
d.Net = "tcp"
|
||||
switch {
|
||||
case c.Bool("4"):
|
||||
d.Net += "4"
|
||||
case c.Bool("6"):
|
||||
d.Net += "6"
|
||||
}
|
||||
in, resp.Answers.RTT, err = d.Exchange(msg, resp.Answers.Server)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Bool("json") {
|
||||
json, err := json.MarshalIndent(in, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
} else {
|
||||
if !c.Bool("short") {
|
||||
// Print everything
|
||||
fmt.Println(in)
|
||||
fmt.Println(";; Query time:", resp.Answers.RTT)
|
||||
fmt.Println(";; SERVER:", resp.Answers.Server)
|
||||
fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123Z))
|
||||
fmt.Println(";; MSG SIZE rcvd:", in.Len())
|
||||
} else {
|
||||
// Print just the responses, nothing else
|
||||
for _, res := range in.Answer {
|
||||
temp := strings.Split(res.String(), "\t")
|
||||
fmt.Println(temp[len(temp)-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
56
query/HTTPS.go
Normal file
56
query/HTTPS.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Resolve a DNS-over-HTTPS query
|
||||
//
|
||||
// Currently only supports POST requests
|
||||
func ResolveHTTPS(msg *dns.Msg, server string) (*dns.Msg, time.Duration, error) {
|
||||
httpR := &http.Client{}
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
req, err := http.NewRequest("POST", server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("DoH: %s", err.Error())
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
now := time.Now()
|
||||
res, err := httpR.Do(req)
|
||||
rtt := time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("DoH HTTP request error: %s", err.Error())
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, 0, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode)
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("DoH body read error: %s", err.Error())
|
||||
}
|
||||
response := dns.Msg{}
|
||||
err = response.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("DoH dns message unpack error: %s", err.Error())
|
||||
}
|
||||
|
||||
return &response, rtt, nil
|
||||
}
|
63
query/QUIC.go
Normal file
63
query/QUIC.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Resolve DNS over QUIC, the hip new standard (for privacy I think, IDK)
|
||||
func ResolveQUIC(msg *dns.Msg, server string) (*dns.Msg, time.Duration, error) {
|
||||
tls := &tls.Config{
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
connection, err := quic.DialAddr(server, tls, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
t := time.Now()
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rtt := time.Since(t)
|
||||
|
||||
// Close with error: no error
|
||||
err = connection.CloseWithError(0, "")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
response := dns.Msg{}
|
||||
err = response.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return &response, rtt, nil
|
||||
}
|
2
query/docs.go
Normal file
2
query/docs.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package for the special query types
|
||||
package query
|
56
query/query_test.go
Normal file
56
query/query_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHTTPS(t *testing.T) {
|
||||
|
||||
for i := range queries {
|
||||
switch i {
|
||||
case 1:
|
||||
msg := new(dns.Msg)
|
||||
testCase := util.Answers{Server: UnmarshallHTTPS(i), Request: dns.TypeAAAA, Name: "localhost"}
|
||||
msg = msg.SetQuestion(testCase.Name, testCase.Request)
|
||||
m, rtt, err := ResolveHTTPS(msg, testCase.Name)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, m)
|
||||
assert.Equal(t, rtt, time.Duration(0))
|
||||
case 2:
|
||||
msg := new(dns.Msg)
|
||||
testCase := util.Answers{Server: UnmarshallHTTPS(i), Request: dns.TypeAAAA, Name: "localhost"}
|
||||
msg = msg.SetQuestion(testCase.Name, testCase.Request)
|
||||
m, rtt, err := ResolveHTTPS(msg, testCase.Name)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, m)
|
||||
assert.Equal(t, rtt, time.Duration(0))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const (
|
||||
one int = iota
|
||||
cloudflare
|
||||
)
|
||||
|
||||
var queries = []int{
|
||||
one,
|
||||
cloudflare,
|
||||
}
|
||||
|
||||
func UnmarshallHTTPS(i int) string {
|
||||
switch i {
|
||||
case 0:
|
||||
return "::1"
|
||||
case 1:
|
||||
return "https://cloudflare-dns.com/dns-query"
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base", ":npm", ":gomod"],
|
||||
"automerge": true
|
||||
"extends": [
|
||||
"config:base",
|
||||
":npm",
|
||||
":gomod"
|
||||
]
|
||||
}
|
||||
|
|
98
template.mk
98
template.mk
|
@ -1,98 +0,0 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Template for the BSD/GNU makefiles
|
||||
|
||||
HASH ?= `git describe --tags --always --dirty --broken | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
|
||||
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*')
|
||||
TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f)
|
||||
|
||||
CGO_ENABLED ?= 0
|
||||
GO ?= go
|
||||
TEST ?= $(GO) test -race -cover
|
||||
COVER ?= $(GO) tool cover
|
||||
GOFLAGS ?= -trimpath -ldflags="-s -w -X=main.version=$(HASH)"
|
||||
DESTDIR :=
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
BIN ?= bin
|
||||
SHARE ?= share
|
||||
|
||||
SCDOC ?= scdoc
|
||||
MAN ?= $(PREFIX)/$(SHARE)/man
|
||||
|
||||
PROG ?= awl
|
||||
|
||||
# hehe
|
||||
all: $(PROG) docs/$(PROG).1
|
||||
|
||||
$(PROG): $(SOURCES)
|
||||
$(GO) build -o $(EXE) $(GOFLAGS) .
|
||||
|
||||
docs/$(PROG).1: docs/$(PROG).1.scd
|
||||
$(SCDOC) <$? >$@
|
||||
|
||||
docs/wiki/$(PROG).1.md: docs/$(PROG).1
|
||||
pandoc --from man --to gfm -o $@ $?
|
||||
|
||||
## update_doc: update documentation (requires pandoc)
|
||||
update_doc: docs/wiki/$(PROG).1.md
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
gofmt -w -s .
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
$(GO) vet ./...
|
||||
|
||||
## lint: lint awl, using fmt, vet and golangci-lint
|
||||
.PHONY: lint
|
||||
lint: fmt vet
|
||||
golangci-lint run --fix
|
||||
|
||||
coverage/coverage.out: $(TEST_SOURCES)
|
||||
$(TEST) -coverprofile=$@ ./...
|
||||
|
||||
.PHONY: test
|
||||
## test: run go test
|
||||
test: coverage/coverage.out
|
||||
|
||||
.PHONY: test-ci
|
||||
test-ci:
|
||||
$(TEST) ./...
|
||||
|
||||
## fuzz: runs fuzz tests
|
||||
fuzz: $(TEST_SOURCES)
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 10000x ./cmd
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 10000x ./cmd
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 10000x ./cmd
|
||||
|
||||
fuzz-ci: $(TEST_SOURCES)
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 1000x ./cmd
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 1000x ./cmd
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 1000x ./cmd
|
||||
|
||||
.PHONY: full_test
|
||||
full_test: test fuzz
|
||||
|
||||
coverage/cover.html: coverage/coverage.out
|
||||
$(COVER) -func=$?
|
||||
$(COVER) -html=$? -o $@
|
||||
|
||||
## cover: generates test coverage, output as HTML
|
||||
cover: coverage/cover.html
|
||||
|
||||
## clean: clean the build files
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(GO) clean
|
||||
# Ignore errors if you remove something that doesn't exist
|
||||
rm -f docs/$(PROG).1
|
||||
rm -f coverage/cover*
|
||||
rm -rf vendor
|
||||
|
||||
## help: Prints this help message
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Usage: "
|
||||
@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'
|
2
util/docs.go
Normal file
2
util/docs.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Helper functions
|
||||
package util
|
57
util/helpers.go
Normal file
57
util/helpers.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Answers Answers `json:"Response"` // These be DNS query answers
|
||||
}
|
||||
|
||||
// The Answers struct is the basic structure of a DNS request
|
||||
// to be returned to the user upon making a request
|
||||
type Answers struct {
|
||||
Server string `json:"Server"` // The server to make the DNS request from
|
||||
Request uint16 `json:"Request"` // The type of request
|
||||
Name string `json:"Name"` // The domain name to make a DNS request for
|
||||
RTT time.Duration `json:"RTT"` // The time it took to make the DNS query
|
||||
}
|
||||
|
||||
// Given an IP or phone number, return a canonical string to be queried
|
||||
func ReverseDNS(address string, query string) (string, error) {
|
||||
if query == "PTR" {
|
||||
return dns.ReverseAddr(address)
|
||||
} else if query == "NAPTR" {
|
||||
// get rid of characters not needed
|
||||
replacer := strings.NewReplacer("+", "", " ", "", "-", "")
|
||||
address = replacer.Replace(address)
|
||||
// reverse the order of the string
|
||||
address = reverse(address)
|
||||
var arpa strings.Builder
|
||||
// Make it canonical
|
||||
for _, c := range address {
|
||||
fmt.Fprintf(&arpa, "%c.", c)
|
||||
}
|
||||
arpa.WriteString("e164.arpa.")
|
||||
return arpa.String(), nil
|
||||
}
|
||||
|
||||
return "", errors.New("ReverseDNS: -x flag given but no IP found")
|
||||
}
|
||||
|
||||
// Reverse a string, return the string in reverse
|
||||
func reverse(s string) string {
|
||||
rns := []rune(s)
|
||||
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)
|
||||
}
|
43
util/helpers_test.go
Normal file
43
util/helpers_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIPv4(t *testing.T) {
|
||||
act, err := ReverseDNS("8.8.4.4", "PTR")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, act, "4.4.8.8.in-addr.arpa.", "IPv4 reverse")
|
||||
}
|
||||
|
||||
func TestIPv6(t *testing.T) {
|
||||
act, err := ReverseDNS("2606:4700:4700::1111", "PTR")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, act, "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa.", "IPv6 reverse")
|
||||
}
|
||||
|
||||
func TestNAPTR(t *testing.T) {
|
||||
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 {
|
||||
act, err := ReverseDNS(test.in, "NAPTR")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.want, act)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalid(t *testing.T) {
|
||||
_, err := ReverseDNS("AAAAA", "A")
|
||||
assert.NotNil(t, err)
|
||||
}
|
Loading…
Reference in a new issue