Skip to content

Commit b2678c7

Browse files
committed
Add DNS first-seen and last-seen history
1 parent 545b72e commit b2678c7

7 files changed

Lines changed: 309 additions & 8 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,25 @@ make
3939
## Usage
4040

4141
For comprehensive help, use `dooked --help`
42+
43+
### DNS history tracking
44+
45+
When a previous JSON scan is passed back as input, dooked now carries DNS
46+
record history forward:
47+
48+
- `first-seen`: first scan time for a domain/type/value tuple
49+
- `last-seen`: most recent scan time where that tuple was observed
50+
- `seen`: number of scans where that tuple was observed
51+
- `currently_seen`: whether the tuple appeared in the latest scan
52+
53+
This keeps rotating DNS records in the output after they disappear from the
54+
latest scan, so load-balanced targets can be reviewed without losing older
55+
addresses immediately.
56+
57+
Additional comparison flags:
58+
59+
- `--fs`: report records first seen in the current scan
60+
- `--ls N`: report records missing from the current scan that have not been
61+
seen in at least `N` days
62+
- `--lsd MM/DD/YYYY`: report missing records last seen before a US-formatted
63+
date. `MM/DD/YYYY HH:MM:SS` is also accepted.

dooked/include/cli_preprocessor.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "dns/dns_resolver.hpp"
44
#include "utils/io_utils.hpp"
5+
#include <ctime>
56
#include <thread>
67

78
// maximum sockets to open regardless of the number of threads
@@ -24,7 +25,10 @@ struct cli_args_t {
2425
int post_http_request{};
2526
int thread_count{};
2627
int content_length{-1};
28+
int last_seen_days{-1};
2729
bool include_date{false};
30+
bool show_first_seen{false};
31+
std::string last_seen_date{};
2832
};
2933

3034
struct runtime_args_t {
@@ -36,6 +40,11 @@ struct runtime_args_t {
3640
http_process_e http_request_time_{};
3741
int thread_count{};
3842
int content_length{-1};
43+
int last_seen_days{-1};
44+
bool show_first_seen{false};
45+
std::string last_seen_date{};
46+
std::string scan_time{};
47+
std::time_t scan_time_epoch{};
3948
};
4049

4150
void run_program(cli_args_t const &cli_args);

dooked/include/utils/io_utils.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,46 @@ void trim(std::string &);
2626
struct json_data_t {
2727
std::string domain_name{};
2828
std::string rdata{};
29+
std::string first_seen{};
30+
std::string last_seen{};
2931
int ttl{};
3032
int http_code{};
3133
int content_length{};
34+
int seen{};
35+
bool currently_seen{true};
3236
dns_record_type_e type{};
3337

3438
static json_data_t serialize(std::string const &d, int const len,
3539
int const http_code,
3640
json::object_t &json_object) {
41+
auto const string_value = [&json_object](char const *first_key,
42+
char const *second_key) {
43+
auto first_iter = json_object.find(first_key);
44+
if (first_iter != json_object.end() && first_iter->second.is_string()) {
45+
return first_iter->second.get<json::string_t>();
46+
}
47+
auto second_iter = json_object.find(second_key);
48+
if (second_iter != json_object.end() && second_iter->second.is_string()) {
49+
return second_iter->second.get<json::string_t>();
50+
}
51+
return json::string_t{};
52+
};
53+
auto const int_value = [&json_object](char const *key, int const fallback) {
54+
auto iter = json_object.find(key);
55+
if (iter != json_object.end() && iter->second.is_number_integer()) {
56+
return static_cast<int>(iter->second.get<json::number_integer_t>());
57+
}
58+
return fallback;
59+
};
60+
auto const bool_value = [&json_object](char const *key,
61+
bool const fallback) {
62+
auto iter = json_object.find(key);
63+
if (iter != json_object.end() && iter->second.is_boolean()) {
64+
return iter->second.get<json::boolean_t>();
65+
}
66+
return fallback;
67+
};
68+
3769
json_data_t data{};
3870
data.domain_name = d;
3971
data.type =
@@ -42,6 +74,10 @@ struct json_data_t {
4274
data.ttl = json_object["ttl"].get<json::number_integer_t>();
4375
data.content_length = len;
4476
data.http_code = http_code;
77+
data.first_seen = string_value("first-seen", "first_seen");
78+
data.last_seen = string_value("last-seen", "last_seen");
79+
data.seen = int_value("seen", data.last_seen.empty() ? 0 : 1);
80+
data.currently_seen = bool_value("currently_seen", true);
4581
return data;
4682
}
4783
};
@@ -82,6 +118,7 @@ void write_json_result_impl(map_container_t<DnsType> const &result_map,
82118
json::object_t res_object;
83119

84120
res_object["program"] = "dooked";
121+
res_object["scanned_at"] = rt_args.scan_time;
85122
res_object["result"] = std::move(list);
86123
(*rt_args.output_file) << json(res_object).dump(2) << "\n";
87124
rt_args.output_file->close();

dooked/include/utils/probe_result.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ struct probe_result_t {
1212
std::string rdata{};
1313
dns_record_type_e type{}; // RR TYPE (2 octets)
1414
std::uint32_t ttl{}; // time to live(4 octets)
15+
std::string first_seen{};
16+
std::string last_seen{};
17+
int seen{};
18+
bool currently_seen{true};
1519

1620
friend bool operator==(probe_result_t const &a, probe_result_t const &b) {
1721
return case_insensitive_compare(a.rdata, b.rdata) && (a.type == b.type);

0 commit comments

Comments
 (0)