Public Access
Shell: tls, ntp and netstat (#90)
The rest of the issue's list. tls makes a handshake that checks nothing, then says the certificate in words: who it is for, who signed it, until when, and whether this device's roots and the name asked for accept it, with the reason when they don't. ntp compares a time server's clock with the device's. netstat lists what listens and what is connected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -224,4 +224,77 @@ bool parseDnsAnswer(const uint8_t* m, size_t len, uint16_t id, DnsAnswer& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void buildNtpRequest(uint8_t out[kNtpPacket]) {
|
||||
std::memset(out, 0, kNtpPacket);
|
||||
out[0] = 0x23; // no warning, version 4, a client
|
||||
}
|
||||
|
||||
bool parseNtpAnswer(const uint8_t* p, size_t len, NtpAnswer& out) {
|
||||
if (len < kNtpPacket || (p[0] & 0x07) != 4) return false; // not a server's
|
||||
if (p[1] == 0 || p[1] > 15) return false; // "kiss of death", or not synchronised
|
||||
uint32_t secs = (static_cast<uint32_t>(p[40]) << 24) | (p[41] << 16) | (p[42] << 8) | p[43];
|
||||
uint32_t frac = (static_cast<uint32_t>(p[44]) << 24) | (p[45] << 16) | (p[46] << 8) | p[47];
|
||||
if (!secs) return false;
|
||||
// NTP counts from 1900 and wraps in 2036: a small number is the era after.
|
||||
constexpr int64_t k1900To1970 = 2208988800LL;
|
||||
int64_t since1900 = secs < 0x80000000u ? static_cast<int64_t>(secs) + 4294967296LL : static_cast<int64_t>(secs);
|
||||
out.stratum = p[1];
|
||||
out.seconds = since1900 - k1900To1970;
|
||||
out.millis = static_cast<uint32_t>((static_cast<uint64_t>(frac) * 1000) >> 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string clockOffset(int64_t ownMs, int64_t serverMs) {
|
||||
int64_t diff = ownMs - serverMs, size = diff < 0 ? -diff : diff;
|
||||
if (size < 100) return "right, to 0.1 s";
|
||||
std::string amount = size < 10000 ? std::to_string(size / 1000) + "." + std::to_string(size % 1000 / 100) + " s"
|
||||
: size < 120000 ? std::to_string(size / 1000) + " s"
|
||||
: size < 7200000 ? std::to_string(size / 60000) + " min"
|
||||
: size < 172800000LL ? std::to_string(size / 3600000) + " h" : std::to_string(size / 86400000LL) + " days";
|
||||
return amount + (diff > 0 ? " ahead" : " behind");
|
||||
}
|
||||
|
||||
std::string certName(const std::string& dn) {
|
||||
for (const char* key : {"CN=", "O="}) {
|
||||
size_t at = 0;
|
||||
while ((at = dn.find(key, at)) != std::string::npos) {
|
||||
if (at == 0 || dn[at - 1] == ' ' || dn[at - 1] == ',') {
|
||||
size_t from = at + std::strlen(key), end = dn.find(", ", from);
|
||||
std::string name = dn.substr(from, end == std::string::npos ? std::string::npos : end - from);
|
||||
// An old kind of string comes out as "#" and hex, type and length first: read it.
|
||||
if (name.size() > 5 && name[0] == '#' && name.size() % 2 == 1) {
|
||||
std::string plain;
|
||||
for (size_t i = 5; i + 1 < name.size(); i += 2) {
|
||||
auto digit = [](char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; };
|
||||
int hi = digit(name[i]), lo = digit(name[i + 1]);
|
||||
if (hi < 0 || lo < 0 || hi * 16 + lo < 0x20 || hi * 16 + lo > 0x7E) return name;
|
||||
plain += static_cast<char>(hi * 16 + lo);
|
||||
}
|
||||
return plain;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
at++;
|
||||
}
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Days since a fixed day long ago (the civil calendar, leap years and all).
|
||||
long dayNumber(int y, int m, int d) {
|
||||
y -= m <= 2;
|
||||
long era = (y >= 0 ? y : y - 399) / 400;
|
||||
long yoe = y - era * 400, doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
return era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int daysBetween(int y1, int m1, int d1, int y2, int m2, int d2) { return static_cast<int>(dayNumber(y2, m2, d2) - dayNumber(y1, m1, d1)); }
|
||||
|
||||
const char* portLabel(uint16_t port, bool tcp) {
|
||||
if (tcp) return port == 3232 ? "updates" : port == 2323 ? "Debug Console" : port == 80 ? "sharing" : "";
|
||||
return port == 68 ? "DHCP" : port == 123 ? "NTP" : "";
|
||||
}
|
||||
|
||||
} // namespace roro::net
|
||||
|
||||
@@ -68,4 +68,31 @@ struct DnsAnswer {
|
||||
// False if it isn't the answer to query `id`, or is cut short.
|
||||
bool parseDnsAnswer(const uint8_t* message, size_t len, uint16_t id, DnsAnswer& out);
|
||||
|
||||
// --- NTP: the clock's offset
|
||||
|
||||
constexpr size_t kNtpPacket = 48;
|
||||
void buildNtpRequest(uint8_t out[kNtpPacket]);
|
||||
struct NtpAnswer {
|
||||
int stratum = 0; // 1: a reference clock; 2 and up: that many steps from one
|
||||
int64_t seconds = 0; // the server's clock when it answered, UTC since 1970
|
||||
uint32_t millis = 0; // and the part of a second
|
||||
};
|
||||
// False if it isn't a server's answer, or says the server has no time to give.
|
||||
bool parseNtpAnswer(const uint8_t* packet, size_t len, NtpAnswer& out);
|
||||
// "0.3 s ahead", "12 s behind", "right, to 0.1 s": this clock against the server's, both in ms.
|
||||
std::string clockOffset(int64_t ownMs, int64_t serverMs);
|
||||
|
||||
// --- TLS: who a certificate is for
|
||||
|
||||
// The common name out of a certificate's subject or issuer as mbedTLS prints it
|
||||
// ("C=US, O=Let's Encrypt, CN=R11"): the CN, else the O, else all of it.
|
||||
std::string certName(const std::string& dn);
|
||||
// Whole days from one date to another (negative: the second is earlier).
|
||||
int daysBetween(int y1, int m1, int d1, int y2, int m2, int d2);
|
||||
|
||||
// --- netstat
|
||||
|
||||
// What listens on a port of this firmware, or "".
|
||||
const char* portLabel(uint16_t port, bool tcp);
|
||||
|
||||
} // namespace roro::net
|
||||
|
||||
Reference in New Issue
Block a user