diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 376da18..81d0be8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - run: | cmake -A win32 . - cmake --build . + cmake --build . --config MinSizeRel working-directory: cli - uses: actions/upload-artifact@v4 with: @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v3 - run: | cmake -A x64 . - cmake --build . + cmake --build . --config MinSizeRel working-directory: cli - uses: actions/upload-artifact@v4 with: @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v3 - run: | cmake -A ARM64 . - cmake --build . + cmake --build . --config MinSizeRel working-directory: cli - uses: actions/upload-artifact@v4 with: diff --git a/backend/http/main.go b/backend/http/main.go index b6052a1..a83f465 100644 --- a/backend/http/main.go +++ b/backend/http/main.go @@ -20,6 +20,7 @@ import ( type JSON struct { IP string `json:"ip,omitempty"` + Hostname string `json:"hostname,omitempty"` ASO string `json:"aso,omitempty"` ASN uint `json:"asn,omitempty"` Continent string `json:"continent,omitempty"` @@ -32,9 +33,18 @@ type JSON struct { TZ string `json:"tz,omitempty"` } +func lookupAddr(ip string) string { + names, err := net.LookupAddr(ip) + if err != nil || len(names) == 0 { + return "" + } + return strings.TrimSuffix(names[0], ".") +} + func toJSON(ip string, city *geoip2.City, asn *geoip2.ASN) JSON { return JSON{ IP: ip, + Hostname: lookupAddr(ip), ASO: asn.AutonomousSystemOrganization, ASN: asn.AutonomousSystemNumber, Continent: city.Continent.Code, diff --git a/cli/.gitignore b/cli/.gitignore index 3701f02..a73dd18 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -9,3 +9,4 @@ _deps/ build/ CMakeCache.txt CMakeFiles/ +identme diff --git a/cli/Response.h b/cli/Response.h index 3aab67f..1b4c50a 100644 --- a/cli/Response.h +++ b/cli/Response.h @@ -5,6 +5,7 @@ struct Response { std::string ip; + std::optional hostname; std::optional aso; std::optional asn; std::optional continent; diff --git a/cli/main.cpp b/cli/main.cpp index ad4372e..c16e045 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -11,6 +11,9 @@ inline void print_ident_data(const std::string& label, const Response& data) { std::cout << label << " address: " << data.ip << '\n'; + if (data.hostname) { + std::cout << " Hostname: " << *data.hostname << '\n'; + } if (data.aso && data.asn) { std::cout << " AS: " << *data.aso << " (" << *data.asn << ")\n"; } @@ -53,16 +56,17 @@ Response parse_ident_json(const std::string& jsonStr) { } }; + maybe_set_string("hostname", result.hostname); maybe_set_string("aso", result.aso); - maybe_set_int("asn", result.asn); + maybe_set_int("asn", result.asn); maybe_set_string("continent", result.continent); - maybe_set_string("cc", result.cc); - maybe_set_string("country", result.country); - maybe_set_string("city", result.city); - maybe_set_string("postal", result.postal); - maybe_set_float("latitude", result.latitude); - maybe_set_float("longitude", result.longitude); - maybe_set_string("tz", result.tz); + maybe_set_string("cc", result.cc); + maybe_set_string("country", result.country); + maybe_set_string("city", result.city); + maybe_set_string("postal", result.postal); + maybe_set_float("latitude", result.latitude); + maybe_set_float("longitude", result.longitude); + maybe_set_string("tz", result.tz); return result; } catch (const nlohmann::json::parse_error& e) { diff --git a/ios/ident.me/identMeApp.swift b/ios/ident.me/identMeApp.swift index ea4ac7e..863abb3 100644 --- a/ios/ident.me/identMeApp.swift +++ b/ios/ident.me/identMeApp.swift @@ -45,6 +45,7 @@ func getInternalAddrs() -> ([InternalAddr], [InternalAddr])? { struct Ident: Codable { let ip: String + let hostname: String? let aso: String? let asn: Int? let postal: String? @@ -183,6 +184,28 @@ struct PublicView: View { Text("Copy") } } + if let hostname = model.hostname { + GridRow { + VStack { + Text("Hostname") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + Text(hostname) + .frame(maxWidth: .infinity, alignment: .leading) + .monospaced() + } + Button { + #if os(OSX) + NSPasteboard.general.setString(hostname, forType: .string) + #else + UIPasteboard.general.string = hostname + #endif + } label: { + Image(systemName: "clipboard") + Text("Copy") + } + } + } GridRow { VStack { Text("Location") diff --git a/www/index.html b/www/index.html index 149be6b..8f92685 100644 --- a/www/index.html +++ b/www/index.html @@ -94,7 +94,9 @@ display: grid; } - #options, #outro, #docs { + #options, + #outro, + #docs { text-align: center; margin: var(--space-sm); } @@ -292,6 +294,7 @@

Browser data

const renderIPData = ({ ip, + hostname, aso, asn, continent, @@ -312,6 +315,11 @@

Browser data

+
Hostname
${ + hostname + ? `${hostname}` + : "unknown" + }
Autonomous System (ISP)
${aso} (${asn})
Timezone
${tz}
@@ -482,11 +490,11 @@

Browser data

async function drawSparkline(canvasId, data1, data2, options = {}) { const { - color1 = '#4CAF50', - color2 = '#2196F3', - label1 = 'reqs', - label2 = 'IPs', - stacked = false + color1 = "#4CAF50", + color2 = "#2196F3", + label1 = "reqs", + label2 = "IPs", + stacked = false, } = options; const canvas = document.getElementById(canvasId); @@ -495,18 +503,27 @@

Browser data

const textPadding = 2; const circleRadius = 4; const graphPadding = circleRadius + 1; - const fontSize = '1em'; + const fontSize = "1em"; ctx.font = `${fontSize} sans-serif`; - const metrics = ctx.measureText(`${label1}: ${data1[0].toLocaleString()}`); - const lineHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; + const metrics = ctx.measureText( + `${label1}: ${data1[0].toLocaleString()}` + ); + const lineHeight = + metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; const textBaseOffset = metrics.actualBoundingBoxAscent; const textBoxHeight = (textPadding + lineHeight) * 2; const points = []; let hoveredPoint = null; - function drawLine(data, color, yKey, maxValue = Math.max(...data), minValue = Math.min(...data)) { + function drawLine( + data, + color, + yKey, + maxValue = Math.max(...data), + minValue = Math.min(...data) + ) { const valueRange = maxValue - minValue; ctx.beginPath(); @@ -514,15 +531,20 @@

Browser data

ctx.lineWidth = 2; data.forEach((value, i) => { - const x = graphPadding + (i * (width - 2 * graphPadding)) / (data.length - 1); - const y = height - graphPadding - ((height - 2 * graphPadding) * (value - minValue)) / valueRange; + const x = + graphPadding + + (i * (width - 2 * graphPadding)) / (data.length - 1); + const y = + height - + graphPadding - + ((height - 2 * graphPadding) * (value - minValue)) / valueRange; if (!points[i]) points[i] = { x }; points[i][label1] = data1[i]; points[i][label2] = data2[i]; points[i][yKey] = y; - ctx[i === 0 ? 'moveTo' : 'lineTo'](x, y); + ctx[i === 0 ? "moveTo" : "lineTo"](x, y); }); ctx.stroke(); @@ -540,28 +562,39 @@

Browser data

if (stacked) { const maxValue = Math.max(...data1.map((v, i) => v + data2[i])); - drawLine(data2, color2, 'y2', maxValue, 0); - drawLine(data1.map((v, i) => v + data2[i]), color1, 'y', maxValue, 0); + drawLine(data2, color2, "y2", maxValue, 0); + drawLine( + data1.map((v, i) => v + data2[i]), + color1, + "y", + maxValue, + 0 + ); } else { - drawLine(data1, color1, 'y'); - drawLine(data2, color2, 'y2'); + drawLine(data1, color1, "y"); + drawLine(data2, color2, "y2"); } if (hoveredPoint) { ctx.font = `${fontSize} sans-serif`; - ctx.textAlign = 'right'; - ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.textAlign = "right"; + ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; const metrics = ctx.measureText( `${label1}: ${hoveredPoint[label1].toLocaleString()}` ); const boxWidth = metrics.width + textPadding * 2; - ctx.fillRect(width - boxWidth, textPadding, boxWidth, textBoxHeight); + ctx.fillRect( + width - boxWidth, + textPadding, + boxWidth, + textBoxHeight + ); [ { label: label1, color: color1 }, - { label: label2, color: color2 } + { label: label2, color: color2 }, ].forEach(({ label, color }, i) => { ctx.fillStyle = color; ctx.fillText( @@ -575,7 +608,7 @@

Browser data

draw(); - canvas.addEventListener('mousemove', (e) => { + canvas.addEventListener("mousemove", (e) => { const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; @@ -588,7 +621,7 @@

Browser data

[ { y: hoveredPoint.y, color: color1 }, - { y: hoveredPoint.y2, color: color2 } + { y: hoveredPoint.y2, color: color2 }, ].forEach(({ y, color }) => { ctx.beginPath(); ctx.arc(hoveredPoint.x, y, circleRadius, 0, 2 * Math.PI); @@ -597,7 +630,7 @@

Browser data

}); }); - canvas.addEventListener('mouseleave', () => { + canvas.addEventListener("mouseleave", () => { hoveredPoint = null; draw(); }); @@ -673,15 +706,28 @@

${m} user agents (sampled)

await Promise.all( mirrors.flatMap((m, i) => [ - drawSparkline(`${m}-hourly`, statsData[i].hourly.reqs, statsData[i].hourly.ips), - drawSparkline(`${m}-daily`, statsData[i].daily.reqs, statsData[i].daily.ips), - drawSparkline(`${m}-daily-pertype`, statsData[i].daily.ipv4, statsData[i].daily.ipv6, { - color1: '#E91E63', - color2: '#9C27B0', - label1: 'IPv4', - label2: 'IPv6', - stacked: true - }) + drawSparkline( + `${m}-hourly`, + statsData[i].hourly.reqs, + statsData[i].hourly.ips + ), + drawSparkline( + `${m}-daily`, + statsData[i].daily.reqs, + statsData[i].daily.ips + ), + drawSparkline( + `${m}-daily-pertype`, + statsData[i].daily.ipv4, + statsData[i].daily.ipv6, + { + color1: "#E91E63", + color2: "#9C27B0", + label1: "IPv4", + label2: "IPv6", + stacked: true, + } + ), ]) );