Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a5c8a3fb
|
@@ -0,0 +1,7 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLD string = "arpa"
|
||||||
|
Zone4 string = "in-addr." + TLD
|
||||||
|
Zone6 string = "ip6." + TLD
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*
|
||||||
|
Package ptr includes some helper functions for working with PTR records/zones
|
||||||
|
and mapping/converting them to/from [net/netip.Addr] and [net/netip.Prefix] representations respectively.
|
||||||
|
*/
|
||||||
|
package ptr
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidBounds error = errors.New("invalid PTR boundary")
|
||||||
|
ErrInvalidPfx error = errors.New("invalid prefix")
|
||||||
|
ErrInvalidPtr error = errors.New("invalid PTR")
|
||||||
|
)
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"go4.org/netipx"
|
||||||
|
"r00t2.io/subnetter/netsplit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddrPtr returns the PTR lookup name of IP address ip (as a [net/netip.Addr]), normalized (lowercased and no trailing dot).
|
||||||
|
func AddrPtr(ip netip.Addr) (ptr string, err error) { return IpStrPtr(ip.String()) }
|
||||||
|
|
||||||
|
// IpPtr returns the PTR lookup name of IP address ip (as a [net.IP]), normalized (lowercased and no trailing dot).
|
||||||
|
func IpPtr(ip net.IP) (ptr string, err error) { return IpStrPtr(ip.String()) }
|
||||||
|
|
||||||
|
// IpStrPtr returns the PTR lookup name of IP address ip (as a string), normalized (lowercased and no trailing dot).
|
||||||
|
func IpStrPtr(ip string) (ptr string, err error) {
|
||||||
|
|
||||||
|
if ptr, err = dns.ReverseAddr(ip); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = normalizePtr(ptr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PfxToPtr returns the PTR zone name(s) for a specified prefix.
|
||||||
|
Any host bits set in pfx will be masked off without error
|
||||||
|
(e.g. 192.0.2.20/24 -> 192.0.2.0/24).
|
||||||
|
|
||||||
|
err will be [ErrInvalidBounds] if pfx prefix length is longer than a /24 (e.g. a /32) for IPv4
|
||||||
|
or longer than the last/shorter than the first 4-bit/nibble/nybble boundary - /124 (e.g. a /128)
|
||||||
|
and /4 respectively - for IPv6.
|
||||||
|
*/
|
||||||
|
func PfxToPtr(pfx netip.Prefix) (ptrZones []string, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var tgtSz uint8
|
||||||
|
var n *net.IPNet
|
||||||
|
var maxBitLen int
|
||||||
|
var pfxs []*netip.Prefix
|
||||||
|
var c *netsplit.CIDRSplitter
|
||||||
|
|
||||||
|
if !pfx.IsValid() {
|
||||||
|
err = ErrInvalidPfx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pfx = pfx.Masked()
|
||||||
|
|
||||||
|
if pfx.Addr().Is4() {
|
||||||
|
maxBitLen = 24
|
||||||
|
if pfx.Bits() <= 24 {
|
||||||
|
tgtSz = 24
|
||||||
|
}
|
||||||
|
if pfx.Bits() <= 16 {
|
||||||
|
tgtSz = 16
|
||||||
|
}
|
||||||
|
if pfx.Bits() <= 8 {
|
||||||
|
tgtSz = 8
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxBitLen = 124
|
||||||
|
if pfx.Bits() < 4 {
|
||||||
|
err = ErrInvalidBounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgtSz = uint8(pfx.Bits())
|
||||||
|
for tgtSz%4 != 0 {
|
||||||
|
tgtSz++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pfx.Bits() > maxBitLen {
|
||||||
|
err = ErrInvalidBounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &netsplit.CIDRSplitter{
|
||||||
|
PrefixLength: tgtSz,
|
||||||
|
BaseSplitter: new(netsplit.BaseSplitter),
|
||||||
|
}
|
||||||
|
n = netipx.PrefixIPNet(pfx)
|
||||||
|
c.BaseSplitter.SetParent(*n)
|
||||||
|
|
||||||
|
if pfxs, _, err = c.Split(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrZones = make([]string, len(pfxs))
|
||||||
|
for idx = range pfxs {
|
||||||
|
ptrZones[idx] = pfxZonePtr(*pfxs[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PtrToAddr returns a [net/netip.Addr] from a PTR record name.
|
||||||
|
func PtrToAddr(ptr string) (addr netip.Addr, err error) {
|
||||||
|
|
||||||
|
var pfx netip.Prefix
|
||||||
|
|
||||||
|
if pfx, err = PtrToPfx(ptr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr = pfx.Addr()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PtrToPfx returns a [net/netip.Prefix] from a PTR zone (or record).
|
||||||
|
|
||||||
|
The same boundary rules as [PfxToPtr] are in place; delegated subzones (RFC 2317) are not supported.
|
||||||
|
If an invalid PTR is received, err will be [ErrInvalidPtr].
|
||||||
|
*/
|
||||||
|
func PtrToPfx(ptr string) (pfx netip.Prefix, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var ok bool
|
||||||
|
var b []byte
|
||||||
|
var sfx string
|
||||||
|
var cidr uint8
|
||||||
|
var spl []string
|
||||||
|
var addr netip.Addr
|
||||||
|
|
||||||
|
ptr = normalizePtr(ptr)
|
||||||
|
spl = strings.Split(ptr, ".")
|
||||||
|
if len(spl) < 2 {
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sfx = strings.Join(spl[len(spl)-2:], ".")
|
||||||
|
spl = strings.Split(normalizePtr(strings.TrimSuffix(ptr, sfx)), ".")
|
||||||
|
|
||||||
|
slices.Reverse(spl)
|
||||||
|
|
||||||
|
switch sfx {
|
||||||
|
case Zone4:
|
||||||
|
switch len(spl) {
|
||||||
|
case 0:
|
||||||
|
cidr = 0
|
||||||
|
spl = []string{"0", "0", "0", "0"}
|
||||||
|
case 1, 2, 3, 4:
|
||||||
|
cidr = uint8(len(spl) * 8)
|
||||||
|
for len(spl) < 4 {
|
||||||
|
spl = append(spl, "0")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addr, err = netip.ParseAddr(
|
||||||
|
strings.Join(spl, "."),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case Zone6:
|
||||||
|
if len(spl) > 32 {
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(spl) == 0 {
|
||||||
|
cidr = 0
|
||||||
|
spl = make([]string, 32)
|
||||||
|
for idx = 0; idx < len(spl); idx++ {
|
||||||
|
spl[idx] = "0"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cidr = uint8(len(spl) * 4)
|
||||||
|
for len(spl) < 32 {
|
||||||
|
spl = append(spl, "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b, err = hex.DecodeString(
|
||||||
|
strings.ToLower(strings.Join(spl, "")),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addr, ok = netip.AddrFromSlice(b); !ok {
|
||||||
|
err = ErrInvalidPfx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pfx, err = addr.Prefix(int(cidr)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePtr(ptr string) (norm string) { return strings.ToLower(strings.TrimRight(ptr, ".")) }
|
||||||
|
|
||||||
|
func pfxZonePtr(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
if pfx.Addr().Is4() {
|
||||||
|
ptr = pfxZonePtr4(pfx)
|
||||||
|
} else {
|
||||||
|
ptr = pfxZonePtr6(pfx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pfxZonePtr4(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var b []byte
|
||||||
|
var s []string
|
||||||
|
|
||||||
|
idx = pfx.Bits() / 8
|
||||||
|
|
||||||
|
b = pfx.Addr().AsSlice()[0:idx]
|
||||||
|
s = make([]string, len(b))
|
||||||
|
|
||||||
|
for i := 0; i < idx; i++ {
|
||||||
|
s[idx-1-i] = strconv.Itoa(int(uint8(b[i])))
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = strings.Join(s, ".") + "." + Zone4
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pfxZonePtr6(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
var b byte
|
||||||
|
var nib byte
|
||||||
|
var numNibs int
|
||||||
|
var b16 [16]byte
|
||||||
|
var sb *strings.Builder = new(strings.Builder)
|
||||||
|
|
||||||
|
numNibs = pfx.Bits() / 4
|
||||||
|
b16 = pfx.Addr().As16()
|
||||||
|
|
||||||
|
for i := numNibs - 1; i >= 0; i-- {
|
||||||
|
b = b16[i/2]
|
||||||
|
if i%2 == 0 {
|
||||||
|
nib = b >> 4
|
||||||
|
} else {
|
||||||
|
nib = b & 0x0f
|
||||||
|
}
|
||||||
|
fmt.Fprintf(
|
||||||
|
sb,
|
||||||
|
"%x.",
|
||||||
|
nib,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sb.WriteString(Zone6)
|
||||||
|
|
||||||
|
ptr = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -636,7 +636,7 @@ pre.rouge .gs {
|
|||||||
<div class="details">
|
<div class="details">
|
||||||
<span id="author" class="author">Brent Saner</span><br>
|
<span id="author" class="author">Brent Saner</span><br>
|
||||||
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
|
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
|
||||||
<span id="revdate">Last rendered 2026-09-04 04:15:33 -0400</span>
|
<span id="revdate">Last rendered 2026-09-11 22:12:00 -0400</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="toc" class="toc2">
|
<div id="toc" class="toc2">
|
||||||
<div id="toctitle">Table of Contents</div>
|
<div id="toctitle">Table of Contents</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<span id="author" class="author">Brent Saner</span>\
|
<span id="author" class="author">Brent Saner</span>\
|
||||||
<span id="email" class="email"><bts@square-r00t.net></span>\
|
<span id="email" class="email"><bts@square-r00t.net></span>\
|
||||||
<span id="revdate">Last rendered 2026-09-04 04:15:38 -0400</span>
|
<span id="revdate">Last rendered 2026-09-11 22:12:05 -0400</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user