package rfc4183 import ( "fmt" "net/netip" "strings" ) // ReverseDomainName implements RFC4183 for turning a CIDR block into // a in-addr name. IP addresses are assumed to be /32 or /128 CIDR blocks. // CIDR host bits are changed to 0s. func ReverseDomainName(cidr string) (string, error) { // Mask missing? Add it. if !strings.Contains(cidr, "/") { a, err := netip.ParseAddr(cidr) if err != nil { return "", fmt.Errorf("not an IP address: %w", err) } if a.Is4() { cidr = cidr + "/32" } else { cidr = cidr + "/128" } } // Parse the CIDR. p, err := netip.ParsePrefix(cidr) if err != nil { return "", fmt.Errorf("not a CIDR block: %w", err) } // RFC4183 4.1 step 4: The notion of fewer than 8 mask bits is not reasonable. if p.Bits() < 8 { return "", fmt.Errorf("mask fewer than 8 bits is unreasonable: %s", cidr) } // Handle IPv6 separately: if p.Addr().Is6() { return reverseIPv6(p.Addr().AsSlice(), p.Bits()) } // Zero out any host bits. p = p.Masked() // IPv4: Implement the RFC4183 process: // 4.p Step 1 b := p.Addr().AsSlice() x, y, z, w := b[0], b[1], b[2], b[3] m := p.Bits() if m == 8 { return fmt.Sprintf("%d.in-addr.arpa", x), nil } if m == 16 { return fmt.Sprintf("%d.%d.in-addr.arpa", y, x), nil } if m == 24 { return fmt.Sprintf("%d.%d.%d.in-addr.arpa", z, y, x), nil } if m == 32 { return fmt.Sprintf("%d.%d.%d.%d.in-addr.arpa", w, z, y, x), nil } // 4.1 Step 2 n := w // I don't understand why the RFC changes variable names at this point, but it does. if m >= 24 && m <= 32 { return fmt.Sprintf("%d-%d.%d.%d.%d.in-addr.arpa", n, m, z, y, x), nil } if m >= 16 && m < 24 { return fmt.Sprintf("%d-%d.%d.%d.in-addr.arpa", z, m, y, x), nil } if m >= 8 && m < 16 { return fmt.Sprintf("%d-%d.%d.in-addr.arpa", y, m, x), nil } return "", fmt.Errorf("fewer than 8 mask bits is not reasonable: %v", cidr) }