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

207 lines
7.3 KiB
Markdown

# 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
```rust
/// 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
```rust
/// 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_24`
- `2001: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
```protobuf
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:
```rust
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