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") }