Skip to content

Commit

Permalink
added --wr
Browse files Browse the repository at this point in the history
  • Loading branch information
dandyvica committed Dec 20, 2024
1 parent 18ae282 commit c0d92f7
Show file tree
Hide file tree
Showing 23 changed files with 332 additions and 189 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ rustls-pki-types = "1.1.0"
serde = { version = "1.0.195", features = [ "derive" ] }
serde_json = { version = "1.0.111", features = ["preserve_order"] }
simplelog = "0.12.2"
tera = "1.20.0"
# tera = "1.20.0"
thiserror = "1.0.65"
tokio = { version = "1", features = ["full"] }
tokio-macros = { version = "0.2.0-alpha.6" }
type2network = { git = "https://github.com/dandyvica/type2network" }
type2network_derive = { git = "https://github.com/dandyvica/type2network/" }
unicode-width = "0.2.0"
webpki-roots = "0.26.0"

[dev-dependencies]
Expand Down
54 changes: 36 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This tool is written in pure Rust with the following features:
* plain vanilla ascii
* Json (useful with ```jq```)
* ability to call a Lua script to fine tune the output (when `mlua` feature is enabled)
* OPT coverage
* OPT coverage: NSID, COOKIE, Padding, Extended, ReportChannel, ZONEVERSION

## Supported resource records
The following list of RRs is supported:
Expand Down Expand Up @@ -66,7 +66,9 @@ The following list of RRs is supported:
* ZONEMD
* WALLET

Those with (*) are not yet fully tested. You can also use a `TYPEn` where `n` is an integer <=255 for the query type.
Those with (*) are not yet fully tested.

You can also use a `TYPEn` where `n` is an integer <=255 for the query type.

## General usage
Usage is similar to __dig__, without support for options starting with `+`.
Expand Down Expand Up @@ -95,14 +97,20 @@ For all network operations (apart from DoQ), a timeout can be set with `--timeou
### UDP
By default, dqy uses UDP on port 53. If response is truncated, query is resend on TCP port 53 as stated in RFC1035.

```console
# uses UDP:53
$ dqy A www.google.com
```

### TCP
You can force to use TCP with the `--tcp` option:
```console
# uses TCP:53
$ dqy A www.google.com --tcp
```

### DoT (DNS over TLS)
You can force to use DNQ over TLS on port 853 with the `--dot` option:
You can force to use DNS over TLS on port 853 with the `--dot` option:
```console
$ dqy A www.google.com @1.1.1.1 --dot
```
Expand Down Expand Up @@ -146,27 +154,33 @@ $ dqy A 스타벅스코리아.com
$ dqy AAAA ουτοπία.δπθ.gr
```

Using --puny gives the punycode string instead of the UTF-8 domain name.
Using `--puny` gives the punycode string instead of the UTF-8 domain name.

## Output options
### JSON support
The _--json_ and _--json-pretty_ options allows to display output data in JSON format with key:
The `--json` and `--json-pretty` options allows to display output data in JSON format with key:

* messages: list of messages
* info: meta-info like elpased time, endpoint address etc

### Debugging mode
You can ask for a info to trace mode using `-v` (info) to `-vvvvv` (trace). In addition the --log option allows to save debug output into a file.
You can ask for a info to trace mode using `-v` (info) to `-vvvvv` (trace). In addition the `--log` option allows to save debug output into a file.

### Colors
By default, output is colored. To dismiss colored output, just add `--no-colors`.

### IPV4 and IPV6 transport
You can force to use IPV4 using -4, and IPV6 -6. You can then verify usage with --stats:
You can force to use IPV4 using `-4`, and IPV6 `-6`. You can then verify usage with `--stats`:
```console
$ dqy A www.google.com @one.one.one.one -6 --stats
```

### Save query and response into a file
You can save raw query or response bytes using `--wq` or `--wr` respectively.
```console
$ dqy TXT dropbox.com --wr response.bin --wq query.bin
```

## Lua scripting support
Using `-l <Lua source file>`, all DNS data are sent as global variables to the Lua interpreter which makes it possible to format the output in a very flexible manner.

Expand Down Expand Up @@ -200,8 +214,8 @@ Following is a tentative roadmap:

* v0.2: ipv6 support (done)
* v0.3: trace option (done)
* v0.4: OPT options full support
* v0.5: DNS over Quic
* v0.4: OPT options full support (done) and DoQ (done)
* v0.5: fine-tuning displaying options
* ...

## Usage
Expand All @@ -223,17 +237,21 @@ Compilation instructions: [compiling dqy](./compile.md)

## Exit codes
* 0: no error
* 1: I/O error (probably a networking error)
* 1: I/O error (probably a networking or file access error)
* 2: UTF-8 conversion error
* 3: IP address parsing error from a string
* 4: internal DNS protocol error
* 5: DoH error
* 6: DoT error
* 7: error fetching OS resolvers
* 8: network timeout error
* 9: Lua script error
* 10: logger error

* 4: timeout during network operations
* 5: TLS error
* 6: DoH error
* 7: Dns procotol error
* 8: error during IP address parsing
* 9: logger error
* 10: resolver error
* 11: QUIC error
* 12: integer parsing error
* 13: network resolving error
* 14: tokio runtime error
* 15: IDNA conversion error



81 changes: 45 additions & 36 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::time::Duration;

use clap::{Arg, ArgAction, Command};
use http::*;
use log::{debug, info, trace};
use log::trace;
use rustc_version_runtime::version;
use simplelog::*;

Expand Down Expand Up @@ -112,7 +112,7 @@ impl CliOptions {

// check if this is a domain (should include a dot)
if arg.contains('.') {
options.protocol.domain = arg.to_string();
options.protocol.domain_string = arg.to_string();
continue;
}

Expand Down Expand Up @@ -546,20 +546,20 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
.arg(
Arg::new("stats")
.long("stats")
.long_help("Prints out statistics around the query.")
.long_help("Prints out statistics about the query.")
.action(ArgAction::SetTrue)
.value_name("STATS")
.help_heading("Display options")
)
// .arg(
// Arg::new("tpl")
// .long("tpl")
// .long_help("Name of the handlebars template to render to display results.")
// .action(ArgAction::Set)
// .value_name("TEMPLATE")
// .value_parser(clap::value_parser!(PathBuf))
// .help_heading("Display options")
// )
.arg(
Arg::new("tpl")
.long("tpl")
.long_help("Name of the handlebars template to render to display results.")
.action(ArgAction::Set)
.value_name("TEMPLATE")
.value_parser(clap::value_parser!(PathBuf))
.help_heading("Display options")
)
.arg(
Arg::new("verbose")
.short('v')
Expand All @@ -581,9 +581,9 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
.help_heading("Miscellaneous options")
)
.arg(
Arg::new("read-query")
.long("rq")
.long_help("Read query from file.")
Arg::new("write-response")
.long("wr")
.long_help("Write the response packet to FILE. Only valid for single-qtype queries.")
.action(ArgAction::Set)
.value_name("FILE")
.value_parser(clap::value_parser!(PathBuf))
Expand All @@ -592,7 +592,7 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
.arg(
Arg::new("write-query")
.long("wq")
.long_help("Write an answer packet to file. If several types are requested, the last answer packet if saved")
.long_help("Write the query packet to FILE. Only valid for single-qtype queries.")
.action(ArgAction::Set)
.value_name("FILE")
.value_parser(clap::value_parser!(PathBuf))
Expand Down Expand Up @@ -708,8 +708,8 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
//───────────────────────────────────────────────────────────────────────────────────
// if no domain to query, by default set root (.)
//───────────────────────────────────────────────────────────────────────────────────
if let Some(d) = matches.get_one::<String>("domain") {
options.protocol.domain = d.to_string();
if let Some(domain) = matches.get_one::<String>("domain") {
options.protocol.domain_string = domain.to_string();
}

//───────────────────────────────────────────────────────────────────────────────────
Expand All @@ -725,13 +725,6 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
//───────────────────────────────────────────────────────────────────────────────────
options.transport.timeout = Duration::from_millis(*matches.get_one::<u64>("timeout").unwrap());

//───────────────────────────────────────────────────────────────────────────────────
// internal domain name processing (IDNA)
//───────────────────────────────────────────────────────────────────────────────────
if options.protocol.domain.len() != options.protocol.domain.chars().count() {
options.protocol.domain = idna::domain_to_ascii(&options.protocol.domain).unwrap();
}

//───────────────────────────────────────────────────────────────────────────────────
// if reverse query, ignore all other options
//───────────────────────────────────────────────────────────────────────────────────
Expand All @@ -746,7 +739,7 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
if addr.is_ipv4() {
let mut limbs: Vec<_> = ip.split('.').collect();
limbs.reverse();
options.protocol.domain = format!("{}.in-addr.arpa", limbs.join("."));
options.protocol.domain_string = format!("{}.in-addr.arpa", limbs.join("."));
} else {
// get individual u8 values because an ipv6 address might omit a heading 0
// ex: 2001:470:30:84:e276:63ff:fe72:3900 => 2001:0470:0030:84:e276:63ff:fe72:3900
Expand All @@ -762,7 +755,7 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
let mut domain: Vec<_> = split.split("").filter(|x| !x.is_empty()).collect();
domain.reverse();

options.protocol.domain = format!("{}.ip6.arpa", domain.join("."));
options.protocol.domain_string = format!("{}.ip6.arpa", domain.join("."));
}
}

Expand Down Expand Up @@ -878,8 +871,16 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
//───────────────────────────────────────────────────────────────────────────────────
options.display.trace = matches.get_flag("trace");

//───────────────────────────────────────────────────────────────────────────────────
// finally convert domain as a string to a domain name
options.protocol.domain_name = DomainName::try_from(options.protocol.domain.as_str())?;
// internal domain name processing (IDNA)
//───────────────────────────────────────────────────────────────────────────────────
if options.protocol.domain_string.len() != options.protocol.domain_string.chars().count() {
let puny = idna::domain_to_ascii(&options.protocol.domain_string).map_err(Error::IDNA)?;
options.protocol.domain_name = DomainName::try_from(puny.as_str())?;
} else {
options.protocol.domain_name = DomainName::try_from(options.protocol.domain_string.as_str())?;
}

// for some types, use TCP instead of UDP right away
if options.protocol.qtype.contains(&QType::ANY)
Expand Down Expand Up @@ -925,7 +926,15 @@ Caveat: all options starting with a dash (-) should be placed after optional [TY
// Dump options
//───────────────────────────────────────────────────────────────────────────────────
if let Some(path) = matches.get_one::<PathBuf>("write-query") {
options.dump.write_query = Some(path.to_path_buf());
if options.protocol.qtype.len() == 1 {
options.dump.write_query = Some(path.to_path_buf());
}
}

if let Some(path) = matches.get_one::<PathBuf>("write-response") {
if options.protocol.qtype.len() == 1 {
options.dump.write_response = Some(path.to_path_buf());
}
}

Ok(options)
Expand Down Expand Up @@ -1018,7 +1027,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::NS]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, ROOT);
assert_eq!(&opts.protocol.domain_string, ROOT);
assert_eq!(opts.transport.ip_version, IPVersion::Any);
assert_eq!(opts.transport.transport_mode, Protocol::Udp);
}
Expand All @@ -1032,7 +1041,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::NS]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "www.google.com");
assert_eq!(&opts.protocol.domain_string, "www.google.com");
assert_eq!(opts.transport.ip_version, IPVersion::Any);
assert_eq!(opts.transport.transport_mode, Protocol::Udp);
}
Expand All @@ -1046,7 +1055,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::AAAA]);
assert_eq!(opts.protocol.qclass, QClass::CH);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "www.google.com");
assert_eq!(&opts.protocol.domain_string, "www.google.com");
assert_eq!(opts.transport.ip_version, IPVersion::Any);
assert_eq!(opts.transport.transport_mode, Protocol::Udp);
}
Expand All @@ -1060,7 +1069,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::A, QType::AAAA, QType::MX]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "www.google.com");
assert_eq!(&opts.protocol.domain_string, "www.google.com");
assert_eq!(opts.transport.ip_version, IPVersion::Any);
assert_eq!(opts.transport.transport_mode, Protocol::Udp);
assert_eq!(&opts.transport.endpoint.server_name, "1.1.1.1");
Expand All @@ -1075,7 +1084,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::A, QType::AAAA, QType::MX]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "www.google.com");
assert_eq!(&opts.protocol.domain_string, "www.google.com");
assert_eq!(opts.transport.ip_version, IPVersion::V6);
assert_eq!(opts.transport.transport_mode, Protocol::Udp);
assert_eq!(&opts.transport.endpoint.server_name, &"2606:4700:4700::1111");
Expand All @@ -1090,7 +1099,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::A, QType::AAAA, QType::MX]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "www.google.com");
assert_eq!(&opts.protocol.domain_string, "www.google.com");
assert_eq!(opts.transport.ip_version, IPVersion::V6);
assert_eq!(opts.transport.transport_mode, Protocol::Tcp);
}
Expand All @@ -1104,7 +1113,7 @@ mod tests {
assert_eq!(opts.protocol.qtype, vec![QType::PTR]);
assert_eq!(opts.protocol.qclass, QClass::IN);
assert_eq!(opts.transport.port, 53);
assert_eq!(&opts.protocol.domain, "4.3.2.1.in-addr.arpa");
assert_eq!(&opts.protocol.domain_string, "4.3.2.1.in-addr.arpa");
assert_eq!(opts.transport.ip_version, IPVersion::V4);
assert_eq!(opts.transport.transport_mode, Protocol::Tcp);
}
Expand Down
Loading

0 comments on commit c0d92f7

Please sign in to comment.