photoncloud-monorepo/specifications/flashdns
centra 5c6eb04a46 T036: Add VM cluster deployment configs for nixos-anywhere
- netboot-base.nix with SSH key auth
- Launch scripts for node01/02/03
- Node configuration.nix and disko.nix
- Nix modules for first-boot automation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 09:59:19 +09:00
..
README.md T036: Add VM cluster deployment configs for nixos-anywhere 2025-12-11 09:59:19 +09:00

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.

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<String>,      // Optional project scope
    pub soa: SoaRecord,                  // Start of Authority
    pub ns_records: Vec<String>,         // 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<String, String>,
    pub tags: HashMap<String, String>,
}

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.

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.

pub struct RecordSet {
    pub zone_id: String,
    pub name: String,                    // Record name
    pub record_type: RecordType,
    pub ttl: u32,
    pub records: Vec<RecordData>,        // Multiple values (e.g., round-robin A)
}

impl RecordSet {
    /// Convert to individual Record entries
    pub fn to_records(&self) -> Vec<Record>;

    /// Merge records into a set for response building
    pub fn from_records(records: Vec<Record>) -> Self;
}

3.4 Query

Represents an incoming DNS query.

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)

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<string, string> 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<string, string> 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)

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

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

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

// 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

#[async_trait]
pub trait ZoneStore: Send + Sync {
    async fn create_zone(&self, zone: &Zone) -> Result<()>;
    async fn get_zone(&self, zone_id: &str) -> Result<Option<Zone>>;
    async fn get_zone_by_name(
        &self,
        org_id: &str,
        project_id: Option<&str>,
        name: &str,
    ) -> Result<Option<Zone>>;
    async fn update_zone(&self, zone: &Zone) -> Result<()>;
    async fn delete_zone(&self, zone_id: &str) -> Result<bool>;
    async fn list_zones(
        &self,
        org_id: &str,
        project_id: Option<&str>,
        limit: usize,
        page_token: Option<&str>,
    ) -> Result<(Vec<Zone>, Option<String>)>;
}

#[async_trait]
pub trait RecordStore: Send + Sync {
    async fn create_record(&self, record: &Record) -> Result<()>;
    async fn get_record(&self, record_id: &str) -> Result<Option<Record>>;
    async fn update_record(&self, record: &Record) -> Result<()>;
    async fn delete_record(&self, record_id: &str) -> Result<bool>;
    async fn list_records_by_zone(
        &self,
        zone_id: &str,
        limit: usize,
        page_token: Option<&str>,
    ) -> Result<(Vec<Record>, Option<String>)>;
    async fn find_records(
        &self,
        zone_id: &str,
        name: &str,
        record_type: Option<RecordType>,
    ) -> Result<Vec<Record>>;
}

6.3 Zone Cache

pub struct ZoneCache {
    zones: DashMap<String, CachedZone>,      // zone_id -> zone data
    name_index: DashMap<String, String>,     // fqdn -> zone_id
    records: DashMap<String, Vec<Record>>,   // zone_id:name:type -> records
    config: CacheConfig,
}

pub struct CachedZone {
    pub zone: Zone,
    pub records: HashMap<(String, RecordType), Vec<Record>>,
    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<Vec<Record>>;

    /// 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)

[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 <PATH>       Config file path
  --grpc-addr <ADDR>        gRPC listen address
  --dns-addr <ADDR>         DNS listen address (UDP & TCP)
  -l, --log-level <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

pub struct RateLimiter {
    limits: DashMap<IpAddr, TokenBucket>,
    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):

flashdns-server --config config.toml

Production Cluster:

# 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:

// 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

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

message GetReverseZoneRequest {
  string zone_id = 1;
}

DeleteReverseZone

message DeleteReverseZoneRequest {
  string zone_id = 1;
}

ListReverseZones

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-<rest>.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.