photoncloud-monorepo/docs/por/T021-flashdns-parity/design.md
centra a7ec7e2158 Add T026 practical test + k8shost to flake + workspace files
- Created T026-practical-test task.yaml for MVP smoke testing
- Added k8shost-server to flake.nix (packages, apps, overlays)
- Staged all workspace directories for nix flake build
- Updated flake.nix shellHook to include k8shost

Resolves: T026.S1 blocker (R8 - nix submodule visibility)
2025-12-09 06:07:50 +09:00

7.3 KiB

T021: Reverse DNS Zone Model Design

Problem Statement

From PROJECT.md:

逆引きDNSをやるためにとんでもない行数のBINDのファイルを書くというのがあり、バカバカしすぎるのでサブネットマスクみたいなものに対応すると良い

Traditional reverse DNS requires creating individual PTR records for each IP address:

  • A /24 subnet = 256 PTR records
  • A /16 subnet = 65,536 PTR records
  • A /8 subnet = 16M+ PTR records

This is operationally unsustainable.

Solution: Pattern-Based Reverse Zones

Instead of storing individual PTR records, FlashDNS will support ReverseZone with pattern-based PTR generation.

Core Types

/// A reverse DNS zone with pattern-based PTR generation
pub struct ReverseZone {
    pub id: String,                    // UUID
    pub org_id: String,                // Tenant org
    pub project_id: Option<String>,    // Optional project scope
    pub cidr: IpNet,                   // e.g., "192.168.1.0/24" or "2001:db8::/32"
    pub arpa_zone: String,             // Auto-generated: "1.168.192.in-addr.arpa."
    pub ptr_pattern: String,           // e.g., "{4}-{3}-{2}-{1}.hosts.example.com."
    pub ttl: u32,                      // Default TTL for generated PTRs
    pub created_at: u64,
    pub updated_at: u64,
    pub status: ZoneStatus,
}

/// Supported CIDR sizes for automatic arpa zone generation
pub enum SupportedCidr {
    // IPv4
    V4Classful8,   // /8  -> x.in-addr.arpa
    V4Classful16,  // /16 -> y.x.in-addr.arpa
    V4Classful24,  // /24 -> z.y.x.in-addr.arpa

    // IPv6
    V6Nibble64,    // /64 -> ...ip6.arpa (16 nibbles)
    V6Nibble48,    // /48 -> ...ip6.arpa (12 nibbles)
    V6Nibble32,    // /32 -> ...ip6.arpa (8 nibbles)
}

Pattern Substitution

PTR patterns support placeholders that get substituted at query time:

IPv4 Placeholders:

  • {1} - First octet (e.g., 192)
  • {2} - Second octet (e.g., 168)
  • {3} - Third octet (e.g., 1)
  • {4} - Fourth octet (e.g., 5)
  • {ip} - Full IP with dashes (e.g., 192-168-1-5)

IPv6 Placeholders:

  • {full} - Full expanded address with dashes
  • {short} - Compressed representation

Examples:

CIDR Pattern Query Result
192.168.0.0/16 {4}-{3}.net.example.com. 5.1.168.192.in-addr.arpa 5-1.net.example.com.
10.0.0.0/8 host-{ip}.cloud.local. 5.2.1.10.in-addr.arpa host-10-0-1-5.cloud.local.
2001:db8::/32 v6-{short}.example.com. (nibble query) v6-2001-db8-....example.com.

CIDR to ARPA Zone Conversion

/// Convert CIDR to in-addr.arpa zone name
pub fn cidr_to_arpa(cidr: &IpNet) -> Result<String, Error> {
    match cidr {
        IpNet::V4(net) => {
            let octets = net.addr().octets();
            match net.prefix_len() {
                8 => Ok(format!("{}.in-addr.arpa.", octets[0])),
                16 => Ok(format!("{}.{}.in-addr.arpa.", octets[1], octets[0])),
                24 => Ok(format!("{}.{}.{}.in-addr.arpa.", octets[2], octets[1], octets[0])),
                _ => Err(Error::UnsupportedCidr(net.prefix_len())),
            }
        }
        IpNet::V6(net) => {
            // Convert to nibble format for ip6.arpa
            let nibbles = ipv6_to_nibbles(net.addr());
            let prefix_nibbles = (net.prefix_len() / 4) as usize;
            let arpa_part = nibbles[..prefix_nibbles]
                .iter()
                .rev()
                .map(|n| format!("{:x}", n))
                .collect::<Vec<_>>()
                .join(".");
            Ok(format!("{}.ip6.arpa.", arpa_part))
        }
    }
}

Storage Schema

flashdns/reverse_zones/{zone_id}                    # Full zone data
flashdns/reverse_zones/by-cidr/{cidr_normalized}    # CIDR lookup index
flashdns/reverse_zones/by-org/{org_id}/{zone_id}    # Org index

Key format for CIDR index: Replace / with _ and . with -:

  • 192.168.1.0/24192-168-1-0_24
  • 2001:db8::/322001-db8--_32

Query Resolution Flow

DNS Query: 5.1.168.192.in-addr.arpa PTR
    │
    ▼
┌─────────────────────────────────────┐
│ 1. Parse query → IP: 192.168.1.5    │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ 2. Find matching ReverseZone        │
│    - Check 192.168.1.0/24           │
│    - Check 192.168.0.0/16           │
│    - Check 192.0.0.0/8              │
│    (most specific match wins)       │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ 3. Apply pattern substitution       │
│    Pattern: "{4}-{3}.hosts.ex.com." │
│    Result: "5-1.hosts.ex.com."      │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ 4. Return PTR response              │
│    TTL from ReverseZone.ttl         │
└─────────────────────────────────────┘

API Extensions

service ReverseZoneService {
  rpc CreateReverseZone(CreateReverseZoneRequest) returns (ReverseZone);
  rpc GetReverseZone(GetReverseZoneRequest) returns (ReverseZone);
  rpc DeleteReverseZone(DeleteReverseZoneRequest) returns (DeleteReverseZoneResponse);
  rpc ListReverseZones(ListReverseZonesRequest) returns (ListReverseZonesResponse);
  rpc ResolvePtrForIp(ResolvePtrForIpRequest) returns (ResolvePtrForIpResponse);
}

message CreateReverseZoneRequest {
  string org_id = 1;
  string project_id = 2;
  string cidr = 3;           // "192.168.0.0/16"
  string ptr_pattern = 4;    // "{4}-{3}.hosts.example.com."
  uint32 ttl = 5;            // Default: 3600
}

Override Support (Optional)

For cases where specific IPs need custom PTR values:

pub struct PtrOverride {
    pub reverse_zone_id: String,
    pub ip: IpAddr,           // Specific IP to override
    pub ptr_value: String,    // Custom PTR (overrides pattern)
}

Storage: flashdns/ptr_overrides/{reverse_zone_id}/{ip_normalized}

Query resolution checks overrides first, falls back to pattern.

Implementation Steps (T021)

  1. S1: ReverseZone type + CIDR→arpa conversion utility (this design)
  2. S2: ReverseZoneService gRPC + storage
  3. S3: DNS handler integration (PTR pattern resolution)
  4. S4: Zone transfer (AXFR) support
  5. S5: NOTIFY on zone changes
  6. S6: Integration tests

Benefits

Approach /24 Records /16 Records /8 Records
Traditional 256 65,536 16M+
Pattern-based 1 1 1

Massive reduction in configuration complexity and storage requirements.

Dependencies

  • ipnet crate for CIDR parsing
  • Existing FlashDNS types (Zone, Record, etc.)
  • hickory-proto for DNS wire format