package main import ( "flag" "fmt" "net" "os" "strconv" "strings" ) var ( txt = flag.Bool("txt", false, "also look up TXT records") asn = flag.Bool("asn", false, "also look up ASN info from -asn-zone{,4,6} reverse zones") asnZone = flag.String("asn-zone", "asn.cymru.com", "ASN info zone for -asn") asnZone4 = flag.String("asn-zone4", "origin.asn.cymru.com", "v4 reverse zone for -asn") asnZone6 = flag.String("asn-zone6", "origin6.asn.cymru.com", "v6 reverse zone for -asn") ) 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 { if *asn { originInfos, err := lookupOriginInfo(ipnet) if err != nil { printErr(ipnet, "origin", err) } else { for _, originInfo := range originInfos { parts := strings.SplitN(originInfo, " | ", 2) if asn, err := strconv.Atoi(parts[0]); err == nil { asnNames, err := lookupAsnNames(asn) if err != nil { printErr(asn, "asn", err) } else if len(asnNames) > 0 { parts[0] += " | " + strings.Join(asnNames, "; ") } } originInfo = strings.Join(parts, " | ") fmt.Printf("# %s\n", originInfo) } } } 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(what interface{}, 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", what, 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 { if ip4 := ip.To4(); ip4 != nil { ip = ip4 } 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") } func lookupOriginInfo(ipnet net.IPNet) ([]string, error) { ones, bits := ipnet.Mask.Size() if bits == 0 { return nil, fmt.Errorf("invalid netmask in %s", ipnet) } var name string if ip4 := ipnet.IP.To4(); ip4 != nil { for b := (ones-1)/8; b >= 0; b-- { name += fmt.Sprintf("%d.", ip4[b]) } name += *asnZone4 } else { ip6 := ipnet.IP for n := (ones-1)/4; n >= 0; n-- { name += fmt.Sprintf("%x.", (ip6[n/2] >> (4-4*(n%2))) & 0xf) } name += *asnZone6 } return net.LookupTXT(name) } func lookupAsnNames(asn int) ([]string, error) { recs, err := net.LookupTXT(fmt.Sprintf("AS%d.%s", asn, *asnZone)) if err != nil { return nil, err } var names []string for _, rec := range recs { parts := strings.Split(rec, " | ") if len(parts) != 5 { return nil, fmt.Errorf("unknown asn info format for %d: %s", asn, rec) } names = append(names, parts[4]) } return names, nil }