- 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)
7.3 KiB
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/24→192-168-1-0_242001:db8::/32→2001-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)
- S1: ReverseZone type + CIDR→arpa conversion utility (this design)
- S2: ReverseZoneService gRPC + storage
- S3: DNS handler integration (PTR pattern resolution)
- S4: Zone transfer (AXFR) support
- S5: NOTIFY on zone changes
- 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
ipnetcrate for CIDR parsing- Existing FlashDNS types (Zone, Record, etc.)
- hickory-proto for DNS wire format