feat: Add extended DNS record types (ClouDNS compatible)

- Add support for 13 additional record types: ALIAS, RP, SSHFP, TLSA,
  DS, DNSKEY, NAPTR, LOC, HINFO, CERT, SMIMEA, WR (Web Redirect), SPF
- Add GetDNSRecordTypeInfo() for metadata with RFC references
- Add GetCommonDNSRecordTypes() for commonly used types
- Add structured types for CAA, SSHFP, TLSA, DS, DNSKEY, NAPTR, RP,
  LOC, ALIAS, and WebRedirect records
- Export new functions in WASM bindings
- Update TypeScript definitions and loader.js
- Add comprehensive tests for new record types
This commit is contained in:
Claude 2025-12-25 12:38:32 +00:00
parent d96c9f266c
commit 298791ef95
No known key found for this signature in database
5 changed files with 573 additions and 7 deletions

View file

@ -21,6 +21,7 @@ import (
type DNSRecordType string
const (
// Standard record types
DNSRecordA DNSRecordType = "A"
DNSRecordAAAA DNSRecordType = "AAAA"
DNSRecordMX DNSRecordType = "MX"
@ -31,6 +32,21 @@ const (
DNSRecordPTR DNSRecordType = "PTR"
DNSRecordSRV DNSRecordType = "SRV"
DNSRecordCAA DNSRecordType = "CAA"
// Additional record types (ClouDNS and others)
DNSRecordALIAS DNSRecordType = "ALIAS" // Virtual ANAME record (ClouDNS, Route53, etc.)
DNSRecordRP DNSRecordType = "RP" // Responsible Person
DNSRecordSSHFP DNSRecordType = "SSHFP" // SSH Fingerprint
DNSRecordTLSA DNSRecordType = "TLSA" // DANE TLS Authentication
DNSRecordDS DNSRecordType = "DS" // DNSSEC Delegation Signer
DNSRecordDNSKEY DNSRecordType = "DNSKEY" // DNSSEC Key
DNSRecordNAPTR DNSRecordType = "NAPTR" // Naming Authority Pointer
DNSRecordLOC DNSRecordType = "LOC" // Geographic Location
DNSRecordHINFO DNSRecordType = "HINFO" // Host Information
DNSRecordCERT DNSRecordType = "CERT" // Certificate record
DNSRecordSMIMEA DNSRecordType = "SMIMEA" // S/MIME Certificate Association
DNSRecordWR DNSRecordType = "WR" // Web Redirect (ClouDNS specific)
DNSRecordSPF DNSRecordType = "SPF" // Sender Policy Framework (legacy, use TXT)
)
// DNSRecord represents a generic DNS record
@ -66,6 +82,146 @@ type SOARecord struct {
MinTTL uint32 `json:"minTtl"`
}
// CAARecord represents a CAA record
type CAARecord struct {
Flag uint8 `json:"flag"`
Tag string `json:"tag"` // "issue", "issuewild", "iodef"
Value string `json:"value"`
}
// SSHFPRecord represents an SSHFP record
type SSHFPRecord struct {
Algorithm uint8 `json:"algorithm"` // 1=RSA, 2=DSA, 3=ECDSA, 4=Ed25519
FPType uint8 `json:"fpType"` // 1=SHA-1, 2=SHA-256
Fingerprint string `json:"fingerprint"`
}
// TLSARecord represents a TLSA (DANE) record
type TLSARecord struct {
Usage uint8 `json:"usage"` // 0-3: CA constraint, Service cert, Trust anchor, Domain-issued
Selector uint8 `json:"selector"` // 0=Full cert, 1=SubjectPublicKeyInfo
MatchingType uint8 `json:"matchingType"` // 0=Exact, 1=SHA-256, 2=SHA-512
CertData string `json:"certData"`
}
// DSRecord represents a DS (DNSSEC Delegation Signer) record
type DSRecord struct {
KeyTag uint16 `json:"keyTag"`
Algorithm uint8 `json:"algorithm"`
DigestType uint8 `json:"digestType"`
Digest string `json:"digest"`
}
// DNSKEYRecord represents a DNSKEY record
type DNSKEYRecord struct {
Flags uint16 `json:"flags"`
Protocol uint8 `json:"protocol"`
Algorithm uint8 `json:"algorithm"`
PublicKey string `json:"publicKey"`
}
// NAPTRRecord represents a NAPTR record
type NAPTRRecord struct {
Order uint16 `json:"order"`
Preference uint16 `json:"preference"`
Flags string `json:"flags"`
Service string `json:"service"`
Regexp string `json:"regexp"`
Replacement string `json:"replacement"`
}
// RPRecord represents an RP (Responsible Person) record
type RPRecord struct {
Mailbox string `json:"mailbox"` // Email as DNS name (user.domain.com)
TxtDom string `json:"txtDom"` // Domain with TXT record containing more info
}
// LOCRecord represents a LOC (Location) record
type LOCRecord struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude"`
Size float64 `json:"size"`
HPrecis float64 `json:"hPrecision"`
VPrecis float64 `json:"vPrecision"`
}
// ALIASRecord represents an ALIAS/ANAME record (provider-specific)
type ALIASRecord struct {
Target string `json:"target"`
}
// WebRedirectRecord represents a Web Redirect record (ClouDNS specific)
type WebRedirectRecord struct {
URL string `json:"url"`
RedirectType int `json:"redirectType"` // 301, 302, etc.
Frame bool `json:"frame"` // Frame redirect vs HTTP redirect
}
// DNSRecordTypeInfo provides metadata about a DNS record type
type DNSRecordTypeInfo struct {
Type DNSRecordType `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
RFC string `json:"rfc,omitempty"`
Common bool `json:"common"` // Commonly used record type
}
// GetDNSRecordTypeInfo returns metadata for all supported DNS record types
func GetDNSRecordTypeInfo() []DNSRecordTypeInfo {
return []DNSRecordTypeInfo{
// Common record types
{DNSRecordA, "A", "IPv4 address record - maps hostname to IPv4", "RFC 1035", true},
{DNSRecordAAAA, "AAAA", "IPv6 address record - maps hostname to IPv6", "RFC 3596", true},
{DNSRecordCNAME, "CNAME", "Canonical name - alias to another domain", "RFC 1035", true},
{DNSRecordMX, "MX", "Mail exchanger - specifies mail servers", "RFC 1035", true},
{DNSRecordTXT, "TXT", "Text record - stores arbitrary text (SPF, DKIM, etc.)", "RFC 1035", true},
{DNSRecordNS, "NS", "Nameserver - delegates DNS zone to nameservers", "RFC 1035", true},
{DNSRecordSOA, "SOA", "Start of Authority - zone administration data", "RFC 1035", true},
{DNSRecordPTR, "PTR", "Pointer - reverse DNS lookup (IP to hostname)", "RFC 1035", true},
{DNSRecordSRV, "SRV", "Service - locates services (port, priority, weight)", "RFC 2782", true},
{DNSRecordCAA, "CAA", "Certification Authority Authorization", "RFC 6844", true},
// Additional/specialized record types
{DNSRecordALIAS, "ALIAS", "Virtual A record - CNAME-like for apex domain", "", true},
{DNSRecordRP, "RP", "Responsible Person - contact info for domain", "RFC 1183", false},
{DNSRecordSSHFP, "SSHFP", "SSH Fingerprint - SSH host key verification", "RFC 4255", false},
{DNSRecordTLSA, "TLSA", "DANE TLS Authentication - certificate pinning", "RFC 6698", false},
{DNSRecordDS, "DS", "Delegation Signer - DNSSEC chain of trust", "RFC 4034", false},
{DNSRecordDNSKEY, "DNSKEY", "DNSSEC public key", "RFC 4034", false},
{DNSRecordNAPTR, "NAPTR", "Naming Authority Pointer - ENUM, SIP routing", "RFC 2915", false},
{DNSRecordLOC, "LOC", "Location - geographic coordinates", "RFC 1876", false},
{DNSRecordHINFO, "HINFO", "Host Information - CPU and OS type", "RFC 1035", false},
{DNSRecordCERT, "CERT", "Certificate - stores certificates", "RFC 4398", false},
{DNSRecordSMIMEA, "SMIMEA", "S/MIME Certificate Association", "RFC 8162", false},
{DNSRecordSPF, "SPF", "Sender Policy Framework (legacy, use TXT)", "RFC 4408", false},
{DNSRecordWR, "WR", "Web Redirect - HTTP redirect (ClouDNS specific)", "", false},
}
}
// GetCommonDNSRecordTypes returns only commonly used record types
func GetCommonDNSRecordTypes() []DNSRecordType {
info := GetDNSRecordTypeInfo()
result := make([]DNSRecordType, 0)
for _, r := range info {
if r.Common {
result = append(result, r.Type)
}
}
return result
}
// GetAllDNSRecordTypes returns all supported record types
func GetAllDNSRecordTypes() []DNSRecordType {
return []DNSRecordType{
DNSRecordA, DNSRecordAAAA, DNSRecordCNAME, DNSRecordMX, DNSRecordTXT,
DNSRecordNS, DNSRecordSOA, DNSRecordPTR, DNSRecordSRV, DNSRecordCAA,
DNSRecordALIAS, DNSRecordRP, DNSRecordSSHFP, DNSRecordTLSA, DNSRecordDS,
DNSRecordDNSKEY, DNSRecordNAPTR, DNSRecordLOC, DNSRecordHINFO, DNSRecordCERT,
DNSRecordSMIMEA, DNSRecordSPF, DNSRecordWR,
}
}
// DNSLookupResult contains the results of a DNS lookup
type DNSLookupResult struct {
Domain string `json:"domain"`

View file

@ -156,6 +156,136 @@ func TestDNSRecordTypes(t *testing.T) {
}
}
func TestDNSRecordTypesExtended(t *testing.T) {
// Test all ClouDNS record types are defined
types := []DNSRecordType{
DNSRecordALIAS,
DNSRecordRP,
DNSRecordSSHFP,
DNSRecordTLSA,
DNSRecordDS,
DNSRecordDNSKEY,
DNSRecordNAPTR,
DNSRecordLOC,
DNSRecordHINFO,
DNSRecordCERT,
DNSRecordSMIMEA,
DNSRecordWR,
DNSRecordSPF,
}
expected := []string{"ALIAS", "RP", "SSHFP", "TLSA", "DS", "DNSKEY", "NAPTR", "LOC", "HINFO", "CERT", "SMIMEA", "WR", "SPF"}
for i, typ := range types {
if string(typ) != expected[i] {
t.Errorf("expected type %s, got %s", expected[i], typ)
}
}
}
func TestGetDNSRecordTypeInfo(t *testing.T) {
info := GetDNSRecordTypeInfo()
if len(info) == 0 {
t.Error("GetDNSRecordTypeInfo should return non-empty list")
}
// Check that common types exist
commonFound := 0
for _, r := range info {
if r.Common {
commonFound++
}
// Each entry should have type, name, and description
if r.Type == "" {
t.Error("Record type should not be empty")
}
if r.Name == "" {
t.Error("Record name should not be empty")
}
if r.Description == "" {
t.Error("Record description should not be empty")
}
}
if commonFound < 10 {
t.Errorf("Expected at least 10 common record types, got %d", commonFound)
}
// Check for specific types
typeMap := make(map[DNSRecordType]DNSRecordTypeInfo)
for _, r := range info {
typeMap[r.Type] = r
}
if _, ok := typeMap[DNSRecordA]; !ok {
t.Error("A record type should be in info")
}
if _, ok := typeMap[DNSRecordALIAS]; !ok {
t.Error("ALIAS record type should be in info")
}
if _, ok := typeMap[DNSRecordTLSA]; !ok {
t.Error("TLSA record type should be in info")
}
if _, ok := typeMap[DNSRecordWR]; !ok {
t.Error("WR (Web Redirect) record type should be in info")
}
}
func TestGetCommonDNSRecordTypes(t *testing.T) {
types := GetCommonDNSRecordTypes()
if len(types) == 0 {
t.Error("GetCommonDNSRecordTypes should return non-empty list")
}
// Check that standard types are present
typeSet := make(map[DNSRecordType]bool)
for _, typ := range types {
typeSet[typ] = true
}
if !typeSet[DNSRecordA] {
t.Error("A record should be in common types")
}
if !typeSet[DNSRecordAAAA] {
t.Error("AAAA record should be in common types")
}
if !typeSet[DNSRecordMX] {
t.Error("MX record should be in common types")
}
if !typeSet[DNSRecordTXT] {
t.Error("TXT record should be in common types")
}
if !typeSet[DNSRecordALIAS] {
t.Error("ALIAS record should be in common types")
}
}
func TestGetAllDNSRecordTypes(t *testing.T) {
types := GetAllDNSRecordTypes()
if len(types) < 20 {
t.Errorf("GetAllDNSRecordTypes should return at least 20 types, got %d", len(types))
}
// Check for ClouDNS-specific types
typeSet := make(map[DNSRecordType]bool)
for _, typ := range types {
typeSet[typ] = true
}
if !typeSet[DNSRecordWR] {
t.Error("WR (Web Redirect) should be in all types")
}
if !typeSet[DNSRecordNAPTR] {
t.Error("NAPTR should be in all types")
}
if !typeSet[DNSRecordDS] {
t.Error("DS should be in all types")
}
}
func TestDNSLookupResultStructure(t *testing.T) {
result := DNSLookupResult{
Domain: "example.com",
@ -400,6 +530,141 @@ func TestSOARecordStructure(t *testing.T) {
}
}
// ============================================================================
// Extended Record Type Structure Tests
// ============================================================================
func TestCAARecordStructure(t *testing.T) {
caa := CAARecord{
Flag: 0,
Tag: "issue",
Value: "letsencrypt.org",
}
if caa.Tag != "issue" {
t.Error("Tag should be 'issue'")
}
if caa.Value != "letsencrypt.org" {
t.Error("Value should be set")
}
}
func TestSSHFPRecordStructure(t *testing.T) {
sshfp := SSHFPRecord{
Algorithm: 4, // Ed25519
FPType: 2, // SHA-256
Fingerprint: "abc123def456",
}
if sshfp.Algorithm != 4 {
t.Error("Algorithm should be 4 (Ed25519)")
}
if sshfp.FPType != 2 {
t.Error("FPType should be 2 (SHA-256)")
}
}
func TestTLSARecordStructure(t *testing.T) {
tlsa := TLSARecord{
Usage: 3, // Domain-issued certificate
Selector: 1, // SubjectPublicKeyInfo
MatchingType: 1, // SHA-256
CertData: "abcd1234",
}
if tlsa.Usage != 3 {
t.Error("Usage should be 3")
}
if tlsa.Selector != 1 {
t.Error("Selector should be 1")
}
}
func TestDSRecordStructure(t *testing.T) {
ds := DSRecord{
KeyTag: 12345,
Algorithm: 13, // ECDSAP256SHA256
DigestType: 2, // SHA-256
Digest: "deadbeef",
}
if ds.KeyTag != 12345 {
t.Error("KeyTag should be 12345")
}
if ds.Algorithm != 13 {
t.Error("Algorithm should be 13")
}
}
func TestNAPTRRecordStructure(t *testing.T) {
naptr := NAPTRRecord{
Order: 100,
Preference: 10,
Flags: "U",
Service: "E2U+sip",
Regexp: "!^.*$!sip:info@example.com!",
Replacement: ".",
}
if naptr.Order != 100 {
t.Error("Order should be 100")
}
if naptr.Service != "E2U+sip" {
t.Error("Service should be E2U+sip")
}
}
func TestRPRecordStructure(t *testing.T) {
rp := RPRecord{
Mailbox: "admin.example.com",
TxtDom: "info.example.com",
}
if rp.Mailbox != "admin.example.com" {
t.Error("Mailbox should be set")
}
}
func TestLOCRecordStructure(t *testing.T) {
loc := LOCRecord{
Latitude: 51.5074,
Longitude: -0.1278,
Altitude: 11,
Size: 10,
HPrecis: 10,
VPrecis: 10,
}
if loc.Latitude < 51.5 || loc.Latitude > 51.6 {
t.Error("Latitude should be near 51.5074")
}
}
func TestALIASRecordStructure(t *testing.T) {
alias := ALIASRecord{
Target: "target.example.com",
}
if alias.Target != "target.example.com" {
t.Error("Target should be set")
}
}
func TestWebRedirectRecordStructure(t *testing.T) {
wr := WebRedirectRecord{
URL: "https://www.example.com",
RedirectType: 301,
Frame: false,
}
if wr.URL != "https://www.example.com" {
t.Error("URL should be set")
}
if wr.RedirectType != 301 {
t.Error("RedirectType should be 301")
}
}
// ============================================================================
// Helper Function Tests
// ============================================================================

View file

@ -193,8 +193,118 @@ export interface InitOptions {
// DNS Tools Types
// ============================================================================
/** DNS record types */
export type DNSRecordType = 'A' | 'AAAA' | 'MX' | 'TXT' | 'NS' | 'CNAME' | 'SOA' | 'PTR' | 'SRV' | 'CAA';
/** DNS record types - standard and extended (ClouDNS compatible) */
export type DNSRecordType =
// Standard record types
| 'A'
| 'AAAA'
| 'MX'
| 'TXT'
| 'NS'
| 'CNAME'
| 'SOA'
| 'PTR'
| 'SRV'
| 'CAA'
// Additional record types (ClouDNS and others)
| 'ALIAS' // Virtual A record - CNAME-like for apex domain
| 'RP' // Responsible Person
| 'SSHFP' // SSH Fingerprint
| 'TLSA' // DANE TLS Authentication
| 'DS' // DNSSEC Delegation Signer
| 'DNSKEY' // DNSSEC Key
| 'NAPTR' // Naming Authority Pointer
| 'LOC' // Geographic Location
| 'HINFO' // Host Information
| 'CERT' // Certificate record
| 'SMIMEA' // S/MIME Certificate Association
| 'WR' // Web Redirect (ClouDNS specific)
| 'SPF'; // Sender Policy Framework (legacy)
/** DNS record type metadata */
export interface DNSRecordTypeInfo {
type: DNSRecordType;
name: string;
description: string;
rfc?: string;
common: boolean;
}
/** CAA record */
export interface CAARecord {
flag: number;
tag: string; // "issue", "issuewild", "iodef"
value: string;
}
/** SSHFP record */
export interface SSHFPRecord {
algorithm: number; // 1=RSA, 2=DSA, 3=ECDSA, 4=Ed25519
fpType: number; // 1=SHA-1, 2=SHA-256
fingerprint: string;
}
/** TLSA (DANE) record */
export interface TLSARecord {
usage: number; // 0-3: CA constraint, Service cert, Trust anchor, Domain-issued
selector: number; // 0=Full cert, 1=SubjectPublicKeyInfo
matchingType: number; // 0=Exact, 1=SHA-256, 2=SHA-512
certData: string;
}
/** DS (DNSSEC Delegation Signer) record */
export interface DSRecord {
keyTag: number;
algorithm: number;
digestType: number;
digest: string;
}
/** DNSKEY record */
export interface DNSKEYRecord {
flags: number;
protocol: number;
algorithm: number;
publicKey: string;
}
/** NAPTR record */
export interface NAPTRRecord {
order: number;
preference: number;
flags: string;
service: string;
regexp: string;
replacement: string;
}
/** RP (Responsible Person) record */
export interface RPRecord {
mailbox: string; // Email as DNS name (user.domain.com)
txtDom: string; // Domain with TXT record containing more info
}
/** LOC (Location) record */
export interface LOCRecord {
latitude: number;
longitude: number;
altitude: number;
size: number;
hPrecision: number;
vPrecision: number;
}
/** ALIAS record (provider-specific) */
export interface ALIASRecord {
target: string;
}
/** Web Redirect record (ClouDNS specific) */
export interface WebRedirectRecord {
url: string;
redirectType: number; // 301, 302, etc.
frame: boolean; // Frame redirect vs HTTP redirect
}
/** External tool links for domain/IP/email analysis */
export interface ExternalToolLinks {
@ -429,6 +539,8 @@ export interface PxAPI {
buildRDAPIPURL(ip: string): Promise<string>;
buildRDAPASNURL(asn: string): Promise<string>;
getDNSRecordTypes(): Promise<DNSRecordType[]>;
getDNSRecordTypeInfo(): Promise<DNSRecordTypeInfo[]>;
getCommonDNSRecordTypes(): Promise<DNSRecordType[]>;
}
export function init(options?: InitOptions): Promise<PxAPI>;

View file

@ -109,7 +109,9 @@ export async function init(options = {}) {
buildRDAPDomainURL: async (domain) => call('pxBuildRDAPDomainURL', domain),
buildRDAPIPURL: async (ip) => call('pxBuildRDAPIPURL', ip),
buildRDAPASNURL: async (asn) => call('pxBuildRDAPASNURL', asn),
getDNSRecordTypes: async () => call('pxGetDNSRecordTypes')
getDNSRecordTypes: async () => call('pxGetDNSRecordTypes'),
getDNSRecordTypeInfo: async () => call('pxGetDNSRecordTypeInfo'),
getCommonDNSRecordTypes: async () => call('pxGetCommonDNSRecordTypes')
};
return api;

View file

@ -664,10 +664,39 @@ func buildRDAPASNURL(_ js.Value, args []js.Value) (any, error) {
}
func getDNSRecordTypes(_ js.Value, _ []js.Value) (any, error) {
// Returns available DNS record types
return []string{
"A", "AAAA", "MX", "TXT", "NS", "CNAME", "SOA", "PTR", "SRV", "CAA",
}, nil
// Returns all available DNS record types
types := pd.GetAllDNSRecordTypes()
result := make([]string, len(types))
for i, t := range types {
result[i] = string(t)
}
return result, nil
}
func getDNSRecordTypeInfo(_ js.Value, _ []js.Value) (any, error) {
// Returns detailed info about all DNS record types
info := pd.GetDNSRecordTypeInfo()
result := make([]any, len(info))
for i, r := range info {
result[i] = map[string]any{
"type": string(r.Type),
"name": r.Name,
"description": r.Description,
"rfc": r.RFC,
"common": r.Common,
}
}
return result, nil
}
func getCommonDNSRecordTypes(_ js.Value, _ []js.Value) (any, error) {
// Returns only commonly used DNS record types
types := pd.GetCommonDNSRecordTypes()
result := make([]string, len(types))
for i, t := range types {
result[i] = string(t)
}
return result, nil
}
func main() {
@ -709,6 +738,8 @@ func main() {
export("pxBuildRDAPIPURL", buildRDAPIPURL)
export("pxBuildRDAPASNURL", buildRDAPASNURL)
export("pxGetDNSRecordTypes", getDNSRecordTypes)
export("pxGetDNSRecordTypeInfo", getDNSRecordTypeInfo)
export("pxGetCommonDNSRecordTypes", getCommonDNSRecordTypes)
// Keep running
select {}