├── LICENSE ├── README.md └── dig-pretty.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Julia Evans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dig-pretty 2 | 3 | Make `dig`'s output prettier. A very basic Python script that reformats the 4 | YAML output of dig into a more human-readable format. Requires a relatively new 5 | version of `dig` (new enough to support the `+yaml` option). 6 | 7 | Mostly intended as a proof of concept to show that `dig` could have a more 8 | human-friendly output format, because I don't know enough C to try to 9 | contribute a different output format to `dig`. 10 | 11 | ## installation 12 | 13 | You'll need Python 3 and `pyyaml` installed. 14 | 15 | ``` 16 | pip install pyyaml 17 | ``` 18 | 19 | Then put it in your PATH somewhere 20 | 21 | ## usage 22 | 23 | You can pass any argument to it that `dig` supports. It'll just pass all the options through. 24 | 25 | ``` 26 | dig-pretty example.com 27 | dig-pretty +norecurse example.com 28 | dig-pretty @8.8.8.8 example.com 29 | ``` 30 | 31 | ## example output 32 | 33 | 34 | ``` 35 | $ dig-pretty example.com 36 | Received response from 192.168.1.1:53 (UDP), 56 bytes in 12ms 37 | HEADER: 38 | status: NOERROR 39 | opcode: QUERY 40 | id: 61335 41 | flags: qr rd ra 42 | records: QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 43 | 44 | OPT PSEUDOSECTION: 45 | EDNS: version: 0, flags: None, udp: 4096 46 | 47 | QUESTION SECTION: 48 | example.com. IN A 49 | 50 | ANSWER SECTION: 51 | example.com. 81459 IN A 93.184.216.34 52 | ``` 53 | 54 | Compare this to the `dig` output for the same query: 55 | 56 | ``` 57 | $ dig example.com 58 | ; <<>> DiG 9.10.6 <<>> +all example.com 59 | ;; global options: +cmd 60 | ;; Got answer: 61 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5151 62 | ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 63 | 64 | ;; OPT PSEUDOSECTION: 65 | ; EDNS: version: 0, flags:; udp: 1232 66 | ;; QUESTION SECTION: 67 | ;example.com. IN A 68 | 69 | ;; ANSWER SECTION: 70 | example.com. 83385 A 93.184.216.34 71 | 72 | ;; Query time: 57 msec 73 | ;; SERVER: 192.168.1.1#53(192.168.1.1) 74 | ;; WHEN: Sun Jul 30 14:22:44 EDT 2023 75 | ;; MSG SIZE rcvd: 56 76 | ``` 77 | 78 | (`dig-pretty` leaves out the query timing information and message size because 79 | I've never used that information and it's not part of the DNS record itself) 80 | 81 | ## why not `dog`? 82 | 83 | The goal of `dig-pretty` is show that you can have the power of `dig` (all of 84 | the same options, and all of the same details in the output), but clearer 85 | formatting. 86 | 87 | Tools like [dog](https://github.com/ogham/dog) or 88 | [doggo](https://github.com/mr-karan/doggo) are great but they don't support all 89 | the options `dig` does, like `+norecurse`. 90 | 91 | I think older tools like `dig` deserve nice formatting too :) 92 | 93 | ## contributing 94 | 95 | I don't necessarily plan to maintain this long term or add a lot of features but bug fixes are welcome! 96 | -------------------------------------------------------------------------------- /dig-pretty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml 4 | import sys 5 | 6 | 7 | def run_dig(args): 8 | import subprocess 9 | 10 | cmd = ["dig"] + args 11 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 12 | out, err = proc.communicate() 13 | return out.decode("utf-8") 14 | 15 | 16 | def pretty_print(output, is_tty): 17 | try: 18 | parsed = yaml.safe_load(output) 19 | except: 20 | print(output) 21 | return 22 | 23 | if len(parsed) == 0: 24 | raise Exception("No output from dig") 25 | if len(parsed) > 1: 26 | for p in parsed: 27 | print(f"======== Querying: {p['message']['response_address']} =========") 28 | print("") 29 | 30 | print_record(p["message"]["response_message_data"], is_tty) 31 | return 32 | resp = parsed[0] 33 | print_summary(resp) 34 | print_record(resp["message"]["response_message_data"], is_tty) 35 | 36 | 37 | def print_summary(resp): 38 | # Prints a summary of the server/timing, not really sure about this formatting 39 | server = f"{resp['message']['response_address']}:{resp['message']['response_port']}" 40 | protocol = resp["message"]["socket_protocol"] 41 | size = resp["message"]["message_size"].rstrip("b") 42 | elapsed = resp["message"]["response_time"] - resp["message"]["query_time"] 43 | # convert timedelta to ms 44 | elapsed_ms = int(elapsed.total_seconds() * 1000) 45 | print( 46 | f"Received response from {server} ({protocol}), {size} bytes in {elapsed_ms}ms" 47 | ) 48 | 49 | 50 | def format_record(q, is_tty): 51 | parts = q.split() 52 | name, ttl, class_, type_ = parts[:4] 53 | answer = " ".join(parts[4:]) 54 | # the colours here are arbitrary 55 | return f"{color(name, 'blue', is_tty)}\t{color(ttl, 'cyan', is_tty)}\t{class_}\t{color(type_, 'magenta', is_tty)}\t{color(answer, 'green', is_tty)}" 56 | 57 | 58 | def format_question(q, is_tty): 59 | name, class_, type_ = q.split() 60 | return f"{color(name, 'blue', is_tty)}\t{class_}\t{color(type_, 'magenta', is_tty)}" 61 | 62 | 63 | def color(text, color, is_tty): 64 | if not is_tty: 65 | return text 66 | if color == "green": 67 | return "\033[92m" + text + "\033[0m" 68 | elif color == "red": 69 | return "\033[91m" + text + "\033[0m" 70 | elif color == "yellow": 71 | return "\033[93m" + text + "\033[0m" 72 | elif color == "blue": 73 | return "\033[94m" + text + "\033[0m" 74 | elif color == "magenta": 75 | return "\033[95m" + text + "\033[0m" 76 | elif color == "cyan": 77 | return "\033[96m" + text + "\033[0m" 78 | else: 79 | raise Exception(f"unknown color {color}") 80 | 81 | 82 | def color_status(status, is_tty): 83 | if status == "NOERROR": 84 | return color(status, "green", is_tty) 85 | else: 86 | return color(status, "red", is_tty) 87 | 88 | 89 | def print_record(data, is_tty): 90 | print("HEADER:") 91 | print(f" status: {color_status(data['status'], is_tty)}") 92 | print(f" opcode: {data['opcode']}") 93 | print(f" id: {data['id']}") 94 | print(f" flags: {data['flags']}") 95 | print( 96 | f" records: QUESTION: {data['QUESTION']}, ANSWER: {data['ANSWER']}, AUTHORITY: {data['AUTHORITY']}, ADDITIONAL: {data['ADDITIONAL']}" 97 | ) 98 | print("") 99 | if "OPT_PSEUDOSECTION" in data: 100 | print("OPT PSEUDOSECTION:") 101 | for k, v in data["OPT_PSEUDOSECTION"].items(): 102 | values = ", ".join([f"{k}: {v}" for k, v in v.items()]) 103 | print(f" {k}: {values}") 104 | print("") 105 | if "QUESTION_SECTION" in data: 106 | print("QUESTION SECTION:") 107 | for q in data["QUESTION_SECTION"]: 108 | print(f" {format_question(q, is_tty)}") 109 | print("") 110 | if "ANSWER_SECTION" in data: 111 | print("ANSWER SECTION:") 112 | for q in data["ANSWER_SECTION"]: 113 | print(f" {format_record(q, is_tty)}") 114 | print("") 115 | if "AUTHORITY_SECTION" in data: 116 | print("AUTHORITY SECTION:") 117 | for q in data["AUTHORITY_SECTION"]: 118 | print(f" {format_record(q, is_tty)}") 119 | print("") 120 | if "ADDITIONAL_SECTION" in data: 121 | print("ADDITIONAL SECTION:") 122 | for q in data["ADDITIONAL_SECTION"]: 123 | print(f" {format_record(q, is_tty)}") 124 | print("") 125 | 126 | 127 | if __name__ == "__main__": 128 | args = sys.argv[1:] 129 | args.append("+yaml") 130 | # check if output is a pipe 131 | is_tty = sys.stdout.isatty() 132 | output = run_dig(args) 133 | pretty_print(output, is_tty) 134 | --------------------------------------------------------------------------------