# FlashDNS Specification > Version: 1.0 | Status: Draft | Last Updated: 2025-12-08 ## 1. Overview ### 1.1 Purpose FlashDNS is an authoritative DNS service providing zone management and DNS resolution for the cloud platform. It enables organizations and projects to host DNS zones with full CRUD operations on DNS records, supporting standard record types and multi-tenant isolation. The name "FlashDNS" reflects fast, reliable DNS resolution with the "Flash" prefix denoting speed and the cloud platform family branding. ### 1.2 Scope - **In scope**: Authoritative DNS serving (UDP/TCP port 53), zone management (SOA, NS configuration), DNS record CRUD (A, AAAA, CNAME, MX, TXT, SRV, NS, PTR), multi-tenant zones (org/project scoped), gRPC management API, aegis integration for access control, ChainFire storage backend - **Out of scope**: Recursive DNS resolution, DNSSEC signing (planned), DNS-over-HTTPS/TLS (planned), dynamic DNS updates (RFC 2136), zone transfers (AXFR/IXFR - planned), GeoDNS/latency-based routing ### 1.3 Design Goals - **Authoritative-only**: Serve authoritative responses for managed zones - **Multi-tenant from day one**: Full org/project zone isolation with aegis integration - **High-performance resolution**: Sub-millisecond query responses with in-memory zone cache - **Standard DNS compliance**: RFC 1035 wire format, common record types - **Cloud-native management**: gRPC API for zone/record management, Prometheus metrics - **Consistent storage**: ChainFire for zone/record persistence with strong consistency ## 2. Architecture ### 2.1 Crate Structure ``` flashdns/ ├── crates/ │ ├── flashdns-api/ # gRPC service implementations │ ├── flashdns-client/ # Rust client library │ ├── flashdns-server/ # Server binary (DNS + gRPC) │ ├── flashdns-proto/ # DNS wire protocol handling │ └── flashdns-types/ # Shared types (Zone, Record, etc.) └── proto/ └── flashdns.proto # gRPC API definitions ``` ### 2.2 Component Topology ``` ┌─────────────────────────────────────────────────────────────────┐ │ FlashDNS Server │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ flashdns-proto │ │ flashdns-api │ │ flashdns-types │ │ │ │ (UDP/TCP :53) │ │ (gRPC) │ │ (core types) │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ └────────────────────┼────────────────────┘ │ │ │ │ │ ┌──────▼──────┐ │ │ │ Core │ │ │ │ (zones, │ │ │ │ records, │ │ │ │ cache) │ │ │ └──────┬──────┘ │ └────────────────────────────────┼────────────────────────────────┘ │ ┌────────────┼────────────┐ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ ChainFire │ │ In-Mem │ │ Aegis │ │ (storage) │ │ Cache │ │ (IAM) │ └───────────┘ └───────────┘ └───────────┘ ``` ### 2.3 Data Flow **DNS Query Flow**: ``` [DNS Client] → [UDP/TCP :53] → [Query Parser] → [Zone Lookup] │ [Cache Check] │ ┌─────────────────┼─────────────────┐ ▼ ▼ [Cache Hit] [Cache Miss] │ │ │ [ChainFire Fetch] │ │ └─────────────────┬─────────────────┘ ▼ [Build Response] │ [DNS Client] ``` **Management API Flow**: ``` [gRPC Client] → [flashdns-api] → [Aegis AuthZ] → [Core Service] │ [ChainFire Store] │ [Cache Invalidate] ``` ### 2.4 Dependencies | Crate | Version | Purpose | |-------|---------|---------| | tokio | 1.x | Async runtime | | tonic | 0.12 | gRPC framework | | prost | 0.13 | Protocol buffers | | trust-dns-proto | 0.24 | DNS wire protocol | | dashmap | 6.x | Concurrent zone cache | | uuid | 1.x | Zone/record identifiers | ## 3. Core Concepts ### 3.1 Zone A DNS zone representing a contiguous portion of the DNS namespace, scoped to an organization and optionally a project. ```rust pub struct Zone { pub id: String, // UUID pub name: String, // FQDN (e.g., "example.com.") pub org_id: String, // Owner organization pub project_id: Option, // Optional project scope pub soa: SoaRecord, // Start of Authority pub ns_records: Vec, // Nameserver FQDNs pub created_at: u64, // Creation timestamp (Unix ms) pub updated_at: u64, // Last modification pub created_by: String, // Principal ID pub serial: u32, // SOA serial (auto-incremented) pub status: ZoneStatus, pub metadata: HashMap, pub tags: HashMap, } pub struct SoaRecord { pub mname: String, // Primary nameserver pub rname: String, // Admin email (dns format) pub serial: u32, // Zone serial number pub refresh: u32, // Refresh interval (seconds) pub retry: u32, // Retry interval (seconds) pub expire: u32, // Expire time (seconds) pub minimum: u32, // Minimum TTL (negative cache) } pub enum ZoneStatus { Active, // Zone is serving Pending, // Zone created, not yet propagated Disabled, // Zone disabled by admin } impl Default for SoaRecord { fn default() -> Self { Self { mname: "ns1.flashdns.local.".into(), rname: "hostmaster.flashdns.local.".into(), serial: 1, refresh: 7200, // 2 hours retry: 3600, // 1 hour expire: 1209600, // 2 weeks minimum: 3600, // 1 hour } } } ``` **Zone Naming Rules**: - Valid DNS domain name (RFC 1035) - Must end with trailing dot (FQDN) - Max 253 characters - Labels: 1-63 characters each - Unique within org (or project if project-scoped) - Fully qualified ID: `{org_id}/{project_id}/{name}` or `{org_id}/{name}` ### 3.2 Record A DNS resource record within a zone. ```rust pub struct Record { pub id: String, // UUID pub zone_id: String, // Parent zone ID pub name: String, // Record name (relative or FQDN) pub record_type: RecordType, pub ttl: u32, // Time-to-live (seconds) pub data: RecordData, // Type-specific data pub created_at: u64, pub updated_at: u64, pub created_by: String, pub enabled: bool, // Soft disable } pub enum RecordType { A, // IPv4 address AAAA, // IPv6 address CNAME, // Canonical name MX, // Mail exchange TXT, // Text record SRV, // Service locator NS, // Nameserver PTR, // Pointer (reverse DNS) CAA, // Certification Authority Authorization SOA, // Start of Authority (zone-level only) } pub enum RecordData { A { address: Ipv4Addr }, AAAA { address: Ipv6Addr }, CNAME { target: String }, MX { preference: u16, exchange: String }, TXT { value: String }, SRV { priority: u16, weight: u16, port: u16, target: String, }, NS { nsdname: String }, PTR { ptrdname: String }, CAA { flags: u8, tag: String, value: String }, } ``` **Record Name Rules**: - Relative names are appended to zone name - `@` represents zone apex - Wildcards: `*.example.com.` supported - Labels validated per RFC 1035 ### 3.3 RecordSet Logical grouping of records with same name and type. ```rust pub struct RecordSet { pub zone_id: String, pub name: String, // Record name pub record_type: RecordType, pub ttl: u32, pub records: Vec, // Multiple values (e.g., round-robin A) } impl RecordSet { /// Convert to individual Record entries pub fn to_records(&self) -> Vec; /// Merge records into a set for response building pub fn from_records(records: Vec) -> Self; } ``` ### 3.4 Query Represents an incoming DNS query. ```rust pub struct DnsQuery { pub id: u16, // Transaction ID pub name: String, // Query name (FQDN) pub qtype: RecordType, // Query type pub qclass: DnsClass, // Usually IN (Internet) pub source_ip: IpAddr, // Client IP pub transport: Transport, // UDP or TCP pub received_at: u64, } pub enum DnsClass { IN, // Internet CH, // Chaos (for version queries) ANY, // Any class } pub enum Transport { Udp, Tcp, } ``` ## 4. API ### 4.1 gRPC Services #### Zone Service (`flashdns.v1.ZoneService`) ```protobuf service ZoneService { // Zone CRUD rpc CreateZone(CreateZoneRequest) returns (Zone); rpc GetZone(GetZoneRequest) returns (Zone); rpc UpdateZone(UpdateZoneRequest) returns (Zone); rpc DeleteZone(DeleteZoneRequest) returns (DeleteZoneResponse); rpc ListZones(ListZonesRequest) returns (ListZonesResponse); // Zone operations rpc GetZoneByName(GetZoneByNameRequest) returns (Zone); rpc IncrementSerial(IncrementSerialRequest) returns (Zone); } message CreateZoneRequest { string name = 1; // Zone FQDN (e.g., "example.com.") string org_id = 2; optional string project_id = 3; optional SoaRecord soa = 4; // Custom SOA (or use defaults) repeated string ns_records = 5; // Nameserver FQDNs map tags = 6; } message GetZoneRequest { string zone_id = 1; } message GetZoneByNameRequest { string name = 1; // Zone FQDN string org_id = 2; optional string project_id = 3; } message UpdateZoneRequest { string zone_id = 1; optional SoaRecord soa = 2; repeated string ns_records = 3; map tags = 4; optional ZoneStatus status = 5; } message DeleteZoneRequest { string zone_id = 1; bool force = 2; // Delete even if records exist } message ListZonesRequest { string org_id = 1; optional string project_id = 2; optional string name_filter = 3; // Prefix match uint32 limit = 4; // Max results (default: 100) string page_token = 5; } message ListZonesResponse { repeated Zone zones = 1; string next_page_token = 2; uint32 total_count = 3; } ``` #### Record Service (`flashdns.v1.RecordService`) ```protobuf service RecordService { // Record CRUD rpc CreateRecord(CreateRecordRequest) returns (Record); rpc GetRecord(GetRecordRequest) returns (Record); rpc UpdateRecord(UpdateRecordRequest) returns (Record); rpc DeleteRecord(DeleteRecordRequest) returns (DeleteRecordResponse); rpc ListRecords(ListRecordsRequest) returns (ListRecordsResponse); // Batch operations rpc BatchCreateRecords(BatchCreateRecordsRequest) returns (BatchCreateRecordsResponse); rpc BatchDeleteRecords(BatchDeleteRecordsRequest) returns (BatchDeleteRecordsResponse); // Query helpers rpc ListRecordsByName(ListRecordsByNameRequest) returns (ListRecordsResponse); rpc ListRecordsByType(ListRecordsByTypeRequest) returns (ListRecordsResponse); } message CreateRecordRequest { string zone_id = 1; string name = 2; // Record name (relative or FQDN) RecordType record_type = 3; uint32 ttl = 4; // Default: 300 RecordData data = 5; } message GetRecordRequest { string record_id = 1; } message UpdateRecordRequest { string record_id = 1; optional uint32 ttl = 2; optional RecordData data = 3; optional bool enabled = 4; } message DeleteRecordRequest { string record_id = 1; } message ListRecordsRequest { string zone_id = 1; optional string name_filter = 2; // Prefix/glob match optional RecordType type_filter = 3; uint32 limit = 4; string page_token = 5; } message ListRecordsByNameRequest { string zone_id = 1; string name = 2; // Exact match } message ListRecordsByTypeRequest { string zone_id = 1; RecordType record_type = 2; } message BatchCreateRecordsRequest { string zone_id = 1; repeated CreateRecordRequest records = 2; } message BatchCreateRecordsResponse { repeated Record records = 1; repeated BatchError errors = 2; // Partial success support } ``` ### 4.2 DNS Protocol (UDP/TCP Port 53) **Supported Query Types**: | Type | Code | Description | |------|------|-------------| | A | 1 | IPv4 address | | NS | 2 | Nameserver | | CNAME | 5 | Canonical name | | SOA | 6 | Start of authority | | PTR | 12 | Pointer record | | MX | 15 | Mail exchange | | TXT | 16 | Text record | | AAAA | 28 | IPv6 address | | SRV | 33 | Service locator | | CAA | 257 | CA Authorization | | ANY | 255 | All records (limited) | **Response Codes**: | Code | Name | Description | |------|------|-------------| | 0 | NOERROR | Success | | 1 | FORMERR | Format error | | 2 | SERVFAIL | Server failure | | 3 | NXDOMAIN | Name does not exist | | 4 | NOTIMP | Not implemented | | 5 | REFUSED | Query refused | **Wire Protocol**: - RFC 1035 compliant message format - UDP: Max 512 bytes (without EDNS), 4096 with EDNS0 - TCP: Full message length prefix (2 bytes) - EDNS0 support for larger responses ### 4.3 Authentication **gRPC API**: - aegis bearer tokens in `authorization` metadata - mTLS for service-to-service communication - API key header (`x-api-key`) for simple auth **DNS Protocol**: - No authentication (standard DNS) - Rate limiting per source IP - Query logging for audit ### 4.4 Client Library ```rust use flashdns_client::FlashDnsClient; let client = FlashDnsClient::connect("http://127.0.0.1:5300").await?; // Create zone let zone = client.create_zone(CreateZoneRequest { name: "example.com.".into(), org_id: "org-1".into(), project_id: Some("proj-1".into()), ns_records: vec![ "ns1.flashdns.example.com.".into(), "ns2.flashdns.example.com.".into(), ], ..Default::default() }).await?; // Create A record let record = client.create_record(CreateRecordRequest { zone_id: zone.id.clone(), name: "www".into(), // Becomes www.example.com. record_type: RecordType::A, ttl: 300, data: RecordData::A { address: "192.0.2.1".parse()?, }, }).await?; // Create MX records client.batch_create_records(BatchCreateRecordsRequest { zone_id: zone.id.clone(), records: vec![ CreateRecordRequest { name: "@".into(), // Zone apex record_type: RecordType::MX, ttl: 3600, data: RecordData::MX { preference: 10, exchange: "mail1.example.com.".into(), }, ..Default::default() }, CreateRecordRequest { name: "@".into(), record_type: RecordType::MX, ttl: 3600, data: RecordData::MX { preference: 20, exchange: "mail2.example.com.".into(), }, ..Default::default() }, ], }).await?; // List records let records = client.list_records(ListRecordsRequest { zone_id: zone.id.clone(), type_filter: Some(RecordType::A), limit: 100, ..Default::default() }).await?; ``` ## 5. Multi-Tenancy ### 5.1 Scope Hierarchy ``` System (platform operators) └─ Organization (tenant boundary) ├─ Org-level zones (shared across projects) └─ Project (workload isolation) └─ Project-level zones ``` ### 5.2 Zone Scoping ```rust pub enum ZoneScope { /// Zone accessible to all projects in org Organization { org_id: String }, /// Zone scoped to specific project Project { org_id: String, project_id: String }, } impl Zone { pub fn scope(&self) -> ZoneScope { match &self.project_id { Some(pid) => ZoneScope::Project { org_id: self.org_id.clone(), project_id: pid.clone() }, None => ZoneScope::Organization { org_id: self.org_id.clone() }, } } } ``` ### 5.3 Access Control Integration ```rust // aegis action patterns for flashdns const ACTIONS: &[&str] = &[ "flashdns:zones:create", "flashdns:zones:get", "flashdns:zones:list", "flashdns:zones:update", "flashdns:zones:delete", "flashdns:records:create", "flashdns:records:get", "flashdns:records:list", "flashdns:records:update", "flashdns:records:delete", ]; // Resource path format // org/{org_id}/project/{project_id}/zone/{zone_name} // org/{org_id}/project/{project_id}/zone/{zone_name}/record/{record_id} async fn authorize_zone_access( iam: &IamClient, principal: &PrincipalRef, action: &str, zone: &Zone, ) -> Result<()> { let resource = ResourceRef { kind: "zone".into(), id: zone.name.clone(), org_id: zone.org_id.clone(), project_id: zone.project_id.clone().unwrap_or_default(), ..Default::default() }; let allowed = iam.authorize(principal, action, &resource).await?; if !allowed { return Err(Error::AccessDenied); } Ok(()) } async fn authorize_record_access( iam: &IamClient, principal: &PrincipalRef, action: &str, zone: &Zone, record: &Record, ) -> Result<()> { let resource = ResourceRef { kind: "record".into(), id: record.id.clone(), org_id: zone.org_id.clone(), project_id: zone.project_id.clone().unwrap_or_default(), ..Default::default() }; let allowed = iam.authorize(principal, action, &resource).await?; if !allowed { return Err(Error::AccessDenied); } Ok(()) } ``` ### 5.4 Zone Isolation - Zones with same name can exist in different orgs/projects - DNS queries route to correct zone based on configured delegation - Internal resolution can scope queries by org/project context ## 6. Storage ### 6.1 ChainFire Key Schema **Zones**: ``` flashdns/zones/{zone_id} # Zone record (by ID) flashdns/zones/by-name/{org_id}/{zone_name} # Name lookup (org-level) flashdns/zones/by-name/{org_id}/{project_id}/{name} # Name lookup (project-level) flashdns/zones/by-org/{org_id}/{zone_id} # Org index flashdns/zones/by-project/{project_id}/{zone_id} # Project index ``` **Records**: ``` flashdns/records/{record_id} # Record by ID flashdns/records/by-zone/{zone_id}/{record_id} # Zone index flashdns/records/by-name/{zone_id}/{name}/{type}/{id} # Name+type lookup flashdns/records/by-type/{zone_id}/{type}/{id} # Type index ``` **Cache Metadata**: ``` flashdns/cache/zones/{zone_id}/version # Cache invalidation version flashdns/cache/serial/{zone_id} # Current serial number ``` ### 6.2 Storage Operations ```rust #[async_trait] pub trait ZoneStore: Send + Sync { async fn create_zone(&self, zone: &Zone) -> Result<()>; async fn get_zone(&self, zone_id: &str) -> Result>; async fn get_zone_by_name( &self, org_id: &str, project_id: Option<&str>, name: &str, ) -> Result>; async fn update_zone(&self, zone: &Zone) -> Result<()>; async fn delete_zone(&self, zone_id: &str) -> Result; async fn list_zones( &self, org_id: &str, project_id: Option<&str>, limit: usize, page_token: Option<&str>, ) -> Result<(Vec, Option)>; } #[async_trait] pub trait RecordStore: Send + Sync { async fn create_record(&self, record: &Record) -> Result<()>; async fn get_record(&self, record_id: &str) -> Result>; async fn update_record(&self, record: &Record) -> Result<()>; async fn delete_record(&self, record_id: &str) -> Result; async fn list_records_by_zone( &self, zone_id: &str, limit: usize, page_token: Option<&str>, ) -> Result<(Vec, Option)>; async fn find_records( &self, zone_id: &str, name: &str, record_type: Option, ) -> Result>; } ``` ### 6.3 Zone Cache ```rust pub struct ZoneCache { zones: DashMap, // zone_id -> zone data name_index: DashMap, // fqdn -> zone_id records: DashMap>, // zone_id:name:type -> records config: CacheConfig, } pub struct CachedZone { pub zone: Zone, pub records: HashMap<(String, RecordType), Vec>, pub loaded_at: u64, pub version: u64, } impl ZoneCache { /// Lookup zone by FQDN (for DNS queries) pub fn find_zone(&self, qname: &str) -> Option<&CachedZone>; /// Lookup records for DNS query pub fn find_records( &self, zone_id: &str, name: &str, qtype: RecordType, ) -> Option>; /// Invalidate zone cache on update pub fn invalidate_zone(&self, zone_id: &str); /// Load zone from storage into cache pub async fn load_zone(&self, store: &dyn ZoneStore, zone_id: &str) -> Result<()>; } ``` ## 7. Configuration ### 7.1 Config File Format (TOML) ```toml [server] grpc_addr = "0.0.0.0:5300" # gRPC management API dns_udp_addr = "0.0.0.0:53" # DNS UDP listener dns_tcp_addr = "0.0.0.0:53" # DNS TCP listener [server.tls] cert_file = "/etc/flashdns/tls/server.crt" key_file = "/etc/flashdns/tls/server.key" ca_file = "/etc/flashdns/tls/ca.crt" [storage] backend = "chainfire" # "chainfire" | "memory" chainfire_endpoints = ["http://chainfire-1:2379", "http://chainfire-2:2379"] [dns] default_ttl = 300 # Default record TTL min_ttl = 60 # Minimum allowed TTL max_ttl = 86400 # Maximum allowed TTL (24h) negative_ttl = 300 # NXDOMAIN cache TTL edns_udp_size = 4096 # EDNS0 buffer size [dns.soa_defaults] refresh = 7200 # 2 hours retry = 3600 # 1 hour expire = 1209600 # 2 weeks minimum = 3600 # 1 hour [dns.rate_limit] enabled = true queries_per_second = 1000 # Per source IP burst = 100 [cache] enabled = true max_zones = 10000 max_records_per_zone = 100000 ttl_seconds = 300 # Cache refresh interval preload_zones = true # Load all zones on startup [iam] endpoint = "http://aegis:9090" service_account = "flashdns" token_path = "/var/run/secrets/iam/token" [logging] level = "info" format = "json" ``` ### 7.2 Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `FLASHDNS_CONFIG` | - | Config file path | | `FLASHDNS_GRPC_ADDR` | `0.0.0.0:5300` | gRPC listen address | | `FLASHDNS_DNS_ADDR` | `0.0.0.0:53` | DNS listen address | | `FLASHDNS_LOG_LEVEL` | `info` | Log level | | `FLASHDNS_STORE_BACKEND` | `memory` | Storage backend | ### 7.3 CLI Arguments ``` flashdns-server [OPTIONS] -c, --config Config file path --grpc-addr gRPC listen address --dns-addr DNS listen address (UDP & TCP) -l, --log-level Log level -h, --help Print help -V, --version Print version ``` ## 8. Security ### 8.1 Authentication **gRPC API**: - aegis bearer tokens for user/service authentication - mTLS for service-to-service communication - API key header for programmatic access **DNS Protocol**: - No authentication (standard DNS behavior) - Access controlled via network policies - Query logging for audit trail ### 8.2 Authorization - All management operations authorized via aegis - Zone-level and record-level permissions - Scope enforcement (org/project boundaries) - Owner-based access patterns supported ### 8.3 Data Security - TLS 1.3 for gRPC transport - DNS queries unencrypted (standard UDP/TCP) - DNS-over-TLS planned for future - Zone data encrypted at rest in ChainFire ### 8.4 Rate Limiting ```rust pub struct RateLimiter { limits: DashMap, config: RateLimitConfig, } pub struct RateLimitConfig { pub queries_per_second: u32, pub burst_size: u32, pub cleanup_interval_secs: u64, } impl RateLimiter { pub fn check(&self, source_ip: IpAddr) -> bool; pub fn record_query(&self, source_ip: IpAddr); } ``` ### 8.5 Audit - All management API calls logged with principal, action, resource - DNS queries logged with source IP, query name, type, response - Integration with platform audit system ## 9. Operations ### 9.1 Deployment **Single Node (Development)**: ```bash flashdns-server --config config.toml ``` **Production Cluster**: ```bash # Multiple stateless API servers behind load balancer # DNS servers with anycast IPs flashdns-server --config config.toml # Shared metadata (ChainFire cluster) # Cache preloading on startup ``` ### 9.2 Monitoring **Metrics (Prometheus)**: | Metric | Type | Description | |--------|------|-------------| | `flashdns_queries_total` | Counter | Total DNS queries | | `flashdns_queries_by_type` | Counter | Queries by record type | | `flashdns_query_latency_seconds` | Histogram | Query response latency | | `flashdns_responses_by_rcode` | Counter | Responses by RCODE | | `flashdns_zones_total` | Gauge | Total zones | | `flashdns_records_total` | Gauge | Total records | | `flashdns_cache_hits_total` | Counter | Cache hits | | `flashdns_cache_misses_total` | Counter | Cache misses | | `flashdns_rate_limited_total` | Counter | Rate limited queries | | `flashdns_grpc_requests_total` | Counter | gRPC API requests | **Health Endpoints**: - `GET /health` - Liveness check - `GET /ready` - Readiness check (storage connected, cache loaded) ### 9.3 Backup & Recovery - **Zone data**: ChainFire snapshots - **Export**: Zone export via gRPC API (planned) - **Import**: Bulk zone import from BIND format (planned) ## 10. Compatibility ### 10.1 API Versioning - gRPC package: `flashdns.v1` - Semantic versioning for breaking changes - Backward compatible additions within major version ### 10.2 DNS Standards | RFC | Description | Status | |-----|-------------|--------| | RFC 1035 | Domain Names - Implementation | Supported | | RFC 2782 | SRV Records | Supported | | RFC 3596 | AAAA Records | Supported | | RFC 6891 | EDNS0 | Supported | | RFC 8659 | CAA Records | Supported | | RFC 4034 | DNSSEC | Planned | | RFC 7858 | DNS over TLS | Planned | | RFC 8484 | DNS over HTTPS | Planned | ### 10.3 Client Compatibility Tested with: - dig (BIND utilities) - nslookup - host - drill - kdig (Knot DNS) ## Appendix ### A. Error Codes **gRPC Errors**: | Error | Description | |-------|-------------| | ZONE_NOT_FOUND | Zone does not exist | | RECORD_NOT_FOUND | Record does not exist | | ZONE_ALREADY_EXISTS | Zone name already in use | | INVALID_ZONE_NAME | Zone name format invalid | | INVALID_RECORD_NAME | Record name format invalid | | INVALID_RECORD_DATA | Record data invalid for type | | ACCESS_DENIED | Permission denied | | ZONE_NOT_EMPTY | Cannot delete zone with records | | QUOTA_EXCEEDED | Zone/record quota exceeded | **DNS RCODEs**: | RCODE | Name | Description | |-------|------|-------------| | 0 | NOERROR | Success | | 1 | FORMERR | Query format error | | 2 | SERVFAIL | Server failure | | 3 | NXDOMAIN | Name does not exist | | 4 | NOTIMP | Query type not implemented | | 5 | REFUSED | Query refused | ### B. Port Assignments | Port | Protocol | Purpose | |------|----------|---------| | 53 | UDP | DNS queries | | 53 | TCP | DNS queries (large responses) | | 5300 | gRPC | Management API | ### C. Glossary - **Zone**: A contiguous portion of the DNS namespace under single administrative control - **Record**: A DNS resource record containing name, type, TTL, and data - **SOA**: Start of Authority - defines zone parameters and primary nameserver - **NS**: Nameserver record - identifies authoritative servers for a zone - **TTL**: Time-to-live - how long resolvers should cache a record - **FQDN**: Fully Qualified Domain Name - complete domain name ending with dot - **RCODE**: Response code - status of DNS query response - **EDNS**: Extension mechanisms for DNS - supports larger responses ### D. Example Zone Configuration **Web application zone**: ```rust // Create zone let zone = client.create_zone(CreateZoneRequest { name: "myapp.example.com.".into(), org_id: "acme".into(), project_id: Some("web-prod".into()), ..Default::default() }).await?; // Add records client.batch_create_records(BatchCreateRecordsRequest { zone_id: zone.id.clone(), records: vec![ // A records for load balancer CreateRecordRequest { name: "@".into(), record_type: RecordType::A, ttl: 60, data: RecordData::A { address: "203.0.113.1".parse()? }, ..Default::default() }, CreateRecordRequest { name: "@".into(), record_type: RecordType::A, ttl: 60, data: RecordData::A { address: "203.0.113.2".parse()? }, ..Default::default() }, // CNAME for www CreateRecordRequest { name: "www".into(), record_type: RecordType::CNAME, ttl: 300, data: RecordData::CNAME { target: "myapp.example.com.".into() }, ..Default::default() }, // MX records CreateRecordRequest { name: "@".into(), record_type: RecordType::MX, ttl: 3600, data: RecordData::MX { preference: 10, exchange: "mail.example.com.".into() }, ..Default::default() }, // TXT for SPF CreateRecordRequest { name: "@".into(), record_type: RecordType::TXT, ttl: 3600, data: RecordData::TXT { value: "v=spf1 include:_spf.example.com ~all".into() }, ..Default::default() }, // SRV for internal service discovery CreateRecordRequest { name: "_http._tcp".into(), record_type: RecordType::SRV, ttl: 300, data: RecordData::SRV { priority: 0, weight: 100, port: 80, target: "myapp.example.com.".into(), }, ..Default::default() }, ], }).await?; ``` ### E. Performance Considerations - **Zone caching**: All active zones cached in memory for sub-ms queries - **Preloading**: Zones loaded on startup to avoid cold-start latency - **UDP preference**: Most queries served over UDP (lower overhead) - **Connection pooling**: gRPC/ChainFire connections pooled - **Rate limiting**: Per-IP rate limits prevent abuse - **Batch operations**: Use batch APIs for bulk record management ## Reverse DNS Support FlashDNS supports pattern-based reverse DNS zones, eliminating the need to create individual PTR records for each IP address in a subnet. ### Overview Traditional reverse DNS requires creating one PTR record per IP address: - A /24 subnet requires 256 PTR records - A /16 subnet requires 65,536 PTR records - A /8 subnet requires 16M+ PTR records FlashDNS's pattern-based approach allows you to define **one ReverseZone** that dynamically generates PTR responses for all IPs within a CIDR block. ### ReverseZone API **CreateReverseZone** ```protobuf message CreateReverseZoneRequest { string org_id = 1; optional string project_id = 2; string cidr = 3; // e.g., "192.168.0.0/16" string ptr_pattern = 4; // e.g., "{4}-{3}.net.example.com." uint32 ttl = 5; // Default: 3600 } ``` **GetReverseZone** ```protobuf message GetReverseZoneRequest { string zone_id = 1; } ``` **DeleteReverseZone** ```protobuf message DeleteReverseZoneRequest { string zone_id = 1; } ``` **ListReverseZones** ```protobuf message ListReverseZonesRequest { string org_id = 1; optional string project_id = 2; } ``` ### Pattern Substitution #### IPv4 Variables | Variable | Description | Example (for 192.168.1.5) | |----------|-------------|---------------------------| | `{1}` | First octet | `192` | | `{2}` | Second octet | `168` | | `{3}` | Third octet | `1` | | `{4}` | Fourth octet | `5` | | `{ip}` | Full IP with dashes | `192-168-1-5` | #### IPv6 Variables | Variable | Description | Example (for 2001:db8::1) | |----------|-------------|---------------------------| | `{short}` | Compressed form with dashes | `2001-db8--1` | | `{full}` | Full expanded form with dashes | `2001-0db8-0000-0000-0000-0000-0000-0001` | ### Examples **Example 1: Subnet-based naming** ``` CIDR: 192.168.0.0/16 Pattern: {4}-{3}.net.example.com. Query: 5.1.168.192.in-addr.arpa (PTR for 192.168.1.5) Response: 5-1.net.example.com. ``` **Example 2: Flat namespace** ``` CIDR: 10.0.0.0/8 Pattern: host-{ip}.cloud.local. Query: 3.2.1.10.in-addr.arpa (PTR for 10.1.2.3) Response: host-10-1-2-3.cloud.local. ``` **Example 3: IPv6** ``` CIDR: 2001:db8::/32 Pattern: v6-{short}.example.com. Query: (nibble-reversed ip6.arpa) Response: v6-2001-db8-.example.com. ``` ### Use Cases 1. **Data Center IP Management**: Pattern-based PTR for all VMs in a subnet 2. **ISP Customer Blocks**: Dynamic PTR without explicit record creation 3. **Development/Test Environments**: Quick reverse DNS setup without maintenance 4. **Large-Scale Deployments**: Eliminate configuration file explosion ### Longest Prefix Matching When multiple ReverseZones overlap (e.g., /8, /16, /24), the most specific (longest prefix) match wins: ``` Zones: - 192.0.0.0/8 → pattern A - 192.168.0.0/16 → pattern B - 192.168.1.0/24 → pattern C Query: 192.168.1.5 Match: 192.168.1.0/24 (pattern C) ✓ (most specific) ``` This enables hierarchical delegation where specific subnets can override broader patterns. ### Storage Schema - Primary key: `/flashdns/reverse_zones/{org_id}/{project_id}/{zone_id}` - CIDR index: `/flashdns/reverse_zones/by-cidr/{normalized_cidr}` → zone_id All three storage backends (ChainFire, FlareDB, InMemory) support reverse zones.