Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,19 @@ make
## Usage

For comprehensive help, use `dooked --help`

### Tracking first-seen / last-seen records

JSON output now keeps `first-seen`, `last-seen`, and `seen` fields for every
DNS record. When you pass a previous JSON output back into `dooked`, records
that are not present in the latest scan are kept in the new JSON with their
last observed timestamp, which makes rotating load-balanced responses easier to
track over time.

Useful comparison flags:

```
--fs show records that are seen for the first time
--ls 2 show missing records last seen at least 2 days ago
--lsd 05/01/2026 show missing records last seen on or before a US date
```
6 changes: 6 additions & 0 deletions dooked/include/cli_preprocessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "dns/dns_resolver.hpp"
#include "utils/io_utils.hpp"
#include <ctime>
#include <thread>

// maximum sockets to open regardless of the number of threads
Expand All @@ -24,7 +25,10 @@ struct cli_args_t {
int post_http_request{};
int thread_count{};
int content_length{-1};
int last_seen_days{-1};
std::string last_seen_date{};
bool include_date{false};
bool report_first_seen{false};
};

struct runtime_args_t {
Expand All @@ -36,6 +40,8 @@ struct runtime_args_t {
http_process_e http_request_time_{};
int thread_count{};
int content_length{-1};
bool report_first_seen{false};
std::optional<std::time_t> last_seen_before{};
};

void run_program(cli_args_t const &cli_args);
Expand Down
20 changes: 20 additions & 0 deletions dooked/include/utils/io_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ void trim(std::string &);
struct json_data_t {
std::string domain_name{};
std::string rdata{};
std::string first_seen{};
std::string last_seen{};
int ttl{};
int http_code{};
int content_length{};
int seen{};
dns_record_type_e type{};

static json_data_t serialize(std::string const &d, int const len,
Expand All @@ -40,6 +43,23 @@ struct json_data_t {
dns_str_to_record_type(json_object["type"].get<json::string_t>());
data.rdata = json_object["info"].get<json::string_t>();
data.ttl = json_object["ttl"].get<json::number_integer_t>();
if (auto const iter = json_object.find("first-seen");
iter != json_object.end()) {
data.first_seen = iter->second.get<json::string_t>();
} else if (auto const legacy_iter = json_object.find("first_seen");
legacy_iter != json_object.end()) {
data.first_seen = legacy_iter->second.get<json::string_t>();
}
if (auto const iter = json_object.find("last-seen");
iter != json_object.end()) {
data.last_seen = iter->second.get<json::string_t>();
} else if (auto const legacy_iter = json_object.find("last_seen");
legacy_iter != json_object.end()) {
data.last_seen = legacy_iter->second.get<json::string_t>();
}
if (auto const iter = json_object.find("seen"); iter != json_object.end()) {
data.seen = iter->second.get<json::number_integer_t>();
}
data.content_length = len;
data.http_code = http_code;
return data;
Expand Down
3 changes: 3 additions & 0 deletions dooked/include/utils/probe_result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ bool case_insensitive_compare(std::string const &, std::string const &);

struct probe_result_t {
std::string rdata{};
std::string first_seen{};
std::string last_seen{};
dns_record_type_e type{}; // RR TYPE (2 octets)
std::uint32_t ttl{}; // time to live(4 octets)
int seen{};

friend bool operator==(probe_result_t const &a, probe_result_t const &b) {
return case_insensitive_compare(a.rdata, b.rdata) && (a.type == b.type);
Expand Down
Loading