Skip to content
Merged
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
2 changes: 2 additions & 0 deletions apps/backend/app/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from app.api.routes.feedback.api import router as feedback_router
from app.api.routes.redis_commander.api import router as redis_commander_router
from app.api.routes.url_shortener.api import router as url_shortener_router
from app.api.routes.dns_lookup.api import router as dns_lookup_router

api_router = APIRouter()
api_router.include_router(health_router)
Expand All @@ -42,3 +43,4 @@
api_router.include_router(feedback_router)
api_router.include_router(redis_commander_router)
api_router.include_router(url_shortener_router)
api_router.include_router(dns_lookup_router)
Empty file.
17 changes: 17 additions & 0 deletions apps/backend/app/api/routes/dns_lookup/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fastapi import APIRouter, Query

from app.api.routes.dns_lookup import services as svc
from app.api.routes.dns_lookup.schema import DNSLookupResult

router = APIRouter(prefix="/dns-lookup", tags=["dns-lookup"])


@router.get("/lookup", response_model=DNSLookupResult, summary="Perform DNS lookup for a domain")
async def lookup(
domain: str = Query(..., description="Domain name to look up"),
record_types: list[str] = Query(
default=["A", "AAAA", "MX", "TXT", "NS", "CNAME"],
description="DNS record types to query",
),
) -> DNSLookupResult:
return await svc.lookup_domain(domain, record_types)
16 changes: 16 additions & 0 deletions apps/backend/app/api/routes/dns_lookup/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Any
from pydantic import BaseModel


class DNSRecord(BaseModel):
type: str
value: str
ttl: int | None = None
priority: int | None = None
extra: dict[str, Any] | None = None


class DNSLookupResult(BaseModel):
domain: str
records: dict[str, list[DNSRecord]]
errors: dict[str, str] = {}
105 changes: 105 additions & 0 deletions apps/backend/app/api/routes/dns_lookup/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import asyncio
import re
from typing import Any

import dns.asyncresolver
import dns.exception
import dns.rdatatype
import dns.resolver

from app.api.routes.dns_lookup.schema import DNSLookupResult, DNSRecord

VALID_RECORD_TYPES = {"A", "AAAA", "MX", "TXT", "NS", "CNAME", "SOA", "CAA", "PTR"}
_DOMAIN_RE = re.compile(r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$")


def _is_valid_domain(domain: str) -> bool:
return bool(_DOMAIN_RE.match(domain)) and len(domain) <= 253


async def _resolve_type(resolver: dns.asyncresolver.Resolver, domain: str, rtype: str) -> tuple[list[DNSRecord], str | None]:
try:
answers = await resolver.resolve(domain, rtype, raise_on_no_answer=False)
if answers.rrset is None:
return [], None
ttl = answers.rrset.ttl
records: list[DNSRecord] = []
for rdata in answers.rrset:
rec = _parse_rdata(rtype, rdata, ttl)
if rec:
records.append(rec)
return records, None
except dns.resolver.NXDOMAIN:
return [], "NXDOMAIN: Domain does not exist"
except dns.resolver.NoNameservers:
return [], "No nameservers available"
except dns.resolver.Timeout:
return [], "Query timed out"
except dns.exception.DNSException as exc:
return [], str(exc)


def _parse_rdata(rtype: str, rdata: Any, ttl: int) -> DNSRecord | None:
if rtype == "A":
return DNSRecord(type=rtype, value=str(rdata.address), ttl=ttl)
if rtype == "AAAA":
return DNSRecord(type=rtype, value=str(rdata.address), ttl=ttl)
if rtype == "MX":
return DNSRecord(type=rtype, value=str(rdata.exchange).rstrip("."), ttl=ttl, priority=rdata.preference)
if rtype == "TXT":
value = " ".join(part.decode("utf-8", errors="replace") for part in rdata.strings)
return DNSRecord(type=rtype, value=value, ttl=ttl)
if rtype == "NS":
return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl)
if rtype == "CNAME":
return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl)
if rtype == "SOA":
return DNSRecord(
type=rtype,
value=str(rdata.mname).rstrip("."),
ttl=ttl,
extra={
"rname": str(rdata.rname).rstrip("."),
"serial": rdata.serial,
"refresh": rdata.refresh,
"retry": rdata.retry,
"expire": rdata.expire,
"minimum": rdata.minimum,
},
)
if rtype == "CAA":
tag = rdata.tag.decode("utf-8", errors="replace") if isinstance(rdata.tag, bytes) else rdata.tag
value = rdata.value.decode("utf-8", errors="replace") if isinstance(rdata.value, bytes) else rdata.value
return DNSRecord(type=rtype, value=f'{rdata.flags} {tag} "{value}"', ttl=ttl)
if rtype == "PTR":
return DNSRecord(type=rtype, value=str(rdata.target).rstrip("."), ttl=ttl)
return DNSRecord(type=rtype, value=str(rdata), ttl=ttl)


async def lookup_domain(domain: str, record_types: list[str]) -> DNSLookupResult:
domain = domain.strip().lower().rstrip(".")
if not _is_valid_domain(domain):
return DNSLookupResult(domain=domain, records={}, errors={"_": "Invalid domain name"})

requested = [rt.upper() for rt in record_types if rt.upper() in VALID_RECORD_TYPES]
if not requested:
requested = ["A", "AAAA", "MX", "TXT", "NS", "CNAME"]

resolver = dns.asyncresolver.Resolver()
resolver.timeout = 5
resolver.lifetime = 10

tasks = {rtype: _resolve_type(resolver, domain, rtype) for rtype in requested}
results = await asyncio.gather(*tasks.values())

records: dict[str, list[DNSRecord]] = {}
errors: dict[str, str] = {}
for rtype, (recs, err) in zip(tasks.keys(), results):
if err and rtype == requested[0] and "NXDOMAIN" in err:
return DNSLookupResult(domain=domain, records={}, errors={"_": err})
if err:
errors[rtype] = err
else:
records[rtype] = recs

return DNSLookupResult(domain=domain, records=records, errors=errors)
8 changes: 8 additions & 0 deletions apps/web/messages/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Sertifikaat / PEM-dekodeerder",
"description": "Dekodeer X.509-sertifikate en CSRs: onderwerp, uitreiker, geldigheid, SANs."
},
"sshKeyGenerator": {
"title": "SSH / RSA-sleutelgenerator",
"description": "Genereer Ed25519- of RSA-sleutelpare in die blaaier. Laai af in OpenSSH- en PEM-formaat."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "IP-/subnetrekenaar",
"description": "IPv4 en IPv6 CIDR: netmasker, reekse en adresse."
},
"dnsLookup": {
"title": "DNS-soektog",
"description": "Navraag DNS-rekords vir enige domein — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Hash-generator",
"description": "MD5, SHA-1 en SHA-2 vir teks of lêers."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "فك ترميز الشهادة / PEM",
"description": "فك ترميز شهادات X.509 وطلبات CSR: الموضوع، المصدر، الصلاحية، وأسماء SAN."
},
"sshKeyGenerator": {
"title": "مولد مفاتيح SSH / RSA",
"description": "توليد أزواج مفاتيح Ed25519 أو RSA في المتصفح. التنزيل بتنسيقات OpenSSH و PEM."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "حاسبة IP / الشبكة الفرعية",
"description": "IPv4 وIPv6 CIDR: قناع الشبكة، النطاقات، وعدد العناوين."
},
"dnsLookup": {
"title": "بحث DNS",
"description": "استعلام عن سجلات DNS لأي نطاق — A، AAAA، MX، TXT، NS، CNAME."
},
"hashGenerator": {
"title": "مُولّد الهاش",
"description": "MD5 وSHA-1 وSHA-2 لنص أو ملفات."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Descodificador de certificats / PEM",
"description": "Descodifica certificats X.509 i CSR: subjecte, emissor, validesa i SAN."
},
"sshKeyGenerator": {
"title": "Generador de claus SSH / RSA",
"description": "Genereu parells de claus Ed25519 o RSA al navegador. Descarregueu en formats OpenSSH i PEM."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "Calculadora IP / subxarxa",
"description": "IPv4 i IPv6 CIDR: màscara, intervals i recompte d’adreces."
},
"dnsLookup": {
"title": "Cerca DNS",
"description": "Consulta els registres DNS de qualsevol domini — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Generador de hash",
"description": "MD5, SHA-1 i SHA-2 per a text o fitxers."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Dekodér certifikátů / PEM",
"description": "Dekódujte certifikáty X.509 a CSR: subjekt, vydavatel, platnost a SAN."
},
"sshKeyGenerator": {
"title": "Generátor klíčů SSH / RSA",
"description": "Generujte páry klíčů Ed25519 nebo RSA v prohlížeči. Stáhněte ve formátech OpenSSH a PEM."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "Kalkulačka IP / podsítě",
"description": "IPv4 a IPv6 CIDR: maska, rozsahy a počty adres."
},
"dnsLookup": {
"title": "Vyhledávání DNS",
"description": "Dotazujte DNS záznamy pro libovolnou doménu — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Generátor hashů",
"description": "MD5, SHA-1 a SHA-2 pro text nebo soubory."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Certifikat / PEM-afkoder",
"description": "Afkod X.509-certifikater og CSR’er: emne, udsteder, gyldighed og SAN."
},
"sshKeyGenerator": {
"title": "SSH / RSA-nøglegenerator",
"description": "Generér Ed25519- eller RSA-nøglepar i browseren. Download i OpenSSH- og PEM-format."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "IP-/subnetberegner",
"description": "IPv4 og IPv6 CIDR: netmaske, intervaller og adresser."
},
"dnsLookup": {
"title": "DNS-opslag",
"description": "Forespørg DNS-poster for ethvert domæne — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Hash-generator",
"description": "MD5, SHA-1 og SHA-2 til tekst eller filer."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Zertifikat- / PEM-Decoder",
"description": "X.509-Zertifikate und CSRs dekodieren: Antragsteller, Aussteller, Gültigkeit, SANs."
},
"sshKeyGenerator": {
"title": "SSH / RSA-Schlüsselgenerator",
"description": "Ed25519- oder RSA-Schlüsselpaare generieren. Private und öffentliche Schlüssel im OpenSSH- und PEM-Format herunterladen."
},
"jsonFormatter": {
"title": "JSON-Formatierer",
"description": "JSON formatieren, validieren und bearbeiten."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "IP-/Subnetz-Rechner",
"description": "IPv4 und IPv6 CIDR: Netzmaske, Bereiche und Adressanzahl."
},
"dnsLookup": {
"title": "DNS-Abfrage",
"description": "DNS-Einträge für jede Domain abfragen — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA."
},
"hashGenerator": {
"title": "Hash-Generator",
"description": "MD5, SHA-1 und SHA-2 für Text oder Dateien."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Αποκωδικοποιητής πιστοποιητικού / PEM",
"description": "Αποκωδικοποίηση πιστοποιητικών X.509 και CSR: θέμα, εκδότης, ισχύς, SAN."
},
"sshKeyGenerator": {
"title": "Γεννήτρια κλειδιών SSH / RSA",
"description": "Δημιουργία ζευγών κλειδιών Ed25519 ή RSA στο πρόγραμμα περιήγησης. Λήψη σε μορφές OpenSSH και PEM."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "Υπολογιστής IP / υποδικτύου",
"description": "IPv4 και IPv6 CIDR: μάσκα, εύρη και πλήθος διευθύνσεων."
},
"dnsLookup": {
"title": "Αναζήτηση DNS",
"description": "Αναζητήστε εγγραφές DNS για οποιοδήποτε τομέα — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Γεννήτρια hash",
"description": "MD5, SHA-1 και SHA-2 για κείμενο ή αρχεία."
Expand Down
22 changes: 22 additions & 0 deletions apps/web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@
"title": "Certificate / PEM Decoder",
"description": "Decode X.509 certificates and CSRs: subject, issuer, validity, SANs."
},
"sshKeyGenerator": {
"title": "SSH / RSA Key Generator",
"description": "Generate Ed25519 or RSA key pairs. Download private and public keys in OpenSSH and PEM formats."
},
"encryptionPlayground": {
"title": "Encryption Playground",
"description": "AES-GCM with a raw key or passphrase; JSON bundles, Web Crypto only in your browser."
Expand Down Expand Up @@ -419,6 +423,10 @@
"title": "IP / Subnet Calculator",
"description": "IPv4 and IPv6 CIDR: netmask, ranges, and address counts."
},
"dnsLookup": {
"title": "DNS Lookup",
"description": "Query DNS records for any domain — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR."
},
"hashGenerator": {
"title": "Hash Generator",
"description": "MD5, SHA-1, and SHA-2 digests for text or files."
Expand Down Expand Up @@ -3430,5 +3438,19 @@
"errors": {
"generationFailed": "Key generation failed"
}
},
"DnsLookup": {
"title": "DNS Lookup",
"subtitle": "Query DNS records for any domain — A, AAAA, MX, TXT, NS, CNAME, SOA, CAA, PTR.",
"placeholder": "Enter domain (e.g. github.com)",
"lookupBtn": "Lookup",
"looking": "Looking up…",
"refreshBtn": "Refresh",
"recordTypesLabel": "Record types",
"allBtn": "All",
"tryLabel": "Try:",
"errors": {
"lookupFailed": "DNS lookup failed"
}
}
}
8 changes: 8 additions & 0 deletions apps/web/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "Decodificador de certificados / PEM",
"description": "Decodifica certificados X.509 y CSR: sujeto, emisor, validez y SAN."
},
"sshKeyGenerator": {
"title": "Generador de claves SSH / RSA",
"description": "Genera pares de claves Ed25519 o RSA. Descarga claves en formatos OpenSSH y PEM."
},
"jsonFormatter": {
"title": "Formateador JSON",
"description": "Formatea, valida y edita datos JSON."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "Calculadora IP / subred",
"description": "IPv4 e IPv6 CIDR: máscara, rangos y recuento de direcciones."
},
"dnsLookup": {
"title": "Búsqueda DNS",
"description": "Consulta registros DNS de cualquier dominio — A, AAAA, MX, TXT, NS, CNAME."
},
"hashGenerator": {
"title": "Generador de hash",
"description": "MD5, SHA-1 y SHA-2 para texto o archivos."
Expand Down
8 changes: 8 additions & 0 deletions apps/web/messages/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"title": "رمزگشای گواهی / PEM",
"description": "رمزگشایی گواهی‌های X.509 و CSR: موضوع، صادرکننده، اعتبار و SAN."
},
"sshKeyGenerator": {
"title": "مولد کلید SSH / RSA",
"description": "در مرورگر جفت کلید Ed25519 یا RSA تولید کنید. در قالب‌های OpenSSH و PEM دانلود کنید."
},
"jsonFormatter": {
"title": "JSON Formatter",
"description": "Format, validate, and edit JSON data."
Expand Down Expand Up @@ -382,6 +386,10 @@
"title": "ماشین‌حساب IP / زیرشبکه",
"description": "IPv4 و IPv6 CIDR: ماسک، ناحیه، و تعداد آدرس."
},
"dnsLookup": {
"title": "جستجوی DNS",
"description": "رکوردهای DNS هر دامنه را جستجو کنید — A، AAAA، MX، TXT، NS، CNAME."
},
"hashGenerator": {
"title": "تولیدکنندهٔ هش",
"description": "MD5، SHA-1 و SHA-2 برای متن یا فایل."
Expand Down
Loading