package main
import (
"flag"
"fmt"
"net"
"os"
"strings"
)
var (
txt = flag.Bool("txt", false, "also look up TXT records")
)
func main() {
flag.Parse()
for _, arg := range flag.Args() {
if ipnets, err := resolveArg(arg); err != nil {
fmt.Fprintf(os.Stderr, "# couldn't resolve %s: %s\n", arg, err)
} else {
fmt.Printf("# %s\n", arg)
for _, ipnet := range ipnets {
ip := ipnet.IP
for ipnet.Contains(ip) {
if names, err := net.LookupAddr(ip.String()); err != nil {
printErr(ip, "PTR", err)
} else {
fmt.Printf("%s\t%s\n", ip, strings.Join(names, " "))
}
if *txt {
recs, err := net.LookupTXT(arpa(ip))
if err != nil {
printErr(ip, "TXT", err)
} else {
for _, rec := range recs {
fmt.Printf("%s\tTXT %q\n", ip, rec)
}
}
}
ip = next(ip)
}
}
}
}
}
func printErr(ip net.IP, rrtype string, err error) {
// the error types here are awful and this feels like it might
// accidentally catch some non-NXDOMAIN errors
if !strings.HasSuffix(err.Error(), "no such host") &&
!strings.HasSuffix(err.Error(), "nodename nor servname provided, or not known") {
fmt.Fprintf(os.Stderr, "%s\t# %s error: %s\n", ip, rrtype, err)
}
}
// arpa returns the .in-addr.arpa or .ip6.arpa name corresponding with the given IP.
func arpa(ip net.IP) string {
if ip4 := ip.To4(); ip4 != nil {
return fmt.Sprintf(
"%d.%d.%d.%d.in-addr.arpa",
ip4[3], ip4[2], ip4[1], ip4[0],
)
} else if ip6 := ip.To16(); ip6 != nil {
return fmt.Sprintf(
"%x.%x.%x.%x.%x.%x.%x.%x."+
"%x.%x.%x.%x.%x.%x.%x.%x."+
"%x.%x.%x.%x.%x.%x.%x.%x."+
"%x.%x.%x.%x.%x.%x.%x.%x."+
"ip6.arpa",
ip6[15]&0xf, ip6[15]&0xf0>>4,
ip6[14]&0xf, ip6[14]&0xf0>>4,
ip6[13]&0xf, ip6[13]&0xf0>>4,
ip6[12]&0xf, ip6[12]&0xf0>>4,
ip6[11]&0xf, ip6[11]&0xf0>>4,
ip6[10]&0xf, ip6[10]&0xf0>>4,
ip6[9]&0xf, ip6[9]&0xf0>>4,
ip6[8]&0xf, ip6[8]&0xf0>>4,
ip6[7]&0xf, ip6[7]&0xf0>>4,
ip6[6]&0xf, ip6[6]&0xf0>>4,
ip6[5]&0xf, ip6[5]&0xf0>>4,
ip6[4]&0xf, ip6[4]&0xf0>>4,
ip6[3]&0xf, ip6[3]&0xf0>>4,
ip6[2]&0xf, ip6[2]&0xf0>>4,
ip6[1]&0xf, ip6[1]&0xf0>>4,
ip6[0]&0xf, ip6[0]&0xf0>>4,
)
} else {
panic("non-ip4 non-ip6 ip? " + ip.String())
}
}
// next returns the next IP address after ip, or nil if there is no such address.
func next(ip net.IP) net.IP {
next := make(net.IP, len(ip))
copy(next, ip)
for i := len(next) - 1; i >= 0; i-- {
next[i]++
if next[i] != 0 {
return next
}
}
return nil
}
// resolveArg parses an argument of one of the following forms:
// * 1.2.3.4
// * 1.2.3.4/24
// * 2001::
// * 2001::/16
// * hostname.example.com
// If the given address is a hostname, it is resolved to all of its A and AAAA records.
// It returns the specified networks as a slice of net.IPNet.
func resolveArg(arg string) ([]net.IPNet, error) {
parts := strings.SplitN(arg, "/", 2)
ips, err := resolveHost(parts[0], len(parts) == 1)
if err != nil {
return nil, err
}
ipnets := make([]net.IPNet, len(ips))
for i, ip := range ips {
if len(parts) > 1 {
if _, ipnet, err := net.ParseCIDR(ip.String() + "/" + parts[1]); err != nil {
return nil, err
} else {
ipnets[i] = *ipnet
}
} else {
ipnets[i].IP = ip
ipnets[i].Mask = net.CIDRMask(len(ip)*8, len(ip)*8)
}
}
return ipnets, nil
}
func resolveHost(host string, canLookup bool) ([]net.IP, error) {
ip := net.ParseIP(host)
if ip != nil {
return []net.IP{ip}, nil
}
if canLookup {
return net.LookupIP(host)
}
return nil, fmt.Errorf("can't parse IP")
}